在 Django 中注释相关和多重过滤的对象

Posted

技术标签:

【中文标题】在 Django 中注释相关和多重过滤的对象【英文标题】:Annotating related and multi-filtered objects in Django 【发布时间】:2017-05-05 02:24:10 【问题描述】:

我有一个配置文件查询集:

型号:

class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, unique=True)
    ...

查看:

Profile.objects.select_related('user')

每个用户/个人资料每天可以注册多个活动:

型号:

class Event(models.Model):

    title = models.CharField(max_length=120)
    date = models.DateField(default=default_event_date)
    ...


class Registration(models.Model):

    event = models.ForeignKey(Event)
    student = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    block = models.ForeignKey(Block, on_delete=models.CASCADE)
    ....

给定一个日期,我如何注释(?我认为这就是我想要的?)每个块一个注册对象(根据用户/个人资料和 Event__Date 过滤)

最后,我想在我的模板中输出的是这样的:

For Date: 19 Dec 2016

User/Profile    Block A      Block B   ...
user1           None         None
user2           Event1       Event2
user3           Event3       None
...

编辑

尝试 1。这是我第一次尝试完成此操作。我怀疑这是非常低效的,并且在生产中会非常缓慢,但至少它有效。如果有人可以提供更有效和优雅的解决方案,将不胜感激! (请注意,这还包括对 homeroom_teacher 的用户配置文件模型的过滤器,该过滤器未包含在原始问题中,但我已离开此处,因为这是有效的代码)

注册模型管理器

类RegistrationManager(models.Manager):

def homeroom_registration_check(self, event_date, homeroom_teacher):
    students = User.objects.all().filter(is_staff=False, profile__homeroom_teacher=homeroom_teacher)
    students = students.values('id', 'username', 'first_name', 'last_name')
    # convert to list of dicts so I can add 'annotate' dict elements
    students = list(students) 

    # get queryset with events? optimization for less hits on db
    registrations_qs = self.get_queryset().filter(event__date=event_date, student__profile__homeroom_teacher=homeroom_teacher)

    # append each students' dict with registration data
    for student in students:
        user_regs_qs = registrations_qs.filter(student_id=student['id'])

        for block in Block.objects.all():
            # add a new key:value for each block
            try:
                reg = user_regs_qs.get(block=block)
                student[block.constant_string()] = str(reg.event)
            except ObjectDoesNotExist:
                student[block.constant_string()] = None

    return students

模板 请注意 block.constant_string() --> "ABLOCK"、"BBLOCK" 等,这是在 block.constant_string() 方法中硬编码的,我也不知道如何解决这个问题。

% for student in students %
  <tr >
    <td> student.username </td>
    <td> student.first_name </td>
    <td> student.last_name </td>
    <td> student.ABLOCK|default_if_none:'-' </td>
    <td> student.BBLOCK|default_if_none:'-' </td>
  </tr>
% endfor %

【问题讨论】:

【参考方案1】:

可能你不需要使用annotate,但可以在模板中使用regroup标签。

从头到尾。您想在页面上显示的所有信息都可以从注册模型中检索到。

获取按活动日期过滤的所有注册:

registrations = Registration.objects.filter(event__date = date)

之后您必须在模板中由用户regroup。

但是我发现以下问题:

    我不确定重组标签是否能与查询集一起正常工作。因此,您可能必须将数据转换为列表。但是,我发现这个answer 使用了查询集。 即使您将重新组合,您也需要在模板中使用一些逻辑来定义事件的块。

【讨论】:

我认为从注册开始的问题是它不会选择没有注册的用户。例如 user1 在我的输出中不会出现。 @43Tesseracts 是的,我也在考虑为用户提供 tempate 的解决方案,但最终它也变得不那么优雅了。可能某种模型重构应该有所帮助。我以后会考虑的。【参考方案2】:

为了解决硬编码名称的问题,我会稍微修改您的解决方案,使其看起来像这样:

def homeroom_registration_check(event_date, homeroom_teacher):
    students = User.objects.filter(
        is_staff=False,
        profile__homeroom_teacher=homeroom_teacher,
    )
    block_ids = Block.objects.values('id')
    for student in students:
        table_row = []
        for block_id in block_ids:
            try:
                reg = Registration.objects.get(
                    student=student,
                    block=block_id,
                    event__date=event_date,
                )
                table_row.append(reg.event)
            except ObjectDoesNotExist:
                table_row.append(None)
        yield (student, table_row)

我会从模型管理器中取出它并将其放入 views.py 或单独的文件(如 table.py)中。对我来说似乎更干净,但这只是一种意见 - 您可以将此代码放在模型管理器中,它无论如何都会运行。

然后在你的模板中:

% for student, row in homeroom_reg_check %
    <tr>
        <td> student.username </td>
        <td> student.other_data </td>
        % for event in row %
            <td> event.name|default_if_none:'-' </td>
        % endfor %
    </tr>
% endfor %

【讨论】:

你能评论一下你的代码和我的代码在效率上的差异吗?你的看起来好多了,这是肯定的。你的只有一个 db 调用吗? @43Tesseracts 我的 sn-p 返回生成器,它产生一个包含对象用户和事件/无值列表的元组。这使其效率更高一些,因为它不会将整个表加载到内存中,而是一次加载一行。关于 db hits,它在每个表格单元格中命中 db 一次,和你的一样。我想我可以使用列表推导将它重构为每行使用一次命中,但总体而言它会使用更多的内存和处理时间。 还有一个问题:为什么要为 block_ids 单独列出一个列表,而不是只使用for block in Block.objects.all() @43Tesseracts 我这样做了,所以我不必为了知道它的 id 而填充整个实例。

以上是关于在 Django 中注释相关和多重过滤的对象的主要内容,如果未能解决你的问题,请参考以下文章

Django 查询 - 在带注释的计数过滤器中获取父对象

Django - 过滤相关对象

Django 按最新的相关对象过滤

模板中的 Django 动态对象过滤问题

如何使用附加过滤的相关对象作为 Django 中的字段来获取结果?

如何通过多对一关系中同一相关对象的两个属性在 django 中进行过滤?