Let's say we have a model Project with age groups as boolean fields:
class Project(models.Model):
# ...
age_group_0_5 = models.BooleanField(u"From 0 to 5 years")
age_group_6_12 = models.BooleanField(u"From 6 to 12 years")
age_group_13_18 = models.BooleanField(u"From 13 to 18 years")
age_group_19_27 = models.BooleanField(u"Fron 19 to 27 years")
# ...
In order to list them as one selection in the admin list filter, we need a specific filter specification.
Let's create it in the admin.py
:
from django.contrib.admin.filterspecs import FilterSpec
class AgeChoicesFilterSpec(FilterSpec):
def title(self):
return u"Age group"
def __init__(self, f, request, params, model, model_admin,
field_path=None):
super(AgeChoicesFilterSpec, self).__init__(f, request, params, model,
model_admin,
field_path=field_path)
self.request = request
def choices(self, cl):
yield {'selected': not(
self.request.GET.get('age_group_0_5', None) or
self.request.GET.get('age_group_6_12', None) or
self.request.GET.get('age_group_13_18', None) or
self.request.GET.get('age_group_19_27', None)
),
'query_string': cl.get_query_string(
{},
['age_group_0_5', 'age_group_6_12', 'age_group_13_18', 'age_group_19_27']),
'display': u'All'}
yield {'selected': self.request.GET.get('age_group_0_5', None) == 'True',
'query_string': cl.get_query_string(
{'age_group_0_5': 'True'},
['age_group_6_12', 'age_group_13_18', 'age_group_19_27']),
'display': u"From 0 to 5 years"}
yield {'selected': self.request.GET.get('age_group_6_12', None) == 'True',
'query_string': cl.get_query_string(
{'age_group_6_12': 'True'},
['age_group_0_5', 'age_group_13_18', 'age_group_19_27']),
'display': u"From 6 to 12 years"}
yield {'selected': self.request.GET.get('age_group_13_18', None) == 'True',
'query_string': cl.get_query_string(
{'age_group_13_18': 'True'},
['age_group_0_5', 'age_group_6_12', 'age_group_19_27']),
'display': u"From 13 to 18 years"}
yield {'selected': self.request.GET.get('age_group_19_27', None) == 'True',
'query_string': cl.get_query_string(
{'age_group_19_27': 'True'},
['age_group_0_5', 'age_group_6_12', 'age_group_13_18']),
'display': u"From 19 to 27 years"}
FilterSpec.filter_specs.insert(0, (lambda f: f.name == 'age_group_0_5',
AgeChoicesFilterSpec))
Here the most important method of FilterSpec
is choices
. It defines the choices for the filter by dictionary with three keys:
selected
is the condition when this choice should be selected.query_string
is the URL query string that will be formed to filter the list by. The query string is formed byget_query_string
method ofChangeList
instance. It takes two parameters: a dictionary of new parameters to add to the current query, and a list of parameters to remove from the current query.display
is the human-readable string which will be shown for this choice in the filter.
The new filter specification should be attached to a field. Here it will be applied to all fields with the name "age_group_0_5" and we have just one such field.
The last thing to do is to define admin options for the Project
model:
from django.contrib import admin
from models import Project
class ProjectAdmin(admin.ModelAdmin):
# ...
list_filter = ("age_group_0_5", )
# ...
admin.site.register(Project, ProjectAdmin)
Now you know how to create a custom admin filter specification for your model. :)
Maybe you should normalise the models instead? Why would you have 4 fields when you can have 1 field with a checkbox widget anyway?
ReplyDeleteBoolean fields on the model are faster than many-to-many relationships. Anyway, this post is about coding a custom FilterSpec which might be even more complex.
ReplyDeleteYou don't need to use many-to-many in this case. If you hard coded the ranges of ages in field names, you can hard code the ranges as choices of a choice field. Remember that if you want to filter by boolean fields, indexes wont help you very much. So the speed of the boolean is questionable.
ReplyDelete@Vinicius - But he wants to be able to select more than one age range, I think you mean MultipleChoiceField. Indeed if you are hard-coding the choices rather than having them as foreign keys, it won't really be many-to-many in terms of DB queries, right?
ReplyDelete