一、DjangoForm组件介绍
我们之前在html页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。
与此同时我们在好多场景下都需要对用户的输入做校验,比如验证用户是否输入,输入的长度和格式等是否正确,如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息。
Django Form组件就实现了上面所述的功能。
1、生成页面可用的html标签(生成input框)
2、对用户提交的数据进行校验
3、错误信息的展示
4、保留上次的输入
缺点:只能生成form表单里面的内容,form标签需要自己建立,提交的按钮需要自己写,csrf_token也需要自己添加
二、form组件的字段和插件
创建form类时,主要涉及到【字段】和【插件】,字段用于对用户请求数据的验证,插件用于自动生成html
字段创建,默认为input框中的类型为text,可以通过插件来修改成按钮,多选框等等。
想通过插件修改,需要导入插件模块:from django.forms import widgets
1、字段
CharField(参数) 所有的字段都继承Field类,初始化都是TextFied,改变type都可以通过widgets改变
注:所有的字段类型都可以用CharField,然后通过widgets来修改
ChoiceField() 选择类的字段
通过widgets可改变为:widget = forms.widgets.radioSelect()
radioSelect(单选按钮)
Select(单选下拉)
SelectMultiple(多选下拉)
checkbox(单选方形按钮)
checkboxSelectMultiple(多选方形按钮)
示例:如何使用choiseField字段,及使用数据库中的数据
class LoginForm(forms.Form): ... hobby = forms.fields.ChoiceField( # choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), choices= models.Hobby.object.all().values_list("id", "name") label="爱好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() ) # 上面choices就用到了从数据库中获取数据,但这种方式,每次更改数据库,都需要重启项目,前端才能更新信息
关于choice的注意事项:
在使用选择标签字段时,需要注意choices的选项可以从数据库中获取,但时由于时静态字段,获取的值无法实时更新,即
中途向数据库中添加数据,前端不会实时更新,需要重启项目的问题,但重启项目明显不好,故可通过构造函数,每次查询时,都先去数据库中查一下使用的数据库中的字段。将如下的方法也放到类中即可。
方案一
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['hobby3'].choices = models.Hobby.objects.all().values_list('id', 'name')
注:其中fields代表的是自定义form类中所有的字段
注:当定义了__init__方法,那么类中对应字段的choices就可以删掉了
方案二
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()) # 单选
DjangoForm所有的内置字段
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类型
2、参数
label 显示的文本,如input框前的“用户名:”
initail 初始化的值,即默认值
required 是否必填,required=True
help_text 在input框后的提示信息
error_messages ={"min_length": '太短了',"required": '此数字必须填写'} 定义字段,验证不通过时提示的错误信息,格式为字典
widget = forms.widgets.PasswrodInput(attrs={'class': "form_contronl"}) 加插件,可以改变input框的类型,也可以为input标签加各种属性,如class,style等等。
validators = [验证器1,] 里面可以时django默认的验证器,也可以自己定义验证方法,然后放入其中
3、验证
1、djangoForm组件字段验证顺序
form_obj.is_vaild()==> 通过则将字段加入clean_data字典集合中,如果不通过,则报错,不会将对应字段名加入到字典中,故后面自定义的方法是拿不到字段的
|
django自己的clean(self) 方法
|
自定义的clean_字段名 的方法 ==> 系统会通过反射检索是否自定义以clean_开头的方法,如果有就执行
|
自定义的clean(self) 方法 ==> 这个方法是全局的,对所有的字段校验,本质是复写django的clean方法
2、form校验的几种方式
validators = [验证器1,]
2.1、内置的验证
max_length/min_length
required
2.2、自定义验证器
方式一、
-
from django.core.Validators import RegexCalidator 有很多这样的类,这个是正则验证的模块 validators=[RegexValidator(r'^[a-zA-Z][a-zA-Z0-9_@]{4,18}', '4~18个字符,首字符为字母')] 格式为:前部分为正则,后半部分为提示信息
方式二、
-
# 定义函数
from django.core.exceptions import ValidationError def validator_check(value): # 定义方法,设计规则,用于验证 if 'sex' in value: raise ValidationError('含有非法字符')
然后在类中的字段 validators中调用方法即可,validators = [validator_check] # 不用加括号
2.3、利用钩子函数自定义验证
钩子,框架级别的东西,流程过程中,预留一些空白,有,就进行验证,没有就过
钩子函数,利用反射hasattr,拿到方法的字符串,有就执行,没有就过
局部:单独为某个字段设置
-
clean_字段名(self): 通过校验:返回当前校验的值 【for循环校验】 不通过:raise ValidationError('不通过') 报错 为什么报错? 因为源码中通过try捕获到错误,放入到err_diact中,继续执行其它的程序
全局:所有的字段都校验完了,才回执行,里面只有通过的字段
-
clean(self): 通过校验:返回 self.cleaned_data 不通过: self.add_error('re_wd', '两次密码不一致') raise ValidationError('不通过') 报错
PS:form表单中,如果在form标签中加入属性novalidate ,则前端不会做校验。
三、定义一个form组件
1、创建
可在视图中创建,但最好在app下单独建立一个py文件,如myform.py
1.1、导入模块 from django import forms
1.2、定义form类,继承forms.Form(只能定义若干个数据库表中的字段) 或 forms.Models(可批量定义)
1.3、定义字段(可以是数据库中的,那么值就需要通过orm操作获取数据库中的值)
示例:
from django import forms from django.forms import widgets # 插件的意思,就是改变【字段】input的类型,如checkbox等等 from django.core.validators import RegexValidator # 用于校验,正则 class MyForms(forms.Form): username = forms.CharField(label='用户名', help_text='人生库端4444', # input框右边的提示语 min_length=4, initial='不能小于5个字符', error_messages={ 'min_length': '长度不符合规则' }, widget=widgets.TextInput(attrs={'class': 'form-control'}), validators=[RegexValidator(r'^[a-zA-Z][a-zA-Z0-9_@]{4,18}', '4~18个字符,首字符为字母')] ) password = forms.CharField(label='密码', # 密码默认明文显示,可通过插件修改 widget=widgets.PasswordInput(attrs={'class': 'form-control'}), hobby2 = forms.ChoiceField(label='love:下拉多选', initial=3, choices=((1, 'lan'), (2, 'qiu'), (3, 'dd')), widget=widgets.SelectMultiple(attrs={'class': 'form-control'}) # 下拉多选 ) )
2、使用
2.1、后端使用:views.py
-
from app_name.myform import MyForm def view_func(request): form_obj = MyForm() 实例化 if request.method == 'POSt': form_obj = MyForm(request.POST) 将接收到的数据传进去 form_obj.is_vaild() 做校验,后台自动完成 form_obj.cleaned_data 字典,已经通过校验的所有字段,没有通过,不会放入到里面 return render(request, 'index.html' , {'form_obj': form_obj}) 注:两次实例化变量名都为form_obj这样优化代码,不然post请求验证不过,返回错误信息,还要写一个return。
2.2、前端使用
-
{{form_obj}} 直接生成所有的字段,不过没有格式
{{form_obj.name.lable_tag}} 直接生成一个lable的标签
{{form_obj.as_p}} 所有的字段生成input框,每个input外面时p标签 生成的html代码如下以as_p为例: <P> <lable for='{{form_obj.name.id_for_label}}'>{{form_obj.name.label}}</lable> <input> </p> {{form_obj.as_table/as_ui}} {{form_obj.name}} name字段生成的input框,框的类型type和定义时charField定义有关 {{form_obj.name.label}} name的标签显示的文字,如用户名 {{form_obj.name.id_for_label}} input框的id,用于与label标签的for关联 {{form_obj.name.errors}} 该字段后台验证返回来的所有的错误 {{form_obj.name.errors.0}} 该字段后台验证返回来的所有的错误的第一个错误,所有的错误放在一个字典中 {{form_obj.errors}} form表单的所有的错误
四、应用bootstrap样式,如何使用form组件
方式一:一个一个字段写
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <title>login</title> </head> <body> <div class="container"> <div class="row"> <form action="/login2/" method="post" novalidate class="form-horizontal"> {% csrf_token %} <div class="form-group"> <label for="{{ form_obj.username.id_for_label }}" class="col-md-2 control-label">{{ form_obj.username.label }}</label> <div class="col-md-10"> {{ form_obj.username }} <span class="help-block">{{ form_obj.username.errors.0 }}</span> </div> </div> <div class="form-group"> <label for="{{ form_obj.pwd.id_for_label }}" class="col-md-2 control-label">{{ form_obj.pwd.label }}</label> <div class="col-md-10"> {{ form_obj.pwd }} <span class="help-block">{{ form_obj.pwd.errors.0 }}</span> </div> </div> <div class="form-group"> <label class="col-md-2 control-label">{{ form_obj.gender.label }}</label> <div class="col-md-10"> <div class="radio"> {% for radio in form_obj.gender %} <label for="{{ radio.id_for_label }}"> {{ radio.tag }}{{ radio.choice_label }} </label> {% endfor %} </div> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <button type="submit" class="btn btn-default">注册</button> </div> </div> </form> </div> </div> <script src="/static/jquery-3.2.1.min.js"></script> <script src="/static/bootstrap/js/bootstrap.min.js"></script> </body> </html>
方式二:通过for循环,直接通过field即可
<div class="panel panel-primary"> <div class="panel-heading ">编辑页面</div> <div class="panel-body"> <form class="form-horizontal " method="post"> {% csrf_token %} {% for field in customer_form_obj %} <div class="form-group "> <label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label> <div class="col-sm-10 "> {{ field }} </div> <span class="help-block">{{ field.errors.0 }}</span> </div> {% endfor %} <button type="submit" class="btn btn-lg btn-success btn-block center-block">提交</button> </form> </div> </div>
五、form组件批量添加样式
可通过重写form类的init方法来实现。
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三", error_messages={ "required": "不能为空", "invalid": "格式错误", "min_length": "用户名最短8位" } ... def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) for field in iter(self.fields): self.fields[field].widget.attrs.update({ 'class': 'form-control' })
如何限制Form中下来框中值的选取范围?
1. 可以将model的实例以参数instance=obj传入,在form组件中引用self.instance 来操作orm,进行限制
2. 在post请求时,data= request.POST ,instance=obj
- views.py # 客户的增删改查 def customer_change(request, edit_id=None): """要求添加完成后还回到之前的页面""" if re.match('^/del_customer/(d+)/$', request.path_info): check_obj = models.Customer.objects.filter(id=edit_id) if not check_obj: return HttpResponse("目标不存在,非法操作") check_obj.delete() next_url = request.GET.get('next_url', '') return redirect(next_url) msg = "" edit_obj = models.Customer.objects.filter(id=edit_id).first() customer_form_obj = forms.CustomerForm(instance=edit_obj) if request.method == "POST": next_url = request.GET.get('next_url', '') customer_form_obj = forms.CustomerForm(data=request.POST, instance=edit_obj) if customer_form_obj.is_valid(): customer_form_obj.save() return redirect(to=next_url) else: msg = "请按要求填写信息" - forms.py class ConsultRecordForm(BaseStyle): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 限制添加客户跟进记录,只能选择自己的客户 self.fields['customer'].widget.choices = [ (i.id, i) for i in self.instance.consultant.customers.all()] # 限制添加客户记录,销售下拉只能是自己 self.fields['consultant'].widget.choices = [(self.instance.consultant.id, self.instance.consultant), ] class Meta: model = models.ConsultRecord fields = '__all__' exclude = ['delete_status'] widgets = { 'consultant': forms.widgets.Select() }
六、ModelForm
form与model的终极结合,可直接使用models中的所有字段
class BookForm(forms.ModelForm): class Meta: model = models.Book fields = "__all__" labels = { "title": "书名", "price": "价格" } widgets = { "password": forms.widgets.PasswordInput(attrs={"class": "c1"}), }
class Meta:下常用参数:
model = models.Student # 对应的Model中的类 fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段 exclude = None # 排除的字段 labels = None # 提示信息 help_texts = None # 帮助提示信息 widgets = None # 自定义插件 error_messages = None # 自定义错误信息