[PYTHON] Django-Verwaltungsbildschirm list_filter-Anpassung

Beachten Sie, dass ich untersucht habe, wie die Elemente von list_filter durch das Ergebnis eines anderen list_filter eingegrenzt werden können.

Was du machen willst

Geben Sie für das Modell, in dem die Eins-zu-Viele-Beziehung verkettet ist, das Verkettungsquellmodell in list_filter an. Zu diesem Zeitpunkt möchte ich die Elemente von list_filter entsprechend den ausgewählten Elementen reduzieren.

Betrachten Sie das folgende Modell als Beispiel, da es in Worten schwer zu verstehen ist.

Kobito.R4Pvai.png

Geben Sie das Geschäft und die Mitarbeiter als Listenfilter auf dem Schichtverwaltungsbildschirm an. Wenn Sie hier ein Geschäft auswählen, möchten Sie die Auswahlelemente der Mitarbeiter eingrenzen.

Kobito.me0mLo.png

Fazit

Überschreiben Sie die field_choices Methode von admin.RelatedFieldListFilter Übergeben Sie den Feldnamen und die erstellte Klasse als Taple an "list_filter".

Übergeben Sie beim Überschreiben das Argument "limit_choices_to", wenn Sie "field.get_choices" aufrufen. Der zu übergebende Wert ist ein Q-Objekt oder ein Wörterbuch.

Grenzen Sie einmal im Geschäft ein, überprüfen Sie, welche Art von Abfrage ausgegeben wird, und schränken Sie diesen Wert ein.

Code

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)

Ergebnis

Kobito.4xD8xK.png

Kobito.lOpw7b.png

Bei der Auswahl eines Geschäfts wurde das Personal durch das Geschäft eingegrenzt, zu dem es gehört.

--

Was ich nachgeschlagen habe

Wenn Sie "list_filter" in der "ModelAdmin" -Klasse definieren, werden Elemente automatisch erstellt. Überprüfen Sie daher diesen Teil.

Überprüfen Sie die Anzeigevorlage

Filtern nach django / contrib / admin / templates / admin / change_list.html Bei der Prüfung der Ausgabemethode von Stellen Sie sicher, dass das Vorlagen-Tag "admin_list_filter" verwendet wird.

Über Zeile 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 %}

Überprüfen Sie das Ausgabevorlagen-Tag

Definiert aus Zeile 418 von django / contrib / admin / templatetags / admin_list.py Wurde entdeckt. Die Spezifikation selection (cl ) scheint die Erstellungsmethode zu sein.

Zeile 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,
    })

Bestätigung der Spezifikationsidentität

Gehen Sie zurück zu changelist_view und sehen Sie, wie cl.filter_specs erstellt wird.

Vorbei an in der Nähe der Zeile 107 von django.contrib.admin.views.main Stellen Sie sicher, dass der list_filter in die Spezifikation eingedrückt ist. Es scheint, dass Funktionen und Taples zusätzlich zu Zeichenfolgen an list_filter übergeben werden können. Im Fall von tapple scheint es als "(field, field_list_filter_class)" definiert zu sein. Bei einer Zeichenfolge wird anscheinend automatisch ein geeigneter Filter erstellt. Spec ist also eine Instanz von field_list_filter_class. Überprüfen wir also die selection-Methode dieser Klasse.

Wenn Sie hier print (type (spec)) einfügen, können Sie bestimmen, welche Klasse Sie verwenden.

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
                    )

RelatedFieldListFilter / Auswahlmethode

Die problematische Methode wurde in [Zeile 197 von django / contrib / admin / filter.py] gefunden (https://github.com/django/django/blob/44a6c27fd461e1d2f37388c26c629f8f170e8722/django/contrib/admin/filters.py#L19). Es scheint sich um eine Generatormethode zu handeln, die jeden Artikel nach der Ausgabe der Artikel zur Stornierung ausgibt ("alle"). Das Auswahlelement ist "self.lookup_choices", daher werden wir dieses als nächstes überprüfen.

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,
            }

Überprüfen Sie self.lookup_choices

Wird von der init -Methode in derselben Klasse zugewiesen. Der Inhalt scheint die Methode field_choices in derselben Klasse zu sein.

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)

Überprüfen Sie die Methode field_choices

Dies wird auch in derselben Klasse definiert. Die Feldmethode get_choices wird aufgerufen.

python:django/contrib/admin/filters.py_l.194


    def field_choices(self, field, request, model_admin):
        return field.get_choices(include_blank=False)

Fügen Sie hier erneut "print (Typ (Feld))" ein und überprüfen Sie die Feldklasse. Die Ausgabe ist wie folgt.

<class 'django.db.models.fields.related.ForeignKey'>

Überprüfen Sie die ForeignKey / get_choices-Methode

Überprüfen Sie in django / db / models / fields / related.py Die Definition der Methode get_choices konnte nicht bestätigt werden. Es scheint, dass die Methode der geerbten Field-Klasse verwendet wird

Überprüfen Sie die Field / get_choices-Methode

Definiert in Zeile 783 von django / db / models / fields / __ init__.py. Entdeckt.

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

Bestätigen Sie, dass, wenn limit_choices_to gesetzt ist, eine Art Filter von der Methode complex_filter angewendet zu werden scheint.

Überprüfen Sie complex_filter

Definiert in in der Nähe der Zeile 855 von django / db / models / query.py.

In der Manager-Klasse gibt es keine Definition, aber der Definitionsspeicherort unterscheidet sich, da die Manager-Klasse über einen Prozess zum Aufrufen der Methode der QuerySet-Klasse verfügt.

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)

Wie Sie in den Kommentaren sehen können, können Sie ein Q-Objekt oder ein Wörterbuch übergeben. Also habe ich es angepasst, um dieses zu bestehen.

Recommended Posts

Django-Verwaltungsbildschirm list_filter-Anpassung
Anpassung des Django-Verwaltungsbildschirms Erster Schritt
Django Management Screen Reverse Memo
Django2-Bildschirmadditionsfluss
Optimieren der Django-Administrationsseite
Wenn Sie den Benutzernamen und das Passwort des Verwaltungsbildschirms in Django vergessen haben
Die Geschichte, dass "calendar.day_abbr" auf dem Admin-Bildschirm von django nicht aktualisiert werden konnte
So registrieren Sie nur eine Daten auf dem Django-Verwaltungsbildschirm