Reputation: 12590
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
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
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