TOP

Django Form组件

FORM 组件

概念

  内置的Django form组件操作更加简单

form组件的主要功能:

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

普通的方式创建 form 表单

  在html文件中创建好form表单并做好排版,然后动态数据通过后台函数提供

views.py

 1 def register(request):
 2     error_msg = ""
 3     if request.method == "POST":
 4         username = request.POST.get("name")
 5         pwd = request.POST.get("pwd")
 6         # 对注册信息做校验
 7         if len(username) < 6:
 8             # 用户长度小于6位
 9             error_msg = "用户名长度不能小于6位"
10         else:
11             # 将用户名和密码存到数据库
12             return HttpResponse("注册成功")
13     return render(request, "register.html", {"error_msg": error_msg})

login.html

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>注册页面</title>
 6 </head>
 7 <body>
 8 <form action="/reg/" method="post">
 9     {% csrf_token %}
10     <p>
11         用户名:
12         <input type="text" name="name">
13     </p>
14     <p>
15         密码:
16         <input type="password" name="pwd">
17     </p>
18     <p>
19         <input type="submit" value="注册">
20         <p style="color: red">{{ error_msg }}</p>
21     </p>
22 </form>
23 </body>
24 </html>

使用form组件实现注册功能

  在后台函数创建好form类实例化后和动态数据一起传到前端HTML文件,再由HTML文件排版

form.py
  (通常会将 form 表单的操作在 一个单独的文件里面写 这样和model.py同级便于区分)

1 from django import forms
2 # 按照Django form组件的要求自己写一个类
3 class RegForm(forms.Form):
4     name = forms.CharField(label="用户名")
5     pwd = forms.CharField(label="密码")

views.py

1 def register2(request):
2     form_obj = RegForm()
3     if request.method == "POST":
4         # 实例化form对象的时候,把post提交过来的数据直接传进去
5         form_obj = RegForm(request.POST)
6         # 调用form_obj校验数据的方法
7         if form_obj.is_valid():
8             return HttpResponse("注册成功")
9     return render(request, "register2.html", {"form_obj": form_obj})

login2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册2</title>
</head>
<body>
    <form action="/reg2/" method="post" novalidate autocomplete="off">
        {% csrf_token %}
        <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>
        <div>
            <input type="submit" class="btn btn-success" value="注册">
        </div>
    </form>
</body>
</html>

常用字段与插件

label

  标签内显示内容

class LoginForm(forms.Form):
  username = forms.CharField(
  min_length=8,
  label="用户名",    # 显示内容
  )

initial

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

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

error_messages

重写错误信息

内置字段格式:

  “错误类型”:“错误显示信息”

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

widget

  插件,添加 各类的HTML 的标签,并调整属性样式等操作,对每一种标签除了常用的属性以外都有不同的属性值可以设置

  •     attr={"k":"v"}                可以添加自定义的字段。通常可用来添加 类 ,id 等 
  •     render_value = True   错误提交时原数据是否保留.
class LoginForm(forms.Form):
  pwd = forms.CharField(
  min_length=6,
  label="密码",
  widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
  )

password

  密码类型input标签

  •     attr={"k":"v"}                可以添加自动以的字段。通常可用来添加 类 id 等 
  •     render_value = True   错误提交时原数据是否保留.
class LoginForm(forms.Form):
  pwd = forms.CharField(
  min_length=6,
  label="密码",
  widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
  )

单选 radioSelect

  •   choices=((num,"v"),((num,"v"),((num,"v"))    choice为选项内容,num为后台提交数据标识,v为选项数据内容 
  •        initial 初始选项值
class LoginForm(forms.Form):
  gender = forms.fields.ChoiceField(
  choices=((1, ""), (2, ""), (3, "保密")),
  label="性别",
  initial=3,
  widget=forms.widgets.RadioSelect()
  )    

多选Select

  •   choices=((num,"v"),((num,"v"),((num,"v"))    choice为选项内容,num为后台提交数据标识,v为选项数据内容 
  •        initial 初始选项值,多选的时候可用列表形式提交
class LoginForm(forms.Form):
  hobby = forms.fields.MultipleChoiceField(
  choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
  label="爱好",
  initial=[1, 3], 
  widget=forms.widgets.SelectMultiple()
  )    

单选checkbox

  •        initial 初始选项值,被选中为 “checked”
class LoginForm(forms.Form):
  keep = forms.fields.ChoiceField(
  label="是否记住密码",
  initial="checked",
  widget=forms.widgets.CheckboxInput()
  )    

多选checkbox

  •   choices=((num,"v"),((num,"v"),((num,"v"))    choice为选项内容,num为后台提交数据标识,v为选项数据内容 
  •        initial 初始选项值,多选的时候可用列表形式提交
class LoginForm(forms.Form):
  ...
  hobby = forms.fields.MultipleChoiceField(
  choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
  label="爱好",
  initial=[1, 3],
  widget=forms.widgets.CheckboxSelectMultiple()
  )    

choice字段注意事项

  choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写 构造方法 从而实现choice实时更新。
 方式一: 

    重写 init 方法,在继承父类的前提下重新在去数据库查询一次

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


class MyForm(Form):

    user = fields.ChoiceField(
  # choices=((1, '上海'), (2, '北京'),),
  initial=2,
  widget=widgets.Select
  )

  def __init__(self, *args, **kwargs):
    super(MyForm,self).__init__(*args, **kwargs)
    # 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()) # 单选

全部的内置字段

  1 Field
  2     required=True,               是否允许为空
  3     widget=None,                 HTML插件
  4     label=None,                  用于生成Label标签或显示内容
  5     initial=None,                初始值
  6     help_text='',                帮助信息(在标签旁边显示)
  7     error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
  8     validators=[],               自定义验证规则
  9     localize=False,              是否支持本地化
 10     disabled=False,              是否可以编辑
 11     label_suffix=None            Label内容后缀
 12  
 13  
 14 CharField(Field)
 15     max_length=None,             最大长度
 16     min_length=None,             最小长度
 17     strip=True                   是否移除用户输入空白
 18  
 19 IntegerField(Field)
 20     max_value=None,              最大值
 21     min_value=None,              最小值
 22  
 23 FloatField(IntegerField)
 24     ...
 25  
 26 DecimalField(IntegerField)
 27     max_value=None,              最大值
 28     min_value=None,              最小值
 29     max_digits=None,             总长度
 30     decimal_places=None,         小数位长度
 31  
 32 BaseTemporalField(Field)
 33     input_formats=None          时间格式化   
 34  
 35 DateField(BaseTemporalField)    格式:2015-09-01
 36 TimeField(BaseTemporalField)    格式:11:12
 37 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
 38  
 39 DurationField(Field)            时间间隔:%d %H:%M:%S.%f
 40     ...
 41  
 42 RegexField(CharField)
 43     regex,                      自定制正则表达式
 44     max_length=None,            最大长度
 45     min_length=None,            最小长度
 46     error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}
 47  
 48 EmailField(CharField)      
 49     ...
 50  
 51 FileField(Field)
 52     allow_empty_file=False     是否允许空文件
 53  
 54 ImageField(FileField)      
 55     ...
 56     注:需要PIL模块,pip3 install Pillow
 57     以上两个字典使用时,需要注意两点:
 58         - form表单中 enctype="multipart/form-data"
 59         - view函数中 obj = MyForm(request.POST, request.FILES)
 60  
 61 URLField(Field)
 62     ...
 63  
 64  
 65 BooleanField(Field)  
 66     ...
 67  
 68 NullBooleanField(BooleanField)
 69     ...
 70  
 71 ChoiceField(Field)
 72     ...
 73     choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
 74     required=True,             是否必填
 75     widget=None,               插件,默认select插件
 76     label=None,                Label内容
 77     initial=None,              初始值
 78     help_text='',              帮助提示
 79  
 80  
 81 ModelChoiceField(ChoiceField)
 82     ...                        django.forms.models.ModelChoiceField
 83     queryset,                  # 查询数据库中的数据
 84     empty_label="---------",   # 默认空显示内容
 85     to_field_name=None,        # HTML中value的值对应的字段
 86     limit_choices_to=None      # ModelForm中对queryset二次筛选
 87      
 88 ModelMultipleChoiceField(ModelChoiceField)
 89     ...                        django.forms.models.ModelMultipleChoiceField
 90  
 91  
 92      
 93 TypedChoiceField(ChoiceField)
 94     coerce = lambda val: val   对选中的值进行一次转换
 95     empty_value= ''            空值的默认值
 96  
 97 MultipleChoiceField(ChoiceField)
 98     ...
 99  
100 TypedMultipleChoiceField(MultipleChoiceField)
101     coerce = lambda val: val   对选中的每一个值进行一次转换
102     empty_value= ''            空值的默认值
103  
104 ComboField(Field)
105     fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
106                                fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
107  
108 MultiValueField(Field)
109     PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
110  
111 SplitDateTimeField(MultiValueField)
112     input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
113     input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
114  
115 FilePathField(ChoiceField)     文件选项,目录下文件显示在页面中
116     path,                      文件夹路径
117     match=None,                正则匹配
118     recursive=False,           递归下面的文件夹
119     allow_files=True,          允许文件
120     allow_folders=False,       允许文件夹
121     required=True,
122     widget=None,
123     label=None,
124     initial=None,
125     help_text=''
126  
127 GenericIPAddressField
128     protocol='both',           both,ipv4,ipv6支持的IP格式
129     unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
130  
131 SlugField(CharField)           数字,字母,下划线,减号(连字符)
132     ...
133  
134 UUIDField(CharField)           uuid类型
135 
136 Django Form内置字段
View Code

form 组件的标签生成方式 

全部默认生成

  直接将 form 类中的所有标签以默认的方式整体生成

    操纵更加简单,但是默认会添加很多的不必要的属性

    而且不方便控制form表单的整体样式

{{ form.as_p }}

自写框架,自动生成标签

  自己写form表单框架,但是标签内容用form 组件来填充循环一个一个生成

    对于form表单的样式可以调节了,但是生成的标签依旧很多不必要的属性

<form action="/reg2/" method="post" novalidate autocomplete="off">
        {% csrf_token %}
        
     <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>

<div> <input type="submit" class="btn btn-success" value="注册"> </div> </form>

完全手写

  完全不用form组件的标签生成,全部手动写

    完全控制了标签的样式自由

    不再需要基于form组件的内置字段来控制样式

<form action="/addbook/" method="post">
    {% csrf_token %}
    <p>名称<input type="text" name="title"></p>
    <p>价格<input type="text" name="price"></p>
    <input type="submit">
    
</form>

  ps:

    但是我还是想要用form 组件的 验证功能则需要注意一点 : 自己写的标签的 “ name ” 属性一定要和 form 组件要验证的字段名字对的上

class BookForm(forms.Form):
    title = forms.CharField()
    price = forms.FloatField()


def addbook(request):
    if request.method == "POST":
        print(request.POST)
        form = BookForm(request.POST)
        if form.is_valid():
            print("cleaned_data", form.cleaned_data)
        else:
            print("errors", form.errors)
    form = BookForm()
    return render(request, "addbook.html", locals())

  详解:

  面代码中 BookForm 注册的字段名字为 “ title ” 以及 “ price ”

  那么自己手写的 标签的 “ name ” 属性一定要和这个对上才可以

<form action="/addbook/" method="post">
    {% csrf_token %}
    <p>名称<input type="text" name="title"></p>
    <p>价格<input type="text" name="price"></p>
    <input type="submit">
    
</form>

  在校验的时候,

    如果 BookForm 中要求字段比 实际传入的字段要少 ,会引发空数据的 error (相当于有数据没拿到)

    如果 BookForm 中要求字段比 实际传入的字段要多 , BookForm 对要求验证字段验证结束无错误后是不会处理多余的字段的。

    最明显的特征为 csrf_token 的值不会进行处理也不会报错

重写钩子函数

 1 # 重写全局的钩子函数,对确认密码做校验
 2 from django.core.exceptions import ValidationError
 3 
 4 
 5 def clean(self):
 6     password = self.cleaned_data.get("password")
 7     re_password = self.cleaned_data.get("re_password")
 8     if re_password and re_password != password:
 9         self.add_error("re_password", ValidationError("两次密码不一致"))
10  return self.cleaned_data
11     
12 
13 
14     
15 # 重写局部钩子函数 对名字字段的敏感字符进行判断
16 from django.core.exceptions import ValidationError
17 
18 
19 def clean_name(self):
20     value = self.cleaned_data.get("name")
21     if "金瓶mei" in value:
22         raiseValidationError("不符合社会主义核心价值观!")
23return value

关于 form is_vaild() 的执行顺序

  0. form is_vaild()

    1. 判断是否有数据 以及 是否有错误 (是否有数据直接可以判断)

    2 . 进行错误信息的判断 (默认的错误信息为 none 定义在 init 中)       

    2.1 执行 full_clean()方法

        2.1.1  创建一个 保存错误信息的 空字典 ( self._error = ErroDict() )

          2.2.2  创建一个 保存校验通过数据的 空字典 ( self.cleaned_data = {} )

      2.2 然后分别执行下面的三个方法

        2.2.1 _clean_fields()

          2.2.1.1  for 循环每个字段 ( self.fields.items() ) 分别校验 ( 利用的是内置的校验规则 )

如果有错误 在 self._error[] 中 在 add_error 中可以捕获到 ValidationError 异常进行处理  (稍微有些废话就隐藏把)

如果没有报错 在cleaned_date[] 中 加入已经通过校验的字段

          2.2.1.2  for 循环结束后 进行一次反射 查询是否有 clean_%s %name 的方法 进行调用后执行

如果有错误 在 self._error[] 中 在 add_error 中可以捕获到 ValidationError 异常进行处理   (稍微有些废话就隐藏把)

如果没有报错 在cleaned_date[] 中 加入已经通过校验的字段

            即 这个 clean_%s 的方法为一个钩子函数 此钩子可以帮我们解决什么问题呢?
 这是个局部变量的钩子 可以对局部变量内部进行一定程度的操作 比如对变量的值进行操作

        2.2.2   _clean_form()

            调用对象的 .clean()方法,默认的继承的 .clean() 是什么都不做的

            即 .clean() 为一个全局的钩子 可以对所有的变量进行操作 比如两个变量的对比等

因此可以利用这个clean方法进行 重写 自定义想要的校验操作

        2.2.3 post_clean()



    通过上面的流程可以发现.不论是使用全局钩子还是局部钩子都是对已经校验通过的数据进行操作
    即是说 在调用 def clean_name(self): 或者 def clean(self): 的时候
      self.cleaned_data 里面已经装好了校验通过的数据对象

    在调用 def clean(self):           通过get("name") 也可以拿到所有的变量对象
    在调用 def clean_name(self):        通过get("name") 也可以拿到当前的局部变量对象

    通过后面的流程也发现这两个函数都要有返回值

      def clean(self):       需要返回全局的 self.cleaned_data

      ddef clean_name(self):    需要返回被处理的局部变量 value 

 

posted @ 2018-12-11 17:35  羊驼之歌  阅读(226)  评论(0编辑  收藏  举报