tags:

views:

112

answers:

2

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.

+2  A: 

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.

Daniel Roseman
Thanks, that was exactly what I was looking for! Now I only need to be sure that my results are correctly order before passing it to the template.
Etienne
+1  A: 

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).

David Berger
Thanks for your answer. It's very interesting approach that move more stuff on the model side, a good thing.About Year and period. They are basically Year and Month but Month could have strange values (ex. Mid-summer) and Year could be a range (ex. 1932-1940). Also Year could have an image associated with it. So I don't know how I could do this differently ?
Etienne
My biggest recommendation would be to have period point to year and Notice point to period only. Is it logically possible for period p to have date June 1, 1963 and year y to have date March 3, 1992 and notice n be associated with y and p? If not, you have a database design problem.
David Berger