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

Annotating related and multi-filtered objects in Django

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

型号:

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

查看:

Profile.objects.select_related('user')

每个 user/profile 每天可以注册多个活动:

型号:

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

给定一个日期,我如何注释(?我想这就是我想要的?)每个块一个注册对象(在 user/profile 和 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 的用户配置文件模型的过滤器,该过滤器未包含在原始问题中,但我离开这里是因为这是有效的代码)

注册模型管理员

class注册管理器(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 %}

可能您不需要使用注释,但可以在模板中使用 regroup 标签。

从最后开始。您要在页面上显示的所有信息都可以从注册模型中检索。

获取按活动日期筛选的所有注册:

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

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

但是我发现了以下问题:

  1. 我不确定重组标签是否与查询集一起正常工作。所以可能你必须将数据转换成列表。但是,我发现这个 answer 其中使用了查询集。
  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 %}