From f01b61c4c99ff8956ccfefabf14872edaf1ec280 Mon Sep 17 00:00:00 2001 From: mdipierro Date: Thu, 19 Jul 2012 16:25:22 -0500 Subject: [PATCH] better export in grid, thanks Niphlod --- VERSION | 2 +- gluon/sqlhtml.py | 192 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 135 insertions(+), 59 deletions(-) diff --git a/VERSION b/VERSION index e8d56c57..9fbcad2f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.00.0 (2012-07-18 21:46:56) dev +Version 2.00.0 (2012-07-19 16:25:16) dev diff --git a/gluon/sqlhtml.py b/gluon/sqlhtml.py index dc5aad38..0c7cae23 100644 --- a/gluon/sqlhtml.py +++ b/gluon/sqlhtml.py @@ -13,13 +13,17 @@ Holds: - form_factory: provides a SQLFORM for an non-db backed table """ - +try: + from urlparse import parse_qs as psq +except ImportError: + from cgi import parse_qs as psq from http import HTTP -from html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR -from html import FORM, INPUT, LABEL, OPTION, SELECT, BUTTON, IMG -from html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT +from html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT +from html import FORM, INPUT, LABEL, OPTION, SELECT, BUTTON +from html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE from html import URL, truncate_string -from dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query +from dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query, \ + bar_encode, regex_table_field, Reference from storage import Storage from utils import md5_hash from validators import IS_EMPTY_OR, IS_NOT_EMPTY, IS_LIST_OF, IS_DATE, \ @@ -1694,32 +1698,34 @@ class SQLFORM(FORM): ondelete(table,request.args[-1]) ret = db(table[table._id.name]==request.args[-1]).delete() return ret - elif csv and len(request.args)>0 and request.args[-1]=='csv': - if request.vars.keywords: - try: - dbset=dbset(SQLFORM.build_query( - fields, - request.vars.get('keywords',''))) - except: - raise HTTP(400) - check_authorization() - response.headers['Content-Type'] = 'text/csv' - response.headers['Content-Disposition'] = \ - 'attachment;filename=rows.csv;' - raise HTTP(200,str(dbset.select()), - **{'Content-Type':'text/csv', - 'Content-Disposition':'attachment;filename=rows.csv;'}) - + + #elif csv and len(request.args)>0 and request.args[-1]=='csv': + # if request.vars.keywords: + # try: + # dbset=dbset(SQLFORM.build_query( + # fields, + # request.vars.get('keywords',''))) + # except: + # raise HTTP(400) + # check_authorization() + # response.headers['Content-Type'] = 'text/csv' + # response.headers['Content-Disposition'] = \ + # 'attachment;filename=rows.csv;' + # raise HTTP(200,str(dbset.select()), + # **{'Content-Type':'text/csv', + # 'Content-Disposition':'attachment;filename=rows.csv;'}) + #============================================================================== - - exportManager = dict(csv_with_hidden_cols=(ExporterCsv,'csv with hidden cols'), + + exportManager = dict(csv_with_hidden_cols=(ExporterCsv,'csv, hidden cols'), csv=ExporterCsv, html=ExporterHtml, - tsv_with_hidden_cols=(ExporterTsv, 'tsv with hidden cols'), - tsv=ExporterTsv) + tsv_with_hidden_cols=(ExporterTsv, 'tsv (Excel compatible), hidden cols'), + tsv=(ExporterTsv, 'tsv (Excel compatible)') + ) if not exportclasses is None: exportManager.update(exportclasses) - + if len(request.args)>0 and request.args[-1]=='export': export_type = request.vars.export_type order = request.vars.order or '' @@ -1734,7 +1740,7 @@ class SQLFORM(FORM): orderby=~db[tablename][fieldname] else: orderby=db[tablename][fieldname] - + table_fields = [f for f in fields if f._tablename in tablenames] if export_type in ('csv_with_hidden_cols','tsv_with_hidden_cols'): if request.vars.keywords: @@ -1744,11 +1750,15 @@ class SQLFORM(FORM): request.vars.get('keywords',''))) except: raise HTTP(400) - check_authorization() rows = dbset.select() else: rows = dbset.select(left=left,orderby=orderby,*columns) - + ##FIXME ? + #check_authorization() + if user_signature: + if not URL.verify(request,user_signature=user_signature,hash_vars=False): + session.flash = T('not authorized') + redirect(referrer) if not export_type is None: if exportManager.has_key(export_type): value = exportManager[export_type] @@ -1761,12 +1771,12 @@ class SQLFORM(FORM): response.headers['Content-Type'] = oExp.content_type response.headers['Content-Disposition'] = \ 'attachment;filename='+filename+';' - + raise HTTP(200, oExp.export(), **{'Content-Type':oExp.content_type, 'Content-Disposition':'attachment;filename='+filename+';'}) #================================================================================ - + elif request.vars.records and not isinstance( request.vars.records,list): request.vars.records=[request.vars.records] @@ -1792,7 +1802,7 @@ class SQLFORM(FORM): INPUT(_type='submit',_value=T('Search')), INPUT(_type='submit',_value=T('Clear'), _onclick="jQuery('#web2py_keywords').val('');"), - mf,ms,_method="GET",_action=url) + mf,ms,_method="GET",_action=url) form = search_widget and search_widget(sfields,url()) or '' console.append(form) keywords = request.vars.get('keywords','') @@ -1825,13 +1835,13 @@ class SQLFORM(FORM): buttontext='Add', buttonurl=url(args=['new',tablename]))) if csv and nrows: - search_actions.append(gridbutton( - buttonclass='buttonexport', - buttontext='Export', - trap = False, - buttonurl=url(args=['csv'], - vars=dict(keywords=request.vars.keywords or '')))) - + #search_actions.append(gridbutton( + # buttonclass='buttonexport', + # buttontext='Export', + # trap = False, + # buttonurl=url(args=['csv'], + # vars=dict(keywords=request.vars.keywords or '')))) + #================================================================ options =[] for k,v in sorted(exportManager.items()): @@ -1840,11 +1850,16 @@ class SQLFORM(FORM): else: label = k options.append(OPTION(T(label),_value=k)) - f = FORM(BUTTON(SPAN(_class="icon downarrow icon-download"), - "Export", _type="submit", _class="btn button"), - SELECT(options, _name="export_type"), + ##FIXME ? + mysignature = url(args=['export']).split('?', 1) + mysignature = psq(mysignature[-1]).get('_signature', [None])[-1] + f = FORM(BUTTON(SPAN(_class=ui.get('buttonexport')), + "Export", _type="submit", _class=ui.get('button')), + SELECT(options, _name="export_type"), INPUT(_type="hidden", _name="order", _value=request.vars.order), + INPUT(_type="hidden", _name="_signature", + _value=mysignature), INPUT(_type="hidden", _name="keywords", _value=request.vars.keywords or ''), _method="GET", _action=url(args=['export'])) @@ -2148,21 +2163,21 @@ class SQLFORM(FORM): check = {} id_field_name = table._id.name for tablename,fieldname in table._referenced_by: - if db[tablename][fieldname].readable: + if db[tablename][fieldname].readable: check[tablename] = check.get(tablename,[])+[fieldname] for tablename in sorted(check): linked_fieldnames = check[tablename] tb = db[tablename] multiple_links = len(linked_fieldnames)>1 - for fieldname in linked_fieldnames: - if linked_tables is None or tablename in linked_tables: + for fieldname in linked_fieldnames: + if linked_tables is None or tablename in linked_tables: t = T(tb._plural) if not multiple_links else T(tb._plural+'('+fieldname+')') args0 = tablename+'.'+fieldname links.append( lambda row,t=t,nargs=nargs,args0=args0:\ A(SPAN(t),_class=trap_class(),_href=URL( args=request.args[:nargs]+[args0,row[id_field_name]]))) - + grid=SQLFORM.grid(query,args=request.args[:nargs],links=links, links_in_grid=links_in_grid, user_signature=user_signature,**kwargs) @@ -2461,43 +2476,104 @@ form_factory = SQLFORM.factory # for backward compatibility, deprecated class ExportClass(object): file_ext = None content_type = None - + def __init__(self, rows): self.rows = rows - + + def represented(self): + def none_exception(value): + """ + returns a cleaned up value that can be used for csv export: + - unicode text is encoded as such + - None values are replaced with the given representation (default ) + """ + if value is None: + return '' + elif isinstance(value, unicode): + return value.encode('utf8') + elif isinstance(value,Reference): + return int(value) + elif hasattr(value, 'isoformat'): + return value.isoformat()[:19].replace('T', ' ') + elif isinstance(value, (list,tuple)): # for type='list:..' + return bar_encode(value) + return value + + represented = [] + for record in self.rows: + row = [] + for col in self.rows.colnames: + if not regex_table_field.match(col): + row.append(record._extra[col]) + else: + (t, f) = col.split('.') + field = self.rows.db[t][f] + if isinstance(record.get(t, None), (Row,dict)): + value = record[t][f] + else: + value = record[f] + if field.type=='blob' and not value is None: + value = '' + elif field.represent: + value = field.represent(value, record) + row.append(none_exception(value)) + + represented.append(row) + return represented + def export(self): raise NotImplementedError class ExporterTsv(ExportClass): - file_ext = "tsv" + + file_ext = "csv" content_type = "text/tab-separated-values" - + def __init__(self, rows): ExportClass.__init__(self, rows) - + def export(self): + out = cStringIO.StringIO() - self.rows.export_to_csv_file(out, delimiter='\t', represent=True) - return str(out.getvalue()) - + final = cStringIO.StringIO() + import csv + writer = csv.writer(out, delimiter='\t') + import codecs + final.write(codecs.BOM_UTF16) + colnames = [a.split('.') for a in self.rows.colnames] + writer.writerow([unicode(col).encode("utf8") for col in self.rows.colnames]) + data = out.getvalue().decode("utf8") + data = data.encode("utf-16") + data = data[2:] + final.write(data) + out.truncate(0) + records = self.represented() + for row in records: + writer.writerow([str(col).decode('utf8').encode("utf-8") for col in row]) + data = out.getvalue().decode("utf8") + data = data.encode("utf-16") + data = data[2:] + final.write(data) + out.truncate(0) + return str(final.getvalue()) class ExporterCsv(ExportClass): file_ext = "csv" content_type = "text/csv" - + def __init__(self, rows): ExportClass.__init__(self, rows) - + def export(self): return str(self.rows) class ExporterHtml(ExportClass): file_ext = "html" content_type = "text/html" - + def __init__(self, rows): ExportClass.__init__(self, rows) - + def export(self): out = cStringIO.StringIO() out.write('\n\n\n')