1

I am trying to paginate get_context_data from views.py, by selecting from among multiple context objects. Only two choices are shown in the code example conditional statements, but I have several more choices which basically cover all choices from the form submission. Only one context is returned, in the end however, passing the context to the template view for pagination.

I tried also setting pagination globally in settings.py, but it is not working.

I have viewed the article below, previously, as a guide to pagination on get-context-objects.

How to perform pagination for context object in django?

From views.py:

from django.shortcuts import render
import django.views.generic
from django.http import HttpResponse
from django.template import loader
from django.template import RequestContext
from ephemera.models import *
from ephemera.serializers import ItemSerializer
from rest_framework import generics
from ephemera.forms import SearchForm, AdvSearchForm
from itertools import chain
from django.core.paginator import Paginator
from django.core.paginator import EmptyPage
from django.core.paginator import PageNotAnInteger



class SearchResultsAdvancedView(django.views.generic.ListView): 
    template_name = 'ephemera/searchresults_advanced.html'
    form = AdvSearchForm()
    paginate_by = 10
    model = Item

    def get_context_data(self, **kwargs):       
        context = super(SearchResultsAdvancedView, self).get_context_data(**kwargs)

        choose_collection = self.request.GET.get('choose_collection')
        user_input = self.request.GET.get('user_input')
        choose_item = self.request.GET.get('choose_item')

        bookpage = False
        imagepage = False

        if choose_collection == 'All' and user_input == '' and choose_item == 'book':
            context['book_qs'] = Item.objects.raw('SELECT  * FROM   ephemera_item WHERE ephemera_item.material_type LIKE %s', ['book']);    
            bookpage = True
        elif choose_collection == 'All' and user_input == '' and choose_item == 'image':
            context['image_qs'] = Item.objects.raw('SELECT  * FROM   ephemera_item WHERE ephemera_item.material_type LIKE %s', ['image']);  
            imagepage = True

        if bookpage:
            paginator = Paginator(context, self.paginate_by)            
            page = self.request.GET.get('page')
            try:
                book_qs = paginator.page(page)
            except PageNotAnInteger:
                book_qs = paginator.page(1)
            except EmptyPage:
                book_qs = paginator.page(paginator.num_pages)
            context['book_qs'] = book_qs

        elif imagepage:
            paginator = Paginator(context, self.paginate_by)            
            page = self.request.GET.get('page')
            try:
                image_qs = paginator.page(page)
            except PageNotAnInteger:
                image_qs = paginator.page(1)
            except EmptyPage:
                image_qs = paginator.page(paginator.num_pages)
            context['image_qs'] = image_qs

        return context

Errors returned include:

Exception Value: unhashable type: 'slice'

Exception Location: c:\users\administrator\appdata\local\programs\python\python36-32\lib\site-packages\django\core\paginator.py in page, line 70

2 Answers2

1

There is no need to use get_context_data [Django-doc] here, you can override get_queryset [Django-doc] and get_context_object_name [Django-doc] to determine the name of the list in your template:

class SearchResultsAdvancedView(django.views.generic.ListView): 
    template_name = 'ephemera/searchresults_advanced.html'
    form = AdvSearchForm()
    paginate_by = 10
    model = Item

    def item_type(self):
        choose_collection = self.request.GET.get('choose_collection')
        if choose_collection != 'All' or not self.request.GET.get('user_input'):
            return None
        choose_item = self.request.GET.get('choose_item')
        if choose_item in ('book', 'image'):
            return choose_item
        return None

    def get_queryset(self, *args, **kwargs):
        item_type = self.get_item_type()
        qs = super.get_queryset(*args, **kwargs)
        if item_type is not None:
            return qs.filter(material_type__iexact=item_type)
        return qs.none()

    def get_context_object_name(self, object_list):
        item_type = self.get_item_type()
        if item_type is not None:
            return '{}_qs'.format(item_type)
        return super().get_context_object_name(object_list)

Django's logic will paginate the QuerySet itself, you thus do not need to worry about that. This is due to the get_context_data [Django-doc] implementation of the MultipleObjectMixin [Django-doc]:

def get_context_data(self, *, object_list=None, **kwargs):
    """Get the context for this view."""
    queryset = object_list if object_list is not None else self.object_list
    page_size = self.get_paginate_by(queryset)
    context_object_name = self.get_context_object_name(queryset)
    if page_size:
        paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
        context = {
            'paginator': paginator,
            'page_obj': page,
            'is_paginated': is_paginated,
            'object_list': queryset
        }
    else:
        context = {
            'paginator': None,
            'page_obj': None,
            'is_paginated': False,
            'object_list': queryset
        }
    if context_object_name is not None:
        context[context_object_name] = queryset
    context.update(kwargs)
    return super().get_context_data(**context)

That being said, I have the impression that the modeling is not done very well. If you have types of items, it makes sence to define an ItemType model. Furthermore you better use Django's ORM to generate queries instead of raw queries.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • I can display the data using get-context-data from raw queries. I just cannot paginate it using code from the views.py/get-context-data based on choices from among multiple context objects. That's what I'm still trying to solve. – Bob Bobsled May 28 '19 at 22:41
  • @BobBobsled: But Django paginates automatically the outcome of `get_queryset`, so here we can easily let Django do the "boilerplate" logic. That is the entire idea of moving it out of the `get_context_data` (of course as well because it simply does not belong there). – Willem Van Onsem May 28 '19 at 22:42
  • @BobBobsled: I've added the relevant code fragment of the `MultipleObjectMixin` (this is a mixin of a `ListView`), as you can see, that part will automatically paginate the outcome of `get_queryset`, so instead of doing all the work ourselves, we can let the mixin do this. This will reduce the amount of boilerplate and is typically safer since that code has been tested a lot by in a lot of situations. – Willem Van Onsem May 28 '19 at 22:48
  • Yes the mixin approach is understood. My template logic expects to choose from among any of the different query sets returned in order to populate tables, and get_queryset will only return a single object list. I need to select from among multiple complex queries and return the chosen context, each of which may contain different context data based on the value of the context object name. Get_context_data doesn't have robust pagination, but it seems what I need here. – Bob Bobsled May 29 '19 at 00:03
  • @BobBobsled: but here we pick the name of the queryset, by overriding `get_context_object_name`, and we can return a different queryset, again based on the values, by overriding the `get_queryset`. You can, as a last resort use the `self.paginate_queryset(queryset, page_size)` in you `get_context_data`, and thus factor out the pagination algorithm, although that would still not be very good code design. – Willem Van Onsem May 29 '19 at 07:41
  • The self.paginate_queryset approach still takes a queryset parameter. I am returning context objects, not a queryset. Surely there must be a solution to pagination using context objects. – Bob Bobsled May 29 '19 at 19:14
  • @BobBobsled: your `Item.objects.raw('SELECT * FROM ephemera_item WHERE ephemera_item.material_type LIKE %s', ['book'])` *is* a `QuerySet` (well it is a `RawQuerySet`, but for the pagination, it is the same). That is exactly why this belongs to the `get_queryset` level. – Willem Van Onsem May 29 '19 at 19:16
0

One problem in the original code example is the RawQuerySets do not work for the pagination code shown in get_context_data, and those should be model objects, with filters or else the pagination code will fail. Ex.

    mydata = self.model.objects.filter(material_type__icontains = 'book')

In this case, however, the RawQuerySets, are needed and useful due to additional (not shown) queries with complex inner joins etc., difficult to accomplish with filters.

One solution for casting a raw query set as a list for pagination is answered in:

Also a django paginator module for RawQuerySet is available to help with this problem linked in that post.

django-paginator-rawqueryset

With the help of the module, the raw query sets can be used effectively with the given paginator code to utilize context objects for returning a context to be passed to the template. The template then, along with typical code to show the page navigation, is also able to utilize the variables attached to the context in order to display the data as needed.

    {% if book_qs %}
    {% endif %}

    {% if image_qs %}
    {% endif %}

The problem example shown, from views.py, is a bit 'brute force' and could be coded more compactly with the help of definitions within the class as shown in the first answer. Another solution to the problem, using the module, is shown here:

    from rawpaginator.paginator import Paginator
    from django.core.paginator import EmptyPage
    from django.core.paginator import PageNotAnInteger


    class SearchResultsAdvancedView(django.views.generic.ListView): 
        template_name = 'ephemera/searchresults_advanced.html'
        form = AdvSearchForm()
        paginate_by = 10
        model = Item

        def get_context_data(self, **kwargs):       
            context = super(SearchResultsAdvancedView, self).get_context_data(**kwargs)

            choose_collection = self.request.GET.get('choose_collection')
            user_input = self.request.GET.get('user_input')
            choose_item = self.request.GET.get('choose_item')

            bookpage = False
            imagepage = False

            if choose_collection == 'All' and user_input == '' and choose_item == 'book':
                mydata = Item.objects.raw('SELECT  * FROM   ephemera_item WHERE ephemera_item.material_type LIKE %s', ['book']);    
                bookpage = True
            elif choose_collection == 'All' and user_input == '' and choose_item == 'image':
                mydata = Item.objects.raw('SELECT  * FROM   ephemera_item WHERE ephemera_item.material_type LIKE %s', ['image']);  
                imagepage = True

             paginator = Paginator(mydata, self.paginate_by)            
             page = self.request.GET.get('page')

            if bookpage:
                try:
                    book_qs = paginator.page(page)
                except PageNotAnInteger:
                    book_qs = paginator.page(1)
                except EmptyPage:
                    book_qs = paginator.page(paginator.num_pages)
                context['book_qs'] = book_qs

            elif imagepage:
                try:
                    image_qs = paginator.page(page)
                except PageNotAnInteger:
                    image_qs = paginator.page(1)
                except EmptyPage:
                    image_qs = paginator.page(paginator.num_pages)
                context['image_qs'] = image_qs

            return context