Django Form 组件用于对页面进行初始化,生成 HTML 标签,此外还可以对用户提交对数据进行校验。
form组件的主要功能如下:
生成页面可用的HTML标签
对用户提交的数据进行校验
保留上次输入内容
创建类
使用 form 组件需要进行导入,导入的语句是: from django import forms
使用时需要在views.py中定义一个类,类中定义了用户输入的字段,大致结构如下
# 需要继承Form类
class Myfrom(forms.Form):
name = forms.CharField(min_length=3, label="姓名", error_messages={"min_length": "名称过短", "required": "该字段不能为空"})
age = forms.IntegerField(label="年龄")
salary = forms.DecimalField(label="工资")
实例化使用
在views.py文件中如下对该类实例化使用
# views.py
class Register(View):
def get(self, request):
# 实例化一个空对象
form_obj = Myfrom()
# 传入到页面中
return render(request, 'register.html', locals())
页面渲染
在register.html页面中渲染,需要注意的是,form标签和subimt按钮等等需要自己编写
1. 渲染方式一,使用高度封装的方法,如下
<form action="" method="post">
# 竖向展示
{{ form_obj.as_p }}
# 横向展示
{{ form_obj.as_table }}
# 类似于列表
{{ form_obj.as_ul }}
<input type="submit">
</form>
这种方式封装程度高,扩展性较差,主要用于快速生成页面测试功能
2. 渲染方式二,手动进行处理,如下示例
<form action="" method="post">
<label>
# 文本提示,若没有设置使用类中定义的字段名
{{ form_obj.username.label }}
# 输入标签
{{ form_obj.username }}
</label>
<label>
{{ form_obj.password.label }}
{{ form_obj.password }}
</label>
<input type="submit" class="btn btn-light">
</form>
此方法封装程度低,扩展性较好,但是字段比较多的情况下不方便
3. 渲染方式三,用for循环展示所有字段,如下示例
<form action="" method="post">
{% for foo in form_obj %}
<label>
{{ foo.label }}
{{ foo }}
</label>
{% endfor %}
<input type="submit" class="btn btn-light">
</form>
推荐使用for循环的方式展示,不用考虑字段的个数
校验数据
我们可以通过一些方法校验数据的对错
1. 将数据传入实例化对象
form_obj =MyForm({'username':'xxx','age':18,'email':'123qq'})
2. 查看数据是否合法(全部合法结果才是True)
form_obj.is_valid()
3. 查看不符合条件的数据及原因
form_obj.errors
4. 查看符合条件的数据
form_obj.cleaned_data
4. 查看字段名字和对字段象
form_obj.fiedls
"""
注意点
1.forms 类中所有的字段数据默认都是必填的,如果想忽略某些字段,需要添加 required=False
2.forms 类中额外传入的字段数据不会做任何的校验,直接忽略
"""
展示信息
错误信息在前端展示的方法是 errors.0 如下示例
# novalidate 取消在浏览器校验
<form action="{% url 'res' %}" method="post" novalidate>
{% for foo in form_obj %}
<label>
{{ foo.label }}
{{ foo }}
# 展示错误信息,需要点0
<p style="color: red">{{ foo.errors.0 }}</p>
</label>
{% endfor %}
<p><input type="submit" class="btn btn-light"></p>
</form>
没有设置错误信息会展示默认的提示信息,可以在 form 类字段中添加 error_messages 自定义错误提示
{{ field.label }}:字段的label,例如Email address。
{{ field.label_tag }}:包含在HTML 标签中的字段值。
{{ field.id_for_label }}:这个字段的ID值。
{{ field.value }}:字段的值
{{ field.html_name }}:该字段的标签中name属性使用的值。
{{ field.help_text }}:该字段的帮助文档。
{{ field.errors }}:字段的验证错误信息,字段标签会在属性中。
{{ field.is_hidden }}:如果该字段为隐藏字段,返回True。反之返回False。
{{ field.field }}:获取当前字段实例,可以用该属性来访问字段实例的属性
1. 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内容后缀
2. CharField(Field)
max_length=None, 最大长度
min_length=None, 最小长度
strip=True 是否移除用户输入空白
3. IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值
4. FloatField(IntegerField)
...
5. DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 总长度
decimal_places=None, 小数位长度
6. BaseTemporalField(Field)
input_formats=None 时间格式化
7. DateField(BaseTemporalField) 格式:2015-09-01
8. TimeField(BaseTemporalField) 格式:11:12
9. DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
10. DurationField(Field) 时间间隔:%d %H:%M:%S.%f
...
11. RegexField(CharField)
regex, 自定制正则表达式
max_length=None, 最大长度
min_length=None, 最小长度
error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}
12. EmailField(CharField)
...
13. FileField(Field)
allow_empty_file=False 是否允许空文件
14. ImageField(FileField)
...
注:需要PIL模块,pip3 install Pillow
以上两个字典使用时,需要注意两点:
- form表单中 enctype="multipart/form-data"
- view函数中 obj = MyForm(request.POST, request.FILES)
15. URLField(Field)
...
16. BooleanField(Field)
...
17. NullBooleanField(BooleanField)
...
18. ChoiceField(Field)
...
choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 插件,默认select插件
label=None, Label内容
initial=None, 初始值
help_text='', 帮助提示
18. ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查询数据库中的数据
empty_label="---------", # 默认空显示内容
to_field_name=None, # HTML中value的值对应的字段
limit_choices_to=None # ModelForm中对queryset二次筛选
19. ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField
20. TypedChoiceField(ChoiceField)
coerce = lambda val: val 对选中的值进行一次转换
empty_value= '' 空值的默认值
21. MultipleChoiceField(ChoiceField)
...
22. TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 对选中的每一个值进行一次转换
empty_value= '' 空值的默认值
23. ComboField(Field)
fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), 24. 24. fields.EmailField(),])
25. MultiValueField(Field)
PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
26. 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']
27. FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
required=True,
widget=None,
label=None,
initial=None,
help_text=''
28. GenericIPAddressField
protocol='both', both,ipv4,ipv6支持的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:29. protocol必须为both才能启用
30. SlugField(CharField) 数字,字母,下划线,减号(连字符)
...
31. UUIDField(CharField) uuid类型
initial
initial 参数用于定义初始值
# 该字段被展示时默认值为xxx
name = forms.CharField(max_length=32, initial='xxx')
validators
validators 参数可以用于正则的比较,需要和 RegexValidator 配合使用使用的方式如下所示
# 导入RegexValidator用于自定义正则表达式
from django.core.validators import RegexValidator
class MyForms(forms.Form):
name = forms.CharField(max_length=32)
pwd = forms.CharField(max_length=32)
phone = forms.CharField(
validators=[
# 正则表达式一,第二个参数是提示信息
RegexValidator(r'^[0-9]+$', '请输入数字'),
# 正则表达式二
RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
],
)
使用的时候需要先导入 RegexValidator ,然后在RegexValidator括号里编写正则即可,可以支持多个正则。
widget
widget 参数它可以配置forms组件的参数配置例如标签类型,属性
处理 input 的部件 | 类型 |
---|---|
TextInput | 默认类型,文本输入框 |
NumberInput | 数字输入框 |
EmailInput | 邮箱输入框 |
URLInput | 地址输入框 |
PasswordInput | 密码输入框 |
HiddenInput | 隐藏输入框 |
Textarea | 多行文本框 |
i1 = forms.CharField(max_length=32)
i2 = forms.CharField(max_length=32, widget=forms.PasswordInput(attrs={'class': 'form-control'}))
i3 = forms.CharField(max_length=32, widget=forms.PasswordInput(attrs={'style': 'background:red'}))
i4 = forms.CharField(max_length=32, widget=forms.NumberInput())
i5 = forms.CharField(max_length=32, widget=forms.EmailInput())
i6 = forms.CharField(max_length=32, widget=forms.URLInput())
i7 = forms.CharField(max_length=32, widget=forms.HiddenInput())
i8 = forms.CharField(max_length=32, widget=forms.DateInput())
i9 = forms.CharField(max_length=32, widget=forms.DateTimeInput())
i10 = forms.CharField(max_length=32, widget=forms.Textarea())
文件部件 | 类型 |
---|---|
FileInput | 文件类型 |
ClearableFileInput | |
ImageField | 图片文件 |
其他 | 类型 |
---|---|
CheckboxInput | 复选框 |
CheckboxSelectMultiple | 多选复选框 |
Select | 下拉框 |
SelectMultiple | 多选下拉框 |
NullBooleanSelect | 下拉框,选择是与否 |
RadioSelect | 单选框 |
单选框
class MyForms(forms.Form):
gender = forms.fields.ChoiceField(
# choices表示的是对应关系
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
# initial表示的是默认选中的单选框
initial=2,
widget=forms.RadioSelect()
)
单选复选框
class MyForms(forms.Form):
keep = forms.ChoiceField(
label="同意协议",
# 表示该单选复选框默认选中
initial="checked",
widget=forms.CheckboxInput()
)
多选复选框
class MyForms(forms.Form):
hobby = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
# 表示多选复选框的第一个和第三个
initial=[1, 3],
widget=forms.CheckboxSelectMultiple()
)
单选下拉框
class MyForms(forms.Form):
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=3,
widget=forms.Select()
)
多选下拉框
class MyForms(forms.Form):
hobby = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
choice字段
choice 字段用于表示对应关系,其实choices的选项可以配置从数据库中获取,但是由于是静态字段,获取的值无法实时
更新,需要重写构造方法从而实现choice实时更新。
方式一
from django.forms import Form
from django.forms import widgets
from django.forms import fields
class MyForm(Form):
user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.Select
)
def __init__(self, *args, **kwargs):
super(MyForm,self).__init__(*args, **kwargs)
# self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
# 或
self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
方式二
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()) # 单选
可通过重写form类的init方法来实现
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三",
error_messages={
"required": "不能为空",
"invalid": "格式错误",
"min_length": "用户名最短8位"
}
...
# 重写__init__方法
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'
})
钩子函数的含义其实就是在程序的执行过程中穿插额外的逻辑,也就是在字段检验后执行的逻辑函数。其分为局部钩子和全局钩子。例如校验用户名是否已存在属于钩子函数里的局部钩子(校验单个字段),而校验密码和确认密码是否一致是钩子函数里的全局钩子(校验多个字段)。
局部钩子可以钩取单个字段的数据进行判断,我们需要创建指定类型的函数名,类似于clean_字段名
class Myfroms(forms.Form):
name = forms.CharField(max_length=32)
pwd = forms.CharField(min_length=3)
# 创建局部钩子函数
def clean_name(self):
# 获取指定字段的值
name = self.cleaned_data.get('name')
# 获取用户对象
is_exist = models.Users.objects.filter(name=name)
# 比对该用户是否存在
if is_exist:
# 若存在添加错误信息,格式为 add_error('字段名', '错误信息')
self.add_error('name', '用户名已存在')
# 在最后要将勾出来的值返回出去
return name
'需要注意的是就是要将勾出来的值返回出去'
全局钩子可以钩取多个字段进行逻辑比较,例如俩个输入字段的值是否相同。全局钩子函数的名字是clean
class Myfroms(forms.Form):
name = forms.CharField(max_length=32)
pwd = forms.CharField(min_length=3)
agin_pwd = forms.CharField(min_length=3)
# 创建全局钩子函数
def clean(self):
# 获取密码字段值
pwd = self.cleaned_data.get('pwd')
# 获取重复密码字段值
agin_pwd = self.cleaned_data.get('agin_pwd')
# 若不相等,则添加错误信息
if pwd != agin_pwd:
self.add_error('agin_pwd', '重复密码不一致')
# 将所有勾出的值返回
return self.cleaned_data
# 或者使用下面一个一个返回,会麻烦一些
# return pwd, agin_pwd
'同样需要注意将勾出的值返回'
def is_valid(self):
"""
Returns True if the form has no errors. Otherwise, False. If errors are
being ignored, returns False.
"""
return self.is_bound and not self.errors
self.is_bound = data is not None or files is not None
@property
def errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors
发现 is_bound 表示的内容是字段输入的内容,只要有内容就是True
class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):
"A collection of Fields, plus their associated data."
self._errors = None
1. 在类双下init中设置了默认值为None
@property
def errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors
def full_clean(self):
... ...
self._clean_fields()
self._clean_form()
self._post_clean()
def _clean_fields(self):
for name, field in self.fields.items(): # 循环获得字段名和字段对象
... ...
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
# 将合法的数据放到 cleaned_data 中
self.cleaned_data[name] = value
# 反射判断类中是否有类似于 clean_XXX 的方法,也就是钩子函数
if hasattr(self, 'clean_%s' % name):
# 执行钩子函数
value = getattr(self, 'clean_%s' % name)()
# 将钩子函数的返回值放回 cleaned_data 里面,所以钩子函数必须有返回值
self.cleaned_data[name] = value
except ValidationError as e:
# 添加异常提示信息
self.add_error(name, e)
语句
from django.core.exceptions import ValidationError
raise ValidationError('提示语句')
示例
class Myfroms(forms.Form):
name = forms.CharField(max_length=32)
pwd = forms.CharField(min_length=3)
agin_pwd = forms.CharField(min_length=3)
def clean_name(self):
# 获取指定字段的值
name = self.cleaned_data.get('name')
# 获取用户对象
is_exist = models.Users.objects.filter(name=name)
# 比对该用户是否存在
if is_exist:
# 若存在添加错误信息,格式为 add_error('字段名', '错误信息')
# self.add_error('name', '用户名已存在')
# 主动抛出异常
from django.core.exceptions import ValidationError
raise ValidationError('用户名已存在了哦')
# 在最后要将勾出来的值返回出去
return name
forms组件主要配合models里面的某些类一起使用,但是某些类里面的字段需要在forms类中相当于重写一遍,代码冗余为了更好的结合forms与models的关系,有了一个ModelForm(基于forms组件)
创建 ModelForm 类
class Myfroms(forms.ModelForm):
class Meta:
model = models.Users # 指定关联的表
fields = '__all__' # 表中所有的字段全部自动生成对应的forms字段
labels = {
# 文字说明
'name': '用户名',
'age': '年龄',
'addr': '地址',
'email': '邮箱'
}
widgets = {
# 如果需要修改字段类型,可以在widgets中修改
'pwd': forms.PasswordInput()
}
新增表数据
class Myfroms(forms.ModelForm):
class Meta:
model = models.Users # 指定关联的表
fields = '__all__' # 表中所有的字段全部自动生成对应的forms字段
widgets = {
'pwd': forms.PasswordInput()
}
class Register(View):
def get(self, request):
form_obj = Myfroms()
return render(request, 'register.html', locals())
def post(self, request):
form_obj = Myfroms(request.POST)
if form_obj.is_valid():
# 将获取到的数据保存到表中
form_obj.save()
return render(request, 'register.html', locals())
修改表数据
class Myfroms(forms.ModelForm):
class Meta:
model = models.Users # 指定关联的表
fields = '__all__' # 表中所有的字段全部自动生成对应的forms字段
widgets = {
'pwd': forms.PasswordInput()
}
class Register(View):
def get(self, request):
form_obj = Myfroms()
return render(request, 'register.html', locals())
def post(self, request):
form_obj = Myfroms(request.POST)
if form_obj.is_valid():
# 查找指定的表记录对象
user_obj = models.Users.objects.filter(pk=1).first()
# 修改form_obj,并使用参数instance指向指定的记录对象
form_obj = Myfroms(request.POST, instance=user_obj)
# 将修改后的值保存
form_obj.save()
return render(request, 'register.html', locals())
跨站请求伪造(Cross-site request forgery),也被称为one-click attack或者session riding,通常缩写为CSRF或者XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。XSS利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
CSRF攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在未授权的情况下执行在权限保护
之下的操作,具有很大的危害性。具体来讲,可以这样理解CSRF攻击;攻击者盗用了你的身份,以你的名义发送恶意请求,对
服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账
号,添加系统管理员,甚至于购买商品、虚拟货币转账等。
CSRF 漏洞检测
检测CSRF漏洞是一项比较繁琐的工作,最简单的方法就是抓取一个正常请求的数据包,去掉Referer字段后再重新提交,如
果该提交还有效,那么基本上可以确定存在CSRF漏洞。随着对CSRF漏洞研究的不断深入,不断涌现出一些专门针对CSRF漏洞
进行检测的工具,如CSRFTester,CSRF Request Buider等。
以CSRFTester工具为例,CSRF漏洞检测工具的测试原理如下︰使用CSRFTester进行测试时,首先需要抓取我们在浏览器中
访问过的所有链接以及所有的表单等信息,然后通过在CSRFTester中修改相应的表单等信息,重新提交,这相当于一次伪造
客户端请求。如果修改后的测试请求成功被网站服务器接受,则说明存在CSRF漏洞,当然此款工具也可以被用来进行CSRF攻
击。
CSRF漏洞防御
CSRF漏洞防御主要可以从三个层面进行,即服务端的防御、用户端的防御和安全设备的防御。
服务端的防御
目前业界服务器端防御CSRF攻击主要有三种策略;验证HTTP Referer字段,在请求地址中添加token并验证,在HTTP头中
自定义属性并验证。下面分别对这三种策略进行简要介绍。
1. 验证HTTP Referer字段
2. 在请求地址中添加token并验证
CSRF攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都存在于Cookie中
因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的Cookie来通过安全验证。由此可知,抵御CSRF攻击
的关键在于:'在请求中放入攻击者所不能伪造的信息,并且该信息不存在于Cookie之中'。所以,可以在HTTP请求中以
参数的形式加入一个随机产生的'token',并在服务器端建立一个拦截器来验证这个token,如果请求中没有token或者
token内容不正确,则认为可能是CSRF攻击而拒绝该请求。
3. 在HTTP头中自定义属性并验证
自定义属性的方法也是使用token并进行验证,和前一种方法不同的是,这里并不是把token以参数的形式置于HTT
P请求之中,而是把它放到HTTP头中自定义的属性里。通过XMLHttpRequest这个类,给所有该类请求加上 csrftoken
这个HTTP头属性,并把token值放入其中。这样解决了前一种方法在请求中加入token的不便,同时,通过这个类请求的
地址不会被记录到浏览器的地址栏,也不用担心token会通过Referer泄露到其他网站。
django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成。
form表单设置csrf_token
使用模板语法在form表单内添加: {% csrf_token %}
装饰器配置
csrf_exempt 忽略csrf校验
csrf_protect 开启csrf校验
使用上面的csrf校验需要导入
导入语句: from django.views.decorators.csrf import csrf_exempt,csrf_protect
'''注意点'''
csrf_exempt、csrf_protect 俩方法在FBV结构中可以直接使用,而在CBV结构中需要导入额外的模块
并且 csrf_exempt 方法只有一种方式可以生效,就是重写的dispatch方法
示例
1. 在类中方法前使用 csrf_protect
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, csrf_protect
class Login(View):
def get(self, request):
pass
@method_decorator(csrf_protect)
def post(self, request):
pass
2. 在类外指定某个方法 csrf_protect
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, csrf_protect
@method_decorator(csrf_protect, name='post')
class Login(View):
def get(self, request):
pass
def post(self, request):
pass
3. 重写 dispatch 方法使用 csrf_protect
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, csrf_protect
class Login(View):
def get(self, request):
pass
def post(self, request):
pass
@method_decorator(csrf_protect)
def dispatch(self, request, *args, **kwargs):
return super(Login, self).dispatch(request, *args, **kwargs)
4. 重写 dispatch 方法使用 csrf_exempt
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, csrf_protect
class Login(View):
def get(self, request):
pass
def post(self, request):
pass
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(Login, self).dispatch(request, *args, **kwargs)