Фильтр / порядок django остальные api на свойство = значение, но свойство является значением вместо поля - PullRequest
2 голосов
/ 20 сентября 2019

Рассмотрим объект, имеющий несколько свойств.

class Object(models.Model):
    name= Charfield(max_length=80)

class Property(models.Model):
    object= ForeignKey(Object, related_name='properties')
    name= Charfield(max_length=80)
    value= Charfield(max_length=80)

Я хотел бы предоставить API-доступный список просмотра для модели объекта, но мне не удается упорядочить ObjectList по значению свойства, а также присортировка ObjectList по значению свойства.

Чтобы сделать пример более осязаемым, вы можете представить, что объект имеет тип книги, а объект книги имеет, помимо прочих свойств, свойство с именем title и значением, таким как 20,000 Leagues Under the Sea.Я хотел бы иметь возможность указывать ?order_by=title и иметь возможность фильтровать ?title__contains=League (или хотя бы ?title="20,000 Leagues Under the Sea".

. Я уже пытался аннотировать объект с помощью title="20,000 Leagues Under the Sea", и яЯ могу показать заголовок в ObjectSerializer, как если бы это было поле, но тогда упорядочение по этому полю не работает.

Ответы [ 4 ]

3 голосов
/ 23 сентября 2019

Подумайте, что вы пытаетесь выполнить, и как оно сопоставляется с запросами SQL (использование ORM удобно, но вы всегда должны помнить, что все ваши вызовы ORM переводятся в SQL).У вас есть стол books, и у вас есть стол properties.Допустим,

create table books (
    id integer primary key,
);

create table properties (
    id integer primary key,
    name text,
    value text,
    book_id integer,
)

И затем вы вставляете записи, подобные этой

insert into books set (id) values (1);
insert into properties set (id, name, value, book_id) values (1, 'title', 'A Book', 1)

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

select b.id, p.value as "title"
from books as b
join properties as p on (b.id = p.book_id)
where p.name = 'title'
order by p.value

К сожалению, построение запроса выше с использованием ORM не тривиально.Причина этого заключается в том, что, вводя имя свойства и значение в виде данных вместо записи данных в определенное поле, вы подрываете свою способность ссылаться на конкретные свойства.Вы не можете просто ссылаться на название книги как на конкретное поле, потому что это не поле.Что если в книге нет записи свойства title?Что если у него более одного?

Вы можете делать то, что хотите, используя функцию Subquery () из ORM API, но я никогда не использовал ее лично, поэтому я не уверен, каков точный синтаксис для этогобыло бы.Возможно, что-то вроде этого

title = Property.objects.filter(name='title', book_id=OuterRef('pk')).values('value')[:1]
books = Object.objects.annotate(title=Subquery(title)).order_by('title')

https://docs.djangoproject.com/en/2.2/ref/models/expressions/#subquery-expressions

Тем не менее, это хрупко и сложно рассуждать, и вы можете пересмотреть архитектуру вашей базы данных.

1 голос
/ 28 сентября 2019

Если у вас есть следующие модели:

class Object(models.Model):
    name= Charfield(max_length=80)

class Property(models.Model):
    object= ForeignKey(Object, related_name='properties')
    name= Charfield(max_length=80)
    value= Charfield(max_length=80)

, тогда у вас есть экземпляры Object, имеющие только одно свойство, с name='title'.

В этом параметре я быс чем-то вроде:

Object.objects.filter(
    properties__name=Value('title'), 
    properties__value__icontains=request.data['title']
    )

для фильтрации.

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

Этот ответ говорит о том, что с вашей структурой трудно справиться.Действительно, в вашем случае, если вы добавите в какой-то момент авторов ваших книг как экземпляр Object, эти объекты вообще не будут иметь свойства title ...

Если вы не можете отказаться от этой БДархитектура, я бы посоветовал посмотреть поля JSON или поля HStore .DjangoORM предоставляет способы запроса этих полей в соответствии с тем, что вы пытаетесь достичь.Однако следует помнить, что это все равно будет медленнее, чем использование традиционной архитектуры для РСУБД.

В противном случае создайте «настоящую» модель Book, которая имеет заголовок, и в этом случае все намного проще;)

РЕДАКТИРОВАТЬ: также у вас есть возможность написать свой запрос непосредственно в SQL, либо выполненный непосредственно из cursor (см. здесь ), либо из набора запросов модели (см. здесь ).Я использовал оба подхода в различных сценариях, вам нужно быть осторожным с тем, как вы обрабатываете пользовательский ввод, но он должен работать =)

1 голос
/ 23 сентября 2019

То, что у вас есть, выглядит как стандартная EAV модель.

Вы можете посмотреть на пакет типа django-eav2 и посмотреть, поможет ли это несколько.Я не думаю, что он обрабатывает порядок, что разумно, так как на самом деле это трудно сделать с помощью ORM.

Рассматривали ли вы возможность изменения вашей модели, чтобы использовать столбец JSON или HStore (если вы используете postgres)?В зависимости от вашей модели данных они могут отлично работать, а также легко индексируются, ищутся и упорядочиваются.

Если вы хотите сделать это самостоятельно, вам нужно написать несколько вещей:

  • Вспомогательные функции для фильтрации и упорядочения по полям в таблице атрибутов
  • Пользовательская OrderingBackend реализация
  • Пользовательская FilterField для обработки фильтрации по атрибуту
  • Пользовательская DjangoFilterBackend в зависимости от того, как вы хотите, чтобы URL-адреса вели себя

Если вы можете предоставить лучший пример того, как вы хотите, чтобы URL выглядел, тогда вам будет легче дать некоторые рекомендации.Поскольку у ваших атрибутов может быть любое имя, у вас есть несколько вариантов:

  1. Может ли параметр url интерпретироваться как имя атрибута (? Title =)
  2. Используете ли вы собственный URLформат, например ?attr=title&value=The Big Bang
  3. Вам нужно фильтровать на уровне Object?
  4. Можно ли фильтровать / заказывать более чем по одной вещи за раз?
  5. Как вы обрабатываете разные типы данных (date / int / string / etc?)

Извините, но это слишком большой вопрос для одного ответа здесь.Хотя это возможно, если вы попытаетесь достаточно усердно, создавать EAV-запросы непросто, и они не быстро.

0 голосов
/ 23 сентября 2019

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

class ObjectViewSet(ModelViewSet):

    def get_queryset(self):
        queryset = super(ObjectViewSet, self).get_queryset()

        order_by = self.request.query_params.get('order_by', '')
        if order_by:
            order_by_name = order_by.split(' ')[1]
            order_by_sign = order_by.split(' ')[0]
            order_by_sign = '' if order_by_sign == 'asc' else '-'
            queryset = queryset.order_by(order_by_sign + order_by_name)

        return queryset
...