* needs_better_patch: 1 => 0
* stage: Accepted => Ready for checkin
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:35>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* stage: Ready for checkin => Accepted
Comment:
Asif set the "Ready for Checkin" flag without reviewing the patch.
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:36>
* cc: andreas@… (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:37>
* cc: simon@… (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:38>
* has_patch: 1 => 0
Comment:
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.
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:39>
Comment (by Harro):
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.
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:40>
* status: assigned => closed
* resolution: => wontfix
Comment:
As said, there is a way to do this with a documented feature, so nothing
to fix.
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:41>
Comment (by Jonas Haag):
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.
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:42>
* status: closed => new
* resolution: wontfix =>
Comment:
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.
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:43>
Comment (by Aymeric Augustin):
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]
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:44>
* cc: Narbonne (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:45>
* owner: Harro => (none)
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:46>
* status: assigned => new
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:47>
* owner: (none) => Aman Pandey
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:48>
* owner: Aman Pandey => (none)
* status: assigned => new
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:49>
* owner: (none) => Andrew Aikman
* status: new => assigned
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:50>
* cc: Doug Harris (added)
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:51>
Comment (by Will Emmerson):
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?
--
Ticket URL: <https://code.djangoproject.com/ticket/8851#comment:52>