views:

26

answers:

3

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?

A: 

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 }}
Daniel Roseman
Thanks Daniel. I'm actually probably going to use this one as I'll be needing the functionality elsewhere in the app. But, yeah, I can see I might be using Manoj's suggestion elsewhere.
alj
+1  A: 

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 %}
Manoj Govindan
+1. That's nice.
Daniel Roseman
Thanks for this Manoj. I think both this and Daniels are good solutions.
alj
A: 

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

John Mee