微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!
Django Rest Framework源码剖析(一)-----认证
一、简介Django REST Framework(简称DRF),是一个用于构建Web API的强大且灵活的工具包。先说说REST:REST是一种Web API设计标准,是目前比较成熟的一套互联网应用程序的API设计理论。REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。Fielding是一个非常重要的人,他是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。所以,他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。我对这个词组的翻译是”表现层状态转化”。如果一个架构符合REST原则,就称它为RESTful架构。所以简单来说,RESTful是一种Web API设计规范,根据产品需求需要定出一份方便前后端的规范,因此不是所有的标准要求都需要遵循。学习RESTful API的资料:RESTful API 设计指南、理解RESTful架构二、安装配置安装需求:Python(2.7,3.2,3.3,3.4,3.5,3.6)Django(1.10,1.11,2.0 alpha)可选安装包:coreapi(1.32.0+) – 支持模式生成。Markdown(2.1.0+) – Markdown支持可浏览的API。django-filter(1.0.1+) – 过滤支持。django-crispy-forms – 改进的HTML显示过滤。django-guardian(1.1.1+) – 支持对象级别的权限控制。安装:pip install djangorestframeworkpip install markdown # Markdown support for the browsable API.pip install django-filter # Filtering support 三、知识预备在开始介绍Django REST Framework之前需要了解下django的路由系统以及csrf中间件。1.csrf校验:基于中间件的process_view方法实现对请求的csrf_token验证2.不需要csrf验证方法:fbv:from django.views.decorators.csrf import csrf_exempt@csrf_exemptdef index(request):passcbv:方式一:###方式一from django.shortcuts import render,HttpResponsefrom django.views.decorators.csrf import csrf_exempt,csrf_protectfrom django.utils.decorators import method_decoratorfrom django.views import Viewclass Myview(View):@method_decorator(csrf_exempt) #必须将装饰器写在dispatch上,单独加不生效def dispatch(self, request, *args, **kwargs):return super(Myview,self).dispatch(request,*args,**kwargs)def get(self):return HttpResponse('get')def post(self):return HttpResponse('post')def put(self):return HttpResponse('put')方式二:from django.shortcuts import render,HttpResponsefrom django.views.decorators.csrf import csrf_exempt,csrf_protectfrom django.utils.decorators import method_decoratorfrom django.views import View@method_decorator(csrf_exempt,name='dispatch')##name参数指定是dispatch方法class Myview(View):def dispatch(self, request, *args, **kwargs):return super(Myview,self).dispatch(request,*args,**kwargs)def get(self):return HttpResponse('get')def post(self):return HttpResponse('post')def put(self):return HttpResponse('put')四、简单认证示例场景:用户查看自己购买的订单,需登陆验证以下是demo:models.pyfrom django.db import modelsclass UserInfo(models.Model):user_type_choice = ((1,"普通用户"),(2,"会员"),)user_type = models.IntegerField(choices=user_type_choice)username = models.CharField(max_length=32,unique=True)password = models.CharField(max_length=64)class UserToken(models.Model):user = models.OneToOneField(to=UserInfo)token = models.CharField(max_length=64)认证url(urls.py)from django.conf.urls import urlfrom django.contrib import adminfrom app01 import viewsurlpatterns = [url(r'^api/v1/auth', views.AuthView.as_view()),url(r'^api/v1/order', views.OrderView.as_view()),]views.pyfrom django.shortcuts import HttpResponsefrom django.http import JsonResponsefrom rest_framework.views import APIViewfrom rest_framework.authentication import BaseAuthenticationfrom . import modelsfrom rest_framework import exceptionsimport hashlibimport timeclass Authentication(BaseAuthentication):"""认证类"""def authenticate(self, request):token = request._request.GET.get("token")toke_obj = models.UserToken.objects.filter(token=token).first()if not toke_obj:raise exceptions.AuthenticationFailed("用户认证失败")return (toke_obj.user, toke_obj) # 这里返回值一次给request.user,request.authdef authenticate_header(self, val):passdef md5(user):ctime = str(time.time())m = hashlib.md5(bytes(user,encoding="utf-8"))m.update(bytes(ctime,encoding="utf-8"))return m.hexdigest()class AuthView(APIView):"""登陆认证"""def dispatch(self, request, *args, **kwargs):return super(AuthView, self).dispatch(request, *args, **kwargs)def get(self, request, *args, **kwargs):return HttpResponse('get')def post(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': "登录成功"}try:user = request._request.POST.get("username")pwd = request._request.POST.get("password")obj = models.UserInfo.objects.filter(username=user, password=pwd).first()if not obj:ret['code'] = 1001ret['msg'] = "用户名或密码错误"else:token = md5(user)models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})ret['token'] = tokenexcept Exception as e:ret['code'] = 1002ret['msg'] = "请求异常"return JsonResponse(ret)class OrderView(APIView):'''查看订单'''authentication_classes = [Authentication,] #添加认证def get(self,request,*args,**kwargs):#request.user#request.authret = {'code':1000,'msg':"你的订单已经完成",'data':"买了一个mac"}return JsonResponse(ret,safe=True)用户使用token访问,不带token或token错误会认证错误。http://127.0.0.1:8000/api/v1/order?token=63743076dfaefa632f6acb302cf90400###返回结果{"code": 1000, "msg": "u4f60u7684u8ba2u5355u5df2u7ecfu5b8cu6210", "data": "u4e70u4e86u4e00u4e2amac"}对于以上demo可能会有疑问为什么添加了authentication_classes认证类列表就会使用我们自己定义的认证类,下面从源码角度分析五、认证过程源码
RESTful API设计概要
一、简介1. 什么是RESTREST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。 他在论文中提到:“我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。” 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。 所以我们这里描述的REST也是通过HTTP实现的REST。特点:REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)二、RESTful API 设计1.协议API与用户的通信协议,总是使用HTTPs协议。2.域名https://api.example.com                         尽量将API部署在专用域名(会存在跨域问题)https://example.org/api/                        API很简单3.版本URL,如:https://api.example.com/v1/                     版本号放到url,如k8s的apiserver将版本放到请求头中                                                  跨域时,引发发送多次请求4.路径路径又称"终点"(endpoint),表示API的具体网址。在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。https://api.example.com/v1/zooshttps://api.example.com/v1/animalshttps://api.example.com/v1/employees5.http方法GET(SELECT):从服务器取出资源(一项或多项)、安全且幂等。POST(CREATE):在服务器新建一个资源、不安全且不幂等PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)、不安全但幂等PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。DELETE(DELETE):从服务器删除资源、不安全但幂等HEAD:获取资源的元数据。OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。####使用列子GET /zoos:列出所有动物园POST /zoos:新建一个动物园GET /zoos/ID:获取某个指定动物园的信息PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)DELETE /zoos/ID:删除某个动物园GET /zoos/ID/animals:列出某个指定动物园的所有动物DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物6.过滤,通过在url上传参的形式传递搜索条件https://api.example.com/v1/zoos?limit=10:指定返回记录的数量https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件7.状态码使用200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)204 NO CONTENT - [DELETE]:用户删除数据成功。400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。8.错误处理错误处理,状态码是4xx时,应返回错误信息,error当做key。9.返回结果GET /collection:返回资源对象的列表(数组)GET /collection/resource:返回单个资源对象POST /collection:返回新生成的资源对象PUT /collection/resource:返回完整的资源对象PATCH /collection/resource:返回完整的资源对象DELETE /collection/resource:返回一个空文档10.超链接APIRESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么{"link": {"rel": "collection https://www.example.com/zoos","href": "https://api.example.com/zoos","title": "List of zoos","type": "application/vnd.yourformat+json"}}11.其他(1)API的身份认证应该使用OAuth 2.0框架。(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。 参考:1.https://www.cnblogs.com/wupeiqi/articles/7805382.html    2.https://blog.csdn.net/tjgamejx2/article/details/51011425    3.http://www.ruanyifeng.com/blog/2014/05/restful_api.html 
Django Rest Framework源码剖析(二)-----权限
一、简介在上一篇博客中已经介绍了django rest framework 对于认证的源码流程,以及实现过程,当用户经过认证之后下一步就是涉及到权限的问题。比如订单的业务只能VIP才能查看,所以这时候需要对权限进行控制。下面将介绍DRF的权限控制源码剖析。二、基本使用这里继续使用之前的示例,加入相应的权限,这里先介绍使用示例,然后在分析权限源码1.在django 项目下新建立目录utils,并建立permissions.py,添加权限控制:class MyPremission(object):message = "您不是会员无权访问"def has_permission(self,request,view):if request.user.user_type == 1: ## user_type 为1代表普通用户,则不能查看return Falsereturn True 2.在订单视图中使用class OrderView(APIView):'''查看订单'''from utils.permissions import MyPremissionauthentication_classes = [Authentication,] #添加认证permission_classes = [MyPremission,] #添加权限def get(self,request,*args,**kwargs):#request.user#request.authret = {'code':1000,'msg':"你的订单已经完成",'data':"买了一个mac"}return JsonResponse(ret,safe=True)urls.pyfrom django.conf.urls import urlfrom django.contrib import adminfrom app01 import viewsurlpatterns = [url(r'^api/v1/auth', views.AuthView.as_view()),url(r'^api/v1/order', views.OrderView.as_view()),]models.pyfrom django.db import modelsclass UserInfo(models.Model):user_type_choice = ((1,"普通用户"),(2,"会员"),)user_type = models.IntegerField(choices=user_type_choice)username = models.CharField(max_length=32,unique=True)password = models.CharField(max_length=64)class UserToken(models.Model):user = models.OneToOneField(to=UserInfo)token = models.CharField(max_length=64)3.验证:订单业务同样使用user_type=1的用户进行验证,这里使用工具postman发送请求验证,结果如下:证明我们的权限生效了。三、权限源码剖析1.同样请求到达视图时候,先执行APIView的dispatch方法,以下源码是我们在认证篇已经解读过了:dispatch()def dispatch(self, request, *args, **kwargs):"""`.dispatch()` is pretty much the same as Django's regular dispatch,but with extra hooks for startup, finalize, and exception handling."""self.args = argsself.kwargs = kwargs#对原始request进行加工,丰富了一些功能#Request(# request,# parsers=self.get_parsers(),# authenticators=self.get_authenticators(),# negotiator=self.get_content_negotiator(),# parser_context=parser_context# )#request(原始request,[BasicAuthentications对象,])#获取原生request,request._request#获取认证类的对象,request.authticators#1.封装requestrequest = self.initialize_request(request, *args, **kwargs)self.request = requestself.headers = self.default_response_headers # deprecate?try:#2.认证self.initial(request, *args, **kwargs)# Get the appropriate handler methodif request.method.lower() in self.http_method_names:handler = getattr(self, request.method.lower(),self.http_method_not_allowed)else:handler = self.http_method_not_allowedresponse = handler(request, *args, **kwargs)except Exception as exc:response = self.handle_exception(exc)self.response = self.finalize_response(request, response, *args, **kwargs)return self.response2.执行inital方法,initial方法中执行perform_authentication则开始进行认证def initial(self, request, *args, **kwargs):"""Runs anything that needs to occur prior to calling the method handler."""self.format_kwarg = self.get_format_suffix(**kwargs)# Perform content negotiation and store the accepted info on the requestneg = self.perform_content_negotiation(request)request.accepted_renderer, request.accepted_media_type = neg# Determine the API version, if versioning is in use.version, scheme = self.determine_version(request, *args, **kwargs)request.version, request.versioning_scheme = version, scheme# Ensure that the incoming request is permitted#4.实现认证self.perform_authentication(request)#5.权限判断self.check_permissions(request)self.check_throttles(request)3.当执行完perform_authentication方法认证通过时候,这时候就进入了本篇文章主题--权限(check_permissions方法),下面是check_permissions方法源码:def check_permissions(self, request):"""Check if the request should be permitted.Raises an appropriate exception if the request is not permitted."""for permission in self.get_permissions(): #循环对象get_permissions方法的结果,如果自己没有,则去父类寻找,if not permission.has_permission(request, self): #判断每个对象中的has_permission方法返回值(其实就是权限判断),这就是为什么我们需要对权限类定义has_permission方法self.permission_denied(request, message=getattr(permission, 'message', None) #返回无权限信息,也就是我们定义的message共有属性)4.从上源码中我们可以看出,perform_authentication方法中循环get_permissions结果,并逐一判断权限,所以需要分析get_permissions方法返回结果,以下是get_permissions方法源码:def get_permissions(self):"""Instantiates and returns the list of permissions that this view requires."""return [permission() for permission in self.permission_classes] #与权限一样采用列表生成式获取每个认证类对象5.get_permissions方法中寻找权限类是通过self.permission_class字段寻找,和认证类一样默认该字段在全局也有配置,如果我们视图类中已经定义,则使用我们自己定义的类。class APIView(View):# The following policies may be set at either globally, or per-view.renderer_classes = api_settings.DEFAULT_RENDERER_CLASSESparser_classes = api_settings.DEFAULT_PARSER_CLASSESauthentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSESthrottle_classes = api_settings.DEFAULT_THROTTLE_CLASSESpermission_classes = api_settings.DEFAULT_PERMISSION_CLASSES #权限控制content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASSmetadata_class = api_settings.DEFAULT_METADATA_CLASSversioning_class = api_settings.DEFAULT_VERSIONING_CLASS6.承接check_permissions方法,当认证类中的has_permission()方法返回fal
Django Rest Framework源码剖析(三)-----频率控制
一、简介承接上篇文章Django Rest Framework源码剖析(二)-----权限,当服务的接口被频繁调用,导致资源紧张怎么办呢?当然或许有很多解决办法,比如:负载均衡、提高服务器配置、通过代理限制访问频率等,但是django rest framework自身就提供了访问频率的控制,可以从代码本身做控制。二、频率控制内部原理概述django rest framework 中频率控制基本原理基于访问次数和时间,通过计算实现,当然我们也可以自己定义频率控制方法。基本原理如下:启用频率,DRF内部会有一个字典记录来访者的IP,以及访问时间最近几(通过配置)次的访问时间,这样确保每次列表中最后一个元素都是该用户请求的最早时间,形式如下:{IP1:[第三次请求时间,第二次请求时间,第一次请求时间,],IP2:[第二次请求时间,第一次请求时间,],.....}举例说明,比如我现在配置了5秒内只能访问2次,每次请求到达频率控制时候先判断请求者IP是否已经在这个请求字典中,若存在,在判断用户请求5秒内的请求次数,若次数小于等于2,则允许请求,若大于2,则超过频率,不允许请求。关于请求频率的的算法(以5秒内最多访问两次为例):1.首先删除掉列表里5秒之前的请求,循环判断当前请求时间和最早请求时间之差记作t1,若t1大于5则代表列表中最早的请求已经在5秒外了,删除掉,继续判断倒数第二个请求,直到t1小于5.2.当确保请求列表中只有5秒内请求时候,接着判断其请求次数(列表长度),若长度大于2,则证明超过5秒内访问超过2次了,则不允许,否则,通过并将此次访问时间插入到列表最前面,作为最新访问时间。三、基本使用同样,先来了解下频率控制的使用方法,后面在分析源码1.在utils目录下新建立文件,throttle.py,添加频率控制为每分钟只能访问5次#!/usr/bin/env python3#_*_ coding:utf-8 _*_#Author:wdfrom rest_framework.throttling import SimpleRateThrottleclass VisitThrottle(SimpleRateThrottle):"""5秒内最多访问三次"""scope = "WD" #settings配置文件中的key,用于获取配置的频率def get_cache_key(self, request, view):return self.get_ident(request)2.settings.py中配置全局频率控制REST_FRAMEWORK = {#频率控制配置"DEFAULT_THROTTLE_CLASSES":['utils.throttle.VisitThrottle'], #全局配置,"DEFAULT_THROTTLE_RATES":{'WD':'5/m', #速率配置每分钟不能超过5次访问,WD是scope定义的值,}}urls.pyfrom django.conf.urls import urlfrom django.contrib import adminfrom app01 import viewsurlpatterns = [url(r'^api/v1/auth', views.AuthView.as_view()),url(r'^api/v1/order', views.OrderView.as_view()),]models.pyfrom django.db import modelsclass UserInfo(models.Model):user_type_choice = ((1,"普通用户"),(2,"会员"),)user_type = models.IntegerField(choices=user_type_choice)username = models.CharField(max_length=32,unique=True)password = models.CharField(max_length=64)class UserToken(models.Model):user = models.OneToOneField(to=UserInfo)token = models.CharField(max_length=64)订单视图class OrderView(APIView):'''查看订单'''from utils.permissions import MyPremissionauthentication_classes = [Authentication,] #添加认证permission_classes = [MyPremission,] #添加权限控制def get(self,request,*args,**kwargs):#request.user#request.authret = {'code':1000,'msg':"你的订单已经完成",'data':"买了一个mac"}return JsonResponse(ret,safe=True)使用postman验证如下图,可以看到频率限制已经起作用了。四、频率控制源码剖析在前面几篇文章中已经分析了DRF的认证、权限源码,频率控制也一样也从APIView的dispatch方法说起,参考注解:dispatch()def dispatch(self, request, *args, **kwargs):"""`.dispatch()` is pretty much the same as Django's regular dispatch,but with extra hooks for startup, finalize, and exception handling."""self.args = argsself.kwargs = kwargs#对原始request进行加工,丰富了一些功能#Request(# request,# parsers=self.get_parsers(),# authenticators=self.get_authenticators(),# negotiator=self.get_content_negotiator(),# parser_context=parser_context# )#request(原始request,[BasicAuthentications对象,])#获取原生request,request._request#获取认证类的对象,request.authticators#1.封装requestrequest = self.initialize_request(request, *args, **kwargs)self.request = requestself.headers = self.default_response_headers # deprecate?try:self.initial(request, *args, **kwargs)# Get the appropriate handler methodif request.method.lower() in self.http_method_names:handler = getattr(self, request.method.lower(),self.http_method_not_allowed)else:handler = self.http_method_not_allowedresponse = handler(request, *args, **kwargs)except Exception as exc:response = self.handle_exception(exc)self.response = self.finalize_response(request, response, *args, **kwargs)return self.response2.执行inital方法,initial方法中执行check_throttles则开始频率控制def initial(self, request, *args, **kwargs):"""Runs anything that needs to occur prior to calling the method handler."""self.format_kwarg = self.get_format_suffix(**kwargs)# Perform content negotiation and store the accepted info on the requestneg = self.perform_content_negotiation(request)request.accepted_renderer, request.accepted_media_type = neg# Determine the API version, if versioning is in use.version, scheme = self.determine_version(request, *args, **kwargs)request.version, request.versioning_scheme = version, scheme# Ensure that the incoming request is permitted#2.实现认证self.perform_authentication(request)#3.权限判断self.check_permissions(request)#4.频率限制self.check_throttles(request)3.下面是check_throttles源码,与认证、权限一样采用列表对象方式,通过判断allow_request方法返回值判断频率是否通过def check_throttles(self, request):"""Check if request should be throttled.Raises an appropriate exception if the request is throttled."""for throttle in self.get_throttles(): #循环频率控制类结果if not throttle.allow_request(request, self): #判断其中的allow_requestf返回结果,true则频率通过,否则返回等待多少秒可以访问self.throttled(request, throttle.wait())4.get_throttles方法,采用列表生成式生成频率控制对象,与认证、权限一直def get_throttles(self):"""Instantiates and returns the list of throttles that this view uses."""return [throttle() for throttle in self.throttle_classes] #列表生成式生成控制频率对象列表5.self.throttle_classes属性获取class APIView(View):# The following policies may be set at either globally, or
Django Rest Framework源码剖析(四)-----API版本
一、简介在我们给外部提供的API中,可会存在多个版本,不同的版本可能对应的功能不同,所以这时候版本使用就显得尤为重要,django rest framework也为我们提供了多种版本使用方法。二、基本使用版本使用方式:1.在url中传递版本:如http://www.example.com/api?version=v1和其他组建一样,我们在utils里面建立version.py,添加版本类#!/usr/bin/env python3#_*_ coding:utf-8 _*_#Author:wdfrom rest_framework.versioning import BaseVersioningclass Myversion(BaseVersioning):def determine_version(self, request, *args, **kwargs):myversion=request.query_params.get('version')return myversion在订单视图中应用版本,(当然直接可以使用request.get获取)class OrderView(APIView):'''查看订单'''from utils.permissions import MyPremissionfrom utils.version import Myversionauthentication_classes = [Authentication,] #添加认证permission_classes = [MyPremission,] #添加权限控制versioning_class = Myversion #添加版本def get(self,request,*args,**kwargs):print(request.version)#获取版本#当然使用request._request.get('version')也可以ret = {'code':1000,'msg':"你的订单已经完成",'data':"买了一个mac"}return JsonResponse(ret,safe=True)models.pyfrom django.db import modelsclass UserInfo(models.Model):user_type_choice = ((1,"普通用户"),(2,"会员"),)user_type = models.IntegerField(choices=user_type_choice)username = models.CharField(max_length=32,unique=True)password = models.CharField(max_length=64)class UserToken(models.Model):user = models.OneToOneField(to=UserInfo)token = models.CharField(max_length=64)urls.pyfrom django.conf.urls import urlfrom django.contrib import adminfrom app01 import viewsurlpatterns = [url(r'^api/v1/auth', views.AuthView.as_view()),url(r'^api/v1/order', views.OrderView.as_view()),]views.pyfrom django.shortcuts import HttpResponsefrom django.http import JsonResponsefrom rest_framework.views import APIViewfrom rest_framework.authentication import BaseAuthenticationfrom . import modelsfrom rest_framework import exceptionsimport hashlibimport timeclass Authentication(BaseAuthentication):"""认证类"""def authenticate(self, request):token = request._request.GET.get("token")toke_obj = models.UserToken.objects.filter(token=token).first()if not toke_obj:raise exceptions.AuthenticationFailed("用户认证失败")return (toke_obj.user, toke_obj) # 这里返回值一次给request.user,request.authdef authenticate_header(self, val):passdef md5(user):ctime = str(time.time())m = hashlib.md5(bytes(user,encoding="utf-8"))m.update(bytes(ctime,encoding="utf-8"))return m.hexdigest()class AuthView(APIView):"""登陆认证"""def dispatch(self, request, *args, **kwargs):return super(AuthView, self).dispatch(request, *args, **kwargs)def get(self, request, *args, **kwargs):return HttpResponse('get')def post(self, request, *args, **kwargs):ret = {'code': 1000, 'msg': "登录成功"}try:user = request._request.POST.get("username")pwd = request._request.POST.get("password")obj = models.UserInfo.objects.filter(username=user, password=pwd).first()if not obj:ret['code'] = 1001ret['msg'] = "用户名或密码错误"else:token = md5(user)models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})ret['token'] = tokenexcept Exception as e:ret['code'] = 1002ret['msg'] = "请求异常"return JsonResponse(ret)class OrderView(APIView):'''查看订单'''from utils.permissions import MyPremissionfrom utils.version import Myversionauthentication_classes = [Authentication,] #添加认证permission_classes = [MyPremission,] #添加权限控制versioning_class = Myversiondef get(self,request,*args,**kwargs):print(request.version)ret = {'code':1000,'msg':"你的订单已经完成",'data':"买了一个mac"}return JsonResponse(ret,safe=True)使用postman发送请求:http://127.0.0.1:8000/api/v1/order?token=7c191332ba452abefe516ff95ea9994a&version=v1,后台可获取版本。当然上面获取版本方式还有更为简单的获取版本方法,使用QueryParameterVersioning,其就是封装的以上过程。class OrderView(APIView):'''查看订单'''from utils.permissions import MyPremissionfrom utils.version import Myversionfrom rest_framework.versioning import QueryParameterVersioningauthentication_classes = [Authentication,] #添加认证permission_classes = [MyPremission,] #添加权限控制versioning_class = QueryParameterVersioning #该方法获取参数的key为versiondef get(self,request,*args,**kwargs):print(request.version)ret = {'code':1000,'msg':"你的订单已经完成",'data':"买了一个mac"}return JsonResponse(ret,safe=True)当然,DRF也提供了可配置的版本,并且还能控制版本使用settings.pyREST_FRAMEWORK = {#版本配置"DEFAULT_VERSION":'v1', #默认的版本"ALLOWED_VERSIONS":['v1','v2'], #允许的版本,这里只允许V1和v2"VERSION_PARAM":'version' , #get方式url中参数的名字 如?version=v1}使用postman验证,发送带token和版本http://127.0.0.1:8000/api/v1/order?token=7c191332ba452abefe516ff95ea9994a&version=v3结果:可见版本配置生效。2.使用url路径传递版本,如http://www.example.com/api/v1,django rest framework 当然也为我们提供了类:URLPathVersioning为了区分,这里新建url和view,如下:urls.pyfrom django.conf.urls import urlfrom django.contrib import adminfrom app01 import viewsurlpatterns = [url(r'^api/v1/auth', view
Django Rest Framework源码剖析(五)-----解析器
一、简介解析器顾名思义就是对请求体进行解析。为什么要有解析器?原因很简单,当后台和前端进行交互的时候数据类型不一定都是表单数据或者json,当然也有其他类型的数据格式,比如xml,所以需要解析这类数据格式就需要用到解析器(也可以将请求体拿到,然后利用其他模块进行解析)。二、基本使用1.json解析器同样以订单视图为例,添加json解析器,如下:from rest_framework.versioning import URLPathVersioningfrom rest_framework.parsers import JSONParserclass UserView(APIView):'''查看用户信息'''parser_classes = [JSONParser,]versioning_class =URLPathVersioningdef get(self,request,*args,**kwargs):res={"name":"wd","age":22}return JsonResponse(res,safe=True)def post(self,request,*args,**kwargs):print(request.data) #获取解析后的请求结果return JsonResponse({"success":"ok"}, safe=True) 使用postman向http://127.0.0.1:8000/api/v1/user视图发送json数据,注意请求头必须是application/json,如下图:查看post结果(结果直接是json格式): 2.form表单解析器视图from rest_framework.versioning import URLPathVersioningfrom rest_framework.parsers import JSONParser,FormParserclass UserView(APIView):'''查看用户信息'''parser_classes = [JSONParser,FormParser]##JSONParser,解析头信息Content-Type:application/json,的json数据##FormParser,解析头信息Content-Type:x-www-form-urlencoded数据versioning_class =URLPathVersioningdef get(self,request,*args,**kwargs):res={"name":"wd","age":22}return JsonResponse(res,safe=True)def post(self,request,*args,**kwargs):print(request.data) #获取解析后的请求结果return JsonResponse({"success":"ok"}, safe=True)使用postman发送form表单数据后台接受,并且结果已经转化为QueryDict类型了 三、源码剖析1.根据以上示例,梳理解析器解析数据流程获取用户请求获取用户请求体根据用户请求头信息和parase_classes=[...],中的请求头进行比较,匹配上请求头就使用该解析器处理解析器从请求体中拿数据进行处理,处理完成之后将结果返回给request.data2.源码剖析:同样和权限源码流程一样,请求进来,先执行APIView的dispatch方法,以下是源码,分析请看注解dispatch方法:def dispatch(self, request, *args, **kwargs):"""`.dispatch()` is pretty much the same as Django's regular dispatch,but with extra hooks for startup, finalize, and exception handling."""self.args = argsself.kwargs = kwargs#对原始request进行加工,丰富了一些功能#Request(# request,# parsers=self.get_parsers(),# authenticators=self.get_authenticators(),# negotiator=self.get_content_negotiator(),# parser_context=parser_context# )#request(原始request,[BasicAuthentications对象,])#获取原生request,request._request#获取认证类的对象,request.authticators#1.封装requestrequest = self.initialize_request(request, *args, **kwargs)self.request = requestself.headers = self.default_response_headers # deprecate?try:self.initial(request, *args, **kwargs)# Get the appropriate handler methodif request.method.lower() in self.http_method_names:handler = getattr(self, request.method.lower(),self.http_method_not_allowed)else:handler = self.http_method_not_allowedresponse = handler(request, *args, **kwargs)except Exception as exc:response = self.handle_exception(exc)self.response = self.finalize_response(request, response, *args, **kwargs)return self.response执行initialize_request()方法,在该方法中,get_parsers用于获取解析器,并被封装到request.parsers中。def initialize_request(self, request, *args, **kwargs):"""Returns the initial request object."""parser_context = self.get_parser_context(request)#return Request(request,parsers=self.get_parsers(), #获取所有的解析器,封装到request.parsers中authenticators=self.get_authenticators(),negotiator=self.get_content_negotiator(),parser_context=parser_context)get_parsers()源码,和认证、权限一样,解析器采用列表生成式返回解析器对象的列表,所以示例中定义解析器的变量是parser_classes:def get_parsers(self):"""Instantiates and returns the list of parsers that this view can use."""return [parser() for parser in self.parser_classes] #列表生成式,返回解析器对象self.praser_classes,默认(全局)配置class APIView(View):# The following policies may be set at either globally, or per-view.renderer_classes = api_settings.DEFAULT_RENDERER_CLASSESparser_classes = api_settings.DEFAULT_PARSER_CLASSES #解析器全局配置authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSESthrottle_classes = api_settings.DEFAULT_THROTTLE_CLASSESpermission_classes = api_settings.DEFAULT_PERMISSION_CLASSEScontent_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASSmetadata_class = api_settings.DEFAULT_METADATA_CLASSversioning_class = api_settings.DEFAULT_VERSIONING_CLASS当调用request.data获取请求数据时候将使用解析器,下面是request.data源码:@propertydef data(self):if not _hasattr(self, '_full_data'):self._load_data_and_files() #执行_load_data_and_files(),获取请求体数据获取文件数据return self._full_data执行self._load_data_and_files(),获取请求数据或者文件数据,self._load_data_and_files()源码:def _load_data_and_files(self):"""Parses the request content into `self.data`."""if not _hasattr(self, '_data'):self._data, self._files = self._parse() #执行self_parse(),获取解析器,并对content_type进行解析,选择解析器,返回数据if self._files:#判断文件流数据,存在则加入到self._full_data(也就是我们的request.data)中self._full_data = self._data.copy() ,self._full_data.update(self._files)else:self._full_data = self._data #不存在将无文件流的解析完成的数据赋值到self._full_data(request.data)# if a form media type, copy data & files refs to the underlying# http request so that closable objects are handled appropriately.if is_form_media_type(self.content_type):self._request._post = self.POSTself._request._files = self.FILES执行self._prase()方法,获取解析器,并对请求的Content-Type进
Django Rest Framework源码剖析(六)-----序列化(serializers)
一、简介django rest framework 中的序列化组件,可以说是其核心组件,也是我们平时使用最多的组件,它不仅仅有序列化功能,更提供了数据验证的功能(与django中的form类似)。便于展现的序列化操作,我们需要在model添加外键、多对多情况。以下是新的models(请删除原有的数据库,重新migrate):models.pyfrom django.db import modelsclass UserInfo(models.Model):user_type_choice = ((1,"普通用户"),(2,"会员"),)user_type = models.IntegerField(choices=user_type_choice)username = models.CharField(max_length=32,unique=True)password = models.CharField(max_length=64)group = models.ForeignKey(to='UserGroup',null=True,blank=True)roles = models.ManyToManyField(to='Role')class UserToken(models.Model):user = models.OneToOneField(to=UserInfo)token = models.CharField(max_length=64)class UserGroup(models.Model):"""用户组"""name = models.CharField(max_length=32,unique=True)class Role(models.Model):"""角色"""name = models.CharField(max_length=32,unique=True)二、使用1.基本使用在urls.py中添加新的角色url,以前的url为了减少干扰,在这里进行注释:from django.conf.urls import urlfrom app01 import viewsurlpatterns = [# url(r'^api/v1/auth', views.AuthView.as_view()),# url(r'^api/v1/order', views.OrderView.as_view()),url(r'^api/v1/roles', views.RoleView.as_view()), # 角色视图# url(r'^api/(?P<version>[v1|v2]+)/user', views.UserView.as_view(),name="user_view"),]views.pyfrom rest_framework import serializersfrom rest_framework.views import APIViewfrom django.shortcuts import HttpResponsefrom app01 import modelsimport jsonclass RolesSerializer(serializers.Serializer): #定义序列化类id=serializers.IntegerField() #定义需要提取的序列化字段,名称和model中定义的字段相同name=serializers.CharField()class RoleView(APIView):"""角色"""def get(self,request,*args,**kwargs):roles=models.Role.objects.all()res=RolesSerializer(instance=roles,many=True) #instance接受queryset对象或者单个model对象,当有多条数据时候,使用many=True,单个对象many=Falsereturn HttpResponse(json.dumps(res.data,ensure_ascii=False))使用浏览器访问http://127.0.0.1:8000/api/v1/roles,结果如下:2.自定义序列化字段当数据模型中有外键或者多对多时候,这时候就需要自定义序列化了新增用户信息urlfrom django.conf.urls import urlfrom app01 import viewsurlpatterns = [# url(r'^api/v1/auth', views.AuthView.as_view()),# url(r'^api/v1/order', views.OrderView.as_view()),url(r'^api/v1/roles', views.RoleView.as_view()),url(r'^api/v1/userinfo', views.UserinfoView.as_view()), #用户信息# url(r'^api/(?P<version>[v1|v2]+)/user', views.UserView.as_view(),name="user_view"),]UserinfoView和序列化类from rest_framework import serializersfrom rest_framework.views import APIViewfrom django.shortcuts import HttpResponsefrom app01 import modelsimport jsonclass UserinfoSerializer(serializers.Serializer): #定义序列化类id=serializers.IntegerField() #定义需要提取的序列化字段,名称和model中定义的字段相同username=serializers.CharField()password=serializers.CharField()#sss=serializers.CharField(source='user_type') #该方法只能拿到user_type的IDsss=serializers.CharField(source='get_user_type_display') #自定义字段名称,和数据模型不一致,需要指定source本质调用get_user_type_display()方法获取数据gp=serializers.CharField(source='group.name') #本质拿到group对象,取对象的name,#rl=serializers.CharField(source='roles.all.first.name')rl=serializers.SerializerMethodField() #多对多序列化方法一def get_rl(self,obj): #名称固定:get_定义的字段名称"""自定义序列化:param obj:传递的model对象,这里已经封装好的:return:"""roles=obj.roles.all().values() #获取所有的角色return list(roles) #返回的结果一定有道是json可序列化的对象class UserinfoView(APIView):"""用户信息"""def get(self,request,*args,**kwargs):users=models.UserInfo.objects.all()res=UserinfoSerializer(instance=users,many=True) #instance接受queryset对象或者单个model对象,当有多条数据时候,使用many=True,单个对象many=Falsereturn HttpResponse(json.dumps(res.data,ensure_ascii=False))访问http://127.0.0.1:8000/api/v1/userinfo,查看结果:除了以上的Serializer,还可以使用ModelSerializer,ModelSerializer继承了serializer,其结果和上面示例一样:class UserinfoSerializer(serializers.ModelSerializer):id = serializers.IntegerField() # 定义需要提取的序列化字段,名称和model中定义的字段相同username=serializers.CharField()password=serializers.CharField()#sss=serializers.CharField(source='user_type') #该方法只能拿到user_type的IDsss=serializers.CharField(source='get_user_type_display') #自定义字段名称,和数据模型不一致,需要指定source本质调用get_user_type_display()方法获取数据#rl=serializers.CharField(source='roles.all.first.name')gp=serializers.CharField(source='group.name')rl=serializers.SerializerMethodField() #多对多序列化方法一def get_rl(self,obj): #名称固定:get_定义的字段名称"""自定义序列化:param obj:传递的model对象,这里已经封装好的:return:"""roles=obj.roles.all().values() #获取所有的角色return list(roles) #返回的结果一定有道是json可序列化的对象class Meta:model = models.UserInfofields = ['id', 'username', 'password', 'sss','rl','gp'] #配置要序列化的字段# fields = "__all__" 使用model中所有的字段class UserinfoView(APIView):"""用户信息"""def get(self,request,*args,**kwargs):users=models.UserInfo.objects.all()res=UserinfoSerializer(instance=users,many=True) #instance接受queryset对象或者单个model对象,当有多条数据时候,使用many=True,单个对象many=Falsereturn HttpResponse(json.dumps(res.data,ensure_ascii=False))3.连表序列化以及深度控制使用depth进行深度控制,越深其序列化的细读越高class UserinfoSerializer(serializers.Model
Django Rest Framework源码剖析(七)-----分页
一、简介分页对于大多数网站来说是必不可少的,那你使用restful架构时候,你可以从后台获取数据,在前端利用利用框架或自定义分页,这是一种解决方案。当然django rest framework提供了分页组件,让我们可以更灵活的进行分页。django rest framework提供了三种分页组件:PageNumberPagination:普通分页,查看第n页,每个页面显示n条数据LimitOffsetPagination: 基于位置的分页,在第n个位置,向后查看n条数据,和数据库的sql语句中的limit offset类似,参数offet代表位置,limit代表取多少条数据。CursorPagination:游标分页,意思就是每次返回当前页、上一页、下一页,并且每次的上一页和下一页的url是不规则的 二、每个分页组件使用这里我们使用之前的模型,如果没有在setting中注册django rest framework 请注册它,为了方便我们查看分页,配置项在INSTALLED_APPS:1.PageNumberPagination类分页settings.pyINSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','app01.apps.App01Config','rest_framework', #注册DRF]models.pyfrom django.db import modelsclass UserInfo(models.Model):user_type_choice = ((1,"普通用户"),(2,"会员"),)user_type = models.IntegerField(choices=user_type_choice)username = models.CharField(max_length=32,unique=True)password = models.CharField(max_length=64)group = models.ForeignKey(to='UserGroup',null=True,blank=True)roles = models.ManyToManyField(to='Role')class UserToken(models.Model):user = models.OneToOneField(to=UserInfo)token = models.CharField(max_length=64)class UserGroup(models.Model):"""用户组"""name = models.CharField(max_length=32,unique=True)class Role(models.Model):"""角色"""name = models.CharField(max_length=32,unique=True)urls.pyurlpatterns = [# url(r'^api/v1/auth', views.AuthView.as_view()),# url(r'^api/v1/order', views.OrderView.as_view()),url(r'^api/v1/roles', views.RoleView.as_view()), #分页示例1url(r'^api/v1/userinfo', views.UserinfoView.as_view()),url(r'^api/v1/group/(?P<xxx>d+)', views.GroupView.as_view(),name='gp'),# url(r'^api/(?P<version>[v1|v2]+)/user', views.UserView.as_view(),name="user_view"),]本次我们使用roles来作为示例,并且为了更好的显示,此次会用到django rest framework 的响应(Response),后续会介绍,下面是对角色视图的序列化,这个已经在前面的序列化篇章中说明如下:from rest_framework import serializersfrom rest_framework.response import Response #使用DRF自带的响应页面更美观class RolesSerializer(serializers.Serializer): #定义序列化类id=serializers.IntegerField() #定义需要提取的序列化字段,名称和model中定义的字段相同name=serializers.CharField()class RoleView(APIView):"""角色"""def get(self,request,*args,**kwargs):roles=models.Role.objects.all()res=RolesSerializer(instance=roles,many=True) #instance接受queryset对象或者单个model对象,当有多条数据时候,使用many=True,单个对象many=Falsereturn Response(res.data)访问:http://127.0.0.1:8000/api/v1/roles,显示出所有的角色,如下:加入分页后的角色视图:from rest_framework import serializersfrom rest_framework.response import Response #使用DRF自带的响应页面更美观from rest_framework.pagination import PageNumberPaginationclass RolesSerializer(serializers.Serializer): #定义序列化类id=serializers.IntegerField() #定义需要提取的序列化字段,名称和model中定义的字段相同name=serializers.CharField()class RoleView(APIView):"""角色"""def get(self,request,*args,**kwargs):roles=models.Role.objects.all() # 获取所有数据pg_obj=PageNumberPagination() # 实例化分页类pg_res=pg_obj.paginate_queryset(queryset=roles,request=request,view=self)# 获取分页数据,参数一 分页的数据,QuerySet类型,请求request,分页的视图,self代表自己res=RolesSerializer(instance=pg_res,many=True) # 对分完页码的数据进行序列化return Response(res.data)同时,我们还需要配置每页显示的数据条数,在settings.py中:REST_FRAMEWORK = {"DEFAULT_PARSER_CLASSES": ["rest_framework.parsers.JSONParser", "rest_framework.parsers.JSONParser"],# 全局解析器配置"PAGE_SIZE":2,#配置每页显示多少条数据}此时我们访问:http://127.0.0.1:8000/api/v1/roles?page=1,则显示第一页,访问http://127.0.0.1:8000/api/v1/roles?page=2则显示第二页,如下图:但是一般情况我们需要自己定义分页类,来定制更多的功能,示例:自定义分页,更多的定制功能:from rest_framework import serializersfrom rest_framework.response import Response #使用DRF自带的响应页面更美观from rest_framework.pagination import PageNumberPaginationclass Mypagination(PageNumberPagination):"""自定义分页"""page_size=2 #默认每页显示个数配置page_query_param = 'p' # 页面传参的key,默认是pagepage_size_query_param='size' # 指定每页显示个数参数max_page_size=4 # 每页最多显示个数配置,使用以上配置,可以支持每页可显示2~4条数据class RolesSerializer(serializers.Serializer): #定义序列化类id=serializers.IntegerField() #定义需要提取的序列化字段,名称和model中定义的字段相同name=serializers.CharField()class RoleView(APIView):"""角色"""def get(self,request,*args,**kwargs):roles=models.Role.objects.all() # 获取所有数据pg_obj=Mypagination() # 实例化分页类pg_res=pg_obj.paginate_queryset(queryset=roles,request=request,view=self)# 获取分页数据,参数一 分页的数据,QuerySet类型,请求request,分页的视图,self代表自己res=RolesSerializer(instance=pg_res,many=True) # 对分完页码的数据进行序列化return Response(res.data)访问:http://127.0.0.1:8000/api/v1/roles?p=1&size=3,需要注意的是此时的分页参数已经重写,查看结果:自带返回上一页下一页功能:from rest_framework import serializersfrom rest_framework.response import Response #使用DRF自带的响应页面更美观from rest_framework.pagination import PageNumberPaginationclass Mypagination(PageNumberPagination):"""自定义分页"""page_size=2 #默认每页显示个数配置page_query_param = 'p' # 页面传参的key,默认是pagepage_size_query_param='size' # 指定每页显示个数参数max_page_size=4 # 每页最多显示个数配置,使用以上配置,可以
Django Rest Framework源码剖析(八)-----视图与路由
一、简介django rest framework 给我们带来了很多组件,除了认证、权限、序列化...其中一个重要组件就是视图,一般视图是和路由配合使用,这种方式给我们提供了更灵活的使用方法,对于使用者而言不同的视图具有不同的功能,这样我们可以根据需求定制自己视图。以下是官网传送门:http://www.django-rest-framework.org/api-guide/views/在之前的文章中,由于参杂了权限、认证等(如果不了解请看博客的以前的文章),但在本章中基本可以不使用,所进使用以下简单模型进行说明:settings中注册django rest frameworkINSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','app01.apps.App01Config','rest_framework',]models.pyfrom django.db import models# Create your models here.class UserInfo(models.Model):username = models.CharField(max_length=32,unique=True)password = models.CharField(max_length=64)urls.pyfrom django.conf.urls import urlfrom django.contrib import adminfrom app01 import viewsurlpatterns = [url(r'^admin/', admin.site.urls),url(r'^api/v1/users', views.UerView.as_view()),]二、各类视图使用1.APIView该视图是最基本的视图,之前的章节中已经介绍如何使用,参考:Django Rest Framework源码剖析(一)-----认证2.GenericAPIView该视图为我们封装一些静态字段,用于调用其他组件,示例(解释请看注解):from django.shortcuts import render# Create your views here.from rest_framework.response import Responsefrom rest_framework.generics import GenericAPIViewfrom rest_framework import serializersfrom rest_framework import paginationfrom app01 import modelsclass Userserializer(serializers.ModelSerializer): #序列化类定义,前面章节已经介绍class Meta:model = models.UserInfofields = '__all__' #序列化字段class Mypagination(pagination.PageNumberPagination): # 分页类定义,前面章节也已经介绍"""自定义分页"""page_size=2 #默认每页显示个数配置page_query_param = 'p' # 页面传参的key,默认是pagepage_size_query_param='size' # 指定每页显示个数参数max_page_size=4 # 每页最多显示个数配置,使用以上配置,可以支持每页可显示2~4条数据class UerView(GenericAPIView): #视图queryset = models.UserInfo.objects.all() #数据的querysetserializer_class = Userserializer # 序列化类使用permission_classes = [] # 权限认证,这里不做认证,前面章节也有介绍权限pagination_class = Mypagination #分页类,前面章节也已经介绍def get(self,*args,**kwargs):roles=self.get_queryset() # 获取queryset,实际取queryset,也就是models.UserInfo.objects.all()page_res=self.paginate_queryset(queryset=roles) #分页结果res=self.get_serializer(instance=page_res,many=True) #序列化return Response(res.data) #返回结果访问http://127.0.0.1:8000/api/v1/users,查看结果如下:3.GenericViewSet该视图类需要和路由配合使用,修改后的urls.pyfrom django.conf.urls import urlfrom django.contrib import adminfrom app01 import viewsurlpatterns = [url(r'^admin/', admin.site.urls),# url(r'^api/v1/users', views.UserView.as_view()),url(r'^api/v2/users', views.UserView.as_view({'get':'show','post':'create'})), #重写as_view方法,并对请求方法进行映射]views.pyfrom django.shortcuts import render# Create your views here.from rest_framework.response import Responsefrom rest_framework.generics import GenericAPIViewfrom rest_framework import serializersfrom rest_framework import paginationfrom rest_framework.viewsets import GenericViewSetfrom app01 import modelsclass Userserializer(serializers.ModelSerializer): #序列化类定义,前面章节已经介绍class Meta:model = models.UserInfofields = '__all__' #序列化字段class Mypagination(pagination.PageNumberPagination): # 分页类定义,前面章节也已经介绍"""自定义分页"""page_size=2 #默认每页显示个数配置page_query_param = 'p' # 页面传参的key,默认是pagepage_size_query_param='size' # 指定每页显示个数参数max_page_size=4 # 每页最多显示个数配置,使用以上配置,可以支持每页可显示2~4条数据class UserView(GenericViewSet): #视图queryset = models.UserInfo.objects.all() #数据的querysetserializer_class = Userserializer # 序列化类使用permission_classes = [] # 权限认证,这里不做认证,前面章节也有介绍权限pagination_class = Mypagination #分页类,前面章节也已经介绍def show(self,*args,**kwargs): #与url中映射的方法名称相同roles=self.get_queryset() # 获取queryset,实际取queryset,也就是models.UserInfo.objects.all()page_res=self.paginate_queryset(queryset=roles) #分页结果res=self.get_serializer(instance=page_res,many=True) #序列化return Response(res.data) #返回结果def create(self,*args,**kwargs):pass访问http://127.0.0.1:8000/api/v2/users,结果和上面结果一样如图:4.ModelViewSetmodelViewSet继承了mixins.CreateModelMixin、mixins.RetrieveModelMixin、mixins.UpdateModelMixin、mixins.DestroyModelMixin、mixins.ListModelMixin、GenericViewSet所以它有很父类的所有功能,而这些父类分别提供创建、获取、更新、删除等方法,所以我们不需要写增删该查,视图已经帮我实现了,示例:此时路由urls.py"""resetful URL ConfigurationThe `urlpatterns` list routes URLs to views. For more information please see:https://docs.djangoproject.com/en/1.11/topics/http/urls/Examples:Function views1. Add an import: from my_app import views2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')Class-based views1. Add an import: from other_app.views import Home2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')Including another URLconf1. Import the include() function: from django.conf.urls import url, include2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))"""from django.conf.urls import urlfrom django.contrib import adminfrom app01 import viewsurlpatterns = [url(r'^admin/', admin.site.urls),ur
Django基础
一、简介Python的WEB框架有Django、Tornado、Flask 等多种,Django相较与其他WEB框架其优势为:大而全,框架本身集成了ORM、模型绑定、模板引擎、缓存、Session等诸多功能。Django是一个开放源代码的Web应用框架,由Python写成。采用了MVC的软件设计模式,即模型M,视图V和控制器C。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的,即是CMS(内容管理系统)软件。并于2005年7月在BSD许可证下发布。这套框架是以比利时的吉普赛爵士吉他手Django Reinhardt来命名的。Django是一个基于MVC构造的框架。但是在Django中,控制器接受用户输入的部分由框架自行处理,所以 Django 里更关注的是模型(Model)、模板(Template)和视图(Views),称为 MTV模式。它们各自的职责如下:模型(Model),即数据存取层处理与数据相关的所有事务: 如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等。模板(Template),即表现层处理与表现相关的决定: 如何在页面或其他类型文档中进行显示。视图(View),即业务逻辑层存取模型及调取恰当模板的相关逻辑。模型与模板之间的桥梁。基本目录结构:mysite #django工程- mysite # 整个站点配置目录- __init__.py- settings.py # 配置文件- urls.py # 路由关系- wsgi.py # 遵循WSIG规范,生产一般采用uwsgi + nginx- manage.py # Django管理程序二、安装配置django以及基本命令使用1、安装django#windowspip install djangopython -m pip install django#linux或者macpip3 install djangopip install django=1.9.5#检查django是否安装成功运行python终端>>> import django>>> django.VERSION2、创建django工程以及apppycharm中创建:file-->New Project-->Django-->create使用命令创建#创建工程django-admin.py startproject projectname#创建apppython manage.py startapp appnametips:django中的工程和app概念,django相当于一个大项目,而其中的小系统如监控、资产管理属于这个项目的一部分我们称之为app。app目录结构app :migrations #数据库修改表结构记录admin.py #Django提供的后台管理apps.py #当前app配置文件models.py #Django中的ORM,写指定的类 通过命令可以创建数据库结构tests.py #项目的单元测试views.py #MTV中的视图,用于写业务逻辑 3、配置django模板配置:django工程-->相应工程名-->settings.py-->TEMPLATES(默认配置好的)TEMPLATES = [{'BACKEND': 'django.template.backends.django.DjangoTemplates','DIRS': [os.path.join(BASE_DIR, 'templates')],'APP_DIRS': True,'OPTIONS': {'context_processors': ['django.template.context_processors.debug','django.template.context_processors.request','django.contrib.auth.context_processors.auth','django.contrib.messages.context_processors.messages',],},},]静态目录(图片、css)配置:django工程-->相应工程名-->settings.py-->STATICFILES_DIRS(需要自己手动添加,目录使用逗号隔开)STATICFILES_DIRS = (os.path.join(BASE_DIR, "static"),) tips:若访问某个连接出现forbbiden,很有可能是有CSRF原因,可以在配置中注释settings.py 4、其他django常用命令#创建django命令django-admin.py startproject project-name#创建django的apppython manage.py startapp app-name或 django-admin.py startapp app-name#同步数据库python manage.py syncdb#注意:Django 1.7.1及以上的版本需要用以下命令python manage.py makemigrationspython manage.py migrate#命令行调试模式python manage.py runserver 8001python manage.py runserver 0.0.0.0:8000#清除数据库python manage.py flush#创建超级管理员python manage.py createsuperuser按照提示输入#修改管理员密码python manage.py changepassword username(需要修改的用户名)#导入和导出数据python manage.py dumpdata appname > appname.jsonpython manage.py loaddata appname.json#进入数据库python manage.py dbshell三、开始你的站点 1、配置路由(工程目录中的urls.py)from django.conf.urls import urlfrom django.contrib import adminfrom cmdb import views#从app中导入views视图urlpatterns = [url(r'^admin/', admin.site.urls),#当用户访问http://127.0.0.1:8000/admin/时候使用admin.site.urls处理url(r'^$',views.index),#当用户访问http://127.0.0.1:8000,采用views下面的index函数处理,^表示开头,$表示结尾]2、使用views处理用户请求(app下面的views.py)from django.shortcuts import render,HttpResponse,redirect# Create your views here.def index(request):#request中包含了用户所有的请求数据,通过该参数可以获取很多请求信息比如:# request.method GET / POST #获取用户请求的方法# request.GET.get('key') #获取请求发来的而数据,参数为数据key# request.POST.get('key')return render(request,'index.html')#使用模板处理、响应用户请求,参数至少是两个,第一个固定为request,第二个为templates下的模板文件#return HttpResponse("<p>this is wd </p>") #使用HttpResponse响应用户请求,参数为字符串#return redirect("http://www.baidu.com") #将用户请求重定向到百度,若本地跳转只能写目录,比如'/login'3、在templates目录中创建模板文件index.html,若模板文件中需要引入css或者images等内容,路径需要加上static,如<link rel="stylesheet" href="/static/css/core/main.css">4、运行django程序,访问http://127.0.0.1:8000你的站点就会呈现 四、视图(views)介绍django中的视图用于处理业务逻辑,常用的定义试图方法有两种:FBV(fuction base  view)、CBV(class base view)1、FBVviews.py使用函数作为逻辑处理def login(request):'''code'''return HttpResponse('login')2、CBVviews.py使用面向对象的方式定义class login(View):#需要继承View类def dispath(self, request, *args, **kwargs):print("before do something")result = super(login, self).dispath(request, *args, **kwargs)#执行父类方法,使得重写子类方法不影响其他操作print("after do something")return result #需要将结果返回def get(self, request):#处理get方法的请求print(request.method)return render(request, "index.html")def post(self, request):#处理post方法的请求print(request.method)return render(request, "index.html")使用cbv进行处理时候最,需要在urls.py中使用as_view()方法url(r'^$', views.login.as_view()) 五、模板语言(template)若我们想让前端和后台进行数据交互、传递,那我需要了解django中的模板引起,下面简单介绍常用的模板语言。1、获取单个数据,后台视图传递数据:{{ key }}视图函数:def person(request):if request.method=="GET":user_dict={'name':'wd','age':22}return render(request,'person.html',user_dict)#通过模板传递第三个参数,向html页面传递数据模板文件:<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title></head><body><h1>{{ name }}</h1><h1>
Django进阶
 内容简介:cookie介绍session介绍分页CSRF中间件缓存信号一、cookie介绍 1、cookie机制在程序中,会话跟踪是很重要的事情。理论上,一个用户的所有请求操作都应该属于同一个会话,而另一个用户的所有请求操作则应该属于另一个会话,二者不能混淆。例如,用户A在超市购买的任何商品都应该放在A的购物车内,不论是用户A什么时间购买的,这都是属于同一个会话的,不能放入用户B或用户C的购物车内,这不属于同一个会话。而Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。即用户A购买了一件商品放入购物车内,当再次购买商品时服务器已经无法判断该购买行为是属于用户A的会话还是用户B的会话了。要跟踪该会话,必须引入一种机制。Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。2、cookie的工作原理Cookie意为“甜饼”,是由W3C组织提出,最早由Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器如IE、Netscape、Firefox、Opera等都支持Cookie。由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。3、django中使用cookie 获取cookie说明:request.COOKIES #代表客户端发送的全部cookie,以键值对方式存储,可以理解为一个字典request.COOKIES['key'] #key存在则获取,不存在则报错,不建议使用request.COOKIES.get('key') #获取cookie,不存在返回None,建议使用设置cookie#创建响应对象response=render(request,'index.html')response=redirect('index')#设置cookieresponse.set_cookie('key',value) #默认关闭浏览器就失效response.set_signed_cookie(key,value,salt='加密盐',...)#设置带签名的cookie其他参数:key, 键value='', 值max_age=None, cookie失效时间,单位秒expires=None, cookie失效时间戳(IE requires expires, so set it if hasn't been already.),参数datetime对象path='/', Cookie生效的路径,/ 表示根路径,特殊的:跟路径的cookie可以被任何url的页面访问domain=None, Cookie生效的域名secure=False, https传输httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖带签名的cookie:设置时候加签,cookie内容是加密的#设置带签名的cookieresponse.set_signed_cookie('username','wd',salt='adada')#从请求中获取cookie,salt参数要一致request.get_signed_cookie('username',salt='adada')  使用javascript操作cookie,前提不能设置httponly=True(使用js,需要下载引人jquery.cookie.js插件) 示例代码一:使用cookies做用户认证def index(request):if request.method=="GET":v=request.COOKIES.get('username') #获取当前用户if not v:return redirect('/app01/login/')else:return render(request,'index.html',{'current_user':v})def login(request):if request.method=="GET":return render(request,'login.html')if request.method=="POST":u=request.POST.get('user')p=request.POST.get('pwd')print(u,p)if u=='admin' and p=='admin':response=redirect('/app01/index/')#response.set_cookie('username',u,max_age=60*60)#设置一个小时以后cookie失效import datetimecurrent_date=datetime.datetime.now()expre_date=current_date+datetime.timedelta(seconds=5)response.set_cookie('username', u, expires=expre_date) # 设置5秒后cookie过期return responseelse:return redirect('/app01/login/')demo示例代码二:基于Cookie实现定制显示数据条数(包含juery操作cookie)模版代码:<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><style>.pg-a{padding: 5px;margin: 5px;background-color: aqua;}.alive{background-color: red;}</style></head><body><ul>{% for row in user_info %}<li>{{ row }}</li>{% endfor %}</ul><div>{{ page_str }}</div><p>选择每页显示条目数:<select id="choose_item"><option value="10" >10</option><option value="30" >30</option><option value="50">50</option><option value="100">100</option></select></p><script src="/static/jquery-2.0.3.js"></script><script src="/static/jquery.cookie.js"></script><script>$(function () {var v=$.cookie('page_count'); //获取当前选择的值$('#choose_item').val(v) //框架加载完成将选项改为当前选项});$('#choose_item').change(function () {var v=$(this).val();$.cookie('page_count',v); //设置cookielocation.reload()})</script></body></html>View Code视图代码:def detail(request):from .utils import pageif request.method=="GET":current_index= int(request.GET.get('p', 1)) # 第二个参数代表获取不到默认设置为1page_num=request.COOKIES.get('page_count')print(page_num)if page_num.isdigit():page_num=int(page_num)mypage = page.PagiNation(current_index=current_index,data_num=500,page_num=page_num)page_str=mypage.page_str('/app01/detail')data =userlist[mypage.start:mypage.end]return render(request,'userlist.html',{'user_info':data,'page_str':page_str})View Code 4、基于cookie的登录验证装饰器 基础篇中的django视图提到了,对于view函数中我们可以定义两种视图,一种是函数(FBV),一种是类(CBV)所以对于认证的装饰器也有两种:FBV#定义装饰器def auth(func):def inner(reqeust,*args,**kwargs):v = reqeust.COOKIES.get('username')if not v:return redirect('/login/')return func(reqeust, *args,**kwargs)return inner#装饰需要登录验证的函数@authdef index(reqeust):return render(reqeust,'index.html',{'current_user': v})CBV#定义装饰器def auth(func):def inner(reqeust,*args,**kwargs):v = reqeust.COOKIES.get('username')if not v:return redirect('/login/')return func(reqeust, *args,**kwargs)return inner#使用装饰器,方法有三种fro
Django之form组件
一、简介web框架中避免不了对表单的验证,我们可以通过js在表单数据提交前做一次校验,然而在Django中form组件不仅仅提供验证功能,还可以生成HTML,还可以与model结合使用,等等强大的功能。先了解下主要的作用:生成HTML标签验证用户数据(显示错误信息)HTML Form提交保留上次提交数据初始化页面显示内容二、基本操作1.创建form类#_*_ coding:utf-8 _*_#Author:wdfrom django.forms import Formfrom django.forms import widgetsfrom django.forms import fieldsclass myform(Form):user=fields.CharField( #字段名字一定要和模板中html中input中name属性一致才能验证widget=widgets.TextInput(attrs={'id':'a1','class':'c1'}), #设置生成hmtl的样式min_length=6,error_messages={'required':'用户名不能为空','min_length':'用户名至少6位'})pwd=fields.CharField(widget=widgets.PasswordInput(),max_length=24,error_messages={'required':'密码不能为空','max_length':'密码长度不能超过24'})email=fields.EmailField(error_messages={'required':'用户名不能为空','invalid':'邮箱格式不正确'}) #校验时候出现的错误提示信息2.views函数处理from .cmdbforms import myform #导入定义的form类def fm(request):if request.method=='GET':obj=myform() #生成对象return render(request,'form.html',{'obj':obj})#将对象传入到模板中用于生成htmlelif request.method=='POST':obj=myform(request.POST)if obj.is_valid(): #该方法验证post提交表单的数据是否合法,合法返回True或者否则Flaseprint(obj.clean()) #获取post表单中提交的数据,格式为字典,可以直接用于model操作数据库print(obj.cleaned_data)#获取post表单中提交的数据,格式为字典,可以直接用于model操作数据库return redirect('/cmdb/fm/')else:print(obj.errors)print(obj.errors.as_json())#json格式的错误return render(request,'form.html',{'obj':obj})#当出现错误的时候返回的错误信息3.HTML模板<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="/cmdb/fm/" method="POST" enctype="multipart/form-data">{% csrf_token %}<p>{{ obj.user }} {{ obj.user.errors.0 }}</p>{# 0代表第一个错误,在这里就是错误的字符串 #}<p>{{ obj.pwd }} {{ obj.pwd.errors.0 }}</p><p>{{ obj.email }} {{ obj.email.errors.0 }}</p><input type="submit" value="提交"/></form></body></html> 三、form类中的字段和插件介绍 创建Form类时,主要涉及到字段(fields)和插件(widgets),字段用于对用户请求数据的验证,插件用于自动生成HTML。1、Django内置字段Fieldrequired=True, 是否允许为空widget=None, HTML插件label=None, 用于生成Label标签或显示内容(使用用户名密码时候,使用)initial=None, 初始值(默认值)help_text='', 帮助信息(在标签旁边显示)error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)validators=[], 自定义验证规则localize=False, 是否支持本地化,示例场景:utc时间和本地时间转化disabled=False, 是否可以编辑label_suffix=None Label内容后缀CharField(Field)max_length=None, 最大长度min_length=None, 最小长度strip=True 是否移除用户输入空白IntegerField(Field)max_value=None, 最大值min_value=None, 最小值FloatField(IntegerField)...DecimalField(IntegerField)max_value=None, 最大值min_value=None, 最小值max_digits=None, 总长度decimal_places=None, 小数位长度BaseTemporalField(Field)input_formats=None 时间格式化DateField(BaseTemporalField) 格式:2015-09-01TimeField(BaseTemporalField) 格式:11:12DateTimeField(BaseTemporalField)格式:2015-09-01 11:12DurationField(Field) 时间间隔:%d %H:%M:%S.%f...RegexField(CharField)regex, 自定制正则表达式max_length=None, 最大长度min_length=None, 最小长度error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}EmailField(CharField)...FileField(Field)allow_empty_file=False 是否允许空文件,文件也在clean_data里ImageField(FileField)...注:需要PIL模块,pip3 install Pillow以上两个字典使用时,需要注意两点:- form表单中 enctype="multipart/form-data"- view函数中 obj = MyForm(request.POST, request.FILES)URLField(Field)...BooleanField(Field)...NullBooleanField(BooleanField)...ChoiceField(Field)...choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)required=True, 是否必填widget=None, 插件,默认select插件label=None, Label内容initial=None, 初始值help_text='', 帮助提示ModelChoiceField(ChoiceField)... django.forms.models.ModelChoiceFieldqueryset, # 查询数据库中的数据empty_label="---------", # 默认空显示内容to_field_name=None, # HTML中value的值对应的字段limit_choices_to=None # ModelForm中对queryset二次筛选ModelMultipleChoiceField(ModelChoiceField) #多选... django.forms.models.ModelMultipleChoiceFieldTypedChoiceField(ChoiceField)coerce = lambda val: val 对选中的值进行一次转换empty_value= '' 空值的默认值MultipleChoiceField(ChoiceField)...TypedMultipleChoiceField(MultipleChoiceField)coerce = lambda val: val 对选中的每一个值进行一次转换empty_value= '' 空值的默认值ComboField(Field)fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])MultiValueField(Field)PS: 抽象类,只能被继承,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用SplitDateTimeField(MultiValueField)input_date_formats=None, 格式列表:[
Django之Model组件
Model组件在django基础篇就已经提到过了,本章介绍更多高级部分。一、回顾1、定义表(类)##单表from django.db import modelsclass user(models.Model): #数据库表名为app_classname,比如现在这个表存在数据库中为cmdb_username=models.CharField(max_length=24) #字符串,最大长度24age=models.IntegerField() #整数类型##一对多from django.db import modelsclass user(models.Model):name=models.CharField(max_length=24)age=models.IntegerField()user_group=models.ForeignKey('usergroup',to_field='gid')#外键约束,to_field表示关联的字段,生成的字段为user_group_id,不写的话默认和表的主键关联class usergroup(models.Model):gid=models.AutoField(primary_key=True)groupname=models.CharField(max_length=20,unique=True)ctime=models.DateField(auto_now_add=True)##一对一from django.db import modelsclass user(models.Model):name=models.CharField(max_length=24)age=models.IntegerField()user_group=models.ForeignKey('usergroup',to_field='gid',unique=True)#加上unique=True确保是一一对应class usergroup(models.Model):gid=models.AutoField(primary_key=True)groupname=models.CharField(max_length=20,unique=True)ctime=models.DateField(auto_now_add=True)###多对多##########第一种使用传统外键方式关联(自己定义关系)######class Host(models.Model):hid=models.AutoField(primary_key=True)hostname=models.CharField(max_length=20)ip=models.GenericIPAddressField()class HostGroup(models.Model):gid=models.AutoField(primary_key=True)groupname=models.CharField(max_length=22)#自己定义第三张表,可以随意添加字段class HostToGroup(models.Model):hobj=models.ForeignKey(to='Host',to_field="hid")#生成的数据库表字段为hobj_idgobj=models.ForeignKey(to='HostGroup',to_field="gid")#生成的数据库表字段为gobj_id##########第二种使用django自带的方式创建########class Host(models.Model):hid=models.AutoField(primary_key=True)hostname=models.CharField(max_length=20)ip=models.GenericIPAddressField()class HostGroup(models.Model):gid=models.AutoField(primary_key=True)groupname=models.CharField(max_length=22)hobj=models.ManyToManyField('Host')#使用django自带字段创建第三张表,自动关联主键,数据库表名为cmdb_hostgroup_hobj,这种方式不能直接操作第三张表,可以间接操作#########第三种自定义第三张表,使用django创建m2m关联字段class Host(models.Model):hid=models.AutoField(primary_key=True)hostname=models.CharField(max_length=20)ip=models.GenericIPAddressField()m=models.ManyToManyField('HostGrop',through='HostToGroup',through_fields=['hobj','gobj'])#通过自定义m2m关系只能做查询操作class HostGroup(models.Model):gid=models.AutoField(primary_key=True)groupname=models.CharField(max_length=22)#自己定义第三张表,可以随意添加字段class HostToGroup(models.Model):hobj=models.ForeignKey(to='Host',to_field="hid")#生成的数据库表字段为hobj_idgobj=models.ForeignKey(to='HostGroup',to_field="gid")#生成的数据库 2、model字段介绍AutoField(Field)- int自增列,必须填入参数 primary_key=TrueBigAutoField(AutoField)- bigint自增列,必须填入参数 primary_key=True注:当model中如果没有自增列,则自动会创建一个列名为id的列from django.db import modelsclass 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 ~ 32767PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)- 正小整数 0 ~ 32767IntegerField(Field)- 整数列(有符号的) -2147483648 ~ 2147483647PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)- 正整数 0 ~ 2147483647BigIntegerField(IntegerField):- 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807BooleanField(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中提供验证 URLSlugField(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.FileSystemStorageImageField(FileField)- 字符串,路径保存在数据库,文件上传到指定目录- 参数:upload_to = "" 上传文件的保存路径storage = None 存储组件,默认django.core.files.storage.FileSystemStoragewidth_field=None, 上传图片的高度保存的数据库字段名(字符串)height_field=None 上传图片的宽度保存的数据库字段名(字符串)DateTimeField(DateField)- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]DateField(DateTimeCheckMixin, Field)- 日期格式 YYYY-MM-DDTimeField(DateTimeCheckMixin, Field)- 时间格式 HH:MM[:ss[.uuuuuu]]DurationField(Field)- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型FloatField(Field)- 浮点型DecimalField(Field)- 10进制小数- 参数:max_digits,小数总长度decimal_places,小数位长度BinaryField(Field)- 二进制类型字段列表class UnsignedIntegerField(models.IntegerField):def d
Django中的F和Q函数
内容简介:介绍Django中的F和Q作用以及使用方法一、F介绍 作用:操作数据表中的某列值,F()允许Django在未实际链接数据的情况下具有对数据库字段的值的引用,不用获取对象放在内存中再对字段进行操作,直接执行原生产sql语句操作。通常情况下我们在更新数据时需要先从数据库里将原数据取出后方在内存里,然后编辑某些属性,最后提交。例如:obj = Order.objects.get(orderid='12')obj.amount += 1obj.order.save()上述方法生成的sql语句为:UPDATE `core_order` SET ..., `amount` = 22 WHERE `core_order`.`orderid` = '12' # ...表示Order中的其他值,在这里会重新赋一遍值; 22表示为计算后的结果但是我们本意想生成的sql语句为:UPDATE `core_order` SET ..., `amount` = `amount` + 1 WHERE `core_order`.`orderid` = '12'此时F的使用场景就在于此:from django.db.models import Ffrom core.models import Orderobj = Order.objects.get(orderid='12')obj.amount = F('amount') + 1obj.save()#生成的sql语句为:UPDATE `core_order` SET ..., `amount` = `core_order`.`amount` + 1 WHERE `core_order`.`orderid` = '12' # 和预计的一样当Django程序中出现F()时,Django会使用SQL语句的方式取代标准的Python操作。上述代码中不管 order.amount 的值是什么,Python都不曾获取过其值,python做的唯一的事情就是通过Django的F()函数创建了一条SQL语句然后执行而已。需要注意的是在使用上述方法更新过数据之后需要重新加载数据来使数据库中的值与程序中的值对应:  order= Order.objects.get(pk=order.pk) 或者使用更加简单的方法:order.refresh_from_db() 参考博客:http://www.tuicool.com/articles/u6FBRz官方地址:https://docs.djangoproject.com/en/1.11/ref/models/expressions/ 二、Q介绍作用:对对象进行复杂查询,并支持&(and),|(or),~(not)操作符。基本使用:from django.db.models import Qsearch_obj=Asset.objects.filter(Q(hostname__icontains=keyword)|Q(ip=keyword))如果查询使用中带有关键字查询,Q对象一定要放在前面Asset.objects.get(Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),question__startswith='Who')参考:博客:http://www.cnblogs.com/linjiqin/p/3817814.html官网地址:https://docs.djangoproject.com/en/1.11/topics/db/queries/#complex-lookups-with-q 
Django文件上传三种方式以及简单预览功能
 主要内容:一、文件长传的三种方式二、简单预览功能实现一、form表单上传1.页面代码<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="{% url 'upload' %} " method="post" enctype="multipart/form-data">{% csrf_token %}<input type="file" name="img"><input type="submit"></form></body></html>2.后端view函数处理def upload(request):if request.method=="GET":return render(request,'upload.html')elif request.method=="POST":res={"status":"success","code":999}img_obj=request.FILES.get('img')#获取文件对象with open(os.path.join('static',img_obj.name),"wb") as f:for chunk in img_obj.chunks(chunk_size=1024):f.write(chunk)return HttpResponse(json.dumps(res))二、利用Jquery中ajax+FormData实现上传文件FormData使用方式:创建一个空的FormData对象,然后再用append方法逐个增加键值对var formdata = new FormData();formdata.append("name","xx");formdata.append("flie", filename);取得form元素对象,将它作为参数传入FormData对象中。var formOjb = document.getElementById("form");var formdata = new FormData(formOjb );利用form元素对象的getFormData方法生成它var formobj = document.getElementById("form");var formdata = formobj.fetFormData(); 1.页面代码<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="{% url 'upload' %} " method="post" enctype="multipart/form-data">{% csrf_token %}<input type="file" name="img"></form><span style="padding: 5px;color: royalblue" onclick="Jqajax();">jq上传</span><script type="text/javascript" src="/static/jquery-2.1.1.min.js"></script><script type="text/javascript" src="/static/jquery.cookie.js"></script><script>function Jqajax() {var file_obj=$('input[name="img"]')[0].files[0];//获取dom形式的文件对象var form_obj=new FormData(); //创建formdata对象form_obj.append('img',file_obj); //将文件对象加载formdata中$.ajaxSetup({ //设置csrf_tokenbeforeSend: function (xhr, settings) {xhr.setRequestHeader("X-CSRFToken", $.cookie("csrftoken"));}});$.ajax({type: 'POST',url: '{% url 'upload' %}',data: form_obj,//指明发送的文件对象processData: false, // 告诉jquery要传输data对象contentType: false, // 告诉jquery不需要增加请求头对于contentType的设置success: function (data) {console.log(data)}})}</script></body></html>后端处理逻辑不变三、基于iframe实现伪ajax文件上传原理:iframe数据提交不刷新页面好处:通过iframe的src属性进行上传功能,这样的好处不用依赖FormData对象,低版本浏览器可能不支持该对象。前端页面<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="{% url 'upload' %}" method="post" target="iframe_1" enctype="multipart/form-data">{% csrf_token %}<iframe style="display: none" id="iframe_1" name="iframe_1" src=""></iframe><input type="file" name="img" /><input type="submit" onclick="iframeSubmmit();" value="iframe上传"/></form><script type="text/javascript" src="/static/jquery-2.1.1.min.js"></script><script type="text/javascript" src="/static/jquery.cookie.js"></script><script>function iframeSubmmit() {$('#iframe_1').load(function () {//用户获取上传成功以后的返回值var text=$('#iframe_1').contents().find('body').text();var obj=JSON.parse(text);console.log(obj) ;})}</script></body></html>后端处理逻辑一样四、图片预览功能原理:通过图片上传时候服务端返回文件路径,然后使用js设置a标签src属性实现预览功能后端代码:def upload(request):if request.method=="GET":return render(request,'upload.html')elif request.method=="POST":img_obj=request.FILES.get('img')#获取文件对象file_path=os.path.join('static',img_obj.name)res={"status":"success","code":999,"file_path":file_path}with open(os.path.join(file_path,"wb") as f:for chunk in img_obj.chunks(chunk_size=1024):f.write(chunk)return HttpResponse(json.dumps(res)) 
Django实现组合搜索
一、实现方法1.纯模板语言实现2.自定义simpletag实现(本质是简化了纯模板语言的判断)二、基本原理原理都是通过django路由系统,匹配url筛选条件,将筛选条件作为数据库查询结果,返回给前端。例如:路由系统中的url格式是这样:url(r'^article-(?P<article_type_id>d+)-(?P<category_id>d+).html',views.filter)其中article_type_id和category_id和数据库中字段是相对应的,此时当一个url为article-1-2.html时候,后台处理函数的参数将是一个字典{'article_type_id': 1, 'category_id': 1},然后将该条件作为数据库查询条件,最后得出结果返回给前端三、代码样例方法1:纯模板语言实现urls.py#!/usr/bin/env python3#_*_ coding:utf-8 _*_#Author:wdfrom django.conf.urls import urlfrom . import viewsurlpatterns = [url(r'^$',views.index),url(r'^article-(?P<article_type_id>d+)-(?P<category_id>d+).html',views.filter),]models.pyfrom django.db import modelsclass Category(models.Model):caption=models.CharField(max_length=64)class Article_type(models.Model):caption=models.CharField(max_length=64)class Article(models.Model):title=models.CharField(max_length=64)content=models.CharField(max_length=256)category=models.ForeignKey(to='Category')article_type=models.ForeignKey(to='Article_type'views.pydef filter(request,*args,**kwargs):if request.method=="GET":condition={}for k,v in kwargs.items():kwargs[k]=int(v) #模板if判断row.id是数字,所以这里需要转换if v=="0":#当条件为0代表所选的是全部,那么就不必要加入到过滤条件中passelse:condition[k]=int(v)aritcle=models.Article.objects.filter(**condition)aritcle_type=models.Article_type.objects.all()aritcle_category=models.Category.objects.all()return render(request,'search.html',{'aritcle':aritcle,'article_type':aritcle_type,'article_category':aritcle_category,'article_arg':kwargs,#将当前的筛选条件传递给html})html模板<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><style>.container a{display: inline-block;padding: 3px 5px;margin: 5px;border: 1px solid #dddddd ;}.active{background-color: rebeccapurple;}</style></head><body><h1>搜索条件</h1><div class="container">{% if article_arg.article_type_id == 0 %}<a class="active" href="/cmdb/article-0-{{ article_arg.category_id }}.html">全部</a>{% else %}<a href="/cmdb/article-0-{{ article_arg.category_id }}.html">全部</a>{% endif %}{% for row in article_type %}{% if row.id == article_arg.article_type_id %}<a class="active" href="/cmdb/article-{{ row.id }}-{{ article_arg.category_id }}.html">{{ row.caption }}</a>{% else %}<a href="/cmdb/article-{{ row.id }}-{{ article_arg.category_id }}.html">{{ row.caption }}</a>{% endif %}{% endfor %}</div><div class="container">{% if article_arg.category_id == 0 %}<a class="active" href="/cmdb/article-{{ article_arg.article_type_id }}-0.html">全部</a>{% else %}<a href="/cmdb/article-{{ article_arg.article_type_id }}-0.html">全部</a>{% endif %}{% for row in article_category %}{% if row.id == article_arg.category_id %}<a class="active" href="/cmdb/article-{{ article_arg.article_type_id }}-{{ row.id }}.html">{{ row.caption }}</a>{% else %}<a href="/cmdb/article-{{ article_arg.article_type_id }}-{{ row.id }}.html">{{ row.caption }}</a>{% endif %}{% endfor %}</div><h1>查询结果</h1><div>{% for row in aritcle %}<div>{{ row.id }}-{{ row.title }}</div>{% endfor %}</div></body></html>方法二:使用simpletag实现定义simpletag,参考博客Django基础篇myfilter.py#!/usr/bin/env python3#_*_ coding:utf-8 _*_#Author:wdfrom django import templatefrom django.utils.safestring import mark_saferegister=template.Library()@register.simple_tagdef filter_all(article_arg,condition):'''处理条件为全部:param article_arg: 当前url字典:如{'article_type_id': 1, 'category_id': 1}:param condition: 要处理的条件,如article_type_id,用于区分当前处理选择了那个全部:return: 返回下面页面形式{% if article_arg.article_type_id == 0 %}<a class="active" href="/cmdb/article-0-{{ article_arg.category_id }}.html">全部</a>{% else %}<a href="/cmdb/article-0-{{ article_arg.category_id }}.html">全部</a>{% endif %}{% for row in article_type %}{% if row.id == article_arg.article_type_id %}<a class="active" href="/cmdb/article-{{ row.id }}-{{ article_arg.category_id }}.html">{{ row.caption }}</a>{% else %}<a href="/cmdb/article-{{ row.id }}-{{ article_arg.category_id }}.html">{{ row.caption }}</a>{% endif %}{% endfor %}'''if condition=='article_type_id':if article_arg[condition]==0:print(article_arg['category_id'])res= '<a class ="active" href="/cmdb/article-0-%s.html">全部</a>' % article_arg['category_id']else:res = '<a href="/cmdb/article-0-%s.html">全部</a>' % article_arg['category_id']return mark_safe(res)elif condition=='category_id':if article_arg['category_id']==0:res = '<a class ="active" href="/cmdb/article-%s-0.html">全部</a>' % art