Django - TypeError: object of type 'NoneType' has no len() occurred while doing Pagination

Viewed 3394

TypeError: object of type 'NoneType' has no len() occurs when I'm adding a pagination to a list view. This happened when I clicked next button to go to page 2. It should have two more results to show.

I guess it's because the value of search is wiped or the queryset becomes none somehow, when I click next (go to page 2), then it returns none.

Error: enter image description here

Search Form: enter image description here

book/views:

class SearchResultView(generic.ListView):
    template_name = 'book/search.html'
    model = Book
    context_object_name = 'book_list'
    paginate_by = 2

    def get_queryset(self):
        queryset = super().get_queryset()
        search = self.request.GET.get('search')
        if search:
            return Book.objects.filter(
                title__icontains=search
            )

book/templates/book.html:

<form class="form-inline my-2 my-lg-0" method="get" action="{% url 'book:search' %}">{% csrf_token %}
    <input style="font-size: 12px; width: 200px" class="form-control mr-sm-2" name="search" type="search" placeholder="Book Name" aria-label="Search">
    <button class="btn btn-outline-primary my-2 my-sm-0" type="submit">Search</button>
</form>

book/templates/search.html:

<div id="customContents">
    {% block customContents %}
    {% if book_list %}
    <ul>
        {% for book in book_list %}
        <li>{{ book.title }}</li>
        {% empty %}
        {% endfor %}
    </ul>

    {% if is_paginated %}

    <div class="pagination">
            <span class="page-links">
                {% if page_obj.has_previous %}
                    <a href="?page={{ page_obj.previous_page_number }}">previous</a>
                {% endif %}
                {% if page_obj.has_next %}
                    <a href="?page={{ page_obj.next_page_number }}">next</a>
                {% endif %}
            </span>
    </div>
    {% endif %}
    {% else %}
    <h3> No result found. </h3>
    {% endif %}
    {% endblock %}
</div>

book/urls:

app_name = 'book'
urlpatterns = [
    path('', views.HomePageView.as_view(), name='home'),
    path('search/', views.SearchResultView.as_view(), name='search'),
    path('<slug:slug>/', views.BookDetailView.as_view(), name='detail'),
]

It only works fine like this:

class SearchResultView(generic.ListView):
    template_name = 'book/search.html'
    model = Book
    context_object_name = 'book_list'
    paginate_by = 2
    queryset = Book.objects.filter(
                title__icontains='Python'
            )
2 Answers

You overwrite the search parameter in the query string, since you're not repeating it. There's a bunch of answers accumulated over time in this question on how to handle that.

This is your mistake to fix:

<a href="?page={{ page_obj.previous_page_number }}">previous</a>

(And the same for the "next" link).

Then everything should work as advertized.

Alternatively, you can make the search parameter part of the URL itself, but that has the side effect of making the search cacheable (which is good or bad depending on your site) and requires javascript to move the input to the url.

To do it purely in Django, you transfer the search results to a RedirectView that does nothing else but validate the search parameter and then redirect to the actual search listing.

Again, there are some side effects to that approach, most notably the effect on the back button.

So I advise you to look into querystring modification at the linked answer and if you want to go a different route, post another question with your preferences.

Your get_queryset() method returns None. Because instead of passing search in query string your are passing page=2. So, your search your variable is None. If the condition fails get_queryset() returning None.

Put your return queryset.filter(title='no result found') outside the condition.

def get_queryset(self):
    queryset = super().get_queryset()
    search = self.request.GET.get('search')
    if search:
        books_with_title = queryset.filter(title__icontains=search)
        if len(books_with_title) > 0:
            return books_with_title

        books_with_author = queryset.filter(authors__name=search)
        if len(books_with_author) > 0:
            return books_with_author

        books_with_publisher = queryset.filter(publisher__name=search)
        if len(books_with_publisher) > 0:
            return books_with_publisher

    return queryset.filter(title='no result found')
Related