DC's blog DC's blog
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)

DC

愿我一生欢喜,不为世俗所及.
首页
  • 计算机基础
  • linux基础
  • mysql
  • git
  • 数据结构与算法
  • axure
  • english
  • docker
  • opp
  • oop
  • 网络并发编程
  • 不基础的py基础
  • 设计模式
  • html
  • css
  • javascript
  • jquery
  • UI
  • 第一次学vue
  • 第二次学vue
  • Django
  • drf
  • drf_re
  • 温故知新
  • flask
  • 前后端不分离

    • BBS
    • 订单系统
    • CRM
  • 前后端部分分离

    • pear-admin-flask
    • pear-admin-django
  • 前后端分离

    • 供应链系统
  • 理论基础
  • py数据分析包
  • 机器学习
  • 深度学习
  • 华中科大的网课
  • cursor
  • deepseek
  • 杂文
  • 罗老师语录
  • 关于我

    • me
  • 分类
  • 归档
GitHub (opens new window)
  • Django

    • 框架引入
    • 基本配置
    • 单表记录CURD
    • 路由和视图层
    • 模版层
    • ORM查询
    • ORM字段
    • Ajax+layer
    • 自定义分页器
    • cookie与session
    • 中间件和csrf
    • Auth认证模块
    • ORM复习
    • 测试Demo
  • 第一次学drf

  • 第二次学drf

  • 温故知新

  • flask

  • 后端
  • Django
DC
2024-03-29

测试Demo

在进行CRM系统的开发过程中, 我突然思考一个问题: Form组件或ModelForm组件 如何实现 多对多 字段的添加..

一开始,我去谷歌上搜索这个问题. 那些阐述的不是很好. 再加上是深夜思考的, 脑袋昏沉沉的 转不过来..
第二天闲暇之余,我去Django官方文档里查 3.2的版本 搜索关键字 ModelMultipleChoiceField、CheckboxSelectMultiple
看官方文档,看到了我想知道的答案!! 于是乎,我急于快速创建一个Demo对其进行验证!

我将代码粘贴到下面,以后进行Demo测试的时候,粘贴复制简单修改下,就可以快速进行测试啦!!
1
2
3
4
5

image-20240329150654511

啥样式都没加,测试下来是ok的!!
喵的,我用layui样式弄,就GG了,多选框硬是点了后取消不了.我搞不懂!! 倒腾了下,出现在样式layui-form上!
但不能不加这个啊,不然表单域里的其他表单项的样式就很难看.. 555
最后我退而求其次,这样搞! 给插件添加个属性"lay-ignore": True,这样的话这个多选框就不会应用layui的样式啦!! 曲线救国成功.
类似于这样:
class UserInfoModelForm(forms.ModelForm):
    roles = forms.ModelMultipleChoiceField(
        label="角色", 
        queryset=Role.objects.all(), 
        # -- 该插件的样式 对应上面截图中的第一个
        widget=forms.CheckboxSelectMultiple(attrs={
            "lay-ignore": True
        })
        # ModelMultipleChoiceField默认的插件是SelectMultiple -- 该插件的样式 对应上面截图中的第二个
        # widget=forms.SelectMultiple(attrs={
        #     "lay-ignore": True
        # })
    )

    class Meta:
        model = models.UserInfo
        fields = ['name', 'password', 'nickname', 'gender', 'phone', 'email', 'depart', 'roles']

    def __init__(self, *args, **kwargs):
        super(UserInfoModelForm, self).__init__(*args, **kwargs)
        for name, field in self.fields.items():
            if name != "roles":
                field.widget.attrs['class'] = 'layui-input'
                field.widget.attrs['autocomplete'] = "off"

PS: ModelMultipleChoiceField 表明是啥类型的数据; CheckboxSelectMultiple表明以什么形式展现数据!!
    以及ModelMultipleChoiceField在ORM表中对应怎么的字段类型,Django的官方文档都写明了的!!
    ★★★ Django的官方文档真的是好东西!!爱看多看.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

补充一点

多对对字段 使用ModelForm内 对应的是 ModelMultipleChoiceField 表单字段对象.. 
ModelMultipleChoiceField默认使用的插件是 SelectMultiple.. 
我想在__init__方法里,通过类型判断,将ModelMultipleChoiceField表单字段对象的插件 一劳永逸的改成 CheckboxSelectMultiple..
这样, 表中的多对多字段就无需自己 在class Meta中通过 widgets 指定了.. 
而且通过 __init__ 的方法,还可以提取父类,其它地方也可以用.


class UserInfoModelForm(forms.ModelForm):
    class Meta:
        model = models.UserInfo
        fields = '__all__'
        widgets = {
            # "roles": forms.CheckboxSelectMultiple()  # 这里写可以
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            if isinstance(field, ModelMultipleChoiceField):  # 默认SelectMultiple
                # 这里写就得传一下choices
                field.widget = forms.CheckboxSelectMultiple(choices=field.widget.choices)  
                
我没仔细看源码 就看了下 CheckboxSelectMultiple 继承的 ChoiceWidget 里的 __init__ 有choices 属性 就实验了下
实验下来 确实可以.. 至于为什么 我的猜想是
在__init__里覆盖字段对象的插件. 像CheckboxSelectMultiple 里面是没有值的!! 要把原来的choices的值拿给它.. 
至于为啥 在Meta里 重写可以,可能是因为 内部源码将原先的choices值给了它 只是猜想,没有深入的探究.. 暂且告一段落!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

数据库表

from django.db import models

class Role(models.Model):
    """ 角色 """
    title = models.CharField(verbose_name='角色名称', max_length=32)

    def __str__(self):
        return self.title


class UserInfo(models.Model):
    """用户表"""
    name = models.CharField(verbose_name='用户名', max_length=32)
    roles = models.ManyToManyField(verbose_name='拥有的所有角色', to=Role)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

路由

# -- 根路由
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app01/', include("app01.urls")),
]


# -- 在项目根目录下的 单个app  urls.py
from django.urls import path, include
from . import views

urlpatterns = [
    path('user/list/', views.user_list),
    path('user/add/', views.user_add),
    path('user/edit/<int:pk>/', views.user_edit),
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

视图函数

from django.shortcuts import render, redirect, HttpResponse
from django.urls import reverse

from . import models
from .form import UserInfoModelForm


def user_list(request):
    """列表"""
    user_queryset = models.UserInfo.objects.all()
    return render(request, 'user_list.html', {'users': user_queryset})


def user_add(request):
    """添加"""
    if request.method == 'GET':
        form = UserInfoModelForm()
        return render(request, 'change.html', {'form': form})

    form = UserInfoModelForm(data=request.POST)
    if form.is_valid():
        form.save()
        return HttpResponse("add ok")
    return render(request, 'change.html', {'form': form})


def user_edit(request, pk):
    """编辑"""
    obj = models.UserInfo.objects.filter(id=pk).first()
    if not obj:
        return HttpResponse("pk error")
    if request.method == 'GET':
        form = UserInfoModelForm(instance=obj)
        return render(request, 'change.html', {'form': form})
    form = UserInfoModelForm(data=request.POST, instance=obj)
    if form.is_valid():
        form.save()
        return HttpResponse("edit ok")
    return render(request, 'change.html', {'form': form})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

列表页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div class="layui-btn-group">
    <a href="/app01/user/add/">
        <button type="button">添加用户</button>
    </a>
</div>
<table class="layui-table" lay-even>
    <thead>
    <tr>
        <th>序号</th>
        <th>用户名</th>
        <th>选项</th>
    </tr>
    </thead>
    <tbody>
    {% for row in users %}
        <tr>
            <td>{{ forloop.counter }}</td>
            <td>{{ row.name }}</td>
            <td>
                <a href="/app01/user/edit/{{ row.pk }}/">
                    <button type="button">修改</button>
                </a>
            </td>
        </tr>
    {% endfor %}
    </tbody>
</table>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

新增/修改页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form method="post" novalidate>
    {% csrf_token %}
    {% for field in form %}
        <label>{{ field.label }}</label>
        <div>
            {{ field }}
            <span style="color: red;">{{ field.errors.0 }}</span>
        </div>
    {% endfor %}

    <div>
        <input type="submit" value="保 存">
    </div>

</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

表单

from django import forms
from app01 import models


class UserInfoModelForm(forms.ModelForm):
    roles = forms.ModelMultipleChoiceField(
        queryset=models.Role.objects.all(), 
        widget=forms.CheckboxSelectMultiple
    )

    class Meta:
        model = models.UserInfo
        fields = '__all__'
1
2
3
4
5
6
7
8
9
10
11
12
13

快速添加测试数据, 脚本代码

import os
import sys
import django

base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dc_stark.settings')
django.setup()

if __name__ == '__main__':
    from apps.app01 import models

    for i in range(1, 302):
        models.UserInfo.objects.create(
            name=f'dc_{i}',
            age=10,
            email=f'18954787{i}@qq.com',
            depart_id=1,
        )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

ORM复习
Postman和restful

← ORM复习 Postman和restful→

最近更新
01
deepseek本地部署+知识库
02-17
02
实操-微信小程序
02-14
03
教学-cursor深度探讨
02-13
更多文章>
Theme by Vdoing | Copyright © 2023-2025 DC | One Piece
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式