Version 0.4: Added action to export selected records
[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 actions = ['export_csv', 'export_ods']
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({
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)),
46 })
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
53 urls = super(ExportAdmin, self).get_urls()
54 info = self.model._meta.app_label, self.model._meta.module_name
55
56 urlpatterns = patterns('',
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),
63 )
64 return urlpatterns + urls
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
88 def get_default_export_fields(self):
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
94
95 def get_export_fields(self):
96 """
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 """
100
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
111 def export_ods(self, request, queryset=None):
112 """
113 Generate HTTP response as ODS file from export_fields property.
114 """
115 export_fields = self.get_export_fields()
116
117 if queryset is None:
118 queryset = self.queryset(request)
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
141 for o in queryset:
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
159 def export_csv(self, request, queryset=None):
160 """
161 Generate HTTP response as CSV file from export_fields property.
162 """
163 csv_fields = self.get_export_fields()
164
165 if queryset is None:
166 queryset = self.queryset(request)
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:
175 headers.append(attr.encode('utf-8') if isinstance(attr, unicode) else attr)
176 writer.writerow(headers)
177
178 for o in queryset:
179 row = []
180 for attr in csv_fields:
181 value = self.get_object_value(o, attr)
182 row.append(value.encode('utf-8') if isinstance(value, unicode) else value)
183 writer.writerow(row)
184
185 return response