Django 管理 list_filter 和带注释字段的排序

问题描述 投票:0回答:3

我尝试在 Django Admin 中过滤带注释的字段,但出现

FieldDoesNotExist
错误。

class Event(models.Model):
    name = models.CharField(max_length=50, blank=True)

class EventSession(models.Model):
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    start_date = models.DateTimeField()
    end_date = models.DateTimeField()


@admin.register(Event)
class EventAdmin(admin.ModelAdmin):
    ordering = ["event_start_date"]
    list_filter = ["event_start_date", "event_end_date"]

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        qs = qs.annotate(
            event_start_date=Min("eventsession_set__start_date"), # start of first day
            event_end_date=Max("eventsession_set__start_date"), # start of last day
        )
        return qs

Django Admin 中产生的错误是:

FieldDoesNotExist at /admin/events/event/
Event has no field named 'event_start_date'

我需要过滤

event_start_date
而不是
eventsession_set__start_date
,因为后者的过滤排序(编辑)会导致每个事件多行(每个会话一个)显示在列表视图中。

错误来自django/db/models/options.py

get_field方法

    try:
        # Retrieve field instance by name from cached or just-computed
        # field map.
        return self.fields_map[field_name]
    except KeyError:
        raise FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, field_name))

我使用的是 Django 3.2。有什么想法吗?

django django-models django-admin
3个回答
3
投票

您可以使用自定义列表过滤器按带注释的字段进行过滤。

并且您可以通过定义显示方法并相应地设置“订单字段”来按注释字段进行排序。

这是一个如何在 django 管理列表页面按注释字段添加排序和过滤的示例

class EventStartDateListFilter(admin.SimpleListFilter):
    title = "start_date"
    parameter_name = "start_date"

    def lookups(self, request, model_admin):
        return (
            ("week", "week"),
            # add other filters
        )

    def queryset(self, request, queryset):
        value = self.value()
        if value == "week":
            return queryset.filter(_event_start_date__gt=now() - timedelta(weeks=1))
    
        return queryset

@admin.register(Event)
class EventAdmin(admin.ModelAdmin):
    list_display = ["__str__", "event_start_date", "event_end_date"]

    ordering = ["event_start_date", "event_end_date"]
    list_filter = [EventStartDateListFilter]


    def event_start_date(self, obj):
       return obj._event_start_date

    event_start_date.admin_order_field = '_event_start_date'

    def event_end_date(self, obj):
        return obj._event_end_date

    event_end_date.admin_order_field = '_event_end_date'

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        qs = qs.annotate(
            _event_start_date=Min("eventsession_set__start_date"), # start of first day
            _event_end_date=Max("eventsession_set__start_date"), # start of last day
        )
        return qs

0
投票

我相信我已经找到了重复记录的解决方案 - 覆盖

get_changelist
如此处所述

def get_changelist(self, request, **kwargs):
    from django.contrib.admin.views.main import ChangeList

    class SortedChangeList(ChangeList):
        def get_queryset(self, request):
            qs = super().get_queryset(request)
            return qs.order_by("event_start_date")

    if request.GET.get("o"):
        return ChangeList

    return SortedChangeList

0
投票

您可以定义自己的

DateListFilter
:

import datetime

from django.contrib.admin import ListFilter
from django.contrib.admin.options import IncorrectLookupParameters
from django.core.exceptions import ValidationError, ImproperlyConfigured
from django.utils import timezone
from django.utils.translation import gettext_lazy as _


class DateListFilter(ListFilter):
    nullable = False
    parameter_name = None

    def __init__(self, request, params, model, model_admin):
        super().__init__(request, params, model, model_admin)
        if self.parameter_name is None:
            raise ImproperlyConfigured(
                "The list filter '%s' does not specify a 'parameter_name'."
                % self.__class__.__name__
            )
        if self.parameter_name in params:
            value = params.pop(self.parameter_name)
            self.used_parameters[self.parameter_name] = value

        self.field_generic = '%s__' % self.parameter_name
        self.date_params = {k: v for k, v in params.items() if k.startswith(self.field_generic)}

        lookup_choices = self.lookups(request, model_admin)
        if lookup_choices is None:
            lookup_choices = ()
        self.lookup_choices = list(lookup_choices)

    def lookups(self, request, model_admin):

        now = timezone.now()
        # When time zone support is enabled, convert "now" to the user's time
        # zone so Django's definition of "Today" matches what the user expects.
        if timezone.is_aware(now):
            now = timezone.localtime(now)

        today = now.date()
        tomorrow = today + datetime.timedelta(days=1)
        if today.month == 12:
            next_month = today.replace(year=today.year + 1, month=1, day=1)
        else:
            next_month = today.replace(month=today.month + 1, day=1)
        next_year = today.replace(year=today.year + 1, month=1, day=1)

        lookup_kwarg_since = '%s__gte' % self.parameter_name
        lookup_kwarg_until = '%s__lt' % self.parameter_name
        links = (
            (_('Any date'), {}),
            (_('Today'), {
                lookup_kwarg_since: str(today),
                lookup_kwarg_until: str(tomorrow),
            }),
            (_('Past 7 days'), {
                lookup_kwarg_since: str(today - datetime.timedelta(days=7)),
                lookup_kwarg_until: str(tomorrow),
            }),
            (_('This month'), {
                lookup_kwarg_since: str(today.replace(day=1)),
                lookup_kwarg_until: str(next_month),
            }),
            (_('This year'), {
                lookup_kwarg_since: str(today.replace(month=1, day=1)),
                lookup_kwarg_until: str(next_year),
            }),
        )
        if self.nullable:
            self.lookup_kwarg_isnull = '%s__isnull' % self.parameter_name
            links += (
                (_('No date'), {self.field_generic + 'isnull': 'True'}),
                (_('Has date'), {self.field_generic + 'isnull': 'False'}),
            )
        return links

    def has_output(self):
        return len(self.lookup_choices) > 0

    def value(self):
        """
        Return the value (in string format) provided in the request's
        query string for this filter, if any, or None if the value wasn't
        provided.
        """
        return self.used_parameters.get(self.parameter_name)

    def expected_parameters(self):
        return [self.parameter_name]

    def choices(self, changelist):
        for title, param_dict in self.lookup_choices:
            yield {
                'selected': self.date_params == param_dict,
                'query_string': changelist.get_query_string(param_dict, [self.field_generic]),
                'display': title,
            }

    def queryset(self, request, 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)

并在您的

admin.py

中使用它
class EventStartDateListFilter(DateListFilter):
    title = "start_date"
    parameter_name = "start_date"


@admin.register(Event)
class EventAdmin(admin.ModelAdmin):
    list_display = ["__str__", "event_start_date", "event_end_date"]

    ordering = ["event_start_date", "event_end_date"]
    list_filter = [EventStartDateListFilter]


    def event_start_date(self, obj):
       return obj._event_start_date

    event_start_date.admin_order_field = '_event_start_date'

    def event_end_date(self, obj):
        return obj._event_end_date

    event_end_date.admin_order_field = '_event_end_date'

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        qs = qs.annotate(
            _event_start_date=Min("eventsession_set__start_date"), # start of first day
            _event_end_date=Max("eventsession_set__start_date"), # start of last day
        )
        return qs
© www.soinside.com 2019 - 2024. All rights reserved.