Django Form表单控件

form组件的常用字段

Django 表单中有许多不同类型的字段,每个字段都有不同的用途。以下是一些常见的 Django 表单字段及其区别:

CharField:用于输入字符串的字段。与 TextField 一样,但有最大长度限制。

IntegerField:用于输入整数的字段。可以指定最小值和最大值。

FloatField:用于输入浮点数的字段。可以指定最小值和最大值。

DecimalField:用于输入十进制数的字段。可以指定最小值和最大值,以及小数点位数。

BooleanField:用于选择是或否的字段。

ChoiceField 和 TypedChoiceField:用于选择预定义选项的字段。区别在于 ChoiceField 将所有选项的值视为字符串,而 TypedChoiceField 可以强制将选项的值转换为指定类型。

使用 ChoiceField,并指定了三个字符串选项。在表单提交时,每个选项的值都将是字符串类型。
TypedChoiceField 类似于 ChoiceField,不同之处在于它将每个选项的值视为指定类型。这可以确保表单处理时以正确的类型处理值。

TypedChoiceField 的 coerce 参数可以是以下类型之一:
int:将选项值强制转换为整数。
float:将选项值强制转换为浮点数。
decimal.Decimal:将选项值强制转换为十进制数。
str:将选项值保留为字符串类型。
自定义函数:可以指定一个自定义函数,该函数将选项值转换为特定类型,例如 coerce=my_func,其中 my_func 是一个自定义转换函数。

class MyForm(forms.Form):
    MY_CHOICES = [
        (1, '选项 1'),
        (2, '选项 2'),
        (3, '选项 3'),
    ]

    my_field = forms.TypedChoiceField(choices=MY_CHOICES, coerce=int)

DateField 和 DateTimeField:分别用于选择日期和日期时间的字段。可以指定日期格式。

EmailField:用于输入电子邮件地址的字段。会进行基本的电子邮件格式验证。

FileField 和 ImageField:分别用于上传文件和图像的字段。可以指定其中一个或多个允许的文件类型。

MultipleChoiceField 和 ModelMultipleChoiceField:用于选择多个选项的字段。MultipleChoiceField 可以使用预定义选项,而 ModelMultipleChoiceField 可以从数据库中检索选项。

form组件的校验说明

先导包 from django import forms,创建一个类继承forms.Form,类里面的字段需要和POST上来的字段一一对应。

class Userform(forms.Form):
    user = forms.CharField()
    pwd = forms.CharField(min_length=5)
    email = forms.EmailField()
    tel = forms.CharField()

def form_test(request):
    if request.method=="POST":
        #Userform 里像xxx这些多余的字段不会管他,只会挨个按照字段对value进行校验,只要这些字段都通过认证,下面的form.is_valid 返回true,否则返回false
        form = Userform({"user":"alex","pwd":"123456","email":"aaa","xxx":"xxx"})
        print(form.is_valid())  #False
        #clean和cleaned_data会返回校验通过的字段
        print(form.clean(),type(form.clean()))  #{'user': 'alex'}
        print(form.cleaned_data,type(form.cleaned_data))  #{'user': 'alex'}
        # errors会返回校验未通过的字段,返回数据是字典{错误字段:[错误的返回值]},下面的打印字段是经过对这个字典转换成标签后的结果,所以我们可以对errors字段进行字典处理
        print(form.errors)
        # <ul class="errorlist"><li>pwd<ul class="errorlist"><li>Ensure this value has at least 5 characters (it has 3).</li></ul></li><li>email<ul class="errorlist"><li>Enter a valid email address.</li></ul></li><li>tel<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
        #这些错误的标签可以按照字段提取对应的标签,第0个元素则是标签内的文本
        print(form.errors.get("email"))  #<ul class="errorlist"><li>Enter a valid email address.</li></ul>
        print(form.errors.get("email")[0])  #Enter a valid email address.
    return render(request,"login.html")

总结:

is_valid() 布尔类型,判断所有的字段是否都符合了Form组件的规则
cleaned_data 字典类型 符合Form组件规则的字段的key和value值会放入这个属性中
errors 字典类型 不符合Form组件规则的字段的key和value值会放入这个属性中

form组件的对POST请求的数据进行校验规则,字段需要和HTML标签字段相对应。

上面的例子中我们 form = Userform({"user":"alex","pwd":"123456","email":"aaa","xxx":"xxx"}) 写死了,
我们从request.POST数据获取的数据也是个字段<QueryDict: {'user': ['ZCB-CWQ'], 'pwd': ['123441'], 'email': ['sxfxtf@sina.com'], 'tel': ['123']}>,因此只要key值对应,就可以用form组件对里面的字段进行校验.

利用form组件的实例对象,便捷的实现和前端字段进行对应,利用实例对象的字段进行循环渲染 方式1

由于字段需要一一对应,那么通过生成form的实例对象字段的渲染可以减少代码,实现字段的对应 ,

利用form实例对象的字段进行循环渲染 方式2

这种方式主要是用for循环实例对象的字段进行渲染,但是input标签前面的text文本,需要先在类中每个字段定义label属性,然后在html中渲染实例字段的label值


利用form实例对象的字段进行循环渲染 方式3 这种更简单,渲染处只要一句话 {{ form_obj.as_p }},这个语法还支持as_table,as_ul

就会把form标签按照这个固定格式用一个P标签包裹一个label和input标签循环输出, 优点是代码量少,缺点是无法定制化显示标签

form组件的错误信息渲染和正确信息的保留

前面的几种情况form表单可以对数据的正确与否做出判断,但是页面刷新后会消失.

form组件的错误信息自定义

上面错误的提示也是默认的英文,我们需要自定义错误的信息,更适合国人看
为了测试效果好,在form标签加上 novalidate 这样可以关闭浏览器校验,直接显示Form的错误提示.生产环境建议去掉这个参数.
多一层校验减轻服务器压力.
<form method="post" novalidate> </form>
from django.forms import widgets

widget=widgets.TextInput() 设置input标签type=text
widget=widgets.CheckboxInput() 设置input标签type=check
widget=widgets.PasswordInput() 设置input标签type=Password
等等很多

自定义错误信息提示(似乎在django3版本上不适用,适用django2)
error_messages={"required":"required是为空的错误提示key","invalid":"这个是格式错误提示key"}

自定义标签内的属性 比如class
widget=widgets.TextInput(attrs={"class":"form-control"}) 设置input标签的class为form-control

代码示例: user = forms.CharField(min_length=1, label="用户名",error_messages={"required":"该字段不能为空","invalid":"该字段不符合要求"},widget=widgets.TextInput(attrs={"class":"form-control"}))

form组件的局部钩子和全局钩子

form组建的局部钩子都是从是它的is_valid()开始的.

局部钩子和全局钩子(第一版)

局部钩子代码示例:

全局钩子代码示例

form组件的局部钩子和全局钩子注册页面案例

局部钩子和全局钩子(第二版)

在full_clean这个方法中,有2个内置方法 self._clean_fields和self._clean_form,前者就是局部钩子,用来对每个Form的字段进行自定义的规则过滤,后者是全局钩子,用来对Form的多个字段进行全局的规则判断,比如2次密码是否输入一致.

所以校验的总体规则是先局部校验,然后全局校验

局部钩子

1. 首先局部钩子代码如上图.先解释下self.fields.items(),就是把自定义的form类的字段和规则做一个字典的对应

比如下面这个在item中就是{user:user字段的规则,pwd:pwd的字段规则,re_pwd:re_pwd的字段规则}

class log_form(forms.Form):
    user = forms.CharField(min_length=1, label="用户名",error_messages={"required":"该字段不能为空","invalid":"该字段不符合要求"},widget=widgets.TextInput(attrs={"class":"form-control"}))
    pwd = forms.CharField(min_length=4,label="密码", widget=widgets.TextInput(attrs={"class":"form-control"}))
    re_pwd = forms.CharField(min_length=4, label="密码核对", widget=widgets.TextInput(attrs={"class":"form-control"}))

2. 接下来直接看390行代码,如果字段的规则通过了fuled.clean()方法,就还是赋值给value这个变量,然后391行,这个value值会被写入到self.cleaned_data这个字典中. 所以这个字典self.cleaned_data记录的是符合字段规则的键值.

3. 392行代码就是局部校验的钩子,检测是否有clean_开头的,字段名结尾的内置属性,

如果有,则运行他.如果通过,按照394行代码,我们需要在自定义的局部钩子里反馈这个字段传进来的值,最终还是统一返回到self.cleaned_data这个字典中去.

如果规则不通过,则报错ValidationError,统一把错误内容整合到self.errors里去

报错有2种方式

  1. 通过raise ValidationError("错误内容"),错误会整合到self.errors
  2. 通过self.add_error("错误字段","错误内容"), 错误也会整合到self.errors 这个方法可以直接将错误字段传入,在前端用form组件渲染的时候可以将对应字段错误的内容显示在对应字段标签中
                {% for user in userform %}
                    <div class="row">
                        <div class="col-sm-3 control-label">
                            <label>{{ user.label }}</label>
                        </div>
                        <div class="col-sm-8">
                            {{ user }}

                            <span>{{ user.errors.0 }}</span>  这里就可以渲染字段的时候,同时把错误信息也渲染在这个span标签
                        </div>

                    </div>
                {% endfor %}

然后通过html标签将上面的错误信息进行渲染

4. 局部钩子总结和代码示例

大致流程是

  1. form组件的字段名和字段规则作为k,v传入self.fields.items()
  2. 符合规则返回到self.cleaned_data,不符合抛出异常整合到self.errors
  3. 检测是否有clean_开头的hook属性,有则运行,符合和不符合的处理逻辑同上.
    def clean_user(self):
        print("校验用户名")
        name = self.cleaned_data.get("user") #获取user字段的内容
        mark_obj=mark_info.objects.filter(mark_name=name).first() #ORM比对是否已存在注册
        if mark_obj:
            # print("%s已存在"%mark_obj.mark_name)
            raise ValidationError("%s已存在"%mark_obj.mark_name)
        else:
            #如果通过,原封不动反馈name这个字段
            return name

全局钩子

### 1. 全局规则代码中`self.clean()`是空的,也是用来覆盖父类进行自定义的.根据421行代码规则,自定义的全局钩子在匹配规则后直接返回`self.cleaned_data`,否则也是抛出异常汇总到`self.errors`.

### 2. 而此时为了在`self.erros`凸显全局和局部的错误信息,全局错误信息的key值是固定的`__all__`,而局部错误信息的key值是该字段的名字.

### 3. 全局钩子的总结和全局的代码示例

3.1 在完成上述局部钩子的前提下,通过局部校验的字段都会在`self.cleaned_data`,而未通过局部的都在`self.errors`中.

3.2 从`self.cleaned_data`获取各个字段的值进行全局匹配.而这个全局匹配的方法名是`self.clean`用来覆盖父类

```python
    def clean(self):
        #全局校验
        pwd = self.cleaned_data.get("pwd")
        re_pwd = self.cleaned_data.get("re_pwd")
        #因为先进行局部校验,如果pwd和re_pwd为NONE,表示局部校验那一环节抛出异常了,
        # 就无须进行全局校验了
        if pwd and re_pwd:
            if pwd==re_pwd:
                print("2次密码一致")
                return self.cleaned_data
            else:
                print("两次密码输入不一致",self.errors)
                raise ValidationError("两次密码输入不一致")

局部/全局钩子和视图的代码


局部钩子带来的局部代码优化

我们从局部钩子的代码可以看到,钩子函数的返回值是被重新覆盖到self.cleaned_data中去的

所以使用form组件可以利用这个特性进行代码优化.
比如 注册功能中我们会在视图中获取post传入的账号密码,然后对账号进行新增,如果不用auth模块,则需要手动对密码进行加密后传入.

forms组件关于代码优化的思路

# 注册视图函数
def login(request):

    if request.method == "POST":
        userform = UserForm(request.POST)
        print(userform)
        if userform.is_valid():
            user = userform.cleaned_data.get("user")
            pwd = userform.cleaned_data.get("pwd")
            pwd=md5(pwd)  # 密码加密
            user_obj = models.Customer.objects.create(username=user,password=pwd)
            if user_obj:
                request.session["user"] = user_obj.username
                print("认证通过")
            else:
                userform.add_error("password","创建账号错误")
        print("全局错误信息",userform.errors.get("__all__"))
        return render(request, "login.html", {"userform": userform,"allerrors":userform.errors.get("__all__")})
    else:
        userform = UserForm()
        return render(request, "login.html", {"userform": userform})

我们完全可以在钩子函数里实现密码加密,从而简化视图代码

# form函数代码
class UserForm(forms.Form):
    acc_user = forms.CharField(label="用户名",required=True,min_length=3,
                           widget=widgets.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的用户名"}),
                           error_messages={'required': 'Please enter your name',"invalid":"用户名至少3位"},
                           validators=[RegexValidator("\w+","必须是文字")]
                           )
    acc_password = forms.CharField(label="密码", widget=widgets.PasswordInput(
        attrs={"class": "form-control", "placeholder": "form渲染的密码"}))

    def clean_acc_user(self):
        略
        return user

    def clean_acc_password(self):
        print("校验密码")
        pwd = self.cleaned_data.get("acc_password")
        if len(pwd)<5:
            raise ValidationError("密码必须大于5位","invalid")
        return md5(pwd)  #直接加密返回,覆盖self.cleaned_data



# 注册视图函数
def login(request):

    if request.method == "POST":
        userform = UserForm(request.POST)
        print(userform)
        if userform.is_valid():
            user = userform.cleaned_data.get("user")
            pwd = userform.cleaned_data.get("pwd")
            pwd=md5(pwd)  # 密码加密
            user_obj = models.Customer.objects.create(username=user,password=pwd)
            if user_obj:
                request.session["user"] = user_obj.username
                print("认证通过")
            else:
                userform.add_error("password","创建账号错误")
        print("全局错误信息",userform.errors.get("__all__"))
        return render(request, "login.html", {"userform": userform,"allerrors":userform.errors.get("__all__")})
    else:
        userform = UserForm()
        return render(request, "login.html", {"userform": userform})

与此类似的优化逻辑还有,可以在局部钩子或者全局钩子里,验证通过后,直接钩子函数中写入session等逻辑操作,简化视图函数中的代码

forms组件中的ModelForm

我们在上面使用form.Form组件时候,是需要一个个字段自己编写的,并且为了操作简便性,字段和orm的字段要保持一致.
但是如果这个表的字段有几十个,那么一个个手写效率就很低下,所以在前端和数据库有交互的场景下,建议使用ModelForm

ModelForm的优点

Form功能:
编写字段
生成HTML标签 + 插件 + 和参数的配置
表单的验证

ModelForm功能
不编写字段,直接引用Model字段【优秀】
生成HTML标签 + 插件 + 和 参数的配置
表单的验证
保存(新增、更新)

ModelForm不用手写字段,以及ModelForm如何设置前端标签样式

class LevelForm(forms.Form):
    title = forms.CharField(
        required=True,label="标签",
        widget=widgets.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的标题"})
    )
    discount = forms.IntegerField(
        required=True,label="折扣",
        widget=widgets.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的折扣"}),
        help_text="折扣,比如70%"
    )


class LevelForm(forms.ModelForm):
    class Meta:
        model = models.Level
        fields=["title","discount"] #建议用这个,还可以控制页面排版的先后顺序
        # fields="__all__"  这里为了不显示哪个active字段 就不用__all__
        # exclude = ['active']  也可以单独控制部显示某个字段
        widgets={
            "title":forms.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的level标题"}),
            "discount":forms.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的百分比"})
        }

# 如何每个字段的样式都是一样的,也可以用循环生成. 这里使用__init__是为了调用父类方法,
# 将所有标签的字段以字典形式赋值给self.fields >> {'title':对象,"discount":对象}
class LevelForm(forms.ModelForm):
    class Meta:
        model = models.Level
        fields = ['title', 'discount']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # 父类会将所有标签的信息用字典形式赋值给self.fields >> {'title':对象,"discount":对象}
        for name, field in self.fields.items():
            field.widget.attrs['class'] = "form-control"
            field.widget.attrs['placeholder'] = "请输入{}".format(field.label)

  # 和Form一样,进行局部钩子校验
  def clean_percent(self):
        value = self.cleaned_data['percent']
        return value

Form和ModelForm的如何展示已填写信息

场景1:Form的提交是会刷新页面的,那么当字段填写错误,页面刷新后,我们是希望将之前填写的数据都保存在页面中.
场景2: 编辑页面,我们也会希望

方法1: Form和ModelForm都适用,用initial参数,将页面数据当做初始数据展示

LevelForm_obj = LevelForm(initial={"title":edit_obj.title,"discount":edit_obj.discount})

方法2 : 适用ModelForm,用来展示数据库已有数据的内容
用instance参数传入orm对象, LevelForm_obj = LevelForm(instance=edit_obj)
2种方法的代码示例

# 视图函数
def level_edit(request, pk):
    """会员等级编辑页面"""
    edit_obj = models.Level.objects.filter(pk=pk).first()
    print(edit_obj.title)
    if request.method == "GET":
        print("进入编辑_get请求")
        # 方法1  LevelForm_obj = LevelForm(initial={"title":edit_obj.title,"discount":edit_obj.discount})
        LevelForm_obj = LevelForm(instance=edit_obj)
        return render(request, "level_edit.html", {"LevelForm_obj": LevelForm_obj})

ModelForm的新增和更新保存

ModelForm可以用save方法进行新增和更新保存,如果传入data和instance,会进行比较2者之间不一致的内容,实现部分更新

#新增
form = LevelModelForm(data=request.POST)
form.save()
#更新
form = LevelModelForm(data=request.POST,instance=对象)
form.save()

代码示例展示

# 新增功能
def level_add(request):
    LevelForm_obj = LevelForm()
    if request.method == "GET":
        return render(request, "level_add.html", {"LevelForm_obj": LevelForm_obj})
    
    LevelForm_obj = LevelForm(request.POST)
    if not LevelForm_obj.is_valid():
        return render(request, "level_add.html", {"LevelForm_obj": LevelForm_obj})

    # models.Level.objects.create(**LevelForm_obj.cleaned_data) #也可以使用orm方法新增
    LevelForm_obj.save()  #ModelForm可以用save进行保存
    
    print("新增成功")
    return redirect(reverse("level_list"))

# 更新保存功能
def level_edit(request, pk):
    print("pk>>", pk)
    edit_obj = models.Level.objects.filter(pk=pk).first()
    print(edit_obj.title)
    if request.method == "GET":
        print("进入编辑_get请求")
        # LevelForm_obj = LevelForm(initial={"title":edit_obj.title,"discount":edit_obj.discount})
        LevelForm_obj = LevelForm(instance=edit_obj)
        return render(request, "level_edit.html", {"LevelForm_obj": LevelForm_obj})


    if request.method == "POST":
        print("进入编辑_post请求")
        # 更新保存
        LevelForm_obj = LevelForm(data=request.POST, instance=edit_obj)
        LevelForm_obj.save()
        print("更新保存成功")
        return redirect(reverse("level_list"))

form对象新增保存对象

ModelForm是有个save方法可以把form对象中表单的内容保存到数据库去的.但是也会有部分字段不是从前端页面传过来的.
比如要在字段中保存登录用户的id 是从request.user.id获取的
需要再form.instance属性添加

from django import forms
from .models import MyModel

class MyForm(forms.ModelForm):
    class Meta:
        model = MyModel
        fields = '__all__'

def my_view(request, id):
    # 获取要编辑的模型实例
    user = request.user
  # user = models.User.objects.get(id=request.user.id)
    if request.method == 'POST':
        # 创建表单对象并设置表单数据和实例
        form = MyForm(request.POST, instance=user)
      # 上面的写法等同于
      # form = MyForm(request.POST)
      #form.instance.creator = user  #这里的creator是orm中的字段

      # 还有一种写法
      # form1 = MyForm(request.POST)
      #form1.instance.creator = user
      #form1.save(commit=False)
      #form1.creator=user
      #form1.save()
        if form.is_valid():
            # 处理表单提交
            form.save()


    return render(request, 'my_template.html', {'form': form})

关于使用了instance参数后再模版渲染的一个注意事项

场景: 我们使用ModelForm对一个编辑页面进行展示已有字段,并且部分更新.
假设更新的orm对象id为1 此时提交的post url 为 order/level/edit/1/ 对应下面的url id为1作为pk传入

我们在前端form标签的action需要实现对id为1的url进行后续更新的POST提交

注意下面的视图代码,form实例对象利用instance参数进行数据库内容的展示 LevelForm_obj = LevelForm(instance=edit_obj)

路由代码
urlpatterns = [
    path('order/level/',views.level_list,name="level_list"),
    path('order/level/add',views.level_add,name="level_add"),
    path('order/level/edit/<str:pk>/',views.level_edit,name="level_edit"),
   
]

视图代码
def level_edit(request, pk):
    edit_obj = models.Level.objects.filter(pk=pk).first()
    if request.method == "GET":
        print("进入编辑_get请求")
        # LevelForm_obj = LevelForm(initial={"title":edit_obj.title,"discount":edit_obj.discount})
        LevelForm_obj = LevelForm(instance=edit_obj)
        return render(request, "level_edit.html", {"LevelForm_obj": LevelForm_obj})

html模版代码
    <form method="post" action="{% url 'level_edit' pk=LevelForm_obj.instance.pk %}">
    {% for obj in LevelForm_obj %}
        <label for="id_{{ obj.name }}">{{ obj.label }}</label>
        {% if obj.help_text %}
            <span style="font-weight: bold;color: gray"> ({{ obj.help_text }}) </span>
        {% endif %}
        {{ obj }}
        <span>{{ obj.errors.0 }}</span>

    {% endfor %}
    <button type="submit" class="btn btn-success">保存</button>
    </form>

form组件代码
class LevelForm(forms.ModelForm):
    class Meta:
        model = models.Level
        fields=["title","discount"] #建议用这个,还可以控制页面排版的先后顺序
        widgets={
            "title":forms.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的level标题"}),
            "discount":forms.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的百分比"})
        }
        help_texts={
            "discount":"折扣,比如70%"
        }

模版代码
注意 <form method="post" action="{% url 'level_edit' pk=LevelForm_obj.instance.pk %}"> 中的 pk=LevelForm_obj.instance.pk
这里如果写成pk=LevelForm_obj.pk 页面渲染会报错 提示pk未传参.
因为modelForm是通过instance参数将orm对象传入,所以这个orm的id实际上是instance的属性获取到的,而不是LevelForm_obj.pk,LevelForm_obj只是用来渲染的form组件实例,本身是不含有orm的任何数据.都是要通过instance传入

    <form method="post" action="{% url 'level_edit' pk=LevelForm_obj.instance.pk %}">
    {% for obj in LevelForm_obj %}
        <label for="id_{{ obj.name }}">{{ obj.label }}</label>
        {% if obj.help_text %}
            <span style="font-weight: bold;color: gray"> ({{ obj.help_text }}) </span>
        {% endif %}
        {{ obj }}
        <span>{{ obj.errors.0 }}</span>

    {% endfor %}
    <button type="submit" class="btn btn-success">保存</button>
    </form>

展示效果和对应代码关联

modelform的显示内容自定义

通常如果需要对前端内容做自定义显示,可以在modelForm组件中用limit_choices_to参数进行默认的前端数据展示,可以添加多个参数,默认会对数据展示前进行过滤.

class Level(ActiveBaseModel):
    title = models.CharField(verbose_name="VIP等级", max_length=30)
    discount = models.IntegerField(verbose_name="折扣")
    # def __str__(self):
    #     return self.title

class Customer(ActiveBaseModel):
    role_Choices=((30,"BOSS"),(20,"ADMIN"),(1,"CUSTOMER"))
    username = models.CharField(max_length=20, verbose_name="用户名称")
    password = models.CharField(max_length=20, verbose_name="密码")
    mobile = models.CharField(verbose_name="手机号", max_length=11)
    balance = models.DecimalField(verbose_name="账户余额", default=0, max_digits=10, decimal_places=2)
    level =models.ForeignKey(verbose_name="会员等级",to="Level",on_delete=models.CASCADE,default=1)
    role_level = models.SmallIntegerField(choices=role_Choices,default=1,verbose_name="角色级别")
    # limit_choices_to 可以用来对关联内容做限制展示
    # level = models.ForeignKey(verbose_name="级别", to="Level", on_delete=models.CASCADE, limit_choices_to={'active': 1})
    create_date=models.DateTimeField(verbose_name="创建日期",auto_now_add=True)

前端渲染字段信息的3种方式

这里以上面的Customer和Level类做例子

  1. __str__自定义类的输出内容

    class Level(ActiveBaseModel):
        title = models.CharField(verbose_name="VIP等级", max_length=30)
        discount = models.IntegerField(verbose_name="折扣")
        def __str__(self):
            return self.title
    
  2. 前端中用Customer的实例对象.level.title进行跨表展示,就是会多查询一次数据库

    如果要提高查询效率 视图中用select_related()函数进行跨表查询 `queryset=models.Customer.object.filter(active=1).select_relateed()

    这种查询方式如果需要加过滤条件显示 那么考虑添加limit_choice_to字段进行过滤显示,可以有多个字段,并且支持orm的过滤语法

    比如id__gt=1

  3. 在使用ModelForm时,调用父类的__init__方法会将form的字段都写到self.fileds中去

    我们可以对这个字段进行重新赋值达到过滤目的

    class Customer_Add(forms.ModelForm):
        class Meta:
            model = models.Customer
            fields = ['username', 'password',"mobile","level","role_level"]
    
        def __init__(self):
            super().__init__()
            self.fields["level"].queryset = models.Level.objects.filter(active__gte=1)
    

modelForm遍历循环的时候自定义标签

我们在循环的时候可能对某个特定标签用法会不同.我们可以自定义一个父类

class BaseModelForm(forms.ModelForm):
    exclude_filed_list = []  #子类自定义

    class Meta:
        model = models.Customer
        fields = ['username', 'password', "mobile", "level", "role_level"]


    def __init__(self):
        super().__init__()
        for name, field in self.fields.items():
            if name in self.exclude_filed_list:
                continue
            field.widget.attrs['class'] = "form-control"
            field.widget.attrs['placeholder'] = "请输入{}".format(field.label)

然后我们在视图代码中可以继承这个类,使得level字段可以使用自定义的标签样式

class Customer_Add(BaseModelForm):
	# 重写这个参数,使得其在父类中可以生效
    exclude_filed_list = ["level"]

    class Meta:
        model = models.Customer
        fields = ['username', 'password', "mobile", "level", "role_level"]
        widgets={
        	# 2. 自定义level字段为radio标签
            "level":forms.RadioSelect()
            # 通过attr设置自定义样式
             #"title": forms.TextInput(attrs={"class": "form-control", "placeholder": "form渲染的level标题"}),
        }

    

modelForm覆盖orm字段

orm定义好的字段有时候在前端展示的时候会遇到部分展示的使用场景,比如下面前端只需要展示充值和扣款字段,并不需要展示创建删除等字段


class TransactionRecord(ActiveBaseModel):
    """ 交易记录 """
    charge_type_choices = ((1, "充值"), (2, "扣款"), (3, "创建订单"), (4, "删除订单"), (5, "撤单"),)
    charge_type = models.SmallIntegerField(verbose_name="类型", choices=charge_type_choices)

这里主要有2种方式,不同方式使用场景也不同

  1. 在form表单对象中重写charge_type字段
class Charge_Add_obj(forms.ModelForm):
    charge_type=forms.ChoiceField(choices=((1, "充值"), (2, "扣款")),label="新的充值类型")
  1. 还是在父类遍历时候修改charge_type字段中的choices属性
class Charge_Add_obj(forms.ModelForm):

    # charge_type=forms.ChoiceField(choices=((1, "充值"), (2, "扣款")),label="新的充值类型")

    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        self.fields["charge_type"].choices=((1, "充值"), (2, "扣款"))

2者使用场景不同,第一种是静态的,表示程序运行后charge_type 就只有2个选项,如果要添加新的选项,必须重启django.
而第二种方法页面每次渲染,都会重新运行父类的方法读取self.fields字段,就算字段有新增,也可以在不重启django程序的前提下,动态渲染展示出第三个字段.

posted @ 2021-09-15 15:11  零哭谷  阅读(269)  评论(0编辑  收藏  举报