08 Django组件-Forms组件

 

Django的Forms组件主要有以下几大功能:

  • 页面初始化,生成HTML标签
  • 校验用户数据(显示错误信息)
  • HTML Form提交保留上次提交数据

一、小试牛刀

  1、定义Form类

from django import forms


class EmpForm(forms.Form):
    name = forms.CharField(min_length=5, label="姓名", error_messages={"required": "该字段不能为空!",
                                                                     "min_length": "用户名太短。"})
    age = forms.IntegerField(label="年龄")
    salary = forms.DecimalField(max_digits=5, decimal_places=2, label="工资")
class Emp(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    salary = models.DecimalField(max_digits=8, decimal_places=2)
Form类的字段与app01/models.py要相对应

  2、设计url与视图对应关系

from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),
    path('add_emp/', views.add_emp),
]
View Code

  3、视图函数

from django.shortcuts import render,redirect
from app01 import models

# Create your views here.
from django.http.response import HttpResponse
def index(request):
 
    return HttpResponse("okok!")

from app01.MyForms import EmpForm

def add_emp(request):
    if request.method == "GET":
        form = EmpForm()  # 初始化form对象
        return render(request, "add_emp.html", {"form":form}) #传送一个实例对象
    else:
        form = EmpForm(request.POST)  # 将数据传给form对象
        if form.is_valid():  # 进行校验
            data = form.cleaned_data  # 校验通过的数据,字典
            print(data)#{'name': 'alex01', 'age': 12, 'salary': Decimal('21')}
            models.Emp.objects.create(**data)
            return redirect("/index/")
        else:  # 校验失败
            print(form.errors)   #<ul class="errorlist"><li>name<ul class="errorlist"><li>用户名太短。</li></ul></li><li>age<ul class="errorlist"><li>This field is required.</li></ul></li><li>salary<ul class="errorlist"><li>This field is required.</li></ul></li></ul>
            '''<ul class="errorlist">
                <li>
                    name
                    <ul class="errorlist">
                        <li>用户名太短。</li>  可能会有多个错误
                        ...
                    </ul>
                </li>
                <li>age<ul class="errorlist"><li>This field is required.</li></ul></li>
                <li>salary<ul class="errorlist"><li>This field is required.</li></ul></li>
            </ul>'''
            return render(request, "add_emp.html", {"form": form}) #传送一个带错误信息实例对象
View Code

  4、模板文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>

<h3>添加员工</h3>

{#1、自己手动写HTML页面#}
{#<form action="" method="post">#}
{#    <p>姓名:<input type="text" name="name"></p>#}
{#    <p>年龄:<input type="text" name="age"></p>#}
{#    <p>工资:<input type="text" name="salary"></p>#}
{#    <input type="submit">#}
{#</form>#}

{#2、通过form对象的as_p方法实现#}
{#<form action="" method="post" novalidate>#}
{#    {% csrf_token %}#}
{#    {{ form.as_p }}#}
{#    <input type="submit">#}
{#</form>#}

</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


<form action="" method="post" novalidate> <!--novalidate  novalidate 属性规定当提交表单时不对其进行验证。如果使用该属性,则表单(浏览器)不会验证表单的输入。-->
    {% csrf_token %}
    <div>
        <label for="id_{{ form.name.name }}">姓名</label>   <!--form.name.name等价于表单类的字段名 等价于  name  age  salary ... -->
        {{ form.name }} <span>{{ form.name.errors.0 }}<!--form.name.errors.0 表单类可能定义了多个错误信息  先显示第一个 解决完 --></span>
    </div>
    <div>
        <label for="id_{{ form.age.name }}">年龄</label>
        {{ form.age }} <span>{{ form.age.errors.0 }}</span>
    </div>
    <div>
        <label for="id_salary">工资</label>
        {{ form.salary }} <span>{{ form.salary.errors.0 }}</span>
    </div>

    <input type="submit">
</form>
<!--以下为浏览器显示的页面-->
<!--<form action="" method="post" novalidate>-->
    <!--<input type="hidden" name="csrfmiddlewaretoken" value="oVa3cSt17v5ie9bQBWo3MEBiv32zysmCAvY2QMCGHk3aDyaMFPhnR8ikIniQoA0H">-->
    <!--<div>-->
        <!--<label for="id_name">姓名</label>-->
        <!--<input type="text" name="name" value="alex" minlength="5" required id="id_name"> <span>用户名太短。</span>-->
    <!--</div>-->
    <!--<div>-->
        <!--<label for="id_age">年龄</label>-->
        <!--<input type="number" name="age" required id="id_age"> <span>This field is required.</span>-->
    <!--</div>-->
    <!--<div>-->
        <!--<label for="id_salary">工资</label>-->
        <!--<input type="number" name="salary" step="0.01" required id="id_salary"> <span>This field is required.</span>-->
    <!--</div>-->

    <!--<input type="submit">-->
<!--</form>-->

</body>
</html>
3、自己写form对象标签
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="" method="post" novalidate>
    {% csrf_token %}
    {% for field in form %}   <!-- 遍历form表单类所有的字段信息   field 相当于 第三种方式的  form.name/form.age/form.salary-->
        <div>
            <label for="id_{{ field.name }}">{{ field.label }}</label>
<!--发现:name = forms.CharField(min_length=5, label="姓名", error_messages={"required": "该字段不能为空!","min_length": "用户名太短。"})  表单类定义的字段属性  字段都可以在前端调用  field.label  -->
            {{ field }} <span>{{ field.errors.0 }}</span>
<!-- field.errors.0     跟这个error_messages={"required": "该字段不能为空!","min_length": "用户名太短。"} 有关联-->
        </div>
    {% endfor %}
    <input type="submit">
</form>

</body>
</html>
4、for循环获取标签

  附:Django内置的字段及属性

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

 

二、局部钩子和全局钩子

  1、定义Form类

from django import forms
from django.core.exceptions import ValidationError  #导包  from django.core.exceptions import ValidationError
from app01 import models

class EmpForm(forms.Form):
    name = forms.CharField(min_length=5, label="姓名", error_messages={"required": "该字段不能为空!",
                                                                     "min_length": "用户名太短。"})
    age = forms.IntegerField(label="年龄")
    salary = forms.DecimalField(max_digits=5, decimal_places=2, label="工资")
    r_salary = forms.DecimalField(max_digits=5, decimal_places=2, label="请确认工资")
    # 局部钩子
    def clean_name(self):  # 名称不能随便起,必须要clean_类属性名称
        val = self.cleaned_data.get("name")

        if val.isdigit():
            raise ValidationError("用户名不能全是数字。")
        elif models.Emp.objects.filter(name=val):
            raise ValidationError("用户名已存在。")
        else:
            return val

    def clean(self):
        salary = self.cleaned_data.get("salary")
        r_salary = self.cleaned_data.get("r_salary")
        if salary != r_salary:
            raise ValidationError("工资输入有误。")
        else:
            return self.cleaned_data
app01/MyForms.py自己分析的版本含源码分析
from django import forms
from django.core.exceptions import ValidationError
from app01 import models


class EmpForm(forms.Form):
    name = forms.CharField(min_length=5, label="姓名", error_messages={"required": "该字段不能为空!",
                                                                     "min_length": "用户名太短。"})
    age = forms.IntegerField(label="年龄")
    salary = forms.DecimalField(max_digits=5, decimal_places=2, label="工资")
    r_salary = forms.DecimalField(max_digits=5, decimal_places=2, label="请再输入工资")

    def clean_name(self):  # 局部钩子
        val = self.cleaned_data.get("name")

        if val.isdigit():
            raise ValidationError("用户名不能是纯数字")
        elif models.Emp.objects.filter(name=val):
            raise ValidationError("用户名已存在!")
        else:
            return val

    def clean(self):  # 全局钩子 确认两次输入的工资是否一致。
        val = self.cleaned_data.get("salary")
        r_val = self.cleaned_data.get("r_salary")

        if val == r_val:
            return self.cleaned_data
        else:
            raise ValidationError("请确认工资是否一致。")

  2、视图函数

from django.shortcuts import render,redirect
from app01 import models

# Create your views here.
from django.http.response import HttpResponse
def index(request):
    return HttpResponse("okok!")

from app01.MyForms import EmpForm

def add_emp(request):
    if request.method == "POST":
        # data = request.POST.get()
        form = EmpForm(request.POST)
        if form.is_valid():
            print(1111,form.cleaned_data)
            models.Emp.objects.create(**form.cleaned_data)
        else:
            print(form.errors)#<ul class="errorlist"><li>name<ul class="errorlist"><li>用户名太短。</li></ul></li><li>__all__<ul class="errorlist nonfield"><li>工资输入有误。</li></ul></li></ul>
            clear_errors = form.errors.get("__all__")  # 获取全局钩子错误信息
            return render(request, "add_emp.html", {"form": form, "clear_errors": clear_errors})
    else:
        form = EmpForm()
        return render(request, "add_emp.html", {"form": form})


'''源码解析 MyForms.py:from django import forms  《---  views.py:from app01.MyForms import EmpForm
第一步:form = EmpForm() 生成一个EmpForm表单类对象(该类继承了from django import forms里面的forms.Form类)
第二步:POST请求接受客户的请求数据 进行验证 form = EmpForm(request.POST)---》form.is_valid()
    1、
    def is_valid(self):
        """Return True if the form has no errors, or False otherwise."""
        return self.is_bound and not self.errors
        
    2、    @property
    def errors(self):
        """Return an ErrorDict for the data provided for the form."""
        if self._errors is None:
            self.full_clean()
        return self._errors
    3、
    def full_clean(self):
    """
    Clean all of self.data and populate self._errors and self.cleaned_data.
    """
    self._errors = ErrorDict()
    if not self.is_bound:  # Stop further processing.
        return
    self.cleaned_data = {}
    # If the form is permitted to be empty, and none of the form data has
    # changed from the initial data, short circuit any validation.
    if self.empty_permitted and not self.has_changed():
        return

    self._clean_fields()
    self._clean_form()
    self._post_clean()
    
    4、MyForms.py有自定义的局部钩子 就会调用 MyForms.py的局部钩子
    def _clean_fields(self):
        for name, field in self.fields.items():
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            if field.disabled:
                value = self.get_initial_for_field(field, name)
            else:
                value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                if isinstance(field, FileField):
                    initial = self.get_initial_for_field(field, name)
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value
                if hasattr(self, 'clean_%s' % name):
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError as e:
                self.add_error(name, e)
    5、MyForms.py有自定义的全局钩子 就会调用 MyForms.py的全局钩子
    def _clean_form(self):
        try:
            cleaned_data = self.clean()
        except ValidationError as e:
            self.add_error(None, e)
        else:
            if cleaned_data is not None:
                self.cleaned_data = cleaned_data
        



'''
app01/views.py自己分析的版本含源码分析
def add_emp(request):
    if request.method == "GET":
        form = EmpForm()  # 初始化form对象
        return render(request, "add_emp.html", {"form":form})
    else:
        form = EmpForm(request.POST)  # 将数据传给form对象
        if form.is_valid():  # 进行校验
            data = form.cleaned_data
            data.pop("r_salary")
            models.Emp.objects.create(**data)
            return redirect("/index/")
        else:  # 校验失败
            clear_errors = form.errors.get("__all__")  # 获取全局钩子错误信息
            return render(request, "add_emp.html", {"form": form, "clear_errors": clear_errors})

  3、模板文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="" method="post" novalidate>
    {% csrf_token %}
    <div>
        <label for="id_{{ form.name.name }}">姓名</label>
        {{ form.name }} <span>{{ form.name.errors.0 }}</span>
    </div>
    <div>
        <label for="id_{{ form.age.name }}">年龄</label>
        {{ form.age }} <span>{{ form.age.errors.0 }}</span>
    </div>
    <div>
        <label for="id_salary">工资</label>
        {{ form.salary }} <span>{{ form.salary.errors.0 }}{{ clear_errors.0 }}</span>
    </div>
    <div>
        <label for="id_r_salary">请再输入工资</label>
        {{ form.r_salary }} <span>{{ form.r_salary.errors.0 }}{{ clear_errors.0 }}</span>
    </div>
    <input type="submit">
</form>

</body>
</html>
add_emp.html

 

 

 

另一种方法导入app01/models模型类的数据,其它方法与上面一致

 

 

posted @ 2019-05-31 09:25  冥想心灵  阅读(164)  评论(0编辑  收藏  举报