How to get 'switch-case' statement functionality in Django templates? - django

I found a link to have a 'switch' tag in Django templates, but I was wondering if this can be somehow achieved without it. Using only the stuff which comes with Django? Basically is there other way then using multiple 'if' or 'ifequal' statements?
Thanks in advance for any tips/suggestions.

As of Django 1.4, there is {% elif %}:
{% if a %}
thing
{% elif b %}
other thing
{% elif c %}
another thing
{% endif %}

To the previous responders: Without understanding the use case, you've made assumptions and criticized the questioner. #Ber says "all over the place" which is certainly not implied by the questioner. Not fair.
I have a case where I would like to do a {% switch %} statement in exactly one place in my Django template. Not only is it not convenient to move the equivalent of the switch statement into Python code, but that would actually make both the view and the template harder to read and take simple conditional logic that belongs in one place and split it into two places.
In many cases where I could imagine a {% switch %} (or an {% if %}) being useful, not using one requires putting HTML in a view. That's a far worse sin and is why {% if %} exists in the first place. {% switch %} is no different.
Fortunately, Django is extensible and multiple people have implemented switch. Check out:
Switch template tag
from django import template
from django.template import Library, Node, VariableDoesNotExist
register = Library()
#register.tag(name="switch")
def do_switch(parser, token):
"""
The ``{% switch %}`` tag compares a variable against one or more values in
``{% case %}`` tags, and outputs the contents of the matching block. An
optional ``{% else %}`` tag sets off the default output if no matches
could be found::
{% switch result_count %}
{% case 0 %}
There are no search results.
{% case 1 %}
There is one search result.
{% else %}
Jackpot! Your search found {{ result_count }} results.
{% endswitch %}
Each ``{% case %}`` tag can take multiple values to compare the variable
against::
{% switch username %}
{% case "Jim" "Bob" "Joe" %}
Me old mate {{ username }}! How ya doin?
{% else %}
Hello {{ username }}
{% endswitch %}
"""
bits = token.contents.split()
tag_name = bits[0]
if len(bits) != 2:
raise template.TemplateSyntaxError("'%s' tag requires one argument" % tag_name)
variable = parser.compile_filter(bits[1])
class BlockTagList(object):
# This is a bit of a hack, as it embeds knowledge of the behaviour
# of Parser.parse() relating to the "parse_until" argument.
def __init__(self, *names):
self.names = set(names)
def __contains__(self, token_contents):
name = token_contents.split()[0]
return name in self.names
# Skip over everything before the first {% case %} tag
parser.parse(BlockTagList('case', 'endswitch'))
cases = []
token = parser.next_token()
got_case = False
got_else = False
while token.contents != 'endswitch':
nodelist = parser.parse(BlockTagList('case', 'else', 'endswitch'))
if got_else:
raise template.TemplateSyntaxError("'else' must be last tag in '%s'." % tag_name)
contents = token.contents.split()
token_name, token_args = contents[0], contents[1:]
if token_name == 'case':
tests = map(parser.compile_filter, token_args)
case = (tests, nodelist)
got_case = True
else:
# The {% else %} tag
case = (None, nodelist)
got_else = True
cases.append(case)
token = parser.next_token()
if not got_case:
raise template.TemplateSyntaxError("'%s' must have at least one 'case'." % tag_name)
return SwitchNode(variable, cases)
class SwitchNode(Node):
def __init__(self, variable, cases):
self.variable = variable
self.cases = cases
def __repr__(self):
return "<Switch node>"
def __iter__(self):
for tests, nodelist in self.cases:
for node in nodelist:
yield node
def get_nodes_by_type(self, nodetype):
nodes = []
if isinstance(self, nodetype):
nodes.append(self)
for tests, nodelist in self.cases:
nodes.extend(nodelist.get_nodes_by_type(nodetype))
return nodes
def render(self, context):
try:
value_missing = False
value = self.variable.resolve(context, True)
except VariableDoesNotExist:
no_value = True
value_missing = None
for tests, nodelist in self.cases:
if tests is None:
return nodelist.render(context)
elif not value_missing:
for test in tests:
test_value = test.resolve(context, True)
if value == test_value:
return nodelist.render(context)
else:
return ""

Unfortunately, this is not possible with the default Django template engine. You'll have to write something ugly like this to emulate a switch.
{% if a %}
{{ a }}
{% else %}
{% if b %}
{{ b }}
{% else %}
{% if c %}
{{ c }}
{% else %}
{{ default }}
{% endif %}
{% endif %}
{% endif %}
or if only one if condition can be true and you don't need a default.
{% if a %}
{{ a }}
{% endif %}
{% if b %}
{{ b }}
{% endif %}
{% if c %}
{{ c }}
{% endif %}
Usually, when the template engine is not powerful enough to accomplish what you want this is a sign that the code should be moved into Django view instead of in the template. For example:
# Django view
if a:
val = a
elif b:
val = b
elif c:
val = c
else:
val = default
# Template
{{ val }}

In a very general view, the need for a switch statement is a sign that there is a need to create new classes and objects that capture the different "cases".
Then, instead of "swtich"ing all over the place, you only need to call an object method or reference an object attribute and your done.

Related

How can I judge the object if is exists in the templates?

In the template:
<h4>
{% if data.wine_one %}
{{ data.wine_one.title }}
{% elif data.news_one %}
{{ data.news_one.title }}
{% endif %}
</h4>
I promise the data.wine_one is exists, because in the views.py I have print out it.
But in the templates it do not shows up the data.wine_one.title, and I use the data.wine_one != None can not judge it too.
EDIT
In the views.py:
def getData():
banner = models.Banner.objects.filter(fk_info=1)
info = models.Info.objects.all().first()
aboutus = models.AboutUs.objects.all().first()
winery = models.Winery.objects.all()[:3]
winery_consult = models.WineryConsult.objects.all()[:4]
data = {
'banner': banner,
'info': info,
'aboutus': aboutus,
'winery': winery,
'winery_consult': winery_consult,
}
return data
def productdetails(request, nid):
data = getData()
wine_one = models.Winery.objects.filter(id=nid).first()
data['wine_one'] = wine_one
print (data['wine_one'].title) # there ouput the "gaoliangjiu"
return render(request, 'article_list_content.html', data)
You've misunderstood how the template context works.
There's no element called data in the template; that's just the local variable you're using in the view to build up the context. In the template, you just reference the keys of that object directly. So it should be:
{% if wine_one %}
{{ wine_one.title }}
{% elif news_one %}
{{ news_one.title }}
{% endif %}

Checking values of context variables in Django custom template tag block

I'm trying to write a custom template tag in Django that will do the following:
get all context variables between the start and end tags
if all variables have a valid, non-empty value (i.e., not None or ''), return the entire block
otherwise (if any variable is empty), return an empty string (so nothing gets rendered)
I realize that I could use {% if var1 and var2 and var3 ... and varN %}, but I'd rather have a custom tag that does it so I don't have to double-check the if statement every single time I change something.
This is my current code:
in template_helpers.py:
#register.tag(name="iff")
def iff(parser,token):
nodelist = parser.parse(('endiff',))
parser.delete_first_token()
return IffNode(nodelist)
def do_all_exist(context):
sendback = True
for var in context:
if (var is None) or (var == ''):
sendback = False
if not sendback:
break
else:
continue
return sendback
class IffNode(template.Node):
def __init__(self,nodelist):
self.nodelist = nodelist
def render(self,context):
if do_all_exist(context):
return context
else:
return ''
in template.html:
{% load template_helpers %}
...
{% for term in terms %}
<div class="entry">
<p class="definition">
<big><b>{{ term.word }}</b></big>
<br /><b>{{ term.part_of_speech }}:</b> {{ term.definition }}
</p>
{% iff %}
<p class="etymology">from: {{ term.etymology }}</p>
{% endiff %}
</div>
{% endfor %}
...
Right now, the iff block is empty whether or not there's a valid value in term.etymology. It shouldn't be checking the value of every entry's etymology, because iff is inside for. But I am lost on how to check the context variables.
You are over-complicate your process. You should do it in your views method:
# this is the context you currently have
context = {foo: bar, baz: test}
# check if all context have values
do_all_exist = all(context.values())
# add the result to the context
context['do_all_exist'] = do_all_exist
# return the context with your HttpResponse
Then in your template:
{% if do_all_exist %}
<p class="etymology">from: {{ term.etymology }}</p>
{% endif %}
You are making things more difficult than they are. I think, this is too much for a template tag.
why dont you just do this?
{% if term.etimology %}
<p class="etymology">from: {{ term.etymology }}</p>
{% endif %}

How do you iterating context variables with forms in Django?

I have a list of questions and a form that has a corresponding number of answer fields.
Example:
questions = ['What is your favorite color?', 'What is your first car?']
How do I display in template, alternating question and answer pair?
Desired results:
What is your favorite color?
Answer: [answer_field1]
What is your first car?
Answer: [answer_field2]
Currently, my template has this code but it is not displaying correctly.
{% load i18n %}
{% block head %}
{{ wizard.form.media }}
{% endblock %}
{% block content %}
Please answer these questions:
<p>
<p>
<form action="" method="post">{% csrf_token %}
{% for question in questions %}
{{ question }}
{{ form.as_p }}
{% endfor %}
I don't have control over looping over 'form' because Django does that automatically. I'm not sure how to zip 'form' and 'questions' together to for loop them together.
you might need to write a custom templatetag to achieve such task. but before you would do that, you might need to consider these questions
Does the index of a question in the questionsList correspond with the index of its answer field in the form?
Does the length of the questionsList equals the number of fields present in the form?
Try this
templatetags/mytag.py
from django import template
register = template.Library()
def do_question_n_ans(parser, token):
"""
Usage:
{% do_question_n_ans_for question in questionsList and ans_field in form.fields %}
"""
bits = token.contents.split()
if len(bits) != 8:
raise template.TemplateSyntaxError("'%s' takes exactly seven arguments" % bits[0])
if len(bits[3]) != len(bits[7]):
raise template.TemplateSyntaxError("Question list and anwser fields must b of the same length")
if bits[2] != 'in':
raise template.TemplateSyntaxError("Second argument to '%s' must be 'in'" % bits[0])
if bits[4] != 'and':
raise template.TemplateSyntaxError("Third argument to '%s' must be 'and'" % bits[0])
if bits[7] != 'in':
raise template.TemplateSyntaxError("Sixth argument to '%s' must be 'in'" % bits[0])
return QuestionAnswerFormatNode(bits[1], bits[3], bits[5], bits[7]
class QuestionAnswerFormatNode(template.Node):
def __init__(self, question, questionsList, ans_field, fields):
self.questionsList = questionsList
self.fields = fields
self.question = question
self.ans_field = ans_field
def render(self, context):
try:
questionsList = template.resolve_variable(self.questionsList, context)
except template.VariableDoesNotExist:
raise template.VariableDoesNotExist
question_index = questionsList.index(question)
ans_field = fields[question_index]
context[self.ans_field] = ans_field
return ''
register.tag('do_question_n_ans_for', do_question_n_ans)
you could then use the tag within your html code
{% load mytag %}
{% for question in questionList %}
{% do_question_n_ans_for question in questionsList and ans_field in form.fields %}
<p>Question: {{ question }}</p>
<p>Answer: {{ ans_field }}</p>
{% endfor %}
Note:
I presumed you already have the questions and the answer fields arranged in this format in their respective list
['question1', 'question2', 'question3'] and ['answerFieldToQuestion1', 'answerFieldToQuestion2', 'answerFieldToQuestion3'].
you should be able to check out the order of your form fields by printing yourForm.fields.
Please let me know if there are any errors as i didn't test the above code, but i guarantee you it would work just fine

Defining "global variable" in Django templates

I'm doing something like:
{% extends 'base.html' %}
{% url myapp.views.dashboard object as object_url %}
{% block sidebar %}
... {{ object_url }} ...
{% endblock %}
{% block content %}
... {{ object_url }} ...
{% endblock %}
Django documentation says url templatetag can define a variable in context, but I don't get any value for object_url in the following blocks.
If I put the url templatetag at the beginning of each block, it works, but I don't want to "repeat myself".
Anyone knows a better solution?
If the URL is view specific, you could pass the URL from your view. If the URL needs to be truly global in your templates, you could put it in a context processor:
def object_url(request):
return {'object_url': reverse('myapp.views.dashboard')}
You could write a custom template tag:
#register.simple_tag(takes_context=True)
def set_global_context(context, key, value):
"""
Sets a value to the global template context, so it can
be accessible across blocks.
Note that the block where the global context variable is set must appear
before the other blocks using the variable IN THE BASE TEMPLATE. The order
of the blocks in the extending template is not important.
Usage::
{% extends 'base.html' %}
{% block first %}
{% set_global_context 'foo' 'bar' %}
{% endblock %}
{% block second %}
{{ foo }}
{% endblock %}
"""
context.dicts[0][key] = value
return ''
Looks like this was answered before, but there is an alternative. It's one thing to use a context processor to keep track of something defined from outside the template, but sometimes you want to count the number of times two loops go through, or something like that. There is another way:
class GlobalVariable(object):
def __init__(self, varname, varval):
self.varname = varname
self.varval = varval
def name(self):
return self.varname
def value(self):
return self.varval
def set(self, newval):
self.varval = newval
class GlobalVariableSetNode(template.Node):
def __init__(self, varname, varval):
self.varname = varname
self.varval = varval
def render(self, context):
gv = context.get(self.varname, None)
if gv:
gv.set(self.varval)
else:
gv = context[self.varname] = GlobalVariable(
self.varname, self.varval)
return ''
def setglobal(parser, token):
try:
tag_name, varname, varval = token.contents.split(None, 2)
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires 2 arguments" % token.contents.split()[0])
return GlobalVariableSetNode(varname, varval)
register.tag('setglobal', setglobal)
class GlobalVariableGetNode(template.Node):
def __init__(self, varname):
self.varname = varname
def render(self, context):
try:
return context[self.varname].value()
except AttributeError:
return ''
def getglobal(parser, token):
try:
tag_name, varname = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires arguments" % token.contents.split()[0])
return GlobalVariableGetNode(varname)
register.tag('getglobal', getglobal)
class GlobalVariableIncrementNode(template.Node):
def __init__(self, varname):
self.varname = varname
def render(self, context):
gv = context.get(self.varname, None)
if gv is None:
return ''
gv.set(int(gv.value()) + 1)
return ''
def incrementglobal(parser, token):
try:
tag_name, varname = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires arguments" % token.contents.split()[0])
return GlobalVariableIncrementNode(varname)
register.tag('incrementglobal', incrementglobal)
This allows you to use it in a template like this:
{% setglobal ii 0 %}
...
{% for ... %}
{% incrementglobal ii %}
current={% getglobal ii %}
{% endfor %}
...
{% for ... %}
{% incrementglobal ii %}
current={% getglobal ii %}
{% endfor %}
...
total of 2 loops={% getglobal ii %}
...
{% setglobal ii 0 %}
...
do something else now that {% getglobal ii %} is back to 0
Well, this is kind of abusive of template inheritance, but you could use {{block.super}} to put object_url into your blocks.
In other words, in your mid-level template do:
{% block sidebar %}{{ object_url }}{% endblock %}
{% block content %}{{ object_url }}{% endblock %}
And then in your block templates use:
{% block sidebar %}
... {{ block.super }}...
{% endblock %}
It's not a great idea because it prevents you from putting anything besides {{ object_url }} into your block... but it works. Just don't tell anyone you got it from me!
In every inherited template any code outside blocks redefinitions is not executed. So in your example you have to call {% url %} tag inside each block or use context processor for setting "global" variable.

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],