You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django 管理多对多(M2M)关系的视图如何进行单元测试

Django 多对多用户组视图单元测试解决方案

核心测试需求实现

你遇到的报错本质是操作了BoundField类型的表单字段对象,没有转为可用的校验对象,以下两种方案均可实现按列表校验用户归属:

方案1:直接校验表单QuerySet(推荐,单元测试优先)

无需解析HTML,直接校验视图传递给表单字段的查询集,效率高逻辑清晰:

def test_adm_update_group(self):
    self.client.force_login(self.user_staff.user)
    url = reverse("polls:adm_group_detail", args=[self.company.comp_slug, self.group1.id])
    response = self.client.get(url)
    self.assertEqual(response.status_code, 200)

    # 获取两个选择框对应的查询集
    all_users_qs = response.context["group_form"].fields["all_users"].queryset
    selected_users_qs = response.context["group_form"].fields["users"].queryset

    # 校验已加入group1的用户在右侧已选列表
    self.assertIn(self.usr11, selected_users_qs)
    self.assertIn(self.usr12, selected_users_qs)
    # 校验未加入group1的用户在左侧待选列表
    self.assertIn(self.usr13, all_users_qs)
    self.assertIn(self.usr14, all_users_qs)
    self.assertIn(self.usr21, all_users_qs)
    self.assertIn(self.usr22, all_users_qs)

    # 额外校验交叉不存在的情况,避免逻辑漏洞
    self.assertNotIn(self.usr11, all_users_qs)
    self.assertNotIn(self.usr21, selected_users_qs)

方案2:校验渲染后的HTML(适合验证前端渲染结果)

将表单字段转为渲染后的HTML字符串,同时注意assertInHTML的参数顺序(第一个是要查找的内容,第二个是目标HTML):

def test_adm_update_group_html(self):
    self.client.force_login(self.user_staff.user)
    url = reverse("polls:adm_group_detail", args=[self.company.comp_slug, self.group1.id])
    response = self.client.get(url)
    self.assertEqual(response.status_code, 200)

    # 把BoundField转为渲染后的HTML字符串
    all_users_html = str(response.context["group_form"]["all_users"])
    selected_users_html = str(response.context["group_form"]["users"])

    # 校验用户在对应列表的HTML中
    self.assertInHTML(self.usr11.user.username, selected_users_html)
    self.assertNotInHTML(self.usr11.user.username, all_users_html)
    self.assertInHTML(self.usr21.user.username, all_users_html)
    self.assertNotInHTML(self.usr21.user.username, selected_users_html)

增删用户功能测试示例

针对POST提交的增删逻辑,可以用以下用例校验:

def test_adm_group_modify_users(self):
    self.client.force_login(self.user_staff.user)
    url = reverse("polls:adm_group_detail", args=[self.company.comp_slug, self.group1.id])
    # 构造提交数据:保留usr12,新增usr13、usr14,移除usr11
    post_data = {
        "group_name": "Groupe 1",
        "weight": 40,
        "group_list": f"{self.usr12.id}-{self.usr13.id}-{self.usr14.id}"
    }
    # 提交POST请求
    response = self.client.post(url, post_data)
    self.assertEqual(response.status_code, 200)

    # 校验数据库关联关系已更新
    updated_group = EventGroup.objects.get(id=self.group1.id)
    self.assertIn(self.usr13, updated_group.users.all())
    self.assertNotIn(self.usr11, updated_group.users.all())

    # 校验返回页面的列表已更新
    selected_qs = response.context["group_form"].fields["users"].queryset
    self.assertIn(self.usr13, selected_qs)
    self.assertNotIn(self.usr11, selected_qs)

选择框标题显示问题修复

你当前的列表容器用了d-flex默认横向排列,导致标题和选择框挤在同一行,修改两个列表容器的flex方向即可:

<!-- 左侧待选列表容器 -->
<div class="col-md-5 d-flex flex-column align-items-center">
    <p>Utilisateurs</p>
    {{ group_form.all_users}}
</div>
<!-- 右侧已选列表容器 -->
<div class="col-md-5 d-flex flex-column align-items-center">
    <p>Utilisateurs sélectionnés</p><br>
    {{ group_form.users }}
    <div class="control-group {%if group_form.users.errors %}error{%endif%}"></div>
</div>

内容的提问来源于stack exchange,提问作者Christophe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 08:24:08