djangodjango-viewsdjango-pagination

Django paginator page turns to list


I'm trying built a matrimonial site in Django. The following is the code for displaying a single profile at a time.

unfiltered_list = profile_matches
for profile in unfiltered_list:
   print("\n profile:",profile,"\n")
    
profiles_list = profile_matches
paginator = Paginator(profiles_list,1)
page_number = request.GET.get('page', 1)
profiles = paginator.page(page_number)
profile_id = profiles.object_list.values('user_model_for_profile_id')
    

The code works fine if I remove the for loop, but when I try to loop through the unfiltered list, 'profiles' becomes a list even though I haven't touched it, other than creating a variable that references to it. I get an Attribute error saying AttributeError: 'list' object has no attribute 'values'

Is this a problem with Django itself? Or am I missing something?


Solution

  • This is indeed the case, the reason this happens is because the Paginator in Django slices the queryset [GitHub], indeed:

    def page(self, number):
        """Return a Page object for the given 1-based page number."""
        # …
    return self._get_page(self.object_list[bottom:top], number, self)

    and this will return a QuerySet if the original QuerySet has no result_cache.

    But if the QuerySet has results loaded into memory, it returns the sliced result cache [GitHub], indeed:

    def __getitem__(self, k):
        """Retrieve an item or slice from the set of results."""
        # …
        if self._result_cache is not None:
            return self._result_cache[k]
        # …

    since the ._result_cache is a list of results, the result will indeed be a list.

    Pagination is normally the end of the process, so extra ORM calls are quite strange. Moreover, using .values() is a bit of an anti-pattern [django-antipatterns].

    If you want to first enumerate over the data, enumerate over a clone of the queryset, so:

    for profile in unfiltered_list.all():
        print('\n profile:', profile, '\n')

    N.B.: I created a ticket [Django-ticket] for this, an idea could be that in case the data is fetched already, it still creates a QuerySet but with a populated result cache.