This has the same functionality as the default csv export in roundup 2.0 and newer. So it is not needed if you are running 2.0 or newer.
Also this patch was changed to add: quoting=csv.QUOTE_NONNUMERIC to the csv.writer invocation to quote all lines to impove security if the csv is imported into some versions of Excel.
This extension listed below needs to be placed in the "extensions" folder. It changes the standard behavior of the "Download as CSV" export where the ids are resolved by the textual representation of the objects and not their ids.
The resolution is done also on list and multilink objects. For user object the "realname". is used.
This extension has been tested against roundup version 1.50.
ExportCSVNamesAction.py:
1 from roundup.cgi.actions import Action
2 from roundup.cgi import templating
3 from roundup import hyperdb
4
5 import csv
6 import re
7 import codecs
8
9
10 class ExportCSVNamesAction(Action):
11 name = 'export'
12 permissionType = 'View'
13 list_sep = ';'
14
15 def handle(self):
16 ''' Export the specified search query as CSV. '''
17 # figure the request
18 request = templating.HTMLRequest(self.client)
19 filterspec = request.filterspec
20 sort = request.sort
21 group = request.group
22 columns = request.columns
23 klass = self.db.getclass(request.classname)
24
25 # full-text search
26 if request.search_text:
27 matches = self.db.indexer.search(
28 re.findall(r'\b\w{2,25}\b', request.search_text), klass)
29 else:
30 matches = None
31
32 header = self.client.additional_headers
33 header['Content-Type'] = 'text/csv; charset=%s' % self.client.charset
34 # some browsers will honor the filename here...
35 header['Content-Disposition'] = 'inline; filename=query.csv'
36
37 self.client.header()
38
39 if self.client.env['REQUEST_METHOD'] == 'HEAD':
40 # all done, return a dummy string
41 return 'dummy'
42 wfile = self.client.request.wfile
43 if self.client.charset != self.client.STORAGE_CHARSET:
44 wfile = codecs.EncodedFile(wfile,
45 self.client.STORAGE_CHARSET, self.client.charset, 'replace')
46 writer = csv.writer(wfile, quoting=csv.QUOTE_NONNUMERIC)
47
48 # handle different types of columns.
49 def repr_no_right(cls, col):
50 """User doesn't have the right to see the value of col."""
51 def fct(arg):
52 return "[hidden]"
53 return fct
54 def repr_link(cls, col):
55 """Generate a function which returns the string representation of
56 a link depending on `cls` and `col`."""
57 def fct(arg):
58 if arg == None:
59 return ""
60 else:
61 return str(cls.get(arg, col))
62 return fct
63 def repr_list(cls, col):
64 def fct(arg):
65 if arg == None:
66 return ""
67 elif type(arg) is list:
68 seq = [str(cls.get(val, col)) for val in arg]
69 return self.list_sep.join(seq)
70 return fct
71 def repr_date():
72 def fct(arg):
73 if arg == None:
74 return ""
75 else:
76 if (arg.local(self.db.getUserTimezone()).pretty('%H:%M') ==
77 '00:00'):
78 fmt = '%Y-%m-%d'
79 else:
80 fmt = '%Y-%m-%d %H:%M'
81 return arg.local(self.db.getUserTimezone()).pretty(fmt)
82 return fct
83 def repr_val():
84 def fct(arg):
85 if arg == None:
86 return ""
87 else:
88 return str(arg)
89 return fct
90
91 props = klass.getprops()
92
93 ncols = []
94 represent = {}
95 for col in columns:
96 ncols.append(col)
97 represent[col] = repr_val()
98 if isinstance(props[col], hyperdb.Multilink):
99 cname = props[col].classname
100 cclass = self.db.getclass(cname)
101 represent[col] = repr_list(cclass, 'name')
102 if not self.hasPermission(self.permissionType, classname=cname):
103 represent[col] = repr_no_right(cclass, 'name')
104 else:
105 if cclass.getprops().has_key('name'):
106 represent[col] = repr_list(cclass, 'name')
107 elif cname == 'user':
108 represent[col] = repr_list(cclass, 'realname')
109 if isinstance(props[col], hyperdb.Link):
110 cname = props[col].classname
111 cclass = self.db.getclass(cname)
112 if not self.hasPermission(self.permissionType, classname=cname):
113 represent[col] = repr_no_right(cclass, 'name')
114 else:
115 if cclass.getprops().has_key('name'):
116 represent[col] = repr_link(cclass, 'name')
117 elif cname == 'user':
118 represent[col] = repr_link(cclass, 'realname')
119 if isinstance(props[col], hyperdb.Date):
120 represent[col] = repr_date()
121
122 columns = ncols
123 # generate the CSV output
124 self.client._socket_op(writer.writerow, columns)
125 # and search
126 for itemid in klass.filter(matches, filterspec, sort, group):
127 row = []
128 for col in columns:
129 # check permission to view this property on this item
130 # TODO: Permission filter doesn't work for the 'user' class
131 if not self.hasPermission(self.permissionType, itemid=itemid,
132 classname=request.classname, property=col):
133 represent[col] = repr_no_right(request.classname, col)
134 row.append(represent[col](klass.get(itemid, col)))
135 self.client._socket_op(writer.writerow, row)
136 return '\n'
137
138 def init(instance):
139 instance.registerAction('export_csv_names', ExportCSVNamesAction)
140
141 # vim: set filetype=python sts=4 sw=4 et si