Django Pagination - how to reduce number of displayed links? - python

I'd like to display << 1 2 3 4... 11 12 >> instead of << 1 2 3 4 5 6 7 8 9 10 11 12>> in my Django site and using pagination.
This is what I get currently :
My html code :
<div class="row">
<div class="col-md-12">
{% if jokes.has_other_pages %}
<ul class="pagination">
{% if jokes.has_previous %}
<li class="page-item">
«
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link">«</a>
</li>
{% endif %}
{% for i in jokes.paginator.page_range %}
{% if jokes.number == i %}
<li class="page-item active">
<a class="page-link">{{i}}</a>
</li>
{% else %}
<li class="page-item">
{{i}}
</li>
{% endif %}
{% endfor %}
{% if jokes.has_next %}
<li class="page-item">
»
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link">»</a>
</li>
{% endif %}
</ul>
{% endif %}
</div>
Thanks in advance !

Here is a flask example, which should give you a good starting point:
<div style="margin:25px 20% 100px 20%;">
{% for page_num in posts.iter_pages(left_edge=1, right_edge=1, left_current=1, right_current=2) %}
{% if page_num %}
{% if posts.page == page_num %}
<a class="btn btn-info mb-4" href=" {{ url_for('myposts', page=page_num) }}">{{ page_num }}</a>
{% else %}
<a class="btn btn-outline-info mb-4" href=" {{ url_for('myposts', page=page_num) }}">{{ page_num }}</a>
{% endif %}
{% else %}
...
{% endif %}
{% endfor %}
</div>

you can use it like this
{% if jokes.has_previous %}
first
{{ jokes.previous_page_number }}
{% endif %}
<span class="current">
{{ jokes.number }}
</span>
{% if jokes.has_next %}
{{ jokes.next_page_number }}
last
{% endif %}
it will result like:
first 4 5 6 last
4 is previous page
5 is current page
6 is next page

Related

How to navigate through multiple pages from search in Django?

I am trying to create a webpage using Django where my database consists thousands of CVEs and display the list of CVEs based on what I typed in the search bar. So far, the search function is working perfectly and it displays the paginated results. But when I try to navigate to next page, it will show a blank page. Below is the code of my views.py and template.
views.py
def search_cve(request):
if request.method == 'GET':
searched = request.GET.get('searched')
if searched:
cve_search = BDSA.objects.filter(cve_id__icontains=searched)
paginator = Paginator(cve_search.order_by('-cve_id'), 10) # show 10 per page
try:
page = int(request.GET.get('page', '1'))
except:
page = 1
try:
cves = paginator.page(page)
except:
cves = paginator.page(1)
index = cves.number - 1
max_index = len(paginator.page_range)
start_index = index - 2 if index >= 2 else 0
end_index = index + 2 if index <= max_index - 2 else max_index
page_range = list(paginator.page_range)[start_index:end_index]
return render(request, 'QueryService/search_cve.html', {'searched':searched, 'cve_search':cve_search, 'cves': cves, 'page_range': page_range})
else:
return render(request, 'QueryService/search_cve.html', {})
template
<body>
{% block content %}
<br/>
<center>
<h1>Searched for: {{ searched }}</h1>
<br/>
<div class="container">
<table class="table table-hover table-striped table-bordered">
{% for cve in cves %}
<tr>
<td>{{ cve }}</td>
</tr>
{% endfor %}
</table>
</div>
<br/>
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
{% if cves.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={{ cves.previous_page_number }}">Prev</a></li>
{% endif %}
{% for pg in page_range %}
{% if cves.number == pg %}
<li class="page-item"><a class="page-link" href="?page={{ pg }}" class="btn btn-default">{{ pg }}</a></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ pg }}" class="btn">{{ pg }}</a></li>
{% endif %}
{% endfor %}
{% if cves.has_next %}
<li class="page-item"><a class="page-link" href="?page={{ cves.next_page_number }}">Next</a></li>
<li class="page-item"><a class="page-link" href="?page={{ cves.paginator.num_pages }}">Last »</a></li>
{% endif %}
</ul>
</nav>
<br/><br/>
</center>
{% endblock %}
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
</body>
Any idea on how to fix this?
Nevermind I got the fix! I added this code in the href element of the pagination.
&searched={{ searched }}
For example:
href="?page=1&searched={{ searched }}

I am stuck when accessing the DetailView template in Django

urls.py
urlpatterns = [
path('',CourseList.as_view(),name='course_list'),
path('create/',CourseCreate.as_view(),name='course_create'),
path('<int:cid>/',CourseView.as_view(),name='course_view'),
]
views.py
COURSE_PERM_GUEST = 0
COURSE_PERM_STUDENT = 1
COURSE_PERM_TEACHER = 2
COURSE_PERM_MEMBER = 3
class CourseAccessMixin(AccessMixin):
permission = None
extra_context = {}
def dispatch(self,request,*args,**kwargs):
if not request.user.is_authenticated:
return super().handle_no_permission()
self.course = get_object_or_404(Course,id=kwargs['cid'])
user_perm = COURSE_PERM_GUEST
if self.course.teacher == request.user:
user_perm = COURSE_PERM_TEACHER
elif self.course.enroll_set.filter(student=request.user).exists():
user_perm = COURSE_PERM_STUDENT
if not request.user.is_superuser and self.permission is not None:
is_accessible = False
if self.permission == COURSE_PERM_GUEST and \
user_perm == COURSE_PERM_GUEST:
is_accessible = True
elif (self.permission & user_perm) != 0:
is_accessible = True
if not is_accessible:
return super().handle_no_permission()
self.extra_context.update({'course':self.course})
return super().dispatch(request,*args,**kwargs)
class CourseView(CourseAccessMixin,DetailView):
extra_context = {'title':'檢視課程'}
model = Course
pk_url_kwarg = 'cid'
models.py
class Course(models.Model):
name = models.CharField('課程名稱',max_length=50)
enroll_password = models.CharField('選課密碼',max_length=50)
teacher = models.ForeignKey(User,models.CASCADE)
def __str__(self):
return '{}#{} ({})'.format(
self.id,
self.name,
self.teacher.first_name)
course_list.html
{% extends "base.html" %}
{% load user_tags %}
{% block content %}
{% if user|is_teacher %}
<div class="mb-2">
建立課程
</div>
{% endif %}
<div id="course_list">
{% for course in course_list %}
<div class="list-group">
<div class="list-group-item d-flex">
{% if user.is_authenticated %}
{{ course.name }}
{% else %}
{{ course.name }}
{% endif %}
<small class="ml-auto">{{ course.teacher.first_name }} 老師</small>
</div>
</div>
{% endfor %}
</div>
{% include 'pagination.html' %}
{% endblock %}
course_detail.html
{% extends 'base.html' %}
{% load user_tags %}
{% block content %}
<div id="course_view" class="card">
{% with b1 = "btn btn-sm btn-primary" b2 = "btn btn-sm btn-secondary" %}
<div class="card-header d-flex">
<div>
{{ course.name }}
<small>{{ course.teacher.first_name }} 老師</small>
</div>
{% if user.is_superuser or course.teacher == user %}
<div class="ml-auto">
<span class="badge badge-light">
選課密碼: {{ course.enroll_password }}
</span>
<a href="{% url 'course_edit' course.id %}" class="{{ b1 }}">
<i class="fas fa-edit"></i>編輯
</a>
</div>
{% endif %}
</div>
<div class="card-body">
{% block course_detail_body %}
<div id="student_op" class="btn-group">
{% if not course|has_member:user and not user.is_superuser %}
<a href="{% url 'course_enroll' course.id %}" class="{{ b1 }}">
<i class="fas fa-id-badge"></i>選修
</a>
{% else %}
<a href="{% url 'course_users' course.id %}" class="{{ b1 }}">
<i class="fas fa-users"></i>修課名單
</a>
{% if course|has_student:user %}
<a href="{% url 'course_seat' course.id %}" class="{{ b1 }}">
<i class="fas fa-chair"></i>更改座號
</a>
{% endif %}
{% endif %}
</div>
{% endblock %}
</div>
{% endwith %}
</div>
{% endblock %}
Error:
ValueError at /course/1/
The view course.views.CourseView didn't return an HttpResponse object. It returned None instead.
I had a view CourseView and when I try to access the detail of the corresponding course by clicking the hyperlink {{ course.name }} from the template course_list.html, I received the error message showing that it didn't return an HttpResponse object. I do not know what is missing in order to have the details accessible even though I had set the pk_url_kwarg?
You forget to add return super().dispatch(request,*args,**kwargs) and the end of dispatch function (you add it, only in if condition)
change this
self.extra_context.update({'course':self.course})
return super().dispatch(request,*args,**kwargs)
To this:
self.extra_context.update({'course':self.course})
return super().dispatch(request,*args,**kwargs)
return super().dispatch(request,*args,**kwargs)

modify the value of paginate_by for all pages

I have a follow-up question on the answer to Change value for paginate_by on the fly
I added the following in the HTML
<form method="GET">
<select name="paginate_by" id="">
<option value="5">5</option>
<option value="10">10</option>
<option value="20">20</option>
<option value="50">50</option>
</select>
<input type="submit" value="Paginate">
</form>
and this function in my ListView class
class ReviewPostListView(ListView):
model = Reviews
template_name = 'reviews/reviews.html'
context_object_name = 'rows'
ordering = ['id']
paginate_by = 5
def get_paginate_by(self, queryset):
return self.request.GET.get("paginate_by", self.paginate_by)
it is working great, and the paginate_by is added to the URL. My problem is when I click on the second page it goes back to 5.
This is my pagination HTML
{% if is_paginated %}
{% if page_obj.has_previous %}
<a class="btn btn-outline-info mb-4" href="?page=1">First</a>
<a class="btn btn-outline-info mb-4" href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<a class="btn btn-info mb-4" href="?page={{ num }}">{{num}}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{num}}</a>
{% endif %}
{% endfor %}
{%if page_obj.has_next %}
<a class="btn btn-outline-info mb-4" href="?page={{page_obj.next_page_number}}">Next</a>
<a class="btn btn-outline-info mb-4" href="?page={{ page_obj.paginator.num_pages }}">Last</a>
{% endif %}
{% endif %}
What is the pythonic of keeping the paginate_by (if exists) parameter in the URL when looping on the pages?
Using {{ request.GET.paginate_by }}, you can access the paginate_by query parameters in your template.
So, your pagination HTML will look like:
{% if is_paginated %}
{% firstof request.GET.paginate_by "5" as paginate_by %}
{% if page_obj.has_previous %}
<a class="btn btn-outline-info mb-4" href="?page=1&paginate_by={{ paginate_by }}">First</a>
<a class="btn btn-outline-info mb-4" href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<a class="btn btn-info mb-4" href="?page={{ num }}">{{num}}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{num}}</a>
{% endif %}
{% endfor %}
{%if page_obj.has_next %}
<a class="btn btn-outline-info mb-4" href="?page={{page_obj.next_page_number}}&paginate_by={{ paginate_by }}">Next</a>
<a class="btn btn-outline-info mb-4" href="?page={{ page_obj.paginator.num_pages }}&paginate_by={{ paginate_by }}">Last</a>
{% endif %}
{% endif %}
firstof is will set the value of paginate_by to the query parameter or 5, which will be the default value.
You can create a template tag which will handle the query params for you.
Here are two simple tags
#register.simple_tag(takes_context=True)
def add_get_param(context, **kwargs):
"Used to add/replace query parameters to the current URL."
params = context["request"].GET.dict()
params.update(kwargs)
return "?{}".format(urlencode(params))
#register.simple_tag(takes_context=True)
def remove_get_param(context, *args):
"Used to remove query parameters from the current URL."
params = context["request"].GET.dict()
for key in args:
params.pop(key, None)
return "?{}".format(urlencode(params))
And your HTML will look like
{% if is_paginated %}
{% if page_obj.has_previous %}
<a class="btn btn-outline-info mb-4" href="?paginate_by={% add_get_param page=1 %}">First</a>
<a class="btn btn-outline-info mb-4" href="{% add_get_param page=page_obj.previous_page_number %}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<a class="btn btn-info mb-4" href="{% add_get_param page=num %}">{{num}}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-info mb-4" href="?paginate_by={{ paginate_by}}&page={{ num }}">{{num}}</a>
{% endif %}
{% endfor %}
{%if page_obj.has_next %}
<a class="btn btn-outline-info mb-4" href="{% add_get_param page=page_obj.next_page_number %}">Next</a>
<a class="btn btn-outline-info mb-4" href="{% add_get_param page={{ page_obj.paginator.num_pages %}">Last</a>
{% endif %}
{% endif %}
With this whatever was already there in the query params will continue to be there, only the page will be changed. So this works if you decide to add more params such as for filtering.
You can try this in your HTML template:
<select name="paginate_by" onchange="location = this.value;">
<option value="">select</option>
<option value="?paginate_by=5&{{page_obj.number}}">5</option>
<option value="?paginate_by=10&{{page_obj.number}}">10</option>
<option value="?paginate_by=20&{{page_obj.number}}">20</option>
<option value="?paginate_by=50&{{page_obj.number}}">50</option>
</select>
Pass the paginate_by object into the html and add it to the pagination links. see below code:
{% if is_paginated %}
{% if page_obj.has_previous %}
<a class="btn btn-outline-info mb-4" href="?paginate_by={{ paginate_by}}&page=1">First</a>
<a class="btn btn-outline-info mb-4" href="?paginate_by={{ paginate_by}}&page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<a class="btn btn-info mb-4" href="?paginate_by={{ paginate_by}}&page={{ num }}">{{num}}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-info mb-4" href="?paginate_by={{ paginate_by}}&page={{ num }}">{{num}}</a>
{% endif %}
{% endfor %}
{%if page_obj.has_next %}
<a class="btn btn-outline-info mb-4" href="?paginate_by={{ paginate_by}}&page={{page_obj.next_page_number}}">Next</a>
<a class="btn btn-outline-info mb-4" href="?paginate_by={{ paginate_by}}&page={{ page_obj.paginator.num_pages }}">Last</a>
{% endif %}
{% endif %}

Python Jinjna variable doesnt keep value outside loop

I am currently trying to fix an issue by using jinja variables, but somehow the variable does not keep the value outside the loop, even though I declared it before the loop begins:
{% set disablecounter = 0 %}
{% if disablecounter == 0 %}
{% for einzelroom in all_einzelzimmer %}
{% if zimmer.id == einzelroom.zimmer_id %}
{% set price = einzelroom.preis %}
<div class="preis-element">
<p class="preis"> <span class="smallab"> ab </span> {{ price|int }}€ </p>
</div>
{% set disablecounter = disablecounter + 1 %}
{{ disablecounter }}
{% endif %}
{% endfor %}
{% endif %}
{{ disablecounter }}
The variable is disablecounter inside the loop it is 1 but outside it is still 0
Thanks!
EDIT
Surrounding with a with statement also didnt worked:
{% with foo = 42 %}
{{ foo }}
{% endwith %}
{% with %}
{% set foo = 42 %}
{{ foo }}
{% endwith %}
I found a great solution here on SO by #Chris Warth.
Original answer by #Peter Hollingsworth:
https://stackoverflow.com/a/32700975/5291566
{% with disablecounter = [0] %}
{% if disablecounter == [0] %}
{% for einzelroom in all_einzelzimmer %}
{% if zimmer.id == einzelroom.zimmer_id %}
<div class="preis-element">
<p class="preis"> <span class="smallab"> ab </span> {{ einzelroom.preis|int }}€ </p>
</div>
{% if disablecounter.append(disablecounter.pop() + 1) %}{% endif %}
{% endif %}
{% endfor %}
{% endif %}

Break items into columns evenly in Jinja2 template

I have a list of cities names that is variable and I want to break it into 4 columns evenly. I have some solution but it looks overwhelmed and dirty. What's the best and simplest way to do it?
My solution is here:
{% set cities_in_column = cities|length/4|int %}
{% set step=0 %}
<div class="four columns">
{% for city in cities|sort %}
{% if step > cities_in_column %}
{% set step = 0 %}
</div>
<div class="four columns">
{% endif %}
<h5>{{ city.name }} <span style="float:right;">({{ city.users_count }})</span></h5>
{% set step=step + 1 %}
{% endfor %}
</div>
You are looking for the slices filter:
{% for column in cities | sort | slice(4) -%}
<div class="four columns">
{%- for city in column -%}
<h5>{{ city.name }}
<span style="float:right;">({{ city.users_count }})</span></h5>
{%- endfor -%}
</div>
{%- endfor %}
There is also a complement to slices called batch that provides runs of n (rather than splitting up the iterable into n groups).
This is a job for CSS*:
<ol style="column-count: 4;">
{% for city in cities|sort %}
<li>
{{ city.name }} ({{ city.users_count }})
</li>
{% endfor %}
</ol>
(*Be it column-count, float, flex, etc..)

Categories