49๐
class MyModelAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
if not request.GET.has_key('decommissioned__exact'):
q = request.GET.copy()
q['decommissioned__exact'] = 'N'
request.GET = q
request.META['QUERY_STRING'] = request.GET.urlencode()
return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
129๐
In order to achieve this and have a usable โAllโ link in your sidebar (ie one that shows all rather than showing pending), youโd need to create a custom list filter, inheriting from django.contrib.admin.filters.SimpleListFilter
and filtering on โpendingโ by default. Something along these lines should work:
from datetime import date
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin import SimpleListFilter
class StatusFilter(SimpleListFilter):
title = _('Status')
parameter_name = 'status'
def lookups(self, request, model_admin):
return (
(None, _('Pending')),
('activate', _('Activate')),
('rejected', _('Rejected')),
('all', _('All')),
)
def choices(self, cl):
for lookup, title in self.lookup_choices:
yield {
'selected': self.value() == lookup,
'query_string': cl.get_query_string({
self.parameter_name: lookup,
}, []),
'display': title,
}
def queryset(self, request, queryset):
if self.value() in ('activate', 'rejected'):
return queryset.filter(status=self.value())
elif self.value() == None:
return queryset.filter(status='pending')
class Admin(admin.ModelAdmin):
list_filter = [StatusFilter]
EDIT: Requires Django 1.4 (thanks Simon)
- [Django]-Django staticfiles not found on Heroku (with whitenoise)
- [Django]-Where does pip install its packages?
- [Django]-Sending images using Http Post
22๐
Took ha22109โs answer above and modified to allow the selection of โAllโ by comparing HTTP_REFERER
and PATH_INFO
.
class MyModelAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
test = request.META['HTTP_REFERER'].split(request.META['PATH_INFO'])
if test[-1] and not test[-1].startswith('?'):
if not request.GET.has_key('decommissioned__exact'):
q = request.GET.copy()
q['decommissioned__exact'] = 'N'
request.GET = q
request.META['QUERY_STRING'] = request.GET.urlencode()
return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
- [Django]-Django, creating a custom 500/404 error page
- [Django]-Auth.User.groups: (fields.E304) Reverse accessor for 'User.groups' clashes with reverse accessor for 'UserManage.groups'
- [Django]-Homepage login form Django
22๐
I know this question is quite old now, but itโs still valid. I believe this is the most correct way of doing this. Itโs essentially the same as Gregโs method, but formulated as an extendible class for easy re-use.
from django.contrib.admin import SimpleListFilter
from django.utils.encoding import force_text
from django.utils.translation import ugettext as _
class DefaultListFilter(SimpleListFilter):
all_value = '_all'
def default_value(self):
raise NotImplementedError()
def queryset(self, request, queryset):
if self.parameter_name in request.GET and request.GET[self.parameter_name] == self.all_value:
return queryset
if self.parameter_name in request.GET:
return queryset.filter(**{self.parameter_name:request.GET[self.parameter_name]})
return queryset.filter(**{self.parameter_name:self.default_value()})
def choices(self, cl):
yield {
'selected': self.value() == self.all_value,
'query_string': cl.get_query_string({self.parameter_name: self.all_value}, []),
'display': _('All'),
}
for lookup, title in self.lookup_choices:
yield {
'selected': self.value() == force_text(lookup) or (self.value() == None and force_text(self.default_value()) == force_text(lookup)),
'query_string': cl.get_query_string({
self.parameter_name: lookup,
}, []),
'display': title,
}
class StatusFilter(DefaultListFilter):
title = _('Status ')
parameter_name = 'status__exact'
def lookups(self, request, model_admin):
return ((0,'activate'), (1,'pending'), (2,'rejected'))
def default_value(self):
return 1
class MyModelAdmin(admin.ModelAdmin):
list_filter = (StatusFilter,)
- [Django]-Invalid http_host header
- [Django]-Best practice for Django project working directory structure
- [Django]-Django: ImproperlyConfigured: The SECRET_KEY setting must not be empty
12๐
Here is my generic solution using redirect, it just checks if there are any GET parameters, if none exist then it redirects with the default get parameter. I also have a list_filter set so it picks that up and displays the default.
from django.shortcuts import redirect
class MyModelAdmin(admin.ModelAdmin):
...
list_filter = ('status', )
def changelist_view(self, request, extra_context=None):
referrer = request.META.get('HTTP_REFERER', '')
get_param = "status__exact=5"
if len(request.GET) == 0 and '?' not in referrer:
return redirect("{url}?{get_parms}".format(url=request.path, get_parms=get_param))
return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
The only caveat is when you do a direct get to the page with โ?โ present in the url, there is no HTTP_REFERER set so it will use the default parameter and redirect. This is fine for me, it works great when you click through the admin filter.
UPDATE:
In order to get around the caveat, I ended up writing a custom filter function which simplified the changelist_view functionality. Here is the filter:
class MyModelStatusFilter(admin.SimpleListFilter):
title = _('Status')
parameter_name = 'status'
def lookups(self, request, model_admin): # Available Values / Status Codes etc..
return (
(8, _('All')),
(0, _('Incomplete')),
(5, _('Pending')),
(6, _('Selected')),
(7, _('Accepted')),
)
def choices(self, cl): # Overwrite this method to prevent the default "All"
from django.utils.encoding import force_text
for lookup, title in self.lookup_choices:
yield {
'selected': self.value() == force_text(lookup),
'query_string': cl.get_query_string({
self.parameter_name: lookup,
}, []),
'display': title,
}
def queryset(self, request, queryset): # Run the queryset based on your lookup values
if self.value() is None:
return queryset.filter(status=5)
elif int(self.value()) == 0:
return queryset.filter(status__lte=4)
elif int(self.value()) == 8:
return queryset.all()
elif int(self.value()) >= 5:
return queryset.filter(status=self.value())
return queryset.filter(status=5)
And the changelist_view now only passes the default parameter if none are present. The idea was to get rid of the generics filters capability to view all by using no get parameters. To view all I assigned the status = 8 for that purpose.:
class MyModelAdmin(admin.ModelAdmin):
...
list_filter = ('status', )
def changelist_view(self, request, extra_context=None):
if len(request.GET) == 0:
get_param = "status=5"
return redirect("{url}?{get_parms}".format(url=request.path, get_parms=get_param))
return super(MyModelAdmin, self).changelist_view(request, extra_context=extra_context)
- [Django]-Redirect to Next after login in Django
- [Django]-Django: Grab a set of objects from ID list (and sort by timestamp)
- [Django]-How to remove all of the data in a table using Django
6๐
def changelist_view( self, request, extra_context = None ):
default_filter = False
try:
ref = request.META['HTTP_REFERER']
pinfo = request.META['PATH_INFO']
qstr = ref.split( pinfo )
if len( qstr ) < 2:
default_filter = True
except:
default_filter = True
if default_filter:
q = request.GET.copy()
q['registered__exact'] = '1'
request.GET = q
request.META['QUERY_STRING'] = request.GET.urlencode()
return super( InterestAdmin, self ).changelist_view( request, extra_context = extra_context )
- [Django]-Django values_list vs values
- [Django]-Django Rest Framework โ Authentication credentials were not provided
- [Django]-Django CSRF Cookie Not Set
6๐
Created a reusable Filter sub-class, inspired by some of the answers here (mostly Gregโs).
Advantages:
Reusable โ Pluggable in any standard ModelAdmin
classes
Extendable โ Easy to add additional/custom logic for QuerySet
filtering
Easy to use โ In its most basic form, only one custom attribute and one custom method need to be implemented (apart from those required for SimpleListFilter subclassing)
Intuitive admin โ The โAllโ filter link is working as expected; as are all the others
No redirects โ No need to inspect GET
request payload, agnostic of HTTP_REFERER
(or any other request related stuff, in its basic form)
No (changelist) view manipulation โ And no template manipulations (god forbid)
Code:
(most of the import
s are just for type hints and exceptions)
from typing import List, Tuple, Any
from django.contrib.admin.filters import SimpleListFilter
from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.views.main import ChangeList
from django.db.models.query import QuerySet
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
class PreFilteredListFilter(SimpleListFilter):
# Either set this or override .get_default_value()
default_value = None
no_filter_value = 'all'
no_filter_name = _("All")
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = None
# Parameter for the filter that will be used in the URL query.
parameter_name = None
def get_default_value(self):
if self.default_value is not None:
return self.default_value
raise NotImplementedError(
'Either the .default_value attribute needs to be set or '
'the .get_default_value() method must be overridden to '
'return a URL query argument for parameter_name.'
)
def get_lookups(self) -> List[Tuple[Any, str]]:
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
appear in the URL query. The second element is the
human-readable name for the option that will appear
in the right sidebar.
"""
raise NotImplementedError(
'The .get_lookups() method must be overridden to '
'return a list of tuples (value, verbose value).'
)
# Overriding parent class:
def lookups(self, request, model_admin) -> List[Tuple[Any, str]]:
return [(self.no_filter_value, self.no_filter_name)] + self.get_lookups()
# Overriding parent class:
def queryset(self, request, queryset: QuerySet) -> QuerySet:
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
if self.value() is None:
return self.get_default_queryset(queryset)
if self.value() == self.no_filter_value:
return queryset.all()
return self.get_filtered_queryset(queryset)
def get_default_queryset(self, queryset: QuerySet) -> QuerySet:
return queryset.filter(**{self.parameter_name: self.get_default_value()})
def get_filtered_queryset(self, queryset: QuerySet) -> QuerySet:
try:
return queryset.filter(**self.used_parameters)
except (ValueError, ValidationError) as e:
# Fields may raise a ValueError or ValidationError when converting
# the parameters to the correct type.
raise IncorrectLookupParameters(e)
# Overriding parent class:
def choices(self, changelist: ChangeList):
"""
Overridden to prevent the default "All".
"""
value = self.value() or force_str(self.get_default_value())
for lookup, title in self.lookup_choices:
yield {
'selected': value == force_str(lookup),
'query_string': changelist.get_query_string({self.parameter_name: lookup}),
'display': title,
}
Full usage example:
from django.contrib import admin
from .models import SomeModelWithStatus
class StatusFilter(PreFilteredListFilter):
default_value = SomeModelWithStatus.Status.FOO
title = _('Status')
parameter_name = 'status'
def get_lookups(self):
return SomeModelWithStatus.Status.choices
@admin.register(SomeModelWithStatus)
class SomeModelAdmin(admin.ModelAdmin):
list_filter = (StatusFilter, )
Hope this helps somebody; feedback always appreciated.
- [Django]-Django: Reverse for 'detail' with arguments '('',)' and keyword arguments '{}' not found
- [Django]-Name '_' is not defined
- [Django]-Gunicorn Connection in Use: ('0.0.0.0', 5000)
4๐
Note that if instead of pre-selecting a filter value you want to always pre-filter the data before showing it in the admin, you should override the ModelAdmin.queryset()
method instead.
- [Django]-Choose test database?
- [Django]-How to reset Django admin password?
- [Django]-Django: Safely Remove Old Migrations?
4๐
You can simply usereturn queryset.filter()
or if self.value() is None
and Override method of SimpleListFilter
from django.utils.encoding import force_text
def choices(self, changelist):
for lookup, title in self.lookup_choices:
yield {
'selected': force_text(self.value()) == force_text(lookup),
'query_string': changelist.get_query_string(
{self.parameter_name: lookup}, []
),
'display': title,
}
- [Django]-Django rest framework serializing many to many field
- [Django]-Chaining multiple filter() in Django, is this a bug?
- [Django]-Disabled field is not passed through โ workaround needed
3๐
A slight improvement on Gregโs answer using DjangoChoices, Python >= 2.5 and of course Django >= 1.4.
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin import SimpleListFilter
class OrderStatusFilter(SimpleListFilter):
title = _('Status')
parameter_name = 'status__exact'
default_status = OrderStatuses.closed
def lookups(self, request, model_admin):
return (('all', _('All')),) + OrderStatuses.choices
def choices(self, cl):
for lookup, title in self.lookup_choices:
yield {
'selected': self.value() == lookup if self.value() else lookup == self.default_status,
'query_string': cl.get_query_string({self.parameter_name: lookup}, []),
'display': title,
}
def queryset(self, request, queryset):
if self.value() in OrderStatuses.values:
return queryset.filter(status=self.value())
elif self.value() is None:
return queryset.filter(status=self.default_status)
class Admin(admin.ModelAdmin):
list_filter = [OrderStatusFilter]
Thanks to Greg for the nice solution!
- [Django]-How do I match the question mark character in a Django URL?
- [Django]-Django filter JSONField list of dicts
- [Django]-How to add a cancel button to DeleteView in django
2๐
I know that is not the best solution, but i changed the index.html in the admin template, line 25 and 37 like this:
25: <th scope="row"><a href="{{ model.admin_url }}{% ifequal model.name "yourmodelname" %}?yourflag_flag__exact=1{% endifequal %}">{{ model.name }}</a></th>
37: <td><a href="{{ model.admin_url }}{% ifequal model.name "yourmodelname" %}?yourflag__exact=1{% endifequal %}" class="changelink">{% trans 'Change' %}</a></td>
- [Django]-How can I activate the unaccent extension on an already existing model
- [Django]-How to properly use the "choices" field option in Django
- [Django]-Django Generic Views using decorator login_required
2๐
Hereโs the Cleanest version I was able to generate of a filter with a redefined โAllโ and a Default value that is selected.
If shows me by default the Trips currently happening.
class HappeningTripFilter(admin.SimpleListFilter):
"""
Filter the Trips Happening in the Past, Future or now.
"""
default_value = 'now'
title = 'Happening'
parameter_name = 'happening'
def lookups(self, request, model_admin):
"""
List the Choices available for this filter.
"""
return (
('all', 'All'),
('future', 'Not yet started'),
('now', 'Happening now'),
('past', 'Already finished'),
)
def choices(self, changelist):
"""
Overwrite this method to prevent the default "All".
"""
value = self.value() or self.default_value
for lookup, title in self.lookup_choices:
yield {
'selected': value == force_text(lookup),
'query_string': changelist.get_query_string({
self.parameter_name: lookup,
}, []),
'display': title,
}
def queryset(self, request, queryset):
"""
Returns the Queryset depending on the Choice.
"""
value = self.value() or self.default_value
now = timezone.now()
if value == 'future':
return queryset.filter(start_date_time__gt=now)
if value == 'now':
return queryset.filter(start_date_time__lte=now, end_date_time__gte=now)
if value == 'past':
return queryset.filter(end_date_time__lt=now)
return queryset.all()
- [Django]-Django using get_user_model vs settings.AUTH_USER_MODEL
- [Django]-Django south migration โ Adding FULLTEXT indexes
- [Django]-Add inline model to django admin site
1๐
I had to make a modification to get filtering to work correctly. The previous solution worked for me when the page loaded. If an โactionโ was performed, the filter went back to โAllโ and not my default. This solution loads the admin change page with the default filter, but also maintains filter changes or the current filter when other activity occurs on the page. I havenโt tested all cases, but in reality it may be limiting the setting of a default filter to occur only when the page loads.
def changelist_view(self, request, extra_context=None):
default_filter = False
try:
ref = request.META['HTTP_REFERER']
pinfo = request.META['PATH_INFO']
qstr = ref.split(pinfo)
querystr = request.META['QUERY_STRING']
# Check the QUERY_STRING value, otherwise when
# trying to filter the filter gets reset below
if querystr is None:
if len(qstr) < 2 or qstr[1] == '':
default_filter = True
except:
default_filter = True
if default_filter:
q = request.GET.copy()
q['registered__isnull'] = 'True'
request.GET = q
request.META['QUERY_STRING'] = request.GET.urlencode()
return super(MyAdmin, self).changelist_view(request, extra_context=extra_context)
- [Django]-Django โ how to create a file and save it to a model's FileField?
- [Django]-How to tell if a task has already been queued in django-celery?
- [Django]-Django โ Annotate multiple fields from a Subquery
1๐
A bit off-topic but my search for a similar question led me here. I was looking to have a default query by a date (ie if no input is provided, show only objects with timestamp
of โTodayโ), which complicates the question a bit. Here is what I came up with:
from django.contrib.admin.options import IncorrectLookupParameters
from django.core.exceptions import ValidationError
class TodayDefaultDateFieldListFilter(admin.DateFieldListFilter):
""" If no date is query params are provided, query for Today """
def queryset(self, request, queryset):
try:
if not self.used_parameters:
now = datetime.datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
self.used_parameters = {
('%s__lt' % self.field_path): str(now + datetime.timedelta(days=1)),
('%s__gte' % self.field_path): str(now),
}
# Insure that the dropdown reflects 'Today'
self.date_params = self.used_parameters
return queryset.filter(**self.used_parameters)
except ValidationError, e:
raise IncorrectLookupParameters(e)
class ImagesAdmin(admin.ModelAdmin):
list_filter = (
('timestamp', TodayDefaultDateFieldListFilter),
)
This is a simple override of the default DateFieldListFilter
. By setting self.date_params
, it insures that the filter dropdown will update to whatever option matches the self.used_parameters
. For this reason, you must insure that the self.used_parameters
are exactly what would be used by one of those dropdown selections (ie, find out what the date_params
would be when using the โTodayโ or โLast 7 Daysโ and construct the self.used_parameters
to match those).
This was built to work with Django 1.4.10
- [Django]-Django โ what is the difference between render(), render_to_response() and direct_to_template()?
- [Django]-How to 'bulk update' with Django?
- [Django]-Python Asyncio in Django View
1๐
This may be an old thread, but thought I would add my solution as I couldnโt find better answers on google searches.
Do what (not sure if its Deminic Rodger, or ha22109) answered in the ModelAdmin for changelist_view
class MyModelAdmin(admin.ModelAdmin):
list_filter = (CustomFilter,)
def changelist_view(self, request, extra_context=None):
if not request.GET.has_key('decommissioned__exact'):
q = request.GET.copy()
q['decommissioned__exact'] = 'N'
request.GET = q
request.META['QUERY_STRING'] = request.GET.urlencode()
return super(MyModelAdmin,self).changelist_view(request, extra_context=extra_context)
Then we need to create a custom SimpleListFilter
class CustomFilter(admin.SimpleListFilter):
title = 'Decommissioned'
parameter_name = 'decommissioned' # i chose to change it
def lookups(self, request, model_admin):
return (
('All', 'all'),
('1', 'Decommissioned'),
('0', 'Active (or whatever)'),
)
# had to override so that we could remove the default 'All' option
# that won't work with our default filter in the ModelAdmin class
def choices(self, cl):
yield {
'selected': self.value() is None,
'query_string': cl.get_query_string({}, [self.parameter_name]),
# 'display': _('All'),
}
for lookup, title in self.lookup_choices:
yield {
'selected': self.value() == lookup,
'query_string': cl.get_query_string({
self.parameter_name: lookup,
}, []),
'display': title,
}
def queryset(self, request, queryset):
if self.value() == '1':
return queryset.filter(decommissioned=1)
elif self.value() == '0':
return queryset.filter(decommissioned=0)
return queryset
- [Django]-Constructing Django filter queries dynamically with args and kwargs
- [Django]-Naming convention for Django URL, templates, models and views
- [Django]-Add additional options to Django form select widget
0๐
Using ha22109โs answer I wrote a mixin for ModelAdmin class:
from urllib.parse import urlencode
from django.contrib.admin.views.main import SEARCH_VAR
from django.http import HttpRequest, QueryDict
class DefaultFilterMixin:
default_filters: Sequence[tuple[str, Any]] | dict[str, Any] | None = None
def get_default_filters(
self,
request: HttpRequest,
) -> Sequence[tuple[str, Any]] | dict[str, Any] | None:
return self.default_filters
def changelist_view(
self,
request: HttpRequest,
extra_context: dict[str, str] | None = None,
):
if request.method == 'GET' and not request.GET:
if default_filters := self.get_default_filters(request):
request.GET = QueryDict(
f"{urlencode(default_filters)}&{SEARCH_VAR}=",
encoding=request.encoding,
)
request.META['QUERY_STRING'] = request.GET.urlencode()
return super().changelist_view(request, extra_context=extra_context)
Simple example:
class MyModelAdmin(DefaultFilterMixin, admin.ModelAdmin):
default_filters = (("status__exact", "pending"),)
...
Or more complex dynamic filter for rangefilter.DateTimeRangeFilter:
class MyModelAdmin(DefaultFilterMixin, admin.ModelAdmin):
def get_default_filters(
self,
request: HttpRequest,
) -> Sequence[tuple[str, Any]] | dict[str, Any] | None:
now = timezone.now()
date_fmt = '%d.%m.%Y'
return (
('created_at__range__gte_0', now.strftime(date_fmt)),
('created_at__range__gte_1', '00:00:00'),
('created_at__range__lte_0', (now + timedelta(1)).strftime(date_fmt)),
('created_at__range__lte_1', '00:00:00'),
)
- [Django]-Django Rest Framework custom response message
- [Django]-How to run Django's test database only in memory?
- [Django]-How do I convert a Django QuerySet into list of dicts?
0๐
replying to the first answer (from Evgeni Shudzel) with the default mixinโฆ here is a solution with less imports and complications
from urllib.parse import urlencode
from django.contrib.admin.views.main import SEARCH_VAR
from django.http import HttpRequest, QueryDict
class DefaultFilterMixin:
default_filters: None
def get_default_filters(self, request: HttpRequest):
return self.default_filters
def changelist_view(self, request: HttpRequest, extra_context=None):
if request.method == 'GET' and not request.GET:
if default_filters := self.get_default_filters(request):
request.GET = QueryDict(
f"{urlencode(default_filters)}&{SEARCH_VAR}=",
encoding=request.encoding,
)
request.META['QUERY_STRING'] = request.GET.urlencode()
return super().changelist_view(request, extra_context=extra_context)
class SampleUsageAdmin(DefaultFilterMixin, admin.ModelAdmin):
default_filters = (("fulfillments_complete__exact", "0"),)
- [Django]-How to solve "Page not found (404)" error in Django?
- [Django]-Is this the right way to do dependency injection in Django?
- [Django]-Django REST framework: non-model serializer
0๐
I did this a slightly different way because none of the answers worked quite right for my use case. I just overrode AdminSite.get_app_list
and changed the actual url to include the filter:
class MyAdminSite(admin.AdminSite):
def get_app_list(self, request, app_label=None):
app_list = super().get_app_list(request, app_label)
for app_dict in app_list:
for model_dict in app_dict["models"]:
model = model_dict["model"]
model_admin = self._registry[model]
if default_filters := getattr(model_admin, "default_list_filters", None):
model_dict["admin_url"] += "?" + urlencode(default_filters)
return app_list
See https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#overriding-default-admin-site for how to override AdminSite.
This then picks up default_list_filters
if defined on a model admin, like so:
@admin.register(Job)
class JobAdmin(admin.ModelAdmin, ):
default_list_filters = {
"date-range": "today",
}
This therefore has the following advantages over the other solutions:
- you donโt have to make a custom Filter
- the actual url in the address bar is correct
- doesnโt require a redirect
- simple
Update: Unfortunately it breaks the highlighting of the currently selected model on the left sidebar
- [Django]-Error: No module named psycopg2.extensions
- [Django]-Django's Double Underscore
- [Django]-Django ModelForm: What is save(commit=False) used for?