My approach to the problem was to implement a custom template filter you can use in a template like this (where object_list is your queryset):
{{ object_list|result_as_table }}i.e. you hand it a queryset, and it gives you back a table.
Here is the code for the filter. I'm using this for a number of subclasses of a class called resultdata, so that they can share an identical template and just provide different querysets in urls.py.
def result_as_table(queryset, fieldnames=None): """Take a resultdata queryset and return it as a HTML table. Columns will be returned in the order they are declared in the model. Optionally, provide a list of fieldnames, which will be used to limit the output.""" dictlist = queryset.values() output = "<table>\n" output_keys = [] if fieldnames: names = fieldnames else: names = dictlist.field_names for name in names: if not name.endswith("_id"): output_keys.append(name) output = "".join( (output, "<th>%s</th>\n" % escape(name.replace("_"," ").capitalize())) ) for rddict in dictlist: output = "".join( (output, "<tr>\n") ) for key in output_keys: val = rddict[key] if not key.endswith("_id"): display_func = get_display_method(rddict, queryset, key) if display_func: output = "".join( (output, "<td>%s</td>\n" % escape(display_func())) ) else: output = "".join( (output, "<td>%s</td>\n" % escape(val)) ) output = "".join( (output, "</tr>\n") ) return mark_safe("".join( (output, "</table>\n") )) result_as_table.is_safe = True
This filter introspects the column headers, strips out any id fields (since users don't care about them), converts underscores to spaces in column headers, and finds the corresponding display methods for any 'choices' fields so you can still get the nice human-readable output. It outputs HTML, so it uses django's own 'django.utils.html.escape' method and marks the output as safe so django doesn't escape all the HTML.
Finding the display methods was a little harder than I expected, since the python inspect code was dying when given a django model object. This code is unfortunately tied to the resultdata object due to the naming of the keys in the values() dictionary, making it less generic than I would like.
def get_display_method(rddict, queryset, key): """Re-implementation of inspect.getmembers(rddict,inspect.ismethod) and a test to see if this is a get_field_status method. Had to reimplement inspect because it was bombing on the object - expecting some property that is not present.""" rdobj = queryset[0].__class__.objects.filter(resultdata_id = rddict["resultdata_id"]).get() targetname = "get_%s_display" % key display_func = False for name in dir(rdobj): if name == targetname: try: display_func = getattr(rdobj, name) break except Exception,e: pass return display_func
There was a major gotcha with using the field_names property of the queryset.values() dictionary. If you passed a queryset with annotations like this is your urls.py:
list_rd = { 'extra_context': {'fieldnames': ['status','first','total']}, 'queryset': ResultData.objects.annotate(total = Count('link__clickback'), first = Min('link__clickback__timestamp')), } urlpatterns += patterns('project.app.views', url(r'^result/detail/$', 'detailed_results', kwargs=list_rd, name='detailed_results'), )
The annotations would not get added into the field_names property. This was solved by providing the optional fieldnames parameter.
4 comments:
Hi - this looks to be very useful while prototyping ... but when I try and use it, then I get this error:
KeyError at /query/site/
'resultdata_id'
at this line:
rdobj = queryset[0].__class__.objects.filter(resultdata_id=rddict["resultdata_id"]).get()
Please can you explain further about the use of the "resultdata" object, given that the template is receiving a queryset and not an object as such?
It's a queryset of resultdata objects. You'll need to change the code to operate on whatever object is in your queryset. Hence why I say 'This code is unfortunately tied to the resultdata object'.
On the assumption that my queryset would likely contain an id field, I replaced the filter part in the robj assignment with:
filter(id=rddict.get("id"))
and that seemed to work.
Thanks for this posting, it was helpful in aiding me to solve a similar problem.
--
Mick T.
Post a Comment