Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Admin list_filter and ordering on annotated fields

I'm trying to filter in Django Admin on an annotated field, but getting a FieldDoesNotExist error.

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

The resulting error in Django Admin is:

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

I need to filter on event_start_date rather than eventsession_set__start_date because filtering ordering (edit) on the latter causes multiples rows per event (one for each session) to show up in the list view.

The error comes from the get_field method of django/db/models/options.py:

    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))

I'm on Django 3.2. Any ideas?

like image 266
Webucator Avatar asked Sep 11 '25 04:09

Webucator


1 Answers

You can filter by annotated fields with a custom List Filter.

And you can order by an annotated field by defining display methods and setting the "order field" accordingly.

This is an example how to add ordering and filter by annotated fields at django admin list page

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
like image 52
Valery Ramusik Avatar answered Sep 13 '25 19:09

Valery Ramusik