I am trying to return paginated objects and then iterate through them. Seems pretty straightforward. Apparently I'm missing something, though. Can you spot the error?
The view:
def thumbnails(request):
page = request.GET.get('page', 1)
album = request.GET.get('a', None )
if (album):
objects = Album_Photos.objects.filter(id=album)
else:
objects = None
if (objects):
paginator = Paginator(objects, 25)
try:
photos = paginator.page(page)
except PageNotAnInteger:
photos = paginator.page(1)
except EmptyPage:
photos = None #paginator.page(paginator.num_pages)
return render_to_response('photos/thumbnails.html', {'photos': photos}, context_instance = RequestContext(request))
The template:
{% if photos %}
{% for photo in photos %}
<img src="{{photo.original.url}}">
{% endfor %}
{%endif%}
The error:
TemplateSyntaxError at /photos/thumbnails/
Caught TypeError while rendering: 'Page' object is not iterable
1 {% if photos %}
2 {% for photo in photos %}
3 <img src="{{photo.original.url}}">
4 {% endfor %}
5 {%endif%}
Well, unlike the example in the Django docs (at least in my case), you must append .object_list to that Page object.
{% if photos %}
{% for photo in photos.object_list %}
<img src="{{photo.original.url}}">
{% endfor %}
{%endif%}
This has been changed in django: somewhere between version 1.3 and 1.6, Paginator.Page has been made iterable.
If you follow the example from the current documentation, while using an older version of django, you get this error.
Either append .object_list as Brian D. said, or upgrade django.
Related
I am a newbie in Elasticsearch, I just try to create a search engine using it in Django. Overall, the engine shows good results. Unfortunately, it loads a large number of the results. Then, I try to paginate it by regular pagination in Django, after that, the page load error object of type 'Search' has no len().
These are my codes:
view.py
from django.shortcuts import render, redirect
from elasticsearch import Elasticsearch
from elasticsearch_dsl import Search
from django.core.paginator import Paginator
def search_es(request):
return render(request,'search/search.html')
def results(request):
s = Search(using=Elasticsearch())
keyword = request.GET.get('q') # keyword that want to be found
print(keyword)
if keyword:
# posts = s.query('match_phrase_prefix',head_title=keyword)
# if posts.count() == 0:
posts = s.query(
"multi_match",
query=keyword,
fields=['head_title^5', 'description^5', 'description.ngram'],
# type="phrase_prefix",
)
posts = posts[0: 100]
else:
posts = ''
page = request.GET.get('page', 1)
paginator = Paginator(posts, 10)
try:
users = paginator.page(page)
except PageNotAnInteger:
users = paginator.page(1)
except EmptyPage:
users = paginator.page(paginator.num_pages)
context = {
'page_title': keyword,
'posts': users,
'count': posts.count(),
'keyword': keyword,
}
return render(request,'search/results.html',context)
results.html
{% if posts.has_other_pages %}
<ul class="pagination">
{% if posts.has_previous %}
<li>«</li>
{% else %}
<li class="disabled"><span>«</span></li>
{% endif %}
{% for i in posts.paginator.page_range %}
{% if posts.number == i %}
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
{% else %}
<li>{{ i }}</li>
{% endif %}
{% endfor %}
{% if posts.has_next %}
<li>»</li>
{% else %}
<li class="disabled"><span>»</span></li>
{% endif %}
</ul>
{% endif %}
Hope for every possible solution.
Thank you very much.
ElasticSearch provided two parameters you can use for pagination: from and size. You can refer to https://elasticsearch-dsl.readthedocs.io/en/latest/search_dsl.html#pagination
for example:
posts = s[0:20].query(
"multi_match",
query=keyword,
fields=['head_title^5', 'description^5', 'description.ngram'],
# type="phrase_prefix",
)
to get first page, and page size is 20.
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.
I have a page on my site that is dedicated to group comments. Users can create posts (i.e. questions or comments), and other users can comment on them. Comment hierarchy is identical to Facebook; there is no comment threading like reddit. Using Heroku.
I'm using django-debug-toolbar to optimize this page. Right now the page loads in 5-6 seconds; that's just the time that my browser waits for a response from the server (doesn't include JS/CSS/IMG load times). There are 30 SQL queries take between 80-100ms to load (am using prefetch_related on relevant m2m fields). The size of the page returned is 344KB, so not a monster page at all. The comments are paginated and I'm only returning 10 posts + all comments (in test each post only has 3-4 comments).
I can't figure out why it takes such a long time to load the page when the SQL queries are only taking up to 100ms to complete.
Below is the relevant code. I am getting back the 'posts' object and using a for loop to render each object. Within that 'posts' for loop I'm doing another for loop for the 'comments'.
What else can I do to further optimize this/reduce page load time?
# views.py
def group_app(request, course_slug):
# get the group & plan objects
group = Group.objects.get(slug=course_slug)
# load the page with a new Post and comment form
group_post_form = newGroupPost()
post_comment_form = newPostComment(auto_id=False)
# gather all the Posts that have already been created
group_posts = GroupPost.objects.filter(group=group).prefetch_related('comments', 'member', 'group')
paginator = Paginator(group_posts, 10)
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
page = 1
posts = paginator.page(page)
except EmptyPage:
posts = paginator.page(paginator.num_pages)
variables = RequestContext(request, {
'group': group,
'group_post_form': group_post_form,
'posts': posts,
'next_page': int(page) + 1,
'has_next': posts.has_next(),
'post_comment_form': post_comment_form
})
if request.is_ajax():
return render_to_response('group-post.html', variables)
else:
return render_to_response('group-app.html', variables)
# group-app.html
{% extends 'base.html' %}
{% block canvas %}
<div id="group-body" class="span8">
{% include "group-body.html" %}
</div>
{% endblock canvas %}
# group-body.html
{% for post in posts %}
{% include 'group-post-item.html' %}
{% endfor %}
# group-post-item.html
{% with post_avatar=post.member.get_profile.medium_image.url %}
<div class="groupPost" id="{{ post.id }}">
<div class="avatarContainer">
{% if post_avatar %}
<img class="memberAvatar" src="{{ post_avatar }}" alt="">
{% else %}
<img class="memberAvatar" src="{{ STATIC_URL }}img/generic-avatar.png">
{% endif %}
</div>
<div class="postContents">
<div class="groupPostComment">
<h6 class="comment-header">SHOW/ADD COMMENTS</h6>
<div class="comments">
{% for comment in post.comments.all %}
{% with comment_avatar=comment.member.get_profile.medium_image.url %}
<div class="commentText">
<p class="comment-p">{{ comment.comment }}</p>
</div>
{% endwith %}
{% endfor %}
</div>
</div>
</div>
</div>
{% endwith %}
As I understand this is related to avatar image and not SQL queries. Working with remote storages like S3 is a bit slow. But it should not hamper the user experience as you have faced above.
I didn't get what you are using to get 'avatar' image, but I would suggest to use some third party packages like.
Easy_thumbnails - It is really nice and has lots of options. I recently shifted to this you. In case of remote storages, you could pregenerate avatars with celery and serve saved thumbnail
sorl-thumbnail - I used this for my previous project. It doesn't have option to pregenerate, so first request is slow. But once thumbnail is created, next request will be fast. It has smart caching. I tried to fork it here to use with celery https://github.com/neokya/sorl-thumbnail-async
I hope it helps.
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.