Jay P.
Jay P.

Reputation: 2590

How to apply pagination to a filtered queryset in Django

I'm currently stuck with applying pagination to my Django view codes. Basically, paginate_by seems automatically to work with queryset attribute in CBV. But, in my case, I'm not just using queryset attribute. I'm using filtered queryset on my own. How can I apply pagination to my codes?

class SearchListView(ListView):
    model = Store
    template_name = 'boutique/search.html'
    paginate_by = 5

    def get(self, request, *args, **kwargs):

          search_text = request.GET.get('search_text')
          sorter = request.GET.get('sorter')

          if not sorter:
              sorter = 'popularity'

          if search_text:
              search_stores = Store.objects.filter(Q(businessName__icontains=search_text) | Q(mKey__icontains=search_text))
              if sorter == 'businessName':
                  search_stores = search_stores.order_by(sorter)
              else:
                  search_stores = search_stores.order_by(sorter).reverse()
          else:
              search_stores = ''

          for store in search_stores:
              store.mKey = store.mKey.split(' ')

          return render(request, self.template_name, {
              'search_stores': search_stores,
              'search_text': search_text,
              'sorter': sorter,
          })

-------------------------UPDATE2-------------------------

I'm stuck with getting inputs from HTML input tags. Currently, I just put self.kwargs(), but I don't know what I should use instead of them.

class SearchListView(ListView):
    model = Store
    template_name = 'boutique/search.html'
    paginate_by = 5

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['search_text'] = self.kwargs['search_text']
        context['sorter'] = self.kwargs['sorter']
        return context

    def get_queryset(self):

        search_text = self.kwargs['search_text']
        sorter = self.kwargs['sorter']

        if not sorter:
            sorter = 'popularity'

        if search_text:
            search_stores = Store.objects.filter(Q(businessName__icontains=search_text) | Q(mKey__icontains=search_text))
            if sorter == 'businessName':
                search_stores = search_stores.order_by(sorter)
            else:
                search_stores = search_stores.order_by(sorter).reverse()
        else:
            search_stores = ''

        for store in search_stores:
            store.mKey = store.mKey.split(' ')

        return search_stores

Upvotes: 4

Views: 4388

Answers (2)

Will
Will

Reputation: 1541

You've overridden the get method of the ListView and have not called up the super chain. As a result, the pagination bits and bob that are implemented in the parent ListView.get() are not getting called.

Rather place your filtering actions in the get_queryset() or get_context() methods (both will have access to self.request) or make sure to call the parent get() in your get().


Look at the source code. ListView inherits BaseListView which implements the pagination stuff.

class BaseListView(MultipleObjectMixin, View):
    """
    A base view for displaying a list of objects.
    """
    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()
        allow_empty = self.get_allow_empty()

        if not allow_empty:
            # When pagination is enabled and object_list is a queryset,
            # it's better to do a cheap query than to load the unpaginated
            # queryset in memory.
            if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
                is_empty = not self.object_list.exists()
            else:
                is_empty = len(self.object_list) == 0
            if is_empty:
                raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
                    'class_name': self.__class__.__name__,
                })
        context = self.get_context_data()
        return self.render_to_response(context)

But your code never calls BaseListView.get() so no pagination is ever done.

More importantly, you need to tell your CBV what to paginate on. See that first line? That is why you need a get_queryset method which literally returns the queryset that is going to be used for pagination.

Now your context variables get implemented in get_context_data which is provided by your parent hierarchy. Here you build up the context data that will be used in your templates.

Upvotes: 2

andilabs
andilabs

Reputation: 23281

I strongly advocate for using this phantasmic library for filtering: https://github.com/carltongibson/django-filter

for the purpose of filtering you define FilterSet with specifying model and allowed fields for filtering

import django_filters


class SpotFilterSet(django_filters.FilterSet):

    class Meta:
        model = Spot
        fields = [
            'is_certificated',
            'name',
            'location',
            'spot_type',
            'tags',
        ]

then in the view you provide filterset_class and override BaseFilterView

from django_filters.views import BaseFilterView


class BaseSpotListView(BaseFilterView, ListView):
    template_name = 'www/spot_list.html'
    model = Spot
    paginate_by = 6
    context_object_name = 'spots'
    filterset_class = SpotFilterSet

Upvotes: 3

Related Questions