Commit | Line | Data |
---|---|---|
76df4659 OL |
1 | # -*- encoding: utf-8 -*- |
2 | ||
3 | import csv | |
12c6d07b PP |
4 | from StringIO import StringIO |
5 | ||
6 | from odf.opendocument import OpenDocumentSpreadsheet | |
7 | from odf.style import Style, TextProperties, TableColumnProperties | |
8 | from odf.text import P | |
9 | from odf.table import Table, TableRow, TableCell | |
10 | ||
76df4659 OL |
11 | from django.core.urlresolvers import reverse |
12 | from django.contrib import admin | |
13 | from django.utils.functional import update_wrapper | |
12c6d07b | 14 | from django.http import HttpResponse |
76df4659 OL |
15 | |
16 | # Create a notation similar to queryset filter | |
17 | SEPARATOR = u"__" | |
18 | ||
19 | # String return if lookup property failed | |
20 | EMPTY = u"" | |
21 | ||
12c6d07b PP |
22 | |
23 | def txt(msg): | |
24 | text = msg | |
25 | if not isinstance(msg, unicode): | |
26 | try: | |
27 | text = unicode (msg, "utf-8") | |
28 | except TypeError: | |
29 | pass | |
30 | return text | |
31 | ||
32 | ||
76df4659 OL |
33 | class ExportAdmin(admin.ModelAdmin): |
34 | change_list_template = 'admin/export/change_list.html' | |
5bf5c706 | 35 | actions = ['export_csv', 'export_ods'] |
76df4659 OL |
36 | |
37 | def changelist_view(self, request, extra_context=None): | |
38 | """ | |
39 | Add export URL for the template | |
40 | """ | |
41 | if extra_context is None: | |
42 | extra_context = {} | |
43 | extra_context.update({ | |
12c6d07b PP |
44 | 'export_csv_url' : reverse("admin:%s_%s_export_csv" % (self.model._meta.app_label, self.model._meta.module_name)), |
45 | 'export_ods_url' : reverse("admin:%s_%s_export_ods" % (self.model._meta.app_label, self.model._meta.module_name)), | |
76df4659 | 46 | }) |
76df4659 OL |
47 | return super(ExportAdmin, self).changelist_view(request, extra_context) |
48 | ||
49 | ||
50 | def get_urls(self): | |
51 | from django.conf.urls.defaults import patterns, url | |
52 | ||
12c6d07b | 53 | urls = super(ExportAdmin, self).get_urls() |
76df4659 OL |
54 | info = self.model._meta.app_label, self.model._meta.module_name |
55 | ||
56 | urlpatterns = patterns('', | |
12c6d07b PP |
57 | url(r'^export-csv/$', |
58 | self.admin_site.admin_view(self.export_csv), | |
59 | name='%s_%s_export_csv' % info), | |
60 | url(r'^export-ods/$', | |
61 | self.admin_site.admin_view(self.export_ods), | |
62 | name='%s_%s_export_ods' % info), | |
76df4659 | 63 | ) |
12c6d07b | 64 | return urlpatterns + urls |
76df4659 OL |
65 | |
66 | def get_object_value(self, obj, header): | |
67 | """ | |
68 | Function which work as a queryset filter access : | |
69 | Father -- Kid (One2One father related_name = 'child') | |
70 | father__name | |
71 | father__child__name | |
72 | It tries to resolve the property from header string, through | |
73 | linked object if needed. If it can't load any value, empty | |
74 | string is return. | |
75 | """ | |
76 | segments = header.split(SEPARATOR) | |
77 | segments.reverse() | |
78 | prop = segments.pop() | |
79 | if len(segments) == 0: | |
80 | return getattr(obj, prop, EMPTY) | |
81 | else: | |
82 | try: | |
83 | child = getattr(obj, prop, None) | |
84 | return self.get_object_value(child, ".".join(segments)) | |
85 | except: | |
86 | return EMPTY | |
87 | ||
12c6d07b | 88 | def get_default_export_fields(self): |
76df4659 OL |
89 | fields_name = [f.name for f in self.model._meta.fields] |
90 | for fk in self.model._meta._related_objects_cache.keys(): | |
91 | fields_name = fields_name + [u"%s%s%s" % (fk.var_name, SEPARATOR, f.name) for f in fk.model._meta.fields] | |
92 | return fields_name | |
93 | ||
12c6d07b PP |
94 | |
95 | def get_export_fields(self): | |
76df4659 | 96 | """ |
76df4659 OL |
97 | This property is a list of string, which should have queryset filter notation style (with X2 underscore). |
98 | If there is no fields, the models is full introspected, stop at 1 depth level. | |
99 | """ | |
76df4659 | 100 | |
12c6d07b PP |
101 | export_fields = getattr(self, 'export_fields', None) |
102 | ||
103 | if export_fields is None: | |
104 | export_fields = getattr(self, 'csv_fields', None) | |
105 | ||
106 | if export_fields is None: | |
107 | export_fields = self.get_default_export_fields() | |
108 | ||
109 | return export_fields | |
110 | ||
5bf5c706 | 111 | def export_ods(self, request, queryset=None): |
12c6d07b PP |
112 | """ |
113 | Generate HTTP response as ODS file from export_fields property. | |
114 | """ | |
115 | export_fields = self.get_export_fields() | |
116 | ||
5bf5c706 PP |
117 | if queryset is None: |
118 | queryset = self.queryset(request) | |
12c6d07b PP |
119 | |
120 | response = HttpResponse(mimetype='application/vnd.oasis.opendocument.spreadsheet') | |
121 | response['Content-Disposition'] = 'attachment; filename=%s.ods' % unicode(self.model._meta.verbose_name_plural) | |
122 | ||
123 | doc = OpenDocumentSpreadsheet() | |
124 | style = Style(name="Large number", family="table-cell") | |
125 | style.addElement(TextProperties(fontfamily="Arial", fontsize="15pt")) | |
126 | doc.styles.addElement(style) | |
127 | widewidth = Style(name="co1", family="table-column") | |
128 | widewidth.addElement(TableColumnProperties(columnwidth="2.8cm", breakbefore="auto")) | |
129 | doc.automaticstyles.addElement(widewidth) | |
130 | ||
131 | table = Table() | |
132 | if len (export_fields) > 0: | |
133 | tr = TableRow () | |
134 | table.addElement (tr) | |
135 | for item in export_fields: | |
136 | tc = TableCell () | |
137 | tr.addElement (tc) | |
138 | p = P(stylename = style, text = txt(item)) | |
139 | tc.addElement (p) | |
140 | ||
5bf5c706 | 141 | for o in queryset: |
12c6d07b PP |
142 | tr = TableRow () |
143 | table.addElement (tr) | |
144 | ||
145 | for attr in export_fields: | |
146 | value = self.get_object_value(o, attr) | |
147 | tc = TableCell () | |
148 | tr.addElement (tc) | |
149 | p = P (stylename = style, text = txt(value)) | |
150 | tc.addElement (p) | |
151 | ||
152 | doc.spreadsheet.addElement(table) | |
153 | buffer = StringIO () | |
154 | doc.write(buffer) | |
155 | ||
156 | response.write(buffer.getvalue()) | |
157 | return response | |
158 | ||
5bf5c706 | 159 | def export_csv(self, request, queryset=None): |
12c6d07b PP |
160 | """ |
161 | Generate HTTP response as CSV file from export_fields property. | |
162 | """ | |
163 | csv_fields = self.get_export_fields() | |
76df4659 | 164 | |
5bf5c706 PP |
165 | if queryset is None: |
166 | queryset = self.queryset(request) | |
76df4659 OL |
167 | |
168 | response = HttpResponse(mimetype='text/csv') | |
169 | response['Content-Disposition'] = 'attachment; filename=%s.csv' % unicode(self.model._meta.verbose_name_plural) | |
170 | ||
171 | writer = csv.writer(response) | |
172 | ||
173 | headers = [] | |
174 | for attr in csv_fields: | |
80b64ea2 | 175 | headers.append(attr.encode('utf-8') if isinstance(attr, unicode) else attr) |
76df4659 OL |
176 | writer.writerow(headers) |
177 | ||
5bf5c706 | 178 | for o in queryset: |
76df4659 OL |
179 | row = [] |
180 | for attr in csv_fields: | |
181 | value = self.get_object_value(o, attr) | |
80b64ea2 | 182 | row.append(value.encode('utf-8') if isinstance(value, unicode) else value) |
76df4659 OL |
183 | writer.writerow(row) |
184 | ||
185 | return response |