柴少的博客 也许终将一事无成,却不能甘于平庸。

Django之Form(十四)

django中的Form功能操作:

验证用户请求
生成HTML标签(保留上一次提交的数据)

#比如用户注册信息验证:用户名不允许为空、密码最短6位,如果自己写需要些很多,比较麻烦,而Django form提供了特别便捷的实现方式。

一、Form例子了解

1.1 初始form,创建form

urls.py:

url(r'^fm/$', views.fm),

views.py:

from django import forms
class FM(forms.Form):
    # 只关心处理自己定义的form表单数据,恶意攻击定义的数据不处理
    user = forms.CharField()
    pwd = forms.CharField()  # 这里的变量名必须和html form里的name保持一致
    email = forms.EmailField()

def fm(request):
    if request.method == "GET":
        return render(request, "fm.html")
    elif request.method == "POST":
        # 获取用户所有数据,每条数据请求的验证
        # 成功 --> 获取所有的正确信息;失败 --> 显示错误信息
        obj = FM(request.POST)
        r1 = obj.is_valid()  #obj.is_valid()表示让其验证是否成功会有一个布尔返回值
        if r1:  # 返回的正确信息
            print(obj.cleaned_data)
        else:   # 返回的错误信息
            print(obj.errors)
            print(obj.errors.as_json())  #errors.as_json()展现为字典的形式
        return redirect('/fm/')

fm.html :

<body>
    <form action="/fm/" method="POST">
        {% csrf_token %}
        <input type="text" name="user">
        <input type="text" name="pwd">
        <input type="text" name="email">
        <input type="submit" value="提交" />
    </form>
</body>

测试一下:

image.png

image.png

1.2 自定制form错误信息,前端显示

views.py :

from django import forms
class FM(forms.Form):
    # 只关心处理自己定义的form表单数据,恶意攻击定义的数据不处理
    user = forms.CharField(error_messages={'required':'用户名不能为空'})
    pwd = forms.CharField(
        max_length=12,
        min_length=6,
        error_messages={'required':'密码不能为空','min_length':'密码长度不能小于6','max_length':'密码长度不能大于12'}
    )
    email = forms.EmailField(error_messages={'required':'用户名不能为空','invalid':'邮箱格式错误'})

def fm(request):
    if request.method == "GET":
        return render(request, "fm.html")
    elif request.method == "POST":
        obj = FM(request.POST)
        r1 = obj.is_valid()
        if r1:  # 返回的正确信息
            print(obj.cleaned_data)
        else:   # 返回的错误信息
            # ErrorDict
            # print(obj.errors['user'][0])
            # print(obj.errors.as_json())
            return render(request,'fm.html',{'obj':obj})
        return render(request,'fm.html')

fm.html :

<body>
    <form action="/fm/" method="POST">
        {% csrf_token %}
        <p><input type="text" name="user"> {{ obj.errors.user.0 }}</p>
        <p><input type="text" name="pwd"> {{ obj.errors.pwd.0 }}</p>
        <p><input type="text" name="email"> {{ obj.errors.email.0 }}</p>
        <input type="submit" value="提交" />
    </form>
</body>

测试一下:

image.png

image.png

1.3 保留上一次提交的数据,自动生成html标签

views里面不仅能帮我们生成错误信息,还能帮我们生成html标签。

views.py:

def fm(request):
    if request.method == "GET":
        obj = FM()  # 自动生成html时,get这里也需要创建对象
        return render(request, "fm.html",{'obj':obj})
    elif request.method == "POST":
        obj = FM(request.POST)
        r1 = obj.is_valid()
        if r1:  # 返回的正确信息
            print(obj.cleaned_data)  # 这是个字典,注册直接下面那一句就成功了
            # models.UserInfo.objects.create(**obj.cleaned_data)
        else:   # 返回的错误信息
            return render(request,'fm.html',{'obj':obj})
        return render(request,'fm.html')

fm.html :

    <form action="/fm/" method="POST">
        {% csrf_token %}        <p>{{ obj.user }} {{ obj.errors.user.0 }}</p>
        <p>{{ obj.pwd }} {{ obj.errors.pwd.0 }}</p>
        <p>{{ obj.email }} {{ obj.errors.email.0 }}</p>
        <input type="submit" value="提交" />
    </form>

测试:

image.png

1.4 更简洁的html标签生成方法

这里生成虽然方便,但是可定制化不如上面高。

obj.as_p

obj.as_ul

obj.as_table

可以把上面html里的form改为:

    <form action="/fm/" method="POST">
        {% csrf_token %}
        {{ obj.as_p }}
        <input type="submit" value="提交" />
    </form>

或者:

 <form action="/fm/" method="POST">
        {% csrf_token %}
        {{ obj.as_ul }}
        <input type="submit" value="提交" />
    </form>

或者:

   <form action="/fm/" method="POST">
        {% csrf_token %}
        <table>
            {{ obj.as_table }}
        </table>  
        <input type="submit" value="提交" />
    </form>

1.5 自定义样式

form类里面的字段,只有一个功能,就是验证客户端发过来的数据。生成html的功能做不了。

但是怎么生成的html标签呢,在charfield里面有个插件,插件生成的。在其源码里做了html字符串的拼接返回。

from django import forms
from django.forms import widgets  # 插件在这里面
class FM(forms.Form):
    # 字段本身只做验证
    user = forms.CharField(         # 修改html标签,并指定样式##############
        error_messages={'required':'用户名不能为空'},
        widget=widgets.Textarea(attrs={'class':'c1'}),  # 页面再看就是textarea了
        label="用户名"
    )
    pwd = forms.CharField(
        max_length=12,
        min_length=6,
        error_messages={'required':'密码不能为空','min_length':'密码长度不能小于6','max_length':'密码长度不能大于12'},
        widget=widgets.PasswordInput  # 密码密文显示,如果自定义样式也可加上(attrs……)
    )
    email = forms.EmailField(error_messages={'required':'用户名不能为空','invalid':'邮箱格式错误'})

而字段都在from django.forms import fields里面,所以上面的forms可以改用fields:email = fields.EmailField()

前端<p>{{ obj.user.label }}{{ obj.user }} {{ obj.errors.user.0 }}</p>

插件里面input、checkbox、select、redio等全部都有

1.6 form 内置字段

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,                      自定制正则表达式
    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类型
    ...

说一下上面的ChoiceField,它主要做选项用的:

city = fields.ChoiceField(
        choices=[(0,'上海'),(1,'广州')]
    )
    city2 = fields.MultipleChoiceField(
        choices=[(0,'上海'),(1,'广州')]
    )

1.7 常用选择插件

# 单radio,值为字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )

# 单radio,值为字符串
# user = fields.ChoiceField(
#     choices=((1, '上海'), (2, '北京'),),
#     initial=2,
#     widget=widgets.RadioSelect
# )

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

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

# 多选select,值为列表
# user = fields.MultipleChoiceField(
#     choices=((1,'上海'),(2,'北京'),),
#     initial=[1,],
#     widget=widgets.SelectMultiple
# )


# 单checkbox
# user = fields.CharField(
#     widget=widgets.CheckboxInput()
# )


# 多选checkbox,值为列表
# user = fields.MultipleChoiceField(
#     initial=[2, ],
#     choices=((1, '上海'), (2, '北京'),),
#     widget=widgets.CheckboxSelectMultiple
# )

1.8 Django内置插件:

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
#
自定义
    - 类
    - 字段(校验)
    - 插件(生成HTML)

1.9 初始化操作

在Web应用程序中开发编写功能时,时常用到获取数据库中的数据并将值初始化在HTML中的标签上。

def fm(request):
    if request.method == "GET":
        dic = {
            "user":'r1',
            "pwd":'123456',
            "email":'aera@11.com',
            "city":1,
            "city2":[1,2],
        }
        obj = FM(initial=dic)  # 初始化
        return render(request, "fm.html",{'obj':obj})
    # ……

二、Form操作select数据

2.1 Form操作动态select数据

views.py :

from django.forms import forms
from django.forms import fields
from django.forms import widgets   #Widget是Django 对HTML 输入元素的表示。Widget 负责渲染HTML和提取GET/POST 字典中的数据。
from app01 import models

class UserInfoForm(forms.Form):
    user = fields.CharField(   #这是给user字段做成Textarea文本输入框,并且通过attrs给其加了一个class选择器c1。
        widget=widgets.Textarea(attrs={"class": "c1"})
    )
    pwd = fields.CharField(
        max_length=12,
        widget=widgets.PasswordInput(attrs={"class": "c1"})   #PasswordInput是密码输入框
    )
    user_type = fields.ChoiceField(
        choices=models.UserType.objects.values_list("id", "name"),  #这是调用数据库的UserType表,将里面的id和name字段查找出来
        widget=widgets.Select   #然后把让面的值放到select元素中。
    )

def index(request):
    obj = UserInfoForm()   
    print(obj)  #后台打印以下可以看到都是啥。
     #obj.fields#这里面封装了user,pwd,user_type,当数据库有新的数据的时候从新赋值
    return render(request,"index.html",{"obj":obj})

index.html :

<body>
    {{ obj.user }}
    {{ obj.pwd }}
    {{ obj.user_type }}
</body>

models.py:

from django.db import models

class UserType(models.Model):
    name = models.CharField(max_length=32)

图片.png

#插入点内容。

测试一下:

图片.png

#上图是浏览器显示结果

图片.png

#上图是后台打印结果。

展示一个问题:

图片.png

#现在后台把游客删掉。

图片.png

#只有重启程序之后才会更新

出现上述的原因是:

      因为views.py文件中的class UserInfoForm类中的user,pwd,usertype在类第一次加载的时候已经执行,所以这个时候user_type中的models.UserType.objects.values_list("id","name")操作已经从数据库取出数据存放在内存中,当再次刷新页面的时候,程序会直接从内存中获取对应的值,而不会再次进行数据库查询。并且这个时候form还做一个操作,当我们在views函数里通过obj = UserInfoForm() 创建form对象的时候,就会执行form中的__init__方法,__init__方法会把存在内存的字段封装到obj.fields中。所以就是每次创建form对象的时候都会去执行__init__方法,所以为了解决修改数据库信息必须重启程序才能生效问题。

解决方法一,将views函数中的代码进行更改:

def index(request):
    obj = UserInfoForm()
    obj.fields["user_type"].choices = models.UserType.objects.values_list("id", "name")
    return render(request,"index.html",{"obj":obj})

#就是在创建对象之后再次进行数据库查询,但是这种方法并不完善,每次设计修改数据的都需要在创建form对象之后进行一次操作。

解决方法二、重写form类的__init__构造方法:

class UserInfoForm(forms.Form):
    user = fields.CharField(
        widget=widgets.Textarea(attrs={"class": "c1"})
    )
    pwd = fields.CharField(
        max_length=12,
        widget=widgets.PasswordInput(attrs={"class": "c1"})
    )
    user_type = fields.ChoiceField(
        choices=[],
        widget=widgets.Select
    )
    def __init__(self, *args, **kwargs):
        super(UserInfoForm, self).__init__(*args, **kwargs)
        self.fields["user_type"].choices = models.UserType.objects.values_list("id", "name")

#重写了__init__方法,并且上面user_type中的choices可以设置为[],因为下面的__init__方法中会再次查询数据库。

2.2 Django自己的动态select数据

models.py :

class UserType(models.Model):
    name=models.CharField(max_length=32)
    def __str__(self):   #要在model里面加__str__,不然显示的名称是对象
        return self.name

views.py :

from django.forms import ModelChoiceField  #需要导入此的模块
...  #在class UserInfoForm(forms.Form):中再加入一个变量赋值
user_type2 = ModelChoiceField(
    queryset=models.UserType.objects.all()   ##django会自动取
)
...

#在ModelChoiceField的参数:

queryset,                  # 查询数据库中的数据
empty_label="---------",   # 默认空显示的内容
to_field_name=None,        # HTML中value的值对应的字段
limit_choices_to=None      # ModelForm中对queryset二次筛选

index.html :

<body>
    {{ obj.user }}
    {{ obj.pwd }}
    {{ obj.user_type }}
    {{ obj.user_type2 }} <!--增加一行-->
</body>

测试一下:

图片.png

创建form对象的时候可以传递参数,例如:

views.py:

def index(request):
    obj = UserInfoForm({"user":"120ni"})

图片.png

作者:chaishaopeng 分类:Django学习 浏览:1485 评论:0
留言列表
发表评论
来宾的头像