在Django模板中查询特定相关对象的数据

0 投票
2 回答
1397 浏览
提问于 2025-04-16 11:19

我正在尝试使用Django来展示多个模型中的信息表格,但我不太确定最佳的做法是什么。以下是我简化后的models.py:

from django.contrib.auth.models import User

class Widget(models.Model):
    name = models.CharField(max_length=50)
    pass

class Finished_Widget(models.Model):
    user = models.ForeignKey(User)
    thing = models.ForeignKey(Thing)
    created = models.DateTimeField(editable=False)

这个设置很简单,User和Widget通过一些未知数量的Finished_Widget对象连接在一起。我想在我的HTML中显示的是Widget名称的实例列表,以及最近完成的Widget的日期。显然,下面的代码是不能工作的,但希望它能说明我想要输出的数据。

{% for widget in widget_list %}
<p>{{ widget.name }}</p>
<p>{{ widget__Finished_Widget.latest.created }}</p>
{% endfor %}

我考虑过几种不同的解决方案,包括:

  • 通过视图将所需的日期作为额外属性添加
  • 在视图中创建一个字典,然后在模板中调用它
  • 使用'通过'参数的ManyToManyField
  • 在Widget中添加元数据,以调用最新Finished_Widget的创建日期

不幸的是,我还没有成功实现这些方法,也不确定哪种方法是正确的,或者是否有任何一种方法是对的。

注意:我在StackOverflow上找到了很多关于这个问题的变体,这让这个问题几乎是重复的,但它们似乎对我没有太大帮助,因为我需要将Finished_Widget列表缩小到匹配用户ID、匹配Widget ID,并且是最新的一个。

2 个回答

0

好吧,我觉得这里的信息还不够多,没法完全回答你的问题。不过,有一个简单的办法可以解决只返回最近一个 Finished_Widget 的问题,那就是在 User 模型里加一个叫“latest_widget”的方法:

class User(models.Model):
    #...
    def latest_widget(self):
        return self.widgets.all().order_by('-created')[0]

其实你也可以把这个方法加到 Finished_Widget 模型里,但这样做就没那么合理了。如果你在用 Django 的认证模块里的 User 模型,可能需要对这个模型进行扩展,才能添加这个功能。

一旦你有了这个方法,从模板里调用它就很简单了。比如,如果你有一个用户列表,叫做 user_list,你可以这样显示每个用户和他/她最近的 Widget:

{% for uu in user_list %}
<p>
    {{uu.username}}: {{uu.latest_widget.name}}
</p>
{% endfor %}
2

根据需要传入 user 对象,更新了答案。

最简单的方法是在你的视图中处理这个问题,但这样做也有一些问题。

for widget in widget_list:
    widget.latest_finished_widget = widget.finished_widget_set.filter(user=request.user).latest('created')

{% for widget in widget_list %}
    <p>{{ widget.name }}</p>
    <p>{{ widget.latest_finished_widget }}</p>
{% endfor %}

虽然这段代码写起来很简单,但它在每次循环时都会执行一次数据库查询,这样效率不高。

一个快速的解决方案叫做查询分区。可以使用两个查询和 Python 来尽可能高效地填充反向关系,而不需要去数据库。

widgets = Widget.objects.filter(...)

finished_widget_map = dict( 
    [(x['thing__id'], x['created']) for x in 
        Finished_Widget.objects.filter(thing__in=widgets, user=request.user).values_list('thing__id', 'created').order_by('created')])
    # this ensures we only select what we need, thing__id and the created column
    # ordering by created will ensure only the last (latest) will appear in our dictionary

for widget in widgets:
    widget.latest_finished_widget_created = finished_widget_map.get(widget.id, 'No Widget')


{% for widget in widgets %}
    {{ widget.name }}
    {{ widget.latest_finished_widget_created }}
{% endfor %}

不过,这样做总是有得有失。如果有很多 Finished_Widgets,Python 可能会无意义地尝试填充一个字典,这样会变得很慢。

撰写回答