Form组件使用
form组件
form组件的功能
- 生产input标签
- 对提交的数据进行校验
- 提供错误提示
form组件中常用字段
# 查看所有字段可点进forms中,fields里面是所有字段,widgets是所有插件
CharField # 生成input框,默认文本输入框
ChoiceField # 选择框,默认是select单选下拉框
MultipleChoiceField # 多选框,默认是select下拉框。里面是选择的内容
注:想更改可以用已经写好的字段,也可以直接改widget
如:email = forms.CharField(label='邮箱',widget=EmailInput)将文本输入框改成了邮箱输入框。
字段参数
required=True, 是否允许为空
widget=None, HTML插件,改变input框中格式,如:从文本变成email
label=None, 用于生成Label标签或显示内容
initial=None, 初始值,默认值,默认填在input框中
error_messages=None, 修改错误信息显示内容 {'required': '不能为空', 'invalid': '格式错误'}
键是对应的参数,值是想要显示的错误信息内容
validators=[], 自定义验证规则
disabled=False, 是否可以编辑
min_length: 设置最小长度
# widget=forms.PasswordInput() 设置成密文
# widget=forms.RadioSelect() 设置单选框
# widget=CheckboxInput() 设置input选择框,应用:记住密码
# widget=forms.CheckboxSelectMultiple() 设置多选框
模板HTML文件
注:模板中使用方法都是不加括号的。
{{ form_obj.as_p }} __> 生产一个个P标签 input label,实际使用不使用这个
{{ form_obj.errors }} ——》 form表单中所有字段的错误
{{ form_obj.username }} ——》 一个字段对应的input框,实际使用中
{{ form_obj.username.label }} ——》 该字段的中文提示
{{ form_obj.username.id_for_label }} ——》 该字段input框的id
{{ form_obj.username.errors }} ——》 该字段的所有的错误
{{ form_obj.username.errors.0 }} ——》 该字段的第一个错误的错误
{{ form_obj.non_field_errors.0 }} ---》 所有字段以外的错误
使用form组件实现注册功能
-
views.py
先定义一个RegForm类:
from django import forms class RegForm(forms.Form) username = forms.CharField(label='用户名',min_length=6) pwd = forms.CharField(label='密码',widget=forms.PasswordInput) # widget设置密文 hobby = forms.MultipleChoiceField(choices=((1,'篮球'),(2,'足球'),(3,'双色球'))) ## 使用get_字段名_display()方法,获取到choices后面显示的结果。 error_messages={ 'required': '用户名是必填项', 'min_length': '用户名长度不能小于6位' } # form组件默认错误提示都是英文,可以通过这种方式自定义成中文。 # min_length: 设置最小长度 # widget=forms.PasswordInput 设置成密文 # choice=((),())
-
再写一个视图函数
# 使用form组件实现注册方式 def register(request): form_obj = RegForm() if request.method == 'POST': form_obj = RegForm(data=request.POST) if form_obj.is_valid(): # 对数据进行校验,返回值是True或False # 校验成功 把数据插入数据中 # models.UserProfile.objects.create(**form_obj.cleaned_data) return HttpResponse('注册成功') return render(request,'register.html',{'form_obj':form_obj})
-
html文件
<form action="" method="post" novalidate> {% csrf_token %} <p> <label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label> {{ form_obj.username }} {{ form_obj.username.errors.0 }} </p> </form>
-
如果部分数据需要从数据库中获取,那么数据需要随时更新,但是py文件并不是每次都加载,只加载一次放到内存中,py文件从上到下加载,RegForm()这个类中的内容只加载一次存到内存中,视图函数使用时,只是实例化这个类,并不会重新加载。只有当程序重启时才会重新加载,那么数据库中如果更新,则无法获取到新的数据。想让数据时时更新
# 手动定义一个__init__,只要类实例化就会执行一次__init__,时时查询 def __init__(self,*args,**kwargs): super(RegForm,self).__init__(*args,**kwargs) # 继承父类,执行父类方法,否则会丢失功能 self.fields['hobby'].choices = models.Hobby.objects.values_list('id','name') # values_list 由id,name组成的一个个元组的列表 # self.fields会拿到RegForm类中所有字段的对象,是一个有序字典,如下 OrderedDict([ ('username', <django.forms.fields.CharField object at 0x0000000004108C18>), ('pwd', <django.forms.fields.CharField object at 0x0000000004108F60>), ('hobby', <django.forms.fields.CharField object at 0x0000000004108978>) ]) # 直接以下面方法写 hobby = forms.ModelMultipleChoiceField( # 多选框 queryset=models.Hobby.objects.all(), )
form验证
内置校验
- required=True
- min_length
- max_length
自定义校验器
class RegForm(forms.Form): # 需要一个一个字段写
username = forms.CharField(
label='用户名',
min_length=6,
initial='张三',
required=True,
validators=[checkname], # 列表中填写自定义的校验器,会把字段的值自动传给校验器进行校验
widget = forms.TextInput(attrs={...})
# attrs定义标签属性
error_messages={
'required': '用户名是必填项',
'min_length': '用户名长度不能小于6位'
}
)
gender = forms.ChoiceField(choices=[(1,'男'),(2,'女')]) # 单选
# 方式一:要定义一个函数
from django.core.exceptions import ValidationError
def checkname(value): # 传的值就是字段要提交的数据,对value进行校验就行了
# 通过校验规则 不做任何操作
# 不通过校验规则 抛出异常
if 'alex' in value:
raise ValidationError('不符合社会主义核心价值观')
# 方式二:内置的校验器
from django.core.validators import RegexValidator # 导入模块,正则校验
# validators还有一些其他格式的校验器
class RegForm(forms.Form):
phone = forms.CharField(
validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式不正确')]
)
# RegexValidator(),第一个参数写正则表达式,第二个参数写错误提示
钩子函数
-
什么叫钩子:
-
其主要思想是提前在可能增加功能的地方埋好(预设)一个钩子,这个钩子并没有实际的意义,当我们需要重新修改或者增加这个地方的逻辑的时候,把扩展的类或者方法挂载到这个点即可。
-
局部钩子
def clean_username(self): # 局部钩子,对单独一个字段进行校验 # 通过校验规则 必须返回当前字段的值 # 不通过校验规则 抛出异常 v = self.cleaned_data.get('username') # 能走到局部钩子,cleaned_data中就有值了,不然无法通过内置校验器和自定义校验器 if 'alex' in v: raise ValidationError('不符合社会主义核心价值观') else: return v
-
全局钩子
def clean(self): # 全局钩子,对多个字段进行校验 # 通过校验规则 必须返回当前所有字段的值 # 不通过校验规则 抛出异常 '__all__' # 如果重写clean方法,必须写_validate_unique = True,不然不会校验字段的唯一性 # 会检查model中的unique的限制 self._validate_unique = True pwd = self.cleaned_data.get('pwd') re_pwd = self.cleaned_data.get('re_pwd') if pwd == re_pwd: return self.cleaned_data else: self.add_error('re_pwd','两次密码不一致!!!!!') # 将错误信息添加到错误字典 raise ValidationError('两次密码不一致')
is_valid的校验流程
#### is_valid的流程:执行is_valid()的流程
1.先执行full_clean()的方法:
- 定义错误字典
- 定义存放清洗数据的字典
2.执行_clean_fields方法:
- 循环所有的字段
- 获取当前的值
- 进行校验 ( 内置校验规则 自定义校验器)
1. 通过校验
self.cleaned_data[name] = value
- 如果有局部钩子,执行进行校验:
- 通过校验——》 self.cleaned_data[name] = value
- 不通过校验——》
self._errors 添加当前字段的错误 并且 self.cleaned_data中当前字段的值删除掉
2. 没有通过校验
self._errors 添加当前字段的错误
3.执行全局钩子clean方法
# 最终的判断就是错误字典中是否有值
ModelForm
-
使用方法
########form组件使用步骤############ 1.先导入forms模块 2.写类,继承forms.ModelForm 3.在类中再写元类Meta 4.在Meta中写字段model,代表根据哪个Model生字段,---》model = models.UserProfile # UserProfile是创建表的类名 5.字段fields,表示生成此Model中的哪些字段。__all__表示所有字段都生成,fields = '__all__'; 单独生成某几个字段 6.如果表中没有的字段,可在类下创建 ###########代码如下################ from django import forms from app名称 import models from django.core.exceptions import ValidationError class RegForm(forms.ModelForm) # 与继承forms.Form的区别在于,forms.Form要在类中写每一个需要创建的input框 # forms.ModelForm不需要每个都写,会根据某一个Model具体的生成每一个字段 password = forms.CharField(widget=forms.PasswordIput('placeholder':'您的密码')) # 重写样式 re_password = forms.CharField(widget=forms.PasswordIput('placeholder':'再次输入密码')) def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs) # 限制当前新增客户为当前访问客户 self.fields['customer'].choices = [(self.instance.customer.pk,self.instance.customer)] # 表中没有的字段可在Meta类下创建 class Meta: model = models.UserProfile # 根据哪个Model生成字段,UserProfile是创建表的类名 fields = '__all__' # 生成表中的哪些字段,'__all__'是生成所有的 # 填写指定字段['username','password'] exclud = ['is_active'] # 排除is_active字段 widgets = { # 字典内写上指定字段的名字 'username':forms.TextInput(attrs={'placeholder':'您的用户名','autocomplete':'off'}) } # autocomplete表示填写记录 # 可以对表中的所有字段样式,进行修改 # attrs中写想要更改的相关属性 error.messages = { # 错误信息 'username':{ 'required':'必填' # 必填 'invalid':'请输入正确邮箱地址' # 格式错误 } } # 在settings配置文件中将LANGUAGE_CODE = 'zh-Hans' def clean(self): password = self.cleaned_data.get('password',"") # 当都不填时会返回None在加密时报错,所以让他获取不到password时获取一个空字符串 re_password = self.cleaned_data.get('re_password',"") if password == re_password: # 对密码进行加密 md5 = hashlib.md5() md5.update(password.encode('utf-8')) self.cleaned_data['password'] = md5.hexdigest() return self.cleaned_data else: self.add_error('re_password', '两次密码不一致') raise ValidationError('两次密码不一致!!') ######视图函数###### def reg(request): # 视图函数 form_obj = RegForm() if request.method == 'POST': form_obj = RegForm(request.POST) if form_obj.is_valid(): form_obj.save() return redirect(reverse('login')) return render(request,'reg.html',{'form_obj':form_obj}) #######模板语法####### <form action='' ,method='post' novalidate> # novalidate 不在前端进行校验,前端写的校验会失效 {% for field in form_obj %} <div class="form-group {% if field.errors %}has-error{% endif %}"> <label {% if not field.field.required %} style="color: #777777" {% endif %} for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label> <div class="col-sm-8"> {{ field }} <span class="help-block">{{ field.errors.0 }}</span> </div> </div> {% endfor %} {{ form_obj.errors }} ——》 form表单中所有字段的错误 {{ form_obj.username }} ——》 一个字段对应的input框,一般使用这个一个个input创建 {{ form_obj.username.label }} ——》 该字段的中文提示 {{ form_obj.username.id_for_label }} ——》 该字段input框的id {{ form_obj.username.errors }} ——》 该字段的所有的错误 {{ form_obj.username.errors.0 }} ——》 该字段的第一个错误的错误 {{ form_obj.non_filed_errors }} ——》 # __all__的错误,不限于表单中 </form>
-
更改input框的中文名显示方式
1.创建表时填写verbose_name属性,值为想要显示的中文名 2.在常见input框时有label标签,可以显示中文
modelformset_factory
-
modelformset_factory
会先创建一个类,参数有model,Form类,extra- model:模型,表的类名
- Form:form表单的类名
- extra:默认为1,未添加数据是显示一条空数据,设置为0则可不设置该条空数据
ModelFormSet = modelformset_factory(models.StudyRecord, StudyRecordForm, extra=0)
def study_record_list(request, course_record_id): ModelFormSet = modelformset_factory( models.StudyRecord, StudyRecordForm, extra=0) form_set_obj = ModelFormSet(queryset=models.StudyRecord.objects.filter (course_record_id=course_record_id)) if request.method == 'POST': form_set_obj = ModelFormSet(queryset=models.StudyRecord.objects.filter (course_record_id=course_record_id),data=request.POST) if form_set_obj.is_valid(): form_set_obj.save() return HttpResponse('保存成功') return render(request, 'teacher/study_record_list.html', {'form_set_obj': form_set_obj})
-
模板中
<!--文本不可修改的内容用该方式取--> <td>{{ form.instance.student }}</td> // form.instance 是一个对象 // form.instance.student 通过对象拿到其中的字段值 <!--在模板中使用--> 在form标签下必须添加如下内容,固定写法 {{ form_set_obj.management_form }} <!--会生成4个隐藏标签--> <input type="hidden" name="form-TOTAL_FORMS" value="2" id="id_form-TOTAL_FORMS"> <input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS"> <input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"> <input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS"> 在循环创建form表单时,再循环下加{{ form.id }},用来标识form表单,目的找对应的对象,生成隐藏input标签 <input type="hidden" name="form-1-id" value="2" id="id_form-1-id"> <!--代码示例--> <form action="" method="post"> {% csrf_token %} {{ form_set_obj.management_form }} <table class="table table-bordered table-hover"> <thead> <tr> <th>序号</th> <th>学生</th> <th>考勤</th> <th>成绩</th> <th>作业批语</th> </tr> </thead> <tbody> {% for form in form_set_obj %} <tr> {{ form.id }} <td>{{ forloop.counter }}</td> <td>{{ form.instance.student }}</td> <td>{{ form.attendance }}</td> <td>{{ form.score }}</td> <td>{{ form.homework_note }}</td> <td class="hidden">{{ form.student }}</td> <td class="hidden">{{ form.course_record }}</td> </tr> {% endfor %} </tbody> </table> <button class="btn btn-primary">保存</button> </form>