魔力python---django---form和modelform组件,formset和modelformset组件

1.简介

form和modelform组件,formset和modelformset组件使用在校验数据方面。
考虑情景,在HTML页面中用form表单向后端提交数据时,HTML中都有让用户输入的标签并且用form标签包起来(考虑ajax异步提交是否可行)。 这时候,前端可以进行初步验证,然后后段再次进行验证。

总结form组件的功能:

  • 生成页面可用的HTML标签
  • 对用户提交的数据进行校验
  • 保留上次输入内容

小结:

  1. 重写__init__的效果:
    • 实时刷新选择字段;
    • 批量添加样式。
  2. cleaned_data 是通过校验的数据,不需要设置,浑然天成.
  3. form,modelform,formset,modelformset区别:
    • form适用于对单个表单的操作,并且需要对每个字段的验证规则自定义。
    • modelform:适用于对用户提交的单个表单操作,字段可以用model中的表的字段来作为验证规则,适用于快速的进行增加、修改。  
    • formset:适用于对多个表单进行操作,字段需要也可以用model中的表的字段来作为验证规则。  
    • modelfoemset:适用于对多个表单进行操作,字段需要也可以用model中的表的字段来作为验证规则,速度可能快一些.

2.form组件

2.1 form组件使用原理

实际上form组件仅仅是用来判断输入内容是否符合form类字段的要求,符合的通过,否则不予通过。

2.2 form组件使用步骤

  • 设置form类(无论在什么地方,建议重新建立form.py单独写),可以对字段进行设置(包括样式);
  • 写完form类后在想使用的views视图函数里进行实例化,并且把数据传到前端页面(form表单是get方法时);
  • 前端页面全靠渲染,input不用自己写(自己写的用不了),可以写label(具体详情参见实例);
  • form表单是post时,写form_obj = RegForm(data = request.POST) RegForm是设定的form类,data=可写可不写;
  • 接下来对form_obj进行判定,写form_obj.is_valid()如果是True,做某事,否则做另一件事.

2.3 form组件常用字段与插件

字段:用于对用户请求数据的验证
插件:用于自动生成HTML

2.3.1 常用字段里面的属性

max_length 最大长度
min_length 最小长度
label 设置标签名
initial 默认值
error_messages 重写错误信息
widget 设置属性和样式 举例: widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) render_value是在前端保留用户输入的数据
choices 应用在选择框
validators 用于正则判断

2.3.2 常用field字段

常用:

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    )
    pwd = forms.CharField(min_length=6, label="密码")

password:

class LoginForm(forms.Form):
    ...
    pwd = forms.CharField(
        min_length=6,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) #这个密码字段和其他字段不一样,默认在前端输入数据错误的时候,点击提交之后,默认是不保存的原来数据的,但是可以通过这个render_value=True让这个字段在前端保留用户输入的数据
    )

radioSelect 单选框

class LoginForm(forms.Form):
    username = forms.CharField(  #其他选择框或者输入框,基本都是在这个CharField的基础上通过插件来搞的
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    )
    pwd = forms.CharField(min_length=6, label="密码")
    gender = forms.fields.ChoiceField(
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )

单选select

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.ChoiceField(  #注意,单选框用的是ChoiceField,并且里面的插件是Select,不然验证的时候会报错, Select a valid choice的错误。
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )

多选select

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField( #多选框的时候用MultipleChoiceField,并且里面的插件用的是SelectMultiple,不然验证的时候会报错。
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )

单选checkbox

class LoginForm(forms.Form):
    ...
    keep = forms.fields.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )

多选checkbox

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

date类型

from django import forms
from django.forms import widgets
class BookForm(forms.Form):
    date = forms.DateField(widget=widgets.TextInput(attrs={'type':'date'}))  #必须指定type,不然不能渲染成选择时间的input框

单选checkbox示例

#单选的checkbox
    class TestForm2(forms.Form):
        keep = forms.ChoiceField(
            choices=(
                ('True',1),
                ('False',0),
            ),

            label="是否7天内自动登录",
            initial="1",
            widget=forms.widgets.CheckboxInput(), 
        )
    选中:'True'   #form只是帮我们做校验,校验选择内容的时候,就是看在没在我们的choices里面,里面有这个值,表示合法,没有就不合法
    没选中:'False'
    ---保存到数据库里面  keep:'True'
    if keep == 'True':
        session 设置有效期7天
    else:
        pass

注意
在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。
示例:(重新构造init函数即可)

from django.forms import Form
from django.forms import widgets
from django.forms import fields

 
class MyForm(Form):
 
    user = fields.ChoiceField(
        # choices=((1, '上海'), (2, '北京'),),
        initial=2,
        widget=widgets.Select
    )
 
    def __init__(self, *args, **kwargs):
        super(MyForm,self).__init__(*args, **kwargs) 

注意重写init方法的时候,*args和**kwargs一定要给人家写上,不然会出问题,并且验证总是不能通过,还不显示报错信息

    # self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
    # 或
    self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')       

更多方法参见链接:https://www.cnblogs.com/clschao/articles/10486468.html

2.3.3 全部字段

Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
validators=[], 自定义验证规则
localize=False, 是否支持本地化
disabled=False, 是否可以编辑
label_suffix=None Label内容后缀

CharField(Field)
max_length=None, 最大长度
min_length=None, 最小长度
strip=True 是否移除用户输入空白

IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值

FloatField(IntegerField)
...

DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 总长度
decimal_places=None, 小数位长度

BaseTemporalField(Field)
input_formats=None 时间格式化

DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12

DurationField(Field) 时间间隔:%d %H:%M:%S.%f
...

RegexField(CharField)
regex, 自定制正则表达式
max_length=None, 最大长度
min_length=None, 最小长度
error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}

EmailField(CharField)
...

FileField(Field)
allow_empty_file=False 是否允许空文件

ImageField(FileField)
...
注:需要PIL模块,pip3 install Pillow
以上两个字典使用时,需要注意两点:
- form表单中 enctype="multipart/form-data"
- view函数中 obj = MyForm(request.POST, request.FILES)

URLField(Field)
...

BooleanField(Field)
...

NullBooleanField(BooleanField)
...

ChoiceField(Field)
...
choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 插件,默认select插件
label=None, Label内容
initial=None, 初始值
help_text='', 帮助提示

ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查询数据库中的数据
empty_label="---------", # 默认空显示内容
to_field_name=None, # HTML中value的值对应的字段
limit_choices_to=None # ModelForm中对queryset二次筛选

ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField


TypedChoiceField(ChoiceField)
​ coerce = lambda val: val 对选中的值进行一次转换
​ empty_value= '' 空值的默认值

MultipleChoiceField(ChoiceField)
...

TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 对选中的每一个值进行一次转换
empty_value= '' 空值的默认值

ComboField(Field)
fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])

MultiValueField(Field)
PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用

SplitDateTimeField(MultiValueField)
input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']

FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
required=True,
widget=None,
label=None,
initial=None,
help_text=''

GenericIPAddressField
protocol='both', both,ipv4,ipv6支持的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用

SlugField(CharField) 数字,字母,下划线,减号(连字符)
...

UUIDField(CharField) uuid类型
复制代码

内置字段

2. 4 form组件实例

2.4.1 urls.py

"""foemtest URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.conf.urls import url, include
    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^register/', views.register,name='register'),
    url(r'^login/', views.login,name='login'),
    url(r'^main/', views.main,name='main'),
]

2.4.2 views.py

from django import forms
from django.shortcuts import render, redirect, HttpResponse


# Create your views here.

# form组件写法

class RegForm(forms.Form):
    username = forms.CharField(
        # 最大长度
        max_length=32,
        # 最小长度
        min_length=4,
        # 提示语句
        help_text='用户名长度大于4,小于32',
        # 标签名(渲染label标签)
        label='用户名',
        #设置默认值
        initial = '001';
        # 重写错误信息
        error_messages={'required': '非空'},
        # widget是确定HTML的样式
        widget=forms.widgets.TextInput(attrs={
            'class': 'form-control'
        })
    )
    # form字段的label标签些什么,前端生成input标签的时候input的name属性就是什么
    password = forms.CharField(label='密码')
    confirmpassword = forms.CharField(label='确认密码')


def register(request):
    form_obj = RegForm()
    if request.method == 'POST':
        # 实例化form对象的时候,把post提交过来的数据直接传进去
        # print(request.POST)
        # 数据格式:<QueryDict: 
        # {'csrfmiddlewaretoken': ['IDNrmeoussAtWMeTp6Im3o1mEqnZUJYDpHi54XUSLDa15ZTYhapyfbFkr1qTWNi1'], 'id_username': 
        # ['1'], 'id_password': ['2'], 'id_confirmpassword': ['3']}>
        print(request.POST)
        form_obj = RegForm(request.POST)

        # print(form_obj)
        # 由于传过来的input标签的name属性值和form类对应的字段名是一样的,所以传过来之后,form就可以取出对应的form字段名相同的数据进行form校验

        # 调用form_obj校验数据的方法
        print(form_obj.is_valid())
        # False
        if form_obj.is_valid():
            # 看传过来的值是否符合格式
            return redirect('login')
    return render(request, 'register.html', {'form_obj': form_obj})


def login(request):
    if request.method == 'GET':
        return render(request, 'base.html')
    else:
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'smithpath' and password == '123':
            return redirect('main')
        else:
            return HttpResponse('输错啦')


def main(request):
    return render(request, 'main.html')

2.4.3 register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post" novalidate autocomplete="off">
    <!--novalidate表名form表单不对输入的内容进行校验-->
    {% csrf_token %}
{#    直接写{{ form_obj.as_p }},会自动生成页面,但是没办法调整#}
    <div>
        <label for="{{ form_obj.username.id_for_label }}">
            {{ form_obj.username.label }}
        </label>
        <!--注意,是自动渲染标签,不要自己打上去-->
       {{ form_obj.username }}
    </div>
{#        <div>#}
{#        <label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label><input type="text" id="{{ form_obj.username.id_for_label }}" name="{{ form_obj.username.id_for_label }}"><span>{{ form_obj.errors.0 }}</span>#}
{#    </div>#}
    <div>
        <label for="{{ form_obj.password.id_for_label }}">
            {{ form_obj.password.label }}
        </label>
        {{ form_obj.password }}
        <span>{{ form_obj.errors.0 }}</span>
    </div>
    <div>
        <label for="{{ form_obj.confirmpassword.id_for_label }}">
            {{ form_obj.confirmpassword.label }}</label>
        {{ form_obj.confirmpassword }}
        <span>{{ form_obj.errors.0 }}</span>
    </div>
    <div>
        <input type="submit" value="注册">
    </div>
</form>
</body>
</html>

2.5 字段校验

这里主要定义form类对输入字段的校验,符合则通过.

2.5.1 RegexValidator验证器

RegexValidator验证器:利用正则匹配进行校验.

from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
 
class MyForm(Form):
    user = fields.CharField(
    validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
    )

2.5.2 自定义验证函数

自定义验证函数:自己定义验证函数,再套用到form类里面.

import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
 
 
# 自定义验证规则
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value):
        raise ValidationError('手机号码格式错误')  #自定义验证规则的时候,如果不符合你的规则,需要自己发起错误
 
 
class PublishForm(Form):
    title = fields.CharField(
            max_length=20,
            min_length=5,
            error_messages={'required': '标题不能为空','min_length': '标题最少为5个字符','max_length': '标题最多为20个字符'},
            widget=widgets.TextInput(attrs={'class': "form-control",
            'placeholder': '标题5-20个字符'}))
 
    # 使用自定义验证规则
    phone = fields.CharField(
            validators=[mobile_validate, ],
            error_messages={'required': '手机不能为空'},
            widget=widgets.TextInput(attrs={'class': "form-control",    'placeholder': u'手机号码'}))
    
    email = fields.EmailField(
            required=False,
            error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'},
            widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))

2.6 hook钩子方法

2.6.1 局部钩子

局部钩子是默认的cleaned_某字段,只针对当前定义的字段,
cleaned_data是通过验证的数据,然后如果符合这个问题就会跑异常.异常通过渲染在HTML文件中显示出来,但要确认一点的是要写上{{ form_obj.username.errors.0 }}是什么字段就写什么.

2.6.1.1 views.py

class RegForm(forms.Form):
    username = forms.CharField(
        # 最大长度
        max_length=32,
        # 最小长度
        min_length=4,
        # 提示语句
        help_text='用户名长度大于4,小于32',
        # 标签名(渲染label标签)
        label='用户名',
        # 提示报错信息
        error_messages={'required': '非空'},
        # widget是确定HTML的样式
        widget=forms.widgets.TextInput(attrs={
            'class': 'form-control'
        })
    )
    # form字段的label标签些什么,前端生成input标签的时候input的name属性就是什么
    password = forms.CharField(label='密码')
    confirmpassword = forms.CharField(label='确认密码')
    #定义局部钩子,用来校验username字段,之前的校验正则依然在,现在是添加了一个校验功能的钩子

    #格式是clean_某字段,cleaned_data是通过验证的数据
    def clean_username(self):
        value = self.cleaned_data.get('username')
        if '123' in value:
            raise ValidationError('可以可以进来了')
        else:
            return value


def register(request):
    form_obj = RegForm()
    if request.method == 'POST':
        form_obj = RegForm(request.POST)
        if form_obj.is_valid():
            return redirect('login')
    return render(request, 'register.html', {'form_obj': form_obj})

2.6.1.2 register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post" novalidate autocomplete="off">
    <!--novalidate表名form表单不对输入的内容进行校验-->
    {% csrf_token %}
{#    直接写{{ form_obj.as_p }},会自动生成页面,但是没办法调整#}
    <div>
        <label for="{{ form_obj.username.id_for_label }}">
            {{ form_obj.username.label }}
        </label>
        <!--注意,是自动渲染标签,不要自己打上去-->
       {{ form_obj.username }}{{ form_obj.username.errors.0 }}
    </div>
    <div>
        <input type="submit" value="注册">
    </div>
</form>
</body>
</html>

注意,上面的py文件和HTML文件为了方便查阅,全都是简化了的

2.6.2 全局钩子

全局钩子是clean()方法,这个可以对多个字段进行比较检验,也可以对单个字段检验,所以是对任意字段进行检验.

2.6.2.1 views.py

from django import forms
from django.shortcuts import render, redirect, HttpResponse
from django.core.exceptions import ValidationError

# Create your views here.

# form组件写法

class RegForm(forms.Form):
    username = forms.CharField(
        # 最大长度
        max_length=32,
        # 最小长度
        min_length=4,
        # 提示语句
        help_text='用户名长度大于4,小于32',
        # 标签名(渲染label标签)
        label='用户名',
        # 提示报错信息
        error_messages={'required': '非空'},
        # widget是确定HTML的样式
        widget=forms.widgets.TextInput(attrs={
            'class': 'form-control'
        })
    )
    # form字段的label标签些什么,前端生成input标签的时候input的name属性就是什么
    password = forms.CharField(label='密码',widget=forms.widgets.PasswordInput())
    confirmpassword = forms.CharField(
        label='确认密码',
        widget=forms.widgets.PasswordInput())
    #定义局部钩子,用来校验username字段,之前的校验正则依然在,现在是添加了一个校验功能的钩子
    def clean(self):
        password = self.cleaned_data.get('password')
        confirmpassword = self.cleaned_data.get('confirmpassword')
        if password == confirmpassword:
            return self.cleaned_data
        else:
            self.add_error('confirmpassword','两次密码不一致')
            return ValidationError('两次密码不一致')


def register(request):
    form_obj = RegForm()
    if request.method == 'POST':
        form_obj = RegForm(request.POST)
        if form_obj.is_valid():
            return redirect('login')
    return render(request, 'register.html', {'form_obj': form_obj})

2.6.2.2 register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post" novalidate autocomplete="off">
    <!--novalidate表名form表单不对输入的内容进行校验-->
    {% csrf_token %}
{#    直接写{{ form_obj.as_p }},会自动生成页面,但是没办法调整#}
    <div>
        <label for="{{ form_obj.username.id_for_label }}">
            {{ form_obj.username.label }}
        </label>
        <!--注意,是自动渲染标签,不要自己打上去-->
       {{ form_obj.username }}{{ form_obj.username.errors.0 }}
    </div>
{#        <div>#}
{#        <label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label><input type="text" id="{{ form_obj.username.id_for_label }}" name="{{ form_obj.username.id_for_label }}"><span>{{ form_obj.errors.0 }}</span>#}
{#    </div>#}
    <div>
        <label for="{{ form_obj.password.id_for_label }}">
            {{ form_obj.password.label }}
        </label>
        {{ form_obj.password }}
        <span>{{ form_obj.password.errors.0 }}</span>
    </div>
    <div>
        <label for="{{ form_obj.confirmpassword.id_for_label }}">
            {{ form_obj.confirmpassword.label }}</label>
        {{ form_obj.confirmpassword }}
        <span>{{ form_obj.confirmpassword.errors.0 }}</span>
    </div>
    <div>
        <input type="submit" value="注册">
    </div>
</form>
</body>
</html>

2.7 进阶补充

2.7.1 bootstrap应用

需要时看链接(讲bootstrap应用进去的)https://www.cnblogs.com/clschao/articles/10486468.html

2.7.2 批量添加样式

通过重写init方法实现

class RegForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    ...

    def __init__(self, *args, **kwargs):
        super(RegForm, self).__init__(*args, **kwargs)
        for field in iter(self.fields):
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })

3. modelform

基于form的基础上进行进一步简化.
我们在models表中已经定义了字段,没必要自己再写一个form类规定,可以直接引用models表的,基于这个想法产生了modelform.
modelform是model和form的结合,使用时会把model中的字段转换成对应的form字段,并且进行标签生成等操作.

3.1 modelform固定写法

class BookForm(forms.ModelForm):

    class Meta:
        model = models.Book
        fields = "__all__"
        labels = {
            "title": "书名",
            "price": "价格"
        }
        widgets = {
            "password": forms.widgets.PasswordInput(attrs={"class": "c1"}),

            "publishDate": forms.widgets.DateInput(attrs={"type": "date"}),
        }

3.2 class Meta下面常用参数

model = models.Book # 对应的Model中的类
fields = "all" # 字段,如果是__all__,就是表示列出所有的字段
exclude = None # 排除的字段
labels = None # 提示信息
help_texts = None # 帮助提示信息
widgets = None # 自定义插件
error_messages = None # 自定义错误信息

error_messages = {
'title':{'required':'不能为空',...} #每个字段的所有的错误都可以写,...是省略的意思,复制黏贴我代码的时候别忘了删了...
}

3.3 批量添加样式

和form一样
实例:

class BookForm(forms.ModelForm):

    r_password = forms.CharField() #想多验证一些字段可以单独拿出来写,按照form的写法,写在Meta的上面或者下面都可以
    class Meta:
        model = models.Book
        # fields = ['title','price']
        fields = "__all__" #['title,'price'] 指定字段生成form
        # exclude=['title',] #排除字段
        labels = {
            "title": "书名",
            "price": "价格"
        }
        error_messages = {
            'title':{'required':'不能为空',} #每个字段的错误都可以写
        }
    #如果models中的字段和咱们需要验证的字段对不齐的是,比如注册时,咱们需要验证密码和确认密码两个字段数据,但是后端数据库就保存一个数据就行,那么验证是两个,数据保存是一个,就可以再接着写form字段

    r_password = forms.CharField()。
    #同样的,如果想做一些特殊的验证定制,那么和form一昂,也是那两个钩子(全局和局部),写法也是form那个的写法,直接在咱们的类里面写:
    #局部钩子:
    def clean_title(self):
        pass
  #全局钩子
    def clean(self):
        pass
    def __init__(self,*args,**kwargs): #批量操作(批量添加样式)
        super().__init__(*args,**kwargs)
        #或者写super(CustomerForm,self).__init__(*args,**kwargs)
        for field in self.fields:
            #field.error_messages = {'required':'不能为空'} #批量添加错误信息,这是都一样的错误,不一样的还是要单独写。
            self.fields[field].widget.attrs.update({'class':'form-control'})

3.4 modelform验证

与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid()或者访问errors属性时会隐式调用.(调用is_valid()的时候是进行匹配的)
也可以像Form类一样自定义局部钩子方法和全局钩子来实现自定义的校验规则.
若不重写具体字段并设置validators属性的话,ModelForm是按照模型中字段的validators来校验的.(validators具体参见正则验证)

3.5 save()方法

该方法是根据表单绑定的数据创建并保存数据库对象.
ModelForm的子类可以接受现有的模型实例作为关键字参数instance;
若提供instance,则save()更新该实例;
若不提供,save()将创建模型的一个新实例.

关于save()的一点疑问:
因为我们再Meta中指定了是哪张表,所以它会自动识别,不管是外键还是多对多等,都会自行处理保存,它完成的就是上面三句话做的事情,并且还有就是如果你验证的数据比你后端数据表中的字段多,那么他自会自动剔除多余的不需要保存的字段,比如那个重复确认密码就不要保存

3.5.1 关于save()的使用

3.5.1.1 views.py

from django import forms
from django.shortcuts import render, redirect, HttpResponse
from django.core.exceptions import ValidationError

from app01 import models

# Create your views here.

# modelform组件写法
#
class CustomerForm(forms.ModelForm):

    class Meta:
        model = models.Customer
        fields = '__all__'
        labels = {
            'id':'序号',
            'goodsname':'书名',
            'price':'价格',
            'number':'购买数量'
        }
        widgets = {
            'password':forms.widgets.PasswordInput()
        }



def register(request):
    form_obj = CustomerForm()
    if request.method == 'POST':
        cust_obj = models.Customer.objects.filter(id=1)
        form_obj = CustomerForm(request.POST,instance=cust_obj)
        #instance是默认值
        if form_obj.is_valid():
            form_obj.save()
            # 因为我们再Meta中指定了是哪张表,所以它会自动识别,不管是外键还是多对多等,都会自行处理保存,它完成的就是上面三句话做的事情,并且还有就是如果你验证的数据比你后端数据表中的字段多,那么他自会自动剔除多余的不需要保存的字段,比如那个重复确认密码就不要保存
            return redirect('login')
    return render(request, 'register.html', {'form_obj': form_obj})

3.5.1.2 HTML文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post" novalidate autocomplete="off">
    {% csrf_token %}
    <div>
        <label for="{{ form_obj.username.id_for_label }}">
            {{ form_obj.goodsname.label }}
        </label>
        <!--注意,是自动渲染标签,不要自己打上去-->
       {{ form_obj.goodsname }}{{ form_obj.goodsname.errors.0 }}
    </div>
    <div>
        <input type="submit" value="注册">
    </div>
</form>
</body>
</html>

3.5.2 补充

modelform让我们避免了一个一个写form的麻烦和尴尬,前端直接就可以把字段传进去进行渲染.使python变得更加"优雅".

3.5.3 modelform实例:

3.5.3.1 form.py

#首先导入ModelForm

from django.forms import ModelForm
#在视图函数中,定义一个类,比如就叫StudentList,这个类要继承ModelForm,在这个类中再写一个原类Meta(规定写法,并注意首字母是大写的)
#在这个原类中,有以下属性(部分):

class StudentList(ModelForm):
    class Meta:
        model =Student #对应的Model中的类
        fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段
        exclude = None #排除的字段
        #error_messages用法:
        error_messages = {
        'name':{'required':"用户名不能为空",},
        'age':{'required':"年龄不能为空",},
        }
        #widgets用法,比如把输入用户名的input框给为Textarea
        #首先得导入模块
        from django.forms import widgets as wid #因为重名,所以起个别名
        widgets = {
        "name":wid.Textarea(attrs={"class":"c1"}) #还可以自定义属性
        }
        #labels,自定义在前端显示的名字
        labels= {
        "name":"用户名"
        }

3.5.3.2 urls.py

写上url分发器

3.5.3.3 views.py

def student(request):

    if request.method == 'GET':
        student_list = StudentList()
        return render(request,'student.html',{'student_list':student_list})

3.5.3.4 前端:

{{student_list.as_p}} <!--简单写法,详细的话就是for循环拿到student_list,然后慢慢拼-->

其他:

链接:https://www.cnblogs.com/clschao/articles/10486468.html

4 formset

适合对多个表单进行操作,也可用于验证.

实际上对于form类的设置formset和form相同,只不过formset使用了更加简洁的封装"工厂模式"(应该是这个),所以才能大批量操作数据. 因此有变化的地方也仅仅是视图函数.

django在不使用formset的时候若想拿到两张表的内容,写法很简单,但实际上接收数据很麻烦,多个form表单的话用formset和modelformset相对容易.

<form >
   {{ form1.as_p }}
   {{ form2.as_p }}
</form>

4.1 导包

from django.forms.formsets import formset_factory

4.2 属性

max_num 表示最大数量
extra 在原数据表示多添加几个
若max_num 为3,而extra为4,则最多显示3个,注意前者优先级高.

4.3 formset实例

4.3.1 views函数

#(非原创)
class MultiPermissionForm(forms.Form):
    id = forms.IntegerField(
        widget=forms.HiddenInput(),
        required=False
    )
    title = forms.CharField(
        widget=forms.TextInput(attrs={'class': "form-control"})
    )
    url = forms.CharField(
        widget=forms.TextInput(attrs={'class': "form-control"})
    )
    name = forms.CharField(
        widget=forms.TextInput(attrs={'class': "form-control"})
    )
    menu_id = forms.ChoiceField(
        choices=[(None, '-----')],
        widget=forms.Select(attrs={'class': "form-control"}),
        required=False,

    )

    pid_id = forms.ChoiceField(
        choices=[(None, '-----')],
        widget=forms.Select(attrs={'class': "form-control"}),
        required=False,
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['menu_id'].choices += models.Menu.objects.values_list('id', 'title')
        self.fields['pid_id'].choices += models.Permission.objects.filter(pid__isnull=True).exclude(
            menu__isnull=True).values_list('id', 'title')

    def clean_pid_id(self):
        menu = self.cleaned_data.get('menu_id')
        pid = self.cleaned_data.get('pid_id')
        if menu and pid:
            raise forms.ValidationError('菜单和根权限同时只能选择一个')
        return pid

def add(request):
    """
      增加
    :param request:
    :return:
    """
    MultiPermissionFormSet = formset_factory(MultiPermissionForm, extra=0)
    #此处extra = 0 是指不额外添加,extra会额外添加一个
    if request.method == 'GET':
        form = MultiPermissionFormSet()
        return render(request,'list.html',{'form':form}
    form =    MultiPermissionFormSet(request.POST)
    if form.is_valid():
        return redirect('url') 
    else:
         .........

def edit(request):
    """
      修改
    :param request:
    :return:
    """
    MultiPermissionFormSet = formset_factory(MultiPermissionForm, extra=0)
    if request.method == 'GET':
        form = MultiPermissionFormSet('数据')
        # formset是支持批量修改的所以 这里传入的数据一定要是可迭代对象,并且里面的 
          数据类型是字典或者对象
        return render(request,'list.html',{'form':form}
    form =    MultiPermissionFormSet(request.post)
    if form.is_valid():
        return redirect('url') 
    else:
         .........

4.3.2 HTML文件

<div class="luffy-container">
        <form method="post" action="?type=generate">
            {% csrf_token %}
            {{ form.management_form }}
    # 这里一定要记得写这一步
            <div class="panel panel-default">
                <!-- Default panel contents -->
                <div class="panel-heading">
                    <i class="fa fa-binoculars" aria-hidden="true"></i> 待新建权限列表
                    <button class="right btn btn-primary btn-xs" style="padding: 2px 8px;margin: -3px;">
                        <i class="fa fa-save" aria-hidden="true"></i>
                        新建
                    </button>
                </div>
                <div class="panel-body" style="color: #9d9d9d;">
                    注意:路由系统中自动发现且数据库中不存在的路由。
                </div>

                <table class="table table-bordered">
                    <thead>
                    <tr>
                        <th>序号</th>
                        <th>名称</th>
                        <th>URL</th>
                        <th>别名</th>
                        <th>所属菜单</th>
                        <th>根权限</th>
                    </tr>
                    </thead>
                    <tbody>
                    {% for form in generate_formset %}
    
                        <tr>
                        <td style="vertical-align: middle;">{{ forloop.counter }}</td>
                        {% for field in form %}  #  这里在渲染标签的时候一定要渲染 id这个标签  这个很重要不然会报错  这里是写了两个fou循环 所以自动渲染了id标签
                            {% if forloop.first %}
                                <td class="hide">
                                    {% else %}
                                <td>
                            {% endif %}
                        {{ field }}<span style="color: firebrick;">{{ field.errors.0 }}</span>
                        </td>
                        {% endfor %}
                    {% endfor %}
                    </tbody>
                </table>
            </div>
        </form>

5 modelformset

同formset,modelformset在定义字段时同modelform,只有视图函数中有变化.

对于modelformset,建议先创建modelform,而不是直接创建formset.
AuthorFormSet = modelformset_factory(Author, form=AuthorForm)这个就是由modelform创建formset的做法.

补充:

惰性查询---queryset是查询集,就是传到服务器上的url里面的查询内容。Django会对查询返回的结果集QuerySet进行缓存,这是为了提高查询效率。也就是说,在你创建一个QuerySet对象的时候,Django并不会立即向数据库发出查询命令,只有在你需要用到这个QuerySet的时候才会这样做。
model实例---Objects是django实现的mvc中的m,Django中的模型类都有一个objects对象,它是一个Django中定义的QuerySet类型的对象,它包含了模型对象的实例。

5.1 导包

from django.forms.models import modelformset_factory

5.2 实例

5.2.1 form类

#首先导入包
class StudyRecordModelForm(forms.ModelForm):
    class Meta:
        model = models.StudyRecord
        fields = ['student','record','score','homework_note']


 def changelist_view(self,request):
        ccid = request.GET.get('ccid')
        model_formset_cls = modelformset_factory(models.StudyRecord,StudyRecordModelForm,extra=0)
        queryset = models.StudyRecord.objects.filter(course_record_id=ccid)
        if request.method == "GET":
            formset = model_formset_cls(queryset=queryset)
            #queryset是查询集,查询符合条件的数据
            # 这里UI定是个可迭代对象,因为modelformset是操作多表的,里面的数据类型可以为字典或者对象  
            return render(request,'study_record.html',{'formset':formset})

        formset = model_formset_cls(data=request.POST)
        print(request.POST)
        if formset.is_valid():
            formset.save()
            return redirect('/stark/crm/studyrecord/list/?ccid=%s' %ccid )
        return render(request, 'study_record.html', {'formset': formset})

5.2.2 HTML文件

<div class="panel panel-default">
        <div class="panel-heading">学习记录</div>
        <div class="panel-body">
            <div style="width: 680px;margin: 0 auto;">
                <form method="post">
                    {% csrf_token %}
                    {{ formset.management_form }}
                   # 这里一定要加这句代码

                    <table class="table table-bordered">
                        <thead>
                        <tr>
                            <th>姓名</th>
                            <th>考勤</th>
                            <th>作业成绩</th>
                            <th>作业评语</th>
                        </tr>
                        </thead>
                        <tbody>
                        {% for form in formset %}
                            <tr>
                                {{ form.id }}  
                           # 这里只写了一层for循环,所以手动写字段,必须把id字段写上
                                <td>{{ form.instance.student }}</td>
                                <td>{{ form.record }} {{ form.record.errors.0 }}</td>
                                <td>{{ form.score }} {{ form.score.errors.0 }}</td>
                                <td>{{ form.homework_note }} {{ form.homework_note.errors.0 }}</td>
                            </tr>
                        {% endfor %}
                        </tbody>
                    </table>
                    <input type="submit" value="保存">
                </form>
            </div>
        </div>
    </div>

5.2.3 CBV用法(modelformset写到class里)

class StudyRecordDeialView(View):
    def get(self, request, class_record_id):
        class_record_obj = models.ClassStudyRecord.objects.get(pk=class_record_id)

        all_study_recored = models.StudentStudyRecord.objects.filter(
            classstudyrecord=class_record_obj,
        )

        form_set_obj = modelformset_factory(model=models.StudentStudyRecord,form=StudyRecordDeialModelForm,extra=0)
        formset = form_set_obj(queryset=all_study_recored)

        return render(request, 'student/study_record_detail.html',{'formset': formset})

    def post(self, request, class_record_id):
        class_record_obj = models.ClassStudyRecord.objects.get(pk=class_record_id)

        all_study_recored = models.StudentStudyRecord.objects.filter(
            classstudyrecord=class_record_obj,
        )

        form_set_obj = modelformset_factory(model=models.StudentStudyRecord, form=StudyRecordDeialModelForm, extra=0)

        formset = form_set_obj(request.POST)

        if formset.is_valid():
            formset.save()
        else:
            print(formset.errors)

        return redirect(reverse('study_decord',args=(class_record_id,)))
posted on 2019-06-21 16:33  流云封心  阅读(109)  评论(0)    收藏  举报