Use variable as dictionary key in Django template - django

I'd like to use a variable as an key in a dictionary in a Django template. I can't for the life of me figure out how to do it. If I have a product with a name or ID field, and ratings dictionary with indices of the product IDs, I'd like to be able to say:
{% for product in product_list %}
<h1>{{ ratings.product.id }}</h1>
{% endfor %}
In python this would be accomplished with a simple
ratings[product.id]
But I can't make it work in the templates. I've tried using with... no dice. Ideas?

Create a template tag like this (in yourproject/templatetags):
#register.filter
def keyvalue(dict, key):
return dict[key]
Usage:
{{dictionary|keyvalue:key_variable}}

You need to prepare your data beforehand, in this case you should pass list of two-tuples to your template:
{% for product, rating in product_list %}
<h1>{{ product.name }}</h1><p>{{ rating }}</p>
{% endfor %}

Building on eviltnan's answer, his filter will raise an exception if key isn't a key of dict.
Filters should never raise exceptions, but should fail gracefully. This is a more robust/complete answer:
#register.filter
def keyvalue(dict, key):
try:
return dict[key]
except KeyError:
return ''
Basically, this would do the same as dict.get(key, '') in Python code, and could also be written that way if you don't want to include the try/except block, although it is more explicit.

There is a very dirty solution:
<div>We need d[{{ k }}]</div>
<div>So here it is:
{% for key, value in d.items %}
{% if k == key %}
{{ value }}
{% endif %}
{% endfor %}
</div>

Related

Boolean check inside a for loop in a Django template

In a Django template I have the following for loop
{% for document in documents %}
<li>{{ document.docfile.name }}</li>
{% endfor %}
Through this loop I am showing the user all the uploaded files of my app.
Now say that I want to show the user only the files he/she has uploaded.
I have the current user in the variable {{ request.user }}
and also I have the user who did the i-th upload in {{ document.who_upload }}
My question is how can I compare these two variables inside the loop to show only the uploads that have a who_upload field that of the current user?
For example I tried the syntax
{% if {{ request.user }} == {{ document.who_upload }} %}
{% endif %}
but it does not seem to work.
What is the proper syntax for this check?
Thank you !
This should get the job done:
{% if request.user.username == document.who_upload.username %}
{% endif %}
But you should consider performing this logic in your view. This is assuming you're not looping over the entire queryset anywhere else.
views.py
========
from django.shortcuts import render
from .models import Document
def documents(request):
queryset = Document.objects.filter(who_upload=request.user)
return render(request, 'document_list.html', {
'documents': queryset
})
A better option would be to compare the users' primary keys, instead of comparing user objects, which most definitely will be different.
{% if request.user.pk == document.who_upload.pk %}
<span>You uploaded this file</span>
{% endif %}

Django dynamic template variable inside if clause

Here's what I'm trying to achieve in "pseudo code":
{% for page in pages %}
{% if 'can_access_page_{{page.name}}' in perms %}
<li>
{{ page.name }}
</li>
{% endif %}
{% endfor %}
How to do this? Permission names I can customize — but still can't figure out this one.
Simplest way is to slightly abuse Django's existing add template filter (intended for numbers but works for strings), as in this answer:
https://stackoverflow.com/a/4524851/202168
You'll need a custom filter. Something like:
#register.filter
def check_page_perms(page, perms):
return 'can_access_page_%s' % page.name in perms
and use it:
{% if page|check_page_perms:perms %}

Problems passing a dictionary to a template in Django

I am having trouble with what seems to be a simple problem:
I want to pass a dictionary to a template, and then have the template render that dictionary on the page. However, when I run the page, the dictionary doesn't show up...
Here's my views page:
def display_meta(request):
values = request.META.items()
values.sort()
c = Context(values)
return render_to_response('meta_data.html', c)
And here's my template:
{% extends "base.html" %}
{% block content %}
<table>
{% for k, v in c %}
<tr><td> {{k}} </td><td> {{v}} </td></tr>
{% endfor %}
</table>
{% endblock %}
I am not sure what is going wrong. Any help would be greatly appreciated. Thanks!
You need to pass context as a dict. You would do so like
def display_meta(request):
values = request.META.items()
values.sort()
return render_to_response('meta_data.html', {'c': values})
Each key represents the variable that will be available, in this case c will be a dict with the items in values

"Last" tag not working

I have a queryset of "promotion" events which are rendered in a template. Each of these promotions also have 1 or more appointments. What I want to do is display the dates of the first and last appointments.
So far, using the "first" tag works. However, using the "last" tag causes:
TemplateSyntaxError Exception Value:
Caught an exception while rendering:
Negative indexing is not supported.
Here's the template script
{% for promotion in promotions%}
{% with promotion.appointment_set.all as appointments %}
{% with appointments|first as first_ap %}
{{ first_ap.date|date }}
{% endwith %}
{% with appointments|last as last_ap %}
{{ last_ap.date|date }}
{% endwith %}
{% endwith %}
{% endfor %}
What am I doing wrong here?
Converting the queryset into a list before giving it to the template also gets you where you want to go:
return render_to_response(template, {
appointments: list(Appointments.objects.all())
})
Since I'm using the whole list I do something like this (which might be open to improvement):
{% for ap in appointments %}
{% ifequal ap appointments|last %}
ap.date
{% endifequal %}
{% endfor %}
The object attributes still work. eg: ap.user.full_name
The last tag works by slicing a list to get the last item, using the negative index format: collection[-1]. But as the error message points out, negative indexing is not supported on querysets.
Probably the easiest way of solving this is to create a new method on your Promotion model to return the last appointment:
class Promotion(models.Model):
... fields, etc ...
def get_last_appointment(self):
try:
return self.appointment_set.all().order_by('-date')[0]
except IndexError:
pass
and call this from the template:
{{ promotion.get_last_appointment.date|date }}
The cause of your problem is what #Daniel pointed out: Querysets do not support negative indexing. His solution is worth exploring.
Another way of addressing this is to add a custom filter that will work with lists and querysets. Something like:
#register.filter
def custom_last(value):
last = None
try:
last = value[-1]
except AssertionError:
try:
last = value.reverse()[0]
except IndexError:
pass
return last
And in the template:
{% with appointments|custom_last as last_ap %}

Accessing initial value of django forms when iterating over the fields

I'm trying to do something pretty simple; I'd like to apply a "hidden" style to a form field inside a django template when I've passed in some initial value like this:
form = form_class(initial={'field':data})
Normally, it would be like this:
<li class="{{form.somefield.name}} {% if form.somefield.initial %} hidden{% endif %}>
...
</li>
But I'm iterating over the forms, so what I want do do is something that looks like this:
{% for field in form %}
<li class="{{field.name}} {% if field.initial %} hidden{% endif %}">
...
</li>
{% endfor %}
but this doesn't work, because field.initial only has the value defined as initial to the field in the form, not the data that's passed in at the form's creation. Is there a good solution for this besides just breaking out the iterating into individual forms?
Some (bad) solutions I've thought of:
overriding init to stuff values form self.initial into self.fields;
writing a template tags called {% hideifhasinitial %}
adding a method to the form that uses zip on self and self.initial (doesn't work, since self.initial only had one element and self had 4, it only iterated over 1 element, and the keys (field names) didn't match up).
how about this?
{% for field in form %}
{% if field.name in field.form.initial.keys %}
...
{% endif %}
{% endfor %}
Initial data can be accessed on the value attribute, initial data represents the value of the field:
{{field.value}}
Turns out there's a way easier way to do this.
{% if field.name in form.initial.keys %}
The solution with the initial keys has not worked for me, because the field contains as a value an empty string. I had to write my own custom tag:
from django import template
register = template.Library()
#register.simple_tag
def field_empty(field):
if not field.form.initial.get(field.name):
return ' hidden'
return ''
In your example, I would use the tag this way:
<li class="{{ field.name }} {% field_empty field %}">