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