Django Form

Form介绍 

我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。

与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.。

Django form组件就实现了上面所述的功能。

总结一下,其实form组件的主要功能如下:

  • 生成页面可用的HTML标签
  • 对用户提交的数据进行校验
  • 保留上次输入内容和渲染错误信息
pwd_err=my_form.errors.get('__all__')
from django import forms

from django.forms import widgets
from app01.models import UserInfo

from django.core.exceptions import NON_FIELD_ERRORS, ValidationError

class UserForm(forms.Form):
    name=forms.CharField(min_length=4,label="用户名",error_messages={"required":"该字段不能为空"},
                         widget=widgets.TextInput(attrs={"class":"form-control"})
                         )
    pwd=forms.CharField(min_length=4,label="密码",
                        widget=widgets.PasswordInput(attrs={"class":"form-control"})
                        )
    r_pwd=forms.CharField(min_length=4,label="确认密码",error_messages={"required":"该字段不能为空"},widget=widgets.TextInput(attrs={"class":"form-control"}))
    email=forms.EmailField(label="邮箱",error_messages={"required":"该字段不能为空","invalid":"格式错误"},widget=widgets.TextInput(attrs={"class":"form-control"}))
    tel=forms.CharField(label="手机号",widget=widgets.TextInput(attrs={"class":"form-control"}))


    def clean_name(self):

        val=self.cleaned_data.get("name")

        ret=UserInfo.objects.filter(name=val)

        if not ret:
            return val
        else:
            raise ValidationError("该用户已注册!")

    def clean_tel(self):

        val=self.cleaned_data.get("tel")

        if len(val)==11:

            return val
        else:
            raise  ValidationError("手机号格式错误")

    def clean(self):
        pwd=self.cleaned_data.get('pwd')
        r_pwd=self.cleaned_data.get('r_pwd')

        if pwd and r_pwd:
            if pwd==r_pwd:
                return self.cleaned_data
            else:
                raise ValidationError('两次密码不一致')
        else:

            return self.cleaned_data
myforms
from django.shortcuts import render,HttpResponse

# Create your views here.





from app01.myforms import *


def reg(request):

    if request.method=="POST":

        print(request.POST)

        #form=UserForm({"name":"yu","email":"123@qq.com","xxxx":"alex"})


        form=UserForm(request.POST) # form表单的name属性值应该与forms组件字段名称一致

        print(form.is_valid()) # 返回布尔值

        if form.is_valid():
            print(form.cleaned_data)  # {"name":"yuan","email":"123@qq.com"}
        else:
            print(form.cleaned_data)  # {"email":"123@qq.com"}
            # print(form.errors)        # {"name":[".........."]}
            # print(type(form.errors))  # ErrorDict
            # print(form.errors.get("name"))
            # print(type(form.errors.get("name")))    # ErrorList
            # print(form.errors.get("name")[0])


            #   全局钩子错误
            #print("error",form.errors.get("__all__")[0])
            errors=form.errors.get("__all__")


            return render(request,"reg.html",locals())

        '''

        form.is_valid()   :返回布尔值
        form.cleaned_data :{"name":"yuan","email":"123@qq.com"}
        form.errors       :{"name":[".........."]}

        '''


    form=UserForm()

    return render(request,"reg.html",locals())
视图函数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .error{
            color: red;
        }
    </style>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
    integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>

<div class="container">

    <div class="row">
        <div class="col-md-6 col-lg-offset-3">

            {#<h3>简单form</h3>#}
            {##}
            {##}
            {#<form action="" method="post" novalidate>#}
            {#    {% csrf_token %}#}
            {#    <p>用户名<input type="text" name="name"></p>#}
            {#    <p>密码 <input type="text" name="pwd"></p>#}
            {#    <p>确认密码 <input type="text" name="r_pwd"></p>#}
            {#    <p>邮箱  <input type="text" name="email"></p>#}
            {#    <p>手机号 <input type="text" name="tel"></p>#}
            {#    <input type="submit">#}
            {##}
            {#</form>#}

            <hr>
            <h3>forms组件渲染方式1</h3>
            <form action="" method="post" novalidate>

                {% csrf_token %}
                <p>{{ form.name.label }}
                    {{ form.name }} <span class="pull-right error">{{ form.name.errors.0 }}</span>
                </p>
                <p>{{ form.pwd.label }}
                    {{ form.pwd }} <span class="pull-right error">{{ form.pwd.errors.0 }}</span>
                </p>
                <p>确认密码
                    {{ form.r_pwd }} <span class="pull-right error">{{ form.r_pwd.errors.0 }}</span><span class="pull-right error">{{ errors.0 }}</span>
                </p>
                <p>邮箱 {{ form.email }} <span class="pull-right error">{{ form.email.errors.0 }}</span></p>
                <p>手机号 {{ form.tel }} <span class="pull-right error">{{ form.tel.errors.0 }}</span></p>
                <input type="submit">

            </form>

            {#<h3>forms组件渲染方式2</h3>#}
            {##}
            {#<form action="" method="post" novalidate>#}
            {#     {% csrf_token %}#}
            {##}
            {#    {% for field in form %}#}
            {##}
            {#        <div>#}
            {#            <label for="">{{ field.label }}</label>#}
            {#            {{ field }}#}
            {#        </div>#}
            {##}
            {#    {% endfor %}#}
            {##}
            {#     <input type="submit">#}
            {#</form>#}
            {##}
            {#<h3>forms组件渲染方式3</h3>#}
            {##}
            {#<form action="" method="post">#}
            {#     {% csrf_token %}#}
            {##}
            {#     {{ form.as_p }}#}
            {##}
            {#     <input type="submit">#}
            {#</form>#}


        </div>
    </div>
</div>

</body>
</html>
模版文件
class RegForm(forms.Form):
    username = forms.CharField(
        min_length=6,
        max_length=32,
        label='用户名',
        initial='初始值',
        error_messages={
            'required':'必填',
            'min_length':'最小长度',
            'invalid':''
        },
        validators=[],
        widget=forms.TextInput(attrs={''})
    )
    gender = forms.ChoiceField(choices=[(1,''),(2,'')])


    def clean_username(self):
        pass
        # 校验成功 返回该字段的值
        # 校验不成功  抛出异常

    def clean(self):
        pass
        # 校验成功 返回所有的值
        # 校验不成功  抛出异常  __all__
        # self.add_error(filed,error)
        
        
        
class RegForm(forms.ModelForm):
    password = forms.CharField(min_length=6,
                               widget=forms.PasswordInput(attrs={'placeholder': '您的密码', 'autocomplete': 'off'}))
    re_password = forms.CharField(min_length=6,
                                  widget=forms.PasswordInput(attrs={'placeholder': '您的确认密码', 'autocomplete': 'off'}))

    class Meta:
        model = models.UserProfile
        fields = '__all__'  # ['username']
        exclude = ['is_active']
        labels={
            'username':'用户名'
        }
        widgets = {
            'username': forms.EmailInput(attrs={'placeholder': '您的用户名', 'autocomplete': 'off',}),
            # 'password':forms.PasswordInput(attrs={'placeholder':'您的密码','autocomplete':'off'}),
            'mobile': forms.TextInput(attrs={'placeholder': '您的手机号', 'autocomplete': 'off'}),
            'name': forms.TextInput(attrs={'placeholder': '您的真实姓名', 'autocomplete': 'off'})
        }
        error_messages = {
            'username': {
                'required': '必填',
                'invalid': '邮箱格式不正确'
            }
        }

    def clean_username(self):
            # 通过校验规则 返回当前字段的值
            # 不通过校验规则  抛出异常 

    def clean(self):
        password = self.cleaned_data.get('password','')
        re_password = self.cleaned_data.get('re_password','')
        if password == re_password:
            # 对密码进行加密
            md5 = hashlib.md5()
            md5.update(password.encode('utf-8'))
            self.cleaned_data['password'] = md5.hexdigest()
            return self.cleaned_data
        else:
            self.add_error('re_password', '两次密码不一致')
            raise ValidationError('两次密码不一致!!')

    def __init__():


#视图:
    form_obj = RegForm()
    render(request,'reg.html',{'form_obj':form_obj})
    
    form_obj = RegForm(data=request.POST)
    form_obj.is_valid()
    form_obj.save()
    
    

#新增:
get   form_obj = CustomerForm()
post  form_obj = CustomerForm(request.POST)
        form_obj.is_valid()
        form_obj.save()
#编辑:
get   form_obj = CustomerForm(instance=obj)
post  form_obj = CustomerForm(request.POST,instance=obj)
        form_obj.is_valid()
        form_obj.save()
  


#模板:
    {{ form_obj.as_p }}
    
    {{ form_obj.username }}    ——》  # input 框
    {{ form_obj.username.label }}    ——》   # 中文提示
    {{ form_obj.username.id_for_label }}    ——》   # input 框的ID
    {{ form_obj.username.errors }}    ——》   # 一个字段所有的错误
    {{ form_obj.username.errors.0 }}    ——》  # 一个字段第一个的错误
    
    {{ form_obj.errors }}     ——》   # 表单所有的错误
    {{ form_obj.non_filed_errors }}     ——》   # __all__的错误


    {% for field in form_obj %}

    {{ field }}   {{ field.label }}   {{ field.id_for_label }}  {{ field.errors.0 }} 

    {% endfor %}

    {{ field.non_field_errors.0 }} 

常用字段与插件

创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

initial

初始值,input框里面的初始值。

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三"  # 设置默认值
    )
    pwd = forms.CharField(min_length=6, label="密码")

error_messages

重写错误信息。

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)
    )

radioSelect

单radio值为字符串

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="密码")
    gender = forms.fields.ChoiceField(
        choices=((1, ""), (2, ""), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )

单选Select

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

多选Select

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField(
        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()
    )

关于choice的注意事项:

在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。

方式一:

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)
        # self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
        #
        self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')

方式二:

from django import forms
from django.forms import fields
from django.forms import models as form_model

 
class FInfo(forms.Form):
    authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())  # 多选
    # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())  # 单选

Django 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类型
Django Form内置字段

校验

方式一:

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

方式二:

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

补充进阶

应用Bootstrap样式

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="x-ua-compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
  <title>login</title>
</head>
<body>
<div class="container">
  <div class="row">
    <form action="/login2/" method="post" novalidate class="form-horizontal">
      {% csrf_token %}
      <div class="form-group">
        <label for="{{ form_obj.username.id_for_label }}"
               class="col-md-2 control-label">{{ form_obj.username.label }}</label>
        <div class="col-md-10">
          {{ form_obj.username }}
          <span class="help-block">{{ form_obj.username.errors.0 }}</span>
        </div>
      </div>
      <div class="form-group">
        <label for="{{ form_obj.pwd.id_for_label }}" class="col-md-2 control-label">{{ form_obj.pwd.label }}</label>
        <div class="col-md-10">
          {{ form_obj.pwd }}
          <span class="help-block">{{ form_obj.pwd.errors.0 }}</span>
        </div>
      </div>
      <div class="form-group">
      <label class="col-md-2 control-label">{{ form_obj.gender.label }}</label>
        <div class="col-md-10">
          <div class="radio">
            {% for radio in form_obj.gender %}
              <label for="{{ radio.id_for_label }}">
                {{ radio.tag }}{{ radio.choice_label }}
              </label>
            {% endfor %}
          </div>
        </div>
      </div>
      <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
          <button type="submit" class="btn btn-default">注册</button>
        </div>
      </div>
    </form>
  </div>
</div>

<script src="/static/jquery-3.2.1.min.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
Django form应用Bootstrap样式简单示例

批量添加样式

可通过重写form类的init方法来实现。

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

    def __init__(self, *args, **kwargs):
        super(LoginForm, self).__init__(*args, **kwargs)
        for field in iter(self.fields):
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })
批量添加样式

ModelForm

form与model的终极结合。

class BookForm(forms.ModelForm):

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

class Meta:下常用参数:

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

Form组件

创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML 

other

form组件的主要功能如下:

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


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

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

    # password
    pwd = forms.CharField(
        min_length=6,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
    )

    # 单radio值为字符串
    gender = forms.fields.ChoiceField(
        required=False,
        choices=((1, ""), (2, ""), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )

    # 单选Select
    hobby = forms.fields.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )

    # 单选checkbox
    keep = forms.fields.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )

    # 多选checkbox
    hobby1 = forms.fields.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

    # 多选Select
    hobby2 = forms.fields.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )


"""
关于choice的注意事项:

在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段
***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。
"""

# 方式一

from crm import models
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)
        # self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
        #
        self.fields['user'].choices = models.Classes.objects.all().values_list('id', 'caption')


# 方式二:

from django import forms
from django.forms import fields
from django.forms import models as form_model


class FInfo(forms.Form):
    authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())  # 多选
    # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())  # 单选


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

from django.core.exceptions import ValidationError
import hashlib
from multiselectfield.forms.fields import MultiSelectFormField


class BSModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for filed in self.fields.values():
            if isinstance(filed, (MultiSelectFormField, forms.BooleanField)):
                continue
            if isinstance(filed.widget, forms.RadioSelect):
                continue
            filed.widget.attrs['class'] = 'form-control'


class RegForm(forms.ModelForm):
    password = forms.CharField(min_length=6,
                               widget=forms.PasswordInput(attrs={'placeholder': '您的密码', 'autocomplete': 'off'}))
    re_password = forms.CharField(min_length=6,
                                  widget=forms.PasswordInput(attrs={'placeholder': '您的确认密码', 'autocomplete': 'off'}))

    class Meta:
        model = models.UserProfile
        fields = '__all__'  # ['username']
        exclude = ['is_active']
        labels = {
            'username': '用户名'
        }
        widgets = {
            'username': forms.EmailInput(attrs={'placeholder': '您的用户名', 'autocomplete': 'off', }),
            # 'password':forms.PasswordInput(attrs={'placeholder':'您的密码','autocomplete':'off'}),
            'mobile': forms.TextInput(attrs={'placeholder': '您的手机号', 'autocomplete': 'off'}),
            'name': forms.TextInput(attrs={'placeholder': '您的真实姓名', 'autocomplete': 'off'})
        }
        error_messages = {
            'username': {
                'required': '必填',
                'invalid': '邮箱格式不正确'
            }
        }

    def clean(self):

        password = self.cleaned_data.get('password', '')
        re_password = self.cleaned_data.get('re_password', '')
        if password == re_password:
            # 对密码进行加密
            md5 = hashlib.md5()
            md5.update(password.encode('utf-8'))
            self.cleaned_data['password'] = md5.hexdigest()
            return self.cleaned_data
        else:
            self.add_error('re_password', '两次密码不一致')
            raise ValidationError('两次密码不一致!!')
View Code

一、注册实例

1、app01.models.py

from django.db import models
 
class UserInfo(models.Model):
    nid = models.BigAutoField(primary_key=True)
    name = models.CharField(verbose_name='用户名', max_length=32, unique=True)
    pwd = models.CharField(verbose_name='密码', max_length=64)
    email = models.EmailField(verbose_name='邮箱', unique=True)
    tel = models.CharField(verbose_name='手机')
    avatar = models.ImageField(verbose_name='头像')
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
View Code

2、app01.myforms.py

from django import forms
from django.forms import widgets
from app01.models import UserInfo
from django.core.exceptions import ValidationError
 
 
class RegisterForm(forms.Form):
    name = forms.CharField(
        required=True,
        min_length=6,
        max_length=32,
        label="用户名",
        error_messages={
            "required": "用户名不能为空",
            "min_length": "用户名长度不能小于6个字符",
            "max_length": "用户名长度不能大于32个字符"},
        widget=widgets.TextInput(attrs={"class": "form-control"})
    )
    pwd = forms.CharField(
        required=True,
        min_length=6,
        max_length=32,
        label="密码",
        error_messages={
            "required": "密码不能为空",
            "min_length": "密码长度不能小于6个字符",
            "max_length": "密码长度不能大于32个字符"},
        widget=widgets.PasswordInput(attrs={"class": "form-control"})
    )
    r_pwd = forms.CharField(
        required=True,
        min_length=6,
        max_length=32,
        label="确认密码",
        error_messages={
            "required": "密码不能为空",
            "min_length": "密码长度不能小于6个字符",
            "max_length": "密码长度不能大于32个字符"},
        widget=widgets.TextInput(attrs={"class": "form-control"})
    )
    email = forms.EmailField(
        label="邮箱",
        error_messages={
            "required": "邮箱不能为空",
            "invalid": "格式错误,必须是邮箱格式"
        },
        widget=widgets.TextInput(attrs={"class": "form-control"})
    )
    tel = forms.CharField(
        label="手机号",
        widget=widgets.TextInput(attrs={"class": "form-control"})
    )
 
    # 局部钩子
    def clean_name(self):
        val = self.cleaned_data.get("name")
        ret = UserInfo.objects.filter(name=val)
        if not ret:
            return val
        else:
            raise ValidationError("该用户已注册!")
 
    def clean_email(self):
        val = self.cleaned_data.get("email")
        ret = UserInfo.objects.filter(email=val)
        if not ret:
            return val
        else:
            raise ValidationError("该邮箱已注册!")
 
    def clean_tel(self):
        val = self.cleaned_data.get("tel")
        if len(val) == 11:
            return val
        else:
            raise ValidationError("手机号格式错误")
 
    # 全局钩子
    def clean(self):
        pwd = self.cleaned_data.get('pwd')
        r_pwd = self.cleaned_data.get('r_pwd')
        if pwd and r_pwd:
            if pwd == r_pwd:
                return self.cleaned_data
            else:
                # raise ValidationError('两次密码不一致') # 加在全局的错误里面 form.errors.get("__all__")
                self.add_error("r_pwd", ValidationError('两次密码不一致'))  # 加在r_pwd的错误里
        else:
            return self.cleaned_data
View Code

3、app01.views.py

from django.shortcuts import render,HttpResponse
from app01.myforms import *
 
def reg(request):
    if request.method=="POST":
        form=RegisterForm(request.POST) # form表单的name属性值应该与forms组件字段名称一致
 
        if form.is_valid(): # 验证成功做什么
            form.cleaned_data.pop('r_pwd')
            user_info = form.cleaned_data
            UserInfo.objects.create(**user_info)
            
            return redirect('/')
        else:
            return render(request,"reg.html",locals())
    form=RegisterForm()
    return render(request,"reg.html",locals())
 
'''
form=UserForm({"name":"bubu","email":"123@qq.com","xxxx":"123123123"}) :xxxx字段不会验证,只验证UserForm类里面有的字段
form.is_valid()                 :返回布尔值
form.cleaned_data               :所有干净的字段以及对应的值{"name":"bubu","email":"123@qq.com"}
form.errors                     :ErrorDict : {"校验错误的字段":["错误信息",]}
form.errors.get("name")         :ErrorList ["错误信息",]
form.errors.get("name")[0]      :取出第一个错误信息
 
全局钩子错误
forms中:
raise ValidationError('两次密码不一致') #加在全局的错误里面 form.errors.get("__all__")
self.add_error("r_pwd", ValidationError('两次密码不一致'))
views.py中:errors=form.errors.get("__all__")
模板中:<span>{{ errors.0 }}</span>
'''
app01.views.py
- 内部原理

                    def login(request):
                        if request.method == 'GET':
                            return render(request,'login.html')
                        else:
                            obj = LoginForm(request.POST)
                            # is_valid
                            """
                            1. LoginForm实例化时,
                                self.fields={
                                    'user': 正则表达式
                                    'pwd': 正则表达式
                                }
                            2. 循环self.fields
                                  flag = True
                                  errors
                                  cleaned_data
                                  for k,v in self.fields.items():
                                    # 1. user,正则表达式
                                    input_value = request.POST.get(k)
                                    正则表达式和input_value
                                    flag = False
                                  return flag
                           """
                            if obj.is_valid():
                                print(obj.cleaned_data)
                            else:
                                print(obj.errors)
                            return render(request,'login.html')
is_valid
def ajax_login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        import json
        ret = {'status': True,'msg': None}
        obj = LoginForm(request.POST)
        if obj.is_valid():
            print(obj.cleaned_data)
        else:
            # print(obj.errors) # obj.errors对象
            ret['status'] = False
            ret['msg'] = obj.errors
        v = json.dumps(ret)
        return HttpResponse(v)


<body>
    <h1>用户登录</h1>
    <form id="f1" >
        {% csrf_token %}
        <p>
            <input type="text" name="user" />
        </p>
        <p>
            <input type="password" name="pwd" />
        </p>

        <a onclick="submitForm();">提交</a>
    </form>
    <script src="/static/jquery-1.12.4.js"></script>
    <script>
        function submitForm(){
            $('.c1').remove();
            $.ajax({
                url: '/ajax_login/',
                type: 'POST',
                data: $('#f1').serialize(),// user=alex&pwd=456&csrftoen=dfdf\
                dataType:"JSON",
                success:function(arg){
                    console.log(arg);
                    if(arg.status){

                    }else{
                        $.each(arg.msg,function(index,value){
                            console.log(index,value);
                            var tag = document.createElement('span');
                            tag.innerHTML = value[0];
                            tag.className = 'c1';
                            $('#f1').find('input[name="'+ index +'"]').after(tag);
                        })
                    }
                }
            })
        }
    </script>
</body>
ajax+forms
from django.shortcuts import render,redirect
from app01 import models
from django.forms import Form
from django.forms import fields
from django.forms import widgets



class ClassForm(Form):
    title = fields.RegexField('全栈\d+') #

def class_list(request):
    cls_list = models.Classes.objects.all()
    return render(request,'class_list.html',{'cls_list':cls_list})

def add_class(request):
    if request.method == "GET":
        obj = ClassForm()
        return render(request,'add_class.html',{'obj': obj})
    else:
        obj = ClassForm(request.POST)
        if obj.is_valid():
            # obj.cleaned_data # 字典
            # 数据库创建一条数据
            # print(obj.cleaned_data)
            # models.Classes.objects.create(title=obj.cleaned_data['tt'])

            models.Classes.objects.create(**obj.cleaned_data)
            return redirect('/class_list/')
        return render(request,'add_class.html',{'obj': obj})

def edit_class(request,nid):
    if request.method == "GET":
        row = models.Classes.objects.filter(id=nid).first()
        # 让页面显示初始值
        # obj = ClassForm(data={'title': 'asdfasdfasdfas'})   # 有HTML标签,含有错误信息
        obj = ClassForm(initial={'title': row.title})         # 只有HTML标签
        return render(request,'edit_class.html',{'nid': nid,'obj':obj})
    else:
        obj = ClassForm(request.POST)
        if obj.is_valid():
            models.Classes.objects.filter(id=nid).update(**obj.cleaned_data)
            return redirect('/class_list/')
        return render(request,'edit_class.html',{'nid': nid,'obj':obj})

class StudentForm(Form):
    name = fields.CharField(
        min_length=2,
        max_length=6,
        widget=widgets.TextInput(attrs={'class': 'form-control'})
    )
    email = fields.EmailField(widget=widgets.TextInput(attrs={'class': 'form-control'}))
    age = fields.IntegerField(min_value=18,max_value=25,widget=widgets.TextInput(attrs={'class': 'form-control'}))
    cls_id = fields.IntegerField(
        # widget=widgets.Select(choices=[(1,'上海'),(2,'北京')])
        widget=widgets.Select(choices=models.Classes.objects.values_list('id','title'),attrs={'class': 'form-control'})
    )

def student_list(request):

    stu_list = models.Student.objects.all()
    return render(request,'student_list.html',{'stu_list':stu_list})

def add_student(request):
    if request.method == "GET":
        obj = StudentForm()
        return render(request,'add_student.html',{'obj':obj})
    else:
        obj = StudentForm(request.POST)
        if obj.is_valid():
            models.Student.objects.create(**obj.cleaned_data)
            return redirect('/student_list/')
        return render(request,'add_student.html',{'obj':obj})

def edit_student(request,nid):
    if request.method == "GET":
        row = models.Student.objects.filter(id=nid).values('name','email','age','cls_id').first()
        obj = StudentForm(initial=row)
        return render(request,'edit_student.html',{'nid':nid,'obj': obj})
    else:
        obj = StudentForm(request.POST)
        if obj.is_valid():
            models.Student.objects.filter(id=nid).update(**obj.cleaned_data)
            return redirect('/student_list/')
        return render(request,'edit_student.html',{'nid':nid,'obj': obj})






def teacher_list(request):
    tea_list = models.Teacher.objects.all()
    return render(request,'teacher_list.html',{'tea_list':tea_list})
from django.forms import models as form_model
class TeacherForm(Form):
    tname = fields.CharField(min_length=2)
    # xx = form_model.ModelMultipleChoiceField(queryset=models.Classes.objects.all())
    # xx = form_model.ModelChoiceField(queryset=models.Classes.objects.all())

    xx = fields.MultipleChoiceField(
        # choices=models.Classes.objects.values_list('id','title'),
        widget=widgets.SelectMultiple
    )
    def __init__(self,*args,**kwargs):
        super(TeacherForm,self).__init__(*args,**kwargs)
        self.fields['xx'].choices = models.Classes.objects.values_list('id','title')

# obj = TeacherForm()
# 1. 找到所有字段
# 2. self.fields = {
#       tname: fields.CharField(min_length=2)
# }

def add_teacher(request):
    if request.method == "GET":
        obj = TeacherForm()
        return render(request,'add_teacher.html',{'obj':obj})
    else:
        obj = TeacherForm(request.POST)
        if obj.is_valid():
            xx = obj.cleaned_data.pop('xx')
            row = models.Teacher.objects.create(**obj.cleaned_data)
            row.c2t.add(*xx) # [1,2]
            return redirect('/teacher_list/')
        return render(request,'add_teacher.html',{'obj':obj})


def edit_teacher(request,nid):
    if request.method == "GET":
        row = models.Teacher.objects.filter(id=nid).first()
        class_ids = row.c2t.values_list('id')
        # print(class_ids)
        # id_list = []
        id_list = list(zip(*class_ids))[0] if list(zip(*class_ids)) else []
        # obj = TeacherForm(initial={'tname':row.tname,'xx':[1,2,3]})
        obj = TeacherForm(initial={'tname':row.tname,'xx':id_list})
        return render(request,'edit_teacher.html',{'obj':obj})
#
# class TestForm(Form):
#     t1 = fields.CharField(
#         widget=widgets.Textarea(attrs={})
#     )
#
#
#     t2 = fields.CharField(
#         widget=widgets.CheckboxInput
#     )
#
#     t3 = fields.MultipleChoiceField(
#         choices=[(1,'篮球'),(2,'足球'),(3,'溜溜球')],
#         widget=widgets.CheckboxSelectMultiple
#     )
#
#     t4 = fields.ChoiceField(
#         choices=[(1,'篮球'),(2,'足球'),(3,'溜溜球')],
#         widget=widgets.RadioSelect
#     )

    # t5 = fields.FileField(
    #     widget=widgets.FileInput
    # )


    # def clean_t1(self):
    #     pass
from django.core.exceptions import ValidationError
class TestForm(Form):
    user = fields.CharField(validators=[])
    pwd = fields.CharField()

    def clean_user(self):
        v = self.cleaned_data['user']
        if models.Student.objects.filter(name=v).count():
            raise ValidationError('用户名已经存在')
        return self.cleaned_data['user']

    def clean_pwd(self):
        return self.cleaned_data['pwd']

    def clean(self):
        # user = self.cleaned_data.get('user')
        # email = self.cleaned_data.get('email')
        # if models.Student.objects.filter(user=user,email=email).count():
        #     raise ValidationError('用户名和邮箱联合已经存在')
        return self.cleaned_data

    # def _post_clean(self):
    #     """
    #     An internal hook for performing additional cleaning after form cleaning
    #     is complete. Used for model validation in model forms.
    #     """
    #     pass
def test(request):
    obj = TestForm(initial={'t3':[2,3]})
    obj.is_valid()
    return render(request,'test.html',{'obj':obj})
班级views.py
<body>
    <h1>班级列表</h1>
    <div>
        <a href="/add_class/">添加</a>
    </div>
    <ul>
        {% for row in cls_list %}
            <li>{{ row.title }} <a href="/edit_class/{{ row.id }}/">编辑</a>  </li>
        {% endfor %}
    </ul>
</body>

<body>
    <h1>添加班级</h1>
    <form method="POST" action="/add_class/" novalidate>
        {% csrf_token %}
        {{ obj.title }} {{ obj.errors.title.0 }}
        <input type="submit" value="提交" />
    </form>
</body>

<body>
    <h1>编辑班级</h1>
    <form method="POST" action="/edit_class/{{ nid }}/">
        {% csrf_token %}
        <p>
            {{ obj.title }} {{ obj.errors.title.0 }}
        </p>
        <input type='submit' value="提交" />
    </form>
</body>

<body>
    <h1>添加学生</h1>
    <form action="/add_student/" method="POST">
        {% csrf_token %}
        <p>
            {{ obj.name }}
        </p>
        <p>
            {{ obj.email }}
        </p>
        <p>
            {{ obj.age }}
        </p>
        <p>
            {{ obj.cls_id }}    <!--/////////////////////////-->
        </p>
        <input type="submit" value="提交" />
    </form>
</body>

<body>
    <h1>学生列表</h1>
    <a href="/add_student/">添加</a>
    <ul>
        {% for row in stu_list %}
            <li>{{ row.name }}-{{ row.email }}-{{ row.age }}-{{ row.cls_id }}-{{ row.cls.title }}
                <a href="/edit_student/{{ row.id }}/">编辑</a></li>
        {% endfor %}
    </ul>
</body>
班级模板
from django.forms import Form
from django.forms import fields
from django.forms import widgets
from django.core.exceptions import ValidationError
class RegisterForm(Form):
    username = fields.CharField(
        widget=widgets.TextInput(attrs={'class':'form-control'})
    )
    password = fields.CharField(
        widget=widgets.PasswordInput(attrs={'class':'form-control'})
    )
    password2 = fields.CharField(
        widget=widgets.PasswordInput(attrs={'class':'form-control'})
    )
    avatar = fields.FileField(widget=widgets.FileInput(attrs={'id':"imgSelect",'class':"f1"  }))
    code = fields.CharField(
        widget=widgets.TextInput(attrs={'class':'form-control'})
    )

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

    def clean_code(self):
        input_code = self.cleaned_data['code']
        session_code = self.request.session.get('code')
        if input_code.upper() == session_code.upper():
            return input_code
        raise ValidationError('验证码错误')


    # def clean_password2(self):
    #     p1 = self.cleaned_data['password']
    #     p2 = self.cleaned_data['password2']
    #     return p2

    def clean(self):
        p1 = self.cleaned_data.get('password')
        p2 = self.cleaned_data.get('password2')
        if p1 == p2:
            # return self.cleaned_data
            return None
        # self.add_error(None,ValidationError('密码不一致'))
        self.add_error("password2",ValidationError('密码不一致'))



###################################################################
from app01.forms import RegisterForm
from django.core.exceptions import NON_FIELD_ERRORS
def register(request):
    """
    用户注册
    :param request:
    :return:
    """
    if request.method == "GET":
        obj = RegisterForm(request)
        return render(request,'register.html',{'obj':obj})

    else:
        # 验证码操作
        obj = RegisterForm(request,request.POST,request.FILES)
        if obj.is_valid():
            pass
        else:
            print(obj.errors['__all__'])
            print(obj.errors[NON_FIELD_ERRORS])
            # obj.errors
            """
            {
                __all__: [错误1,错误2]
                user: [错误1,错误2]
                password: [错误1,错误2]
            }
            """
        return render(request,'register.html',{'obj':obj})


###################################################################
# 方式一:views.py中:form.errors['__all__'] 或者 form.errors[NON_FIELD_ERRORS]  模板中:{{ form.non_field_errors }}  获取 __all__的错误
# 方式二:views.py中:errors=form.errors.get("__all__")   模板中:<span>{{ errors.0 }}</span>
Form组件中通过构造方法可以封装自己想要值

4、templates.reg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
    .error{
            color: red;
    }
    .register {
    width: 400px;
    margin-top: 20px;
    margin-left: auto;
    margin-right: auto;
    border: 1px solid #f0f0f0;
    padding: 10px 30px 50px 30px;
    -webkit-box-shadow: 5px 10px 10px rgba(0, 0, 0, .05);
    box-shadow: 5px 10px 10px rgba(0, 0, 0, .05);
    }
    .register h3{font-size: 25px; text-align: center;font-weight: bold;}
    </style>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
    integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
  
<div class="register">
            <h3>用户注册</h3>
            <form action="" method="post" novalidate>
                {% csrf_token %}
                <div class="form-group">
                    <label for="id_name">{{ form.name.label }}</label>
                    {{ form.name }} <span class="pull-right error">{{ form.name.errors.0 }}</span>
                </div>
                <div class="form-group">
                    <label for="id_pwd">{{ form.pwd.label }}</label>
                    {{ form.pwd }} <span class="pull-right error">{{ form.pwd.errors.0 }}</span>
                </div>
                <div class="form-group">
                    <label for="id_r_pwd">确认密码</label>
                    {{ form.r_pwd }} <span class="pull-right error">{{ form.r_pwd.errors.0 }}</span><span class="pull-right error">{{ errors.0 }}</span>
                </div>
                <div class="form-group">
                    <label for="id_email">邮箱</label>
                    {{ form.email }} <span class="pull-right error">{{ form.email.errors.0 }}</span></div>
                <div class="form-group">
                    <label for="id_tel">手机号</label>
                    {{ form.tel }} <span class="pull-right error">{{ form.tel.errors.0 }}</span></div>
  
                <input type="submit" class="btn btn-default" value="注册"/>
            </form>
  
<!--
            <h3>forms组件渲染方式2</h3>
            <form action="" method="post" novalidate>
                 {% csrf_token %}
                {% for field in form %}
                    <div class="form-group">
                        <label for="{{ field.auto_id }}">{{ field.label }}</label>
                        {{ field }} <span class="pull-right error">{{ field.errors.0 }}</span>
                    </div>
                {% endfor %}
                 <input type="submit" class="btn btn-default" value="注册"/>
            </form>
  
            <h3>forms组件渲染方式3</h3>
            <form action="" method="post">
                 {% csrf_token %}
                 {{ form.as_p }}
                 <input type="submit" class="btn btn-default" value="注册"/>
            </form>
-->
  
</div>
  
</body>
</html>
templates.reg.html

二、常用字段与插件

from django import forms
from django.forms import widgets

from blog.models import UserInfo
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator


class TestForm(forms.Form):
    user = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",  # 初始值,input框里面的初始值。
        error_messages={  # 重写错误信息。
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    )

    pwd = forms.CharField(
        min_length=6,
        label="密码",
        widget=widgets.PasswordInput(attrs={'class': 'form-control'})
    )

    content = forms.CharField(
        widget=widgets.TextInput(attrs={'id': 'i1', 'class': 'form-control'})
    )

    # 单radio,值为字符串
    gender = forms.ChoiceField(
        # choices=((1, '男'), (2, '女'),),
        initial=2,
        widget=widgets.RadioSelect()
    )

    # 单checkbox
    keep = forms.fields.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )

    # 多选checkbox,值为列表
    hobbies1 = forms.MultipleChoiceField(
        initial=[2, ],
        choices=((1, '足球'), (2, '篮球'),),
        widget=widgets.CheckboxSelectMultiple()
    )

    # 单select,值为字符串
    city = forms.ChoiceField(
        choices=((1, '上海'), (2, '北京'),),
        initial=2,
        widget=widgets.Select()
    )

    # 多选select,值为列表
    hobbies2 = forms.MultipleChoiceField(
        choices=((1, '足球'), (2, '篮球'),),
        initial=[1, ],
        widget=widgets.SelectMultiple()
    )

    """
    #choice字段注意事项
    在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,
    方法1:那么需要自定义构造方法从而达到此目的。
    """

    def __init__(self, *args, **kwargs):
        super(TestForm, self).__init__(*args, **kwargs)
        # self.fields['city2'].choices = ((1, '上海'), (2, '北京'),)
        #
        self.fields['gender'].choices = ((1, ''), (2, ''),)
        self.fields['city'].choices = UserInfo.objects.all().value_list('id', 'caption')
        self.fields['hobbies1'].choices = UserInfo.objects.all().value_list('id', 'hobbies')
        self.fields['hobbies2'].choices = UserInfo.objects.all().value_list('id', 'hobbies')


"""
在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,
方法2:使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现
"""
class TestForm2(forms.Form):
    authors = forms.ModelMultipleChoiceField(queryset=UserInfo.objects.all())
    # authors = forms.ModelChoiceField(queryset=UserInfo.objects.all())

    # authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())  # 多选
    # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())  # 单选
常用字段与插件
1、表单的绑定属性
Form.is_bound:如果你需要区分绑定的表单和未绑定的表单,可以检查下表单的is_bound属性值 f = ContactForm({}) f.is_bound 空字典也是True
  
2、使用表单验证数据
Form.clean()如果你要自定义验证功能,那么你需要重新实现这个clean方法。
Form.is_valid()调用is_valid()方法来执行绑定表单的数据验证工作,并返回一个表示数据是否合法的布尔值。
Form.errors 表单的errors属性保存了错误信息字典
Form.errors.as_data() 返回一个字典,它将字段映射到原始的ValidationError实例。
Form.errors.as_json(escape_html=False) 返回JSON序列化后的错误信息字典。
Form.add_error(field, error) 向表单特定字段添加错误信息。 field参数为字段的名称。如果值为None,error将作为Form.non_field_errors()的一个非字段错误。
Form.has_error(field, code=None) 判断某个字段是否具有指定code的错误。当code为None时,如果字段有任何错误它都将返回True。
Form.non_field_errors() 返回Form.errors中不是与特定字段相关联的错误。
对于没有绑定数据的表单 验证没有绑定数据的表单是没有意义的。即 f = ContactForm({}) f.is_valid() 是False
  
3、检查表单数据是否被修改
Form.has_changed() 当你需要检查表单的数据是否从初始数据发生改变时,可以使用has_changed()方法。
Form.changed_data返回有变化的字段的列表。
  
4、访问表单中的字段
Form.cleaned_data:如果你的数据没有通过验证,cleaned_data字典中只包含合法的字段
通过fileds属性访问表单的字段: f.fields['name'].label = "Username"
  
5、表单的HTML生成方式
Form.as_p()
Form.as_ul()
Form.as_table()
表单API
Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    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,                      自定制正则表达式 phone = fields.RegexField('139\d+')
    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类型
    ...
内置字段
TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
内置插件
"""
方式1:RegexValidator验证器
"""
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from blog.models import UserInfo


class FInfo(forms.Form):
    user = forms.CharField(max_length=5,
                           validators=[RegexValidator(r'^[0-9]+$', '请输入数字', 'invalid'),
                                       RegexValidator(r'^159[0-9]+$', '数字必须以159开头')], )

    def clean_user(self):
        val = self.cleaned_data.get("username")
        user = UserInfo.objects.filter(username=val).first()
        if not user:
            return val
        else:
            raise ValidationError("该用户已注册!")


"""
方式2:自定义验证函数
"""
import re
from django import forms
from django.forms import widgets
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(forms.Form):
    title = forms.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 = forms.CharField(validators=[mobile_validate, ],
                            error_messages={'required': '手机不能为空'},
                            widget=widgets.TextInput(attrs={'class': "form-control",
                                                            'placeholder': '手机号码'}))

    email = forms.EmailField(required=False,
                             error_messages={'required': '邮箱不能为空', 'invalid': '邮箱格式错误'},
                             widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': '邮箱'}))
字段的两种校验

三、Hook方法(常见校验)

  • 除了上面两种方式,我们还可以在Form类中定义钩子函数,来实现自定义的验证功能。
  • 局部钩子:在Form类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。
  • 全局钩子:在Form类中定义 clean() 方法,就能够实现对字段进行全局校验。
from django import forms
from django.core.exceptions import ValidationError


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"})
    )
    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)
    )

    # 定义局部钩子,用来校验username字段
    def clean_username(self):
        value = self.cleaned_data.get("username")
        if "666" in value:
            raise ValidationError("光喊666是不行的")
        else:
            return value

    # 定义全局的钩子,用来校验密码和确认密码字段是否相同
    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', '两次密码不一致')
            raise ValidationError('两次密码不一致')

    # 批量添加样式
    def __init__(self, *args, **kwargs):
        super(LoginForm, self).__init__(*args, **kwargs)
        for field in iter(self.fields):
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })
Hook方法和批量添加样式

四、模板属性

属性说明
{{ field.label }} 字段对应的label信息
{{ field.label_tag }} 自动生成字段的label标签,注意与{{ field.label }}的区别。
{{ field.id_for_label }} 自定义字段标签的id
{{ field.value }} 当前字段的值,比如一个Email字段的值someone@example.com
{{ field.html_name }} 指定字段生成的input标签中name属性的值
{{ field.help_text }} 字段的帮助信息
{{ field.errors }} 包含错误信息的元素
{{ field.is_hidden }} 用于判断当前字段是否为隐藏的字段,如果是,返回True
{{ field.field }} 返回字段的参数列表。例如{{ char_field.field.max_length }}

Django提供了两种独立的方法,用于循环那些不可见的和可见的字段,hidden_fields()和visible_fields()

属性
{% for field in form.visible_fields %}....{% endfor %}
{% for field in form.hidden_fields %}....{% endfor %}
posted @ 2019-01-16 00:01  silencio。  阅读(282)  评论(0编辑  收藏  举报