Accessing a dict element who's key begins with an # - django

I have a dict passed to my template, some of the element names / keys begin with an # symbol, ie:
{ '#id': 123, '#name': 'Alex' }
Is there a good / clean way I can access these elements?

A custom template tag is the only way to access dictionary keys with special characters. The answer to this question provides a good example.
For the lazy:
from django import template
register = template.Library()
#register.filter
def dictKeyLookup(the_dict, key):
# Try to fetch from the dict, and if it's not found return an empty string.
return the_dict.get(key, '')
Which you would use like so:
{% dictKeyLookup your_dict "#blarg!#$^&*" %}
As homework, you could also convert this into a simple filter, which would give you a syntax like:
{{ your_dict|getkey:"#blarg!##$%" }}

Related

Getting request.path in django template tags

I want to create a custom filter for django templates. I've got some currency pairs (which are links) that I pass to my html page so I need to check if current url is equal to a currency pair from my list, then I gotta skip it.
For example, my url is:
https://website.com/usd/eur/
So if there's a pair USD/EUR, it won't be shown on my page.
To do this, I need to loop over all pairs and compare them to request.path value. So, how can I get it within my template tags ?
You can directly receive request into template tags like -
#register.simple_tag(name='new_tag')
def new_tag(request):
path = request.path
....
and then in your html use it like {% new_tag request %}.

Filter 2D list with template tag in Django

I'm trying to write a template tag that will filter a 2D list.
This is my template tag:
from django import template
register = template.Library()
#register.filter
def index_two(seq, position1, position2):
return seq[position1][position2]
This is my list that I'm passing to my template:
summary = [[50, 0, 0], [50, 100, 100]]
I'm trying to reference the first element of the first list in summary, like so:
{{summary|index_two:0 0}}
However, I received a template syntax error: index_two requires 3 arguments, 2 provided.
I've tried to adjust my template tag to the answer here, but I can't get it to work for me.
Any suggestions?
Thanks
A Django template filter takes at most two parameters (the one that is passed with the "pipe" (|) character, and an optional extra one, like is specified in the documentation [Django-doc]:
Custom filters are just Python functions that take one or two
arguments:
The value of the variable (input) – not necessarily a string.
The value of the argument – this can have a default value, or be left out altogether
But we can make the component more reusable, and thus obtain an single element each time, like:
from django import template
register = template.Library()
#register.filter
def index(seq, position):
return seq[position]
Then we can write it like:
{{ summary|index:0|index:0 }}
So now we can use index to obtain an element of a list, and by chaining, we can dive deeper into the list. We thus make the function more reusable.
This technique is somewhat related to currying in functional programming where functions always take exactly one parameter.
Alternative, you can use some sort of format that can be decoded, like for example a string containing comma-separated values:
from django import template
register = template.Library()
#register.filter
def index_two(seq, positions):
p1, p2 = map(int, positions.split(','))
return seq[p1][p2]
and then use it like:
{{ summary|index_two:'0,0' }}
But personally I find this less elegant, and will probably cause more trouble, since it requires a well defined format, and it is always possible that it fails for some corner cases.
This answer is inspired by Willem Van Onsem's comment:
Template tag:
from django import template
register = template.Library()
#register.filter
def index(seq, pos):
position1 = int(pos.split(" ")[0])
position2 = int(pos.split(" ")[1])
return seq[position1][position2]
Template:
{{summary|index:'0 0'}

Django url template with query parameters

I'm trying to pass query parameters via my view into a link, but it escapes me on how to actually achieve this in a good way.
My template is as following:
<a class="link-button" href="{% url 'videos:index' %}?tag={{ tag }}&page={{ next }}">Next</a>
This returns what I want:
http://127.0.0.1:8000/videos/?tag=1&page=2
While this works, it's quite fragile, does not handle None values and there must be a better way of doing this.
I tried to pass this via the urltemplate tag but it did not seem to be what I was looking for since it requires url config changes for path:
{% url 'videos:index' page=next tag=tag %}
Is there an actual way of doing this or a template tag I can use to get the parameters? I tried searching for this but it gave me a lot of old results and more path urls, like: /videos/page-1/tag-1/ which I'm not looking for.
I was hoping to do something like:
Next
There is no builtin support, but you can add one yourself. You can for example define the following template tag. We can for example construct files in boldface:
app/
templatetags/
__init__.py
urlparams.py
Where in urlparams.py, we define:
from django import template
from urllib.parse import urlencode
register = template.Library()
#register.simple_tag
def urlparams(*_, **kwargs):
safe_args = {k: v for k, v in kwargs.items() if v is not None}
if safe_args:
return '?{}'.format(urlencode(safe_args))
return ''
In the template, we can then load the template tag and then use it like with:
{% load urlparams %}
Next
Note that strictly speaking, the URL parameters can contain the same key multiple times. This is here not possible. So we can not generate all possible URL parameters, but this is usually quite rare, and in my opinion not a good idea in the first place.
you can use default template filter and update your example
<a class="link-button" href="{% url 'videos:index' %}?tag={{ tag|default:'' }}&page={{ next|defaul:'' }}">Next</a>
output for empty tag and page is:
http://127.0.0.1:8000/videos/?tag=&page=
but if you want to dont print None Tag in url you must your own template tag or filter. simply you can write this template filter
#register.filter
def print_query_param(value, key)
if value and key:
return "%s=%s&" % (key, value)
and you can use it as below
<a class="link-button" href="{% url 'videos:index' %}?{{ tag|print_query_param:'tag' }}{{ next|print_query_param:'page' }}">Next</a>
Template tag urlencode
Use the template tag urlencode:
example
Small note:
Note that there is no need to pass the param names themselves through urlencode, since in this case they are literals. If the param names were not literals, you would need to, like this:
example

Is it possible to use a freshly created context variable anywhere in a Django template?

For a long time now, I have been trying to figure out how to work with lists created on the fly in Django templates, meaning being able to:
create a list directly in a django template,
add new elements to that list,
concatenate 2 lists together.
Those lists should be able to handle django objects and not only simple strings or so. For instance, in my case, I wanted my lists to be able to store form fields (example to follow).
After many researches, I figured out that it was impossible to do that but with simple things, and that I had to create my own custom tags if I ever wanted to achieve my purpose. My custom tag is written below. Please notice that this post helped me to do so.
The issue I am facing with
The custom tag works, and I use it in a for loop. The list generated here is correctly evolving according to the loop, and I can call it like any variable while still being in the loop (because it was exported in Django context): {{ listName }}
But! Once I'm outside that loop, my list does not seem having being updated at all! Like if it was only existing inside the for loop... I thought at first that when something was defined into a Django template context, it was available anywhere inside the template, and not only inside the block where it was defined. Am I missing something? Is that the normal behaviour for Django? I have not been able to find the answer to that question.
Custom tag
#register.tag()
def setList(parser, token):
"""
Use : {% setList par1 par2 ... parN as listName %}
'par' can be a simple variable or a list
To set an empty list: {% setList '' as listName %}
"""
data = list(token.split_contents())
if len(data) >= 4 and data[-2] == "as":
listName = data[-1]
items = data[1:-2]
return SetListNode(items, listName)
else:
raise template.TemplateSyntaxError(
"Erreur ! L'utilisation de %r est la suivante : {%% setList par1 par2 ... parN as listName %%}" % data[0]
)
class SetListNode(template.Node):
def __init__(self, items, listName):
self.items = []
for item in items: self.items.append(template.Variable(item))
self.listName = listName
def render(self, context):
finalList = []
for item in self.items:
itemR = item.resolve(context)
if isinstance(itemR, list): finalList.extend(itemR)
elif itemR == '': pass
else: finalList.append(itemR)
context[self.listName] = list(finalList)
return "" # django doc : render() always returns a string
Use of my custom tag in a Django Template
{% setList '' as new_list %}
new_list value is: {{ new_list }} # shows me an empty list: OK!
# then I iter on a forms.RadioSelect field
{% for field in form.fields %}
{% if field.choice_label in some_other_list %}
{% setList new_list field as new_list %}
{% endif %}
{{ new_list }} # a new item is added to new_list when necessary: OK!
{% endfor %}
{{ new_list }} # just shows an empty list, the one from the begining: THE ISSUE!
So: it looks like that my initial list is just being updated locally in my for loop. What a disappointment! Any idea about how I could use my custom list outside the loop? Is it impossible?
Thank you so much for the time you will take to help me with that thing. First time I am posting something in here, so if one needs anything please tell me!
First of all, that's a great question.
Now to the business: Context object (a dictionary mapping variable names to variable values) is a stack. That is, you can push() it and pop() it.
With that knowledge let's look at the code once again:
# Enter scoped block.
# Push new_list as empty list onto the context stack.
{% setList '' as new_list %}
new_list value is: {{ new_list }}
# Enter for-loop scoped block.
{% for field in form.fields %}
{% if field.choice_label in some_other_list %}
# Push new new_list onto context.
{% setList new_list field as new_list %}
{% endif %}
# Print most current value named new_list (at the top)
{{ new_list }}
# Exit for-loop.
# Pop the loop variables pushed on to the context to avoid
# the context ending up in an inconsistent state when other
# tags (e.g., include and with) push data to context.
{% endfor %}
# new_list's value is again empty list,
# since all other values under that name were poped off the stack.
{{ new_list }}
# Pop any values left.
# Exit scoped block.
What's this all about?, from django.template.base source code:
How [Django template system] works:
The Lexer.tokenize() function converts a template string (i.e., a
string containing markup with custom template tags) to tokens, which
can be either plain text (TOKEN_TEXT), variables (TOKEN_VAR) or
block statements (TOKEN_BLOCK).
The Parser() class takes a list of tokens in its constructor, and
its parse() method returns a compiled template -- which is, under
the hood, a list of Node objects.
Each Node is responsible for creating some sort of output -- e.g.
simple text (TextNode), variable values in a given context
(VariableNode), results of basic logic (IfNode), results of
looping (ForNode), or anything else. The core Node types are
TextNode, VariableNode, IfNode and ForNode, but plugin modules
can define their own custom node types.
Each Node has a render() method, which takes a Context and
returns a string of the rendered node. For example, the render()
method of a VariableNode returns the variable's value as a string.
The render() method of a ForNode returns the rendered output of
whatever was inside the loop, recursively.
The Template class is a convenient wrapper that takes care of
template compilation and rendering.
So, in sum, what ForNode does is:
It takes some mark-up code (whatever is inside for tags), pushes some variables on the stack, compiles HTML with them, pops off introduced variables off the stack and returns said HTML.
*Additionally, you can have a look at ForNode's render implementation itself. It takes context as argument and returns mark_safe(''.join(nodelist)), which is a string.
Sadly, you cannot circumvent this mechanism. Unless you write your own completely.
Cheers.
There is a solution!
Well, after spending hard time understanding and printing everything I could to understand what was going on, I finally succeeded in doing so! Thanks a lot to #Siegmeyer who really helped me to see clear in what was really a Django Context object.
To begin with, you can have a look at Django Template Context source code over here.
As #Siegmeyer said, Context in Django works as a stack. You won't be able to use it as a classic dictionary, especially if you want to add variables to your context. Please read #Siegmeyer explanations, it was clear enough for me. Also, he/she told me the answer without giving me the one existing appropriate method dedicated to my need, but maybe that was on purpose to make me read harder the doc ;-) Which would have been a good thing after all.
Let's have a look at that method of the BaseContext (from django.template.context source code) I am writing about :
def set_upward(self, key, value):
"""
Set a variable in one of the higher contexts if it exists there,
otherwise in the current context.
"""
context = self.dicts[-1]
for d in reversed(self.dicts):
if key in d.keys():
context = d
break
context[key] = value
As you can see, set_upward method answers perfectly to my need (the custom list is in a higher context). Except that it seems it is only looking at the n-1 context, so if you define your custom variable too deep in for loops, you might not be able to access it at some higher level (>n-1, but I did not test that). Maybe a good thing to avoid that would be to define your needed variables in your context_processors.py file so it can be accessible anywhere in your template (not sure about that though).
Custom tag: the end
Finally, my custom tag allowing me to define lists on the fly is the one following:
#register.tag()
def setList(parser, token) [...] # no change, please see my question
class SetListNode(template.Node):
def __init__(self, items, listName):
self.items = []
for item in items: self.items.append(template.Variable(item))
self.listName = listName
def render(self, context):
finalList = []
for item in self.items:
itemR = item.resolve(context)
if isinstance(itemR, list): finalList.extend(itemR)
elif itemR == '': pass
else: finalList.append(itemR)
context.set_upward(self.listName, list(finalList))
return "" # django doc : render() always return a string
The usecase of my question works, the custom list is displaying what is required.
If anything is not clear, or if you think I might be missing something, please feel free to tell me! Anyway, thanks for reading!

How to interpolate already translated string in Django templates?

I have a generic delete view that includes a confirmation question as a translated string containing one placeholder. I would like to interpolate it like this:
<p class="text-error">
{% message % object %}
</p>
Variable message contains a string like: "Do you want to remove user %s?".
How can I use string interpolation in templates?
You can use the following dictionary with strings:
strings = { 'object': 'word' }
as follows:
{{ strings|stringformat:message }}
with the stringformat filter. Note that the leading % is dropped from the string (see the documentation for more details).
Finally I made a custom filter:
from django.template.base import Library
register = Library()
#register.filter
def interpolate(value, arg):
"""
Interpolates value with argument
"""
try:
return value % arg
except:
return ''