Version 0.4: Added action to export selected records
[auf_django_export.git] / auf / django / export / admin.py
CommitLineData
76df4659
OL
1# -*- encoding: utf-8 -*-
2
3import csv
12c6d07b
PP
4from StringIO import StringIO
5
6from odf.opendocument import OpenDocumentSpreadsheet
7from odf.style import Style, TextProperties, TableColumnProperties
8from odf.text import P
9from odf.table import Table, TableRow, TableCell
10
76df4659
OL
11from django.core.urlresolvers import reverse
12from django.contrib import admin
13from django.utils.functional import update_wrapper
12c6d07b 14from django.http import HttpResponse
76df4659
OL
15
16# Create a notation similar to queryset filter
17SEPARATOR = u"__"
18
19# String return if lookup property failed
20EMPTY = u""
21
12c6d07b
PP
22
23def 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
33class 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