Как использовать поля сериализатора DRF в качестве полей фильтра django-filter?
Я работаю с фреймворком Django REST и django-filter для реализации фильтрации API.
Я создал пользовательские поля сериализатора (например, JalaliDateField
, которые преобразуют даты по-джалалийски в григорианские и применяют настройки часового пояса Django).
Я ожидал, что смогу просто передать эти поля сериализатора в django_filters.Filter
, установив field_class
, но оказалось, что Filter.field_class
совместим только с django.forms.Field
, даже при использовании django_filters.rest_framework
.
Итак, мой вопрос таков: Есть ли простой способ заставить django-фильтры работать напрямую с полями сериализатора DRF?
<время работы/>То, что я пробовал
- Наивное подключение полей сериализатора DRF:
class JalaliDateFilter(django_filters.Filter):
field_class = MyCustomJalaliDateSerializerField
Это не удается, поскольку django-filters ожидает forms.Field
, а не DRF serializers.Field
.
- Предлагаемое решение №1: Напишите оболочку, которая адаптирует поля DRF к полям формы Django Вот минимальный набросок:
from django import forms
from rest_framework import serializers
class DRFFieldFormWrapper(forms.Field):
"""
Wrap a DRF serializer field so it can behave like a Django form field.
"""
def __init__(self, drf_field: serializers.Field, *args, **kwargs):
self.drf_field = drf_field
kwargs.setdefault("required", drf_field.required)
kwargs.setdefault("label", getattr(drf_field, "label", None))
super().__init__(*args, **kwargs)
def to_python(self, value):
if value in self.empty_values:
return None
return self.drf_field.run_validation(value)
def prepare_value(self, value):
return self.drf_field.to_representation(value)
Затем пользовательский фильтр:
import django_filters
class DRFFieldFilter(django_filters.Filter):
def __init__(self, *args, drf_field=None, **kwargs):
if drf_field is None:
raise ValueError("drf_field is required")
kwargs["field_class"] = lambda *a, **kw: DRFFieldFormWrapper(drf_field(), *a, **kw)
super().__init__(*args, **kwargs)
Использование:
class MyFilterSet(django_filters.FilterSet):
jalali_date = DRFFieldFilter(drf_field=MyCustomJalaliDateSerializerField)
- Предлагаемое решение №2: Переопределить мои поля DRF как классы Django
forms.Field
По сути, я дублирую мою пользовательскую логику полей сериализатора (анализ даты Jalali, обработка часовых поясов) в поля формы и использую их непосредственно вdjango_filters.Filter
.
Мой вопрос
- Существует ли существующий / наилучший способ заставить
django-filter
напрямую использовать поля сериализатора DRF? - Один из двух вариантов, описанных выше (перенос полей DRF или дублирование их в виде полей формы), который считается более чистым и удобным в обслуживании?
django-filter построен поверх формуляров Django.fields, а не сериализаторов DRF.Field, поэтому вы не можете напрямую подключить поля сериализатора. Не существует первоклассного способа “использовать поля сериализатора в фильтрах”.
Таким образом, у вас остается два варианта:
Перенесите поля сериализатора в форму.Адаптер полей (например, ваш DRFFormFieldWrapper). Это разумный подход, если вы хотите повторно использовать точную логику синтаксического анализа / проверки, которая уже есть в полях DRF. Это упрощает работу, но вам нужно будет поддерживать оболочку.
Реализуйте синтаксический анализ на уровне форм (т.е. напишите пользовательские формы.Поле для дат Джалали). Это более идиоматичное решение в экосистеме Django, поскольку фильтры концептуально относятся к уровню форм, а не к уровню сериализатора.
Если вы заботитесь о ремонтопригодности и согласовании с остальной частью стека Django, вариант #2 является “наилучшей практикой”. Если для вас важнее избежать дублирования и вам удобен тонкий адаптер, то подойдет вариант #1.
Нет встроенного способа объединить эти два уровня, поэтому выбор зависит от того, хотите ли вы сохранить идиоматичность (на основе форм) или сухость (на основе оболочки).