Home > Net >  Django 4.x - Conditional QuerySet for Pagination and a many-to-many relationship
Django 4.x - Conditional QuerySet for Pagination and a many-to-many relationship

Time:06-21

Disclaimer: I have searched and a question tackling this particular challenge could not be found at the time of posting.

The Requirement

For a Class Based View I need to implement Pagination for a QuerySet derived through a many to many relationship. Here's the requirement with a more concrete description:

  • Many Library Records can belong to many Collections
  • Web pages are required for most (but not necessarily all) Collections, and so I need to build views/templates/urls based on what the client identifies as required
  • Each Collection Page displaying the relevant Library Records requires Pagination, as there may be 100's of records to display.

The First Approach

And so with this requirement in mind I approached this as I normally would when building a CBV with Pagination. However, this approach did not allow me to meet the requirement. What I quickly discovered was that the Pagination method in the CBV was building the object based on the declared model, but the many to many relationship was not working for me.

I explored the use of object in the template, but after a number of attempts I was getting nowhere. I need to display Library Record objects but the many to many relationship demands that I do so after determining the records based on the Collection they belong to.

EDIT - Addition of model

models.py

class CollectionOrder(models.Model):
    collection = models.ForeignKey(
        Collection,
        related_name='collection_in_collection_order',
        on_delete=models.PROTECT,
        null=True,
        blank=True,
        verbose_name='Collection'
    )
    record = models.ForeignKey(
        LibraryRecord,
        related_name='record_in_collection_order',
        on_delete=models.PROTECT,
        null=True,
        blank=True,
        verbose_name='Library record',
    )
    order_number = models.PositiveIntegerField(
        blank=True,
        null=True,
    )

CodePudding user response:

Please do not work with record.record.id: this will each time make a query for each CollectionOrder object, and thus if there are 100 CollectionOrder objects, that will make 100 extra queries, and thus eventually make 102 queries. If the number of matches is thus quite large, it will eventually no longer respond (within reasonable time).

Furthermore pk__in=library_records_ids will not respect the order of the library_record_ids. Indeed, it can return the LibraryRecords in any order, as long as these have primary keys that are members of the list.

You can query with:

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.

This will thus respect the collectionorder__order_number as ordering condition, and perform this in a single database query, minimizing the amount of queries to the database.

CodePudding user response:

Hopefully, this Q&A helps someone else. If in reading the following approach you can think of ways to refactor/optimize I'd love to learn. Note: I deliberately did not implement Pythonic List Comprehension for my personal preference of readability.

What I ended up doing was adding get_queryset() to:

  • Query the Collection for the records belong to it, to then
  • Build a list of record ids, to then
  • Return the QuerySet by filtering for pk__in (the pk exists in the list of library_record_ids)

Here's the resulting code. (Edit: This code has been optimized following another answer - I just didn't want to leave a lesser snippet up)

    def get_queryset(self):
        return LibraryRecord.objects.filter(
            record_in_collection_order__collection__collection='Collection Name'
        ).order_by('record_in_collection_order__order_number')

The requirement has been met. I welcome constructive criticism. My intention in sharing this Q&A is to try and give a little back to the Stack Overflow Community that has served me so well since starting this journey into Django.

  • Related