一、forms组件
1.功能
- 渲染页面
- 校验数据
- 展示信息
需要先写一个类,该类类似于models中的表类
from django import forms class MyRegForm(forms.Form): # 用户名最少3位最多8位 username = forms.CharField(max_length=8,min_length=3) password = forms.CharField(max_length=8,min_length=3) # email字段必须填写符合邮箱格式的数据 email = forms.EmailField()
2.如何校验数据
# 1.传入待校验的数据 用自己写的类 传入字典格式的待校验的数据 form_obj = views.MyRegForm({'username':'jason','password':'12','email':'123456'}) # 2.判断数据是否符合校验规则 form_obj.is_valid() # 该方法只有在所有的数据全部符合校验规则才会返回True False # 3.如何获取校验之后通过的数据 form_obj.cleaned_data {'username': 'jason'} # 4.如何获取校验失败及失败的原因 form_obj.errors { 'password': ['Ensure this value has at least 3 characters (it has 2).'], 'email': ['Enter a valid email address.'] } # 5.注意 forms组件默认所有的字段都必须传值 也就意味着传少了是肯定不行的 而传多了则没有任何关系 只校验类里面写的字段 多传的直接忽略了 form_obj = views.MyRegForm({'username':'jason','password':'123456'}) form_obj.is_valid() Out[12]: False form_obj.errors Out[18]: {'email': ['This field is required.']} form_obj = views.MyRegForm({'username':'jason','password':'123456',"email":'123@qq.com',"hobby":'hahahaha'}) form_obj.is_valid() Out[14]: True form_obj.cleaned_data Out[15]: {'username': 'jason', 'password': '123456', 'email': '123@qq.com'} form_obj.errors Out[16]: {}
数据校验一个前后端都得有,但是前端的校验弱不禁风,可有可无,而后端的校验则必须非常全面
取消校验功能:
form表单取消前端浏览器自动校验功能
<form action="" method="post" novalidate>
3.如何渲染页面
forms组件只帮你渲染获取用户输入(输入、选择、下拉、文件)的标签,不渲染按钮和form表单标签,渲染出来的每一个input提示信息都是类中字段首字母大写
<p>第一种渲染方式:多个p标签 本地测试方便 封装程度太高了 不便于扩展</p> {{ form_obj.as_p }} {{ form_obj.as_ul }} {{ form_obj.as_table }}
<p>第二种渲染方式: 扩展性较高 书写较为繁琐</p> <label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label> {{ form_obj.username }} {{ form_obj.password.label }}{{ form_obj.password }} {{ form_obj.email.label }}{{ form_obj.email }}
<p>第三种渲染方式 推荐使用</p> {% for form in form_obj %} <p> {{ form.label }}{{ form }}</p> {% endfor %}
4.如何渲染错误信息
前端
<form action="" method="post" novalidate> {% for form in form_obj %} <p> {{ form.label }}{{ form }} <span>{{ form.errors.0 }}</span> # 这个是模板语法 索引不会出现超出报错 </p> {% endfor %} <input type="submit"> </form>
后端
def reg(request): # 1 先生成一个空的类对象 form_obj = MyRegForm() if request.method == 'POST': # 3 获取用户数据并交给forms组件校验 request.POST form_obj = MyRegForm(request.POST) # 4 获取校验结果 if form_obj.is_valid(): return HttpResponse('数据没问题') else: # 5 获取校验失败的字段和提示信息 print(form_obj.errors) # 2 直接将该对象传给前端页面 return render(request,'reg.html',locals())
5.常用参数
label input的提示信息
error_messages 自定义报错的提示信息
required 设置字段是否允许为空
initial 设置默认值
widget 控制type类型及属性
- widget=forms.widgets.TextInput(attrs={'class':'form-control c1 c2'})
- widget=forms.widgets.PasswordInput(attrs={'class':'form-control'})
6.钩子函数
作用:实现自定义的验证功能
全局钩子:
def clean(self): # 校验密码和确认密码是否一致 password = self.cleaned_data.get('password') confirm_password = self.cleaned_data.get('confirm_password') if not password == confirm_password: # 展示提示信息 self.add_error('confirm_password','两次密码不一致') return self.cleaned_data
局部钩子:
def clean_username(self): username = self.cleaned_data.get('username') if '666' in username: self.add_error('username','光喊666是不行的') return username
小结:如果你想同时操作多个字段的数据你就用全局钩子,如果你想操作单个字段的数据你就用局部钩子
7.其他渲染方式
- radioSelect
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三", error_messages={ "required": "不能为空", "invalid": "格式错误", "min_length": "用户名最短8位" } ) pwd = forms.CharField(min_length=6, label="密码") gender = forms.fields.ChoiceField( choices=((1, "男"), (2, "女"), (3, "保密")), label="性别", initial=3, widget=forms.widgets.RadioSelect() )
- 单选select
class LoginForm(forms.Form): ... hobby = forms.ChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=3, widget=forms.widgets.Select() )
- 多选select
class LoginForm(forms.Form): ... hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=[1, 3], widget=forms.widgets.SelectMultiple() )
- 单选checkbox
class LoginForm(forms.Form): ... keep = forms.ChoiceField( label="是否记住密码", initial="checked", widget=forms.widgets.CheckboxInput() )
- 多选checkbox
class LoginForm(forms.Form): ... hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), label="爱好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() )
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类型
补充:
正则校验:
phone = forms.CharField( validators=[ RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头') ] )
例子:使用form组件实现注册功能
先定义好一个RegForm类:
from django import forms # 按照Django form组件的要求自己写一个类 class RegForm(forms.Form): name = forms.CharField(label="用户名") pwd = forms.CharField(label="密码")
再写一个视图函数:
# 使用form组件实现注册方式 def register2(request): form_obj = RegForm() if request.method == "POST": # 实例化form对象的时候,把post提交过来的数据直接传进去 form_obj = RegForm(request.POST) # 调用form_obj校验数据的方法 if form_obj.is_valid(): return HttpResponse("注册成功") return render(request, "register2.html", {"form_obj": form_obj})
前端页面:login2.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册2</title> </head> <body> <form action="/reg2/" method="post" novalidate autocomplete="off"> {% csrf_token %} <div> <label for="{{ form_obj.name.id_for_label }}">{{ form_obj.name.label }}</label> {{ form_obj.name }} {{ form_obj.name.errors.0 }} </div> <div> <label for="{{ form_obj.pwd.id_for_label }}">{{ form_obj.pwd.label }}</label> {{ form_obj.pwd }} {{ form_obj.pwd.errors.0 }} </div> <div> <input type="submit" class="btn btn-success" value="注册"> </div> </form> </body> </html>
二、cookie
1.什么是cookie?
Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。
原理:服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。
简而言之就是指保存在客户端浏览器上的键值对
2.设置cookie
rep = HttpResponse(...) rep = render(request, ...) rep.set_cookie(key,value,...) rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)
参数:
- key, 键
- value='', 值
- max_age=None, 超时时间
- expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
- path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
- domain=None, Cookie生效的域名
- secure=False, https传输
- httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
3.删除cookie
def logout(request): rep = redirect("/login/") rep.delete_cookie("user") # 删除用户浏览器上之前设置的usercookie值 return rep
例子:Cookie版登陆校验:
def check_login(func): @wraps(func) def inner(request, *args, **kwargs): next_url = request.get_full_path() if request.get_signed_cookie("login", salt="SSS", default=None) == "yes": # 已经登录的用户... return func(request, *args, **kwargs) else: # 没有登录的用户,跳转刚到登录页面 return redirect("/login/?next={}".format(next_url)) return inner def login(request): if request.method == "POST": username = request.POST.get("username") passwd = request.POST.get("password") if username == "xxx" and passwd == "dashabi": next_url = request.GET.get("next") if next_url and next_url != "/logout/": response = redirect(next_url) else: response = redirect("/class_list/") response.set_signed_cookie("login", "yes", salt="SSS") return response return render(request, "login.html")
三、session
1.什么是session?
Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session
简而言之,Session就是保存在服务端上的键值对
2.设置session
request.session['key'] = value
实现该方法内部机制:
- django内部会自动生成一个随机字符串
- 去django_session表中存储数据,键就是随机字符串,值是要保存的数据
- 将生成好的随机字符串返回给客户端浏览器,浏览器保存键值对
3.获取session
request.session.get('key')
实现该方法内部机制:
- django会自动取浏览器的cookie查找sessionid键值对,获取随机字符串
- 拿着该随机字符串取django_session表中比对数据
- 如果比对上了,就将随机字符串对应的数据获取出来并封装到request.session供用户调用
4.设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
- 如果value是个整数,session会在些秒数后失效。
- 如果value是个datatime或timedelta,session就会在这个时间后失效。
- 如果value是0,用户关闭浏览器session就会失效。
- 如果value是None,session会依赖全局session失效策略。
django中默认的session超时时间为14天
5.删除session
request.session.delete():删除当前会话的所有Session数据
request.session.flush():删除当前的会话数据并删除会话的Cookie,用于确保前面的会话数据不可以再次被用户的浏览器访问
补充:django中默认的session超时时间为14天
例子:Session版登陆验证
from functools import wraps def check_login(func): @wraps(func) def inner(request, *args, **kwargs): next_url = request.get_full_path() if request.session.get("user"): return func(request, *args, **kwargs) else: return redirect("/login/?next={}".format(next_url)) return inner def login(request): if request.method == "POST": user = request.POST.get("user") pwd = request.POST.get("pwd") if user == "alex" and pwd == "alex1234": # 设置session request.session["user"] = user # 获取跳到登陆页面之前的URL next_url = request.GET.get("next") # 如果有,就跳转回登陆之前的URL if next_url: return redirect(next_url) # 否则默认跳转到index页面 else: return redirect("/index/") return render(request, "login.html") @check_login def logout(request): # 删除所有当前请求相关的session request.session.delete() return redirect("/login/") @check_login def index(request): current_user = request.session.get("user", None) return render(request, "index.html", {"user": current_user}) Session版登录验证