The TLDR answer:

Django utilizes the array slicing syntax of python to implement limit offset. For pagination in Django models, you need to use limit offset. Suppose you have 20 rows in a queryset and you want to access the data in batches of 5.

first_five_rows = DBModel.objects.all()[:5]

To access the rows from 10 to 15 then you can use something like this

next_next_five_rows = DBModel.objects.all()[10:15]

The syntax is [OFFSET:OFFSET+LIMIT] where offset would be the no. of rows which we want to skip and the limit is the total no. of rows we want to retrieve.

Model.objects.all()[OFFSET:OFFSET+LIMIT]

The Detailed Answer:

There isn't much detail into this topic apart from some caveats which one should be aware of:

  • Implementing pagination in Django models using limit offset is recommended for smaller datasets. If you want performance improvement, implementing range query and then limit offset for pagination is the best solution.
  • DRF pagination is an out-of-the-box solution that implements the above method internally.
  • Django also has a paginator class which most people are not aware of django.core.paginator.Paginator.
  • Negative indexing doesn't work. Model.objects.all()[-1] is not valid
  • Slicing off a Queryset returns a new Queryset that doesn't return the equivalent query.
  • Slicing can also be used with steps Model.objects.all()[:10:2] that would return every second object for the first 10 elements.
  • Once you perform a slicing then it is prohibited to do filtering or ordering due to the ambiguous nature of the output.
  • If you want to retrieve just one element, the best approach is to use .first() or .last() since a slicing query of Model.objects.filter(name='John')[0] might give you IndexError  if the filter query returns an empty list.

The official documentation of Django mentions all these points.

Making queries | Django documentation | Django