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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 13:39:18