微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!
Django 缓存、序列化、信号
一,缓存由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5分钟内再有人来访问时,则不再去执行view中的操作,而是直接从内存或者memcache中之前缓存的内容拿到,并返回。Django中提供了6种缓存方式:开发调试内存文件数据库Memcache缓存(python-memcached模块)Memcache缓存(pylibmc模块)1,配置a、开发调试# 此为开始调试用,实际内部不做任何操作# 配置:CACHES = {'default': {'BACKEND': 'django.core.cache.backends.dummy.DummyCache', # 引擎'TIMEOUT': 300, # 缓存超时时间(默认300,None表示永不过期,0表示立即过期)'OPTIONS':{'MAX_ENTRIES': 300, # 最大缓存个数(默认300)'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)},'KEY_PREFIX': '', # 缓存key的前缀(默认空)'VERSION': 1, # 缓存key的版本(默认1)'KEY_FUNCTION': '函数名' # 生成key的函数(默认函数会生成为:【前缀:版本:key】)}}# 自定义keydef default_key_func(key, key_prefix, version):"""Default function to generate keys.Constructs the key used by all other methods. By default it prependsthe `key_prefix'. KEY_FUNCTION can be used to specify an alternatefunction with custom key making behavior."""return '%s:%s:%s' % (key_prefix, version, key)def get_key_func(key_func):"""Function to decide which key function to use.Defaults to ``default_key_func``."""if key_func is not None:if callable(key_func):return key_funcelse:return import_string(key_func)return default_key_funcView Codeb、内存# 此缓存将内容保存至内存的变量中# 配置:CACHES = {'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache','LOCATION': 'unique-snowflake',}}# 注:其他配置同开发调试版本View Codec、文件# 此缓存将内容保存至文件# 配置:CACHES = {'default': {'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache','LOCATION': '/var/tmp/django_cache',}}# 注:其他配置同开发调试版本View Coded、数据库# 此缓存将内容保存至数据库# 配置:CACHES = {'default': {'BACKEND': 'django.core.cache.backends.db.DatabaseCache','LOCATION': 'my_cache_table', # 数据库表}}# 注:执行创建表命令 python manage.py createcachetableView Codee、Memcache缓存(python-memcached模块)# 此缓存使用python-memcached模块连接memcacheCACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache','LOCATION': '127.0.0.1:11211',}}CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache','LOCATION': 'unix:/tmp/memcached.sock',}}CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache','LOCATION': ['172.19.26.240:11211','172.19.26.242:11211',]}}View Codef、Memcache缓存(pylibmc模块)# 此缓存使用pylibmc模块连接memcacheCACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache','LOCATION': '127.0.0.1:11211',}}CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache','LOCATION': '/tmp/memcached.sock',}}CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache','LOCATION': ['172.19.26.240:11211','172.19.26.242:11211',]}}View Code2,应用a. 全站使用'''使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存'''MIDDLEWARE = ['django.middleware.cache.UpdateCacheMiddleware', # 最开始位置# 其他中间件...'django.middleware.cache.FetchFromCacheMiddleware', # 最后位置]CACHE_MIDDLEWARE_ALIAS = ""CACHE_MIDDLEWARE_SECONDS = ""CACHE_MIDDLEWARE_KEY_PREFIX = ""View Codeb. 单独视图缓存# 方式一:from django.views.decorators.cache import cache_page@cache_page(60 * 15)def my_view(request):...# 方式二:from django.views.decorators.cache import cache_pageurlpatterns = [url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)),]View Codec、局部模板使用a. 引入TemplateTag{% load cache %}b. 使用缓存{% cache 5000 缓存key %} # 时间(秒)和key(key名字自定义)缓存内容{% endcache %}View Code更多:猛击这里 二,序列化关于Django中的序列化主要应用在将数据库中检索的数据返回给客户端用户,特别的Ajax请求一般返回的为Json格式。1、serializersfrom django.core import serializersret = models.BookType.objects.all()data = serializers.serialize("json", ret)2、json.dumpsimport json#ret = models.BookType.objects.all().values('caption')ret = models.BookType.objects.all().values_list('caption')ret=list(ret)result = json.dumps(ret)由于json.dumps时无法处理datetime日期,所以可以通过自定义处理器来做扩展,如:import jsonfrom datetime import datefrom datetime import datetimeclass JsonCustomEncoder(json.JSONEncoder):def default(self, field):if isinstance(field, datetime):return o.strftime('%Y-%m-%d %H:%M:%S')elif isinstance(field, date):return o.strftime('%Y-%m-%d')else:return json.JSONEncoder.default(self, field)# ds = json.dumps(d, cls=JsonCustomEncoder)View Code 三,信号Django中提供了“信号调度”,用于在框架执行操作时解耦。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。1,Django内置信号Model signalspre_init # django的modal执行其构造方法前,自动触发post_init # django的modal执行其构造方法后,自动触发pre_save # django的modal对象保存前,自动触发post_save # django的mo
Django 数据库与ORM
一、数据库的配置1django默认支持sqlite,mysql, oracle,postgresql数据库。     <1> sqlite            django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 。 引擎名称:django.db.backends.sqlite3     <2> mysql            引擎名称:django.db.backends.mysql2mysql驱动程序   MySQLdb(mysql python)   mysqlclient   MySQL   PyMySQL(纯python的mysql驱动程序)3在django的项目中会默认使用sqlite数据库,在settings里有如下设置:      如果我们想要更改数据库,需要修改如下:   DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME': 'books', #你的数据库名称'USER': 'root', #你的数据库用户名'PASSWORD': '', #你的数据库密码'HOST': '', #你的数据库主机,留空默认为localhost'PORT': '3306', #你的数据库端口}}View Code注意:NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建USER和PASSWORD分别是数据库的用户名和密码。设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。然后,启动项目,会报错:no module named MySQLdb这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb对于py3有很大问题,所以我们需要的驱动是PyMySQL所以,我们只需要找到项目名文件下的__init__,在里面写入:import pymysqlpymysql.install_as_MySQLdb()问题解决!二、ORM表模型表(模型)的创建:实例:我们来假定下面这些概念,字段和关系作者模型:一个作者有姓名。作者详细模型:把作者的详情放到详情表,包含性别,email地址和出生日期,作者详情模型和作者模型之间是一对一的关系(one-to-one)(类似于每个人和他的身份证之间的关系),在大多数情况下我们没有必要将他们拆分成两张表,这里只是引出一对一的概念。出版商模型:出版商有名称,地址,所在城市,省,国家和网站。书籍模型:书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many),一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many),也被称作外键。from django.db import models<br>class Publisher(models.Model):name = models.CharField(max_length=30, verbose_name="名称")address = models.CharField("地址", max_length=50)city = models.CharField('城市',max_length=60)state_province = models.CharField(max_length=30)country = models.CharField(max_length=50)website = models.URLField()class Meta:verbose_name = '出版商'verbose_name_plural = verbose_namedef __str__(self):return self.nameclass Author(models.Model):name = models.CharField(max_length=30)def __str__(self):return self.nameclass AuthorDetail(models.Model):sex = models.BooleanField(max_length=1, choices=((0, '男'),(1, '女'),))email = models.EmailField()address = models.CharField(max_length=50)birthday = models.DateField()author = models.OneToOneField(Author)class Book(models.Model):title = models.CharField(max_length=100)authors = models.ManyToManyField(Author)publisher = models.ForeignKey(Publisher)publication_date = models.DateField()price=models.DecimalField(max_digits=5,decimal_places=2,default=10)def __str__(self):return self.titleView Code分析代码:       <1>  每个数据模型都是django.db.models.Model的子类,它的父类Model包含了所有必要的和数据库交互的方法。并提供了一个简介漂亮的定义数据库字段的语法。       <2>  每个模型相当于单个数据库表(多对多关系例外,会多生成一张关系表),每个属性也是这个表中的字段。属性名就是字段名,它的类型(例如CharField)相当于数据库的字段类型(例如varchar)。大家可以留意下其它的类型都和数据库里的什么字段对应。       <3>  模型之间的三种关系:一对一,一对多,多对多。             一对一:实质就是在主外键(author_id就是foreign key)的关系基础上,给外键加了一个UNIQUE=True的属性;             一对多:就是主外键关系;(foreign key)             多对多:(ManyToManyField) 自动创建第三张表(当然我们也可以自己创建第三张表:两个foreign key)1、ORM之增(create,save)from app01.models import *#create方式一: Author.objects.create(name='Alvin')#create方式二: Author.objects.create(**{"name":"alex"})#save方式一: author=Author(name="alvin")author.save()#save方式二: author=Author()author.name="alvin"author.save()重点来了------->那么如何创建存在一对多或多对多关系的一本书的信息呢?(如何处理外键关系的字段如一对多的publisher和多对多的authors)#一对多(ForeignKey):#方式一: 由于绑定一对多的字段,比如publish,存到数据库中的字段名叫publish_id,所以我们可以直接给这个# 字段设定对应值:Book.objects.create(title='php',publisher_id=2, #这里的2是指为该book对象绑定了Publisher表中id=2的行对象publication_date='2017-7-7',price=99)#方式二:# <1> 先获取要绑定的Publisher对象:pub_obj=Publisher(name='河大出版社',address='保定',city='保定',state_province='河北',country='China',website='http://www.hbu.com')OR pub_obj=Publisher.objects.get(id=1)# <2>将 publisher_id=2 改为 publisher=pub_objBook.objects.create(title='php',publisher=pub_obj, #添加的为出版社对象时不使用**_id了。publication_date='2017-7-7',price=99)一对多#多对多(ManyToManyField()):#方式一:#取出要添加的作者对象列表,通过一本书籍的<ManyToManyDescriptor object> .add方法添加作者对象。author1=Author.objects.get(id=1)author2=Author.objects.filter(name='alvin')[0]book=Book.objects.get(id=1)book.authors.add(author1,author2)#等同于:book.authors.add(*[author1,author2])book.authors.remove(*[author1,author2])#-------------------#方式二:#取出要添加的书籍对象列表,通过一个作者对象的.book_set.add(*book)添加书籍对象。book=models.Book.objects.filter(id__gt=1)authors=models.Author.objects.filter(id=1)[0]authors.book_set.add(*book)authors.book_set.remove(*book)#-------------------#方式三:取出书籍或者作者对象,直接添加对应的id.book.authors.add(1) #book.authors.add(1,2) 可一次添加多个id,已存在的id不会重复添加book.authors.remove(1)authors.book_set.add(1)authors.book_set.remove(1)#注意: 如果第三张表是通过models.ManyToManyField()自动创建的,那么绑定关系只有上面一种方式# 如果第三张表是自己创建的:class Book2Author(models.Model):author=models.ForeignKey("Author")Book= models.ForeignKey("Book")# 那么就还有一种方式:author_obj=models.Author.objects.filter(id=2)[0]book_obj =models.Book.objects.filter(id=3)[0]s=models.Book2Author.objects.create(author_id=1,Book_id=2)s.save()s=models.Book2Author(author=author_obj,Book_id=1)s.save()多对
RESTful
RESTful规范web服务交互我们在浏览器中能看到的每个网站,都是一个web服务。那么我们在提供每个web服务的时候,都需要前后端交互,前后端交互就一定有一些实现方案,我们通常叫web服务交互方案。目前主流的三种web服务交互方案:-- REST ( Representational State Transfer)表述性状态转移-- SOAP (Simple Object Access Protocol)  简单的对象访问协议-- XML-RPC (XML Remote Procedure Call)基于XML的远程过程调用XML-RPC是通过XML将调用函数封装,并使用HTTP协议作为传送机制。后来在新的功能不断被引入下,这个标准慢慢演变成为今日的SOAP协定。SOAP服务则是以本身所定义的操作集,来访问网络上的资源。SOAP也是基于XML的,但是它不只限于HTTP协议的传输,包括TCP协议,UDP协议都可以传输。REST是Roy Thomas Fielding博士于2000年在他的博士论文里提出来的。REST相比SOAP更加简洁,性能和开发效率也有突出的优势。我们今天主要说一下这个REST,现在越来越多的web服务开始采用REST风格设计和实现。例如,amazon.com提供接近REST风格的Web服务进行图书查找;雅虎提供的Web服务也是REST风格的。我们接下来要学的框架也是遵循REST风格的,那么我们来看下它到底是个什么样的风格,了解了它是什么后,我们看下它的优点是什么,我们为什么用它。理解REST如果我们想要理解restful,就要理解Representational State Transfer这个词组的意思,表征性状态转移。这里所说的表征性,其实指的就是资源。通常我们称为资源状态转移。什么是资源任何事物,只要有被引用到的必要,它就是一个资源。我们在浏览器中看到的文本,视频,图片等等都是资源。这些都是实实在在存在的实体。资源可以是一个实体,也可以是抽象概念。比如说吧:-- 张三的个人信息-- 李四的手机号-- 张三跟李四的潜在关系这些都是资源,可以是实体比如个人信息,手机号。也可以是抽象的概念,比如两个人的关系......那么在我们的网络中,我们要引用资源,资源一定要有一个标识,在web中的唯一标识就是URI,URI我们不常听说,我们经常用URL,那么两者区别是什么呢~什么是URI,URLURI 统一资源标志符。URL 统一资源定位符。URI是给我们的资源进行标识的,URL是描述我们资源地址的。比如说我们每个人都有名字和身份证,名字可能重名,但是身份证是唯一的,那么身份证号就可以是我们的URI,标识我们每个人,也可以说标识我们每个资源。我们可以通过身份证号找到Alex,也可以通过下面这种方式找到他.....Alex的住址协议://地球/中国/屌丝省/屌丝市/寡妇村/250号街道/250号/Alex这个就是我们的URL,我们通过这两种方式都可以找到我们的资源,其实我们的URL可以说是URI的子集,通过定位的方式实现的URI。这是我们资源的定位~~有了资源的地址后,我们要去访问资源,那么我们要通过什么方式去访问呢~~统一资源接口现在我们可以通过URL去访问到资源,那么我们对资源会有很多不同的操作,增删改查,以前我们可能会为了这个增加新设计一个URL,然后这个URL就是对数据进行增加的,还会为了更新和删除分别设计一个URL,现在我们不用了,我们只有一个URL,然后根据HTTP请求方式的不同,对资源进行不同的操作,这个就是是统一资源接口。我们一定要遵循HTTP请求方法的语义,也就是说POST请求就在新增数据等....资源的表述资源的表述其实就是资源的展现形式,我们客户端和服务端传输的都是资源的表述,而不是资源本身。例如文本资源可以采用html、xml、json等格式,图片可以使用PNG或JPG展现出来。那么客户端如何知道服务端提供哪种表述形式呢?可以通过HTTP内容协商,客户端可以通过Accept头请求一种特定格式的表述,服务端则通过Content-Type告诉客户端资源的表述形式。这些资源的表述呈现在页面上,就是我们说的资源状态。状态转移我们在看页面的时候,从当前资源的表述(也可以说状态或者表现层)会跳转到其他的资源状态。服务端通过超媒体告诉客户端当前状态有哪些后续状态可以进入。这些类似"下一页"之类的链接起的就是这种推进状态的作用——指引你如何从当前状态进入下一个可能的状态。总结可以得知REST风格的特点如下:(1)在web中,只要有被引用的必要都叫资源。(2)每个URI代表一个资源,独一无二的。(3)客户端通过HTTP的方法,对服务器端资源进行操作;(4)客户端和服务器之间,传递这种资源的某种表现层;(5)通过超链接的指引,实现"表现层状态转移"。RESTful 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。一种软件的架构风格,设计风格,  为客户端和服务端的交互提供一组设计原则和约束条件。restful规范一  面向资源编程每个URL代表一种资源,URL中尽量不要用动词,要用名词。二  根据method不同,进行不同的操作GET/POST/PUT/DELETE/PATCH三  在URL中体现版本https://www.bootcss.com/v1/mycsshttps://v1.bootcss.com/mycss四  在URL中体现是否是APIhttps://www.bootcss.com/api/mycsshttps://api.bootcss.com/mycss五  在URL中的过滤条件https://www.bootcss.com/v1/mycss?page=3六  尽量使用HTTPShttps://www.bootcss.com/v1/mycss七  响应时设置状态码1**   信息,服务器收到请求,需要请求者继续执行操作2**  成功,操作被成功接收并处理3**  重定向,需要进一步的操作以完成请求4**  客户端错误,请求包含语法错误或无法完成请求5**  服务器错误,服务器在处理请求的过程中发生了错误八  返回值GET请求 返回查到所有或单条数据POST请求  返回新增的数据PUT请求  返回更新数据PATCH请求  局部更新  返回更新整条数据DELETE请求  返回值为空九  返回错误信息返回值携带错误信息十   Hypermedia API如果遇到需要跳转的情况 携带调转接口的URLret = {code: 1000,data:{id:1,name:'小强',depart_id:http://www.luffycity.com/api/v1/depart/8/}} 
CORS 跨域
CORS跨域请求CORS即Cross Origin Resource Sharing 跨域资源共享,那么跨域请求还分为两种,一种叫简单请求,一种是复杂请求~~简单请求HTTP方法是下列方法之一HEAD, GET,POSTHTTP头信息不超出以下几种字段Accept, Accept-Language, Content-Language, Last-Event-IDContent-Type只能是下列类型中的一个application/x-www-from-urlencodedmultipart/form-datatext/plain任何一个不满足上述要求的请求,即会被认为是复杂请求~~复杂请求会先发出一个预请求,我们也叫预检,OPTIONS请求~~浏览器的同源策略跨域是因为浏览器的同源策略导致的,也就是说浏览器会阻止非同源的请求~那什么是非同源呢~~即域名不同,端口不同都属于非同源的~~~浏览器只阻止表单以及ajax请求,并不会阻止src请求,所以我们的cnd,图片等src请求都可以发~~解决跨域JSONPjsonp的实现原理是根据浏览器不阻止src请求入手~来实现的~~class Test(APIView):def get(self, request):callback = request.query_params.get("callback", "")ret = callback + "(" + "'success'" + ")"return HttpResponse(ret)JsonP实现的后端代码<button id="btn_one">点击我向JsonP1发送请求</button><script>// 测试发送请求失败 跨域不能得到数据$('#btn_one').click(function () {$.ajax({url: "http://127.0.0.1:8000/jsonp1",type: "get",success: function (response) {console.log(response)}})});function handlerResponse(response) {alert(response)};window.onload = function () {$("#btn_one").click(function () {let script_ele = document.createElement("script");script_ele.src = "http://127.0.0.1:8000/jsonp1?callback=handlerResponse";document.body.insertBefore(script_ele, document.body.firstChild);})}</script>JsonP测试前端代码JsonP解决跨域只能发送get请求,并且实现起来需要前后端交互比较多。添加响应头from django.utils.deprecation import MiddlewareMixinclass MyCors(MiddlewareMixin):def process_response(self, request, response):response["Access-Control-Allow-Origin"] = "*"if request.method == "OPTIONS":# 证明它是复杂请求先发预检response["Access-Control-Allow-Headers"] = "Content-Type"response["Access-Control-Allow-Methods"] = "DELETE, PUT, POST"return responseDjango中间件加响应头 
DRF 序列化组件
Serializers 序列化组件Django的序列化方法class BooksView(View):def get(self, request):book_list = Book.objects.values("id", "title", "chapter", "pub_time", "publisher")book_list = list(book_list)# 如果我们需要取外键关联的字段信息 需要循环获取外键 再去数据库查然后拼接成我们想要的ret = []for book in book_list:pub_dict = {}pub_obj = Publish.objects.filter(pk=book["publisher"]).first()pub_dict["id"] = pub_obj.pkpub_dict["title"] = pub_obj.titlebook["publisher"] = pub_dictret.append(book)ret = json.dumps(book_list, ensure_ascii=False, cls=MyJson)return HttpResponse(ret)# json.JSONEncoder.default()# 解决json不能序列化时间字段的问题class MyJson(json.JSONEncoder):def default(self, field):if isinstance(field, datetime.datetime):return field.strftime('%Y-%m-%d %H:%M:%S')elif isinstance(field, datetime.date):return field.strftime('%Y-%m-%d')else:return json.JSONEncoder.default(self, field).values 序列化结果from django.core import serializers# 能够得到我们要的效果 结构有点复杂class BooksView(View):def get(self, request):book_list = Book.objects.all()ret = serializers.serialize("json", book_list)return HttpResponse(ret)django serializersDRF序列化的方法首先,我们要用DRF的序列化,就要遵循人家框架的一些标准,-- Django我们CBV继承类是View,现在DRF我们要用APIView-- Django中返回的时候我们用HTTPResponse,JsonResponse,render ,DRF我们用Response为什么这么用~我们之后会详细讲~~我们继续来看序列化~~序列化class BookSerializer(serializers.Serializer):id = serializers.IntegerField()title = serializers.CharField(max_length=32)CHOICES = ((1, "Linux"), (2, "Django"), (3, "Python"))chapter = serializers.ChoiceField(choices=CHOICES, source="get_chapter_display")pub_time = serializers.DateField()第一步 声明序列化类from rest_framework.views import APIViewfrom rest_framework.response import Responseclass BookView(APIView):def get(self, request):book_list = Book.objects.all()ret = BookSerializer(book_list, many=True)return Response(ret.data)第二部 序列化对象外键关系的序列化# by gaoxinfrom rest_framework import serializersfrom .models import Bookclass PublisherSerializer(serializers.Serializer):id = serializers.IntegerField(read_only=True)title = serializers.CharField(max_length=32)class UserSerializer(serializers.Serializer):id = serializers.IntegerField(read_only=True)name = serializers.CharField(max_length=32)age = serializers.IntegerField()class BookSerializer(serializers.Serializer):id = serializers.IntegerField(read_only=True)title = serializers.CharField(max_length=32)CHOICES = ((1, "Linux"), (2, "Django"), (3, "Python"))chapter = serializers.ChoiceField(choices=CHOICES, source="get_chapter_display", read_only=True)pub_time = serializers.DateField()publisher = PublisherSerializer(read_only=True)user = UserSerializer(many=True, read_only=True)外键关系的序列化反序列化当前端给我们发post的请求的时候~前端给我们传过来的数据~我们要进行一些校验然后保存到数据库~这些校验以及保存工作,DRF的Serializer也给我们提供了一些方法了~~首先~我们要写反序列化用的一些字段~有些字段要跟序列化区分开~~Serializer提供了.is_valid()  和.save()方法~~# serializers.py 文件class BookSerializer(serializers.Serializer):id = serializers.IntegerField(read_only=True)title = serializers.CharField(max_length=32)CHOICES = ((1, "Linux"), (2, "Django"), (3, "Python"))chapter = serializers.ChoiceField(choices=CHOICES, source="get_chapter_display", read_only=True)w_chapter = serializers.IntegerField(write_only=True)pub_time = serializers.DateField()publisher = PublisherSerializer(read_only=True)user = UserSerializer(many=True, read_only=True)users = serializers.ListField(write_only=True)publisher_id = serializers.IntegerField(write_only=True)def create(self, validated_data):book = Book.objects.create(title=validated_data["title"], chapter=validated_data["w_chapter"], pub_time=validated_data["pub_time"], publisher_id=validated_data["publisher_id"])book.user.add(*validated_data["users"])return book反序列化serializer.pyclass BookView(APIView):def get(self, request):book_list = Book.objects.all()ret = BookSerializer(book_list, many=True)return Response(ret.data)def post(self, request):# book_obj = request.dataprint(request.data)serializer = BookSerializer(data=request.data)if serializer.is_valid():print(12341253)serializer.save()return Response(serializer.validated_data)else:return Response(serializer.errors)反序列化views.py但前端给我们发送patch请求的时候, 前端传给我们用户需要更新的数据, 我们要对数据进行部分验证.class BookSerializer(serializers.Serializer):id = serializers.IntegerField(read_only=True)title = serializers.CharField(max_length=32)CHOICES = ((1, "Linux"), (2, "Django"), (3, "Python"))chapter = serializers.ChoiceField(choices=CHOICES, source="get_chapter_display", read_only=True)w_chapter = serializers.IntegerField(write_only=True)pub_time = serializers.DateField()publisher = PublisherSerializer(read_only=True)user = UserSerializer(many=True, read_only=True)users = serializers.ListField(write_only=True)publisher_id = serializers.IntegerField(write_only=True)def create(self, validated_data):book = Book.objects.create(title=validated_data["title"], chapter=validated_data["w_chapter"], pub_time=validated_data["pub_time"],publisher_id=va
DRF 视图和路由
Django Rest Feamework 视图和路由DRF的视图APIView我们django中写CBV的时候继承的是View,rest_framework继承的是APIView,那么他们两个有什么不同呢urlpatterns = [url(r'^book$', BookView.as_view()),url(r'^book/(?P<id>d+)$', BookEditView.as_view()),]我们可以看到,不管是View还是APIView最开始调用的都是as_view()方法~~那我们走进源码看看~~我们能看到,APIView继承了View, 并且执行了View中的as_view()方法,最后把view返回了,用csrf_exempt()方法包裹后去掉了csrf的认证。那我们看看View中的as_view()方法做了什么~我们看到了~在View中的as_view方法返回了view函数,而view函数执行了self.dispatch()方法~~但是这里的dispatch方法应该是我们APIView中的~~我们去initialize_request中看下把什么赋值给了request,并且赋值给了self.request, 也就是我们在视图中用的request.xxx到底是什么~~我们看到,这个方法返回的是Request这个类的实例对象~~我们注意我们看下这个Request类中的第一个参数request,是我们走我们django的时候的原来的request~我们看到了,这个Request类把原来的request赋值给了self._request, 也就是说以后_request是我们老的request,新的request是我们这个Request类~~那我们继承APIView之后请求来的数据都在哪呢~~我们用了rest_framework框架以后,我们的request是重新封装的Request类~request.query_params 存放的是我们get请求的参数request.data 存放的是我们所有的数据,包括post请求的以及put,patch请求~~~相比原来的django的request,我们现在的request更加精简,清晰了~~~现在我们知道了APIView和View的一些区别~~当然还有~~后面我们还会说~~我们写的视图可能对多个表进行增删改查,就导致我们的视图特别多重复的代码~~那么我们尝试着来进行封装一下~~第一次封装class BookView(APIView):def get(self, request):query_set = Book.objects.all()book_ser = BookSerializer(query_set, many=True)return Response(book_ser.data)def post(self, request):query_set = request.databook_ser = BookSerializer(data=query_set)if book_ser.is_valid():book_ser.save()return Response(book_ser.validated_data)else:return Response(book_ser.errors)class BookEditView(APIView):def get(self, request, id):query_set = Book.objects.filter(id=id).first()book_ser = BookSerializer(query_set)return Response(book_ser.data)def patch(self, request, id):query_set = Book.objects.filter(id=id).first()book_ser = BookSerializer(query_set, data=request.data, partial=True)if book_ser.is_valid():book_ser.save()return Response(book_ser.validated_data)else:return Response(book_ser.errors)def delete(self, request, id):query_set = Book.objects.filter(id=id).first()if query_set:query_set.delete()return Response("")else:return Response("删除的书籍不存在")APIView视图class GenericAPIView(APIView):queryset = Noneserializer_class = Nonedef get_queryset(self):return self.queryset.all()def get_serializer(self, *args, **kwargs):return self.serializer_class(*args, **kwargs)class ListModelMixin(object):def list(self, request, *args, **kwargs):queryset = self.get_queryset()serializer = self.get_serializer(queryset, many=True)return Response(serializer.data)class CreateModelMixin(object):def create(self, request, *args, **kwargs):serializer = self.get_serializer(data=request.data)if serializer.is_valid():serializer.save()return Response(serializer.validated_data)else:return Response(serializer.errors)class RetrieveModelMixin(object):def retrieve(self, request, id, *args, **kwargs):book_obj = self.get_queryset().filter(pk=id).first()book_ser = self.get_serializer(book_obj)return Response(book_ser.data)class UpdateModelMixin(object):def update(self, request, id, *args, **kwargs):book_obj = self.get_queryset().filter(pk=id).first()book_ser = self.get_serializer(book_obj, data=request.data, partial=True)if book_ser.is_valid():book_ser.save()return Response(book_ser.validated_data)else:return Response(book_ser.errors)class DestroyModelMixin(object):def destroy(self, request, id, *args, **kwargs):queryset = self.get_queryset()try:queryset.get(pk=id).delete()return Response("")except Exception as e:return Response("信息有误")# 我们把公共的部分抽出来 这样不管写多少表的增删改查都变的很简单# 这样封装后我们的视图会变成这样class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):queryset = Book.objects.all()serializer_class = BookSerializerdef get(self, request, *args, **kwargs):return self.list(request, *args, **kwargs)def post(self, request, *args, **kwargs):return self.create(request, *args, **kwargs)class BookEditView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):queryset = Book.objects.all()serializer_class = BookSerializerdef get(self, request, id, *args, **kwargs):return self.retrieve(request, id, *args, **kwargs)def patch(self, request, id, *args, **kwargs):return self.update(request, id, *args, **kwargs)def destroy(self, request, id, *args, **kwargs):return self.delete(request, id, *args, **kwargs)第一次封装我们封装的GenericAPIView,包括封装每个方法的类,其实框架都帮我们封装好了我们可以直接继承这些类~~来实现上面的视图~~可是还有没有更简单的方法呢~我们再次封装一下第二次封装# 上面我们写的继承类太长了~~我们再改改class ListCreateAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):passclass RetrieveUpdateDestroyAPIView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):passclass BookView(ListCreateAPIView):queryset = Book.objects.all()serializer_class = BookSerializerdef get(self, request, *args, **kwargs):return self.list(request, *args, **kwargs)def post(self, request, *args, **kwargs):return self.create(request, *args, **kwargs)class BookEditView(RetrieveUpdateDestroyAPIView):queryset = Book.objects.all()serializer_class = BookSerializerdef get(self, request, id, *args, **kwargs):return self.retrieve(request, id, *args, **kwargs)def patch(self, request, id, *args, **kwargs):return self.update(request, id, *args, **kwargs)def delete(self, request, id, *args, **kwargs):return sel
DRF 版本和认证
Django Rest Framework 版本控制组件DRF的版本版本控制是做什么用的, 我们为什么要用首先我们要知道我们的版本是干嘛用的呢~~大家都知道我们开发项目是有多个版本的~~当我们项目越来越更新~版本就越来越多~~我们不可能新的版本出了~以前旧的版本就不进行维护了~~~那我们就需要对版本进行控制~~这个DRF也给我们提供了一些封装好的版本控制方法~~版本控制怎么用之前我们学视图的时候知道APIView,也知道APIView返回View中的view函数,然后调用的dispatch方法~那我们现在看下dispatch方法~~看下它都做了什么~~执行self.initial方法之前是各种赋值,包括request的重新封装赋值,下面是路由的分发,那我们看下这个方法都做了什么~~我们可以看到,我们的version版本信息赋值给了 request.version  版本控制方案赋值给了 request.versioning_scheme~~其实这个版本控制方案~就是我们配置的版本控制的类~~也就是说,APIView通过这个方法初始化自己提供的组件~~我们接下来看看框架提供了哪些版本的控制方法~~在rest_framework.versioning里~~框架一共给我们提供了这几个版本控制的方法~~我们在这里只演示一个~~因为基本配置都是一样的~~详细用法我们看下放在URL上携带版本信息怎么配置~~REST_FRAMEWORK = {# 默认使用的版本控制类'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',# 允许的版本'ALLOWED_VERSIONS': ['v1', 'v2'],# 版本使用的参数名称'VERSION_PARAM': 'version',# 默认使用的版本'DEFAULT_VERSION': 'v1',}第一步 setting.pyurlpatterns = [url(r"^versions", MyView.as_view()),url(r"^(?P<version>[v1|v2]+)/test01", TestView.as_view()),]第二步 urls.pydef get(self, request, *args, **kwargs):print(request.versioning_scheme)ret = request.versionif ret == "v1":return Response("版本v1的信息")elif ret == "v2":return Response("版本v2的信息")else:return Response("根本就匹配不到这个路由")测试视图其他的版本控制的类,配置方法都差不多~~这里就不一一例举了~~ Django Rest Framework 认证组件DRF的认证认证是干嘛的呢~我们都知道~我们可以在网站上登录~然后可以有个人中心,对自己信息就行修改~~~但是我们每次给服务器发请求,由于Http的无状态,导师我们每次都是新的请求~~那么服务端需要对每次来的请求进行认证,看用户是否登录,以及登录用户是谁~~那么我们服务器对每个请求进行认证的时候,不可能在每个视图函数中都写认证~~~一定是把认证逻辑抽离出来~~以前我们可能会加装饰器~或者中间件~~那我们看看DRF框架给我们提供了什么~~~认证怎么用上面讲版本的时候我们知道~在dispatch方法里~执行了initial方法~~那里初始化了我们的版本~~如果我们细心我们能看到~版本的下面其实就是我们的认证,权限,频率组件了~~我们先看看我们的认证组件~~我们进去我们的认证看下~~我们这个权限组件返回的是request.user,那我们这里的request是新的还是旧的呢~~我们的initial是在我们request重新赋值之后的~所以这里的request是新的~也就是Request类实例对象~~那这个user一定是一个静态方法~我们进去看看~~我没在这里反复的截图跳转页面~~大家可以尝试着自己去找~~要耐心~~细心~~我们通过上面基本可以知道我们的认证类一定要实现的方法~~以及返回值类型~~以及配置的参数authentication_classes~下面我们来看看具体用法~~~认证的详细用法我们先写个认证的小demo~~我们先建一个用户表~字段为用户名以及对应的token值~~# 先在model中注册模型类# 并且进行数据迁移# 测试我就简写了~class UserInfo(models.Model):username = models.CharField(max_length=32)token = models.UUIDField()models.py# 写视图类并且用post请求注册一个用户class UserView(APIView):def post(self, request, *args, **kwargs):username = request.data["username"]UserInfo.objects.create(username=username, token=uuid.uuid4())return Response("注册成功")views.py准备工作完成~我们来开始我们的认证~~# 注意我们这个认证的类必须实现的方法以及返回值class MyAuth(BaseAuthentication):def authenticate(self, request):request_token = request.query_params.get("token", None)if not request_token:raise AuthenticationFailed({"code": 1001, "error": "缺少token"})token_obj = UserInfo.objects.filter(token=request_token).first()if not token_obj:raise AuthenticationFailed({"code": 1001, "error": "无效的token"})return token_obj.username, token_obj写一个认证的类class TestAuthView(APIView):authentication_classes = [MyAuth, ]def get(self, request, *args, **kwargs):return Response("测试认证")视图级别认证REST_FRAMEWORK = {# 默认使用的版本控制类'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',# 允许的版本'ALLOWED_VERSIONS': ['v1', 'v2'],# 版本使用的参数名称'VERSION_PARAM': 'version',# 默认使用的版本'DEFAULT_VERSION': 'v1',# 配置全局认证'DEFAULT_AUTHENTICATION_CLASSES': ["BRQP.utils.MyAuth", ]}全局配置认证 
DRF 权限和频率
Django Rest Framework 权限组件DRF的权限 权限组件源码解析我们之前说过了DRF的版本和认证~也知道了权限和频率跟版本认证都是在initial方法里初始化的~~其实我们版本,认证,权限,频率控制走的源码流程大致相同~~大家也可以在源码里看到~~我们的权限类一定要有has_permission方法~否则就会抛出异常~~这也是框架给我提供的钩子~~我们先看到在rest_framework.permissions这个文件中~存放了框架给我们提供的所有权限的方法~~我这里就不带着大家详细去看每一个了~大家可以去浏览一下每个权限类~看看每个都是干嘛的~~这里主要说下BasePermission 这个是我们写权限类继承的一个基础权限类~~~ 权限的详细用法在这里我们一定要清楚一点~我们的Python代码是一行一行执行的~那么执行initial方法初始化这些组件的时候~~也是有顺序的~~我们的版本在前面~然后是认证,然后是权限~ 最后是频率~~所以大家要清楚~~我们的权限执行的时候~我们的认证已经执行结束了~~~前提在model中的UserInfo表中加了一个字段~用户类型的字段~~做好数据迁移~~class MyPermission(BasePermission):message = "VIP用户才能访问"def has_permission(self, request, view):"""自定义权限只有vip用户能访问,注意我们初始化时候的顺序是认证在权限前面的,所以只要认证通过~我们这里就可以通过request.user,拿到我们用户信息request.auth就能拿到用户对象"""if request.user and request.auth.type == 2:return Trueelse:return False第一步 写权限类class TestAuthView(APIView):authentication_classes = [MyAuth, ]permission_classes = [MyPermission, ]def get(self, request, *args, **kwargs):print(request.user)print(request.auth)username = request.userreturn Response(username)局部视图注册REST_FRAMEWORK = {# 默认使用的版本控制类'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',# 允许的版本'ALLOWED_VERSIONS': ['v1', 'v2'],# 版本使用的参数名称'VERSION_PARAM': 'version',# 默认使用的版本'DEFAULT_VERSION': 'v1',# 配置全局认证# 'DEFAULT_AUTHENTICATION_CLASSES': ["BRQP.utils.MyAuth", ]# 配置全局权限"DEFAULT_PERMISSION_CLASSES": ["BROP.utils.MyPermission"]}全局注册 settings.py Django Rest Framework 频率组件DRF的频率频率限制是做什么的开放平台的API接口调用需要限制其频率,以节约服务器资源和避免恶意的频繁调用。我们的DRF提供了一些频率限制的方法,我们看一下。频率组件源码版本,认证,权限,频率这几个组件的源码是一个流程,这里就不再带大家走源码了~相信大家可以自己看懂了~~下面我们谈谈频率组件实现的原理~~频率组件原理DRF中的频率控制基本原理是基于访问次数和时间的,当然我们可以通过自己定义的方法来实现。当我们请求进来,走到我们频率组件的时候,DRF内部会有一个字典来记录访问者的IP,以这个访问者的IP为key,value为一个列表,存放访问者每次访问的时间,{  IP1: [第三次访问时间,第二次访问时间,第一次访问时间],}把每次访问最新时间放入列表的最前面,记录这样一个数据结构后,通过什么方式限流呢~~如果我们设置的是10秒内只能访问5次,-- 1,判断访问者的IP是否在这个请求IP的字典里-- 2,保证这个列表里都是最近10秒内的访问的时间判断当前请求时间和列表里最早的(也就是最后的)请求时间的查如果差大于10秒,说明请求以及不是最近10秒内的,删除掉,继续判断倒数第二个,直到差值小于10秒-- 3,判断列表的长度(即访问次数),是否大于我们设置的5次,如果大于就限流,否则放行,并把时间放入列表的最前面。频率组件的详细用法频率组件的配置方式其实跟上面的组件都一样,我们看下频率组件的使用。VISIT_RECORD = {}class MyThrottle(object):def __init__(self):self.history = Nonedef allow_request(self, request, view):"""自定义频率限制60秒内只能访问三次"""# 获取用户IPip = request.META.get("REMOTE_ADDR")timestamp = time.time()if ip not in VISIT_RECORD:VISIT_RECORD[ip] = [timestamp, ]return Truehistory = VISIT_RECORD[ip]self.history = historyhistory.insert(0, timestamp)while history and history[-1] < timestamp - 60:history.pop()if len(history) > 3:return Falseelse:return Truedef wait(self):"""限制时间还剩多少"""timestamp = time.time()return 60 - (timestamp - self.history[-1])自定义的频率限制类REST_FRAMEWORK = {# ......# 频率限制的配置"DEFAULT_THROTTLE_CLASSES": ["Throttle.throttle.MyThrottle"],}}配置自定义频率限制from rest_framework.throttling import SimpleRateThrottleclass MyVisitThrottle(SimpleRateThrottle):scope = "WD"def get_cache_key(self, request, view):return self.get_ident(request)使用自带的频率限制类REST_FRAMEWORK = {# 频率限制的配置# "DEFAULT_THROTTLE_CLASSES": ["Throttle.throttle.MyVisitThrottle"],"DEFAULT_THROTTLE_CLASSES": ["Throttle.throttle.MyThrottle"],"DEFAULT_THROTTLE_RATES":{'WD':'5/m', #速率配置每分钟不能超过5次访问,WD是scope定义的值,}}配置频率限制我们可以在postman~~或者DRF自带的页面进行测试都可以~~ 
DRF 分页组件
Django Rest Framework 分页组件DRF的分页为什么要使用分页其实这个不说大家都知道,大家写项目的时候也是一定会用的,我们数据库有几千万条数据,这些数据需要展示,我们不可能直接从数据库把数据全部读取出来,这样会给内存造成特别大的压力,有可能还会内存溢出,所以我们希望一点一点的取,那展示的时候也是一样的,总是要进行分页显示,我们之前自己都写过分页。那么大家想一个问题,在数据量特别大的时候,我们的分页会越往后读取速度越慢,当有一千万条数据,我要看最后一页的内容的时候,怎么能让我的查询速度变快。DRF给我们提供了三种分页方式,我们看下他们都是什么样的~~分页组件的使用DRF提供的三种分页from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination全局配置 REST_FRAMEWORK = {'PAGE_SIZE': 2}第一种 PageNumberPagination  看第n页,每页显示n条数据http://127.0.0.1:8000/book?page=2&size=1class MyPageNumber(PageNumberPagination):page_size = 2 # 每页显示多少条page_size_query_param = 'size' # URL中每页显示条数的参数page_query_param = 'page' # URL中页码的参数max_page_size = None # 最大页码数限制自定义分页类class BookView(APIView):def get(self, request):book_list = Book.objects.all()# 分页page_obj = MyPageNumber()page_article = page_obj.paginate_queryset(queryset=book_list, request=request, view=self)ret = BookSerializer(page_article, many=True)return Response(ret.data)视图class BookView(APIView):def get(self, request):book_list = Book.objects.all()# 分页page_obj = MyPageNumber()page_article = page_obj.paginate_queryset(queryset=book_list, request=request, view=self)ret = BookSerializer(page_article, many=True)# return Response(ret.data)# 返回带超链接 需返回的时候用内置的响应方法return page_obj.get_paginated_response(ret.data)返回带页码链接的响应第二种 LimitOffsetPagination 在第n个位置  向后查看n条数据http://127.0.0.1:8000/book?offset=2&limit=1class MyLimitOffset(LimitOffsetPagination):default_limit = 1limit_query_param = 'limit'offset_query_param = 'offset'max_limit = 999自定义的分页类# 视图和上面的大体一致# 只有用的分页类不同,其他都相同class BookView(APIView):def get(self, request):book_list = Book.objects.all()# 分页page_obj = MyLimitOffset()page_article = page_obj.paginate_queryset(queryset=book_list, request=request, view=self)ret = BookSerializer(page_article, many=True)# return Response(ret.data)# 返回带超链接 需返回的时候用内置的响应方法return page_obj.get_paginated_response(ret.data)视图第三种 CursorPagination 加密游标的分页 把上一页和下一页的id记住 class MyCursorPagination(CursorPagination):cursor_query_param = 'cursor'page_size = 1ordering = '-id'自定义分页类class BookView(APIView):def get(self, request):book_list = Book.objects.all()# 分页page_obj = MyCursorPagination()page_article = page_obj.paginate_queryset(queryset=book_list, request=request, view=self)ret = BookSerializer(page_article, many=True)# return Response(ret.data)# 返回带超链接 需返回的时候用内置的响应方法return page_obj.get_paginated_response(ret.data)视图 
DRF 解析器和渲染器
一,DRF 解析器根据请求头 content-type 选择对应的解析器就请求体内容进行处理。1. 仅处理请求头content-type为application/json的请求体from django.conf.urls import url, includefrom web.views.s5_parser import TestViewurlpatterns = [url(r'test/', TestView.as_view(), name='test'),]urls.pyfrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.request import Requestfrom rest_framework.parsers import JSONParserclass TestView(APIView):parser_classes = [JSONParser, ]def post(self, request, *args, **kwargs):print(request.content_type)# 获取请求的值,并使用对应的JSONParser进行处理print(request.data)# application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值print(request.POST)print(request.FILES)return Response('POST请求,响应内容')def put(self, request, *args, **kwargs):return Response('PUT请求,响应内容')views.py 2. 仅处理请求头content-type为application/x-www-form-urlencoded 的请求体from django.conf.urls import url, includefrom web.views import TestViewurlpatterns = [url(r'test/', TestView.as_view(), name='test'),]urls.py from rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.request import Requestfrom rest_framework.parsers import FormParserclass TestView(APIView):parser_classes = [FormParser, ]def post(self, request, *args, **kwargs):print(request.content_type)# 获取请求的值,并使用对应的JSONParser进行处理print(request.data)# application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值print(request.POST)print(request.FILES)return Response('POST请求,响应内容')def put(self, request, *args, **kwargs):return Response('PUT请求,响应内容')views.py3. 仅处理请求头content-type为multipart/form-data的请求体from django.conf.urls import url, includefrom web.views import TestViewurlpatterns = [url(r'test/', TestView.as_view(), name='test'),]urls.pyfrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.request import Requestfrom rest_framework.parsers import MultiPartParserclass TestView(APIView):parser_classes = [MultiPartParser, ]def post(self, request, *args, **kwargs):print(request.content_type)# 获取请求的值,并使用对应的JSONParser进行处理print(request.data)# application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值print(request.POST)print(request.FILES)return Response('POST请求,响应内容')def put(self, request, *args, **kwargs):return Response('PUT请求,响应内容')views.py<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="http://127.0.0.1:8000/test/" method="post" enctype="multipart/form-data"><input type="text" name="user" /><input type="file" name="img"><input type="submit" value="提交"></form></body></html>upload.html4. 仅上传文件from django.conf.urls import url, includefrom web.views import TestViewurlpatterns = [url(r'test/(?P<filename>[^/]+)', TestView.as_view(), name='test'),]urls.pyfrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.request import Requestfrom rest_framework.parsers import FileUploadParserclass TestView(APIView):parser_classes = [FileUploadParser, ]def post(self, request, filename, *args, **kwargs):print(filename)print(request.content_type)# 获取请求的值,并使用对应的JSONParser进行处理print(request.data)# application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值print(request.POST)print(request.FILES)return Response('POST请求,响应内容')def put(self, request, *args, **kwargs):return Response('PUT请求,响应内容')views.py<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="http://127.0.0.1:8000/test/f1.numbers" method="post" enctype="multipart/form-data"><input type="text" name="user" /><input type="file" name="img"><input type="submit" value="提交"></form></body></html>upload.html5. 同时多个Parser当同时使用多个parser时,rest framework会根据请求头content-type自动进行比对,并使用对应parserfrom django.conf.urls import url, includefrom web.views import TestViewurlpatterns = [url(r'test/', TestView.as_view(), name='test'),]urls.pyfrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework.request import Requestfrom rest_framework.parsers import JSONParser, FormParser, MultiPartParserclass TestView(APIView):parser_classes = [JSONParser, FormParser, MultiPartParser, ]def post(self, request, *args, **kwargs):print(request.content_type)# 获取请求的值,并使用对应的JSONParser进行处理print(request.data)# application/x-www-form-urlencoded 或 multipart/form-data时,request.POST中才有值print(request.POST)print(request.FILES)return Response('POST请求,响应内容')def put(self, request, *args, **kwargs):return Response('PUT请求,响应内容')views.py6. 全局使用REST_FRAMEWORK = {'DEFAULT_PA
极验滑动验证码
Django中使用滑动验证码注册账号极验官方: https://www.geetest.com/注册好后去申请key和value 下载SDK从Github: gt3-python-sdk下载.zip文件这是python的, 当然也有其他语言的~~知道你们懒, 还是贴上来吧: https://docs.geetest.com/install/deploy/server/csharp 运行项目找到demo里面的django_demo文件夹可以直接启动项目看到效果当然, 里面还有flask和tornado的项目包from geetest import GeetestLibfrom django.contrib import authfrom django.http import JsonResponsefrom django.shortcuts import renderdef login(request):if request.method == "POST":# 初始化一个给AJAX返回的数据ret = {"status": 0, "msg": ""}# 从提交过来的数据中 取到用户名和密码username = request.POST.get("username")pwd = request.POST.get("password")# 获取极验 滑动验证码相关的参数gt = GeetestLib(pc_geetest_id, pc_geetest_key)challenge = request.POST.get(gt.FN_CHALLENGE, '')validate = request.POST.get(gt.FN_VALIDATE, '')seccode = request.POST.get(gt.FN_SECCODE, '')status = request.session[gt.GT_STATUS_SESSION_KEY]user_id = request.session["user_id"]if status:result = gt.success_validate(challenge, validate, seccode, user_id)else:result = gt.failback_validate(challenge, validate, seccode)if result:# 验证码正确# 利用auth模块做用户名和密码的校验user = auth.authenticate(username=username, password=pwd)if user:# 用户名密码正确# 给用户做登录auth.login(request, user) # 将登录用户赋值给 request.userret["msg"] = "/index/"else:# 用户名密码错误ret["status"] = 1ret["msg"] = "用户名或密码错误!"else:ret["status"] = 1ret["msg"] = "验证码错误"return JsonResponse(ret)return render(request, "login.html")# 请在官网申请ID使用,示例ID不可使用pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"# 处理极验 获取验证码的视图def get_geetest(request):user_id = 'test'gt = GeetestLib(pc_geetest_id, pc_geetest_key)status = gt.pre_process(user_id)request.session[gt.GT_STATUS_SESSION_KEY] = statusrequest.session["user_id"] = user_idresponse_str = gt.get_response_str()return HttpResponse(response_str)views.pyfrom django.conf.urls import urlfrom app01 import viewsurlpatterns = [url(r'^admin/', admin.site.urls),url(r'^login/', views.login),# 极验滑动验证码 获取验证码的urlurl(r'^pc-geetest/register', views.get_geetest),]urls.py<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>欢迎登录</title><link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"><link rel="stylesheet" href="/static/mystyle.css"></head><body><div class="container"><div class="row"><form class="form-horizontal col-md-6 col-md-offset-3 login-form">{% csrf_token %}<div class="form-group"><label for="username" class="col-sm-2 control-label">用户名</label><div class="col-sm-10"><input type="text" class="form-control" id="username" name="username" placeholder="用户名"></div></div><div class="form-group"><label for="password" class="col-sm-2 control-label">密码</label><div class="col-sm-10"><input type="password" class="form-control" id="password" name="password" placeholder="密码"></div></div><div class="form-group"><!-- 放置极验的滑动验证码 --><div id="popup-captcha"></div></div><div class="form-group"><div class="col-sm-offset-2 col-sm-10"><button type="button" class="btn btn-default" id="login-button">登录</button><span class="login-error"></span></div></div></form></div></div><script src="/static/jquery-3.3.1.js"></script><script src="/static/bootstrap/js/bootstrap.min.js"></script><!-- 引入封装了failback的接口--initGeetest --><script src="http://static.geetest.com/static/tools/gt.js"></script><script>// 极验 发送登录数据的var handlerPopup = function (captchaObj) {// 成功的回调captchaObj.onSuccess(function () {var validate = captchaObj.getValidate();// 1. 取到用户填写的用户名和密码 -> 取input框的值var username = $("#username").val();var password = $("#password").val();$.ajax({url: "/login/", // 进行二次验证type: "post",dataType: "json",data: {username: username,password: password,csrfmiddlewaretoken: $("[name='csrfmiddlewaretoken']").val(),geetest_challenge: validate.geetest_challenge,geetest_validate: validate.geetest_validate,geetest_seccode: validate.geetest_seccode},success: function (data) {console.log(data);if (data.status) {// 有错误,在页面上提示$(".login-error").text(data.msg);} else {// 登陆成功location.href = data.msg;}}});});$("#login-button").click(function () {captchaObj.show();});// 将验证码加到id为captcha的元素里captchaObj.appendTo("#popup-captcha");// 更多接口参考:http://www.geetest.com/install/sections/idx-client-sdk.html};// 当input框获取焦点时将之前的错误清空$("#username,#password").focus(function () {// 将之前的错误清空$(&
Django 笔记二 新建 ~ 渲染
新建APPpython manange.py startapp app_name然后右键 pycharm 的项目目录,将新建的目录从服务器上下载进来 URL(Uniform Resoure Locator)统一资源定位符格式:http://127.0.0.1:8000/hello/URL解释:schema://host[:port#]/path/.../[?query-string][#anchor]schema:指定使用的协议(例如:http,https,ftp)host:Http服务器的IP地址或者域名port:端口号,http默认是80端口path:访问资源的路径query-string:发送给http服务器的数据anchor:锚点 urls.py的作用path('test/<xx>/', views.test)前面的url匹配成功后就会调用后面的视图函数。尖括号从url中捕获值,包含一个转化器类型(converter type)。没有转化器,将匹配任意字符串,当然也包括了 / 字符。注:<xx> 必须与视图函数的参数一致例:def test(request, xx) 转换器:str:匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式int:匹配正整数,包含0slug:匹配字母、数字以及横杠、下划线组成的字符串uuid:匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00path:匹配任何非空字符串,包含了路径分隔符 re_path 正则匹配:可使用正则的方法匹配 include 的作用:一个 project 有一个棕的 urls.py ,各个 app 也可以自己建立自己的 urls.py用 include() 函数在project的 urls.py 文件进行注册例:from django.urls import path, includefrom . import viewsurlpatterns = [path('book/', include('book.urls'))] 此时 APP books 里面的 urls.pyfrom django.urls import pathfrom . import viewsurlpatterns = [path('index/', views.index)]此时 APP books 里面的 views.pyfrom django.shortcuts import renderdef index(request):return render(request, '这是 book 的首页') kwargs 的作用:不定长参数如果在分 urls.py 添加了字典参数path('book/',views.test, {‘switch’: 'true'})views 下面的 test 函数形参需要额外增加一个 **kwargs 如果在使用了 include , 主 views 添加了 字典参数其分之下所有函数形参都需要添加 **kwargs name 的作用:可以给 url 取名字,通过该名字找到对应 url,这样左的原因是防止 url 的规则更改,导致使用了该 url 的地方也要更改,但如去了名字,就不要做任何改动了。APP books里的 urls.pypath('article_name/', views.article_new, name='new_article')APP books 里的 views.pyfrom django.shortcuts import render, reverse, redirectreturn redirect(reverse('new_article'))注:redirect 是重定向(跳转), reverse 是将 url 的 name 解析成 url 本身的函数 templates 模板:该模板可新建各个以 app 命名的目录存放各自 app 模板文件然后在 setting.py 中的模板路径配置修改以下内容TEMPLATES = ['DIRS': [os.path.join(BASE_DIR, 'templates')],] 渲染模板(三种方法选一,皆需 import 导入才能使用):1.直接将html字符串硬编码 HttpResponse 中def index(request):return HttpResponse('<h1>Hello Django World!</h1>') 2. django.template.loader 定义了函数以加载模板from django.template.loader import get_templatedef index(request):t = get_template('book/index.html')html = t.render()return HttpResponse(html) 3.使用 render 进行渲染(建议此方法)def index(request):return render(request, 'book/index.html') 
Django 笔记三模版路径 ~ 静态引用
1.模版路径:在 settings,py 里的 TEMPLATES = [] 内添加一句代码拼接路径'DIRS': [os.path.join(BASE_DIR, 'templates')] 有两种通用方式(看个人喜好)一:DIRS 定义一个目录列表,模板引擎按列表顺序搜索这些目录以查找模板源文件。在项目根目录下的 templates 文件夹下创建多个以APP名命名的存放HTML页面的文件夹。 二: APP_DIRS告诉模板引擎是否应该进入每个已安装的应用中查找模板,值为True则模板会去安装了的app下面的templates文件夹查找模板。在每个APP文件夹下分别创建一个独立的 templates ,存放各自APP的HTML页面,然后在 settings,py 里的 INSTALLED_APPS = [] 内添加 APP 名的字符串(注册APP)。例:'book', 2.模版变量:语法:{{ 变量名 }}命名规则:由字母和数字以及下划线组成,不能有空格和标点符号类型:可以使用字典、模型、方法、函数、列表注:列表等查询方式:{{ list1.下标 }},而不是 {{ list1[下标] }}。注意:不要和python或django关键字重名 render 里的一个上下文 context 以键值对方式进行传递参数, 然后便可在模版中使用下面的 key 名 例:return render(request, 'book/book_index.html', context={'name': 'bear','list1': [1,2,3,4,5],'age': 18}) 3.过滤器:语法:{{ 变量名|方法 }}例:{{ name|lower }}注意: 使用参数的时候,冒号和参数之间不能有任何空格。常用的过滤器 date和time过滤器格式: 过滤器的例子:    4.静态文件目录: 在 settings.py 文件中添加以下代码 设置静态文件目录路径STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] 5.静态文件引用:
Django 中间件 请求前
中间件:class TestMiddleware(object):"""中间件类"""def __init__(self):"""服务器重启之后,接收第一个请求时调用"""passdef process_request(self, request):"""产生request对象之后,url匹配之前调用"""passdef process_view(self, request, view_func, *view_args, **view_kwargs):"""url匹配之后,视图函数调用之前调用"""passdef process_exception(self, request, exception):"""视图函数发生异常时调用"""passdef process_response(self, request, response):"""视图函数调用之后,内容返回浏览器之前调用"""return response 正常调用顺序是从上往下,但如注册了多个中间件类中包含 process_exception函数的时候,process_exception函数调用的顺序跟注册的顺序是相反的,下面两张图说明:  流程图: 注意:process_response之前的中间件如无返回,则默认返回 None,会继续执行下一个中间件,但如有返回,则直接跳到 process_response中间件   例子:1.在app内新建一个 middleware.py 文件2.from django.http import HttpResponseclass BlacklistIPSMiddleware(object):"""中间件类"""EXCLUDE_IPS = ['192.168.1.1']def process_view(self, request, view_func, *view_args, **view_kwargs):"""视图函数调用之前会调用"""user_ip = request.META['REMOTE_ADDR'] # 获取访问用户的IPif user_ip in BlacklistIPSMiddleware.EXCLUDE_IPS:return HttpResponse('您在黑名单中')3.在settings配置文件中注册中间件类# 'app名.中间件文件名.中间件类名'MIDDLEWARE_CLASSES = (...,...,‘book.middleware.BlacklistIPSMiddleware’) 
Django基础篇--Models
在Django中创建与数据库的链接并调用数据库的数据是很关键的步骤,那么怎么实现这个过程呢?下面这篇文章简单梳理了一下创建Model层的过程和应用模型-Models首先需要理解什么是模型? 模型,根据数据库中数据表中数据表的结构而创建出来的class 1. 创建和使用模型的方法 1)创建数据库需要到数据库中手动创建     create  database  … 2) 配置      setting.py中配置  DATABASES ={‘default’:{‘ENGINE’:‘NAME’:},} 参数的解析:1. ENGINE: 引擎      django.db.backends.mysql2. NAME :连接到的数据库名称       web.db3.USER: 用户名称 ,通常为root4. PASSWORD: 密码5. HOST : 连接的主机,本机localhost/127.0.0.1/可以不写6. PORT :端口,3306例如下面这张图:在做数据库集群的时候如果要连接多个数据库,可以在写default后面再添加一个字典类型的key-value对,key的名称可以自己定义。 2. 如何编写Models首先需要理解两个概念,实体类和实体  1)实体类   Models中每个class都称之为模型类(Model)或者实体类(Entry)   Models中的每个实体类,必须继承自models.Model(原生类是没有models的操作的)  2)实体:    数据表中的一行记录,就是一个实体。    实体完整性:确保每张表中的数据不能有重复。      主键是为了实现实体完整性的方法之一。 3.Django中提供的数据字段 和 字段选项 这里只写一些比较常用的,更多详细的用法细节可以移步到官网 : www.djangoproject.com在Documentaiton栏目中相应找到 The  model  layer 1. 数据字段   1) CharField()             字符串, 属性值max_length必须要写,表明字符串长度,相当于原生MySQL中的char()   2) BooleanField()        布尔值,True或者False   3) DataField()              时间,只有日期,不带时间点,例如:2018-1-1   4) DateTimeField()      时间,带时间点,例如:2018-1-1 12:00:00   5) DecimalField()         带小数点的定点数,可以用于与金融或者数字有关的字段   6) EmailField()            Email类型   7) URLField()             URL类型,在数据库中会转换为固定长度(200)字符串   8) FileField()              文件类型   9) IntergerField()        整型数   10) FloatField()          浮点数   11) ImageField()        图片,一般在实际应用中存放图片的地址   12) TextField()           文本    2. 字段选项   1) null                       是否允许为空,赋值True或者False,例如:name =  models.Char(max_length= 10 , null = False)   2) blank                    是否为空格   3) choices            4)db_column           属性名,例如:db_column = ' music_name '   5)db_index              索引,为该字段设置索引   6) default                默认值,例如:password = models.Char(max_length= 10 , defalut = '123456')   7) primary_key       主键,值为True/False,默认为False(不设置为主键) 
Django--数据库查询操作
 MySQL是几乎每一个项目都会使用的一个关系数据库,又因为它是开源免费的,所以很多企业都用它来作为自家后台的数据库。BAT这类大公司除外,它们的业务数据是以亿级别来讨论的,而MySQL的单表6000万条数据运行在64位操作系统中已经很吃力了,所以像BAT这类有钱,有需求的企业来说,用的数据库当然就是Oracle了。 抛开Oracle不说,MySQL的优越性还是挺突出的,有面对大数据量的分表操作和比较强大的多表关联查询,索引优化查询速度等等。在中小型企业中还是很吃得开的。那么作为一个中小型的Django项目中,如何使用MySQL呢?本文的内容主要是介绍Django项目中如何通过ORM关系映射来操作MySQL数据库,内容比较基础,提供给初学者作为借鉴使用。 关于什么是ORM关系映射,可以写一篇文章了,度娘上也有很多这类型的文章,我就不讨论了,这篇文章的主要内容还是关注在基础操作上。 以下的案例一我自己曾经的一个项目,论坛网址为例来进行说明。首先我为我的网站建立了一个Author表,一个Article表,一个News表(实际上不止这些表),这里就以这些表为例进行说明。Author表和Aticle表我做了一个多对多的关联,News表相对独立。具体的表字段如下:Author(缩减版,为了降低学习难度)Id(用于索引) Name Phone PasswordArticle表tid Title Content Star(点赞数量) CommentNews表Nid Message实际上以上这些表格的部分内容用非关系型数据库Mongodb来存更好,这里就不展开讨论了,以后有机会再谈谈自己的一些学习心得。 一、数据库常用操作细节--增删查改(1)插入数据ORM有三种方法实现对数据库插入数据1.Django中可以直接通过Views(视图)在DB(数据库)中创建记录语法如下:Entry.objects.create(属性1=值,属性2=值)例如: Author.objects.create(id = 1, name=’Tom’, phone= '88888888888' , password = '123456')概念理解:什么是Entry?Entry的意思是实体,所有的数据库中的表都是通过实体来映射到程序中的。 2. 创建一个实体对象用于进行数据库的操作,当向数据库中增加数据时,可以通过save()方法完成增加数据语法如下:obj= Entry(属性1=值,属性2=值)obj.save()例如:author = Author(id = 1, name=’Tom’, phone= '88888888888' , password = '123456')author.save()3. 通过字典创建实体对象,再调用save()例如:dic = {id = 1,name = ‘Tom’,phone = ‘88888888888’,password = '123456'}author = Author(**dic)author.save() (2)查询操作敲黑板,查询操作是数据库操作的重点内容,Django支持不少的查询操作,但是复杂点的关联操作,ORM就无法实现了?那怎么办?没有办法了吗?实现的方法是有的,用pymysql的原生语法去操作就可以了 好了,言归正传,开始讲ORM的查询操作~~ 首先,要注意:所有的查询都要在Entry.objects的基础上完成1. 基本查询操作 据说,Django已经查询语句的效率优化上已经达到最好,在ORM内部在B树的基础上做了一些修改。      1)语法:all()用法:Entry.objects.all()相当于原生SQL的 select * from返回值:QuerySet(查询结果集),以字典的形式存储查询的结果,因此对结果取数据的时候可以使用python的字典方法           2)语法:Entry.objects.all().values()          查询所有记录的某一列的值例如:Author.objects.all().values()相当于 select name from Author      3)语法: Entry.objects.all().values_list(‘列1’ , ’列2’)例如:Author.objects.all().values_list(‘name’,’age’ )相当于 select name,age from Author  注意:一定是从查询结果集中进行筛选,即必须要有all()方法     1.1版本可以直接用values(‘name’,’age’)  取多个列值       4) 语法: Entry.objects.get()      这条查询语句只能查找一条记录,也只能返回一条记录。如果查询返回多条记录会报错,即传入的参数一定要使得查询的记录只有一条,一般可以用id值      如果不确定可以用try , except语句来捕捉错误例如:Author.objects.get(id=4)      5) 语法: Entry.objects.exclude()      作用:对给定条件取反1 例如:房屋租赁系统,不想查哪个地区的房源2 Entry.objects.exclude(id=3)3 相当于 :select * from … where not id = 3;45 又例如:筛选id不是3,同时年龄不是35的客户6 Entry.objects.exclude(id=3,age=35)7 相当于 :select * from … where not (id = 3 and age=35);     6) 语法: Entry.objects.order_by(‘列名1’ , ’ - 列名2’)         对所有查询数据进行排序,可以多级排序(只需要多写几个列名就行)         注意:默认是升序排序,需要降序的话,只需要在列名前加‘ - ’(减号)       7)语法:Entry.objects.filter()        根据自定义条件查询结果集。条件可以是一个,也可以是多个,多个的话,条件用’,’逗号隔开,其内部是使用AND来进行条件连接的。       1. 使用Entry的属性来作为filter()的条件例如:Author.objects.filter(id=1)相当于select * from author where id=1        2. 使用Field Lookups (查询谓词,主要结合filter或者get一起用)                (1) __exact 等值判断(用得比较少)                  语法:Entry.objects.get(id__exact=num)例如:Author.objects.get(id__exact=11)相当于select * from author where id=11                 (2)__contains 模糊查询例如:Entry.objects.get(headline__contains = ‘Lennon’)相当于 select … where headline like %Lennon%;                 (3)__in 范围查询例如:Entry.objects.get(id__in =[1,3,5])相当于 select …. where id in [1,3,5]                  (4)__gt  __lt   大于,小于查询例如:Entry.objects.get(id__gt=3)相当于 select … where id>3                   (5) __startwith  以…开始  (注意与__contains区分)Entry.objects.get(headline__startwith = ‘Lennon’)相当于select … where headline like Lennon%;         3.子查询    例如:需求是获取比贾乃亮年龄要大的人inner = Author.objects.filter(name__exat=’贾乃亮’).values(‘age’) #获取贾乃亮的年龄authors = Author.objects.filter(age__gt = inner)实际上是嵌套查询操作:获取结果集的内容,因为结果集是许多条记录(集合)组成的一个列表,所以可以用下标来索引每一条记录,再通过索引来获得具体的字段例如:name = authors[0][1]第一天记录是贾乃亮相关的信息,信息下的第二个字段为姓名  二、 F()操作和Q()操作1. F()操作     在执行中获取某列的值,再进行增删改查。     语法: F(‘列名’)例如: SQL语句中 update author set age=age+10Django中写法:Author.object.all().update(age=F(‘age’)+10)注意:在用F()函数前需要导包(下面的Q()函数也一样)    from  django.db.models  import F    应用:一般用在累加操作  2. Q()操作Author.objects.filter(id=1,age=35)  这种情况是id=1 and age=35,无法写or逻辑运算符可以使用以下的语法实现:Author.objects.filter( Q(id_exact=1)|Q(age=35),name=’王’ ) #也可以用,逗号表示and   and逻辑运算应用:登录系统的用户和密码验证
Django基础篇--模板和路由分发
Django模板首先什么是一个模板?简单来说就是一个网页,可以被view响应给用户目的是为了解决复杂的显示问题 2. 模板的设置问题setting.py中的TEMPLATES配置  1)BACKEND: 指定模板的搜索引擎,不用改动  2)DIRS: 指定模板存放的目录     DIRS=[‘ index.temp’ ,’music.temp’]     但是,如果DIRS中为空的话,那么django会自动搜索每个应用中的templates的文件夹作为模板的管理目录     推荐:DIRS保持空,但必须在每个应用中,创建一个templates的文件夹   3)APP_DIRS     True : 首先从DIRS中指定的文件夹中查找模板,如果没有找到指定模板的话,则再搜索templates目录 3. 模板的加载方式  HttpResponse() 这是加载字符串的 1. 使用loader 获取模板,通过HttpResponse进行响应   最终返回模板前都是把模板转化为字符串 2. 使用render直接加载   return  render (request, ’模板名称’ , {参数字典}) 4. url()函数的name参数(这个用法是比较灵活的)urlpatterns = [ url( regex,views , kwargs =None, name = None)]name: 定义当前url的别名,允许在Template中使用别名来例如:urlpatterns = [ url( regex,views , kwargs =None, name = ’my’)]在超链接中用得很多需要使用标签{% url ‘name值’%}    #服务器解析<a href=’ {%url  ‘ my’  %}’ >去往second 页面</a>注意:name别名不能重复定义  url参数 <a href=’/music/show/35’> 去往show/35地址的页面</a>  #不用别名的条件下写全路由  <a href=’ {%url  ‘ my’  87 %}’ >去往show/87页面</a>  #传参数,直接在’别名’ 后加空格加参数 模板语法:1. 变量  作用:允许将后端的数据传递给模板(html),模板中,会根据变量实际值进行显示  1)允许传递给变量的数据类型   数字,字符串,列表,元组,字典,函数,对象(类) (注意:集合不行)   注意:函数一定要返回值  2)如何传递变量到模板中renderdict={ ‘变量1’:’值1’,‘变量1’:’值1’,}return  render(request, ‘xx.html’,dict) 重点:   如果是传列表,元组,字典等,先组织好数据,再把数据放dict中   例如:dict ={ ‘l’ :list, ‘d’ :dict,}或者用locals()将局部变量组织成字典传入参数,对象除外  3)模板中获取变量的值   {{ 变量名称}}      #字符串   {{变量名称.1}}     #列表/元组   {{变量名称. key}}    #字典   {{函数名}}         #函数   {{类名.方法名 }}    #对象 2. 标签 (具体更多标签用法可以看看官方文档)1)什么是标签?允许嵌套一些服务器段端的逻辑运算到模板中而变量只管数值,标签管运算 2)语法  {% %}注意:写的过程运算符,变量都必须用空格隔开常用标签1. {% if 条件%}   #条件可以用简单逻辑判断,例如 >  <  == 等等     {% else  %}     {% end if %}接受not, and ,or 运算但是 and  和  or不能同时出现以下内容当做False处理:   空[]   空 ()   空{}数值0空字符串None  {% ifequal  值1  值2 %}   #判断值1 和值2是否相等,相当于值1 ==值2 {% else %} {% endifequal %} 2. for标签 {% for  变量  in  列表| 元组 | 字典 %} {% endfor%} 允许使用的内置变量(免声明):   forloop  1. forloop.counter :记录循环次数,从1开始  2. forloop.counter0 :同上,从0开始  3. forloop.revcounter:记录未被遍历的数量  4. forloop.revcounter0 :同上,从0开始  5. forloop.first : 布尔值,标记是否是第一个项(通常对第一个、最后一个做一些样式设置)  6. forloop.first : 布尔值,标记是否是最后一个项 <li  { % if  forloop.first  %} ‘color:red’ {% endif%} ></li> 3.过滤器在显示变量之前,运行对数据进行筛选或改变语法:{{ var | 过滤器}}常用过滤器:1. {{var| upper}} 将英文字母改写为大写2. {{var| lower}}   将英文字母改写为小写常用的过滤器1. 加法操作var|add : ‘ num’first | add: second   #first 和 second 是两个数字类型的列表 2.对小数进行四舍五入(金融和交系统) var | floatformat:num  # num指定保留多少位小数 3. 超长的字符显示省略号(一些字符串显示)value | truncatechars:num   #num指定保留的字符数value | truncatewords: num 也可以用css实现white-space:nowraptext-overflow:ellipsis
Django基础篇--用户权限管理和组管理
Django作为一个成熟的python后台开发框架,为开发者提供了很多内置的功能,开发者只需要做一些配置就可以完成原生操作中比较复杂的代码编写。这些内置功能中其中一个比较强大的功能就是后台用户管理类。首先什么是用户?用户指能够登录站点,进行对本站点的进行操作(例如session会话,日志管理等)的开发者账号。在Django中可以输入网址url/admin(例如:localhost:8000/admin)进入管理页面来创建用户和进行组管理。什么是组?组就是指用户拥有的权限,一个组可以将用户允许拥有的权限放在一起。 Django管理页面之用户创建 首先,想用进入管理页面,需要去创建一个超级用户。如何创建?Linux操作系统进入项目所在的目录(manage.py文件同级目录)输入./manage.py  createsuperuser 接下来跟着终端输入的提示来输入就可以了,需要设置的值有用户名,密码,邮箱(注意:密码要大于8位,且不能全部为数字,并且不能跟用户名太相似,否则就会报错)。创建好超级用户之后,在浏览器输入localhost:8000/admin进入用户登录入口 输入账号和密码后,可以看到主页的界面    Django管理页面之用户和组      下面主要讲讲认证和权限这一块的内容。鼠标点击‘用户’,即可以进入以下的用户展示页面以修改超级用户Tan为例点击进入Tan的管理页面,拖动到下面可以看到用户权限一栏,可以为每个用户设置不同的权限,如对日志的管理权限,对其他用户的管理权限,对数据库的管理权限等等。直接点击选择,再按中间的箭头就可以添加权限了。 在当前页面往上拉,可以看到组管理栏,点击右上角的‘+’符号,可以进入组添加页面那么什么是组,组有什么用呢?Django管理后台管理中将同类型的管理权限组织在一起,就叫做一个组,为这个组织起的名称叫组名。而用户可以自由地添加到组中,被添加到组中的用户会拥有这个组中所包含的管理权限。这样就很好地实现了多人拥有相同权限的管理,而不需要手动为每个用户添加相同重复的权限,用户只需要添加自己独特拥有的权限就可以。创建完组后,可以在‘用户’设置页面,看到创建好的组,只要点击中间的箭头按钮就可以将用户添加到组里面。(这里由于字符集的关系,我用英文‘log admin’来表示‘日志管理’组)回退到‘站点管理’页面,点击‘组’,可以看到之前创建的组。点击进去还可以修改组的权限。 注意以下这一栏的内容一般对于超级用户而言是全部选择的,而对于普通用户(由超级用户创建)而言,就可以通过以下的选项来对用户是否可以登录管理站点和(超级用户)是否可以删除用户进行设置。       Django管理页面之数据库管理          Django另外一个强大的功能是它能够在管理页面中对数据库进行直接的操作。当初的设计据说是为了方便新闻内容的更新所以添加上上去的。那么Django是如何实现这个过程的呢?首先我把一些必要的代码写出来。在admin.py文件中的代码如下from django.contrib import admin #这一句代码是在初始化app应用的时候,Django自动帮我们写好的,不用改动from .models import *admin.site.register(Author)原理解析:首先第一句代码:from django.contrib import admincontrib模块是Django的强大的功能包,也可以说是Django的标准库,就像math数学库是python的标准库一样。实际上,这个Django的标准库在Django中的地位可就强得多了。打个比喻,就好像腾讯在国内手机移动端通信App一家独大的位置一样,Django的很多模块包是基于contrib来构建的。具体有什么包,这些包又有什么用?需要讲的内容很多,笔者自己也没有学习完每一个包。在这里就大概说一下一些常用的包和它们的用途。admin : 自动化的站点管理工具(我们这里的管理页面就是用到了这个包)。auth : Django的用户验证包。csrf : 用于防御跨站请求伪造的包。redirects : 用来管理重定向的包。sessions : 会话包。sites : 一个可以让开发者在同一个数据库与 Django项目中管理多个网站的包。 这里借用网上一张总结得比较完整的图第二句和第三句代码:from  .models  import *     admin.site.register(Author)from  .models  import *  将models.py中定义的映射类引入到当前文件中,那么就可注册任何一个想在管理页面中进行管理的数据库表。为了在管理页面中对数据库表进行管理和操作,同时还需要在admin.site中注册相应的表。这里Django用的是admin下的site模块进行一个代理,将数据库表传输到管理页面中。如果打开Django的sites.py文件,就会看到下面一段说明意思是用register()来注册在models.py中定义的类,并通过get_urls()方法来提供一个获取管理views模型层的接口,跟我们平常经常的MTV架构(即Models-Templates-Views)没有什么太大的区别,本质上权限管理页面也是一个页面,服务端的逻辑还是那样。理解这一点很有用,接下来修改数据库实体对象的名称在页面上的显示结果就很好理解了。 在管理页面中可以看到Inedx-Authors表点击进入Authors表之后可以看到以下这个页面,但是有没发现,这个管理页面的字段都是英文?显然,这是跟实体类的字段保持一致的。那么如果是一个后台管理者,在平常阅读英文的时候觉得比较麻烦,那怎么办呢?可以在实体类的字段定义时为字段添加verbose_name属性(默认条件下verbose_name=None),例如:class Author(models.Model):name = models.CharField(max_length=30,null=False,verbose_name='用户名')age = models.IntegerField(verbose_name='年龄')email = models.URLField(null=True,verbose_name='Email')添加verbose_name属性值之后,字段的名称变为中文既然我上面说到,本质上权限管理页面也是一个页面,那么可以通过在页面中对数据进行一定的修改,那么映射到数据库中的数据也会跟着改变。上面的Authors实体类中,name,age,email字段都是可以修改的(注意id值不会显示在管理页面中) 在页面中可以数据库的内容进行修改,那么一个即使不懂编程的管理者,也可以为网站添加需要的内容。比如说一个新闻网站,有一个Aritcle的数据库,其中有一个Content的字段,那么新闻编辑人员就可以大段大段地往数据库里面添加文本。这就是为什么Django早期是为新闻网站而设计的框架的原因。自动化的管理可以让不懂编程的人在管理网站的时候更加方便和容易操作。这就是Django的强大之处。 
Django+MongoDB批量插入数据
在百万级和千万级数据级别进行插入,pymongo的insert_many()方法有着很强的优势。原因是每次使用insert_one()方法进行插入数据,都是要对数据库服务器进行一次访问,而这样的访问是基于TCP连接的,每次在发送请求的时候服务器端都需要对TCP报文进行解析。而使用insert_many(),可以一次给服务器发送大量的数据,只需要一次的TCP报文解析,既可以插入大量数据,避免了大量的报文解析工作。这样一来,数据插入的效率就会大大提升。所以,为了提升效率,笔者建议在十万数据级别以上使用insert_many()方法。下面是我通过使用pymongo原生方法和框架中的经常使用的Mongoengine进行的性能测试。 一、使用MongoengineMongoengine是基于面向对象的,在构建集合的时候非常方便,就直接写一个类。而pymongo是继承于MongoDB的文档类型的,所以在框架中使用会相对没有那么方便。但是Mongoengine目前来讲还有许多还没有完善的地方。个人觉得,还是根据实际的需求来选择不同的引擎。插入数据方面,Mongoengine在0.15版本之前只有save()方法,在0.15版本之后添加了insert_one()和inset_many()方法,具体可以看官方文档:。但是由于Django官方没有将MongoDB列为建议使用的数据库,所以不支持0.9之后的版本。 代码: 插入一千条数据插入方法:使用save()保存数据耗时:大概2秒钟 插入10万条数据插入方法:使用save()耗时:两分多钟 二、使用pymongo(1)测试1:插入一百万数据插入方法:insert_many()耗时:28秒 代码: 耗时时间: 插入结果: (2)测试2:在一百万条数据基础上增加十万数据插入方法:insert_one()耗时:1分钟29秒 代码: 耗时时间: 插入结果: 通过上面的测试,很明显的看到,无论是使用Mongoengine的save()方法,还是使用pymongo的insert_one()方法,在大数据量的插入时都会耗费大量的时间,在百万级别的数据就已经需要花费5-6个小时的时间了,显然,这样的效率是很低的。更别说千万级别的数据了。而使用pymongo的insert_many()方法,在插入百万条数据只是花费了28秒,速度好像快得有点难以想象是吧?按照这样的推算,千万级别数据的数据也大概花费不到5分钟就可以完成了。 那么为什么pymongo的原生方法insert_many()有这么高的效率呢?如何能更进一步提高效率呢?通过阅读源码和分析参数,来了解一下。insert_many() 定义源码:def insert_many(self, documents, ordered=True,bypass_document_validation=False, session=None):官方对参数的解析:参数:documents就是我们需要插入的数据文档,也就是上文的articles下面重点讲ordered和bypass_document_validaion1. ordered默认情况下是True,即按顺序来插入多条数据,如果发生错误,就会终止后面的插入。如果设置为False,文档将以任意的顺序将数据插入到服务器中,并且是并行进行的,客户端会尽力将所有的数据都插入到服务器中。所以,设置为False会在数据的插入效率有很大的提升,但也要付出一点数据安全性的代价。 2. bypass_document_validation默认情况为False。如果为True,那么允许在写入发生错误的时候推出文档级别的验证,不影响后面的数据插入。 设置合适的参数值,可以更好地为海量数据的插入提供更好的插入环境。 笔者:欢迎评论!希望本人的文章对阅读者有帮助,在写作过程中难免有疏漏,希望读者在发现错误的地方及时向我提出,我会尽快修改自己的技术疏漏。我也会定时写一些自己学习中的收获和项目中的经验。希望前行的路上,有更多乐于分享的人一起作伴。
Django数据库--事务及事务回滚
数据库的读写操作中,事务在保证数据的安全性和一致性方面起着关键的作用,而回滚正是这里面的核心操作。Django的ORM在事务方面也提供了不少的API。有事务出错的整体回滚操作,也有基于保存点的部分回滚。本文将讨论Django中的这两种机制的运行原理。 Django利用django.db.transaction模块中的API对数据库进行事务的管理Django provides a straightforward API in the django.db.transaction module to manage the autocommit state of each database connection. 主要函数:1. get_autocommit(using=None)   判断事务是否自动提交2. set_autocommit(autocommit, using=None)  设置自动提交事务 这些函数使接受一个 using 参数表示所要操作的数据库。如果未提供,则 Django 使用 "default" 数据库。 3. on_commit(do something)事务提交后马上执行任务,例如celery任务例如:with transation.atomic:    #do something and commit the transaction    transaction.on_commit(lambda: some_celery_task.delay('arg1'))  怎么使用?在哪里使用?事务是一系列的数据库操作,在数据的安全性和减少网络请求方面都有很大的优势。关于数据库事务的文章有很多,我这里就不展开讨论了。那么ORM中有哪些相关的API呢?trasation模块中最重要的是一个Atomic类,Atomic是一个上下文管理器。可以使用@transaction.atomic 或者with transaction.atomic 的方式来调用。 为了设置保存点,即断点进行事务的执行和回滚,可以嵌套使用with transaction.atomic,例如官网的例子(伪代码):with transaction.atomic(): # Outer atomic, start a new transactiontransaction.on_commit(foo) #事务提交后马上执行foo函数try:with transaction.atomic(): # Inner atomic block, create a savepointtransaction.on_commit(bar) #事务提交后马上执行foo函数raise SomeError() # Raising an exception - abort the savepointexcept SomeError:pass第一个with transaction.atomic()创建事务,第二个with transaction.atomic()创建保存点。虽然错误raiseSomeError是从‘内部’的保存点发出来的,但只会影响到‘外部’的保存点,即只会回滚前面的数据库操作。 下面还会讨论另一种创建保存点的方法。在使用transaction.atomic前需要注意的问题:1. 数据库的自动提交默认为开启,如果要将它关闭,必须很小心。一旦使用了transaction,即关闭了自动提交。2. 如果数据库之前的使用的是自动提交,那么在切换为非自动提交之前,必须确保当前没有活动的事务,通常可以手动执行commit() 或者 rollback() 函数来把未提交的事务提交或者回滚。  一、整体回滚所有的数据库更新操作都会在一个事务中执行,如果事务中任何一个环节出现错误,都会回滚整个事务。 案例(伪代码1):from django.db import transaction# open a [email protected] #装饰器格式def func_views(request):do_something()a = A() #实例化数据库模型try:a.save()except DatabaseError:pass此方案整个view都会在事务之中,所有对数据库的操作都是原子性的。 案例(伪代码2):from django.db import transactiondef func_views(request):try:with transaction.atomic(): #上下文格式,可以在python代码的任何位置使用a = A()a.save()#raise DatabaseError #测试用,检测是否能捕捉错误except DatabaseError: # 自动回滚,不需要任何操作pass此方案比较灵活,事务可以在代码中的任意地方开启,对于事务开启前的数据库操作是必定会执行的,事务开启后的数据库操作一旦出现错误就会回滚。 需要注意的是:1. python代码中对Models的修改和对数据库的修改的区别,数据库层面的修改不会影响Models实例变量。如果在代码中修改一个变量,例如:try:with transaction.atomic():a = A()a.attribute = True #A表的某一个属性(即数据库的某一列)a.save()raise DatabaseErrorexcept DatabaseError:passprint(a.attribute)#输出结果:True 即使数据库回滚了,但是a实例的变量a.attribute还是会保存在Models实例中,如果需要修改,就需要在except DatabaseError后面进行。 2. transaction不需要在代码中手动commit和rollback的。因为只有当一个transaction正常退出的时候,才会对数据库层面进行操作。除非我们手动调用transaction.commit和transaction.rollback 实际案例(此实例用伪代码2的格式):models.py数据表class Author(models.Model):name = models.CharField(max_length=30,null=False)age = models.IntegerField()email = models.URLField(null=True)class Count(models.Model):name = models.CharField(max_length=30)article_amount = models.IntegerField() views.pyfrom django.shortcuts import renderfrom django.http import HttpResponsefrom index.models import Author,Countfrom django.db import transaction,IntegrityErrordef add_author_views(request):author_name = u'renyingying'author = Author(name=author_name, age=24, email='[email protected]')# author.save()count = Count(name=author_name, article_amount=1)count.save()try:with transaction.atomic():author.save()raise DatabaseError #报出错误,检测事务是否能捕捉错误except DatabaseError: # 自动回滚,不需要任何操作pass事务外的数据库操作正常执行,而事务内的数据库操作则会回滚。author表  count表  将raise DatabaseError这一行代码注释掉,author才会有数据   二、保存点Savepoint(断点回滚)保存点是事务中的标记,从原理实现上来说是一个类似存储结构的类。可以回滚部分事务,而不是完整事务,同时会保存部分事务。python后端程序可以使用保存点。一旦打开事务atomic(),就会构建一系列等待提交或回滚的数据库操作。通常,如果发出回滚命令,则会回滚整个事务。保存点则提供了执行细粒度回滚的功能,而不是将执行的完全回滚transaction.rollback()。 工作原理:savepoint通过对返回sid后面的将要执行的数据库操作进行计数,并保存在内置的列表中,当对数据库数据库进行操作时遇到错误而中断,根据sid寻找之前的保存点并回滚数据,并将这个操作从列表中删除。 相关API:1. savepoint(using = None)创建一个新的保存点。这表示处于正常状态的事务的一个点。返回保存点ID(sid)。在一个事务中可以创建多个保存点。2. savepoint_commit(sid,using = None)发布保存点sid,从创建保存点开始执行的数据库操作将成为可能回滚事务的一部分3. savepoint_rollback(sid,using = None)将事务回滚到保存点sid4. clean_savepoints(using = None)重置用于生成唯一保存点ID的计数器值得注意的是:这些函数中的每一个都接受一个using参数,该参数是数据库的名称。如果using未提供参数,则使用"default"默认数据库。  案例:models.py上文的案例一样 views.pyfrom django.db import transaction# open a [email protected] add_author_views(request):# 自动提交方式# Author.objects.create(name=u'wangbaoqiang',age=33,email='[email protected]')author_name = u'linghuchong'author = Author(name=author_name,age=26,email='[email protected]')author.save()# transaction now contains author.save()sid = transaction.savepoint()try:count = Count(name=author_name, article_amount=1)count.save()# transaction now contains author.save() and count.save()transaction.savepoint_commit(sid)# open transaction still contains author.save() and count.save()except IntegrityError:transaction.savepoint_rollback(sid)# open transaction now contains only count.save()# 保存author操作回滚后,事务只剩下一个操作transaction.clean_savepoints() #清除保存点 注意:希望当遇到错误得到回滚的事务一定要放在try里面(如果放在try外面,虽然不会报错,但是是不会执行的)。如上面的例子,如果在给Count表执行插入数据发生错误,就会‘断点’回滚到Count表插入数据前,Author表插入的数据不变。 结果显示:Author表 Count表 参考文章:https://