Paginate results from django_filter - python

I am very new at this technology and I want to paginate a table containing results using django_filter (In my case I want to filter results from Book Model which are displayed in a HTML Table).
I tried every documentation and forum post but don't know what I am doing wrong or if I am doing something correctly.
Here is my code:
models
class Book(models.Model):
name = models.CharField(max_length=350)
author = models.CharField(max_length=350)
category = models.CharField(max_length=200)
def __str__(self):
return self.name
filters
class BookFilter(django_filters.FilterSet):
name = django_filters.CharFilter(lookup_expr='icontains')
author = django_filters.CharFilter(lookup_expr='icontains')
category = django_filters.CharFilter(lookup_expr='icontains')
class Meta:
model = Book
ields = ['name', 'author', 'category',]
views
class SearchBooksView(ListView):
template_name = "booksearch.html"
book_list = Book.objects.all()
context_object_name = 'book_list'
paginate_by = 3
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
book_filter = BookFilter(self.request.GET)
paginator = Paginator(book_filter, self.paginate_by)
page = self.request.GET.get('page')
try:
book_list = paginator.page(page)
except PageNotAnInteger:
book_list = paginator.page(1)
except EmptyPage:
book_list = paginator.page(paginator.num_pages)
context['book_list'] = book_list
return context
** html **
<h1 class="h1"> Search Books </h1>
<form method="get">
{{ book_filter.form.as_p }}
<button type="submit">Search</button>
</form>
<div class="container">
<table>
<thead>
<tr>
<th>Name</th>
<th>Author</th>
<th>Category</th>
</tr>
</thead>
<tbody>
{% for book in book_list %}
<tr>
<td>{{ book.name }}</td>
<td>{{ book.author }}</td>
<td>{{ book.category }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if is_paginated %}
<ul class="pagination">
{% if page_obj.has_previous %}
<li>
<span>Previous</span>
</li>
{% endif %}
<li class="">
<span>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.</span>
</li>
{% if page_obj.has_next %}
<li>
<span>Next</span>
</li>
{% endif %}
</ul>
{% else %}
<p>No books available</p>
{% endif %}
</div>
with this code I am getting :
paginator.py, line 84, in count return len(self.object_list)
'BookFilter' has no len()
Can you please help me? Thanks!

You need to pass the filtered queryset to the Paginator which can be accessed as filterObj.qs.
From the django_filter docs:
If you want to access the filtered objects in your views, for example if you want to paginate them, you can do that. They are in f.qs
So create a paginated response like this:
book_filter = BookFilter(self.request.GET)
paginator = Paginator(book_filter.qs, self.paginate_by)

You are using generic ListView from django right? That view already take care of pagination for you, you should refactor the code:
class SearchBooksView(ListView):
model = Book
template_name = "booksearch.html"
context_object_name = 'book_list'
paginate_by = 3
def get_queryset(self):
return BookFilter(self.request.GET, queryset=Book.objects.all()).qs

Related

Accessing items in a list through template tags in Django template tags

Template tag screenshot and possible solution options
First time, posting to stack-overflow. Please excuse if formatting is not ideal.
html
<tbody>
{% for list in todolist %}
<tr>
<td>
{{ list.name }}
</td>
<td>
{{ list.items.all|length }}
</td>
<td>
{% comment %}need this to be allCompleted[list.id - 1]
#allCompleted.0 works. allCompleted[0] or allCompleted['0'] does not.{% endcomment %}
{% if allCompleted == True %}
{{ allCompleted|getindex:list.id }}
Yes
{% else %}
No
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
Views.py:
class TodoListListView(ListView):
model = TodoList
context_object_name = "todolist"
template_name = "todos/list.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
allCompletedList = []
for list in TodoList.objects.all():
allCompleted = True
for item in list.items.all():
if item.is_completed == False:
allCompleted = False
allCompletedList.append(allCompleted)
context['allCompleted'] = allCompletedList
print ('context: ', context)
return context
Models.py
class TodoList(models.Model):
name = models.CharField(max_length=100, blank=False)
created_on = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class TodoItem(models.Model):
task = models.CharField(max_length=100)
due_date = models.DateTimeField(null=True, blank=True)
is_completed = models.BooleanField(default=False)
list = models.ForeignKey("Todolist", related_name="items", on_delete=models.CASCADE)
def __str__(self):
return self.task
When printing context:
I get 'allCompleted': [False, True]
This is accurate as I have some items in housing chores not completed but I triple checked to make sure all my coding projects are completed.
As seen from the HTML screenshot, I need something like:
{{ allCompleted[list.id - 1] }} to match with the corresponding list in each row.
But it seems Django does not like that. I've tried many combos like allCompleted['list.id-1']. Strangely, allCompleted.0 = False but allCompleted[0] gets a parse error. I have also tried to create a custom template tag in my app/templatetag folder under a file I made (getindex.py)
from django import template
from todos.models import TodoItem, TodoList
register = template.Library()
def getindex(lst, idx):
return lst[idx]
register.filter(getindex)
For my template tag, I did {{ allCompleted|getindex:list.id-1 }} and it says getindex is not a valid filter so maybe I am registering it incorrectly?
If there is no way to access allCompleted[list.id - 1], I thought of other solutions explained in my HTMl screenshot.
Instead of using template tags for this purpose, it is best to give to the template data in the form it can easily use. The most efficient way to do this would be to leave the task to the database itself and write a query that will give you allCompleted as a column in the result. You can use Exists() subqueries to do this:
from django.db.models import Exists, OuterRef
class TodoListListView(ListView):
model = TodoList
context_object_name = "todolist"
template_name = "todos/list.html"
def get_queryset(self):
queryset = super().get_queryset()
subquery = TodoItem.objects.filter(
list=OuterRef('pk'),
is_completed=False
)
queryset = queryset.annotate(all_completed=~Exists(subquery))
return queryset
Then in your template you can simply write {{ list.all_completed }}:
<tbody>
{% for list in todolist %}
<tr>
<td>
{{ list.name }}
</td>
<td>
{{ list.items.all|length }}
</td>
<td>
{% if list.all_completed == True %}
Yes
{% else %}
No
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>

Adding a maximum limit to the number of post using python

I need to limit the number of posts in Django queries. I have tried to add a min and max but nothing seemed to have worked. I have added home.html into the code.
Example: I should only have the 15 most recent posts in my blog. The rest can be seen by clicking on the category button.
Home.html:
{% extends 'base.html' %}
{% block content %}
<h1>Posts</h1>
<ul>
{% for post in object_list %}
<li>{{post.title}}
<style>
a {
text-transform: capitalize;
}
</style>
- {{ post.category }} - <a href="{% url 'show_profile_page' post.author.profile.id %}">{{ post.author.first_name }}
{{ post.author.last_name }}</a> - {{ post.post_date }} <small>
{% if user.is_authenticated %}
{% if user.id == post.author.id %}
- (Edit)
(Delete)
{% elif user.id == 1 %}
- (Edit)
(Delete)
{% endif %}
{% endif %}
</small><br/>
{{ post.snippet }}</li>
{% endfor %}
</ul>
{% endblock %}
view.py:
class HomeView(ListView):
model = Post
template_name = 'home.html'
ordering = ['-id']
def get_context_data(self, *args, **kwargs):
cat_menu = Category.objects.all()
context = super(HomeView, self).get_context_data(*args,**kwargs)
context["cat_menu"] = cat_menu
return context
models.py:
class Post(models.Model):
title = models.CharField(max_length=255)
header_image = models.ImageField(null=True, blank=True, upload_to='images/')
title_tag = models.CharField(max_length=255)
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = RichTextField(blank=True, null=True)
post_date = models.DateField(auto_now_add=True)
category = models.CharField(max_length=255, default='intro')
snippet = models.CharField(max_length=255)
likes = models.ManyToManyField(User, related_name='post_likes')
dislikes = models.ManyToManyField(User, related_name='post_dislikes')
I think you have another template for displaying categorised objects when you click category button. As you said
"I should only have the 15 most recent posts in my blog. The rest can
be seen by clicking on the category button."
In this case you can use a simple hack to display most recent posts from your table.
query all objects in descending order in views
all_objs = Post.objects.all().order_by('-id')
Then use {% if forloop.counter <= 15 %} to display last 15 items only. as follow.
templates
{% for post in object_list %}
{% if forloop.counter <= 15 %}
<h4>{{obj}} #or anything really that is meant to be shown on the home page.</h4>
{% endif %}
{% endfor %}
You can do something like this:
def get_context_data(self, *args, **kwargs):
context = super(HomeView, self).get_context_data(*args,**kwargs)
context["cat_menu"] = Category.objects.all()
context["most_recent_posts"] = Post.objects.filter(author=self.request.user).order_by('-post_date')[:15]
return context
This will get the 15 most recent posts authored by the current user, ordered by the date it was posted.
Then just handle displaying this in home.html for example:
<ul>
{% for p in most_recent_posts %}
<li>{{ p.title }}</li>
{% endfor %}
</ul>
Just limit your query to the latest 15 entries sorted by post_date:
cat_menu = Category.objects.latest("post_date")[:15]
https://docs.djangoproject.com/en/3.2/topics/pagination/
The best way is Django Pagintion.
{% for contact in page_obj %}
{# Each "contact" is a Contact model object. #}
{{ contact.full_name|upper }}<br>
...
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
next
last »
{% endif %}
</span>
</div>
from django.core.paginator import Paginator
from django.shortcuts import render
from myapp.models import Contact
def listing(request):
contact_list = Contact.objects.all()
paginator = Paginator(contact_list, 25) # Show 25 contacts per page.
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'list.html', {'page_obj': page_obj})
you can use Django pagination api . Manage your data through page number. Initially pass 1 and after that page number given by pagination.
paginator = Paginator(yourquerysetdata, 20)
page_num = request.data.get('page')
result = yourSerializerName(paginator.get_page(page_num) many=True).data
try:
page = paginator.page(page_num)
except:
page = paginator.page(1)
count = paginator.num_pages
resultobj = paginator.get_page(page_num)
has_prev = resultobj.has_previous()
has_next = resultobj.has_next()
page_range = resultobj.paginator.page_range.stop - 1
if has_prev:
prev_page_no = resultobj.previous_page_number()
else:
prev_page_no = 0
if has_next:
next_page_no = resultobj.next_page_number()
else:
next_page_no = None
context = dict()
context['page'] = page.number
context['page_no'] = count
It is very simple. You just have to modify the query that you are using to fetch the posts.
In the get_context_data() method, replace cat_menu = Category.objects.all() with cat_menu = Category.objects.all().order_by('-post_date')[:15]. This will limit the number of results to 15 most recent objects.
For more understanding, you can take a look at the official Django docs for Limiting QuerySets.

NoReverseMatch at /availability/1/edit/ Reverse for 'availability_list' with no arguments not found. Tried: ['availability\\/(?P<pk>[0-9]+)\\/list$']

I'm getting this error using Django 3 when trying to either 1) create a new object (CreateView), or 2) edit an existing one (UpdateView). Using CBVs and ModelForms.
I explored similar questions on Stackoverflow. Very often, it's about the template missing pk reference, but I triple-checked that and it doesn't seem to be the case.
Any idea?
Here are my codes:
URLs
path('availability/<int:pk>/list', views.AvailabilityListView.as_view(), name='availability_list'),
path('availability/new/', views.AvailabilityCreateView.as_view(), name='availability_new'),
path('availability/<int:pk>/edit/', views.AvailabilityUpdateView.as_view(), name='availability_edit'),
Views
class AvailabilityUpdateView(UpdateView):
template_name = 'crm/availability_form.html'
form_class = AvailabilityForm
model = Availability
class AvailabilityUpdateView(UpdateView):
template_name = 'crm/availability_form.html'
form_class = AvailabilityForm
model = Availability
class AvailabilityListView(TemplateView):
template_name = 'crm/availability_list.html'
def get_context_data(self, **kwargs):
user = self.request.user
kwargs['availabilities'] = Availability.objects.filter(staff__manager=Manager.objects.get(user=user)).filter(staff=self.kwargs['pk'])
return super().get_context_data(**kwargs)
Forms
class AvailabilityForm(forms.ModelForm):
class Meta():
model = Availability
exclude = []
Models
class Availability(models.Model):
staff = models.ForeignKey(Staff, on_delete=models.CASCADE)
start = models.DateTimeField()
end = models.DateTimeField()
class Meta:
verbose_name_plural = "Availabilities"
def get_absolute_url(self):
return reverse('staff_list')
Template
{% block body_block %}
<div class="container">
<div class="heading">
<h1>Availability</h1>
<a class = 'btn btn-primary' href="{% url 'availability_new' %}">+Create new availability</a>
</div>
<hr/>
{% if availabilities %}
<table class="table">
<tbody>
{% for availability in availabilities %}
<tr>
<td>{{availability.pk}}</td>
<td>{{availability.staff}}</td>
<td>{{availability.start}}</td>
<td>{{availability.end}}</td>
<td>
Edit
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class='empty'>Staff does not have any availability</p>
{% endif %}
</div>
{% endblock %}

Django Pagination "Page not found"

I'm currently having issues with my Django pagination. I have a query set of 9 objects and am paginating one object to a page. So I should have 9 pages total.
Paginator is showing that I have 9 pages, but when I click the "next" page button my (Always on the last/last two pages) my url turns to: http://127.0.0.1:8000/forum/1?page=7
Then I get a 404 page not found error along with "Invalid page (7): That page contains no results"
Here is my class code:
class DirectoryClass(generic.ListView):
template_name = "forum_directory.html"
model = Directory
paginate_by = 1
def get_context_data(self, **kwargs):
context = super(DirectoryClass, self).get_context_data(**kwargs)
directory = Directory.objects.filter(pk=self.kwargs['directory_id'])
context['page'] = 'forum'
context['name'] = directory.first().name
context['user'] = self.request.user
topic_set = directory.first().topic_set.all().order_by('-last_post')
print(topic_set.all())
paginator = Paginator(topic_set, self.paginate_by)
page = self.request.GET.get('page')
try:
topic_set = paginator.page(page)
except PageNotAnInteger:
topic_set = paginator.page(1)
except EmptyPage:
topic_set = paginator.page(paginator.num_pages)
context['topics'] = topic_set
context['page'] = page
return context
Here is the HTML used to change the page:
<div style="width: 1240px; margin: auto; text-align: right;">
{% if topics.has_other_pages %}
<ul class="pagination forum-pagination">
{% if topics.has_previous %}
<li>«</li>
{% else %}
<li class="disabled"><span>«</span></li>
{% endif %}
{% for i in topics.paginator.page_range %}
{% if topics.number == i %}
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
{% else %}
<li>{{ i }}</li>
{% endif %}
{% endfor %}
{% if topics.has_next %}
<li>»</li>
{% else %}
<li class="disabled"><span>»</span></li>
{% endif %}
</ul>
{% endif %}
</div>
Here is the url
path(r'/<int:directory_id>',views.DirectoryClass.as_view(), name='forum_directory'),
What am I doing wrong here?
def get_context_data(self, **kwargs):
context = super(DirectoryClass, self).get_context_data(**kwargs)
context['page'] = 'forum'
context['user'] = self.request.user
return context
def get_queryset(self):
directory = Directory.objects.filter(pk=self.kwargs['directory_id'])
topics = directory.first().topic_set.all().order_by('-last_post')
return topics
use this only in your ListView, as Django provides pagination to all the Class Based View

Django - How to check if id contains in a table column?

guys.
I want to check in my django template, if request.user exists in some row of column user in my table LeagueMember. The way I found is not working.
views.py
#login_required(login_url='login/')
def search_leagues(request):
if request.method == 'POST':
return redirect('join_league')
leagues = League.objects.all()
return render(request, 'search_leagues.html', { 'allleagues': leagues })
model.py
class League(models.Model):
league_owner = models.ForeignKey('auth.User')
league_name = models.CharField(max_length=30)
creation_date = models.DateTimeField()
def is_member(self):
member = LeagueMember.objects.get(league=self)
if member:
return True
else:
return False
class LeagueMember(models.Model):
league = models.ForeignKey('League', related_name='leaguemember_league')
user = models.ForeignKey('auth.User')
search_leagues.html
{% for league in allleagues %}
<tr>
<td class="center">{{ league.league_name }}</td>
<td class="center">{{ league.leaguemember_league.count}}/{{ league.leaguesettings_league.league_number_teams }}</td>
<td class="center">{{ league.leaguesettings_league.league_eligibility }}</td>
<td class="center">{{ league.leaguesettings_league.league_lifetime }}</td>
{% if request.user in league.leaguemember_league.user %}
DO SOMETHING!!!
{% else %}
{% if league.leaguemember_league.count < league.leaguesettings_league.league_number_teams %}
{% if league.leaguesettings_league.league_eligibility == "Private" %}
<form method="post" action="{% url 'joinleague' pk=league.id %}">
<td class="center">Soliticar</td>
</form>
{% elif league.leaguesettings_league.league_eligibility == "Public" %}
<form method="post" action="{% url 'joinleague' pk=league.id %}">
<td class="center">Entrar</td>
</form>
{% endif %}
{% endif %}
{% endif %}
</tr>
{% endfor %}
This error is in this line:
{% if request.user in league.leaguemember_league.user %}
Always goes to ELSE block
Thank you all
league.leaguemember_league will not give you a LeagueMember object but a RelatedManager object (so you cannot find a user property in it, therefore your template logic will not work).
What you are trying to do is go two levels deep in your relationship (League -> LeagueMember -> User). You can't easily do this kind of logic in your template and probably need to do it in your view code instead. For example:
league_data = []
for league in League.objects.all():
league_data.append({
'league': league,
'users': User.objects.filter(leaguemember__league=league) # This gives you all the users that are related to this league
})
return render(request, 'search_leagues.html', { 'allleagues': league_data})
You then need to modify all your template logic to use this new structure:
{% for league_data in allleagues %}
# Replace league with league_data.league in all the template logic below this
In the if block you can then do:
{% if request.user in league_data.users %}
Note that this querying may not be very efficient if you have a large number of users/leagues - in which case you may need to rethink your model design.

Categories

Resources