Django 4.x - Conditional QuerySet для пагинации и отношений "многие-ко-многим

Отказ от ответственности: Я искал, и вопрос, решающий эту конкретную задачу, не был найден на момент публикации.

Требование

Для представления на основе классов мне нужно реализовать пагинацию для набора запросов, полученного через отношения "многие ко многим". Вот требование с более конкретным описанием:

  • Многие библиотечные записи могут принадлежать многим Коллекциям
  • Для большинства (но не обязательно всех) Коллекций требуются веб-страницы, поэтому мне нужно создавать представления/шаблоны/урлы на основе того, что клиент определяет как необходимое
  • Каждая страница коллекции, отображающая соответствующие записи библиотеки, требует пагинации, так как может быть 100 записей для отображения.

Первый подход

Поэтому, учитывая это требование, я подошел к делу так, как обычно подхожу к созданию CBV с пагинацией. Однако такой подход не позволил мне выполнить требование. Я быстро обнаружил, что метод Pagination в CBV создавал объект на основе объявленной модели, но связь "многие ко многим" не работала.

Я исследовал использование объекта в шаблоне, но после нескольких попыток у меня ничего не получилось. Мне нужно отобразить объекты записей библиотеки, но отношения "многие ко многим" требуют, чтобы я сделал это после определения записей на основе коллекции, к которой они принадлежат.

Надеюсь, что этот вопрос поможет кому-нибудь еще. Если, читая следующий подход, вы сможете придумать, как рефакторить/оптимизировать, я буду рад узнать. Примечание: я намеренно не реализовал Pythonic List Comprehension для моего личного предпочтения читабельности.

В итоге я добавил get_queryset() к:

  • Запросите коллекцию на предмет принадлежащих ей записей, чтобы затем
  • Создать список идентификаторов записей, чтобы затем
  • Вернуть QuerySet, отфильтровав для pk__in (пк существует в списке library_record_ids)

Вот полученный код.

    def get_queryset(self):
        library_record_ids = []
        library_collection = CollectionOrder.objects.filter(
            collection__collection='<Collection Name>'
        ).order_by('order_number')
        for record in library_collection:
            library_record_ids.append(record.record.id)
        return LibraryRecord.objects.filter(pk__in=library_record_ids)

Требование было выполнено. Я приветствую конструктивную критику. Мое намерение поделиться этим Q&A состоит в том, чтобы попытаться немного отблагодарить сообщество Stack Overflow, которое так хорошо послужило мне с начала этого путешествия в Django.

Пожалуйста, неработайте с record.record.id: это будет каждый раз делать запрос для каждого CollectionOrder объекта, и таким образом, если есть 100 CollectionOrder объектов, это сделает 100 дополнительных запросов, и таким образом, в конечном итоге сделает 102 запроса. Таким образом, если количество совпадений будет достаточно большим, то в конечном итоге он перестанет отвечать (в пределах разумного времени)

Более того, pk__in=library_records_ids будет не соблюдать порядок library_record_ids. Действительно, он может возвращать LibraryRecord в любом порядке, если только они имеют первичные ключи, которые являются членами списка.

Вы можете сделать запрос с помощью:

def get_queryset(self):
    return LibraryRecord.objects.filter(
        collectionorder__collection__collection='collection-name'
    ).order_by('collectionorder__order_number')

Where collectionorder is the related_query_name=… [Django-doc] for the ForeignKey, OneToOneField or ManyToManyField named record from CollectionOrder to the LibraryRecord model. If you did not specify a value for the related_query_name=… parameter, it will take the value for the related_name=… parameter [Django-doc], and if you did not specify that one either, it will use the name of the source model (so where the relation is defined) in lowercase, so in this case collectionorder.

Таким образом, будет соблюдаться collectionorder__order_number как условие упорядочивания, и выполняться в одном запросе к базе данных, минимизируя количество запросов к базе данных.

Вернуться на верх