DRF ModelMultipleChoiceFilter - недопустимое значение в одном из многих параметров приводит к пустому списку - PullRequest
0 голосов
/ 25 августа 2018

У меня есть проект, который построен с использованием Django REST Framework. У меня есть модели Item и Tag, между которыми существует отношение многие ко многим. При запросе списка Item экземпляров я использую ModelMultipleChoiceFilter для фильтрации Item списка по тегам.

Вот мой filters.py:

import django_filters
from .models import Item, Tag

class ItemTagFilter(django_filters.FilterSet):
    tags = django_filters.ModelMultipleChoiceFilter(name='tags__text',
                                                    to_field_name='text',
                                                    queryset=Tag.objects.all(),
                                                    conjoined=False,)

    class Meta:
        model = Item
        fields = ['tags']

Как вы можете заметить, поскольку значение conjoined по умолчанию равно False, я ожидаю, что любой экземпляр Item, имеющий любой из текстов Tag, которые я запрашиваю, будет включен в итоговый список. И, похоже, он работает для существующих Tag экземпляров, записанных в базе данных.

Проблема в том, что, когда я ввожу несуществующий текст Tag, возвращается пустой список, даже если я отправил вместе с ним несколько Tag текстов, которые существуют в базе данных. (то есть я ожидаю, что фильтр вернет объединение элементов Item, в которых есть какие-либо теги, которые я отправил в своем запросе)

Я изучил документацию Django REST Framework и несколько соответствующих сообщений SO, таких как this , но не смог найти ни основную причину проблемы, ни ее решение. Буду признателен за любую помощь.

Вы можете найти мои models.py и views.py ниже, если вам нужна дополнительная информация.

models.py:

from django.db import models

class Tag(models.Model):
    text = models.CharField(max_length = 100, unique = True)
    ...

class Item(models.Model):
    info = models.CharField(max_length = 200)
    tags = models.ManyToManyField(Tag, related_name='items')

views.py

from rest_framework import generics
from .models import Item
from .filters import ItemTagFilter
import django_filters.rest_framework as filters
...

class ListCreateItemView(generics.ListCreateAPIView):
    queryset = Item.objects.all()
    filter_backends = (filters.DjangoFilterBackend,)
    filter_class = ItemTagFilter
    serializer_class = ItemSerializer

1 Ответ

0 голосов
/ 18 сентября 2018

Я немного посмотрел на код django_filters, и кажется, что нет никакой конфигурации / опции, чтобы заставить поле ModelMultipleChoiceFilter делать то, что вы хотите; ModelMultipleChoiceField, используемый фильтром, проверяет наличие всех связанных тегов с использованием ModelMultipleChoiceField Джанго, который:

Выдает ошибку ValidationError, если заданное значение недопустимо (недопустимый PK, отсутствует в наборе запросов и т. Д.)

Это означает, что список, из которого вы выбираете теги, используемые для фильтрации, будет пустым, если какой-либо из предоставленных тегов недействителен.

Однако вы можете обойти это, переопределив метод ModelMultipleChoiceField _check_values и вычислить набор запросов самостоятельно. Я думаю, что-то вроде этого должно работать:

class CustomField(django_filters.fields.ModelMultipleChoiceField):
    def _check_values(self, value):
        """
        Override the base class' _check_values method so our queryset is not
        empty if one of the items in value is invalid.
        """
        null = self.null_label is not None and value and self.null_value in value
        if null:
            value = [v for v in value if v != self.null_value]
        field_name = self.to_field_name or 'pk'
        result = list(self.queryset.filter(**{'{}__in'.format(field_name): value}))
        result += [self.null_value] if null else []
        return result


class CustomModelMultipleChoiceFilter(django_filters.ModelMultipleChoiceFilter):
    field_class = CustomField


class ItemTagFilter(django_filters.FilterSet):
    tags = CustomModelMultipleChoiceFilter(name='tags__text',
                                           to_field_name='text',
                                           queryset=Tag.objects.all(),
                                           conjoined=False,)

class Meta:
    model = Item
    fields = ['tags']

Надеюсь, это поможет!

...