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

day67

一丶FORM的介绍

1.生成页面可用的HTML标签

2.对用户提交的数据进行校验

3.保留上次输入内容

二丶使用form组件实现注册功能

from django import forms  # 导入forms组件

# 按照Django form组件的要求自己写一个类
class RegForm(forms.Form):  # 继承Form
   name = forms.CharField(label="用户名")  # 在HTML就会生成一个文本框
   pwd = forms.CharField(label="密码")  
# 使用form组件实现注册方式
def register2(request):
   form_obj = RegForm() # 实例化一个自定义form组件类的对象
   if request.method == "POST":
       # 实例化form对象的时候,把post提交过来的数据直接传进去
       form_obj = RegForm(request.POST) # 自动html页面的数据以k:v形式传递给Form对象中
       # 调用form_obj校验数据的方法
       if form_obj.is_valid():  # is_valid() 是校验方法.错误信息是字典类型.有错误就是False
           return HttpResponse("注册成功")
   return render(request, "register2.html", {"form_obj": form_obj}) # 将form对象传递给前端
<body>
   <form action="/reg2/" method="post" novalidate autocomplete="off"> <!-- -->
      {% csrf_token %}     <!--生成 CSRFTOKEN -->
       
       <!--方式一: 通过传递的form对象, 获得每个属性. -->
      {{ form_obj.name.label }}   # name对象的lable属性
      {{ form_obj.name }}   # name对象
      {{ form_obj.name.errors.0 }} # name对象的错误信息. errors是一个错误列表.取第一个错误即可
       <div>
           <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label>
          {{ form_obj.name }} {{ form_obj.name.errors.0 }}
       </div>
       <div>
           <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label>
          {{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }}
       </div>
       
       
       <!-- 方式二 -->
      {{form_obj.as_p}} # 展示所有的字段
       <p>
          {{form_obj.as_p}}
       </p>
       <div>
           <input type="submit" class="btn btn-success" value="注册">
       </div>
   </form>
</body>

总结:

{{ form_obj.as_p }}    # 展示所有的字段

{{ form_obj.user }}   # input框
{{ form_obj.user.label }}   # label标签的中文提示
{{ form_obj.user.id_for_label }} # input框的id
{{ form_obj.user.errors  }}   # 一个字段的错误信息
{{ form_obj.user.errors.0  }}   # 一个字段的第一个错误信息
{{ form_obj.errors  }}   # 所有字段的错误

三丶FORM组件

常用字段与插件

###  initial  默认值
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": "不能为空",
           "invalid": "格式错误",
           "min_length": "用户名最短8位"
      }
  )

   
   
### password 修改input文本框的类型为密码类型
# attrs 设置属性(可以添加bootstrap样式)
   # render_value
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):
   gender = forms.fields.ChoiceField(
       choices=((1, "男"), (2, "女"), (3, "保密")),  # 在python中是 可供选择.存数据库的值为数字,显示在页面上的是 男/女
       label="性别",
       initial=3,
       widget=forms.widgets.RadioSelect()  # 设置这个input框的类型是 radio
  )    
   
   
   
### 单选Select
class LoginForm(forms.Form):
   hobby = forms.fields.ChoiceField(
       choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
       label="爱好",
       initial=3,
       widget=forms.widgets.Select() # 设置显示的标签 select标签
  )
 


### 多选Select
class LoginForm(forms.Form):
   hobby = forms.fields.MultipleChoiceField(
       choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
       label="爱好",
       initial=[1, 3],
       widget=forms.widgets.SelectMultiple() # 设置 select标签的属性 multiple多选
  )
   
   

### 单选checkbox
class LoginForm(forms.Form):
   keep = forms.fields.ChoiceField(
       label="是否记住密码",
       initial="checked",
       widget=forms.widgets.CheckboxInput()  #input的类型是 CheckBox单选框
  )
   
   
   
### 多选checkbox
class LoginForm(forms.Form):
   hobby = forms.fields.MultipleChoiceField(
       choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
       label="爱好",
       initial=[1, 3],
       widget=forms.widgets.CheckboxSelectMultiple() # input类型是 CheckBox 属性时multiple
  )

多选框的传值

def form(request):

   obj=TestForm()

   if request.method=='POST':
       obj=TestForm(request.POST)
       print(obj,type(obj))  # <class 'app01.views.TestForm'> 得到一个TestForm 对象
       print(obj.data,type(obj.data)) # <QueryDict: 'hobby2': ['1', '3'], 'hobby3': ['1', .. 拿到所有的键值对 前端页面提交的 数据.

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

 

关于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') # 重写init方法 使用values_list对数据分组
       
#### 方式二
from django import forms
from django.forms import fields
from django.forms import models as form_model

class FInfo(forms.Form):
    # 多选   设置queryset的属性 查询数据库
   authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())
   
    # 单选 设置queryset的属性 查询数据库
   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内置字段

校验

### validators 校验器校验
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开头')],
    )
### 自定义检验规则
	# 通过校验   不做任何操作
	# 不通过校验  抛出ValidationError异常 
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('手机号码格式错误')  # 必须抛出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'手机号码'})
                            )
    

 

form校验的流程

# 1. is_valid()中要执行full_clean():
   1. self._errors ={}    定义一个存放错误信息的字典   
   2. self.cleaned_data = {}   #  定义一个存放有效的数据

# 2. 执行self._clean_fields()
   1. 先执行内置的校验和校验器的校验
   2. 有局部钩子,执行局部钩子

# 3. 执行 self.clean() 全局钩子

局部钩子和全局钩子

def clean_gender(self):  # 必须是clean_XXX某字段
    v = self.cleaned_data.get('gender')
    # 局部钩子
    # 不通过校验 抛出异常
    if len(v)>10:
        raise ValidationError('长度不能小于10')  # 抛出异常
    # 通过校验   必须返回当前字段的值
    return v

def clean(self):
    # 全局钩子
    # 不通过校验 抛出异常
    # 通过校验   必须返回所有字段的值 self.cleaned_data
    pwd = self.cleaned_data.get('pwd')
    re_pwd = self.cleaned_data.get('re_pwd')
    if pwd != re_pwd:
        self.add_error('re_pwd','两次密码不一致!')
        raise ValidationError('两次密码不一致')  # 抛出异常
    return self.cleaned_data  # 必须返回

 

Form使用bootstrap样式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
    {% load static %}
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-12 text-center">
            <h1>欢迎注册</h1>
        </div>
    </div>

    <div class="row">

        <div class="col-md-12">

            <form action="" class="form-horizontal" method="post" novalidate>
                {% csrf_token %}

                <div class="form-group">
                    <label class="col-sm-3 control-label "
                           for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label>
                    <div class="col-sm-6">
                        {{ form_obj.username }}
                    </div>
                    <label class="control-label text-danger">{{ form_obj.username.errors.0 }}</label>
                </div>

                <div class="form-group">
                    <label for="{{ form_obj.pwd.id_for_label }}"
                           class="col-sm-3 control-label">{{ form_obj.password.label }}</label>
                    <div class="col-sm-6">
                        {{ form_obj.password }}
                    </div>
                    <label class="control-label text-danger">{{ form_obj.password.errors.0 }}</label>
                </div>

                <div class="form-group">
                    <label for="{{ form_obj.re_password.id_for_label }}"
                           class="col-sm-3 control-label">{{ form_obj.re_password.label }}</label>
                    <div class="col-sm-6">
                        {{ form_obj.re_password }}
                    </div>
                    <label class="control-label text-danger">{{ form_obj.re_password.errors.0 }}</label>

                </div>

                <div class="form-group">
                    <label for="{{ form_obj.name.id_for_label }}"
                           class="col-sm-3 control-label">{{ form_obj.name.label }}</label>
                    <div class="col-sm-6">
                        {{ form_obj.name }}
                    </div>
                    <label class="control-label text-danger">{{ form_obj.name.errors.0 }}</label>

                </div>

                <div class="form-group">
                    <label for="{{ form_obj.address.id_for_label }}"
                           class="col-sm-3 control-label">{{ form_obj.address.label }}</label>
                    <div class="col-sm-6">
                        {{ form_obj.address }}
                    </div>
                    <label class="control-label text-danger">{{ form_obj.address.errors.0 }}</label>

                </div>

                <div class="form-group">
                    <label for="{{ form_obj.phone.id_for_label }}"
                           class="col-sm-3 control-label">{{ form_obj.phone.label }}</label>
                    <div class="col-sm-6">
                        {{ form_obj.phone }}
                    </div>
                    <label class="control-label text-danger">{{ form_obj.phone.errors.0 }}</label>
                </div>

                <div class="form-group">
                    <label for="{{ form_obj.useremail.id_for_label }}"
                           class="col-sm-3 control-label">{{ form_obj.useremail.label }}</label>
                    <div class="col-sm-6">
                        {{ form_obj.useremail }}
                    </div>
                    <label class="control-label text-danger">{{ form_obj.useremail.errors.0 }}</label>
                </div>

                <div class="form-group">
                    <div class="col-sm-offset-3 col-sm-10">
                        <button type="submit" class="btn btn-default">注&nbsp;册</button>
                    </div>
                </div>

            </form>

        </div>

    </div>
</div>


<script src="{% static 'js/jquery-1.11.1.min.js' %}"></script>
<script src="{% static 'js/ajax-csrf.js' %}"></script>
<script>
    $(function () {
        $('#id_username').blur(function () {
            var now_obj = $(this)
            console.log(now_obj.val())
            $.ajax({
                url: '/check_user/',
                type: 'post',
                data: {
                    'check_name': now_obj.val()
                },
                success: function (data) {
                    console.log(data.status)
                    if (data.status) {
                        $(now_obj.parent().next()).text('用户名已存在,不可使用')
                    } else {
                        $(now_obj.parent().next()).removeClass('text-danger')
                        $(now_obj.parent().next()).addClass('text-success')
                        $(now_obj.parent().next()).text('用户名可以使用')
                    }
                }
            })
        });

        $('#id_username').focus(function () {
            $(this).parent().next().removeClass('text-success')
            $(this).parent().next().addClass('text-danger')
            $(this).parent().next().text('')
        })
    })
</script>
</body>
</html>

批量添加样式

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

    def __init__(self, *args, **kwargs): # 重写form的 init方法
        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"}),
        }
        
        
### MATE参数
model = models.Student  # 对应的Model中的类
fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
exclude = None  # 排除的字段
labels = None  # 提示信息
help_texts = None  # 帮助提示信息
widgets = None  # 自定义插件
error_messages = None  # 自定义错误信息
        

 

posted on 2020-03-02 12:20  向往1  阅读(358)  评论(0编辑  收藏  举报

导航

……