在Django中如何按年份显示对象列表

1 投票
2 回答
627 浏览
提问于 2025-04-15 13:45

我有这些模型:

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)

在我的视图中,我想按年份和时期显示所有通知,但如果某个通知的年份和/或时期与前一个相同,我就不想重复显示。我想要这样的结果:

1932-1940
仲夏

  • 通知1的内容 ...

  • 通知2的内容 ...

九月

  • 通知3的内容 ...

1950
一月

  • 通知4的内容 ...

等等。

我找到了一种解决方案,通过遍历所有行来构建嵌套列表,像这样:

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

这是视图中的代码:

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)

但这样做速度慢且不够优雅。有什么好的方法可以做到这一点呢?

如果我能在模板中设置一些变量,我就可以遍历所有通知,并通过检查它们是否与前一个相同来只打印年份和时期。但在模板中无法设置临时变量。

2 个回答

1

你的问题其实并不难,主要是因为你遇到了不太好的数据结构设计。我会忽略Year这个类来回答你的问题,因为我不太明白它和时间段逻辑之间的关系。

简单来说,在模板里,你可以放:

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

这样做完全没有考虑排序,所以如果你愿意,可以在你的Period模型里定义:

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

然后使用

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

如果你一定要用年份,我强烈建议在Year模型里定义一个方法,形式如下:

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

然后在你的模板里:

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

补充说明:请记住,成功的关键在于模板只能调用那些不需要任何参数(除了self)的函数。

2

幸运的是,Django 有一些内置的模板标签可以帮助你。你可能最想用的是 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 %}

还有一个 {% ifchanged %},它可以在遍历列表时帮助你处理某个值保持不变的情况。

撰写回答