DRF - фильтр набора запросов, использующий поиск полей в SlugRelatedField - PullRequest
0 голосов
/ 09 июня 2018

Я изо всех сил пытаюсь выяснить, как запустить фильтр наборов запросов, используя «field__contains» в SlugRelatedField.У меня есть простая модель Book и модель Tag, которая выглядит следующим образом:

class Book(models.Model):
  title = models.CharField(max_length=100)
  authors = models.ManyToManyField(Author)
  publisher = models.ForeignKey(Publisher)
  publication_date = models.DateField()

class MetaTag(models.Model):
  book = models.ManyToManyField('Book', related_name='meta_tags',
  help_text='The book this meta tag belongs to')
  value = models.CharField(max_length=400, unique=True, help_text='Meta tag value')

class BookSerializer(serializers.HyperlinkedModelSerializer):
  class BookHyperlink(serializers.HyperlinkedIdentityField):
    """A Hyperlink field for book details"""

      def get_url(self, obj, view_name, request, format):
        url_kwargs = {
            'pk': obj.id,
        }
        return reverse(view_name, kwargs=url_kwargs, request=request, format=format)
  url = BookHyperlink(view_name='book-detail')
  meta_tags = CreatableSlugRelatedField(many=True, slug_field='value', queryset=MetaTag.objects.all())

  class Meta:
    model = Book
    fields = (
        'id',
        'title',
        'publisher',
        'publication_date',
        'meta_tags',
        'url'
    )

class MetaTagSerializer(serializers.ModelSerializer):

  class Meta:
    model = MetaTag
    fields = ('id', 'book', 'value',)

class CreatableSlugRelatedField(serializers.SlugRelatedField):

  def to_internal_value(self, data):
    try:
        return self.get_queryset().get_or_create(**{self.slug_field: data})[0]
    except ObjectDoesNotExist:
        self.fail('does_not_exist', slug_name=self.slug_field, value=smart_text(data))
    except (TypeError, ValueError):
        self.fail('invalid')

  class Meta:
    model = MetaTag
    fields = ('id', 'book', 'value', )

Теперь в моем BooksView я хочу иметь возможность фильтровать набор запросов по значению meta_tags.Я пробовал следующее с поиском поля "__contains":

class Books(viewsets.ModelViewSet):
  """Default view for Book."""

  queryset = Book.objects.all()
  serializer_class = BookSerializer
  permission_classes = (IsAuthenticated, )

  filter_backends = (DjangoFilterBackend,)
  filter_fields = tuple(f.name for f in Book._meta.get_fields())

  def get_queryset(self):

    search_pattern = self.request.query_params.get('search', None)
    if search_pattern is not None and search_pattern is not '':
        self.queryset = self.queryset.filter(meta_tags__contains = search_pattern)
    return self.queryset

def get_object(self):
    if self.kwargs.get('pk'):
        return Book.objects.get(pk=self.kwargs.get('pk'))

Но я получаю следующую ошибку от django:

File "~ MyProject / venv / lib / python3.6 / site-packages / django / db / models / sql / query.py ", строка 1076, в build_lookup повысить FieldError ('Связанное поле получило недопустимый поиск: {}'. Format (lookup_name)) django.core.exceptions.FieldError: Related Field получил недопустимый поиск: содержит

Что, как я понимаю, означает, что, поскольку «meta_tags» не является обычным массивом или текстовым полем, поиск содержимого поля не может быть применен к этому полю.

Как лучше всего фильтровать набор запросов в этом случае по значению meta_tags?

1 Ответ

0 голосов
/ 10 июня 2018

Эксперт по django, с которым я консультировался по этой проблеме, предложил попробовать добавить поле «slug_field» (в данном случае «__value») к поиску поля «__contains» при использовании с внешней моделью.

Это былоне документировано нигде или даже в официальной документации django на https://docs.djangoproject.com/en/2.0/ref/contrib/postgres/fields/#contains,, поэтому у меня не было возможности узнать, как это работает, но это решение действительно работает:

queryset = queryset.filter(meta_tags__value__contains=search_pattern)

Это действительно имеет смысл, когда вы смотритеглубже в модели MetaTag, поскольку «значение» - это внутреннее поле модели meta_tags:

class MetaTag(models.Model):

    book = models.ManyToManyField('Book', related_name='meta_tags',
                                 help_text='The book this meta tag belongs to')
    value = models.CharField(max_length=400, unique=True, help_text='Meta tag value')

    def __str__(self):
        return '%s > %s' % (self.channel, self.value)

Причина, по которой было не так очевидно добавлять __value на первое место, заключается в том, что массив meta_tags (массив объектов) выравнивается с помощью сериализатора SlugRelatedField, где проецируется только поле slug_field, а остальные поля опускаются.Таким образом, окончательный вывод массива meta_tags плоский:

meta_tags: ['tag1','tag2']

вместо:

meta_tags: [{book: 'a', value: 'tag1'},{book: 'a', value: 'tag2'}]

Но поскольку сериализация в DRF django выполняется на поздней стадии (после завершения набора запросов),следует рассмотреть оригинальную схему поля.

Надеюсь, это когда-нибудь спасет чью-то головную боль.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...