Как реализовать фильтр диапазона дат в Django Admin с помощью настраиваемого поля?

Я хотел бы реализовать фильтрацию диапазона дат для модели в Django Admin по значению, которое на самом деле не существует в модели как поле. Я хочу использовать средство выбора диапазона дат, например DateRangeFilter из пакета django-admin-rangefilter.

Допустим, в модели есть поле date и мы хотим отобразить объекты, для которых date - 7 лежит между указанным диапазоном (просто искусственный пример).

Я создал собственный класс фильтра.

class CustomDateFilter(DateRangeFilter):
   title = "custom_date"

   def queryset(self, request, queryset):
       queryset = queryset.annotate(custom_date=F("date") - 7)
       if self.form.is_valid():
           validated_data = dict(self.form.cleaned_data.items())
           if validated_data:
               return queryset.filter(
                   **self._make_query_filter(request, validated_data)
                )
       return queryset

Но его использование приводит либо к ошибке FieldDoesNotExist ... , если используется так:

@admin.register(SomeModel)
class SomeModelAdmin(admin.ModelAdmin):
    list_filter = [("custom_date", CustomDateFilter)]

or

SystemCheckError: System check identified some issues:

ERRORS:
<class '...SomeModelAdmin'>: (admin.E114) The value of 'list_filter[0]' must not inherit from 'FieldListFilter'.

если использовать так:

@admin.register(SomeModel)
class SomeModelAdmin(admin.ModelAdmin):
    list_filter = [CustomDateFilter]

Как я могу достичь того, что я хочу сделать?


person vemikhaylov    schedule 20.01.2021    source источник


Ответы (1)


Прежде всего, вы пытаетесь:

queryset = queryset.annotate(custom_date=F("date") - 7)

что неправильно. date — DateField (формат даты по умолчанию в Django: гггг-мм-дд). Мы можем сделать что-то вроде:

from datetime import datetime, timedelta
queryset = queryset.annotate(custom_date=F("date") - timedelta(days=7))

Вы переопределяете приведенный ниже метод набора запросов по умолчанию класса DateRangeFilter из пакета django-admin-rangefilter.

def queryset(self, request, queryset):
    if self.form.is_valid():
        validated_data = dict(self.form.cleaned_data.items())
        if validated_data:
            return queryset.filter(
                **self._make_query_filter(request, validated_data)
            )
    return queryset

Кажется, вы пытаетесь добавить динамическое поле после фильтрации. Попробуйте сделать это:

queryset = queryset.annotate(custom_date=F("date") - timedelta(days=7))

перед фильтрацией.

Таким образом, метод запроса будет выглядеть примерно так:

def queryset(self, request, queryset):
    from datetime import datetime, timedelta
    from django.db.models import DateField, F
    queryset = queryset.annotate(custom_date=F("date") - timedelta(days=7), output_field=DateField())
    if self.form.is_valid():
        validated_data = dict(self.form.cleaned_data.items())
        if validated_data:
            return queryset.filter(
                **self._make_query_filter(request, validated_data)
            )
    return queryset
person Siva Sankar    schedule 20.01.2021
comment
Переход на timedelta приводит к следующей ошибке (по крайней мере, при использовании PostgreSQL): django.core.exceptions.FieldError: Expression содержит смешанные типы: DateField, DurationField. Вы должны установить output_field. Чтобы узнать больше об операциях Date + Integer, обратитесь к postgresql.org/docs/13/ функции-datetime.html. О порядке действий. Однако это имеет смысл, но не решает мою проблему, поскольку упомянутые мной ошибки возникают до фактического выполнения метода набора запросов. - person vemikhaylov; 21.01.2021
comment
Изменил ответ (добавлено поле вывода). Это подойдет. Теперь, что касается операций «Дата + целое число», которые вы мне прислали, это postgres. Я думаю, что мы не можем напрямую выполнять операции postgres в django. Нам нужно выполнить необработанный запрос postgresql в Django, что в данном случае не подходит. - person Siva Sankar; 21.01.2021
comment
Собственно, сам запрос там работает, неважно, какой подход лучше использовать. Основная проблема заключается в другом — заставить администратора Django выполнить этот код для пользовательского поля, которого на самом деле нет в модели. - person vemikhaylov; 21.01.2021
comment
Вы пытались добавить output=DateField, я имею в виду, что я изменил свой ответ в последнем разделе кода. - person Siva Sankar; 21.01.2021
comment
Я пытался, но это не имеет значения, метод queryset даже не вызывается, потому что раньше он терпит неудачу. - person vemikhaylov; 21.01.2021
comment
django.core.exceptions.FieldError: Выражение содержит смешанные типы: DateField, DurationField. Вы получили эту ошибку, не так ли? Это означает, что он вызвал метод queryset. Я думаю, что пакет django-admin-filter не поддерживает фильтрацию динамических полей django. - person Siva Sankar; 21.01.2021
comment
Нет, я получил эту ошибку при вызове вашего кода вручную. - person vemikhaylov; 21.01.2021