Django: Access num_pages in view to generate pagination - django

I want to generate a range of pages in my template when using a ListView and it's pagination, to generate dynamically the number of pages for the pagination:
https://getbootstrap.com/docs/4.0/components/pagination/
My first attempt was to make a for loop for everyelement in page_obj.paginator.num_pages, getting error int is not iterable:
{% if is_paginated %}
<ul class="pagination">
{% for i in page_obj.paginator.num_pages %}
<li class="page-item">
<span class="page-link">
<a href="/catalogo?page={{i}}">
<span class="sr-only">(current)</span>
</span>
</li>
{% endfor %}
</ul>
{% endif }
Then I've discovered that there isn't and wont be a range template filter because this calculation should be done in the view and send the range to the template, not generated in the template. See:
https://code.djangoproject.com/ticket/13088
So how can I access the page_obj.paginator.num_pages inside the
view???
My LisView:
class CatalogoListView(ListView):
model = UnitaryProduct
template_name = "shop/catalogo.html"
paginate_by = 10
def get_queryset(self):
filter_val = self.request.GET.get('filtro', 'todas')
order = self.request.GET.get('orderby', 'created')
if filter_val == "todas":
context = UnitaryProduct.objects.all().filter(available=True).order_by('-created')
return context
else:
context = UnitaryProduct.objects.filter(
subcategory2=filter_val,
).filter(available=True).order_by('-created')
return context
def get_context_data(self, **kwargs):
context = super(CatalogoListView, self).get_context_data(**kwargs)
context['filtro'] = self.request.GET.get('filtro', 'todas')
context['orderby'] = self.request.GET.get('orderby', 'created')
context['category'] = Category.objects.get(slug="catalogo")
return context

num_pages is an integer storing the total number of pages, thus not iterable.
What you are looking for is page_range, which is a list of page numbers.
You can iterate on it from your template, just replace
{% for i in page_obj.paginator.num_pages %}
with
{% for i in page_obj.paginator.page_range %}

Related

Django how to pass user object in class based view pagenation?

The default class based view pagination returning all object. I have an my account page for my each individual user where I need to show pagination number. By default pagination is same for all user. Let you explain little bit. If user A have 10 items and I listed 2 items per page then user A will be see total 5 page in his account which is fine but user B haven't any items and he is currently seeing only pagination number like page1,page2,page3... in his account because the current pagination returning all items from database. here is my code:
views.py
class BlogMyAccount(ListView):
paginate_by = 10
model = Blog
template_name = 'blog/my-account.html'
ordering = ['-id']
#html
{% for blog in object_list %}
{% if user.id == blog.author.id %}
....my code
{% endif %}
{% endfor %}
<!---pagination code---->
<ul class="pagination justify-content-center mb-4">
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?page=1">First Page</a></li>
<li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">← Back</a></li>
{% endif %}
{% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}">Next Page →</a></li>
{% endif %}
{% for i in paginator.page_range %}
{% if page_obj.number == i %}
<li class="page-item"><a class="page-link" href="#!">{{ i }}</a></li>
{% elif i > page_obj.number|add:'-3' and i < page_obj.number|add:'3' %}
<li class="page-item"><a class="page-link" href="?page={{ i }}">{{ i }}</a></li>
{%endif%}
{% endfor %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Last Page</a></li>
</ul>
in function based view I am passing user object like this in pagination:
notifications = Notifications.objects.all().filter(user=user).order_by('-date')
paginator = Paginator(notifications, 20) # Show 25 contacts per page.
I tried to apply same method by using context in my classed view but didn't work. Pagination is same for all user in class based view.
You need to filter the queryset the same way you did in FBV. Override get_queryset() to only return objects associated with your requesting user.
class BlogMyAccount(ListView):
paginate_by = 10
model = Blog
template_name = 'blog/my-account.html'
ordering = ['-id']
def get_queryset(self):
queryset = Blog.objects.filter(author=self.request.user)
return queryset
Also you dont need to call .all() on Notifications.objects, it is already a queryset, Notifications.objects.filter() is okay.
If possible, move ordering to the models.py file, as in:
class Meta:
ordering = ['-id']
You can override the get_querset method, and by using a super() call, let Django do the .order_by clause for us:
from django.contrib.auth.mixins import LoginRequiredMixin
class BlogMyAccount(LoginRequiredMixin, ListView):
paginate_by = 10
model = Blog
template_name = 'blog/my-account.html'
ordering = ['-id']
def get_queryset(self, *args, **kwargs):
# &downarrow; will take care of the ordering
return super().get_querset(*args, **kwargs).filter(
author=self.request.user
)
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].

multiple query model in same view django

I've been doing some django these past weeks and I'm having a problem.
I have a page where Articles are shown. No problem while reovering all articles from db. But now I'd like to get all categories (an Article has a category) that I have in my database.
So I can display like this in my page:
List of categories
-cat1
-cat2
-cat3
List of articles
-art1
-art2
-art3
But I don't know how to do with both queries.
Here's what I've tried.
class IndexView(generic.ListView):
template_name = 'eduardoApp/index.html'
context_object_name = 'article_list'
def get_queryset(self):
return Article.objects.order_by('article_name')
def get_categories(request):
category_list=Category.objects.all()
context = {'category_list':category_list}
return render(request,'eduardoApp/index.html',context)
And in my view:
<h2>List of categories</h2>
{% if category_list %}
{% for category in category_list %}
<p>{{ category.name }}</p>
{% endfor %}
{% else %}
<p>no categorys</p>
{% endif %}
<h2>List of articles</h2>
{% if article_list %}
<div class="flex-container">
{% for article in article_list %}
<div>{{ article.article_name }}</div>
{% endfor %}
</div>
{% else %}
<p>No articles...</p>
{% endif %}
{% endblock %}
In my view I keep seeing no categorys displayed (since category_list does not exist but don't know why and how to fix)
ListView is creating context with 'objects' as queryset get_queryset returns.
I suppose your custom method get_categories hasn't been used anywhere?
Best practice here is to override get_context_data method like...
class IndexView(generic.ListView):
...
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['category_list'] = ...
return context

infinite pagination doesn't render variables

I'm trying to use django infinite pagination, but I'm getting this error:
TemplateSyntaxError at /
u'paginate' tag requires a variable name `as` argumnent if the queryset is provided as a nested context variable (prodmatrix.items). You must either pass a direct queryset (e.g. taking advantage of the `with` template tag) or provide a new variable name to store the resulting queryset (e.g. `paginate prodmatrix.items as objects`).
This is my template:
{% load endless %}
**{% paginate prodmatrix.items %}**
{% for key, values in prodmatrix.items %}
<li class="span3">
<div class="product-box">
<span class="sale_tag"></span>
<p><img src="{{ STATIC_URL }}{{values.1.0}}" alt="" /></p>
<h4>{{ values.0.0 }}</h4><br/>
<p class="category">{{values.2.0}} {{values.2.1}} {{values.2.2}}</p> </div>
</li>
{% endfor %}
{% show_pages %}
This is my view:
def home(request):
if request.user.is_authenticated():
print "login"
user = request.user
prods = Product.objects.all()
i = 0
print 'numero de produtos ' + str(len(prods))
prodmatrix = {}
for prod in prods:
# 0 1 2 3
prodmatrix[str(i)] = [[prod.name], [prod.image], [], [prod.slug]]
reviews = Review.objects.filter(product=prod.id) # ^ this is for tags
print str(len(reviews))
if len(reviews) != 0:
for review in reviews:
rev_alltags = review.tag.all()
for tags in rev_alltags[:3]: #
print tags.name
prodmatrix[str(i)][2].append(tags.name) # append only tags
print str(i)
i = i + 1
return render(request, 'home.html',{'prodmatrix':prodmatrix})
This error is occurring because you don't pass the template any variable called entries.
I don't know enough about the lib to give a solution but I believe you will need to do something along the lines of:
{% paginate prodmatrix.items %}

Slicing pagination in ListView?

I have successfully added pagination to a list_contacts view (obviously for a Contact model). And am now trying to setup the template context so that I can limit the pagination display to look like this :
< 1 ... 5 6 7 8 9 ... 42 >
I went from this answer and tried to use a "paginator" template tag, unsuccessfully. The main problem is that I'm using ListView instead of the old object_list, and these two don't set the context the same way.
Here's my view :
from django.views.generic import ListView
from app.models import Contact
list_contacts = ListView.as_view(
model=Contact,
http_method_names = ['get'],
template_name='contacts.html',
context_object_name='contacts',
paginate_by=6
)
And my template "contacts.html" :
<ul class="pagination">
{# Previous page link #}
{% if page_obj.has_previous %}
<li>
«
</li>
{% else %}
<li class="disabled">
«
</li>
{% endif %}
{# First page #}
{% if show_first %}
<li>
1
</li>
<li>...</li>
{% endif %}
{# List of pages (with current "active") #}
{% for page in page_numbers %}
{% ifequal page page_obj.number %}
<li class="active">
{{ page }}
</li>
{% else %}
<li>
{{ page }}
</li>
{% endifequal %}
{% endfor %}
{# Last page #}
{% if show_last %}
<li>...</li>
<li>{{ page_obj.pages }}</li>
{% endif %}
{# Next page link #}
{% if page_obj.has_next %}
<li>
»
</li>
{% else %}
<li class="disabled">
»
</li>
{% endif %}
</ul>
EDIT: here's my view code that ended up working
class ListContactsView(ListView):
model = Contact
http_method_names = ['get']
template_name = 'contacts.html'
context_object_name = 'contacts'
paginate_by = 6
def get_context_data(self, **kwargs):
_super = super(ListContactsView, self)
context = _super.get_context_data(**kwargs)
adjacent_pages = 2
page_number = context['page_obj'].number
num_pages = context['paginator'].num_pages
startPage = max(page_number - adjacent_pages, 1)
if startPage <= 3:
startPage = 1
endPage = page_number + adjacent_pages + 1
if endPage >= num_pages - 1:
endPage = num_pages + 1
page_numbers = [n for n in xrange(startPage, endPage) \
if n > 0 and n <= num_pages]
context.update({
'page_numbers': page_numbers,
'show_first': 1 not in page_numbers,
'show_last': num_pages not in page_numbers,
})
return context
list_contacts = ListContactsView.as_view()
There are different ways to achieve this (or even a combination of some):
Override or extend get_context_data by calling super
Write a templatetag
Write a template filter
Override or extend the PaginationMixin
An example of the first
class MyListView(ListView):
def get_context_data(self, **kwargs):
context = super(MyListView, self).get_context_data(**kwargs)
context['foo'] = 'bar'
return context

Django class based views with url issue

I'm trying to get to grips with class based views.
I have urls.py as follows:
urlpatterns = patterns('homework.views',
(r'^index/$', 'index'),
url(r'^(?P<sub_slug>\w+)/$', NavListView.as_view(), name='nav'),
url(r'^(?P<sub_slug>\w+)/(?P<class_grp_slug>\w+)/$', SubNavListView.as_view(), name='subnav'),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)),)
I have my views.py:
# Subject navigation
class NavListView(ListView):
template_name = 'templates/home.html'
def get_queryset(self):
self.subject = Subject.objects.all()
return self.subject
def get_context_data(self, **kwargs):
context = super(NavListView, self).get_context_data(**kwargs)
context['subjects'] = self.subject
return context
# Class group navigation
class SubNavListView(NavListView):
def get_queryset(self):
self.group = Group.objects.filter(subject__name__iexact=self.kwargs['sub_slug'])
return self.group
def get_context_data(self, **kwargs):
context = super(NavListView, self).get_context_data(**kwargs)
context['groups'] = self.group
return context
In my 'templates/home.html' I have:
{% extends 'templates/base.html' %}
{% load url from future %}
{% block nav-menu-items %}
<ul class="nav">
{% for sub in subjects %}
<li class="">{{ sub }}</li>
{% endfor %}
<li class="active">Add Subject</li>
</ul>
{% endblock nav-menu-items %}
{% block class_groups_nav %}
<div class="tabbable">
<ul class="nav nav-tabs">
{% for group in groups %}
<li>
<a data-toggle="tab" href="{% url 'subnav' sub_slug class_grp_slug %}">{{ group }}</a>
</li>
{% endfor %}
<li>Add</li>
</ul>
{% endblock class_groups_nav %}
I'm trying to achieve a 'nav' of subjects, then a 'subnav' below showing a tab for each class group for the subject selected in the navigation above.
I've tried different ways of doing this such as making Subject.objects.all() available as context processors. I have also attempted to subclass NavListView so I can inherit the previous context, making them available in SubNavListView.
At the moment, I'm getting a NoReverseMatch error where the url named 'nav' is not passing the sub_slug and so I can't use it in the url in the template.
Any thoughts on getting this working?
Many thanks,
Assuming your Subject model has field named slug in it, you need to update your code to
<li class="">{{ sub }}</li>
ie. pass an appropriate parameter to {%url ... %}. Change sub.slug to whatever field name you want to refer to.
If you want to, you can also do {% url 'nav' sub_slug=sub.slug %}.
You are trying to pass sub_slug, but which is not defined in the template context, and result in empty string. So nav url will not get any parameter.
I see similar problem in your other {%url ...%} tags in the template.