"Last" tag not working - django

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

Related

Accessing nested list by variable in Django templates [duplicate]

I have some loop on the page and need list item depending from loop number.
When I call:
{{ mylist.1 }}
{{ mylist.2 }}
{{ mylist.3 }}
all works fine but what I really need is something like:
{% for x in somenumber|MyCustomRangeTag %}
{{ mylist.x }}
{% endfor %}
MyCustomRangeTag gives me Python range() it works and I already have x as number. So x is 1, 2, 3 etc. depending from loop number.
Is this possible and how?
This is not possible directly because Django thinks that "x" is the key to lookup in mylist - instead of the value of x. So, when x = 5, Django tries to look up mylist["x"] instead of mylist[5].
Use the following filter as workaround:
#register.filter
def lookup(d, key):
return d[key]
and use it like
{{ mylist|lookup:x }}
The slice tag in Django templates may use python's slicing code, but the syntax is unmistakably different. For instance, if you wanted to get an element of a sequence with a variable, in python you'd write something similar to the following:
>>>mylist = ["0th Element", "1th Element"]
>>>zero, one = 0, 1
>>>mylist[zero]
"0th Element"
>>>mylist[one]
"1th Element"
Using this syntax with the Django slice template tag will return a sliced list in every case, of dubious utility for getting an item of known index:
{% with "0" as zero %}
{% with "1" as one %}
{% with "2" as two %}
{{mylist|slice:zero}} {{mylist|slice:one}} {{mylist|slice:two}}
{% endwith %}
{% endwith %}
{% endwith %}
Renders to the html:
[] ["0th Element"] ["0th Element", "1th Element"]
Note the differences: you are getting the result of mylist[:x] instead of mylist[x].
Django provides enough tools to work around this. The first trick is to use explicit slices like 0:1 for your indices, and then |join:"" the resultant list into a single element. Like so:
{% with "0:1" as zero %}
{{mylist|slice:zero|join:""}}
{% endwith %}
Yields:
0th Element
This comes in particularly handy if you need to access a parent loop's index when dealing with an iterable inside a child loop:
{% for parent in parent_loop %}
{% cycle "0:1" "1:2" "2:3" as parent_loop_index silent %}
{% for child in child_loop %}
{{child|slice:parent_loop_index|join:""}}
{% endfor %}
{% endfor %}
Completed with nothing but stock parts, although I don't think Django has implemented achievements yet.
I notice that #e-satis mentioned it, but I think the built-in slice template tag deserves some love.
{{ item | slice:"2" }} #gets the third element of the list
Are you sure you can't just do:
{% for item in mylist %}
{{ item }}
{% endfor %}
With the slice filter, you can even do some customisation.
Following worked for me
{% for 1,2,3 in mylist %}
# do stuff
Just don't use brackets around 1,2,3

Get list item dynamically in django templates

I have some loop on the page and need list item depending from loop number.
When I call:
{{ mylist.1 }}
{{ mylist.2 }}
{{ mylist.3 }}
all works fine but what I really need is something like:
{% for x in somenumber|MyCustomRangeTag %}
{{ mylist.x }}
{% endfor %}
MyCustomRangeTag gives me Python range() it works and I already have x as number. So x is 1, 2, 3 etc. depending from loop number.
Is this possible and how?
This is not possible directly because Django thinks that "x" is the key to lookup in mylist - instead of the value of x. So, when x = 5, Django tries to look up mylist["x"] instead of mylist[5].
Use the following filter as workaround:
#register.filter
def lookup(d, key):
return d[key]
and use it like
{{ mylist|lookup:x }}
The slice tag in Django templates may use python's slicing code, but the syntax is unmistakably different. For instance, if you wanted to get an element of a sequence with a variable, in python you'd write something similar to the following:
>>>mylist = ["0th Element", "1th Element"]
>>>zero, one = 0, 1
>>>mylist[zero]
"0th Element"
>>>mylist[one]
"1th Element"
Using this syntax with the Django slice template tag will return a sliced list in every case, of dubious utility for getting an item of known index:
{% with "0" as zero %}
{% with "1" as one %}
{% with "2" as two %}
{{mylist|slice:zero}} {{mylist|slice:one}} {{mylist|slice:two}}
{% endwith %}
{% endwith %}
{% endwith %}
Renders to the html:
[] ["0th Element"] ["0th Element", "1th Element"]
Note the differences: you are getting the result of mylist[:x] instead of mylist[x].
Django provides enough tools to work around this. The first trick is to use explicit slices like 0:1 for your indices, and then |join:"" the resultant list into a single element. Like so:
{% with "0:1" as zero %}
{{mylist|slice:zero|join:""}}
{% endwith %}
Yields:
0th Element
This comes in particularly handy if you need to access a parent loop's index when dealing with an iterable inside a child loop:
{% for parent in parent_loop %}
{% cycle "0:1" "1:2" "2:3" as parent_loop_index silent %}
{% for child in child_loop %}
{{child|slice:parent_loop_index|join:""}}
{% endfor %}
{% endfor %}
Completed with nothing but stock parts, although I don't think Django has implemented achievements yet.
I notice that #e-satis mentioned it, but I think the built-in slice template tag deserves some love.
{{ item | slice:"2" }} #gets the third element of the list
Are you sure you can't just do:
{% for item in mylist %}
{{ item }}
{% endfor %}
With the slice filter, you can even do some customisation.
Following worked for me
{% for 1,2,3 in mylist %}
# do stuff
Just don't use brackets around 1,2,3

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

Use variable as dictionary key in Django template

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>

Django Newbie ManyToManyField Template Question

I have a Django model with a ManyToManyField and I'm trying to iterate the contents of that field in a comma-delimited list in my template. I'm getting some unexpected results.
{% for painting in paintings_list %}
<p>{% for item in painting.style.all %}
{{ item.style|join:', ' }}
{% endfor %}</p>
{% endfor %}
The contents are being displayed as they exist in the database, but they're displayed in an unanticipated way...ie. instead of:
Renaissance, Baroque, Expressionist
I'm getting:
R,e,n,a,i,s,s,a,n,c,e,,B,a,r,o,q,u,e,,E,x,p,r,e,s,s,i,o,n,i,s,t
Any idea what I'm doing wrong? Would have thought the join template filter was for exactly this type of scenario, but perhaps the proper way to do this would be to create a custom method of the model...
Ignacio Vasquez-Abrams is correct (as usual). A solution to your problem might lie in the forloop variables.
<p>{% for item in painting.style.all %}
{{item.style}} {% if not forloop.last %}, {% endif %}
{% endfor %}</p>
item.style is returning a string, so you're joining each character with , instead of each item.