form组

一.Form简介

1.form组件的主要功能

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

2.页面显示的步骤

1.views.py中导入forms模块:from django import froms
2.定义继承自forms.Form的类,类中定义字段(和models中的写法相同)
3.视图函数中创建该类的对象
4.如果是get请求,向页面中渲染form对象。

3.简单使用form组件实现的例子

class Regform(forms.Form):  # 查看所有字段(forms->fields->查看__all__)
    """
    CharField对应input标签,users、pwd是input标签中的name的值,label对应label标签,widget的值对应input标签的类型
    ChoiceField对应select标签,choices中的选项是option的值
    """
    users = forms.CharField(label="用户名", widget=forms.TextInput)
    pwd = forms.CharField(label="密码", widget=forms.PasswordInput)
    choice = forms.ChoiceField(label="性别", choices=((1, "alex"), (2, "egon")))

def form_test(req):
    form_obj = Regform()  # 实例化Regform对象
    if req.method == "POST":
        form_obj = Regform(data=req.POST)
        if form_obj.is_valid():  # is_valid()是遍历所有提交过来的字段的值是否合法
            if req.POST.get("password") != req.POST.get("re_password"):
                return render(req, "form_test.html", {"form_obj": form_obj})  # 此处保留在前端界面上的数据
            else:
                return redirect("/index/")
    return render(req, "form_test.html", {"form_obj": form_obj})  # 将form_obj渲染给前端界面
views.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/form_test/" method="post">
    {% csrf_token %}
    {{ form_obj.as_p }} #生成所有标签,将生成的标签用p标签包起来
    #{{ form_obj.as_table }} #用table标签包裹
    #{{ form_obj.as_ul }} #用ul表标签包裹
    <p>
        <label for="{{ form_obj.users.id_for_label }}">{{ form_obj.users.label }}</label>:
        {{ form_obj.users }}{{ form_obj.users.errors }} #errors是这个字段的所有出现的错误,errors.0是出现的错误的第一条
    </p>
    <p>
        <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label>
        {{ form_obj.pwd }}{{ form_obj.pwd.errors }}
    </p>
    <p>
        <label for="{{ form_obj.choice.id_for_label }}">{{ form_obj.choice.label }}</label>
        {{ form_obj.choice }}{{ form_obj.choice }}
    </p>
    <input type="submit">
</form>
</body>
</html>
form_test.html
# print(status)
# print(obj.errors.as_json) #拿到错误信息

#平常使用
obj=FormData(req.POST)
        if obj.is_valid():  #is_valid() for循环遍历FormData中所有字段,如果所有字段都合法,返回true,如果有一个不合法,返回false
            print(obj.clean())  #输出拿到的正确信息
        else:
            print(obj.errors.as_json)#如果验证错误输出错误信息
            u=obj.errors['username'][0] #拿到错误值(含标签)
            #u=obj.errors['username'] #拿到错误的文本值
            e=obj.errors["email"][0]
views.py简单使用

二.Form常用字段和插件

initial 

  初始值,input框的初始值

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",  
)

error_messages

  重写错误信息

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={ 
            "required":"不能为空",    #验证前端中username字段是否为空,为空再提示
            "invalid":"格式错误",       #格式错误,正则校验错误
            "min_length":"用户名最短8位"
        }
    )

 password

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

radioSelect

class LoginForm(forms.Form):
    gender = forms.ChoiceField(
        choices=((1,"男"),(2,"女")),
        label="性别",
        initial=2,
        widget=forms.RadioSelect()
    )

单选Select

class LoginForm(forms.Form):
    hobby = forms.ChoiceField(
        choices=((1,"篮球"),(2,"足球"),(3,"双色球")),
        label="爱好",
        initial=3,
        widget=forms.Select()
    )

多选Select

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

单选checkbox

class LoginForm(forms.Form):
    keep = forms.ChoiceField(
        choices=(("True",1),("False",0)),
        label="是否7天内自动登录",
        initial="1",   #????
        widget=forms.CheckboxInput,
    )

多选checkbox

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

date类型

class LoginForm(forms.Form):
    date = forms.DateField(
        widget=forms.TextInput(attrs={"type":"date"})  #必须指定type
    )

emailField

email = forms.EmailField(label="邮箱",     #EmailField
                            widget=forms.EmailInput(attrs={"name":"email","placeholder":"邮箱"}),  #EmailInput
                             error_messages={
                                 "required":"不能为空",  
                                 "invalid": "格式错误",    #验证格式
                             })

choice字段注意事项

  通过重构方法对choice进行实时更新

class LoginForm(forms.Form):
    city = forms.ChoiceField(
        initial=2,
        widget=forms.Select
    )
    def __init__(self,*args,**kwargs):
        super(LoginForm,self).__init__(*args,**kwargs)   #参数必须写
        self.fields['city'].choices=models.City.objects.all().values_list("id","name")  #values_list返回[(),()]类型

通过init给字段设置属性值

#通过init方法给所有字段设置属性
def __init__(self,*args,**kwargs):
    super().__init__(*args,**kwargs)
    print(self.fields)
    for filed in self.fields.values():
        #设置单个属性值
        self.fields["password"].widget.attrs={"class":"form-control"} 
        #设置多个属性值
        filed.widget.attrs.update({"class":"form-control"}) 
        # filed.widget.attrs={"class":"form-control"}

三.Form中内置字段

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类型
复制代码
View Code

四.字段校验

1.RegexValidator验证器

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

2.自定义验证函数

def mobile_validate(value): #phone字段用此验证,就将phone字段得到的值传参给value
    if "alex" in value:
        raise ValidationError("错误")  #必须自己指定错误
class Regexsform(forms.Form):
    phone = forms.CharField(validators=[mobile_validate,],
                            label="ppp",
                            error_messages={
                                'required':"手机号不能为空"
                            },
                            widget=forms.TextInput(attrs={
                            "class":"form-control",
                            "placeholder":u"手机号码"
                            })
    )

五.Hook钩子方法

在Form中定义钩子方法,也是实现自定义的验证功能

执行顺序:自己先前定义的的校验-->局部钩子-->全局钩子

1.局部钩子

Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        },
        widget=forms.widgets.TextInput(attrs={"class": "form-control"})
    )
    ...
    # 定义局部钩子,用来校验username字段,之前的校验股则还在,给你提供了一个添加一些校验功能的钩子
    def clean_username(self):
        value = self.cleaned_data.get("username")  #cleaned_data是上面所有通过验证的字段
        if "666" in value:
            raise ValidationError("光喊666是不行的")
        else:
            return value

2.全局钩子

我们在Fom类中定义 clean() 方法,就能够实现对字段进行全局校验,字段全部验证完,局部钩子也全部执行完之后,执行这个全局钩子校验.

class LoginForm(forms.Form):
    ...
    password = forms.CharField(
        min_length=6,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )
    re_password = forms.CharField(
        min_length=6,
        label="确认密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )
    ...
    # 定义全局的钩子,用来校验密码和确认密码字段是否相同,执行全局钩子的时候,cleaned_data里面肯定是有了通过前面验证的所有数据
    def clean(self):
        password_value = self.cleaned_data.get('password')
        re_password_value = self.cleaned_data.get('re_password')
        if password_value == re_password_value:
            return self.cleaned_data #全局钩子要返回所有的数据
        else:
            self.add_error('re_password', '两次密码不一致') #在re_password这个字段的错误列表中加上一个错误,并且clean_data里面会自动清除这个re_password的值,所以打印clean_data的时候会看不到它
            raise ValidationError('两次密码不一致')

六.ModelForm

1.ModelForm认识

这个组件的功能就是把model和form组合起来,假如我们有个model模型Student类,想通过前端处理一些字段的验证,有3种方式:

1.自己手写前端界面中的标签,然后自己写一些验证

2.通过Form组件(需要将要验证的字段重写)

3.以上两种方式都跟model模型没有耦合性,都需要自己一个个配对,现在通过ModelForm可以实现耦合,取出model模型中的任意字段进行验证,这样不用再像form组件那样再重写字段

①首先在视图函数中导入ModelForm

from django.forms import ModelForm

  

②定义一个类,就叫StudentList,继承自ModelForm

class StudentList(ModelForm):
    class Meta:                 # 想要验证的字段都写在Meta类中
        model = models.Student  # 对应model中的Student类
        fields = "__all__"      # 字段,如果是all,就列出所有字段
        exclude = None          # 排除的字段 exclude = ["name","age"]
        help_texts = None       # 帮助信息 help_texts = ["name","age"]
        from django.forms import widgets as wig  # 因为重名,起个别名
        widgets = {  # 设置字段的类型
            "name": wig.Textarea(attrs={"class": "c1"})
        }
        error_messages = {
            "name": {"required": "用户名不能为空"},
            "age": {"required": "年龄不能为空"},
        }
        labels = {"name": "用户名", "age": "年龄"}  # 自定义在前端显示的名字

③在url对应的视图函数中实例化此类,并将对象传给前端

def modelfrom(req):
    if req.method == "GET":
        student_list = StudentList()
        return render(req,"studentlist.html",{"student_list":student_list})
    else:
        student_list = StudentList(req.POST)
        if student_list.is_valid():
            student_list.save()    #提交post请求时,只需要.save()就能保存所有提交的数据
        return render(req,"studentlist.html",{"student_list":student_list})

④前端只需要{{student_list.as_p}},所有的字段就能显示,或者for循环遍历student_list,写法和form表单中略有不同,详见下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/modelform/" method="post" novalidate>
    {% csrf_token %}
{#    {{ student_list.as_p }}#}

    {% for i in student_list %}
        <label for="{{ i.id_for_label }}">{{ i.label }}</label>
        {{ i }}
    {% endfor %}
    <input type="submit">
</form>
</body>
</html>

2.编辑(添加)数据

只需要加一个instance=obj(obj是model模型中的类实例化的一条对象),前端界面和上④中相同即可

obj = models.Student.objects.filter(pk=1)[0]
if req.method == "GET":
    student_list = StudentList(instance=obj)  #此处obj为要显示在前端的数据
    return render(req,"student_edit.html",{"student_list":student_list})
else:
    student_list = StudentList(req.POST,instance=obj) 
    if student_list.is_valid():
        student_list.save() #不加instance为添加记录,如果加上为更新obj的内容
    return redirect("/modelform_edit/")

3.其他

外键相关(在model模型中有外键Foreignkey,modelform会生成其对应的所有字段,想解决此种方式,就要修改queryset)

model模型
def __init__(self,req,*args,**kwargs):
    super().__init__(*args,**kwargs)
    for ids,filed in self.fields.items():
        if ids == "consultant":
            filed.queryset = models.UserInfo.objects.filter(username=req.username) #设置字段名为consultant的外键关联数据
        filed.widget.attrs.update({"class": "form-control"})

MultiSelectField

from multiselectfield import MultiSelectField  #要使用需安装django-multiselectfield模块
class Customer(models.Model):
    """客户表"""
    course = MultiSelectField("咨询课程",choices=course_choices) #多选,modelform生成多选框,如下图

 此处如果给控件添加bootstrap样式,生成如下混乱状态:

 解决方式

def __init__(self,*args,**kwargs):
     super().__init__(*args,**kwargs)
     for filed in self.fields.values():
        print(type(filed))
        if not isinstance(filed,MultiSelectFormField):
            filed.widget.attrs.update({"class": "form-control"})

七.modelformset_factory

工厂模式用来生成如下效果(主要用来批量编辑使用):

views视图函数

from app01 import models
class StudyForms(ModelForm):
class Meta:
model = models.StudyRecord
fields = "__all__"
 
 
from django.forms.models import modelformset_factory
class StudyRecordView(views.View):
    def get(self,req):  #get请求,用来将后台数据显示到前端界面,如上图
        pk = req.GET.get("pk")
        formset_obj = modelformset_factory(models.StudyRecord,myforms.StudyForms,extra=0)  #models模型中的StudyRecord类,自定义的modelform对象
        #formset_obj = formset_obj(queryset=models.StudyRecord.objects.filter(course_record__id=pk))  #设置前端显示的数据,不写的话显示所有
        return render(req,"course_record/studyrecord_list.html",{"formset_obj":formset_obj})
    def post(self,req): #post请求,用来接受前端发来的数据
        print(req.get_full_path())
        formset_obj = modelformset_factory(models.StudyRecord,myforms.StudyForms,extra=0)
        formset_obj = formset_obj(req.POST)
        if formset_obj.is_valid():  
            formset_obj.save()   #更新数据库
            return redirect(req.get_full_path())
        else:
            return render(req,"course_record/studyrecord_list.html",{"formset_obj":formset_obj})

html界面

<tbody>
                                    {% for f_obj in formset_obj %}
                                        <tr role="row" class="odd">
                                        {{ f_obj.id }}
                                            <td>
                                                <input type="checkbox" name="cids" value={{ f_obj.pk }}>
                                            </td>
                                            <td>{{ forloop.counter }}</td>
                                            <td>{{ f_obj.attendance }}</td>
                                            <td>{{ f_obj.score }}</td>
                                            <td>{{ f_obj.homework_note }}</td>
                                            <td class="hidden">{{ f_obj.course_record }}</td>   #post请求时,会将所有f_obj的数据发送到后端,除了f_obj.instance..xx,此处用来在前端界面显示
                                            <td>{{ f_obj.instance.course_record }}</td>
                                            <td class="hidden">{{ f_obj.student.name }}</td>
                                            <td>{{ f_obj.instance.student }}</td>
                                        </tr>
                                    {% endfor %}
                                    </tbody>
 

猜你喜欢

转载自www.cnblogs.com/ly0123/p/11938652.html