Django实现Users与UserGroup数据合并下拉框及关联存储方案
问题背景
现有Users、UserGroup两张业务表,前端访问规则配置页面时,需要将两表全量数据合并返回,用于渲染支持混合选择的下拉选择组件;页面操作完成后,下拉框选中的混合数据会通过POST请求回传后端。当前已给出三张数据表的初始模型定义,需要明确三表关联关系实现数据统一管理。
现有初始模型定义
User.py
class Users(AbstractBaseUser): vendor_name = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None, null=True) username = models.CharField(max_length=100, verbose_name="username", unique=True) password = models.CharField(max_length=100) created_by = models.DateField(verbose_name="created_by", auto_now_add=True) USERNAME_FIELD = "username" REQUIRED_FIELDS = ['password', 'hardware_id'] is_active = models.BooleanField(default=True) is_admin = models.BooleanField(default=False) is_role_vendor = models.BooleanField(default=False) is_role_customer = models.BooleanField(default=True) def __str__(self): return self.username objects = UserManager()
UserGroup.py
class UserGroup(models.Model): vendor_id = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None, null=True) id = models.IntegerField(primary_key=True) name = models.CharField(max_length=100) description = models.CharField(max_length=1000) users = models.ManyToManyField(Users) def __str__(self): return self.name
Rules.py
class Rules(models.Model): vendor_id = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None, null=True) id = models.IntegerField(primary_key=True) name = models.CharField(max_length=100) description = models.CharField(max_length=1000) # 待补全字段:存储下拉框选中的用户、用户组混合数据 def __str__(self): return self.name
核心待实现需求
- GET请求场景下,编写View层逻辑拉取Users、UserGroup两表合并数据,返回给前端用于下拉框渲染
- 支持下拉框同时选择两类表的选项,将选中的混合数据统一存储到Rules数据表
- 输出调整优化后的完整模型结构定义
具体实现方案
一、模型结构优化
原有模型存在几处会导致运行报错或后续维护问题的点,先做修正:
- 外键字段命名不规范:Django会自动为外键字段添加
_id后缀存储到数据库,原有vendor_id、vendor_name命名会导致实际数据库字段出现vendor_id_id这类冗余命名,统一调整为语义化的vendor - 重复定义字段:
AbstractBaseUser已经内置哈希存储的password字段,原有自定义明文password字段会覆盖内置逻辑,存在严重安全隐患,直接删除 - 字段声明与定义不匹配:
REQUIRED_FIELDS中声明了hardware_id但模型未定义该字段,补全对应字段;created_by字段为日期类型且配置了自动写入时间,明显为创建时间字段,修正字段名为created_time - 冗余主键定义:Django默认会生成自增主键,手动定义
id = models.IntegerField(primary_key=True)会丢失自增特性,新增数据时需要手动维护主键值,移除手动定义 - 混合关联实现:采用Django内置的ContentType框架实现通用关联,不需要硬编码固定外键,即可同时绑定Users、UserGroup两类不同模型的实例,满足混合存储选中项的需求
优化后的完整模型代码如下:
# User.py from django.contrib.auth.models import AbstractBaseUser, BaseUserManager from django.db import models class UserManager(BaseUserManager): def create_user(self, username, password=None, **extra_fields): if not username: raise ValueError('Username is required') user = self.model(username=username, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, username, password=None, **extra_fields): extra_fields.setdefault('is_admin', True) extra_fields.setdefault('is_active', True) return self.create_user(username, password, **extra_fields) class Vendor(models.Model): # 原有Vendor模型保持不变即可 name = models.CharField(max_length=100) def __str__(self): return self.name class Users(AbstractBaseUser): vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None, null=True) username = models.CharField(max_length=100, verbose_name="username", unique=True) hardware_id = models.CharField(max_length=100, verbose_name="hardware_id", blank=True, default="") created_time = models.DateTimeField(verbose_name="created_time", auto_now_add=True) USERNAME_FIELD = "username" REQUIRED_FIELDS = [] is_active = models.BooleanField(default=True) is_admin = models.BooleanField(default=False) is_role_vendor = models.BooleanField(default=False) is_role_customer = models.BooleanField(default=True) def __str__(self): return self.username objects = UserManager()
# UserGroup.py from django.db import models from User.models import Users, Vendor class UserGroup(models.Model): vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None, null=True) name = models.CharField(max_length=100) description = models.CharField(max_length=1000) users = models.ManyToManyField(Users, related_name="joined_groups") def __str__(self): return self.name
# Rules.py from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from User.models import Vendor # 规则绑定主体中间表,存储规则关联的用户/用户组 class RuleAssignedSubject(models.Model): rule = models.ForeignKey("Rules", on_delete=models.CASCADE, related_name="assigned_subjects") content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() subject = GenericForeignKey("content_type", "object_id") class Meta: unique_together = ("rule", "content_type", "object_id") class Rules(models.Model): vendor = models.ForeignKey(Vendor, on_delete=models.CASCADE, default=None, null=True) name = models.CharField(max_length=100) description = models.CharField(max_length=1000) def __str__(self): return self.name
二、GET接口View层实现
逻辑非常直接:分别查询全量有效用户、全量用户组,给每条数据添加类型标记后合并返回,前端拿到带类型标记的列表即可直接渲染下拉组件。示例代码(Django原生View实现):
from django.http import JsonResponse from User.models import Users from UserGroup.models import UserGroup def get_rule_select_options(request): # 查询所有有效用户 user_list = list(Users.objects.filter(is_active=True).values("id", "username")) for item in user_list: item["type"] = "user" item["display_name"] = item["username"] # 查询所有用户组 group_list = list(UserGroup.objects.all().values("id", "name")) for item in group_list: item["type"] = "usergroup" item["display_name"] = item["name"] # 合并两类数据返回 all_options = user_list + group_list return JsonResponse({"code": 0, "data": all_options})
返回的每条选项数据都包含type字段,前端选中后回传时保留该字段,后端即可区分数据属于哪张表。
三、POST数据存储逻辑
前端回传的选中项为数组格式,每个元素包含type和id两个核心字段。后端存储时先校验对应实例是否存在,再批量写入中间表即可。如果是更新已有规则,先清空原有绑定的关联数据再写入新数据。示例代码:
from django.http import JsonResponse from django.contrib.contenttypes.models import ContentType from User.models import Users from UserGroup.models import UserGroup from Rules.models import Rules, RuleAssignedSubject import json def save_rule(request): if request.method != "POST": return JsonResponse({"code": 405, "msg": "Method not allowed"}) # 解析请求参数,根据实际接口格式调整取值逻辑 request_data = json.loads(request.body) rule_name = request_data.get("name") rule_desc = request_data.get("description") vendor_id = request_data.get("vendor_id") selected_subjects = request_data.get("selected", []) # 格式:[{"type":"user", "id":1}, {"type":"usergroup", "id":2}] # 创建/更新规则主表 rule, created = Rules.objects.update_or_create( id=request_data.get("rule_id"), # 传rule_id则为更新,不传则新建 defaults={ "name": rule_name, "description": rule_desc, "vendor_id": vendor_id } ) # 更新规则关联的选中项:先删旧数据 RuleAssignedSubject.objects.filter(rule=rule).delete() # 批量写入新数据 subject_objs = [] user_ct = ContentType.objects.get_for_model(Users) group_ct = ContentType.objects.get_for_model(UserGroup) for item in selected_subjects: if item["type"] == "user": # 校验用户是否存在 if not Users.objects.filter(id=item["id"], is_active=True).exists(): return JsonResponse({"code": 400, "msg": f"用户{item['id']}不存在"}) subject_objs.append(RuleAssignedSubject( rule=rule, content_type=user_ct, object_id=item["id"] )) elif item["type"] == "usergroup": if not UserGroup.objects.filter(id=item["id"]).exists(): return JsonResponse({"code": 400, "msg": f"用户组{item['id']}不存在"}) subject_objs.append(RuleAssignedSubject( rule=rule, content_type=group_ct, object_id=item["id"] )) RuleAssignedSubject.objects.bulk_create(subject_objs) return JsonResponse({"code": 0, "msg": "保存成功", "data": {"rule_id": rule.id}})
后续需要查询规则绑定的所有用户和用户组时,直接通过rule.assigned_subjects.all()即可拿到所有关联实例,不需要额外做数据解析。
内容的提问来源于stack exchange,提问作者Mukul
相关产品推荐
相关产品推荐

