我尝试在 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 管理列表页面按注释字段添加排序和过滤的示例
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
我相信我已经找到了重复记录的解决方案 - 覆盖
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
您可以定义自己的
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