Django入门到放弃之forms组件

1.介绍

1 注册功能,登录功能,前端需要校验(字段长度,邮箱是否合法。。。)
2 前端校验可以没有,后端校验是必须的,使用传统方式  if判断写的很多
3 借助于forms组件,可以快速实现字段的校验
	from django.forms import Form
总结一下,其实form组件的主要功能如下:
	生成页面可用的HTML标签
	对用户提交的数据进行校验
	保留上次输入内容

2.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类型

3.forms校验字段功能

### 1 写一个类,类里写要校验的字段
class MyForm(forms.Form):
    # 校验这个字段,最大长度是32,最小长度是3
    # required=Ture表示不能为空 为False表示可以为空,但是如果有值则进行校验
    name = forms.CharField(required=False, max_length=32, min_length=3,label='用户名')
    email = forms.EmailField(label='邮箱')
    age=forms.IntegerField(max_value=200,min_value=0,label='年龄')
    
    
### 2 视图函数中使用
def register(request):
    # 数据可以是从前端传过来的,也可以是自己后台的数据

    # 我现在有以下数据
    data={'name':'lqz','email':'33333@qq.com','age':900}
    # data={'email':'33333@qq.com','age':100}
    # data={'age':100}
    # 校验数据是否合法
    # 实例化得到form对象,把要校验的数据传入
    form=myforms.MyForm(data)
    # 校验数据:form.is_valid() 返回布尔类型

    if form.is_valid():
        print('校验通过')
        # 校验通过的数据
        print(form.cleaned_data) #无论是否校验通过,都可以获取 cleaned_data,但是必须在is_valid之后
    else:
        print(form.cleaned_data) # form.cleaned_data 不一定是上面传入的数据,只包含校验通的的字段
        print('校验失败')
        # 哪个字段失败了?失败的原因是什么
        print(form.errors)
        print(type(form.errors))
        from django.forms.utils import ErrorDict
        #### 重写了__str__
        print(form.errors.as_json())   #可以返回多种不同格式的数据
        print(form.errors.as_data())

        # form.errors.as_ul()  # 是为了渲染模板


    return HttpResponse('ok')

4.组件参数配置

# 定制模板中的显示样式,及配置类
# widget=widgets.PasswordInput(attrs={'class': 'form-control'})
# 错误信息中文显示
	error_messages={'min_length': '太短了小伙子'}

from django import forms
from django.forms import widgets

from django.forms import ValidationError
class Myform(forms.Form):
    name = forms.CharField(required=False, max_length=32, min_length=3, label='用户名',
                           widget=widgets.TextInput(attrs={'class': 'form-control'}),
                           error_messages={'min_length': '太短了小伙子'})
    password = forms.CharField(required=False, max_length=32, min_length=3, label='密码',
                               widget=widgets.PasswordInput(attrs={'class': 'form-control'}),
                               error_messages={'min_length': '太短了小伙子'})
    re_password = forms.CharField(required=False, max_length=32, min_length=3, label='确认密码',
                                  widget=widgets.PasswordInput(attrs={'class': 'form-control'}),
                                  error_messages={'min_length': '太短了小伙子'})
    email = forms.EmailField(label='邮箱', error_messages={'required': '小惠子,这个必填'},
                             widget=widgets.TextInput(attrs={'class': 'form-control'}))
    age = forms.IntegerField(max_value=200, min_value=0, label='年龄',
                             widget=widgets.TextInput(attrs={'class': 'form-control'}))
    text = forms.CharField(label='个人简介', widget=widgets.Textarea(attrs={'class': 'form-control'}))
    date = forms.DateField(label='日期', widget=forms.DateInput(attrs={'type':'date','class': 'form-control'}))



    def clean_name(self):
        name = self.cleaned_data.get('name')
        if name.startswith('sb'):
            raise ValidationError('不能以sb开头')
        else:
            return name

    def clean(self):
        password = self.cleaned_data.get('password')
        re_password = self.cleaned_data.get('re_password')
        if password == re_password:
            return self.cleaned_data
        else:
            raise ValidationError('两次密码不一致')



class LgoinForm(forms.Form):
    name = forms.CharField(
        required=True,
        strip=True,
        help_text='不能小于六位',
        max_length=16,
        initial='狗剩',
        min_length=6,
        label="用户名",
        error_messages={
            "max_length":"太长了",
            "min_length":"太短了了",
        },
        widget=widgets.TextInput(attrs={'class':'form-control'})
    )
    password = forms.CharField(
        max_length=16,
        min_length=6,
        label="密码",
        widget=widgets.PasswordInput(attrs={'class':'form-control'})
    )
    sex = forms.ChoiceField(
        label='性别',
        initial=3,
        choices=((1,'男'),(2,'女'),(3,'保密')),
        widget=widgets.RadioSelect()

    )
    city = forms.ChoiceField(
        label='籍贯',
        initial=1,
        choices=((1, '上海'), (2, '北京'), (3, '芜湖')),
        widget=widgets.Select()
    )

    hobby = forms.MultipleChoiceField(
        label='爱好',
        initial=[1,3],
        choices=((1, '抽烟'), (2, '喝酒'), (3, '烫头')),
        widget=widgets.CheckboxSelectMultiple,
    )

    girls = forms.MultipleChoiceField(
        label='女朋友',
        choices=((1, '红旭妹妹'), (2, '相熙哥哥'), (3, '程根姐姐')),
        widget=widgets.SelectMultiple,
    )

    status = forms.ChoiceField(
        label='remeber me',
        choices=(('True', '红旭妹妹'),('False', '程根姐姐')),
        widget=widgets.CheckboxInput
    )

    birthday = forms.CharField(
        label='生日',
        widget=widgets.TextInput(attrs={'type':'date'})
    )

5.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开头')],
    )

6.自定义验证函数

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'邮箱'}))

7.forms渲染模板功能

forms类

from django import forms


# 写一个类,写字段
class MyForm(forms.Form):
    # 校验这个字段,最大长度是32,最小长度是3
    # required=Trues时字段允许不传,如果传则必须符合校验
    name = forms.CharField(required=False, max_length=32, min_length=3,label='用户名')
    email = forms.EmailField(label='邮箱')
    age=forms.IntegerField(max_value=200,min_value=0,label='年龄')

视图函数

from app01 import myforms
def register(request):
    if request.method=='GET':
        form=myforms.MyForm()  #获取form
        return render(request,'register.html',{'form':form})
    elif request.method=='POST':
        # 数据校验
        form=myforms.MyForm(request.POST)
        if form.is_valid():
            print('校验通过,存数据库')
        else:
            print(form.errors.as_data())
            print('校验失败,返回错误')
        return HttpResponse('ok')

模板

手动创建
<h1>手动创建模板</h1>
<form action="" method="post">
    <p>用户名:<input type="text" name="name"></p>
    <p>邮箱:<input type="text" name="email"></p>
    <p>年龄:<input type="text" name="age"></p>
    <p><input type="submit" value="提交"></p>
</form>
半自动渲染模板1
<h1>半自动渲染模板1</h1>
<form action="" method="post">
    <p>用户名:{{ form.name }}</p>
    <p>邮箱:{{ form.email }}</p>
    <p>年龄:{{ form.age }}</p>
    <p><input type="submit" value="提交"></p>
</form>
半自动渲染模板2
<h1>半自动渲染模板2</h1>
<form action="" method="post">
    <p>{{ form.name.label }}--{{ form.name }}</p>
    <p>{{ form.email.label }}---{{ form.email }}</p>
    <p>{{ form.age.label }}---{{ form.age }}</p>
    <p><input type="submit" value="提交"></p>
</form>
半自动渲染模板3(使用最多)
<h1>半自动渲染模板3(用的最多)</h1>
<form action="" method="post">
    {% for foo in form %}
       <p>{{ foo.label }} :{{ foo }}</p>
    {% endfor %}

    <p><input type="submit" value="提交"></p>
</form>
全自动
<h1>全自动(了解)</h1>
<form action="" method="post">
{#    {{ form.as_ul }}#}
    {{ form.as_p }}
{#    <table>#}
{#        {{ form.as_table }}#}
{#    </table>#}

    <p><input type="submit" value="提交"></p>
</form>
渲染错误信息
1 form对象.errors 字典
2 name对象.errors

## 视图函数
def register(request):
    if request.method=='GET':
        form=myforms.MyForm()
        return render(request, 'register.html',{'form':form})
    else:
        form=myforms.MyForm(request.POST)
        if form.is_valid():
            return redirect('http://www.baidu.com')
        else:
            return render(request, 'register.html',{'form':form})
## 模板
 <form action="" method="post" novalidate>
	{% for foo in form %}
	<div class="form-group">
        <label for="">{{ foo.label }}</label>
            {{ foo }}
            # foo.errors 每一个form字段的错误
            <span class="text-danger pull-right">{{ foo.errors }}</span>
        </div>
	{% endfor %}
	<div class="text-center">
		<input type="submit" value="提交" class="btn btn-danger">
        # form.errors 所有的报错信息
        <span>{{ form.errors }}</span>  
	</div>
</form>

8.Hook钩子方法

局部钩子

# 对特定字段进行校验
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"})
    )
    ...
    # 定义局部钩子clean_fieldName,用来校验username字段,之前的校验股则还在,给你提供了一个添加一些校验功能的钩子
    def clean_username(self):
        value = self.cleaned_data.get("username")
        if "666" in value:
            raise ValidationError("光喊666是不行的")
        else:
            return value

全局钩子

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('两次密码不一致')
            
   # form.errors.get('__all__')  python中获取全局错误  

9.ModelForm

创建modelform

#首先导入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":"用户名"
        }
        
然后在url对应的视图函数中实例化这个类,把这个对象传给前端
def student(request):

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

然后前端只需要 {{ student_list.as_p }} 一下,所有的字段就都出来了,可以用as_p显示全部,也可以通过for循环
<body>
<div class="container">
    <h1>student</h1>
    <form method="POST" novalidate>
        {% csrf_token %}
        {# {{ student_list.as_p }}#}
        {% for student in student_list %}
            <div class="form-group col-md-6">
                {# 拿到数据字段的verbose_name,没有就默认显示字段名 #}
                <label class="col-md-3 control-label">{{ student.label }}</label>
                <div class="col-md-9" style="position: relative;">{{ student }}</div>
            </div>
        {% endfor %}
        <div class="col-md-2 col-md-offset-10">
            <input type="submit" value="提交" class="btn-primary">
        </div>
    </form>
</div>
</body>

添加纪录

def student(request):

    if request.method == 'GET':
         student_list = StudentList()
         return render(request,'student.html',{'student_list':student_list})
    else:
         student_list = StudentList(request.POST)
         if student_list.is_valid():
         student_list.save()
         return redirect(request,'student_list.html',{'student_list':student_list}

编辑数据

from django.shortcuts import render,HttpResponse,redirect
from django.forms import ModelForm
# Create your views here.
from app01 import models
def test(request):
    # model_form = models.Student
    model_form = models.Student.objects.all()
    return render(request,'test.html',{'model_form':model_form})

class StudentList(ModelForm):
    class Meta:
        model = models.Student #对应的Model中的类
        fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段
        exclude = None #排除的字段
        labels = None #提示信息
        help_texts = None #帮助提示信息
        widgets = None #自定义插件
        error_messages = None #自定义错误信息
        #error_messages用法:
        error_messages = {
        'name':{'required':"用户名不能为空",},
        'age':{'required':"年龄不能为空",},
        }
        #widgets用法,比如把输入用户名的input框给为Textarea
        #首先得导入模块
        from django.forms import widgets as wid #因为重名,所以起个别名
        widgets = {
        "name":wid.Textarea
        }
        #labels,自定义在前端显示的名字
        labels= {
        "name":"用户名"
        }
def student(request):
    if request.method == 'GET':
        student_list = StudentList()
        return render(request,'student.html',{'student_list':student_list})
    else:
        student_list = StudentList(request.POST)
        if student_list.is_valid():
            student_list.save()
            return render(request,'student.html',{'student_list':student_list})

def student_edit(request,pk):
    obj = models.Student.objects.filter(pk=pk).first()
    if not obj:
        return redirect('test')
    if request.method == "GET":
        student_list = StudentList(instance=obj)
        return render(request,'student_edit.html',{'student_list':student_list})
    else:
        student_list = StudentList(request.POST,instance=obj)
        if student_list.is_valid():
            student_list.save()
            return render(request,'student_edit.html',{'student_list':student_list})

10.form组件的源码分析

    def full_clean(self):
        """
        Clean all of self.data and populate self._errors and self.cleaned_data.
        """
        self._errors = ErrorDict()
        if not self.is_bound:  # Stop further processing.
            return
        self.cleaned_data = {}
        # If the form is permitted to be empty, and none of the form data has
        # changed from the initial data, short circuit any validation.
        if self.empty_permitted and not self.has_changed():
            return

        # 为什么全局钩子是在以上所有校验通过才走 
        self._clean_fields() 局部钩子
        self._clean_form()   全句钩子
        self._post_clean()
        



    def _clean_fields(self):
        for name, field in self.fields.items():
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            if field.disabled:
                value = self.get_initial_for_field(field, name)
            else:
                value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                # 字段自己的校验完成后才执行局部钩子
                if isinstance(field, FileField):
                    initial = self.get_initial_for_field(field, name)
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value
                # 为什么名字一定要叫clean_字段名,
                if hasattr(self, 'clean_%s' % name):
                    value = getattr(self, 'clean_%s' % name)() #执行局部钩子
                    # 为什么成功要把字段值返回,
                    self.cleaned_data[name] = value  # 把局部钩子的返回结果覆盖到字段执行结果中
        		# 为失败抛出ValidationError   
        except ValidationError as e:
                self.add_error(name, e)  #如果发生错误,给字段添加错误

                
    def _clean_form(self):
        try:
            cleaned_data = self.clean() 
        except ValidationError as e:
            self.add_error(None, e)
        else:
            if cleaned_data is not None:
                # 为什么在视图函数中拿到的就是返回的
                self.cleaned_data = cleaned_data  #如果全局钩子有返回则覆盖所有返回内容
				为什么全局钩子返回一个自己写的字典 ?

   

posted @ 2022-08-27 23:08  百衲本  阅读(58)  评论(0编辑  收藏  举报
cnblogs_post_body { color: black; font: 0.875em/1.5em "微软雅黑" , "PTSans" , "Arial" ,sans-serif; font-size: 15px; } cnblogs_post_body h1 { text-align:center; background: #333366; border-radius: 6px 6px 6px 6px; box-shadow: 0 0 0 1px #5F5A4B, 1px 1px 6px 1px rgba(10, 10, 0, 0.5); color: #FFFFFF; font-family: "微软雅黑" , "宋体" , "黑体" ,Arial; font-size: 23px; font-weight: bold; height: 25px; line-height: 25px; margin: 18px 0 !important; padding: 8px 0 5px 5px; text-shadow: 2px 2px 3px #222222; } cnblogs_post_body h2 { text-align:center; background: #006699; border-radius: 6px 6px 6px 6px; box-shadow: 0 0 0 1px #5F5A4B, 1px 1px 6px 1px rgba(10, 10, 0, 0.5); color: #FFFFFF; font-family: "微软雅黑" , "宋体" , "黑体" ,Arial; font-size: 20px; font-weight: bold; height: 25px; line-height: 25px; margin: 18px 0 !important; padding: 8px 0 5px 5px; text-shadow: 2px 2px 3px #222222; } cnblogs_post_body h3 { background: #2B6695; border-radius: 6px 6px 6px 6px; box-shadow: 0 0 0 1px #5F5A4B, 1px 1px 6px 1px rgba(10, 10, 0, 0.5); color: #FFFFFF; font-family: "微软雅黑" , "宋体" , "黑体" ,Arial; font-size: 18px; font-weight: bold; height: 25px; line-height: 25px; margin: 18px 0 !important; padding: 8px 0 5px 5px; text-shadow: 2px 2px 3px #222222; } 回到顶部 博客侧边栏 回到顶部 页首代码 回到顶部 页脚代码