Opened 16 years ago
Last modified 18 months ago
#8851 assigned New feature
Add a default option to list_filter in the admin interface
Reported by: | Owned by: | Andrew Aikman | |
---|---|---|---|
Component: | contrib.admin | Version: | dev |
Severity: | Normal | Keywords: | admin, list_filter, default |
Cc: | Carl Meyer, Trevor Caira, rlaager@…, andy@…, wizard@…, kc9ddi, remco@…, net147, trbs@…, a@…, markus.magnuson@…, andreas@…, simon@…, Narbonne, Doug Harris | Triage Stage: | Accepted |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
There should be a way to set a default list_filter in the admin interface. For example, if I had a model like this:
DECOM_CHOICES = ( ('N', 'No'), ('Y', 'Yes'), ) class Host(models.Model): hostname = models.CharField(max_length=36, unique=True) decommissioned = models.CharField(max_length=1, choices=DECOM_CHOICES, default='N') ip_address = models.IPAddressField() def __unicode__(self): return self.hostname class HostAdmin(admin.ModelAdmin): fieldsets = [ ('Host Info', {'fields': ['hostname','decommissioned','ip_address']}), list_display = ('hostname', 'ip_address', 'decommissioned') list_filter = ('decommissioned') admin.site.register(Host, HostAdmin)
I might want to make it so that by default only hosts that are not marked as decommissioned are displayed by default in the index. It would be nice if I could just pass an option to list_filter to set the a filter that is enabled by default. Perhaps something like this:
list_filter = ('decommissioned__exact=N') # Matches the URL if you click on "No" in the filter list
...or perhaps...
list_filter = ('decommissioned__default') # Have it choose the same default as specified in the model.
If there's already a simple way to do this I haven't found it and would appreciate an example.
Thanks!
Change History (52)
comment:1 by , 16 years ago
milestone: | 1.0 |
---|
comment:2 by , 16 years ago
Triage Stage: | Unreviewed → Accepted |
---|
comment:3 by , 16 years ago
The syntax doesn't matter... The feature is what matters =). If you're going to implement this you can do it however you like (and I thank you!).
comment:4 by , 16 years ago
Resolution: | → fixed |
---|---|
Status: | new → closed |
That's a great idea to add this feature.
for now, I make a bit hack of code to set django.contrib.admin.filterspecs.RelatedFilterSpec.lookup_choices to get objects from "ModelAdmin.formfield_for_foreignkey()"
class RelatedFilterSpec(FilterSpec): def __init__(self, f, request, params, model, model_admin): super(RelatedFilterSpec, self).__init__(f, request, params, model, model_admin) if isinstance(f, models.ManyToManyField): self.lookup_title = f.rel.to._meta.verbose_name else: self.lookup_title = f.verbose_name rel_name = f.rel.get_related_field().name self.lookup_kwarg = '%s__%s__exact' % (f.name, rel_name) self.lookup_val = request.GET.get(self.lookup_kwarg, None) # self.lookup_choices = f.rel.get_choices(include_blank=False); >> f.rel.get_choices will return lists of object tuple self.lookup_choices = [] ; for object in model_admin.formfield_for_foreignkey(f,request).queryset : list = (object.id,object.__str__()) ; self.lookup_choices.append(list);
Then just use formfield_for_foreignkey() which is described in django document to filter the objects we want.
comment:5 by , 16 years ago
Resolution: | fixed |
---|---|
Status: | closed → reopened |
A workaround in a comment doesn't make the ticket fixed. The ticket is fixed when a change is checked into the Django source tree.
comment:6 by , 16 years ago
Cc: | added |
---|
comment:7 by , 15 years ago
Cc: | added |
---|
comment:8 by , 15 years ago
Cc: | added |
---|
comment:9 by , 15 years ago
Cc: | added |
---|
comment:11 by , 14 years ago
Cc: | added |
---|
comment:12 by , 14 years ago
Cc: | added |
---|
comment:13 by , 14 years ago
Severity: | → Normal |
---|---|
Type: | → New feature |
comment:14 by , 14 years ago
Easy pickings: | unset |
---|
This would be very useful. Hoping for this in 1.4.
There is a ugly workaround on StackOverflow, but this does not show the default filter in the filterbar.
comment:16 by , 12 years ago
Cc: | added |
---|
comment:17 by , 12 years ago
I think you can achieve this in 1.4+ by subclassing django.contrib.admin.ChoicesFieldListFilter
.
comment:18 by , 12 years ago
Status: | reopened → new |
---|
comment:19 by , 11 years ago
Resolution: | → fixed |
---|---|
Status: | new → closed |
This feature has been already introduced in Django, unfortunately, I don't know when. Basically, subclass SimpleListFilter
and override two methods lookups
and queryset
. You can read about it in ModelAdmin.list_filter docs
I'm closing this ticket as fixed, as described feature is in my opinion what you were looking for. If you feel it's not what you need, please reopen with detailed description what should be changed.
comment:20 by , 11 years ago
Resolution: | fixed |
---|---|
Status: | closed → new |
I would argue that this is not fixed. I believe I have the same issue as the OP - I want to choose which of the available filter options should be selected by default. I don't want to customize the filtering behavior by subclassing SimpleListFilter. The desired behavior is that when I go to admin changelist url, it is *as if I had already chosen one of the filtering options*. I don't want to change the behavior of the filtering options. I just want one (the defined default) to be pre-selected.
comment:21 by , 10 years ago
Cc: | added |
---|
comment:22 by , 9 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:23 by , 9 years ago
Owner: | removed |
---|---|
Status: | assigned → new |
comment:24 by , 9 years ago
Owner: | set to |
---|---|
Status: | new → assigned |
Working on this during duth sprints
comment:25 by , 9 years ago
Has patch: | set |
---|
comment:26 by , 9 years ago
Cc: | added |
---|
comment:27 by , 9 years ago
I'm willing to test this out once a patch has been committed.
Implemented simply, as originally described, this would be a very useful feature indeed.
comment:28 by , 9 years ago
While testing after commit is certainly helpful, if you can give feedback on and/or test the pull request before it's committed, that's even more helpful.
comment:29 by , 9 years ago
Patch needs improvement: | set |
---|
Just did some testing as well, and the filters selected value does not work correctly with the defaults. Working on a fix.
comment:30 by , 9 years ago
Patch needs improvement: | unset |
---|
Totally forgot to update the ticket, it's all done and ready now.
comment:31 by , 9 years ago
Cc: | added |
---|
comment:33 by , 9 years ago
You can help by reviewing the patch using the PatchReviewChecklist and marking the ticket "ready for checkin" when all looks good.
comment:34 by , 9 years ago
Patch needs improvement: | set |
---|---|
Summary: | Please add a default option to list_filter in the admin interface → Add a default option to list_filter in the admin interface |
Left some comments on the pull request. Not sure if the current approach is ideal with regard to reusing the choice logic from list filter classes.
comment:35 by , 8 years ago
Patch needs improvement: | unset |
---|---|
Triage Stage: | Accepted → Ready for checkin |
comment:36 by , 8 years ago
Triage Stage: | Ready for checkin → Accepted |
---|
Asif set the "Ready for Checkin" flag without reviewing the patch.
comment:37 by , 8 years ago
Cc: | added |
---|
comment:38 by , 8 years ago
Cc: | added |
---|
comment:39 by , 7 years ago
Has patch: | unset |
---|
Also see: https://github.com/django/django/pull/5579#issuecomment-360387527
I think the solution to change the url linking to the list view is a much better one that what I've implemented, I'm closing my pull request and will do some research into the other solution.
comment:40 by , 7 years ago
Ok, I found a much nicer solution for this whole thing, no need to do changes to urls at all.
Say you have an is_archived flag on a model and you want to default filter the list to hide archived items:
class ArchivedListFilter(admin.SimpleListFilter): title = _('Archived') parameter_name = 'show_archived' def lookups(self, request, model_admin): return ( ('all', _('Show all')), ('archived', _('Show archived only')), ) def queryset(self, request, queryset): show_archived = request.GET.get('show_archived') if (show_archived == 'all'): return queryset.all() elif (show_archived == 'archived'): return queryset.filter(is_archived=True) return queryset.filter(is_archived=False) def choices(self, cl): yield { 'selected': self.value() is None, 'query_string': cl.get_query_string({}, [self.parameter_name]), 'display': _('Hide archived'), # Changed All to Hide archived. } 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, }
Simply add this filter and by default it will filter and on filter values it will either show all items or only the archived items.
Now it would be nice to be able to change the label in for "All" in the choices method without the need to override whole method, so maybe the only fix would be to move the label to a propery on SimpleListFilter so you can quickly override it.
comment:41 by , 7 years ago
Resolution: | → wontfix |
---|---|
Status: | assigned → closed |
As said, there is a way to do this with a documented feature, so nothing to fix.
comment:42 by , 7 years ago
That's a lot of code to copy and paste, and it's probably going to break when changes to the admin are being made.
I'd say reopen this.
comment:43 by , 5 years ago
Resolution: | wontfix |
---|---|
Status: | closed → new |
Sorry for the close / reopen ping-pong... I think this is a worthwhile feature request. For example, when using soft-delete, by default, you don't want soft-deleted objects to show up in the admin. The API proposed in https://github.com/django/django/pull/5579 is interesting.
comment:44 by , 5 years ago
Furthermore, while SimpleListFilter
is a public API, FieldListFilter
isn't. The docs note:
The
FieldListFilter
API is considered internal and might be changed.
There's no way to make a good list filter for handling soft-deletes with SimpleListFilter
because it defaults to showing everything, which is the main behavior that needs changing.
Here's how I did it with FieldListFilter
:
class DeactivableListFilter(admin.ListFilter): title = _("active") parameter_name = 'active' def __init__(self, request, params, model, model_admin): super().__init__(request, params, model, model_admin) if self.parameter_name in params: value = params.pop(self.parameter_name) self.used_parameters[self.parameter_name] = value def show_all(self): return self.used_parameters.get(self.parameter_name) == '__all__' def has_output(self): return True def choices(self, changelist): return [ { 'selected': self.show_all(), 'query_string': changelist.get_query_string({self.parameter_name: '__all__'}), 'display': "Tout", }, { 'selected': not self.show_all(), 'query_string': changelist.get_query_string(remove=[self.parameter_name]), 'display': "Actif", }, ] def queryset(self, request, queryset): if not self.show_all(): return queryset.filter(active=True) def expected_parameters(self): return [self.parameter_name]
comment:45 by , 5 years ago
Cc: | added |
---|
comment:46 by , 2 years ago
Owner: | removed |
---|---|
Status: | new → assigned |
comment:47 by , 2 years ago
Status: | assigned → new |
---|
comment:48 by , 2 years ago
Owner: | set to |
---|---|
Status: | new → assigned |
comment:49 by , 2 years ago
Owner: | removed |
---|---|
Status: | assigned → new |
comment:50 by , 2 years ago
Owner: | set to |
---|---|
Status: | new → assigned |
comment:51 by , 2 years ago
Cc: | added |
---|
comment:52 by , 18 months ago
I recently had a similar requirement to set a default list_filter
and was surprised how difficult it was and that the solutions aren't that great.
The solutions are basically:
a) write a custom ListFilter
which defaults to filtering something: but this means the get params aren't in the url when you initial go to that changelist, it's a pain having to make a custom ListFilter, and it can mess with the 'All' option.
b) Override ModelAdmin.changelist_view
and mess with the GET params there in an ugly way using request.META, and maybe do a redirect: but then you're introducing another unnecessary request.
It seems the correct, RESTful way to do this is to include the query params in the model link itself. I managed to do this using AdminSite.get_app_list
but it's far from perfect:
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
This just picks up ModelAdmin.default_list_filters
if it exists and adds them to the url. But unfortunately it breaks the yellow highlighting of the current model because it's not expecting there to be query params there (contrib/templates/admin/app_list.html
):
{% for model in app.models %} <tr class="model-{{ model.object_name|lower }}{% if model.admin_url in request.path|urlencode %} current-model{% endif %}"> {% if model.admin_url %} <th scope="row"><a href="{{ model.admin_url }}"{% if model.admin_url in request.path|urlencode %} aria-current="page"{% endif %}>{{ model.name }}</a></th> {% else %} <th scope="row">{{ model.name }}</th> {% endif %}
if model.admin_url in request.path|urlencode
returns false when on the right page, but it would work if request.get_full_path
was used instead.
Obviously it'd be a lot better if this functionality was built in, is it worth me creating a PR to do this or is adding GET params to the admin urls a terrible idea? Will it break other things?
Not a big fan of the syntax, but a useful feature.