diff -r 901d7ba146ad roundup/cgi/actions.py --- a/roundup/cgi/actions.py Thu Sep 27 11:38:05 2018 +0000 +++ b/roundup/cgi/actions.py Wed Feb 06 10:12:45 2019 -0800 @@ -1294,6 +1294,7 @@ class ExportCSVAction(Action): name = 'export' permissionType = 'View' + list_sep = ';' # Separator for list types def handle(self): ''' Export the specified search query as CSV. ''' @@ -1324,10 +1325,10 @@ else: matches = None - h = self.client.additional_headers - h['Content-Type'] = 'text/csv; charset=%s' % self.client.charset + header = self.client.additional_headers + header['Content-Type'] = 'text/csv; charset=%s' % self.client.charset # some browsers will honor the filename here... - h['Content-Disposition'] = 'inline; filename=query.csv' + header['Content-Disposition'] = 'inline; filename=query.csv' self.client.header() @@ -1341,21 +1342,96 @@ self.client.STORAGE_CHARSET, self.client.charset, 'replace') writer = csv.writer(wfile) + + # handle different types of columns. + def repr_no_right(cls, col): + """User doesn't have the right to see the value of col.""" + def fct(arg): + return "[hidden]" + return fct + def repr_link(cls, col): + """Generate a function which returns the string representation of + a link depending on `cls` and `col`.""" + def fct(arg): + if arg == None: + return "" + else: + return str(cls.get(arg, col)) + return fct + def repr_list(cls, col): + def fct(arg): + if arg == None: + return "" + elif type(arg) is list: + seq = [str(cls.get(val, col)) for val in arg] + return self.list_sep.join(seq) + return fct + def repr_date(): + def fct(arg): + if arg == None: + return "" + else: + if (arg.local(self.db.getUserTimezone()).pretty('%H:%M') == + '00:00'): + fmt = '%Y-%m-%d' + else: + fmt = '%Y-%m-%d %H:%M' + return arg.local(self.db.getUserTimezone()).pretty(fmt) + return fct + def repr_val(): + def fct(arg): + if arg == None: + return "" + else: + return str(arg) + return fct + + props = klass.getprops() + + # Determine translation map. + ncols = [] + represent = {} + for col in columns: + ncols.append(col) + represent[col] = repr_val() + if isinstance(props[col], hyperdb.Multilink): + cname = props[col].classname + cclass = self.db.getclass(cname) + represent[col] = repr_list(cclass, 'name') + if not self.hasPermission(self.permissionType, classname=cname): + represent[col] = repr_no_right(cclass, 'name') + else: + if 'name' in cclass.getprops(): + represent[col] = repr_list(cclass, 'name') + elif cname == 'user': + represent[col] = repr_list(cclass, 'realname') + if isinstance(props[col], hyperdb.Link): + cname = props[col].classname + cclass = self.db.getclass(cname) + if not self.hasPermission(self.permissionType, classname=cname): + represent[col] = repr_no_right(cclass, 'name') + else: + if 'name' in cclass.getprops(): + represent[col] = repr_link(cclass, 'name') + elif cname == 'user': + represent[col] = repr_link(cclass, 'realname') + if isinstance(props[col], hyperdb.Date): + represent[col] = repr_date() + + columns = ncols + # generate the CSV output self.client._socket_op(writer.writerow, columns) - # and search for itemid in klass.filter(matches, filterspec, sort, group): row = [] for name in columns: # check permission to view this property on this item + # TODO: Permission filter doesn't work for the 'user' class if not self.hasPermission('View', itemid=itemid, classname=request.classname, property=name): - raise exceptions.Unauthorised(self._( - 'You do not have permission to view %(class)s' - ) % {'class': request.classname}) - row.append(str(klass.get(itemid, name))) + represent[name] = repr_no_right(request.classname, name) + row.append(represent[name](klass.get(itemid, name))) self.client._socket_op(writer.writerow, row) - return '\n'