Django Admin Custom Change List Arguments: Override /?e=1
-
15-03-2021 - |
Question
I'm trying to pass in a custom argument to the Django Admin change list view so I can filter the list in a specialized way. I'd like to filter the queryset on 2 fields, start_date and end_date, based on the GET parameter called 'active_pp'. I've gotten the filtering to work correctly, but I'm not able to pass in a GET query parameter that specifies whether I should display the filtered results or the normal results.
I know that, due to security, the Django Admin filters out any query parameters passed to it that aren't related to specified model fields; upon finding bad arguments, the admin redirects the user to the current view but replaces the GET query parameters with e=1. I'd like to whitelist my custom 'active_pp' parameter so the page won't be redirected and I'll be able to use the parameter.
Here is an example of the ModelAdmin in admin.py with the queryset customization.
class FeaduredAdmin(admin.ModelAdmin):
....
def get_changelist(self, request, **kwargs):
from django.contrib.admin.views.main import ChangeList
# Try to get the 'active_pp' query parameter
active_pp = request.GET.get('active_pp',None)
# Define a custom ChangeList class with a custom queryset
class ActiveChangeList(ChangeList):
def get_query_set(self, *args, **kwargs):
now = datetime.datetime.now()
qs = super(ActiveChangeList, self).get_query_set(*args, **kwargs)
return qs.filter((Q(start_date=None) | Q(start_date__lte=now))
& (Q(end_date=None) | Q(end_date__gte=now)))
# use the custom ChangeList class if the parameter exists
if active_pp:
return ActiveChangeList
return ChangeList
Does anyone know how to whitelist custom GET querystring arguments passed to the change_list?
Thanks for reading and for your consideration, Joe
UPDATE:
Using Uvasal's provided links, I was able to properly whitelist the GET parameter.
class ActiveFilterAminForm(forms.Form):
active_pp = forms.CharField()
class FeaduredAdmin(admin.ModelAdmin):
....
# Based on: http://djangosnippets.org/snippets/2322/
advanced_search_form = ActiveFilterAminForm()
def get_changelist(self, request, **kwargs):
from django.contrib.admin.views.main import ChangeList
active_pp = self.other_search_fields.get('active_pp',None)
# now we have the active_pp parameter that was passed in and can use it.
class ActiveChangeList(ChangeList):
def get_query_set(self, *args, **kwargs):
now = datetime.datetime.now()
qs = super(ActiveChangeList, self).get_query_set(*args, **kwargs)
return qs.filter((Q(start_date=None) | Q(start_date__lte=now))
& (Q(end_date=None) | Q(end_date__gte=now)))
if not active_pp is None:
return ActiveChangeList
return ChangeList
def lookup_allowed(self, lookup):
if lookup in self.advanced_search_form.fields.keys():
return True
return super(MyModelAdmin, self).lookup_allowed(lookup)
def changelist_view(self, request, extra_context=None, **kwargs):
self.other_search_fields = {}
asf = self.advanced_search_form
extra_context = {'asf':asf}
request.GET._mutable=True
for key in asf.fields.keys():
try:
temp = request.GET.pop(key)
except KeyError:
pass
else:
if temp!=['']:
self.other_search_fields[key] = temp
request.GET_mutable=False
return super(FeaduredProductAdmin, self)\
.changelist_view(request, extra_context=extra_context)
Solution
I think you just need to put your custom filter fields in the search_fields class variable as outlined in the Advanced Search Django Snippet.
You should be able to modify the snippet to support date ranges as well.
OTHER TIPS
In summary, here is the undocumented hack used above:
set request.GET._mutable = True
, then request.GET.pop()
off the custom GET argument(s) you are using.
I know this is an old post, but just ran into a need for this and discovered a very short and simple solution that I thought I would share. Key here is to make a filter that doesn't affect the queryset and accepts anything passed to it in the lookups as valid option. Something like the following:
from django.contrib.admin import SimpleListFilter
class PassThroughFilter(SimpleListFilter):
title = ''
parameter_name = 'pt'
template = 'admin/hidden_filter.html'
def lookups(self, request, model_admin):
return (request.GET.get(self.parameter_name), ''),
def queryset(self, request, queryset):
return queryset
The hidden_filter
template is blank to prevent adding anything to the filter area, and the lookups
method will always return whatever I have entered for the pt
parameter as a valid filter entry. This will prevent the ?e=1
error from popping up as the page loads.
This can be reused with any admin, using the pt
parameter. If you need to pass multiple parameters for a single admin, then just subclass this into separate filters and override parameter_name
with whatever parameters you need. This will have the effect of allowing those parameters in the query string without affecting the queryset or showing up in the filter column, and you can then use them for whatever purpose you needed them elsewhere.
Hope this helps someone down the road.