Сделайте "django-filter" динамическим
У меня есть django-фильтр, который работает для одной категории, и я пытаюсь сделать его динамическим, чтобы он работал для всех категорий сайта электронной коммерции.
Вот модель:
class Listing(models.Model):
sub_category = models.ForeignKey(SubCategory, on_delete=models.SET_NULL, related_name="sub_category", blank=False, null=True)
is_active = models.BooleanField(default=True, null=True, blank=True)
facilities = models.JSONField(default=dict, null=True, blank=True)
nearby = models.JSONField(default=dict, null=True, blank=True)
details = models.JSONField(default=dict, null=True, blank=True)
# ... OTHER FIELDS
Вот версия, которая работает:
class ListingFilter(django_filters.FilterSet):
class Meta:
model = Listing
fields = {
'sub_category__sub_category_name': ['contains'],
'is_active': ['exact'],
}
country = django_filters.CharFilter(field_name="details__country", lookup_expr="icontains")
min_price = django_filters.NumberFilter(
method=lambda queryset, _, value: queryset.filter(details__price__gte=float(value))
)
max_price = django_filters.NumberFilter(
method=lambda queryset, _, value: queryset.filter(details__price__lte=float(value))
)
kindergarten = django_filters.BooleanFilter(field_name="nearby__kindergarten", lookup_expr="exact")
# ...OVER 40 OTHER FIELDS
class ListingNode(DjangoObjectType):
class Meta:
model = Listing
interfaces = (graphene.relay.Node, )
class Query(graphene.ObjectType):
one_listing = graphene.relay.Node.Field(ListingNode)
all_listingss = DjangoFilterConnectionField(ListingNode, filterset_class=ListingFilter)
Вот что я попытался сделать динамическим:
class ListingFilter(django_filters.FilterSet):
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
for field in Listing._meta.get_fields():
field_name = (field.__str__().split('.'))[-1]
if field_name == 'details':
cls.get_declared_filters['min_price'] = \
django_filters.NumberFilter(
field_name='details__price',
lookup_expr='gte',
method='details_filter'
)
class Meta:
model = Listing
fields = {
'sub_category__sub_category_name': ['contains'],
'is_active': ['exact'],
}
def details_filter(self, queryset, name, value):
return queryset.filter(details__price__gte=float(value))
Проблема в том, что я не уверен, к какому методу django-filter подключиться, как вы можете видеть cls.get_declared_filters['min_price']
, я перепробовал много подобных методов.
Итак, я пытаюсь добавить дополнительные поля в класс ListingFilter
.
Для тех, кто столкнулся с этой проблемой, вот мое решение:
def FilterFactory( model_class, class_name):
sub_fields = SubCategory.objects.filter(category__category_name="real_estate").values('details', 'facilities', 'nearby')[0]
class_vars = {
'min_price': django_filters.NumberFilter(
method=lambda queryset, _, value: queryset.filter(details__price__gte=float(value))
)
}
class_vars['max_price'] = django_filters.NumberFilter(
method=lambda queryset, _, value: queryset.filter(details__price__lte=float(value))
)
for key in list(sub_fields.keys()):
for item in sub_fields[key]:
if item[1] == 'string':
class_vars[item[0]] = django_filters.CharFilter(
field_name=f'{key}__{item[0]}',
lookup_expr=item[2],
)
elif item[1] == 'number':
model_field = f'{key}__{item[0]}__{item[2]}'
class_vars[item[0]] = django_filters.NumberFilter(
field_name=f'{key}__{item[0]}',
method=lambda queryset, _, value: queryset.filter(Q((model_field, float(value))))
)
elif item[1] == 'bool':
model_field = f'{key}__{item[0]}__{item[2]}'
class_vars[item[0]] = django_filters.BooleanFilter(
field_name=f'{key}__{item[0]}',
lookup_expr=item[2],
)
class_vars['Meta'] = type( 'Meta', (object, ), {
"model": model_class,
"fields": {
'sub_category__sub_category_name': ['contains'],
'is_active': ['exact'],
}
}
)
return type( class_name, (django_filters.FilterSet, ), class_vars )
class ListingNode(DjangoObjectType):
class Meta:
model = Listing
interfaces = (graphene.relay.Node, )
class Query(graphene.ObjectType):
one_listing = graphene.relay.Node.Field(ListingNode)
all_listingss = DjangoFilterConnectionField(ListingNode, filterset_class=FilterFactory( Listing, 'ListingFilter'))
Вот форма кверисета sub_fields:
{
'details': [
['bedrooms', 'number', 'contains'], ['building_size', 'number', 'gte'],
['built_year', 'number', 'gte'], ['city', 'string', 'contains'],
['country', 'string', 'contains'], ['floors', 'number', 'gte'],
['max_price', 'number', 'lte'], ['plot_size', 'number', 'gte'],
['min_price', 'number', 'gte'], ['renovated_year', 'number', 'gte'],
['state', 'string', 'contains'], ['street_address', 'string', 'contains'],
['title', 'string', 'contains'], ['zip', 'string', 'contains']
],
'facilities': [
['air_condition', 'bool', 'exact'], ['car_wash_area', 'bool', 'exact'],
['cats_allowed', 'bool', 'exact'], ['child_friendly', 'bool', 'exact'],
['dog_keepers', 'bool', 'exact'], ['dogs_allowed', 'bool', 'exact'],
['electricity', 'bool', 'exact'], ['fireplace', 'bool', 'exact'],
['garage', 'bool', 'exact'], ['gym_room', 'bool', 'exact'],
['hot_tub', 'bool', 'exact'], ['internet', 'bool', 'exact'],
['maintenance_24_hrs', 'bool', 'exact'], ['media_room', 'bool', 'exact'],
['microwave', 'bool', 'exact'], ['oven', 'bool', 'exact'], ['pool', 'bool', 'exact'],
['pool_table', 'bool', 'exact'], ['refrigerator', 'bool', 'exact'], ['water', 'bool', 'exact']
],
'nearby': [
['beach', 'bool', 'exact'], ['car_wash', 'bool', 'exact'], ['church', 'bool', 'exact'],
['cinema', 'bool', 'exact'], ['dog_park', 'bool', 'exact'], ['grocery_store', 'bool', 'exact'],
['gym', 'bool', 'exact'], ['high_school', 'bool', 'exact'], ['hiking', 'bool', 'exact'],
['kindergarten', 'bool', 'exact'], ['library', 'bool', 'exact'], ['mosque', 'bool', 'exact'],
['night_club', 'bool', 'exact'], ['primary_school', 'bool', 'exact'],
['secondary_school', 'bool', 'exact'], ['shopping_center', 'bool', 'exact'],
['synagogue', 'bool', 'exact'], ['theater', 'bool', 'exact'], ['university', 'bool', 'exact'],
['user_fields', 'bool', 'exact']
]
}
Я буду писать о решении на Medium, ловите его там.