infinite pagination doesn't render variables - django

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 %}

Related

Django: Access num_pages in view to generate pagination

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 %}

How to load data from variable into django paginator?

I need to create paginator based on data from variable. Variable is just python lists.
views.py:
def test(request):
db = MySQLdb.connect(host="localhost", # your host, usually localhost
user="pc", # your username
passwd="3346378", # your password
db="mc")
cur = db.cursor()
cur.execute('''SELECT * FROM hello left join hell on hello.Id=hell.Id ''')
row = cur.fetchall()
So, row is lot's of lists. And how can I load it into paginator?
Or one way is to create table and model for data and work with it using Django ORM or something?
Django has built in Paginator class you can check this out: paginator objects and a few examples here: Pagination examples, to use in a view : Using Pagination in view
if it is referencing muliple objects then write plural variable names: so it is rows instead of row
So in your case in the view:
paginator = Paginator(rows, 25) # Show 25 rows per page
page = request.GET.get('page')
rows = paginator.get_page(page)
return render(request, 'example.html', {'rows': rows})
And in HTML:
{% for r in rows %}
{{ r }}<br>
...
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if rows.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ rows.number }} of {{ rows.paginator.num_pages }}.
</span>
{% if rows.has_next %}
next
last »
{% endif %}
</span>
</div>

Django Admin Action using intermediate page

I have a model with a lot of fields. I only have a few fields I that I want to be required. So instead of the change list super long, I want to have a short change list then have admin actions that can give predefined subsets of the fields.
The initial action takes me to the correct page but when I submit the form it returns me to whatever page I designate, but doesn't update the fields. I am okay with tearing this down starting over again if needed. I think what I really need to know, what do I put in the action="" portion of the html to have the recursion work properly?
I am using django 1.7. I have to obfuscate a lot of my fields as a cya thing since I am working in a heavily information secure field.
Here is my admin.py
class CredentialAdmin(admin.ModelAdmin):
fields = ['reservedBy','reserveto']
list_display = ['reservedBy','reserveto']
class reserveToFormAdmin(forms.Form):
reservedBy = forms.CharField(widget=forms.Textarea, max_length=50)
reserveto = forms.DateTimeField(widget=forms.DateTimeInput)
def reserveCred(self, request, queryset):
form = None
plural = ''
if 'submit' in request.POST:
form = self.reserveToFormAdmin(request.POST)
for f in form.fields:
print f
print form.is_valid()
print form.errors
if form.is_valid():
reservetos = form.cleaned_data['reserveto']
reservedBys = form.cleaned_data['reservedBy']
print "hello"
count = 0
for cred in queryset:
cred.reserveto = reservetos
cred.reservedBy = reservedByss
cred.save()
count += 1
if count != 1:
plural = 's'
self.message_user(request, "Successfully reserved %s cred%s." % (count, plural))
return HttpResponseRedirect(request.get_full_path(),c)
if not form:
form = self.reserveToFormAdmin(initial={'_selected_action' : request.POST.getlist(admin.ACTION_CHECKBOX_NAME)})
return render(request,'admin/reserveCreds.html',{'creds':queryset, 'form':form, 'path':request.get_full_path()})
reserveCred.short_description = "Reserve Selected Creds"
actions = [check_out_a_cred,check_in_a_cred,audit_creds,CompareAudits,reserveCred]
reserveCreds.html
{% extends "admin/base_site.html" %}
{% block content %}
<p>How long and which department to reserver creds:</p>
<form action="{{ path }}" method="post">{% csrf_token %}
{{ form }}
<input type="submit" name="submit" value="submit" />
<input type="button" value = "Cancel" />
</form>
<h2> reserving: </h2>
<ul>
{% for cred in creds %}
<li> {{ cred.userid }} </li>
{% endfor %}
</ul>
{% endblock %}

Image appears in one template, but not in the other

I'm trying to create an app with 2 pages, a general one an a view details page.
In both pages I need the image of the product. In the general view, the image appears, but not in the details page.
This is my html for details page:
<div class="span4">
<h2>{{ prod.name }}</h2><br>
<img alt="" src="{{ STATIC_URL }}{{prod.image}}"></a>
</div>
And here is the html for the general page (the same image is defined here as {{ values.3.0 }}):
{% 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>
{{ values.0.0 }}<br/>
Commodo consequat
<p class="price">#{{values.2.0}} #{{values.2.1}} #{{values.2.2}}</p>
</div></li>
{% endfor %}
Both templates are based on a base, which already imports:
{% load static %}
{% load i18n %}
{% load staticfiles %}
Here is the view for the detailed template:
def single_product(request, slug):
product = get_object_or_404(Product, slug=slug)
prod = Product.objects.get(slug=slug)
reviews = Review.objects.get(product=prod.id)
reviewmatrix = {}
i = 0
try:
for rev in reviews:
reviewmatrix[str(i)] = [[review.review_text]]
u = User.objects.get(username=rev.user)
i = i + 1
except:
u = User.objects.get(username=reviews.user)
reviewmatrix[str(i)] = [[reviews.review_text]]
return render_to_response('product_detail.html', {'prod':prod, 'reviews':reviews, 'user':u.first_name})
And here is the view for the general template
def home(request):
if request.user.is_authenticated():
user = request.user
prods = Product.objects.all()
i = 0
prodmatrix = {}
for prod in prods:
# 0 1 2 3
prodmatrix[str(i)] = [[prod.name], [prod.image], [], [prod.slug]]
review = Review.objects.get(product=prod.id) # ^ this is for tags
for tags in review.tag.all(): #
print tags.name
prodmatrix[str(i)][2].append(tags.name) # append only tags
i = i + 1
#for prod in prods:
# tags = Review.objects.filter(product=prod.id)
# for tag in tags:
# prodmatrix[str(i)] = [[prod.name], [prod.image], [tag]]
# i = i + 1
return render(request, 'home.html',{'prodmatrix':prodmatrix, 'prods':prods})
else:
products = Product.objects.all()
return render(request, 'home.html',{'products':products})
return render(request, 'home.html')
The second view is using the render shortcut, so the context processors are run, which includes the STATIC_URL variable in the context. The first view uses the old render_to_response shortcut, which by default does not run context processors. Change it to use render.

How to paginate Django with other get variables?

I am having problems using pagination in Django. Take the URL below as an example:
http://127.0.0.1:8000/users/?sort=first_name
On this page I sort a list of users by their first_name. Without a sort GET variable it defaults to sort by id.
Now if I click the next link I expect the following URL:
http://127.0.0.1:8000/users/?sort=first_name&page=2
Instead I lose all get variables and end up with
http://127.0.0.1:8000/users/?page=2
This is a problem because the second page is sorted by id instead of first_name.
If I use request.get_full_path I will eventually end up with an ugly URL:
http://127.0.0.1:8000/users/?sort=first_name&page=2&page=3&page=4
What is the solution? Is there a way to access the GET variables on the template and replace the value for the page?
I am using pagination as described in Django's documentation and my preference is to keep using it. The template code I am using is similar to this:
{% if contacts.has_next %}
next
{% endif %}
I thought the custom tags proposed were too complex, this is what I did in the template:
<a href="?{% url_replace request 'page' paginator.next_page_number %}">
And the tag function:
#register.simple_tag
def url_replace(request, field, value):
dict_ = request.GET.copy()
dict_[field] = value
return dict_.urlencode()
If the url_param is not yet in the url, it will be added with value. If it is already there, it will be replaced by the new value. This is a simple solution the suits me, but does not work when the url has multiple parameters with the same name.
You also need the RequestContext request instance to be provided to your template from your view. More info here:
http://lincolnloop.com/blog/2008/may/10/getting-requestcontext-your-templates/
I think url_replace solution may be rewritten more elegantly as
from urllib.parse import urlencode
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
query = context['request'].GET.copy()
query.update(kwargs)
return query.urlencode()
with template string simplified to
<a href="?{% url_replace page=paginator.next_page_number %}">
After some playing around I found a solution... although I don't know if it's really a good one. I'd prefer a more elegant solution.
Anyway I pass the request to the template and am able to access all the GET variables via request.GET. Then I loop through the GET dictionary and as long as the variable isn't page I print it.
{% if contacts.has_previous %}
previous
{% endif %}
<span class="current">
Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
</span>
{# I have all of this in one line in my code (like in the previous section), but I'm putting spaces here for readability. #}
{% if contacts.has_next %}
<a href="?page={{ contacts.next_page_number }}
{% for key,value in request.GET.items %}
{% ifnotequal key 'page' %}
&{{ key }}={{ value }}
{% endifnotequal %}
{% endfor %}
">next</a>
{% endif %}
In your views.py you will somehow access the criteria on which you sort, e.g. first_name. You'll need to pass that value to the template and insert it there to remember it.
Example:
{% if contacts.has_next %}
next
{% endif %}
One can create a context processor to use it wherever pagination is applied.
For example, in my_project/my_app/context_processors.py:
def getvars(request):
"""
Builds a GET variables string to be uses in template links like pagination
when persistence of the GET vars is needed.
"""
variables = request.GET.copy()
if 'page' in variables:
del variables['page']
return {'getvars': '&{0}'.format(variables.urlencode())}
Add the context processor to your Django project settings:
TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.core.context_processors.i18n',
'django.core.context_processors.request',
'django.core.context_processors.media',
'django.core.context_processors.static',
...
'my_project.my_app.context_processors.getvars',
)
Then, in your templates, you can use this when paginating:
<div class="row">
{# Initial/backward buttons #}
<div class="col-xs-4 col-md-4 text-left">
{% trans 'first' %}
{% if page_obj.has_previous %}
{% trans 'previous' %}
{% endif %}
</div>
{# Page selection by number #}
<div class="col-xs-4 col-md-4 text-center content-pagination">
{% for page in page_obj.paginator.page_range %}
{% ifequal page page_obj.number %}
<a class="active">{{ page }}</a>
{% else %}
{{ page }}
{% endifequal %}
{% endfor %}
</div>
{# Final/forward buttons #}
<div class="col-xs-4 col-md-4 text-right">
{% if page_obj.has_next %}
{% trans 'next' %}
{% endif %}
{% trans 'last' %}
</div>
</div>
Whatever GET variables you have in your request, they will be appended after the ?page= GET parameter.
Improvement of this by:
Use urlencode from django instead of urllib, to prevent UnicodeEncodeError error with unicode arguments.
Template tag:
from django.utils.http import urlencode
#register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
query = context['request'].GET.dict()
query.update(kwargs)
return urlencode(query)
Template:
<!-- Pagination -->
<div class="pagination">
<span class="step-links">
{% if coupons.has_previous %}
Prev
{% endif %}
<span class="current">
Page {{ objects.number }} of {{ objects.paginator.num_pages }}
</span>
{% if objects.has_next %}
Next
{% endif %}
</span>
</div>
This is a simple way how I do it
In view :
path = ''
path += "%s" % "&".join(["%s=%s" % (key, value) for (key, value) in request.GET.items() if not key=='page' ])
Then in template:
href="?page={{ objects.next_page_number }}&{{path}}"
I had this problem while using django-bootstrap3. The (easy) solution without any template tags is using:
{% bootstrap_pagination page_obj extra=request.GET.urlencode %}
Took me a while to find this out... I finally did thanks to this post.
My solution is based on this one above with the slight improvement to remove &page= from appearing multiple times. See this comment
#register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
query = context['request'].GET.copy()
query.pop('page', None)
query.update(kwargs)
return query.urlencode()
This line query.pop('page', None) silently removes the page from the url
Another take on the url_encode solution, in this case as simplified by skoval00.
I had a few issues with that version. One, it didn't support Unicode encoding and two, it broke for filters with multiple of the same keys (like a MultipleSelect widget). Due to the .dict() conversion, all values but one are lost. My version supports unicode and multiple of the same key:
from django import template
from django.utils.html import mark_safe
register = template.Library()
#register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
query = context['request'].GET.copy()
for kwarg in kwargs:
try:
query.pop(kwarg)
except KeyError:
pass
query.update(kwargs)
return mark_safe(query.urlencode())
This creates a QueryDict copy, then removes all keys that match kwargs (since update for a QueryDict adds instead of replacing). Mark_safe was needed due to a double encoding issue.
You would use it like this (don't forget to load the tags):
<a class="next" href="?{% url_replace p=objects.next_page_number%}">Next</a>
where ?p=1 is our pagination syntax in the View.
#skoval00 's answer is the most elegant, however it adds duplicate &page= query parameters to the url.
Here is the fix:
from urllib.parse import urlencode
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def url_replace(context, next_page):
query = context['request'].GET.copy().urlencode()
if '&page=' in query:
url = query.rpartition('&page=')[0]
else:
url = query
return f'{url}&page={next_page}'
Here's a useful custom template tag for constructing query strings.
Next page
If the URL is http://example.com/django/page/?search=sometext, the generated HTML should be something like:
Next page
More examples:
<!-- Original URL -->
<!-- http://example.com/django/page/?page=1&item=foo&item=bar -->
<!-- Add or replace arguments -->
{% make_query_string page=2 item="foo2" size=10 %}
<!-- Result: page=2&item=foo2&size=10 -->
<!-- Append arguments -->
{% make_query_string item+="foo2" item+="bar2" %}
<!-- Result: page=1&item=foo&item=bar&item=foo2&item=bar2 -->
<!-- Remove a specific argument -->
{% make_query_string item-="foo" %}
<!-- Result: page=1&item=bar -->
<!-- Remove all arguments with a specific name -->
{% make_query_string item= %}
<!-- Result: page=1 -->
Finally, the source code (written by me):
# -*- coding: utf-8 -*-
from django import template
from django.utils.encoding import force_text # Django 1.5+ only
register = template.Library()
class QueryStringNode(template.Node):
def __init__(self, tag_name, parsed_args, var_name=None, silent=False):
self.tag_name = tag_name
self.parsed_args = parsed_args
self.var_name = var_name
self.silent = silent
def render(self, context):
# django.core.context_processors.request should be enabled in
# settings.TEMPLATE_CONTEXT_PROCESSORS.
# Or else, directly pass the HttpRequest object as 'request' in context.
query_dict = context['request'].GET.copy()
for op, key, value in self.parsed_args:
if op == '+':
query_dict.appendlist(key, value.resolve(context))
elif op == '-':
list_ = query_dict.getlist(key)
value_ = value.resolve(context)
try:
list_.remove(value_)
except ValueError:
# Value not found
if not isinstance(value_, basestring):
# Try to convert it to unicode, and try again
try:
list_.remove(force_text(value_))
except ValueError:
pass
elif op == 'd':
try:
del query_dict[key]
except KeyError:
pass
else:
query_dict[key] = value.resolve(context)
query_string = query_dict.urlencode()
if self.var_name:
context[self.var_name] = query_string
if self.silent:
return ''
return query_string
#register.tag
def make_query_string(parser, token):
# {% make_query_string page=1 size= item+="foo" item-="bar" as foo [silent] %}
args = token.split_contents()
tag_name = args[0]
as_form = False
if len(args) > 3 and args[-3] == "as":
# {% x_make_query_string ... as foo silent %} case.
if args[-1] != "silent":
raise template.TemplateSyntaxError(
"Only 'silent' flag is allowed after %s's name, not '%s'." %
(tag_name, args[-1]))
as_form = True
silent = True
args = args[:-1]
elif len(args) > 2 and args[-2] == "as":
# {% x_make_query_string ... as foo %} case.
as_form = True
silent = False
if as_form:
var_name = args[-1]
raw_pairs = args[1:-2]
else:
raw_pairs = args[1:]
parsed_args = []
for pair in raw_pairs:
try:
arg, raw_value = pair.split('=', 1)
except ValueError:
raise template.TemplateSyntaxError(
"%r tag's argument should be in format foo=bar" % tag_name)
operator = arg[-1]
if operator == '+':
# item+="foo": Append to current query arguments.
# e.g. item=1 -> item=1&item=foo
parsed_args.append(('+', arg[:-1], parser.compile_filter(raw_value)))
elif operator == '-':
# item-="bar": Remove from current query arguments.
# e.g. item=1&item=bar -> item=1
parsed_args.append(('-', arg[:-1], parser.compile_filter(raw_value)))
elif raw_value == '':
# item=: Completely remove from current query arguments.
# e.g. item=1&item=2 -> ''
parsed_args.append(('d', arg, None))
else:
# item=1: Replace current query arguments, e.g. item=2 -> item=1
parsed_args.append(('', arg, parser.compile_filter(raw_value)))
if as_form:
node = QueryStringNode(tag_name, parsed_args,
var_name=var_name, silent=silent)
else:
node = QueryStringNode(tag_name, parsed_args)
return node
Another slight modification to skoval00 and Reinstate Monica to fully get rid of duplication and avoid the ugly ?&page=1 part:
from urllib.parse import urlencode
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def url_replace(context, next_page):
if query.startswith('page') or not len(query):
new_url = f'page={next_page}'
elif '&page=' in query:
get_params = query.rpartition('&page=')[0] # equivalent to .split('page='), except more efficient
new_url = f'{get_params}&page={next_page}'
else:
new_url = f'{query}&page={next_page}'
return new_url
#Elrond Supports Monica
#register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
query = context['request'].GET.copy()
for key in kwargs:
query[key] = kwargs[key]
return query.urlencode()
Use in template
<a class="page-link" href="?{% url_replace p=1 q='bar'%}">
Every such link you put in your view has to be equipped with relevant parameters. There is no implicit magic that would convert:
http://127.0.0.1:8000/users/?page=2
into:
http://127.0.0.1:8000/users/?sort=first_name&page=2
So what you need is some Sorter object/class/function/snippet (whatever might fit here without overdoing it), that would act similarly to django.core.paginator.Paginator, but would handle sort GET parameter.
It could be as simple as this:
sort_order = request.GET.get('sort', 'default-criteria')
<paginate, sort>
return render_to_response('view.html', {
'paginated_contacts': paginated_contacts, # Paginator stuff
'sort_order': sort_order if sort_oder != 'default-criteria' else ''
})
Then, in your view:
{% if contacts.has_next %}
next
{% endif %}
I could be made more generic, but I hope you get the concept.
I would say generate the next and previous link from your controller, then pass it to the view and use it from there. I will give you an example (more like a pseudocode):
("next_link", "?param1="+param1+"&param2="+param2+"&page_nr="+(Integer.parseInt(page_nr)-1)
then in your view use it like this:
{% if contacts.has_next %}
next
{% endif %}
You will need to return the GET as stated above. You can pass the GET request part of the url by calling
render_dict['GET'] = request.GET.urlencode(True)
return render_to_response('search/search.html',
render_dict,
context_instance=RequestContext(request))
you can then use this in the template to build your URL e.g.
href="/search/client/{{ page.no }}/10/?{{ GET }}
With Django's Pagination - preserving the GET params is simple.
First copy the GET params to a variable (in view):
GET_params = request.GET.copy()
and send it to the template in via context dictionary:
return render_to_response(template,
{'request': request, 'contact': contact, 'GET_params':GET_params}, context_instance=RequestContext(request))
Second thing you need to do is use it, specify it in the url calls (href) in the template - an example (extending the basic pagination html to handle extra param condition):
{% if contacts.has_next %}
{% if GET_params %}
next
{% else %}
next
{% endif %}
{% endif %}
Source
your code should be like:
{% if contacts.has_next %}
next
{% endif %}
this works for me and i find it simpler
from django.http import HttpRequest
def get_query_params_href(req: HttpRequest):
query_strings = req.GET.dict()
string = '?'
for i in query_strings:
string += f'{i}={query_strings[i]}&'
return string[0:string.__len__()-1]
'path': request.get_full_path().rsplit('&page')[0],