- Author:
- btaylordesign
- Posted:
- August 11, 2011
- Language:
- Python
- Version:
- 1.3
- Score:
- 0 (after 0 ratings)
Django's built-in {% regroup %} template tag is great, but sometimes, you need to pass in the attribute you want to group on instead of declaring the attribute when you define the tag.
{% dynamic_regroup %} is identical in function to {% regroup %}, except that it will attempt to resolve a context variable for the attribute you want to group by.
{% dynamic regroup %} is also backward compatible, so you can also hand in the attribute literal and it will work as expected.
See the end of the code for an example of usage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | from itertools import groupby
from django import template
from django.template import TemplateSyntaxError
class DynamicRegroupNode(template.Node):
def __init__(self, target, parser, expression, var_name):
self.target = target
self.expression = template.Variable(expression)
self.var_name = var_name
self.parser = parser
def render(self, context):
obj_list = self.target.resolve(context, True)
if obj_list == None:
# target variable wasn't found in context; fail silently.
context[self.var_name] = []
return ''
# List of dictionaries in the format:
# {'grouper': 'key', 'list': [list of contents]}.
"""
Try to resolve the filter expression from the template context.
If the variable doesn't exist, accept the value that passed to the
template tag and convert it to a string
"""
try:
exp = self.expression.resolve(context)
except template.VariableDoesNotExist:
exp = str(self.expression)
filter_exp = self.parser.compile_filter(exp)
context[self.var_name] = [
{'grouper': key, 'list': list(val)}
for key, val in
groupby(obj_list, lambda v, f=filter_exp.resolve: f(v, True))
]
return ''
@register.tag
def dynamic_regroup(parser, token):
firstbits = token.contents.split(None, 3)
if len(firstbits) != 4:
raise TemplateSyntaxError("'regroup' tag takes five arguments")
target = parser.compile_filter(firstbits[1])
if firstbits[2] != 'by':
raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
lastbits_reversed = firstbits[3][::-1].split(None, 2)
if lastbits_reversed[1][::-1] != 'as':
raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
" be 'as'")
"""
Django expects the value of `expression` to be an attribute available on
your objects. The value you pass to the template tag gets converted into a
FilterExpression object from the literal.
Sometimes we need the attribute to group on to be dynamic. So, instead
of converting the value to a FilterExpression here, we're going to pass the
value as-is and convert it in the Node.
"""
expression = lastbits_reversed[2][::-1]
var_name = lastbits_reversed[0][::-1]
"""
We also need to hand the parser to the node in order to convert the value
for `expression` to a FilterExpression.
"""
return DynamicRegroupNode(target, parser, expression, var_name)
Example:
#models.py
class Thing(models.Model):
category = models.ForeignKey('Category')
#views.py
from django.shortcuts import render
from my_app.models import Thing
def index(request):
things = Thing.objects.all()
group_by = request.GET.get('group_by')
return render(request, 'my_template.html',
{'things' : things, 'group_by' : group_by})
#my_template.html
{% dynamic_regroup things by group_by as regrouped_things %}
With the regular {% regroup %} tag, "group_by" cannot be a variable.
Enjoy!
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Please login first before commenting.