[Django] Form assemblies --2019-08-16 11:56:26

Original: http://blog.gqylpy.com/gqy/269

"@
***

Form Components Introduction

When submitting the form data to the back-end using the form in the HTML page will write the label to obtain user input and label them with a form wrap.

At the same time we all need to do in a lot of scenes of user input validation, such as checking whether the user input, the input length and format is not correct. If the user input has an error requires appropriate error message corresponding to the location on the page.

Django form components on the realization of the above functions.

To summarize, the main function of form components as follows:

  1. HTML tags generated pages available
  2. To verify the data submitted by users
  3. Reservations upload content input
    ***

    A simple registration example

    form file:
from django import forms

# 按照Django form组件的要求自己写一个类
# 且必须继承forms.Form
class RegForm(forms.Form):
    user = forms.CharField(label="名称")
    pwd = forms.CharField(label="密码", min_length=6)

View file:

from django.shortcuts import render, HttpResponse
from blog01 import forms  # 导入上面的form文件

def register(request):
    form_obj = forms.RegForm()
    if request.method == 'POST':
        form_obj = forms.RegForm(request.POST)
        if form_obj.is_valid():
            # 只有执行了is_valid之后,才可执行cleaned_data,否则会报错
            # cleaned_data的数据是经过校验的
            print(form_obj.cleaned_data)
            return HttpResponse("注册成功!")
    return render(request, 'register.html', {'form': form_obj})

HTML file:

<body>
{# novalidate:阻止验证(form组件会自动验证)#}
<form action="" method="post" novalidate>
{% csrf_token %}
    {# {{ form.as_p }}  以p标签的形式自动生成 #}
    {# 下面为简单的自定义形式 #}
    <p>
        {{ form.user.label }}
        {{ form.user }}
        {# 错误提示信息 #}
        {# {{ form.errors }}  生成form表单的所有错误信息(全局错误信息)#}
        {# <span style="color: red">{{ form.user.errors }}</span>  指定字段的所有错误信息 #}
        <span style="color: red">{{ form.user.errors.0 }}</span> {# 指定字段的所有错误信息的第一个 #}
    </p>
    <p>
        {{ form.pwd.label }}
        {{ form.pwd }}
        <span style="color: red">{{ form.pwd.errors.0 }}</span>
    </p>
    <button>注册</button>
</form>
</body>


Common Fields with plug-ins

from django import forms


class RegForm(forms.Form):
    user = forms.CharField(
        label="名称",  # 用于生成label标签或显示内容
        # required=False,  # 为False时,允许为空
        min_length=2,  # 最小长度
        max_length=6,  # 最大长度
        initial='zyk01',  # 初始值
        error_messages={
            'required': "用户名不能为空",
            'invalid': "用户名格式错误",
            'min_length': "用户名最短2位",
        }  # 自定义错误提示
    )

    pwd = forms.CharField(
        label="密码",
        min_length=6,
        error_messages={
            'required': "密码不能为空",
            'invalid': "密码格式错误",
            'min_length': "密码最短6位",
        },
        widget=forms.widgets.PasswordInput(),  # 指定input的type类型为password
    )

    # ===================================================
    #    
    # ===================================================

    """checkbox标签(单选)"""
    keep = forms.fields.CharField(
        label="记住密码",
        initial='checked',  # 默认选中
        widget=forms.widgets.CheckboxInput(),
    )

    """checkbox标签(多选)"""
    hobby01 = forms.fields.MultipleChoiceField(
        label="爱好01",
        choices=((1, "篮球"), (2, "足球"), (3, "乒乓球"),),
        initial=[1, 2],  # 默认选中1, 2
        widget=forms.widgets.CheckboxSelectMultiple(),
    )

    """radioSelect(等价于<input type="radio">)"""
    gender = forms.fields.ChoiceField(
        label="性别",
        choices=((1, "男"), (2, "女"), (3, "阴阳人"),),
        initial=1,  # 默认选中1
        widget=forms.widgets.RadioSelect(),
    )

    """Select标签"""
    hobby02 = forms.fields.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好02",
        initial=3,  # # 默认选中3
        # widget=forms.widgets.Select(),  # 实现单选Select
        widget=forms.widgets.SelectMultiple(),  # 实现多选Select
    )

Form components all built-in fields

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类型

Get data from the database

When using the select tag, you need to pay attention to choices of options can be obtained from the database, it is a static field, the value == == get the update can not be implemented, it is necessary to construct a custom method to achieve this purpose.

from django import forms
from blog01 import models

class RegForm(forms.Form):
    hobby03 = forms.fields.ChoiceField(
        label="爱好03",
        # choices=models.Hobby.objects.values_list('id', 'name'),
        widget=forms.widgets.CheckboxSelectMultiple(),
    )

    def __init__(self, *args, **kwargs):
        super(RegForm, self).__init__(*args, **kwargs)
        # self.fields['hobby03'].choices = ((1, 'Python'), (2, 'Django'),)
        self.fields['hobby03'].choices =  models.Hobby.objects.values_list('id','name')


Check example


Check the phone number is legitimate

View file:

from django.shortcuts import render, HttpResponse
from blog01 import forms  # 导入form文件

def register(request):
    form_obj = forms.RegForm()
    if request.method == 'POST':
        form_obj = forms.RegForm(request.POST)
        if form_obj.is_valid():
            # 只有执行了is_valid之后,才可执行cleaned_data,否则会报错
            # cleaned_data的数据是经过校验的
            print(form_obj.cleaned_data)
            return HttpResponse("注册成功!")
    return render(request, 'register.html', {'form': form_obj})

HTML file:

<body>
{# novalidate:阻止验证(form组件会自动验证)#}
<form action="" method="post" novalidate>
    {% csrf_token %}
    <p>
        {{ form.phone.label }}
        {{ form.phone }}
        <span style="color: red">{{ form.phone.errors.0 }}</span>
    </p>
    <button>注册</button>
</form>
</body>

A mode (basic)

form file:

from django import forms
from django.core.validators import RegexValidator

class RegForm(forms.Form):
    phone = forms.fields.CharField(
        label="手机号",
        validators=[
            RegexValidator(r'^1[3-9]\d{9}$', "号码不合法")
            # RegexValidator(r"正则表达式", "错误提示信息")
        ],
    )

Second way (custom validation rules)

form file:

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 RegForm(Form):
    phone = fields.CharField(
        label="手机号",
        # 使用自定义验证规则
        # validators=[校验器函数1, 校验器函数2]
        validators=[mobile_validate, ],
        error_messages={'required': "手机号不能为空", },
        widget=widgets.TextInput(
            attrs={'class': 'form-control', 'placeholder': u"手机号码"},
        )
    )

Three ways (using the hook)

forms file:

import re
from django.forms import Form
from django.forms import widgets
from django.core.exceptions import ValidationError


class RegForm(Form):
    phone = forms.fields.CharField(
        label="手机号",
        error_messages={'required': "手机号不能为空", },
        widget=widgets.TextInput(
            attrs={'class': 'form-control', 'placeholder': u"手机号码"},
        )
    )

    def clean_phone(self):
        value = self.cleaned_data.get('phone')
        mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
        if  mobile_re.match(value):
            return value
        raise ValidationError("手机号不合法")

Verify Password consistency

View file Ibid ⬆️

form file:

from django.forms import Form
from django.core.validators import RegexValidator


class RegForm(Form):
    pwd = forms.CharField(
        label="密码",
        min_length=6,
        widget=widgets.PasswordInput(),
    )
    re_pwd = forms.CharField(
        label="确认密码",
        min_length=6,
        widget=widgets.PasswordInput(),
    )

    def clean_re_pwd(self):
        pwd = self.cleaned_data.get('pwd')
        re_pwd = self.cleaned_data.get('re_pwd')
        if pwd == re_pwd:
            return re_pwd
        raise ValidationError("密码不一致")

HTML file:

<body>
{# novalidate:阻止验证(form组件会自动验证)#}
<form action="" method="post" novalidate>
    {% csrf_token %}
    {# {{ form.as_p }}  以p标签的形式自动生成 #}
    <p>
        {{ form.pwd.label }}
        {{ form.pwd }}
        <span style="color: red">{{ form.pwd.errors.0 }}</span>
    </p>
    <p>
        {{ form.re_pwd.label }}
        {{ form.re_pwd }}
        <span style="color: red">{{ form.re_pwd.errors.0 }}</span>
    </p>
    <button>注册</button>
</form>
</body>


hook


Local hooks

from django.forms import Form


class RegForm(Form):
    test01 = forms.CharField()

    test02 = forms.CharField()

    test03 = forms.CharField()

    # 局部钩子
    def clean_test02(self):
        test01 = self.cleaned_data.get('test01')
        test02 = self.cleaned_data.get('test02')
        test03 = self.cleaned_data.get('test03')
        print(test01, test02, test03)  # test03的值定会为None
        return test02

    """
    这里定义的是test02的钩子
    ! 定义哪个字段的钩子,就得返回哪个字段的值
    此时的钩子只能获取test01与test02的值,而test03的值为None
    因为test03字段在test02字段的下面,所以test02的钩子无法获取test03的值(更多可看源码)
    """

Global hook

from django.forms import Form


class RegForm(Form):
    test01 = forms.CharField()

    test02 = forms.CharField()

    test03 = forms.CharField()

    # 全局钩子
    def clean(self):
        test01 = self.cleaned_data.get('test01')
        test02 = self.cleaned_data.get('test02')
        test03 = self.cleaned_data.get('test03')
        print(test01, test02, test03)
        return self.cleaned_data

    """
    这里定义的是全局钩子
    ! 定义全局钩子, 要返回self.cleaned_data的数据
    此时的钩子可以获取所有字段的值
    """


Advanced


Bulk add style

Achieved by rewriting form class init method.

from django.forms import Form

class RegForm(Form):
    test01 = forms.CharField()
    test02 = forms.CharField()
    test03 = forms.CharField()

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

model Form

Combined with the end of the form model

== binding rules using the database field after ==

Form the checksum file:

from django import forms
from django.core.exceptions import ValidationError  # 用于返回错误信息
from . import models


class BaseForm(forms.ModelForm):
    # 重写父类的init方法来批量添加样式
    def __init__(self, *args, **kwargs):
        super(BaseForm, self).__init__(*args, **kwargs)
        for field in iter(self.fields):
            self.fields[field].widget.attrs.update({"class": "input"})


# 注册Form
class RegForm(BaseForm):

    class Meta:

        # 指定数据表
        model = models.UserProfile

        # 指定要校验的字段,如果指定为 "__all__",则校验所有的字段;如果指定为列表, 则只校验列表内的字段
        fields = ["username", "password", "email"]

        # 提示信息
        labels = {
            "username": "用户名",
            "password": "密码",
            "email": "邮箱",
        }

        # 自定义错误信息
        error_messages = {
            "username": {
                "required": "请输入用户名"
            },
            "password": {
                "required": "请输入密码",
            },
            "email": {
                "required": "请输入邮箱地址",
                "invalid": "邮箱格式错误",
            }
        }

        # 自定义插件
        widgets = {
            "username": forms.widgets.TextInput(
                attrs={
                    "id": "reg_username",
                }
            ),
            "password": forms.widgets.PasswordInput(
                attrs={
                    "id": "reg_password",
                }
            ),  # 指定input框的type类型为password,并添加id属性及值
        }

        # 帮助信息
        help_texts = {}


    # 确认密码
    re_password = forms.CharField(
        label="确认密码",
        error_messages= {
            "required": "",
        },
        widget=forms.widgets.PasswordInput(
            attrs={
                "id": "reg_re_password",
            }
        ),
    )


    # 用于校验密码格式
    def clean_password(self):
        password = self.cleaned_data.get("password")
        if len(password) == 32:  # 前端用md5加密后的固定长度
            return password
        raise ValidationError("密码格式错误!")


    # 用于校验密码一致性
    def clean_re_password(self):
        """
        局部钩子:clean_字段名
        :return: 定义哪个字段的钩子,就得返回哪个字段的值
        """
        password = self.cleaned_data.get("password")
        re_password = self.cleaned_data.get("re_password")
        if not password or password == re_password:  # not password:如果clean_password函数校验失败,则password为空
            return re_password
        raise ValidationError("密码不一致!")


"""
class Meta常见参数如下:

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

View file:

# 部分相关的代码如下:

if reg_form.is_valid():  # 成功校验数据后,才可执行reg_form.cleaned_data
    obj = reg_form.save(commit=False)  # Form组件自带的方法,commit=False表示稍后通过obj.save()方法保存数据
    obj.set_password(obj.password)  # 设置用户密码
    obj.blog_path = obj.username
    obj.nickname = obj.username
    obj.save()  # 保存
    # data = reg_form.cleaned_data  # 只有执行了reg_form.is_valid()后才可执行reg_form.cleaned_data

Form套Form

View file:

# 生成每行可编辑的form套form的表单
from django.forms import modelformset_factory  

def study_record(request, course_id):
    FormSet = modelformset_factory(models.StudyRecord, StudyRecordForm, extra=0)
    # 会自动在表单下面多生成一个form > extra=0:不生成
    queryset = models.StudyRecord.objects.filter(course_record_id=course_id)
    form_set = FormSet(queryset=queryset)
    return render(request, 'crm/sutdy_record_list.html', {'form_set': form_set})

HTML file:

{% for form in form_set %}
    <tr>
        {{ form.id }}
        <td style="display: none">{{ form.student }}</td>
        {# style="display: none" 可禁用编辑#}
        <td>{{ forloop.counter }}</td>
        <td>{{ form.instance.student.name }}</td>
        <td>{{ form.attendance }}</td>
        <td>{{ form.score }}</td>
        <td>{{ form.homework_note }}</td>
    </tr>
{% endfor %}

"

Original: http://blog.gqylpy.com/gqy/269

Guess you like

Origin www.cnblogs.com/gqy02/p/11363032.html