I am a beginner in Django, and I am really thoughtful about how things really work under the Django hood. I am currently implementing pagination in my web app.
Take a look at this view.py file:
def post_list(request):
object_list = Post.published.all(); # '.published' is a manager.
paginator = Paginator(object_list, 3); # 3 posts in each page.
page = request.GET.get("page");
try:
posts = paginator.page(page);
except PageNotAnInteger:
posts = paginator.page(1);
except EmptyPage:
posts = paginator.page(paginator.num_pages);
return render(request, "post/list.html", {"page": page, "posts": posts});
Isn't request.GET a dictionary object containing all the the GET request parameters that are in the url, and the .get() method used to return the value
for the given key inside the parameter? As my URL is currently just localhost:8000 when I start the application, why does it work if I pass the key "page"?
My list.html file:
{% extends "base.html" %}
{% block title %}My Blog{% endblock %}
{% block content %}
<h1>My Blog</h1>
{% for post in posts %}
<h2>{{ post.title }}</h2> <!-- How does this absurl work?-->
<p class="date">Published {{ post.publish }} by {{ post.author }}</p> <!-- What does '.publish' print?-->
{{ post.body|truncatewords:30|linebreaks }}
{% endfor %}
{% include "pagination.html" with page=posts %}
<!-- The statement above is the little menu: "Page 1 of 2. Next" -->
<!-- It also sends the 'page' variable as a GET parameter. -->
{% endblock %}
My pagination.html file:
<!-- This pagination template expects a paginator object. -->
<div class="pagination">
<span class="step-links">
{% if page.has_previous %}
Previous
{% endif %}
<span class="current">
Page {{ page.number }} of {{ page.paginator.num_pages }}. <!-- what!? -->
</span>
{% if page.has_next %}
Next
{% endif %}
</span>
</div>
When there are no parameters in the request (when you hit http://localhost:8000 directly), the value of page will be None. That is the default behaviour of request.GET.get() when it can't find the key you're asking for - the same as a normal Python dictionary (because GET extends it).
# page will be None
page = request.GET.get("page")
This means that None is passed to paginator.page():
try:
# Passing None here
posts = paginator.page(page)
except PageNotAnInteger:
Which likely means (although we can't see the code of paginagor) that a PageNotAnInteger exception is raised, and thus a value of 1 is passed to paginagor.page():
try:
posts = paginator.page(page) # Raises PageNotAnInteger because None passed
except PageNotAnInteger:
# Posts are retrieved for page 1
posts = paginator.page(1)
The posts from the above call, and the value of page (still None) are then passed to the template.
return render(request, "post/list.html", {"page": page, "posts": posts});
The template list.html then iterates the posts and displays them.
Rather confusingly, when the pagination.html template is included, it defines a context variable called page to the current value of posts:
<!-- Pass the value of posts using a variable name of page -->
{% include "pagination.html" with page=posts %}
So the places where the pagination.html template refers to page, it is actually using the value of posts.
<!-- Really posts.number and posts.paginator.num_pages -->
Page {{ page.number }} of {{ page.paginator.num_pages }}
Hope that helps to explain things.
One other thing, you don't need to add a semi-colon to the end of every line in Python.
Related
I'm making a portfolio project where I'm using the Google Books API to do a books search, and the Django Paginator class to paginate the results. I've been able to get search results using a CBV FormView and a GET request, but I can't seem to figure out how to get pagination working for the API response.
The solution I can think of is to append &page=1 to the url of the first search, then pull that param on every GET request and use that to paginate. The problem is, I can't figure out how to append that param on the first search, and I don't know how I'd increment that param value when clicking the pagination buttons.
Here's what I've got now:
Form:
class SearchForm(forms.Form):
search = forms.CharField(label='Search:', max_length=150)
View:
class HomeView(FormView):
template_name = "home.html"
form_class = SearchForm
pageIndex = 0
def get(self, request, *args, **kwargs):
# get submitted results in view and display them on results page. This will be swapped out for an AJAX call eventually
if "search" in request.GET:
# getting search from URL params
search = request.GET["search"]
kwargs["search"] = search
context = super().get_context_data(**kwargs)
# Rest API request
response = requests.get(
f'https://www.googleapis.com/books/v1/volumes?q={search}&startIndex={self.pageIndex}&key={env("BOOKS_API_KEY")}'
)
response = response.json()
items = response.get("items")
# pagination...needs work
paginator = Paginator(items, 2)
page_obj = paginator.get_page(1)
context["results"] = page_obj
return self.render_to_response(context)
else:
return self.render_to_response(self.get_context_data())
Template:
{% extends "base.html" %}
{% block content %}
<form action="/">
{{ form }}
<input type="submit" value="Submit">
</form>
<h1>Books</h1>
<ul>
{% for result in results %}
<li>{{ result.volumeInfo.title }} : {{result.volumeInfo.authors.0}}</li>
{% empty %}
<li>Search to see results</li>
{% endfor %}
</ul>
{% if results %}
<div class="pagination">
<span class="step-links">
{% if results.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ results.number }} of {{ results.paginator.num_pages }}
</span>
{% if results.has_next %}
next
last »
{% endif %}
</span>
</div>
{% endif %}
{% endblock content %}
I also looked at Django REST Framework for this, but the Google Books API response doesn't contain any info on next page, previous page, etc. I've done this kind of pagination in React and it's not difficult, I'm just having trouble adjusting my mental model for how to do this to Django. If anyone could offer some advice on how to make this work, I'd be very grateful.
I am using django as backend for website
There will be 10 qwestions
i want to paginate them (one qwestion's ans is given then go to next so on ) and atlast give marks calculated based on all the answers given
Any hints on how to do it?
I found somthing similar but dont know how to implement in django
What's the most efficient way to calculate a running total/balance when using pagination (PHP, MySQL)
Have you looked to Django Pagination docs?
https://docs.djangoproject.com/en/2.1/topics/pagination/
From that page
Your view:
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.shortcuts import render
def listing(request):
contact_list = Contacts.objects.all()
paginator = Paginator(contact_list, 25) # 25 is the items per page, in Your case would be 1
page = request.GET.get('page')
contacts = paginator.get_page(page)
return render(request, 'list.html', {'contacts': contacts})
Your template:
{% for contact in contacts %}
{# Each "contact" is a Contact model object. #}
{{ contact.full_name|upper }}<br>
...
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if contacts.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
</span>
{% if contacts.has_next %}
next
last »
{% endif %}
</span>
</div>
To go to a page you should add to the url ?page=1 where 1 is the page number.
I follow this Document of djangoproject.com : https://docs.djangoproject.com/en/1.8/topics/pagination/. But it is too simple. It is only Next and Previous button.
Now I want create pagination with more features such as http://i.imgur.com/ZiFeAqG.jpg.
This is code:
View.py
def hire(request):
hire_article_list = hire_article.objects.all().order_by('-id')
#hire_article_list = hire_article.objects.order_by("-publication_date")
paginator = Paginator(hire_article_list, 2) # Show 25 contacts per page
page = request.GET.get('page')
try:
hire_article_s = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
hire_article_s = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
hire_article_s = paginator.page(paginator.num_pages)
#return render_to_response('hire/list.html', {"page_list": page_list})
context = {'hire_article_s': hire_article_s}
return render(request, 'hire/list.html', context)
list.html
{% for j in hire_article_s %}
{# Each "j" is a page_list model object. #}
<li>{{ j.hiring}}</li>
{% endfor %}
{% if hire_article_s.has_previous %}
previous
{% endif %}
<span class="current">
Page {{ hire_article_s.number }} of {{ hire_article_s.paginator.num_pages }}.
</span>
{% if hire_article_s.has_next %}
next
{% endif %}
</span>
</div>
I had a similar need last week and found this super useful gist (https://gist.github.com/jsatt/8183993) that worked fine (though I'm not sure why it wouldn't work till I put request in the function parameters). It's a subclass of django's Paginator function. You could put this in a utility file and call it whenever you want to use Paginate with the range.
For instance, I had mine in a file called utils.py, which is in my core app.
views.py
from core.utils import paginate
def hire(request):
hire_article_list = hire_article.objects.all().order_by('-id')
'''
Show 25 contacts per page, with a page range of 5, which means if you are
on page 8, it shows links to pages 6,7,8,9,10.
'''
hire_article_s = paginate(request, hire_article_list, 25, 5)
context = {'hire_article_s': hire_article_s}
return render(request, 'hire/list.html', context)
list.html
{% if hire_article_s.has_previous %}
previous
{% endif %}
{% for range in hire_article_s.neighbor_range %}
{% if range == hire_article_s.number %}
<li class="pagination__item active ">{{ range }}</li>
{% else %}
<li class="{% if range == hire_article_s.number %}active {% endif %}">{{ range }}</li>
{% endif %}
{% endfor %}
{% if hire_article_s.has_next %}
next
{% endif %}
Hope this helps.
UPDATE
The above code has been edited a bit. I've added the context and the template format. Note that I'm using a loop to go through {{ hire_article_s.neighbor_range }} and print out the page numbers. I also do a check to highlight the current page's number. the This should work, as it's pretty much my own code with your own variable names.
So I am trying to use pagination to display all the matching classes available on a certain day, but following the pagination docs, each page just returns the same 10 results. What am I missing/ what should I have in urlconf? Additionally, if I try using pagination to display search results, I get the error "The view search.views.search_classes didn't return an HttpResponse object" when I try to select the next page. Any input into either or both examples would be greatly appreciated.
#views.py
def upcoming_class_list(request, day):
try:
day = int(day)
except ValueError:
raise Http404()
today = datetime.date.today()
day_x = datetime.date.today() + datetime.timedelta(days=day)
day_x_classes = UpcomingClasses.objects.filter(class_date=day_x)
all_matches = day_x_classes
paginator = Paginator(all_matches, 10)
page = request.GET.get('page')
try:
matches = paginator.page(page)
except PageNotAnInteger:
matches = paginator.page(1)
except EmptyPage:
matches = paginator.page(paginator.num_pages)
return render_to_response('preview.html', {'today': today, 'tomorrow': tomorrow,
'past_classes': past_classes, 'day_x': day_x, 'day': day,
'day_x_classes': day_x_classes, 'yesterday': yesterday, 'matches': matches,
'page': page}, context_instance = RequestContext(request))
#urls.py
(r'^upcoming_class_list/plus/(\d{1,2})/$', upcoming_class_list),
#preview.html
<h3>Classes for {{ day_x }}</h3>
{% if matches %}
<div class="pagination">
<span class="step-links">
{% if matches.has_previous %}
« previous
{% endif %}
<span class="current">
Page {{ matches.number }} of {{ matches.paginator.num_pages }}
</span>
{% if matches.has_next %}
next »
{% endif %}
</span>
</div>
{% if day_x_classes %}
<ul type=none>
{% for class in day_x_classes %}
<li>
<ul type=none>
<li><strong>{{ class.type }}</strong></li>
<li>Teacher: {{ class.teacher }}</li>
<li>Class Description: {{ class.description }}</li>
...
</ul>
</li><br />
{% endfor %}
</ul>
{% endif %}
{% else %}
<p>There are currently no scheduled upcoming classes for {{ day_x }}.</p>
{% endif %}
Anything coming from GET or POST will be a string, so you're always hitting that first exception. Try:
try:
matches = paginator.page(int(page))
except (PageNotAnInteger, ValueError):
matches = paginator.page(1)
It's hard to guess at the rest of the issue without seeing the rest of your view. Looking at other bits in the view, you shouldn't need the check for day being an int as you've already assured that in your urls.py file with the regex, but you don't call the Http404 object, it's simply raise Http404
Ok so I figured out the answer to both of my questions. The first part was because I was making a dumb mistake in my template. The view urlconf were correct, but in my template, my for loop stated:
{% for class in day_x_classes %}
when I should have been using
{% for class in matches %}
since matches was being paginated, not day_x_classes.
As far as paginating my search results, I simply needed to edit the "previous" and "next" buttons from
« previous
from
« previous
to account for q (the searched term).
I hope that my mistakes will be able to help someone who was stuck in a similar situation.
I am using django pagination, as told in documentation:
view part is :
def list(request):
job_list = Job.objects.all()
paginator = Paginator(job_list, 25) # Show 25 jobs per page
page = request.GET.get('page',1)
try:
jobs = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
jobs = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
jobs = paginator.page(paginator.num_pages)
return render_to_response('jobs/list.html', {"jobs": jobs})
and template is:
<div>
{% for job in jobs %}
{# Each "contact" is a Contact model object. #}
{{ job.title|upper }}<br />
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if contacts.has_previous %}
previous
{% endif %}
<span class="current">
Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
</span>
{% if contacts.has_next %}
next
{% endif %}
</span>
</div>
</div>
But it gives the error saying:
In template d:\programming\django_projects\kaasib\templates\jobs\list.html, error at line 32
Caught TypeError while rendering: 'Page' object is not iterable
I am new in django and this error seems general but very strange. Because in loop there is some other variable, not job. So please tell if anyone have any idea about it.
thanks
The error should be clear - the variable you've called jobs actually contains a Page object from the paginator. Which is as it should be, as you assigned jobs to paginator.page(x). So obviously, it contains a Page.
The documentation shows what to do:
{% for job in jobs.object_list %}
etc.