How to handle sorting, filtering and pagination in the same ListView
GitHub link: https://github.com/IgorArnaut/Django-ListView-Pagination-Search-Sorting-Issue I have an issue with the ListView.
I have pagination, side form for filtering and a form with select for sorting in a single view. These 3 use get method and are "handled" in this list view.
class ListingListView(generic.ListView):
model = Listing
context_object_name = "listings"
template_name = "listings/listing_list.html"
def get_context_data(self, **kwargs):
context = super(ListingListView, self).get_context_data(**kwargs)
context["form"] = SearchForm()
return context
def get_queryset(self):
queryset = super(ListingListView, self).get_queryset()
if (self.request.GET.dict()):
query = filter(self.request.GET.dict())
queryset = queryset.filter(query)
sorting = self.request.GET.get("sorting")
if sorting == "new":
queryset = queryset.order_by("-created_at")
if sorting == "big":
queryset = queryset.order_by("-apartment__area")
if sorting == "small":
queryset = queryset.order_by("apartment__area")
if sorting == "expensive":
queryset = queryset.order_by("-apartment__price")
if sorting == "cheap":
queryset = queryset.order_by("apartment__price")
paginator = Paginator(queryset, per_page=2)
page_number = self.request.GET.get("page")
listings = paginator.get_page(page_number)
return listings
Urls:
urlpatterns = [
path("", views.ListingListView.as_view(), name="listing-list"),
path("postavka", login_required(views.ListingCreateView.as_view()), name="listing-create"),
path("<int:pk>", views.ListingDetailView.as_view(), name="listing-detail"),
path("<int:pk>/izmena", views.ListingUpdateView.as_view(), name="listing-update"),
path("<int:pk>/brisanje", login_required(views.listing_delete), name="listing-delete")
]
Templates:
<div class="col-4">{% include "../partials/listings/search_form.html" %}</div>
<div class="col-6">
<form class="mb-3" action="{% url 'listing-list' %}" method="get">
<select class="form-select w-50" name="sorting">
<option value="new">Prvo najnoviji</option>
<option value="popular">Prvo najpopularniji</option>
<option value="big">Po kvadraturi (opadajuće)</option>
<option value="small">Po kvadraturi (rastuće)</option>
<option value="expensive">Po ceni (opadajuće)</option>
<option value="cheap">Po ceni (rastuće)</option>
</select>
</form>
{% if listings %}
{% for listing in listings %}
{% include "../partials/listings/list_card.html" with listing=listing %}
{% endfor %}
{% else %}
<div class="row">Oglasi nisu dostupni.</div>
{% endif %}
<ul class="pagination">
{% if listings.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1">« first</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ listings.previous_page_number }}">previous</a>
</li>
{% endif %}
<span class="current">Page {{ listings.number }} of {{ listings.paginator.num_pages }}.</span>
{% if listings.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ listings.next_page_number }}">next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ listings.paginator.num_pages }}">last »</a>
</li>
{% endif %}
</ul>
<form class="mb-3" action="{% url 'listing-list' %}" method="get">
{# {% csrf_token %} #}
{% for field in search_form %}
<div class="row mb-3">
<label class="col-sm-4 col-form-label">{{ field.label }}</label>
<div class="col-sm-8">{{ field }}</div>
</div>
{% endfor %}
<input class="btn btn-primary fw-semibold" type="submit" value="Pretraži">
</form>
I have 3 listings in the database, but I show only 2 per page. When I go to the next page, I het an KeyError
for city
, a field which is not used in pagination, but in side form.
Traceback (most recent call last):
File "D:\Documents\_Igor\Projekti\_Master\Real Estate Listings App\Secure\Django\venv\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
response = get_response(request)
File "D:\Documents\_Igor\Projekti\_Master\Real Estate Listings App\Secure\Django\venv\Lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "D:\Documents\_Igor\Projekti\_Master\Real Estate Listings App\Secure\Django\venv\Lib\site-packages\django\views\generic\base.py", line 105, in view
return self.dispatch(request, *args, **kwargs)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\Documents\_Igor\Projekti\_Master\Real Estate Listings App\Secure\Django\venv\Lib\site-packages\django\views\generic\base.py", line 144, in dispatch
return handler(request, *args, **kwargs)
File "D:\Documents\_Igor\Projekti\_Master\Real Estate Listings App\Secure\Django\venv\Lib\site-packages\django\views\generic\list.py", line 158, in get
self.object_list = self.get_queryset()
~~~~~~~~~~~~~~~~~^^
File "D:\Documents\_Igor\Projekti\_Master\Real Estate Listings App\Secure\Django\realestate\listings\views.py", line 27, in get_queryset
query = filter(self.request.GET.dict())
File "D:\Documents\_Igor\Projekti\_Master\Real Estate Listings App\Secure\Django\realestate\listings\helpers\search.py", line 28, in filter
query &= filter_by_city(data["city"])
~~~~^^^^^^^^
KeyError: 'city'
You filter with:
def filter(data):
query = Q()
query &= filter_by_city(data["city"])
query &= filter_by_num_of_rooms(data["num_of_rooms"])
query &= filter_by_price(data["price_from"], data["price_to"])
query &= filter_by_area(data["m2_from"], data["m2_to"])
return query
But it is not said data
contains all these keys. Your filter_by_city(..)
and other functions contain checks for None
, so we can use:
def filter(data):
query = Q()
query &= filter_by_city(data.get("city"))
query &= filter_by_num_of_rooms(data.get("num_of_rooms"))
query &= filter_by_price(data.get("price_from"), data.get("price_to"))
query &= filter_by_area(data.get("m2_from"), data.get("m2_to"))
return query
That being said, I would strongly advise to use a tool like django-filter
, since this can automate a lot of this, like the range checks, and only filtering on aspects filled in.