ORM操作
ORM概念
对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。
简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
ORM在业务逻辑层和数据库层之间充当了桥梁的作用。
ORM由来
让我们从O/R开始。字母O起源于"对象"(Object),而R则来自于"关系"(Relational)。
几乎所有的软件开发过程中都会涉及到对象和关系数据库。在用户层面和业务逻辑层面,我们是面向对象的。当对象的信息发生变化的时候,我们就需要把对象的信息保存在关系数据库中。
按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂很多SQL语句用来增加、读取、修改、删除相关数据,而这些代码通常都是极其相似或者重复的。
ORM的优势
ORM解决的主要问题是对象和关系的映射。它通常将一个类和一张表一一对应,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段。
ORM提供了对数据库的映射,不用直接编写SQL代码,只需操作对象就能对数据库操作数据。
让软件开发人员专注于业务逻辑的处理,提高了开发效率。
ORM的劣势
ORM的缺点是会在一定程度上牺牲程序的执行效率。
ORM的操作是有限的,也就是ORM定义好的操作是可以完成的,一些复杂的查询操作是完成不了。
ORM用多了SQL语句就不会写了,关系数据库相关技能退化...
ORM总结
ORM只是一种工具,工具确实能解决一些重复,简单的劳动。这是不可否认的。
但我们不能指望某个工具能一劳永逸地解决所有问题,一些特殊问题还是需要特殊处理的。
但是在整个软件开发过程中需要特殊处理的情况应该都是很少的,否则所谓的工具也就失去了它存在的意义。
Django中的ORM
Django项目使用MySQL数据库
1. 在Django项目的settings.py文件中,配置数据库连接信息:
DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "你的数据库名称", # 需要自己手动创建数据库 "USER": "数据库用户名", "PASSWORD": "数据库密码", "HOST": "数据库IP", "POST": 3306 } }
2. 在与Django项目同名的目录下的__init__.py文件中写如下代码,告诉Django使用pymysql模块连接MySQL数据库
import pymysql
pymysql.install_as_MySQLdb()
注:数据库迁移的时候出现一个警告
WARNINGS: ?: (mysql.W002) MySQL Strict Mode is not set for database connection 'default' HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL, such as data truncation upon insertion, by escalating warnings into errors. It is strongly recommended you activate it.
在配置中多加一个OPTIONS参数:Django官网解释
'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"},
Model
字段
常用字段
AutoField
自增的整形字段,必填参数primary_key=True,则成为数据库的主键。无该字段时,django自动创建。
一个model不能有两个AutoField字段。
IntegerField
一个整数类型。数值的范围是 -2147483648 ~ 2147483647。
CharField
字符类型,必须提供max_length参数。max_length表示字符的长度。
DateField
日期类型,日期格式为YYYY-MM-DD,相当于Python中的datetime.date的实例。
参数:
- auto_now:每次修改时修改为当前日期时间。
- auto_now_add:新创建对象时自动添加当前日期时间。
auto_now和auto_now_add和default参数是互斥的,不能同时设置。
DatetimeField
日期时间字段,格式为YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime的实例。
字段类型,详情可点击查询官网。
AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id的列 from django.db import models class UserInfo(models.Model): # 自动创建一个列名为id的且为自增的整数列 username = models.CharField(max_length=32) class Group(models.Model): # 自定义自增列 nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数, max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField): - 字符串类型,Django Admin以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 - 参数: protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6" unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both" URLField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField(CharField) - 字符串类型,格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField(Field) - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 - 参数: path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 FileField(Field) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage width_field=None, 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10进制小数 - 参数: max_digits,小数总长度 decimal_places,小数位长度 BinaryField(Field) - 二进制类型 字段类型
创建数据库
models.py
from django.db import models # Create your models here. class Biao(models.Model): nid = models.AutoField(primary_key=True) #设置主键 name = models.CharField(max_length=32) age = models.IntegerField() birth = models.DateTimeField(auto_now_add=True) #时间为当前时间
建库
python3 manage.py makemigrations
python3 manage.py migrate
字段参数
null 数据库中字段是否可以为空 db_column 数据库中字段的列名 default 数据库中字段的默认值 primary_key 数据库中字段是否为主键 db_index 数据库中字段是否可以建立索引 unique 数据库中字段是否可以建立唯一索引 unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引 unique_for_month 数据库中字段【月】部分是否可以建立唯一索引 unique_for_year 数据库中字段【年】部分是否可以建立唯一索引 verbose_name Admin中显示的字段名称 blank Admin中是否允许用户输入为空 editable Admin中是否可以编辑 help_text Admin中该字段的提示信息 choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作 如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1) error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息; 字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date 如:{'null': "不能为空.", 'invalid': '格式错误'} validators 自定义错误验证(列表类型),从而定制想要的验证规则 from django.core.validators import RegexValidator from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\ MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator 如: test = models.CharField( max_length=32, error_messages={ 'c1': '优先错信息1', 'c2': '优先错信息2', 'c3': '优先错信息3', }, validators=[ RegexValidator(regex='root_\d+', message='错误了', code='c1'), RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'), EmailValidator(message='又错误了', code='c3'), ] ) 字段参数
ORM查询操作
13种操作方法
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "diccc.settings") import django django.setup() from app01 import models
aa = models.Biao.objects.all() # all()获取到所有的数据 # get 获取一个满足条件的对象,获取不到或者获取多个就报错(一般不用) bb = models.Biao.objects.get(age=25) # filter 获取满足条件的所有对象,获取不到返回空,不会报错 cc = models.Biao.objects.filter(name='wk') #first()获取多个对象的第一个 dd = models.Biao.objects.filter(name='wk').first() #last() 获取多个对象的最后一个 ee = models.Biao.objects.filter(name='wk').last() #exclude 获取不满足条件的所有对象,获取不到返回空,不会报错 ff = models.Biao.objects.exclude(name='wk') #values() 获取数据的key以及对应的值,获取的是字典形式的QuerySet gg = models.Biao.objects.all().values() #以键值的形式获取所有数据 hh = models.Biao.objects.all().values('name','age') #也可以获取指定键的 #values_list() 以元组的形式获取数据,根据位置取值 ii = models.Biao.objects.all().values_list() #order_by 排序 可指定字段,也可指定多个字段,多字段先排第一个,加"-"降序 jj = models.Biao.objects.order_by('-nid','age') #reverse()反向排序, 对一个有序列表才有效 kk = models.Biao.objects.order_by('age').reverse() #distinct() 去重 ll = models.Biao.objects.values('name').distinct() # count() 计数,记录有多少数据 返回一个整数 mm = models.Biao.objects.count() # exists()判断数据是否存在 不在返回False 存在True nn = models.Biao.objects.first(name="wk").exists()
单表的双下划线方法
# __gt大于 __lt小于 cc = models.Biao.objects.filter(nid__gt=1) # __gte大于等于 __lte小于等于 dd = models.Biao.objects.filter(nid__gte=1) #__in 取等于1和3的 ee = models.Biao.objects.filter(nid__in=[1,3]) #__range 取1到3的 ff = models.Biao.objects.filter(nid__range=[1, 3]) #__contains name字段的值含有字母w的 gg = models.Biao.objects.filter(name__contains='w') # __icontains name字段的值含有字母w的 忽略大小写 jj = models.Biao.objects.filter(name__icontains='w')
#类似的还有:startswith,istartswith 以什么什么开头 加i不区分大小写 # , endswith, iendswith 以什么什么结尾
# date字段还可以: models.Class.objects.filter(first_day__year=2017)
#__isnull查找为空的 =False查出不为空的 (空字符串和null不一样,空字符串查不出,null才查的出)
jj = models.Biao.objects.filter(name__isnull=True)
外键的操作
创建两个关联表 一对多
class chubanshe(models.Model): meme = models.CharField(max_length=32) class Book(models.Model): title = models.CharField(max_length=32) chubanshe = models.ForeignKey('chubanshe',on_delete=models.CASCADE) #关联chubanshe表 ,并且级联删除
方法
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "diccc.settings") import django django.setup() from app01 import models #正向查 多对一 (book对chubanshe) book_obj = models.Book.objects.get(pk=1) #取出book表里主键为1的对象 print(book_obj.chubanshe) #点book表里对应的外键的key 得到对应外键chubansh表里的关联的对象 #反向查 一对多 (chubanshe对book 以下方法2选一 ) #models.py里 不写 related_name的时候 chubanshe = models.chubanshe.objects.get(pk=2) #取出版社表里的对象 print(chubanshe.book_set) #关系对象管理(根据chubanshe表反向关联Book表 book即Book表的小写) print(chubanshe.book_set.all()) #models.py里写related_name的时候(在 models.py里的 外键表里related_name = books) chubanshe = models.chubanshe.objects.get(pk=2) # 取出版社表里的对象 print(chubanshe.books) # 关系对象管理(根据chubanshe表反向关联Book表 book即Book表的小写) print(chubanshe.books.all()) #基于字段的查询 根据book表查 chubanshe表的内容 指定book内容的方法(related_name__键=值) #如果不指定related_name就小写类名book__title 这种跨表__字段的方法效率更高 ret = models.chubanshe.objects.filter(books__title="呵呵哒") rett = models.Book.objects.filter(chubanshe__meme='沙河出版社') print(ret)
多对多
class Book(models.Model): title = models.CharField(max_length=32) #related_name 反向查询时的名字 chubanshe = models.ForeignKey('chubanshe',related_name='books',on_delete=models.CASCADE) def __str__(self): return "%s" % self.title class zuozhe(models.Model): name = models.CharField(max_length=32)
# ManyToManyField 多对多 zuozhe表关联Book表 这样创建在zuozhe表里不会出现books字段而是在库里多一个zuozhe_books的表
books = models.ManyToManyField('Book')
提交数据库会出现两个表
多表关联操作
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "diccc.settings") import django django.setup() from app01 import models #多对多 zuozhe_obj = models.zuozhe.objects.get(pk=1) print(zuozhe_obj) print(zuozhe_obj.name) #拿到主键为1的作者写的所有书籍 books为related_name起的别名 print(zuozhe_obj.books.all()) book_obj = models.Book.objects.get(pk=3) #拿到 主键为3的书的所有作者 用小写类名_set的方法(zuozhe_set) print(book_obj.zuozhe_set.all()) #create 创建新数据并生成对应关系 #通过做作者对象创建关联的书籍对象 会在多对多的关联表生成关系数据 zuozhe_obj.books.create(title='鬼吹灯',chubanshe_id=1) #通过书籍对象创建关联的作者对象 book_obj.zuozhe_set.create(name='吴老狗') #add添加 书与作者的关系 外键的管理对象只能用对象 不饿能用ID zuozhe_obj.books.add(2,5) #给 zuozhe_obj这个对象(主键为1的作者)添加主键为2和5的两本书 book_obj.zuozhe_set.add(2,4,6) #给book_obj这个对象(主键为1的书)添加主键为2,4,6的三个作者 zuozhe_obj.books.add(*models.Book.objects.filter(id__in=[1,2,3])) #*为打散 #remove 删除对应关系 和add一样 zuozhe_obj.books.remove(2, 5) book_obj.zuozhe_set.remove(2, 4, 6) #clear 清空 zuozhe_obj.books.clear() book_obj.zuozhe_set.clear() #set 重新设置作者对应的书籍为1,2,3,4 set之后该作者对应的书只有1,2,3,4之前有的其他的会删除,之前没的会添加 zuozhe_obj.books.set([1,2,3,4])
聚合和分组
聚合
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。
键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。
用到的内置函数:
from django.db.models import Avg, Sum, Max, Min, Count
定义表
class Book(models.Model): title = models.CharField(max_length=32) #related_name 反向查询时的名字 chubanshe = models.ForeignKey('chubanshe',related_name='books',on_delete=models.CASCADE) jiage = models.DecimalField(max_digits=5,decimal_places=2) #价格 位数最大5位 999.99 def __str__(self): return "%s" % self.title
聚合函数的使用
#聚合 引用对应的方法 from django.db.models import Max,Min,Sum,Avg,Count #aggregate 聚合函数 Max拿价格最高的 Min最低的 Avg平均价格 Sum总和 Count个数 ret = models.Book.objects.aggregate(Max('jiage'),Min('jiage'),Avg('jiage'),Sum('jiage'),Count('jiage')) #给前端关键字传参 聚合后是个字典 rett = models.Book.objects.aggregate(Max=Max('jiage'), Min=Min('jiage'), Avg=Avg('jiage')) print(ret)
分组
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "diccc.settings") import django django.setup() from app01 import models from django.db.models import Max, Min, Sum, Avg, Count #分组 #看每个出版社的书籍的平均价 #第一种方法 按出版社分组 外接书籍的平均价格 以字典的形式显示 ret = models.chubanshe.objects.annotate(Avg('books__jiage')).values() #第二种方法 # 查询Book表 以外接出版社的名字划分字典 给书的价格分组 ret = models.Book.objects.values('chubanshe__meme').annotate(Avg('jiage')) print(ret)
F查询和Q查询
F查询 用来对俩个字段作比较
在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
class Book(models.Model): title = models.CharField(max_length=32) #related_name 反向查询时的名字 chubanshe = models.ForeignKey('chubanshe',on_delete=models.CASCADE) jiage = models.DecimalField(max_digits=5,decimal_places=2) #最大5位 999.99 shouchu = models.IntegerField() #增加书的售出字段 kucun = models.IntegerField() #增加书的库存字段 def __str__(self): return "%s" % self.title
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "diccc.settings") import django django.setup() from app01 import models from django.db.models import F, Q #引用F 和Q #查询库存大于售出的书 ret = models.Book.objects.filter(kucun__gt=F('shouchu')).values() print(ret) #Django支持 F() 对象之间以及F() 对象和常数之间的加减乘除和取模的操作。 #更新出售的书是库存书的2倍 models.Book.objects.update(kucun=F('shouchu') * 2) #库存=售出*2
Q查询
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行或查询需要用到Q
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "diccc.settings") import django django.setup() from app01 import models from django.db.models import F, Q #引用F 和Q
#查询主键大于5 或者主键小于3的 (|是或 &是与 ~是非) ret = models.Book.objects.filter(Q(pk__gt=5)|Q(pk__lt=3)) print(ret)
事务
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "diccc.settings") import django django.setup() from app01 import models from django.db import transaction #事务 try: with transaction.atomic(): #一次性这行,如果中间有报错则回滚.前边执行成功的全部作废 #一系列操作 models.chubanshe.objects.create(meme='唉呀妈呀') #给出版社表增加内容 models.zuozhe.objects.create(name='卧底玛雅') #给作者表增加内容 except Exception as e: print(e)
Django ORM执行原生SQL
# extra # 在QuerySet的基础上继续执行子语句 # extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # select和select_params是一组,where和params是一组,tables用来设置from哪个表 # Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,)) # Entry.objects.extra(where=['headline=%s'], params=['Lennon']) # Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"]) # Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid']) 举个例子: models.UserInfo.objects.extra( select={'newid':'select count(1) from app01_usertype where id>%s'}, select_params=[1,], where = ['age>%s'], params=[18,], order_by=['-age'], tables=['app01_usertype'] ) """ select app01_userinfo.id, (select count(1) from app01_usertype where id>1) as newid from app01_userinfo,app01_usertype where app01_userinfo.age > 18 order by app01_userinfo.age desc """ # 执行原生SQL # 更高灵活度的方式执行原生SQL语句 # from django.db import connection, connections # cursor = connection.cursor() # cursor = connections['default'].cursor() # cursor.execute("""SELECT * from auth_user where id = %s""", [1]) # row = cursor.fetchone()
Django终端打印SQL语句
在Django项目的settings.py文件中,在最后复制粘贴如下代码:
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } }
即为你的Django项目配置上一个名为django.db.backends的logger实例即可查看翻译后的SQL语句
cookie
https://www.cnblogs.com/maple-shaw/articles/9502602.html
是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。
一个简单的cookie认证登录页
from django.conf.urls import url, include from app01 import views urlpatterns = [ url(r'^login/$', views.login, name='login'), #登录页 url(r'^zhanshi/$', views.zhanshi, name='zhanshi'), #展示页 ]
zhanshi.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>OJBK</h1> </body> </html>
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="" method="post"> {% csrf_token %} <p> 用户名: <input type="text" name="yh"> </p> <p> 密码: <input type="password" name="pas"> </p> <button>登录</button> </form> </body> </html>
后台逻辑
from django.shortcuts import render,reverse,redirect # Create your views here. #一个简单的登录展示页 def login_required(func): #cookie 验证装饰器 def inner(request,*args,**kwargs): is_login = request.COOKIES.get('is_login') # 展示页获取cookie if is_login != '1': # 如果cookie的值不是我们给的值则反回一个让其重新登录 return redirect(reverse('login')) ret = func(request,*args,**kwargs) return ret return inner def login(request): if request.method == 'POST': username = request.POST.get('yh') passwd = request.POST.get('pas') if username == 'wk' and passwd == '123456': ret = redirect(reverse('zhanshi')) ret.set_cookie('is_login', '1') #给返回的页面加一个cookie return ret return render(request,'login.html')
@login_required def zhanshi(request): return render(request,'zhanshi.html') #如果cookie认证成功 怎返回展示页
实现 未登陆时,进入登录页,登陆后直接跳转到之前的页面
from django.shortcuts import render,reverse,redirect # Create your views here. #实现 未登陆时,进入登录页,登陆后直接跳转到之前的页面 def login_required(func): #cookie 验证装饰器 def inner(request,*args,**kwargs): is_login = request.COOKIES.get('is_login') # 展示页获取cookie if is_login != '1': # 如果cookie的值不是我们给的值则反回一个让其重新登录 url = request.path_info #获取到 进入登录页前的页面地址 return redirect('login/?next=%s' % url) #将获取到的url地址传递给登录页 ret = func(request,*args,**kwargs) return ret return inner def login(request): if request.method == 'POST': username = request.POST.get('yh') passwd = request.POST.get('pas') if username == 'wk' and passwd == '123456': url = request.GET.get('next') #获取进入到登录页时装饰器传进来的url地址 if url : #如果url不为空 ret = redirect(url) #则重定向到获取到的url地址 else: #如果为空 ret = redirect(reverse('zhanshi')) #则重定向到展示页 ret.set_cookie('is_login', '1') # 给返回的页面加一个cookie return ret return render(request,'login.html') @login_required def zhanshi(request): return render(request,'zhanshi.html') #如果cookie认证成功 怎返回展示页
加密加盐的cookie (加密和没加密一样所以没啥需求不用)
ret.set_cookie('is_login', '1') # 普通的cookie ret.set_signed_cookie('is_login', '1',salt='yan') #加盐的cookie is_login = request.COOKIES.get('is_login') #获取普通cookie is_login = request.get_signed_cookie('is_login',salt='yan',default='') #获取加密的cookie,default如果获取不到值则报错所以给他一个空
删除cookie(注销功能)
逻辑: def logout(request): ret = redirect('/login/') #注销后返回登录页面 ret.delete_cookie('is_login') #并且根据键删除对应的cookle return ret 路由 url(r'^logout/$', views.logout, name='logout'), 前端 <a href="{% url 'logout' %}">注销</a>
设置Cookie
参数:
- 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传输 True是http传输
- httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖
session
Cookie虽然在一定程度上解决了“保持状态”的需求,但是由于Cookie本身最大支持4096字节,以及Cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。
基于HTTP协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的Cookie就起到桥接的作用。
我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。
总结而言:Cookie弥补了HTTP无状态的不足,让服务器知道来的人是“谁”;但是Cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过Cookie识别不同的用户,对应的在Session里保存私密的信息以及超过4096字节的文本。
另外,上述所说的Cookie和Session其实是共通性的东西,不限于语言和框架
session的使用
设置与获取
request.session['is_login'] = 1 is_login = request.session.get('is_login')
更多方法
# 获取、设置、删除Session中数据 request.session['k1'] request.session.get('k1',None) request.session['k1'] = 123 request.session.setdefault('k1',123) # 存在则不设置 如果不存在则设置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表里的session_key字段(session_data的键) # 将所有Session失效日期小于当前日期的数据删除 request.session.clear_expired() # 检查会话session的key在数据库中是否存在 request.session.exists("session_key") #返回True 和Filus # 删除当前会话的所有Session数据 request.session.delete() #用它可以做注销 他只删数据库的session 不删浏览器之前保存的 # 删除当前的会话数据并删除会话的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失效策略。
在后台的使用
from django.shortcuts import render,reverse,redirect # Create your views here. #实现 未登陆时,进入登录页,登陆后直接跳转到之前的页面 def login_required(func): #session 验证装饰器 def inner(request,*args,**kwargs): #is_login = request.COOKIES.get('is_login') #获取普通cookie is_login = request.session.get('is_login') #session的获取 验证是否已登陆 if is_login != 1: # 如果session的值不是我们给的值则反回一个让其重新登录 url = request.path_info #获取到 进入登录页前的页面地址 print(url) return redirect('/login/?next=%s' % url) #将获取到的url地址传递给登录页 ret = func(request,*args,**kwargs) return ret return inner def login(request): if request.method == 'POST': username = request.POST.get('yh') passwd = request.POST.get('pas') if username == 'wk' and passwd == '123456': url = request.GET.get('next') #获取进入到登录页时装饰器传进来的url地址 if url : #如果url不为空 ret = redirect(url) #则重定向到获取到的url地址 else: #如果为空 ret = redirect(reverse('zhanshi')) #则重定向到展示页 #ret.set_cookie('is_login', '1',max_age=5,path='/zhanshi/') request.session['is_login'] = 1 #session的使用 request.session.set_expiry(0) #关闭浏览器则session超时 return ret return render(request,'login.html') @login_required def zhanshi(request): return render(request,'zhanshi.html') #如果session认证成功 怎返回展示页 @login_required def xixi(request): return render(request,'xixi.html') def logout(request): #注销 request.session.flush() return redirect('/login/')
Django中的Session配置
Django中默认支持Session,其内部提供了5种类型的Session供开发者使用。
from django.conf import global_settings #打开里面是django的所有配置 session的也在
1. 数据库Session SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认) 2. 缓存Session SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎 可用redis和memacach 一般都存在缓存里 SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置 3. 文件Session SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎 SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 4. 缓存+数据库 SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎 5. 加密Cookie Session SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎 其他公用设置项: SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认) SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认) SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认) SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认) SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认) SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认) SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认) Django中Session相关设置
中间件
什么是中间件?
官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。
但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。
说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在处理请求的特定的时间去执行这些方法。
我们一直都在使用中间件,只是没有注意到而已,打开Django项目的Settings.py文件,看到下图的MIDDLEWARE配置项。
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
MIDDLEWARE配置项是一个列表,列表中是一个个字符串,这些字符串其实是一个个类,也就是一个个中间件。
我们之前已经接触过一个csrf相关的中间件了?我们一开始让大家把他注释掉,再提交post请求的时候,就不会被forbidden了,后来学会使用csrf_token之后就不再注释这个中间件了。
自定义中间件
中间件可以定义五个方法,分别是:(主要的是process_request和process_response)
- process_request(self,request)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response) #需要后台有TemplateResponse() 才触发
- process_exception(self, request, exception) #需要后台有错误才触发
- process_response(self, request, response)
以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。
1.首先在项目目录下创建存放中间件文件的目录
中间件文件
2.在项目里的settings.py里注册中间件
process_request(self,request) 方法
参数: request 请求的对象和视图中是同一个
执行时间 :在视图之前(就是之前的后台,views.py)
执行顺序:按照注册的顺序执行
return返回值:
None: 走正常流程
HttpResponse ,不执行下边的中间件,不走视图,如果此类中有process_response方法,则走process_response方法中的return,如果有process_view则执行
当前函数中的process_view,再执行当前函数中的process_response 其他不执行
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import render,reverse,redirect,HttpResponse class MD1(MiddlewareMixin): def process_request(self,request): print('MD1 process_request' ) return HttpResponse("MD1 process_request")
process_response(self, request, response) 方法
参数: request 请求的对象 和视图中的一样 response响应对象,就是后台执行完后return 的返回值
执行时间: 在视图函数之后
执行顺序: 按注册的顺序 倒序执行
返回值: 必 须 有 返回值,否则报错 返回response
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import render,reverse,redirect,HttpResponse class MD1(MiddlewareMixin): def process_request(self,request): print('MD1 process_request') # 执行完视图函数后才执行process_response(self,request,response) 必须有return response就是视图执行完后return的结果 def process_response(self,request,response): print('MD1 process_response') return response class MD2(MiddlewareMixin): def process_request(self,request): print('MD2 process_request') def process_response(self, request, response): print('MD2 process_response') return response
注册顺序是MD1在前MD2在后 process_response的执行顺序是倒序先执行MD2再执行MD1
此时的执行流程
process_view(self,request,view_func,view_args,view_kwargs) 方法
参数: request 请求的对象 和视图中的一样 view_func view_args view_kwargs分别是 urls.py里的
# view_func = views.index view_args = url位置参数(\d+) view_kwargs = 关键字参数(?P<num>[0-9]+) url(r'^index/(\d+)/(?P<num>[0-9]+)', views.index, name='index'),
执行时间: 在process_request之后 ,在视图函数之前执行
执行顺序: 按注册的顺序执行
return返回值:
None: 走正常流程
HttpResponse : 下一个中间件的process_view和视图不执行,直接执行最后一个中间件的process_response 在往上执行其他的process_response
process_exception(self,request,exception):
参数: request 请求的对象 和视图中的一样 exception 错误对象
执行时间: 有异常才执行
执行顺序: ,在视图出现异常后 按注册的顺序 倒序执行,能够处理掉异常后, 再执行process_response
返回值:
None: 交给下一个中间件的process_exception方法处理异常
HttpResponse : 下一个中间件的process_exception方法就不执行了,直接走最后一个中间件的process_response
使用方法
def process_exception(self, request, exception): #后台报错则触发 return HttpResponse('2322')
process_template_response(self,request,response)
参数: request 请求的对象 和视图中的一样 response响应对象,就是后台执行完后return 的返回值
执行时间: 视图函数返回一个template_response对象
执行顺序: ,按注册表倒序执行
返回值:
HttpResponse : 返回response
使用方法:
视图:执行index 返回 return TemplateResponse() 并且里边带有一个字典
from django.template.response import TemplateResponse def index(request): return TemplateResponse(request,'xixi.html',{'ret':'呵呵哒'})
中间件:
def process_template_response(self,request,response): print('MD1 process_template_response ') response.context_data = {'ret':'卧槽'} #用context_data把ret的值替换掉 并返回 response return response
前端:展示
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>嘻嘻</h1> <h1> {{ ret }}</h1> </body> </html>
五种方法合集
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import render,reverse,redirect,HttpResponse class MD1(MiddlewareMixin): def process_request(self,request): print('MD1 process_request') def process_response(self,request,response): print('MD1 process_response') return response def process_view(self,request,view_func,view_args,view_kwargs): print('MD1 process_view') #return HttpResponse('MD1 process_view') def process_exception(self, request, exception): return HttpResponse('2322') def process_template_response(self,request,response): print('MD1 process_template_response ') response.context_data = {'ret':'卧槽'} return response class MD2(MiddlewareMixin): def process_request(self,request): print('MD2 process_request') def process_response(self, request, response): print('MD2 process_response') return response def process_view(self, request, view_func, view_args, view_kwargs): print('MD1 process_view') # return HttpResponse('MD2 process_view') def process_exception(self, request, exception): print('MD1 process_exception') print(exception) return HttpResponse('2322') def process_template_response(self, request, response): return response