多对多三种创建方式,forms组件,cookie和session组件
一.多对多三种创建方式
1.全自动
优点:不需要手动创建第三张表
缺点:由于第三张表不是手动创建的,是orm自动创建的,也就意味着无法进行扩展,也就是加入其它字段
# 第一种方式,全自动 class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) author = models.ManyToManyField(to='Author') class Author(models.Model): name = models.CharField(max_length=32)
2.纯手动
优点:第三张表自己创建,所以可以添加任意字段,扩展性较高
缺点:缺少了多对多字段,orm查询不方便
class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) class Author(models.Model): name = models.CharField(max_length=32) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') create_time = models.DateTimeField(auto_now_add=True) # 可以扩展字段,扩展性较强
3.半自动
优点:结合了全自动和纯手动的两个优点
class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) author = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('book','author')) # through告诉orm两个表之间的多对多关系是由Book2Author表记录的 # through_fields告诉orm记录多对多关系时使用through_fields的book和author字段来记录 class Author(models.Model): name = models.CharField(max_length=32) class Book2Author(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author') create_time = models.DateTimeField(auto_now_add=True) # 可以扩展字段,扩展性较强
使用情况:
在不需要添加其它字段的业务逻辑较为简单的情况下可以使用第一种方式,其它情况下都推荐使用第三种方式,第二种方式不推荐
二.forms组件
1.自己手动实现一个注册功能
当用户的用户名包含'sb'时,提示用户名不符合规定
当用户的密码小于三位时,提示密码不能小于三位
def login(request): errors = {'username':'', 'password':''} if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') if '666' in username: errors['username'] = '用户名不符合规定' if len(password) < 3: errors['password'] = '密码不能小于三位' return render(request,'login.html',locals())
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> </head> <body> <form action="" method="post"> <p>username:<input type="text" name="username"><span style="color: red">{{ errors.username }}</span></p> <p>password:<input type="password" name="password"><span style="color: red">{{ errors.password }}</span></p> <input type="submit"> </form> </body> </html>
1.前端页面搭建 >>> 渲染页面 2.将数据传输到后端做校验 >>> 校验数据 3.展示错误信息 >>> 展示信息
使用form组件之后能够直接帮我们完成上述的三步操作
1.渲染前端页面 2.校验数据是否合法 3.展示错误信息
2.form组件的基本用法
1.写一个继承了forms.Form的类
from django import forms class LoginForm(forms.Form): username = forms.CharField(max_length=8,min_length=3) password = forms.CharField(max_length=8,min_length=2) email = forms.EmailField()
2.基本使用
# 1.将需要校验的数据,以字典的方式传递给自定义的累,实例化生成对象 from app01 import views form_obj =views.LoginForm({'username':'sxc','password':'1','email':'321'}) # 2.对象.is_valid()来查看数据是否全部合法,只有全部合法才会为True form_obj.is_valid() False # 3.对象.errors查看错误原因 form_obj.errors {'password': ['Ensure this value has at least 2 characters (it has 1).'], 'email': ['Enter a valid email address.']} # 4.对象.cleaned_data查看合法的数据 form_obj.cleaned_data {'username': 'sxc'}
注意:1.自定义类中的所有字段都需要传值,当少一个字段时也会返回False
2.但是我们可以额外传入类中没有定义的字段,只要类中的字段满足时就会返回True
3.form组件渲染页面
三种方式
第一种,封装程度太高
{# 第一种渲染方式,不推荐,封装程度太高,一般用于本地测试#} {{ form_obj.as_p }} {{ form_obj.as_table }} {{ form_obj.as_ul }}
第二种,书写麻烦
{# 第二种渲染方式,可扩展性高,但是书写麻烦#} <p>{{ form_obj.username.label }}{{ form_obj.username }}</p> <p>{{ form_obj.password.label }}{{ form_obj.password }}</p> <p>{{ form_obj.email.label }}{{ form_obj.email }}</p>
第三种,推荐
{# 第三种渲染方式,推荐#} {% for foo in form_obj %} <p>{{ foo.label }}{{ foo }}</p> {% endfor %}
注意:1.form组件在渲染页面的时候,只会获取用户在自定义类中定义的标签,提交按钮需要自己手动添加
2.input框的label注释,不指定的情况下,默认使用的是类中字段的首字母大写,可以在自定义类的时候指定默认值
4.后端校验并展示错误信息
在校验数据的时候,前后端都可以校验,做一个双重校验
但是,前端的校验是可以可无的,因为很容易被修改,而后端的校验是必须要有的
上图就是前端自动识别后作出的校验
前端取消浏览器校验功能只要在form标签指定novalidate属性即可
<form action="" method="post" novalidate>
此时,我们就可以进行后端校验的测试
def form_login(request): # 1.先生成一个空的自定义对象 form_obj = LoginForm() # 2.获取前端提交的数据进行校验 if request.method == 'POST': # 校验form组件中的数据需要传入一个字典,而request.POST获取的数据就是一个字典 form_obj = LoginForm(request.POST) if form_obj.is_valid(): pass return render(request,'form_login.html',locals())
后端代码,foo.errors可以拿到错误信息,但会展示成列表的形式,加.0就能正常展示
{% for foo in form_obj %} <p>{{ foo.label }}{{ foo }}<span style="color:red;">{{ foo.errors.0 }}</span></p> {% endfor %}
输入不能为空可以修改为可以为空,只需要在限制条件required=False即可
email = forms.EmailField(label='邮箱',required=False,error_messages={ 'invalid':'邮箱格式不正确', }) # email必须是邮箱格式
补充:
自定义类中的限制条件也可以写正则表达式
# 先导入正则相关的模块 from django.core.validators import RegexValidator # 在自定义类中声明的字段添加validators参数即可 phone = forms.CharField( validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')], )
5.form组件之钩子函数
局部钩子函数(针对某一个字段做出的额外校验)
# 局部钩子函数 def clean_username(self): username = self.cleaned_data.get('username') if 'sb' in username: self.add_error('username','不能有非法字符') return username
全局钩子函数(针对多个字段做校验)
# 全局钩子函数(针对多个字段做校验) def clean(self): password = self.cleaned_data.get('password') confirm_password = self.cleaned_data.get('confirm_password') if password != confirm_password: self.add_error('confirm_password','两次密码不一致') return self.cleaned_data
6.form组件其他字段及操作方式
字段中的一些主要属性
required 是否必填
label 注释信息
error_messages 报错信息
initial 默认值
widget 控制标签属性和样式
from django.forms import widgets # 先导入
widget=widgets.PasswordInput() 控制标签属性
widget=widgets.PasswordInput(attrs={'class':'form-control c1 c2','username':'jason'})
常用字段与插件
创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;
initial
初始值,input框里面的初始值。
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三" # 设置默认值 ) pwd = forms.CharField(min_length=6, label="密码")
error_messages
重写错误信息。
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="密码")
password
class LoginForm(forms.Form): ... pwd = forms.CharField( min_length=6, label="密码", widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) )
radioSelect
单radio值为字符串
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() )
三.cookie和seesion
cookie
Cookie的由来
大家都知道HTTP协议是无状态的。
无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。
一句有意思的话来描述就是人生只如初见,对服务器来说,每次的请求都是全新的。
状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。
什么是Cookie
Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。
Cookie的原理
cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。
Django中操作Cookie
Django返回给客户端浏览器的都必须是HttpResponse对象
return HttpResponse() return render() return redirect() # 等价于下面的 obj1 = HttpResponse() return obj1 obj2 = render() return obj2 obj3 = redirect() return obj3
设置cookie利用HttpResponse对象
obj.set_cookie('k1','v1')
获取cookie
request.COOKIE.get()
删除cookie
obj.delete_cookie('k1')
设置超时时间
max_age=None, 超时时间 expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)(针对IE浏览器的)
from functools import wraps def login_auth(func): @wraps(func) def inner(request,*args,**kwargs): target_url = request.get_full_path() if request.COOKIES.get('name'): res = func(request,*args,**kwargs) return res else: # 把用户登录前的页面放到url后缀中 r_url = '/lg/?next=%s'%target_url return redirect(r_url) return inner def lg(request): if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') if username == 'sxc' and password == '123': # 取出url后缀,如果用户之前在其他界面old_url就是其他页面,如果就是登录界面,就跳转到主页 print(request.GET.get('next')) old_url = request.GET.get('next','/home/') # 保存用户状态 obj = redirect(old_url) obj.set_cookie('name','sxc',max_age=80) return obj return render(request,'lg.html') @login_auth def home(request): return HttpResponse('这是home页面,登录才能看') @login_auth def index(request): return HttpResponse('这是index页面,登录才能看') # 删除cookie def logout(request): obj = redirect('/lg/') obj.delete_cookie('name') return obj
session
Session的由来
Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。
问题来了,基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。
我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。
总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。
另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架。
session就是保存在服务器上的键值对
session虽然是保存在服务器上的键值对
但是它是依赖于cookie工作的
服务端返回给浏览器一个随机的字符串
浏览器以键值对的形式保存
sessionid:随机字符串
浏览器在访问服务端的时候 就会将随机字符串携带上
后端获取随机串与后端的记录的做比对
随机字符串1:数据1
随机字符串2:数据2
Django中Session相关方法
设置session
request.session['name'] = 'sxc' """ 上面这一句话发生了三件事 1.django 内部自动生成一个随机字符串 2.将随机字符串和你要保存的数据 写入django_session表中(现在内存中生成一个缓存记录 等到经过中间件的时候才会执行) 3.将产生的随机字符串发送给浏览器写入cookie sessionid:随机字符串 """
获取session
request.session.get('name') """ 上面这一句话发生了三件事 1.django内部会自动从请求信息中获取到随机字符串 2.拿着随机字符串去django_session表中比对 3.一旦对应上了就将对应的数据解析出来放到request.session中 """
# 获取、设置、删除Session中数据 request.session['k1'] request.session.get('k1',None) request.session['k1'] = 123 request.session.setdefault('k1',123) # 存在则不设置 del request.session['k1'] # 所有 键、值、键值对 request.session.keys() request.session.values() request.session.items() request.session.iterkeys() request.session.itervalues() request.session.iteritems() # 会话session的key request.session.session_key # 将所有Session失效日期小于当前日期的数据删除 request.session.clear_expired() # 检查会话session的key在数据库中是否存在 request.session.exists("session_key") # 删除当前会话的所有Session数据 request.session.delete() # 删除的是浏览器的sessionid信息 # 删除当前的会话数据并删除会话的Cookie。 request.session.flush() # 将浏览器和服务端的全部删除 这用于确保前面的会话数据不可以再次被用户的浏览器访问 例如,django.contrib.auth.logout() 函数中就会调用它。 # 设置会话Session和Cookie的超时时间 request.session.set_expiry(value) * 如果value是个整数,session会在些秒数后失效。 * 如果value是个datatime或timedelta,session就会在这个时间后失效。 * 如果value是0,用户关闭浏览器session就会失效。 * 如果value是None,session会依赖全局session失效策略。
Session流程解析
Django session默认的超时时间是14天(面试容易问)
Django_session表中的一条记录针对一个浏览器,一个浏览器只会有一条记录
总结:你在后期可以将一些数据保存到session表中,保存的数据 可以在后端任意位置获取到
58