Notez que j'ai étudié comment affiner les éléments de list_filter par le résultat d'un autre list_filter.
Pour le modèle dans lequel la relation un-à-plusieurs est concaténée, spécifiez le modèle source de concaténation dans list_filter. Pour le moment, je souhaite réduire les éléments de list_filter en fonction des éléments sélectionnés.
Puisqu'il est difficile à comprendre avec des mots, considérez le modèle suivant comme un exemple.
Spécifiez le magasin et le personnel comme list_filter sur l'écran de gestion des équipes. Si vous sélectionnez un magasin ici, vous souhaitez restreindre les éléments de sélection du personnel.
Remplacez la méthode field_choices
de ʻadmin.RelatedFieldListFilteret Passez le nom du champ et la classe créée à
list_filter` en tant que taple.
Lors de la substitution, passez l'argument limit_choices_to
lors de l'appel de field.get_choices
.
La valeur à passer est un objet Q
ou un dictionnaire.
Affinez-vous une fois au magasin, vérifiez le type de requête émise et affinez-vous par cette valeur.
admin.py
from django.contrib import admin
from django.db.models import Q
from .models import Shift
class StaffFieldListFilter(admin.RelatedFieldListFilter):
def field_choices(self, field, request, model_admin):
shop_id = request.GET.get('staff__shop__id__exact')
limit_choices_to = Q(shop_id__exact=shop_id) if shop_id else None
return field.get_choices(include_blank=False, limit_choices_to=limit_choices_to)
class ShiftAdmin(admin.ModelAdmin):
list_display = (
'staff',
'start_at',
'end_at',
)
list_filter = (
'staff__shop',
('staff', StaffFieldListFilter),
)
admin.site.register(Shift, ShiftAdmin)
Lors de la sélection d'un magasin, le personnel était restreint par le magasin auquel ils appartiennent.
--
Si vous définissez list_filter
dans la classe ModelAdmin
, les éléments seront créés automatiquement, alors vérifiez cette partie.
Filtrer par [django / contrib / admin / templates / admin / change_list.html](https://github.com/django/django/blob/44a6c27fd461e1d2f37388c26c629f8f170e8722/django/contrib/admin/templates/admin/change_list.html# Lors de l'examen de la méthode de sortie de Confirmez que la balise de modèle ʻadmin_list_filter` est utilisée.
À propos de la ligne 66
{% block filters %}
{% if cl.has_filters %}
<div id="changelist-filter">
<h2>{% trans 'Filter' %}</h2>
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
</div>
{% endif %}
{% endblock %}
Défini à partir de la ligne 418 de [django / contrib / admin / templatetags / admin_list.py](https://github.com/django/django/blob/44a6c27fd461e1d2f37388c26c629f8f170e8722/django/contrib/admin/templatlist.min. A été découvert. La spécification «choice (cl)» semble être la méthode de création.
Ligne 418
@register.simple_tag
def admin_list_filter(cl, spec):
tpl = get_template(spec.template)
return tpl.render({
'title': spec.title,
'choices': list(spec.choices(cl)),
'spec': spec,
})
Revenez à changelist_view et voyez comment cl.filter_specs est créé.
Passé par près de la ligne 107 de django.contrib.admin.views.main Confirmez que list_filter est inséré dans la spécification.
Il semble que les fonctions et les taples peuvent être passés à list_filter en plus des chaînes de caractères.
Dans le cas de tapple, il semble être défini comme (field, field_list_filter_class)
.
Dans le cas d'une chaîne de caractères, il semble qu'un filtre approprié soit automatiquement créé.
Donc, spec est une instance de field_list_filter_class
, donc vérifions la méthode choice
de cette classe.
Si vous insérez print (type (spec)) ici, vous pouvez déterminer la classe que vous utilisez.
python:django/contrib/admin/views/main.py_l.107
if self.list_filter:
for list_filter in self.list_filter:
if callable(list_filter):
# This is simply a custom list filter class.
spec = list_filter(request, lookup_params, self.model, self.model_admin)
else:
field_path = None
if isinstance(list_filter, (tuple, list)):
# This is a custom FieldListFilter class for a given field.
field, field_list_filter_class = list_filter
else:
# This is simply a field name, so use the default
# FieldListFilter class that has been registered for
# the type of the given field.
field, field_list_filter_class = list_filter, FieldListFilter.create
if not isinstance(field, models.Field):
field_path = field
field = get_fields_from_path(self.model, field_path)[-1]
lookup_params_count = len(lookup_params)
spec = field_list_filter_class(
field, request, lookup_params,
self.model, self.model_admin, field_path=field_path
)
J'ai trouvé la méthode du problème dans ligne 197 de django / contrib / admin / filters.py.
Il semble que ce soit une méthode génératrice qui émet chaque élément après avoir émis les éléments pour annulation («tous»).
L'élément de sélection est self.lookup_choices
, donc nous allons ensuite vérifier celui-ci.
python:django/contrib/admin/filters.py_l.197
def choices(self, changelist):
yield {
'selected': self.lookup_val is None and not self.lookup_val_isnull,
'query_string': changelist.get_query_string(
{},
[self.lookup_kwarg, self.lookup_kwarg_isnull]
),
'display': _('All'),
}
for pk_val, val in self.lookup_choices:
yield {
'selected': self.lookup_val == str(pk_val),
'query_string': changelist.get_query_string({
self.lookup_kwarg: pk_val,
}, [self.lookup_kwarg_isnull]),
'display': val,
}
if self.include_empty_choice:
yield {
'selected': bool(self.lookup_val_isnull),
'query_string': changelist.get_query_string({
self.lookup_kwarg_isnull: 'True',
}, [self.lookup_kwarg]),
'display': self.empty_value_display,
}
Attribué par la méthode init dans la même classe. Le contenu semble être la méthode field_choices
dans la même classe.
python:django/contrib/admin/filters.py_l.161
def __init__(self, field, request, params, model, model_admin, field_path):
other_model = get_model_from_relation(field)
self.lookup_kwarg = '%s__%s__exact' % (field_path, field.target_field.name)
self.lookup_kwarg_isnull = '%s__isnull' % field_path
self.lookup_val = request.GET.get(self.lookup_kwarg)
self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull)
super().__init__(field, request, params, model, model_admin, field_path)
self.lookup_choices = self.field_choices(field, request, model_admin)
Ceci est également défini dans la même classe.
La méthode de champ get_choices
est appelée.
python:django/contrib/admin/filters.py_l.194
def field_choices(self, field, request, model_admin):
return field.get_choices(include_blank=False)
Ici, insérez à nouveau print (type (field))
et vérifiez la classe du champ.
La sortie est la suivante.
<class 'django.db.models.fields.related.ForeignKey'>
Archiver django / db / models / fields / related.py La définition de la méthode get_choicesn'a pas pu être confirmée. Il semble que la méthode de la classe héritée
Field` soit utilisée
Défini à la ligne 783 de django / db / models / fields / __ init__.py Découvert.
python:django/db/models/fields/__init__.py_l.783
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, limit_choices_to=None):
"""
Return choices with a default blank choices included, for use
as <select> choices for this field.
"""
blank_defined = False
choices = list(self.choices) if self.choices else []
named_groups = choices and isinstance(choices[0][1], (list, tuple))
if not named_groups:
for choice, __ in choices:
if choice in ('', None):
blank_defined = True
break
first_choice = (blank_choice if include_blank and
not blank_defined else [])
if self.choices:
return first_choice + choices
rel_model = self.remote_field.model
limit_choices_to = limit_choices_to or self.get_limit_choices_to()
if hasattr(self.remote_field, 'get_related_field'):
lst = [(getattr(x, self.remote_field.get_related_field().attname),
smart_text(x))
for x in rel_model._default_manager.complex_filter(
limit_choices_to)]
else:
lst = [(x.pk, smart_text(x))
for x in rel_model._default_manager.complex_filter(
limit_choices_to)]
return first_choice + lst
Confirmez que si limit_choices_to
est défini, une sorte de filtre semble être appliquée par la méthode complex_filter
.
Défini dans près de la ligne 855 de django / db / models / query.py.
Il n'y a pas de définition dans la classe Manager, mais l'emplacement de la définition est différent car la classe Manager dispose d'un processus pour appeler la méthode de la classe QuerySet.
python:django/db/models/query.py_l.855
def complex_filter(self, filter_obj):
"""
Return a new QuerySet instance with filter_obj added to the filters.
filter_obj can be a Q object or a dictionary of keyword lookup
arguments.
This exists to support framework features such as 'limit_choices_to',
and usually it will be more natural to use other methods.
"""
if isinstance(filter_obj, Q):
clone = self._chain()
clone.query.add_q(filter_obj)
return clone
else:
return self._filter_or_exclude(None, **filter_obj)
Comme vous pouvez le voir dans les commentaires, vous pouvez passer un objet Q ou un dictionnaire. Alors, je l'ai ajusté pour passer celui-ci.
Recommended Posts