Roundup Tracker

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


CategoryActions