Etienne
Etienne

Reputation: 12590

In Django how to show a list of objects by year

I have theses models:

class Year(models.Model):
    name = models.CharField(max_length=15)
    date = models.DateField()

class Period(models.Model):
    name = models.CharField(max_length=15)
    date = models.DateField()

class Notice(models.Model):
    year = models.ForeignKey(Year)
    period = models.ForeignKey(Period, blank=True, null=True)
    text = models.TextField()
    order = models.PositiveSmallIntegerField(default=1)

And in my view I would like to show all the Notices ordered by year and period but I don't want to repeat the year and/or the period for a notice if they are the same as the previous. I would like this kind of result:

1932-1940
Mid-summer

  • Text Lorem ipsum from notice 1 ...

  • Text Lorem ipsum from notice 2 ...

September

  • Text Lorem ipsum from notice 3 ...

1950
January

  • Text Lorem ipsum from notice 4 ...

etc.

I found a solution by looping over all the rows to built nested lists like this:

years = [('1932-1940', [
                        ('Mid-summer', [Notice1, Notice2]),
                        ('September',  [Notice3])
                       ]),
           ('1950',    [
                        ('January', [Notice4])
                       ])
         ]

Here's the code in the view:

years = []
year = []
period = []
prev_year = ''
prev_period = ''
for notice in Notice.objects.all():
    if notice.year != prev_year:
        prev_year = notice.year
        year = []
        years.append((prev_year.name, year))
        prev_period = ''
    if notice.periode != prev_period:
        prev_period = notice.periode
        period = []
        if prev_period:
            name = prev_period.name
        else:
            name = None
        year.append((name, period))
    period.append(notice)

But this is slow and inelegant. What's the good way to do this ?

If I could set some variables inside the template I could only iterate over all the notices and only print the year and period by checking if they are the same as the previous one. But it's not possible to set some some temp variables in templates.

Upvotes: 1

Views: 647

Answers (2)

David Berger
David Berger

Reputation: 12823

Your problem would not actually be that hard if not for the unfortunate denormalization you have going on. I'm going to answer your question ignoring the Year class, because I don't understand how that logic relates to the period logic.

Most simply, in the template, put:

{% for period in periods %}
    period.name
    {% for notice in period.notice_set.all %}
        notice.text
    {% endfor %}
{% endfor %}

Now that completely leaves out ordering, so if you like, you could define in your Period model:

def order_notices(self):
    return self.notice_set.order_by('order')

Then use

{% for period in periods %}
    period.name
    {% for notice in period.order_notices %}
        notice.text
    {% endfor %}
{% endfor %}

If you must use years, I strongly suggest defining a method in the Year model of the form

def ordered_periods(self):
    ... #your logic goes here
    ... #should return an iterable (list or queryset) or periods

Then in your template:

{% for year in years %}
    year.name
    {% for period in year.ordered_periods %}
        period.name
        {% for notice in period.order_notices %}
            notice.text
        {% endfor %}
    {% endfor %}

EDIT: Please keep in mind that the secret to all this success is that the template can only call methods that don't take any arguments (except self).

Upvotes: 1

Daniel Roseman
Daniel Roseman

Reputation: 600041

Luckily Django has some built-in template tags that will help you. Probably the main one you want is regroup:

{% regroup notices by year as year_list %}


{% for year in year_list %}
  <h2>{{ year.grouper }}<h2>

  <ul>
  {% for notice in year.list %}
     <li>{{ notice.text }}</li>
  {% endfor %}
  </ul>
{% endfor %}

There's also {% ifchanged %}, which can help with looping over lists when one value stays the same.

Upvotes: 2

Related Questions