Paging Django Model Instances Queried with Complex Filters Using Paginator

Django
2015-10-15 19:10 (9 years ago) ytyng
class Content(models.Model):
    content_name = models.CharField(...)
    group_id = models.PositiveIntegerField(...)
    volume_number = models.PositiveIntegerField(...)
    ...

Assuming we have a typical Django model class, we want to search for its instances using a complex SQL query in a single shot. The results should be displayed on a web page, but since many rows are expected, we want to display a paginator.

For instance, in the above model, we might want to list only the instances with the maximum volume_number within the same group_id.

We will use the general django.core.paginator.Paginator for pagination.

Review of Basic Paginator Usage

contents = Content.objects.filter(...)
paginator = Paginator(contents, 100)

By putting a queryset into the first argument, you can paginate it. A list would also work.

Looking at the Paginator code, you can see that it works if slicing and .count() are implemented. .count can also be __len__.

Therefore, you can wrap the list of instances extracted using complex SQL (which cannot be made into a queryset) in a custom class instance that implements slicing and .count(). Slicing has a "step" feature, but it's cumbersome so we won't implement the step. (Step: hoge_sequence[0:100:2] ← the third number in this slice)

Implementation Code

Model Class

class Content(models.Model):
    content_name = models.CharField(...)
    group_id = models.PositiveIntegerField(...)
    volume_number = models.PositiveIntegerField(...)
    ...

    @classmethod
    def last_volumes(cls, limit=100, offset=0):
        """
        Method for searching with raw SQL if absolutely necessary
        Uses objects.raw() to convert to model instances.
        The SQL is a bit peculiar, but don't mind it.
        """
        sql = """SELECT t1.*
FROM (
  SELECT *
  FROM content_content
  ORDER BY volume_number DESC
) t1
GROUP BY group_id
LIMIT %s OFFSET %s
"""
        return cls.objects.raw(sql, params=[limit, offset])

Class for Paginator

class LastContents(object):
    """
    Class to be used with the Paginator.
    Implements slicing and count()
    """

    def __getitem__(self, key):
        if isinstance(key, slice):
            # In case of slicing (e.g., instance[0:100])
            # Ignore the step!
            limit = key.stop - key.start
            return Content.last_volumes(
                limit=limit, offset=key.start)
       # In case of getting a single element (e.g., instance[50])
       # Not used this time, but
       return Content.last_volumes(limit=1, offset=key)[0]

    def count(self):
        # Implement a method to get the total count
        return Content.objects.filter(volume_number=1).count()

View Function

# Put the instance of LastContents we just created!
paginator = Paginator(LastContents(), 100)

# You can paginate.
page = paginator.page(page_number)

page.object_list ...
Current rating: 1.7
The author runs the application development company Cyberneura.
We look forward to discussing your development needs.

Comments

Archive

2025
2024
2023
2022
2021
2020
2019
2018
2017
2016
2015
2014
2013
2012
2011