Link to Current Page in Django, with Additional GET Params? - django

How do I place an HTML link what refers to the current page, and which adds additional GET parameters (or overwriting existing ones, if they already exist)?
Right now I have something like:
My Link
Currently, request is passed to the page. If request.path is https://stackoverflow.com/, then the resulting link becomes https://stackoverflow.com/?Key=Value
The problem?
But of course, if the current URL is https://stackoverflow.com/?PrevKey=PrevValue then it becomes:
https://stackoverflow.com/?PrevKey=PrevValue?Key=Value
Notice the incorrect second question mark -- it should in fact be:
https://stackoverflow.com/?PrevKey=PrevValue&Key=Value
Furthermore, if there is already a key with the same name, then instead of overwriting it, my current solution ignores it -- which is wrong.
How do I solve these two problems?

You'll need a custom tag. There are a couple on djangosnippets - this one looks pretty comprehensive.

For anyone seeing this in future: https://bitbucket.org/monwara/django-url-tools

You can use: {{ request.get_full_path }} to get the current path with additional parameters.

I'm just a beginner with Django, so you'll have to take my response with a grain of salt.
Firstly, request.path should return the path to the page without the domain, according to the documentation. So if the request is for http://example.com/, request.path should be just "/". Correct me if I'm wrong.
But that's not relevant. More importantly, request.path won't have any query parameters, so if the page requested is http://example.com/?PrevKey=PrevValue, then request.path will still be "/". If you want to get the query parameters, you have to make use of the dictionary-like access of the GET and POST properties (in this case GET) of the request object. Or better yet, access them through the QueryDict methods.
What I would do here, and this is by no means the best method nor code, is to prepare a custom template filter in which you pass the current request object and the key-value pair to test against.
This is how your template code would look. Note that you can still hard code the key value pair, although here it is formatted as a string with "key colon value". The filter function can handle (if you need to) more than just one set of key-value pairs.
My Link
The filter function:
from urllib import urlencode
def addQueryParameter(request, parameter):
# parse the key-value pair(s) in parameter (which looks like a JSON string)
# then add to dictionary d
# note i am not taking into account white-space.
for param in string.split(','):
key, value = param.split(':', 1)
d[key] = value
# check if any keys in d are already in the request query parameters
# if so, delete from d. If I misunderstood your question, then refactor
# to delete from request.GET instead:
for key in d.keys():
if key in request.GET.keys():
del d[key]
# add the keys in request.GET into d:
d.extend(request.GET.items())
# convert dictionary of key value pairs into a query string for urls,
# prepend a ?
queryString = "?%s" % urlencode(d)
return "%s%s" % (request.path, queryString if queryString else "")
I should point out that if the request is for the page http://example.com/login/?prev=news and your template looks like
My Link
Then the output would be (hopefully, if it works):
<a href="/login/?prev=news&goto=dashboard&feature=products">
My Link</a>
<!-- the order of the above parameters may differ -->
Note that there is no domain (i.e. the http://example.com/ part) in this link. That is because the filter doesn't add it. But you can modify it to do so.
I'll leave it to you to register this template filter. More here. Hope this helps.

A JavaScript solution is much suitable in this case.
Example :
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a page_number="1">First Page</a>
<a page_number="{{ page_obj.previous_page_number }}">Last Page</a>
{% endif %}
{% if page_obj.has_previous or page_obj.has_next %}
<span class="current">{{ page_obj.number }}/{{ page_obj.paginator.num_pages }}</span>
{% endif %}
{% if page_obj.has_next %}
<a page_number="{{ page_obj.next_page_number }}">Next Page</a>
<a page_number="{{ page_obj.paginator.num_pages }}">End Page</a>
{% endif %}
</span>
<script type="text/javascript">
document.querySelectorAll('.pagination a').forEach(link => {
let page_number = link.getAttribute('page_number');
const gotopageUrl = new URL(location.href);
gotopageUrl.searchParams.set("page", page_number);
link.href = gotopageUrl.href;
});
</script>
</div>

Related

Django template variable containing template tag, ex {{ {% some_tag %} }}

I have a template that receives a list context variable, tags_list. I need to iterate over this list 'inserting' the tags in the template something like this:
{% for tag in tags_list %}
{{ tag.tag }}
{% endfor %}
When this renders it returns the text value of tag.tag, "{% tagxxx %}", not the rendered tag.
How can I cause the template render to render the value of a context variable? Alternately, is there a filter, a sort of inverse verbatim, that will cause the value of a context variable to be rendered?
Updated background
tags_list is created by a fairly sophisticated process involving exec of some user provided text from a table/model field. The relevant portion of the real template looks like this:
{% for graph_row in graph_rows %}
<div class="row">
{% for graph in graph_row %}
<div class="col-md-{{ graph.width }}">
{{ graph.graph }}
</div>
{% endfor %}
</div>
{% endfor %}
The graph values look like this: {'graph':'{% piechart data1 %}', 'width':3}
Note that the order of entries in the context variable graph_rows is significant as is order of graph(s) in the row as that determines the placement of graphs on the page. Preserving this order is essential for the scheme to work correctly.
Currently, the view function simply does an {% include ... %} to get the template segment above to render in the correct order. This approach is simple and clean.
I could, as has been suggested, perform a template render within the view function but that complicates the design a bit and I'd hoped to avoid doing that if there is an easy way to trigger a render of {{ graph.graph }}. Note, as well, by moving the render into the view I loose the ability to easily take the template from arbitrary places, in particular table fields.
One of the great things about Django is the library of solution and code snippets. Sadly, they aren't a well organized and easy to find as one might wish. Nevertheless, a bit of google found a number of solutions of the general form
{% render tag.tag %}
Here are links to several:
render_as_template template tag
Allow template tags in a Flatpage's content
render_as_template.py
I'll use the general approach cleaned up a bit for error checking.
As an aside, the technique strikes me as generally useful and might be appropriate for inclusion in the standard tags.
Update 3/28/2014
After looking at the above and several others this is what I used from render_as_template template tag. There is a useful comment here.
from django import template
from django.template import Template, Variable, TemplateSyntaxError
register = template.Library()
class RenderAsTemplateNode(template.Node):
def __init__(self, item_to_be_rendered):
self.item_to_be_rendered = Variable(item_to_be_rendered)
def render(self, context):
try:
actual_item = self.item_to_be_rendered.resolve(context)
return Template(actual_item).render(context)
except template.VariableDoesNotExist:
return ''
def render_as_template(parser, token):
bits = token.split_contents()
if len(bits) !=2:
raise TemplateSyntaxError("'%s' takes only one argument"
" (a variable representing a template to render)" % bits[0])
return RenderAsTemplateNode(bits[1])
render_as_template = register.tag(render_as_template)
This gets part of the way to a solution. Unfortunately custom template tags, in my case
{% pie_chart %} are not available to render within the class RenderAsTemplateNode.
I've not tested this but it appears that this stack overflow question, Django - replacing built-in templatetag by custom tag for a whole site without {% load .. %}, points the way.
I believe I can provide a way for you to get the results you want, but there might be a better way for you to achieve the desired functionality if you can provide some context.
Anyway, you might do something like this in your view.py:
tags_list = [
Template('{% load my_tags %}{% ' + t.tag + ' %}').render(Context())
for t in tags_list
]

Django : model instance url is appending to the current url

i have some link in a template to which i want to point to particular url.
template is accessed at url : loacalhost:8000/account/profile
{% for poll in voted_poll_list %}
<h4>{{ poll.title }} </h4>
{% endfor %}
in models.py i have created the url for the poll objects to be used in a template.
def link_url(self):
return "polls/"+ "allcat/" +str(self.id)
the problem is when a link in template is clicked it is point to the loacalhost:8000/account/profile/polls/allcat/1 instead of loacalhost:8000/polls/allcat/1 which matches to url pattern
url(r'^polls/(\w+)/(?P<pid>[-\d]+)/', 'pollsite.views.poll_detail', name='detail_poll'),
the problem is link url of object is appended to current url. how can i avoid this ?
#garnertb 's solution works, but you should probably look into using the reverse function rather than hardcoding your url.
Something like:
return reverse('detail_poll', self.id)
This will not only take care of the leading slash, but also avoid trouble if you ever start changing your url configuration.
Try leading the url with a forward slash:
def link_url(self):
return "/polls/allcat/" +str(self.id)

django NoReverseMatch error for related object when in a loop

My model: a "Workshop" has a related item that stores time and place in a "Session" object:
item=models.OneToOneField(Session)
a Session stores the location as a foreign key to the locations:
location = models.ForeignKey(conference_models.Location,
verbose_name=_("location"), blank=True, null=True)
I'm showing a Workshop in a template and I want to link to its location. If the template is a single Workshop view, this all works, but if I pass a list of workshops and wrap this all in:
{% for w in workshops %}
then bad things happen...
I have this in urls.py to define the URL for a location view:
url(r'^locations/(?P<location_pk>\d+)/$',
views.view_location, name='view-location'),
and in the workshop template I have:
<a href="{% url 'view-location' location_pk=w.item.location.pk %}">
{{w.item.location.pk}} {{w.item.location}}</a>
- {{w.item.start}} to {{w.item.end}}
And I get Reverse for 'view-location' with arguments '()' and keyword arguments '{'location_pk': ''}' not found.
where location_pk is the parameter to my location view. Everything seems to be correct. For example, if I do:
<a href="{% url 'view-location' location_pk=123546 %}">
{{w.item.location.pk}} {{w.item.location}}</a>
- {{w.item.start}} to {{w.item.end}}
I get the expected URL with 123546 in it. If I do:
<a href="{% url 'view-location' location_pk=w.item.pk %}">
{{w.item.location.pk}} {{w.item.location}}</a>
- {{w.item.start}} to {{w.item.end}}
then I get the item primary key in the URL (which isn't what I want, but proves the point that I'm not going mad expecting this to work...).
In all cases the {{w.item.location.pk}} tag expands to the correct value.
I've tried wrapping it in a {% with %} tag so there's no dotting going on. No joy.
This is Django 1.4.5, part of a complex project that probably won't handle an update to 1.5. If this requires 1.5 for a bigfix I'll have to rethink...
Note this only seems to happen in a {% for %} loop...
Since location field has null=True, blank=True, There might be some entries which are null, hence w.item.location.pk is evaluating to ''.
You can check {% if w.item.location %} and then load the URL for location.

Showing 'cancel' on login page to return user to where they were (using django.contrib.auth)

We are using the #login_required decorator so that users see a login page if they try to access a url for which they need to be authenticated.
We want to show a 'cancel' button on the login page, which should return the user to whichever page they were on when they tried to access the url (by clicking a link etc - we don't need to deal with them manually entering the url).
At the moment our login.html looks for a request parameter 'login_cancel_url' and if present uses that (otherwise the home page).
However, this means we have to manually pass this parameter (set to the url of the current page) whenever we show a link or button that leads to an 'authentication required' url.
Is there a more elegant way to do this?
Thanks, Martin
Well you can try get the referrer header from the request but as far as I am aware, it's browser dependent and is not very reliable so the way you are doing it is probably best. You could try make life easier by creating template tags to avoid having to rewrite the return URL manually.
You are easily able to get the current URL from django's request object on any page, so instead of setting it manually on the link, you could write a snippet of html:
link_to_login.html
<!-- You should probably get /login/ using the {% url ... %} template tag -->
<a href="/login/?login_cancel_url={{ request.path|urlencode }}">
Login Page</a>
and use the {% include "link_to_login.html"%} template tag.
Alternatively, If the text needs to be different depending on the link you can instead create an inclusion template tag:
templatetags/extra_auth_tags.py
#register.inclusion_tag('templates/extra_auth_tags/login_link.html')
def login_link(context, text=None):
return {
'text':text
}
templates/extra_auth_tags/login_link.html
<!-- You should probably get /login/ using the {% url ... %} template tag -->
<a href="/login/?login_cancel_url={{ request.path|urlencode }}">
{% if text %}
{{ text }}
{% else %}
Some Default Text
{% endif %}
</a>
and then call it in your templates as {% login_link text="Check you messages" %}. Be aware that keyword arguments for inclusion tags are only supported in the django dev version so you might need to write the template tag by hand.

Combining constant and variable value for template default value

I'm setting up a template in which I'd like the default value to be a combination of a constant string and a variable id value. The desired HTML output would be something like:
<span id="id1234" class="foo">
Click here to view image.
</span>
In the template code for the span, I would like something like:
<span id="{{ spanid|default:'id'object.id }}" class="foo">
Similarly, the a tag would use:
<a href="/images/{{ image_file|default:'img'object.id'.jpg'"> here to view...
This doesn't work, is there a way to do this within the syntax of django templates and the default filter?
You can't. However, default is merely a shortcut. In this scenario, the shortcut doesn't work, but the longer form will allow you do what you need:
{% if image_file %}{{ image_file }}{% else %}img{{ object.id }}.jpg{% endif %}
#Chris Pratt's answer is a nice workaround, but it's not accurate that you can't achieve this with the default filter.
In my project, I just used the "add" filter to concatenate a string onto a variable value as follows:
{{ instance.image.url|default:STATIC_URL|add:"img/icons/my-image.svg" }}
UPDATE: I apologize. Upon more thorough testing, I discovered that the solution I presented here doesn't actually work. I thought that the "add" filter would apply to the default value, but it ends up being applied to whatever value the first part of the expression resolves to.
So, if there is a value at {{ instance.image.url }} then "img/icons/my-image.svg" gets concatenated that, which breaks it.