"@
目录
?
Form组件介绍
在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。
与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息。
Django form组件就实现了上面的功能。
先总结一下,form组件的主要功能如下:
- 生成页面可用的HTML标签
- 对用户提交的数据进行校验
- 保留上传输入的内容
一个简单的注册示例
form文件:
from django import forms # 按照Django form组件的要求自己写一个类 # 且必须继承forms.Form class RegForm(forms.Form): user = forms.CharField(label="名称") pwd = forms.CharField(label="密码", min_length=6)
视图文件:
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文件:
<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>
常用字段与插件
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组件中所有内置字段
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类型
从数据库中获取数据
在使用选择标签时,需要注意choices的选项可以从数据库中获取,但由于是静态字段,获取的值无法实施更新,所以需要自定义构造方法从而达到此目的.
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‘)
校验示例
检验手机号是否合法
视图文件:
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文件:
<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>
方式一(基本操作)
form文件:
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"正则表达式", "错误提示信息") ], )
方式二(自定义验证规则)
form文件:
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"手机号码"}, ) )
方式三(利用钩子)
forms文件:
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("手机号不合法")
验证密码一致性
视图文件同上??
form文件:
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文件:
<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>
钩子
局部钩子
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的值(更多可看源码) """
全局钩子
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的数据 此时的钩子可以获取所有字段的值 """
进阶
批量添加样式
通过重写form类的init方法来实现.
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", } )
ModelForm
form 与 model 的终结结合
结合后将使用数据库字段的规则
Form校验文件:
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 # 自定义错误信息 """
视图文件:
# 部分相关的代码如下: 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
视图文件:
# 生成每行可编辑的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文件:
{% 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 %}
"
?
?
?
?