| Django 创建新的app程序 |
| python manage.py startapp 新app名字 |
| 切记继承的关键:子类继承父类,如果子类没有这个方法,那么就从父类找,每次找内容都是从子到父,不会跨过子类直接找父类 |
同源策略
| 服务器正常执行,正常的返回结果,但是结果被前端拦截 |
| 浏览器的同源策略,浏览器基本的安全功能(支持js) |
| 所有的浏览器使用的同源策略(保护服务端的数据) |
| 同源: |
| 域名,协议,端口相同 (内部只要有一个不同,那么就不同源) |
| |
| 可以理解为: |
| 发送ajax请求的网址ip和端口,与浏览器导航上的不同,就会出现不同源的现象 |
| |
| 发起请求的ip or 端口 与 发送给服务器的端口 ip 是否同源 |
| 例如: |
| http: |
| $.ajax({ |
| url:'http://127.0.0.1:8000/...' 同源不会被浏览器拦截 |
| }) |
| |
| 不同源: |
| file: |
| $.ajax({ |
| url:'http://127.0.0.1:8000/...' 不同源会被浏览器拦截 |
| }) |
| 协议不同,端口不同,ip不同 |
| |
| |
| 解决方式: |
| 1.jsonp |
| 2.cors(服务器给浏览器说一声,数据可以发送) |
| 3.前端代理(vue ...) |
| |
| cors 将缺少的响应头给设置了 |
| 给返回的相应头中设置 * 代表全部的全部可以访问 |
| res['Access-Control-Allow-Origin'] = '*' or 设置 'ip端口' |
| res['Access-Control-Allow-Headers'] = '*' |
| |
| 当发送复杂请求时: |
| 会出现两次请求,在数据发送之前进行遇见,只有预检通过后,在发送一次请求用于数据的传输 |
| |
| 请求方式options |
| 检查通过传输数据,不通过不发数据 |
| |
Restful规范
| 协议 |
| http 协议 |
| https 协议 内部存在数据加密 保证数据安全 |
| |
| 域名: |
| 后端 api接口中体现api标识 |
| api.xxx.com |
| |
| 版本 |
| url 体现版本: |
| http://api.xxx.com/v1 |
| http://api.xxx.com/>version=v1 |
| http://v1.xxx.com/ 在二级域名中体现版本 |
| http://api.xxx.com/ |
| 请求头中: accept:application/json;rsion=v1 |
| |
| 路径 |
| http://api.xxx.com/v1/名词 |
| http://api标识/版本标识/名词(具有代表代表性) |
| |
| 请求的方式 |
| get http://api.xxx.com/v1/users/ |
| get http://api.xxx.com/v1/users/?id=1 |
| post http://api.xxx.com/v1/users |
| pui http://api.xxx.com/v1/users |
| patch http://api.xxx.com/v1/users |
| delete http://api.xxx.com/v1/users |
| |
| |
| |
| 搜索条件 |
| 当存在搜索条件时,使用get形式 ? 请求参数 分页内容 指定内容 |
| http://api.xxx.com/v1/users?limit=10 指定返回记录数据 |
| |
| 返回数据 |
| 针对请求方式不同,返回数据结构不同 |
| http://api.xxx.com/v1/users |
| 例如: |
| /user/ get 返回的是一个用户列表 [{'id':1},{'id':2},...] |
| /user/?id=1 get 返回的是单条用户数据 {'id':1} |
| /user/ post 添加一条用户数据 {'id':'新添加id','name':xxx} |
| /user/d+ delect 删除数据 返回 null |
| /user/d+ put 更新数据 返回更新数据的当条的全部内容 {'id':'id','name':xxx,...} |
| /user/d+ patch 更新局部数据 返回更新数据的当条的全部内容 {'id':'id','name':xxx,...} |
| |
| 返回的内容 |
| 正确 |
| {'code':'返回码','data': [{'id':1},{'id':2},...]} |
| 错误 |
| {'code':'返回码','error':'详细的错误信息'} |
| |
| 状态码 |
| |
| 200 ok |
| 201 post put patch 用户新建或者修改数据成功 |
| 202 表示一个请求已经后台排队 (异步任务) |
| 204 表示用户删除数据成功 |
| 400 用户发送请求错误 服务器没有进行新建或者修改数据操作 |
| 401 表示用户请没有认证 密码 用户名 token |
| 403 表示用户没有被授权(无权访问) |
| 404 用户请求数据不存在 |
| 406 用户请求的发送数据格式不对 json格式 发成xml格式 |
| 410 请求资源被永久删除,不会在得到 |
| 422 创建对象时发生验证错误 |
| 500 服务器出现错误 |
| |
| 错误处理 |
| 需要将错误信息进行返回 |
| { |
| code:'返回码', |
| error:'错误定义' |
| } |
FBV 和 CBV
| FBV 函数 function base views |
| 路由的对应的url 对应这函数 |
| |
| CBV 类开发 class base views |
| 通过类名执行 |
| 类名.as_view()方法,本质就是fbv 在这一上层的分装 |
| 在内部获取请求方式,进行反射调用CBV的类中的方法进行执行 |
安装使用
| 1. |
| pip install djangorestframework |
| 需要安装指定版本 |
| django 3.1以上 和python 3.5 以上 安装 3.12.4 |
| django3.0以下 和 python 3.8 以下 安装 3.11.2 |
| |
| 2. |
| 在配置文件中进行配置 |
| 本质上restful属于一个别人写好的app程序需要在配置文件中进行注册 |
| INSTALLED_APPS = [ |
| .... |
| 'rest_framework' |
| ] |
| |
| 在配置文件中写上restful配置变量 |
| REST_FRAMEWORK = { |
| |
| } |
| |
| 3. |
| 路由配置 |
| from django.urls import path |
| from app import views |
| |
| urlpatterns = [ |
| path('api/user/', views.Users.as_view()), |
| ] |
| |
| |
| 视图编写 |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| |
| |
| class Users(APIView): |
| |
| def get(self, request, *args, **kwargs): |
| return Response({'code': 1008, 'data': 1}) |
| |
| 本质上rest_framwork继承了django自带的View API类,但是在当前的View中进行了更上次一层的封装 |
| 在rest_framwork 中的继承了 View, 在路由调用rest_framwork 中的方法as_view()时 使用super().as_view() 也就是说本质上调用了View类中的as_view()方法,而rest_framwork中的as_view()就是对继承的View中的进一步封装(内部免除了csrf的认证) |
| |
| rest_framwork API类就是 中的dispatch方法比 View Api 进行封装了更多的功能 |
数据获取
| 因为是对原来的Viwe进行了封装,同时也对请求的request也进行了封装 |
| |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| |
| |
| class Users(APIView): |
| |
| def get(self, request, *args, **kwargs): |
| |
| |
| |
| kwarag |
| |
| |
| args |
| |
| |
| |
| print(self.request.POST) |
| print(self.request.GET) |
| print(self.request.method) |
| print(self.request.body) |
| print(self.headers) |
| |
| print(request.POST) |
| print(request.GET) |
| print(request.body) |
| print(request.method) |
| print(request.headers) |
| |
| |
| print(request._request.POST) |
| print(request._request.GET) |
| print(request._request.body) |
| print(request._request.method) |
| print(request._request.headers) |
| return Response({'code': 1008, 'data': 1}) |
| |
| |
| 另一种方式(封装好的获取数据的方式) |
| request.query_params |
| request.data |
版本控制
1.通过url中get 传参方式传递版本(常用)
| url :127.0.0.1:8000/api/user/?version=1 |
| django drf 进行继承的版本方式 共5种 |
| 1.支持通过url以get形式传参形式(默认固定需要vresion,是可以修改的需要在rest frame 的配置文件中进行修改) |
| 1.单独视图类设置 |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| from rest_framework.versioning import QueryParameterVersioning |
| class Users(APIView): |
| versioning_class = QueryParameterVersioning |
| |
| def get(self, request, *args, **kwargs): |
| print(request.version) |
| return Response({'code': 1008, 'data': 1}) |
| |
| url :127.0.0.1:8000/api/user/?version=1 |
| |
| 2.全局视图类设置 |
| 需要在配置文件中setting.py 关于restframe 的配置中设置 |
| REST_FRAMEWORK = { |
| |
| 'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.QueryParameterVersioning' |
| } |
| |
| 3.其他参数配置 |
| REST_FRAMEWORK = { |
| 'VERSION_PARAM':'v' |
| 'DEFAULT_VERSION':'v1', |
| 'ALLOWED_VERSIONS':['v1','v2','v3'], |
| } |
| |
| ''' |
| 源码:在initial函数中调用了类API中determine_version(自己编写视图类中没有找父类)方法,对versioning_class变量对应的类进行实例化,并且调用了实例化对象中determine_version方法获取url get请求中的version版本值(同时内部还读取了difult_version默认版本,以及判断是否设置allowed_versions版本限制,对着3个值进行了判断处理,然后返回版本) |
| ''' |
2.通过在url路径中传递版本(常用)
| url :127.0.0.1:8000/api/v2/user/ |
| 需要在视图类中和url中进行调整 |
| |
| 1.url的设置 |
| 注意:参数参数名称必须是 version |
| path('api/<str:version>/user/', views.Users.as_view()) |
| re_path(r'^api/(?P<v>\w+)/user/$', views.Users.as_view()), |
| |
| 2.视图类设置 |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| from rest_framework.versioning import URLPathVersioning |
| |
| |
| class Users(APIView): |
| versioning_class = URLPathVersioning |
| |
| def get(self, request, *args, **kwargs): |
| print(request.version) |
| return Response({'code': 1008, 'data': 1}) |
| |
| |
| 3.配置文件中也可以设置一些参数 |
| REST_FRAMEWORK = { |
| 'VERSION_PARAM':'v' |
| 'ALLOWED_VERSIONS':['v1','v2','v3'], |
| 'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning' |
| } |
3.通过请求头传递版本
| url : 127.0.0.1:8000/api/user/ |
| |
| |
| 请求头key:Accept 请求头val:application/json; version=1.0 |
| |
| 1.url设置正常设置 |
| path('api/user/', views.Users.as_view()) |
| |
| 2.视图类 |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| from rest_framework.versioning import AcceptHeaderVersioning |
| |
| |
| class Users(APIView): |
| versioning_class = AcceptHeaderVersioning |
| |
| def get(self, request, *args, **kwargs): |
| print(request.version) |
| return Response({'code': 1008, 'data': 1}) |
| |
| 3.前端请求头中进行设置请求头 |
| 请求头key:Accept 请求头val:application/json; version=1.0 |
| |
| |
| 配置中可以设置参数: |
| REST_FRAMEWORK = { |
| 'VERSION_PARAM':'v' |
| 'ALLOWED_VERSIONS':['v1','v2','v3'], |
| 'DEFAULT_VERSIONING_CLASS':'rest_framework.AcceptHeaderVersioning' |
| } |
4.通过2级域名传递版本信息
| 想要测试域名的设置 |
| |
| 1.找到C:\Windows\System32\drivers\etc\hosts文件 |
| 修改当前127.0.0.1 的对应关系 |
| 127.0.0.1 v1.wkx.com |
| |
| 2.在django的配置文件中设置 |
| ALLOWED_HOST = ['*'] |
| |
| |
| URL : v1.wkx.com:8000/api/user/ |
| |
| url设置: |
| path('api/user/', views.Users.as_view()) |
| |
| 视图类设置 |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| from rest_framework.versioning import HostNameVersioning |
| |
| |
| class Users(APIView): |
| versioning_class = HostNameVersioning |
| |
| def get(self, request, *args, **kwargs): |
| print(request.version) |
| return Response({'code': 1008, 'data': 1}) |
5.通过namespace传递版本信息
| 主要是通过url的路由分发设置 |
| |
| 1.视图类 |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| from rest_framework.versioning import NamespaceVersioning |
| |
| |
| class Users(APIView): |
| versioning_class = NamespaceVersioning |
| |
| def get(self, request, *args, **kwargs): |
| print(request.version) |
| return Response({'code': 1008, 'data': 1}) |
| |
| |
| 2.url设置 |
| 主路由 |
| path('api/v1/',include('app.urls',namespace='v1')) |
| |
| 子路由 |
| path('user/', views.Users.as_view()) |
| |
| |
| |
| api/v1/user/ 可以获取版本信息 |
版本反向生成url
| 需要配合使用版本类进行使用,如果不使用的情况下,会进行报错 |
| |
| 使用request.versioning('路由的name','request') |
| |
| 视图: |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| from rest_framework.versioning import URLPathVersioning |
| |
| class Users(APIView): |
| versioning_class = URLPathVersioning |
| |
| def get(self, request, *args, **kwargs): |
| print(request.version) |
| |
| print(request.versioning_scheme.reverse('v1', request=request)) |
| |
| return Response({'code': 1008, 'data': 1}) |
| |
| url:需要对url设置name值 |
| path('api/<str:version>/user/', views.Users.as_view(),name='v1'), |
| |
| |
| 如果使用版本反向生成url,1.需要配置版本类,2.在url中设置name别名 底层就是根据django方向生成url |
认证
| 作用: |
| 根据用户发来的请求,从中获取请求的凭证,获取到用户的信息,用户登录没有登录的作用 |
| |
| |
| 为了区别对待,一些接口可以不需要凭证就能访问,一些接口必须要凭证才能访问 |
| 例如: |
| 商城页面:当用户登录后,才能将商品添加购物车,并且结账 |
| 如果没有登录,那么就会提示当前用户,必须登录后才能添加购物车 |
| |
| 认证类一个就可以 + jwt 认证 就可以(为什么一个就可以,因为如果认证成功,就会直接返回,内部循环就会终止,多个认证类没有意义) |
| |
| 与jwt 一样,在登录时生成一个随机码,发送给前端,前端进行保存,每次访问都要携带 |
1.快速使用
| # 补充 : 登录部分的随机生成使用的uuid 用来携带到前端进行使用 |
| import uuid |
| def post(self, request, *args, **kwargs): |
| print(request.data) |
| name = 'wkx' |
| pwd = '123' |
| if request.data.get('name') == name and request.data.get('pwd') == pwd: |
| name_uuid = uuid.uuid4() |
| return Response( |
| {'code': 200, 'msg': '登录成功', 'data': {'usernanme': request.data.get('name'), 'token': name_uuid}}) |
| return Response({'code': 400, 'msg': '用户名密码错误', 'data': None}) |
| |
| |
| |
| |
| # 1.需要导入认证父类和认证异常类 |
| from rest_framework.authentication import BaseAuthentication # 1.导入restframe的认证父类 |
| from rest_framework.exceptions import AuthenticationFailed # 1.认证类的异常 |
| |
| |
| 以url get 方式将登录生成的认证的值存放 也可以放在header头部中(需要前端将值放到请求头中或者url get 方法中) |
| url :http: |
| |
| |
| # 2.需要创建一个类进行继承认证父类 |
| 重写父类的两个方法 |
| |
| authenticate # 认证逻辑编写的地方,返回两个值 这个两个值分别赋值给rquest.user='第一个值' request.auth = '第二个值' |
| |
| authenticate_header # 如果认证失败会将 return 携带到响应头中 WWW-Authenticate Bearer realm=API(不重要) |
| |
| class Authentication(BaseAuthentication): |
| |
| def authenticate(self, request): # 负责认证 具体认证方法 |
| a = '7cf2bb20-ea20-4394-b109-55a22c548ca6' |
| token = request.query_params.get('token') # 从url中取token值 |
| token = request.headers.get('Token') # 从请求头中获取 |
| if not token: |
| raise AuthenticationFailed({'code': 1002, 'msg': '未认证'}) # 认证不通过,会将错误信息给前端 终止 |
| if not token == a: |
| raise AuthenticationFailed({'code': 1002, 'msg': '认证失败'}) |
| # 返回的值 被赋值了request中的属性user and auth (必须返回两个值) |
| # request.user = '用户对象' |
| # request.auth = 'token' |
| return '用户对象','token' # 返回元组 (None,None) 无论返回什么都可以 必须是两个值 要不然就会报错 |
| |
| def authenticate_header(self, request): |
| return 'Bearer realm=API' |
| |
| |
| # 3.api类中的使用 |
| class PayView(APIView): |
| |
| authentication_classes = [Authentication,认证1,认证2.... ] # 使用认证类(支持多个),如果认证不通过,那么不会进入接口的方法中 |
| |
| def get(self, request, *args, **kwargs): |
| print(request.user) # '用户对象' |
| print(request.auth) # 'token' |
| return Response({'code': 1, 'data': 123456}) |
2.当认证类返回none时
| |
| class Authentication(BaseAuthentication): |
| def authenticate(self, request): |
| return Nnoe |
| def authenticate_header(self, request): |
| return 'Bearer realm=API' |
| |
| class PayView(APIView): |
| |
| authentication_classes = [Authentication,认证2,认证3,认证4 ] |
| |
| 注意: |
| 如果每一个认证类都返回none时 |
| request.user 用户对象就是一个匿名用户 AnoymousUser |
| request.auth None |
| |
| 可以在配置文件中设置 |
| REST_FRAMEWORK = { |
| |
| 'UNAUTHENTICATED_USER':lambda:None, |
| 'UNAUTHENTICATED_TOKEN':lambda:None, |
| } |
| 如果认证返回none那么在对request.user赋值 none request.auth 赋值none |
| |
| |
| |
| |
| def authenticate(self, request): |
| a = '当前用户从数据库中查到的认证token值' |
| token = request.headers.get('Token') |
| if not token: |
| return Nnoe |
| if not token == a: |
| raise AuthenticationFailed({'code': 1002, 'msg': '认证失败'}) |
| |
| class PayView(APIView): |
| |
| authentication_classes = [Authentication,认证2,认证3,认证4 ] |
| |
| def get(self,request,...) |
| if not request.user: |
| return {'code':200,'data':[11,22,33]} |
| return {'code':200,'data':[110,220,330]} |
| |
| |
| 认证: 返回的结果最新的 |
| 未认证: 返回结果是旧记录 |
| 也就是登录和未登录的区别,像商城一样 |
3.认证的全局配置
| |
| REST_FRAMEWORK = { |
| 'DEFAULT_AUTHENTICATION_CLASSES':['认证类的路径1','认证类的路径2'] |
| } |
| |
| |
| authentication_classes = [] |
4.源码
| 在内部对原view api类进封装时,对认证类authentication_classes变量进行了内部进行 变量 = [auth() for auth in authentication_classes],将authentication_classes变量中的认证类s全部进行了实例化(如果api类中没有认证类变量,那么就会从基层的APIView中去找,内部定义了一个authentication_classes指向配置文件中的DEFAULT_AUTHENTICATION_CLASSES配置) 封装了 request中 |
| |
| |
| 在 self.initial() 执行后 内部调用了self.perform_authentication(request),内部直行了request.user()方法,内部将进行了调用,执行了 自定义认证类中(父级认证类),重写的authenticate方法... |
| |
| 如果认证成功就会终止return,所以认证类多个没有意义 |
权限
| 权限: |
| 读取认证中获取的用户信息,判断用户是否有权限访问,不同的权限可以访问的接口不同 |
| |
| 用户的id 或者 用户对象怎么让前端携带到后端 |
| 1. 使用 在登录接口时生成一个 关于用户的随机验证码 或者内部进行存储用户id 或者对象的 加密随机码 |
| 2. 使用jwt 将用户的id 进行加密 |
| 等等..... |
| |
| 当获取到用户id时,可以在数据库中进行查看当前的用户是否可以访问 |
| 可以 返回 True |
| 不可以 返回 False |
1.快速使用
| |
| from rest_framework.permissions import BasePermission |
| |
| |
| |
| class BaseP(BasePermission): |
| |
| message = {'code':1002,'msg':'无权访问'} |
| |
| def has_permission(self, request, view): |
| '''编写对用户的权限的判断 具体逻辑自己定义根据什么来进行定义 |
| request : 请求的对象 |
| view : 当前访问的api接口的类 |
| 作用:对当前的访问的url是否有权限 力度小 |
| ''' |
| name = 1 |
| if name == 2: |
| return True |
| else: |
| return False |
| |
| def has_object_permission(self, request, view, obj): |
| ''' |
| request.user :当前用户 |
| obj :当前查询 删除 更新的对象 |
| 在视图类内部如果调用了(单条详细 删除 更新)get_object内部调用了has_object_permission方法 |
| 作用:单条详细查询 删除数据 更新数据 操作中当前用户是否对这条数据有权限 力度大(将权限力度控制到每一条数据权限上) |
| ''' |
| return True |
| |
| |
| |
| class PayView(APIView): |
| |
| permission_classes = [BaseP,权限类2,权限类3 ] |
| |
| def get(self, request, *args, **kwargs): |
| return Response({'code': 1, 'data': 123456}) |
2.权限全局设置
| |
| REST_FRAMEWORK = { |
| 'DEFAULT_PERMISSION_CLASSES':['权限的路径1','权限类的路径2'] |
| } |
| |
| |
| authentication_classes = [] |
3.源码
| 源码中将classes 的全部的权限类加() 进行实例化 循环执行内部has_permission方法进行权限的判断 |
| 如果权限通过那么就可以访问到api 如果权限不通过 |
| |
| 注意: |
| 关于权限类返回的message 变量 |
| message = {'code':1002,'msg':'无权访问'} |
| 源码中认证组件的存在(如果没有认证组件那么就会报错Authentication credentials were not provided.未提供认证用户信息),才会将当前权限为false情况 返回message变量, |
| 因为源码中的这段代码 |
| 需要存在认证类的存在(你认证通过了,但是权限没有通过) |
| |
| if request.authenticators and not request.successful_authenticator: |
| raise exceptions.NotAuthenticated() |
限流
| 简单理解为是一种限制用户访问的内置组件(组件) |
| 限制用户访问频率 |
| |
| 登录用户的访问频率限制 |
| 未登录用户的访问频率限制 使用ip限制 |
| |
| 主要需要利用缓存进行设置 |
| |
| pip install django-redis |
| |
| |
| |
| CACHES = { |
| 'default': { |
| 'BACKEND': 'django_redis.cache.RedisCache', |
| 'LOCATION': 'redis://127.0.0.1:6379', |
| 'OPTIONS': { |
| 'CLIENT_CLASS': 'django_redis.client.DefaultClient', |
| |
| } |
| } |
| } |
1.快速开始
| |
| from rest_framework.throttling import SimpleRateThrottle |
| from django.core.cache import cache as cache_redis |
| from rest_framework import exceptions |
| |
| |
| |
| class ThrottledException(exceptions.APIException): |
| '''自定义的异常类 用来抛出限流类的限流信息''' |
| pass |
| |
| class MyS(SimpleRateThrottle): |
| '''自定义限流类''' |
| cache = cache_redis |
| |
| |
| cache_format = 'throttle_%(scope)s_%(ident)s' |
| scope = 'user' |
| |
| |
| |
| THROTTLE_RATES = {'user': '10/m'} |
| |
| 1.唯一标识 |
| def get_cache_key(self, request, view): |
| '''重写父类的方法作用(返回唯一标识)如果唯一标识需要修改,就在当前方法中进行修改''' |
| if str(request.user) != 'AnonymousUser' and request.user: |
| ident = 1 |
| else: |
| ident = self.get_ident(request) |
| return self.cache_format % {'scope': self.scope, 'ident': ident} |
| |
| 2.当限流时返回限流的结构重构 |
| def throttle_failure(self): |
| '''如果超过当前的限制次数,那么就会调用这个方法,将限制时间返回给前端页面''' |
| wait = self.wait() |
| detail = { |
| 'code': 1005, |
| 'data': '访问限制', |
| 'detail': '需要等待%ss才能访问' % int(wait) |
| } |
| raise ThrottledException(detail) |
| |
| |
| |
| class PayView(APIView): |
| throttle_classes = [MyS, ] |
| def get(self, request, *args, **kwargs): |
| print(cache_redis) |
| return Response({'code': 1, 'data': 123456}) |
| |
| |
| |
| ''' |
| 1.使用的当前的redis进行设置限制信息的存储(存储在redis中的key val 会自动删除)这个比利用中间件方便的多 |
| 2.需要重写 父类中的 get_cache_key方法 主要就是自定义生成一个当前用户的唯一标识 |
| 3.重写父类的 throttle_failure 方法 主要是当达到限制条件就会触发 将结果返回给页面中 |
| 4.需要配置一定的参数 |
| cache 配置对象的参数 |
| cache_format 存储在redis对象中的 key的构造 |
| scope 名称 |
| THROTTLE_RATES 限制条件 {'scope':'限制条件'} 当前的变量需要根据scope进行获取限制条件(所以与scope有关联) |
| ''' |
2.多个限流类
| 多个限流类中都有一个allow_request方法 |
| 返回true 表示当前限流允许访问,接着执行限流类 |
| 返回为falas 表示当前限流类不可以访问 接着执行下述的限流类,将全部的限流类的时间计算在一起 |
| 抛出异常的话 后面的限流类就不会执行 |
| |
| 如果定义多个限流类,让他同一的将限流时间返回需要在接口中定义方法 |
| api 接口中定义 统一返回时间多个限流类中的时间最大的值(不推荐使用,推荐使用上述的在定义的限流类中进行设置) |
| 在api 接口中重新定义的父类api 的方法,原方法 raise exceptions.Throttled(wait) 单纯的抛限流时间最长的值,而重新的方法显示的更为明确 |
| def throttled(self, request, wait): |
| wait 就是当前全部限流类的时间总和 |
| detail = { |
| 'code': 1005, |
| 'data': '访问限制', |
| 'detail': '需要等待%ss才能访问' % int(wait) |
| } |
| raise ThrottledException(detail) |
3.限流的全局配置
| |
| REST_FRAMEWORK = { |
| 'DEFAULT_THROTTLE_CLASSES':['限流类的路径1','限流类的路径2'], |
| 'DEFAULT_THROTTLE_RATES':{ |
| {'user': '10/m'}, user 就是当前 scope变量 标识 |
| .....1, |
| .....2, |
| } |
| } |
| |
| |
| |
| DEFAULT_THROTTLE_CLASSES = [] |
4.源码
| 源码中的 throttle_durations变量就是存储这 循环每一个对象执行.allow_request方法 将限流不通过的 限流类中的.wait()方法将限制时间进行获取.添加到throttle_durations列表中,在判断throttle_durations列表中有没有值,如果没有值什么都不做(说明没有超过频率限制),如果有值就获取当前事件最大的值,在将值 进行异常抛出异常 |
数据效验序列化(serializert)
| 作用: |
| 1.对数据的校验(django 调用的form和modelform) |
| 2.对数据库查到的对象进行序列化 |
| |
| 用户通过请求发来的数据通过serializert 进行格式化的校验 |
1.数据校验
1.开始使用(当前已经覆盖了全部的可能校验的方式) Serializer
| from 表单一样 |
| 配置修改: |
| LANGUAGE_CODE = 'zh-hans' 将表单错误现象变为中文 |
| 查看django2.0中的from表单认证字段全部的内容 |
| |
| 1.serializert自定义的类的编写 |
| from rest_framework import serializers |
| from django.core.validators import EmailValidator |
| from rest_framework import exceptions |
| |
| 2.编写自定义的验证类 |
| |
| class RegexValidator(object): |
| def __init__(self, base): |
| self.base = base |
| |
| def __call__(self, value): |
| import re |
| match_obj = re.match(self.base, value) |
| if not match_obj: |
| raise serializers.ValidationError('邮箱验证失败') |
| |
| |
| |
| class UserSerializers(serializers.Serializer): |
| level_choices = [(0, 'vip'), (1, 'vips'), (2, 'vipss')] |
| |
| |
| level = serializers.ChoiceField(label='角色', choices=level_choices) |
| |
| |
| username = serializers.CharField(label='姓名', min_length=3, max_length=10) |
| |
| |
| age = serializers.IntegerField(label='年龄', min_value=0, max_value=200) |
| |
| |
| email1 = serializers.EmailField(label='邮箱1') |
| |
| |
| email2 = serializers.CharField(label='邮箱2', validators=[EmailValidator, ]) |
| |
| |
| email3 = serializers.CharField(label='邮箱3', validators=[RegexValidator(r'^\w+@\w+\.\w+$')]) |
| |
| email4 = serializers.CharField(label='邮箱4') |
| |
| def validate_email4(self, value): |
| '''email4钩子函数''' |
| import re |
| if re.match(r'^\w+@\w+\.\w+$', value): |
| return value |
| raise exceptions.ValidationError('邮箱格式有误') |
| |
| |
| class Users(APIView): |
| |
| def post(self, request, *args, **kwargs): |
| print(request.data) |
| |
| data_dic = UserSerializers(data=request.data) |
| ''' |
| data_dic.errors 验证失败的字段都存储在这里 |
| data_dic.validated_data 验证成功的结果都在这里 |
| ''' |
| if not data_dic.is_valid(): |
| return Response({'code': 1004, 'error': data_dic.errors}) |
| |
| print(data_dic.validated_data) |
| return Response({'code': 1002, 'mag': data_dic.validated_data}) |
2.验证前端数据ModelSerializer(orm操作)
| 与 modelfrom一样 |
| ModelSerializer 根据数据库的字段进行认证比Serializer 自己编写字段代码要少 |
| |
| 通过数据库表的字段进行认证 |
| 1.表设置 |
| class User(models.Model): |
| |
| username = models.CharField(verbose_name='用户名',max_length=32) |
| pwd = models.CharField(verbose_name='密码',max_length=64) |
| |
| 2.导包 |
| from rest_framework import serializers |
| from rest_framework import exceptions |
| from app import models |
| |
| 3.自定义ModelSerializer |
| |
| class UserSerializers(serializers.ModelSerializer): |
| email4 = serializers.CharField(label='邮箱4') |
| |
| class Meta: |
| model = models.User |
| fields = ['username','email4'] |
| extra_kwargs = { |
| 'username': {'min_length': 6, 'max_length': 32} |
| } |
| |
| def validate_email4(self, value): |
| '''email4钩子函数''' |
| import re |
| if re.match(r'^\w+@\w+\.\w+$', value): |
| return value |
| raise exceptions.ValidationError('邮箱格式有误') |
| |
| 4.在api接口中进行使用 |
| class Users(APIView): |
| |
| def post(self, request, *args, **kwargs): |
| print(request.data) |
| data_dic = UserSerializers(data=request.data) |
| |
| if not data_dic.is_valid(): |
| return Response({'code': 1004, 'error': data_dic.errors}) |
| |
| |
| data_dic.validated_data.pop('email4') |
| |
| |
| user_obj = data_dic.save(pwd='666666') |
| |
| return Response({'code': 1002, 'mag': data_dic.validated_data}) |
| |
| |
| 当前ModelSerializer也是可以进行1对多和1对1的进行存储的只需要将字段给添加就可以 |
| 多:[1,2,3] |
| 1:1 |
| ModelSerializer 具有对第三表进行存储的功能 |
2.orm对象序列化(orm)
1.简单
| 通过orm从数据库中获取到的queryset 或者对象序列化为json格式数据 |
| 1.导入包 |
| from rest_framework import serializers |
| from app import models |
| |
| 2.定义自定类 |
| |
| class UserSerializers(serializers.ModelSerializer): |
| class Meta: |
| model = models.User |
| fields = ['username', 'pwd'] |
| |
| 3.查询api结果进行序列化 |
| class Users(APIView): |
| |
| def get(self, request, *args, **kwargs): |
| '''获取user列表''' |
| queryset = models.User.objects.all() |
| ser = UserSerializers(instance=queryset, many=True) |
| print(ser.data) |
| return Response({'code': 2000, 'data': ser.data}) |
| |
| """ |
| 内部支持 序列化嵌套与字段自定义 |
| """ |
2.1自定义字段
| 1.表结构 |
| class Role(models.Model): |
| '''角色表''' |
| name = models.CharField(verbose_name='角色', max_length=32, null=True) |
| |
| |
| class Depart(models.Model): |
| '''部门表''' |
| title = models.CharField(verbose_name='部门', max_length=32, null=True) |
| |
| |
| class User(models.Model): |
| level_choices = ((1, '普通会员'), (2, 'vip')) |
| level = models.IntegerField(verbose_name='级别', choices=level_choices, null=True) |
| username = models.CharField(verbose_name='用户名', max_length=32) |
| pwd = models.CharField(verbose_name='密码', max_length=64) |
| departs = models.ForeignKey(verbose_name='用户or部门', to=Depart, on_delete=models.CASCADE, null=True, blank=True) |
| roles = models.ManyToManyField(verbose_name='用户 or 角色', to=Role) |
| |
| |
| |
| 2.导包 |
| from rest_framework import serializers |
| from app import models |
| from django.forms.models import model_to_dict |
| |
| 3.自定义序列化类 |
| class UserSerializers(serializers.ModelSerializer): |
| '''source 在序列化器参数字段参数设置(source='表模型字段名') 具有执行当前表对象.字段 获取数据赋值给变量(底层通过反射进行对象.什么内容..) ''' |
| |
| |
| |
| level_text = serializers.CharField( |
| source='get_level_display') |
| |
| |
| |
| depart = serializers.CharField(source='departs.title') |
| |
| |
| roles = serializers.SerializerMethodField() |
| |
| class Meta: |
| model = models.User |
| fields = ['username', 'pwd', 'level_text','depart','roles'] |
| |
| def get_roles(self,obj): |
| '''roles 的钩子方法从对象.tokto进行获取全部的角色信息''' |
| print(obj) |
| data_list = obj.roles.all() |
| return [model_to_dict(item,['id','name']) for item in data_list] |
| |
| |
| class Users(APIView): |
| |
| def get(self, request, *args, **kwargs): |
| '''获取user列表''' |
| queryset = models.User.objects.all() |
| ser = UserSerializers(instance=queryset, many=True) |
| print(ser.data) |
| return Response({'code': 2000, 'data': ser.data}) |
3.序列化类嵌套
| 针对表和表之间的关联 fk m2m |
| |
| class Role(models.Model): |
| '''角色表''' |
| name = models.CharField(verbose_name='角色', max_length=32, null=True) |
| |
| |
| class Depart(models.Model): |
| '''部门表''' |
| title = models.CharField(verbose_name='部门', max_length=32, null=True) |
| |
| |
| class User(models.Model): |
| level_choices = ((1, '普通会员'), (2, 'vip')) |
| level = models.IntegerField(verbose_name='级别', choices=level_choices, null=True) |
| username = models.CharField(verbose_name='用户名', max_length=32) |
| pwd = models.CharField(verbose_name='密码', max_length=64) |
| departs = models.ForeignKey(verbose_name='用户or部门', to=Depart, on_delete=models.CASCADE, null=True, blank=True) |
| roles = models.ManyToManyField(verbose_name='用户 or 角色', to=Role) |
| |
| |
| from rest_framework import serializers |
| from app import models |
| from django.forms.models import model_to_dict |
| |
| |
| |
| 定义部门学序列化类 |
| class DepartModel(serializers.ModelSerializer): |
| class Meta: |
| model = models.Depart |
| fields = '__all__' |
| |
| |
| 定义角色序列化类 |
| class RolesModel(serializers.ModelSerializer): |
| class Meta: |
| model = models.Role |
| fields = '__all__' |
| |
| |
| 定义显示的user表中的序列化类 |
| class UserSerializers(serializers.ModelSerializer): |
| '''source 在序列化器参数字段参数设置(source='表模型字段名') 具有执行当前表对象.字段 获取数据赋值给变量(底层通过反射进行对象.什么内容..) ''' |
| |
| |
| |
| level_text = serializers.CharField( |
| source='get_level_display') |
| |
| departs = DepartModel() |
| roles = RolesModel(many=True) |
| |
| class Meta: |
| model = models.User |
| fields = ['username', 'pwd', 'level_text','departs','roles'] |
| |
| |
| |
| class Users(APIView): |
| |
| def get(self, request, *args, **kwargs): |
| '''获取user列表''' |
| queryset = models.User.objects.all() |
| ser = UserSerializers(instance=queryset, many=True) |
| print(ser.data) |
| return Response({'code': 2000, 'data': ser.data}) |
3.序列化和数据校验 整合在一起使用
| 导包 |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| |
| from rest_framework import serializers |
| from app import models |
| |
| |
| 序列化数据校验 |
| |
| class DepartModel(serializers.ModelSerializer): |
| class Meta: |
| model = models.Depart |
| fields = '__all__' |
| |
| |
| |
| class RolesModel(serializers.ModelSerializer): |
| class Meta: |
| model = models.Role |
| fields = '__all__' |
| |
| |
| |
| class UserSerializers(serializers.ModelSerializer): |
| ''' |
| 在对serializer类对用户前端传入的数据进行验证时 |
| many=False 显示一条 |
| many=True 显示多条 |
| partial=True 代表局部更新(不用强制性将serializers中的全部字段填写) |
| |
| 设置serializer中字段设置 |
| read_only=True 不用写 |
| write_only=True 不用读 |
| 如果不设置read_only和write_only 那么当前字段及强制写在查询时必须显示 |
| ''' |
| |
| level_text = serializers.CharField( |
| source='get_level_display', read_only=True) |
| |
| |
| |
| departs = DepartModel(many=False, read_only=True) |
| roles = RolesModel(many=True, read_only=True) |
| email = serializers.EmailField(write_only=True) |
| |
| class Meta: |
| model = models.User |
| fields = ['username', 'pwd', 'level_text', 'departs', 'roles', 'email'] |
| extra_kwargs = { |
| 'pwd': {'read_only': True} |
| } |
| |
| def validated_username(self, val): |
| '''对name字段的验证''' |
| return val |
| |
| 接口 |
| class Users(APIView): |
| |
| def get(self, request, *args, **kwargs): |
| '''获取用户列表''' |
| queryset = models.User.objects.all() |
| ser = UserSerializers(instance=queryset, many=True) |
| print(ser.data) |
| return Response({'code': 2000, 'data': ser.data}) |
| |
| def post(self, request, *args, **kwargs): |
| '''添加用户''' |
| res = UserSerializers(data=request.data) |
| if not res.is_valid(): |
| return Response({'code': 1004, 'data': res.errors}) |
| |
| res.validated_data.pop('email') |
| instance = res.save(pwd='123') |
| print(instance) |
| |
| return Response({'code': 1002, 'data': res.data}) |
4.源码
| 序列化部分: |
| 在内部当进行实例化时会根据参数many=True and False 进行条件生成 |
| 如果只获取一个对象,那么直接就会进行序列化 |
| 如果是多个对象(多个值),那么就会调用many_init() 方法将每一个对象进行循环序列化 |
| 在调用的 data方法就是执行了将instance=queryset对象的全部的值进行循环获取对应的参数 |
| to_representation 内部进行处理将将数据从数据库中进行获取,整理为字典形式返回data 方法 |
| 对象.data 就是获取校验过的正确被序列化的值 |
| |
| 数据校验: |
| is_valid内部的校验都是由run_validation进行校验完成的 |
| 内部的to_internal_value 方法是对自定义的钩子函数进行校验(内置的校验规则) |
| run_validators 是对字段中定义validators 进行的校验 |
| |
| |
| 一种承包商的感觉 |
| 将大的步骤分给了小的承包商进行一步一步的进行操作 |
视图
| 是对APIviwe的一层封装 |
| APIview 继承了django view |
| 新增了 : 免除csrf 请求封装 版本 认证 权限 限流 数据校验数据序列化 |
1.GenericAPIview(不是重点)
| class GenericAPIview(APIview): |
| pass |
| GenericAPIview 当前在apivie基础上添加了一些功能 get_queryset get_object |
| |
| 1. 导入方式 |
| from rest_framework.generics import GenericAPIView |
| |
| |
| 2.新增的方法 |
| from rest_framework.generics import GenericAPIView |
| from rest_framework.response import Response |
| from app import models |
| from rest_framework import serializers |
| |
| class MySerializers(serializers.ModelSerializer): |
| class Meta: |
| model = models.User |
| fields = '__all__' |
| |
| class Users(GenericAPIView): |
| queryset = models.User.objects.filter(status=True) |
| serializer_class = MySerializers |
| |
| def get(self, request, *args, **kwargs): |
| |
| queryset = self.get_queryset() |
| |
| |
| |
| ser = self.get_serializer(intance=queryset, many=True) |
| |
| print(ser.data) |
| return Response({'code': 2000, 'data': 111}) |
| |
| |
| |
| 3.作用 |
| ''' |
| GenericAPIView 的主要作用就是 |
| - 从数据库中获取数据 |
| - 对序列化类进行序列化 |
| 将变量提取到类类变量中,方便定义,如果调用了get_serializer get_queryset 就会进行读取 |
| 不会直接继承GenericAPIView |
| 作为一个中间人的作用,完成一些功能,方便继承它的子类调用它新增的方法 |
| 提供了get_object 也就是在数据库对象.get(id=1) 的单条数据查询 |
| ''' |
2.GenericViewSet
| 主要的作用就是将方法拆开,独立的实现当前方法自己功能 查看一条就是一条数据 更新就是更新 删除就是删除 |
| |
| 1.导入 |
| from rest_framework.viewsets import GenericViewSet |
| 内部继承了GenericAPIview 和 ViewSetMixin |
| |
| 2.新增加的功能 |
| ViewSetMixin 类的主要作用就可以在url中写上对应关系 |
| |
| |
| urls.py: url |
| |
| path('api/<str:version>/user/', views.Users.as_view()), |
| |
| path('api/<str:version>/user/<int:pk>', views.Users.as_view()), |
| |
| views.py:视图 每一个方法都可能对应了两个逻辑,单条处理函数多条处理 |
| from rest_framework.views import APIView |
| |
| class Users(APIView): |
| def get(self, request, *args, **kwargs,pk=None): |
| if pk: |
| .... |
| return Response({'code': 2000, 'data': 111}) |
| def post(self, request, *args, **kwargs): |
| return Response({'code': 2000, 'data': 111}) |
| def put(self, request, *args, **kwargs,pk=None): |
| if pk: |
| .... |
| return Response({'code': 2000, 'data': 111}) |
| def delete(self, request, *args, **kwargs,pk=None): |
| if pk: |
| .... |
| return Response({'code': 2000, 'data': 111}) |
| |
| |
| |
| 主要的作用就是将方法拆开,独立的实现当前方法自己功能 查看一条就是一条数据 更新就是更新 删除就是删除 |
| |
| url: |
| |
| path('api/<str:version>/user/', views.Users.as_view({ |
| 'get': 'list', |
| 'post': 'create' |
| })), |
| |
| path('api/<str:version>/user/<int:pk>', views.Users.as_view({ |
| 'get':'retrieve', |
| 'put':'update', |
| 'dalete':"destory" |
| })), |
| |
| api接口类 |
| |
| from rest_framework.viewsets import GenericViewSet |
| |
| class Users(GenericViewSet): |
| |
| def list(self, request): |
| return Response({'code': 2000, 'data': 111}) |
| |
| def create(self, request): |
| return Response({'code': 2000, 'data': 111}) |
| |
| def retrieve(self, request, pk): |
| return Response({'code': 2000, 'data': 111}) |
| |
| def update(self, request,pk): |
| return Response({'code': 2000, 'data': 111}) |
| |
| def destory(self, request, pk): |
| return Response({'code': 2000, 'data': 111}) |
3.五大视图(重点)
| 1.导入方式 |
| from rest_framework.mixins import (ListModelMixin, CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin) |
| |
| 2.每个类都实现了什么(增删改查) |
| |
| ''' |
| 这5大视图内部帮助我们进行了增删改查的一系列的操作 |
| 前提需要使用GenericViewSet类在url中设置对应关系 |
| GenericViewSet 继承了GenericAPIview 和 ViewSetMixin两个类 |
| 而这5大类在内部操作时都离不开两个类变量 |
| queryset 与 serializer_class 类变量(这两个类变量是有GenericViewSet父类进行提供的) |
| 主要根据这两个类变量进行获取数据查询和数据验证操作 |
| ''' |
| 1.ListModelMixin 实现了.../url get方式 获取全部的数据列表 |
| 内部实现了list方法 |
| 2.RetrieveModelMixin 实现.../url/1/ get方式 获取单条数据 |
| 内部实现了retrieve方法 |
| 3.DestroyModelMixin 实现.../url/1/ delete方法 get方式 单条删除 |
| 内部实现了destroy方法 |
| 4.CreateModelMixin 实现 .../url post方式 对数据的增加 |
| 内部实现了create方法 将数据进行增加 |
| ''' |
| 1.用户提交的数据 request.data |
| 2.调用serializers进行数据校验 |
| 3.保存到数据库(如果数据不够多的情况下,在save替用户补充数据) |
| 4.在serializers中如果fields中有id字段(在添加时id不用添加 默认时 read_only=True) |
| ''' |
| 在类中存在一个钩子方法,这个方法可以在自己类中进行重写,替用户补充数据 |
| def perform_create(self, serializer): |
| serializer.save() |
| 将数据提交到后台,保存到数据库,并且将添加的数据信息返回给前端 |
| |
| 5.UpdateModelMixin 实现了.../url/1/ patch请求和put请求 局部更新和全部更新 |
4.对5大视图类重写父类中的方法
| 1.假设如果 list和retrieve 需要使用的serializer序列化类 与 create 使用的序列化类不一样,怎么办? |
| |
| |
| |
| 需要编写两个serializer类在不同条件下进行返回 例如 |
| def get_serializer_class(self): |
| '''重写父类的方法,解决使用不同的serializer序列化类 |
| post 请求使用的serializer序列化类 |
| 与 |
| get delete 使用的不同就可以重写这个方法加上一层判断 |
| ''' |
| if self.request.method == 'POST': |
| return MySerializers |
| return MySerializers2 |
| |
| |
| 2.如果用在进行post请求添加数据时数据不够多的情况下,在save替用户补充数据 |
| |
| def perform_create(self, serializer): |
| ''' |
| CreateModelMixin类中的create方法调用的perform_create保存方法 |
| 解决如果数据不够多的情况下,在save替用户补充数据 |
| ''' |
| serializer.save(departs=1) |
| |
| 3.如果用户进行更新,需要对用户更新一些其他的信息,可以重写perform_update |
| |
| def perform_update(self, serializer): |
| ''' |
| UpdateModelMixin 中的 perform_update |
| 可以每次更新时可以进行为用户更新一些字段 |
| 如果不重写,那么用户更新什么就写什么 |
| ''' |
| serializer.save(tiem=time) |
5.多视图怎么使用区分
| 1.接口与数据库没有操作:使用APIview |
| 2.接口中需要对数据库进行增删改查 使用5大视图因为内部已经进行增删改查 |
| 利用视图中提供的钩子自定义完善功能 |
| 重写某个方法完善功能 |
6.5大视图url编写和视图编写
| url.py: |
| |
| |
| |
| path('api/<str:version>/user/', views.Users.as_view({ |
| 'get': 'list', http://127.0.0.1:8000/api/v1/user/ 发get请求获取全部列表 |
| 'post': 'create' http://127.0.0.1:8000/api/v1/user/ 发post请求加上参数添加一条数据 |
| })), |
| |
| path('api/<str:version>/user/<int:pk>/', views.Users.as_view({ |
| 'get': 'retrieve', http://127.0.0.1:8000/api/v1/user/1/ 发get请求获取id=1数据 |
| 'put': 'update', http://127.0.0.1:8000/api/v1/user/1/ 发put请求更新id=1数据 |
| 'delete': "destroy", http://127.0.0.1:8000/api/v1/user/1/ 发destroy请求删除id=1数据 |
| 'patch':'partial_update' http://127.0.0.1:8000/api/v1/user/1/ 发put请求更新id=1局部数据 |
| })), |
| |
| 注意: |
| 使用这种方式需要继承GenericViewSet类,5大视图内部使用的方法也是路由也是使用路由对应关系的,如果想使用5大视图急需要和GenericViewSet进行配合使用 |
| |
| |
| view.py |
| ''' |
| put 全部更新 (serializers提供的全部字段,必须提供全,不然就会报错某个字段时必填项) |
| partial_update 局部更新 (serializers提供部分字段,不用全部提供,少提供了也不会报错) |
| ''' |
| |
| from rest_framework.viewsets import GenericViewSet |
| from app import models |
| from rest_framework import serializers |
| from rest_framework.mixins import (ListModelMixin, CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, |
| UpdateModelMixin) |
| |
| |
| class MySerializers(serializers.ModelSerializer): |
| class Meta: |
| model = models.User |
| fields = ['id', 'username', 'pwd'] |
| |
| |
| class MySerializers2(serializers.ModelSerializer): |
| class Meta: |
| model = models.User |
| fields = ['id', 'username', 'pwd'] |
| |
| |
| class Users(GenericViewSet, |
| ListModelMixin, |
| RetrieveModelMixin, |
| DestroyModelMixin, |
| CreateModelMixin, |
| UpdateModelMixin): |
| |
| queryset = models.User.objects.all() |
| serializer_class = MySerializers |
| |
| def perform_create(self, serializer): |
| ''' |
| CreateModelMixin类中的create方法调用的perform_create保存方法 |
| 解决如果数据不够多的情况下,在save替用户补充数据 |
| ''' |
| serializer.save(departs=1) |
| |
| def get_serializer_class(self): |
| '''重写GenericViewSet父类的方法,解决使用不同的serializer序列化类 |
| post 请求使用的serializer序列化类 |
| 与 |
| get delete 使用的不同就可以重写这个方法加上一层判断 |
| ''' |
| if self.request.method == 'POST': |
| return MySerializers |
| return MySerializers2 |
| |
| def perform_update(self, serializer): |
| ''' |
| UpdateModelMixin 中的 perform_update |
| 可以每次更新时可以进行为用户更新一些字段 |
| 如果不重写,那么用户更新什么就写什么 |
| ''' |
| serializer.save() |
| |
| |
| 注意: |
| 1.使用5大视图(5大视图就是针对这增删改查)需要url进行对应关系,需要继承GenericViewSet类 |
| 2.使用5大视图 需要用到queryset对象 和 serializer对象 也需要继承GenericViewSet类来提供 |
| 3.使用5大视图可以重写内部的钩子方法,增加业务的需求量 |
| 4. 切记编写url对应关系 与 queryset对象 和 serializer对象 |
搜索条件filter
| 通过get进行查询,进行返回 |
| api/v1/user?a=1&b=10 |
| |
| 使用这个搜索组件需要使用到 |
| 类变量:queryset 和 serializer_class |
| 那么就需要使用到 GenericViewSet 使用到这个类就需要使用到 url映射关系 |
| 需要设置url映射关系,就要使用到5大视图 |
| |
| 所以后面的全部组件都和五大视图与路由的映射关系都有关 |
自定义Filter
| 用的比较少,项目比较小时进行使用 |
| |
| 1.导入模块 |
| from rest_framework.filters import BaseFilterBackend |
| |
| 2.编写自定义的fiter组件 |
| 继承BaseFilterBackend类 |
| class Filter1(BaseFilterBackend): |
| ''' |
| 1.先读api类中的类变量queryset对象 |
| 2.将queryset对象传给filter_queryset(内部可以加条件) |
| 3.会将第一个类中queryset对象的值在给到filter_backends = [filter1,filter2...] 中的的二个元素以此类推 |
| 4.而第2个类开始,每个filter接收到queryset对象都是上一个传递下来的queryset对象 |
| 5.除了post中的create不会在源码中执行Filter类,其他的5中方式都会执行 |
| 6.其他的api读取到的queryset对象都是由filter类过滤下来的 |
| ''' |
| 例如:http://127.0.0.1:8000/api/v1/user/4/?user_id=4 |
| def filter_queryset(self, request, queryset, view): |
| user_id = request.query_params.get('user_id') |
| if not user_id: |
| return queryset |
| return queryset.filter(id=user_id) |
| |
| |
| 3.在api接口中进行使用 |
| |
| class Users(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin, CreateModelMixin, UpdateModelMixin): |
| queryset = models.User.objects.all() |
| serializer_class = MySerializers |
| |
| filter_backends = [Filter1,filter2,filter3] |
| |
| |
第三方Filter
| 通用的使用方式属于第三方的组件 |
| 1.安装 |
| pip install django-filter |
| |
| 2.在项目配置文件中进行注册 |
| INSTALLED_APPS = [ |
| django_filters |
| ] |
1.第三方搜索案例(简单)
| 1.导入模块 |
| from django_filters.rest_framework import DjangoFilterBackend |
| |
| 2.在api类中设置类属性 |
| class Users(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin, CreateModelMixin, UpdateModelMixin): |
| # 将导入的第三方进行配置到filter_backends变量上 |
| filter_backends = [DjangoFilterBackend] |
| # 查询字段的条件 例如:xxx/?id=1 or xxx/?username='xxx' or xxx/?username='xxx'&id=1 |
| filterset_fields = ['id','username'] |
| |
| queryset = models.User.objects.all() |
| serializer_class = MySerializers |
| # 当进行查询时,就会进行先调用当前filter_backends的filter类调用中的filter_queryset方法进行检索,并将检索过的queryset返回,使用的就是当前检索过的queryset对象进行序列化数据给前端 |
2.第三方搜索(复杂)
| 需要定义自定义的类进行设置搜索条件,使用复杂的搜索 |
| 补充内容: |
| ''' |
| field_name是搜索的字段名字/ lookup_expr搜索的属性 |
| lookup_expr属性常见的选择/构造的条件: |
| exact 等于 |
| iexact 等于 |
| |
| contains 包含 |
| icontains 包含 |
| |
| startswith 以什么开头 |
| istartswith 以什么开头 |
| |
| endswith 以什么结尾 |
| iendswith 以什么结尾 |
| |
| gt 大于等于 |
| gte 大于等于 |
| lt 小于等于 |
| lte 小于等于 |
| |
| in 在什么里面 |
| range is in range |
| isnull 是否为空 |
| |
| regex 正则表达式搜索 |
| iregex |
| ''' |
| |
| 1.导入类 |
| from django_filters.rest_framework import DjangoFilterBackend |
| from django_filters import FilterSet, filters |
| |
| 2.编写复杂的搜索条件类 |
| |
| class MyFilterSet(FilterSet): |
| '''自定义第三方搜索参考了serializers的使用方式 |
| 可以进行自定义字段进行搜索 |
| 例如: |
| ''' |
| |
| |
| |
| min_id = filters.NumberFilter(field_name='id',lookup_expr='gte') |
| |
| class Meta: |
| model = models.User |
| fields = ['id','min_id'] |
| |
| |
| |
| 3.给api进行配置 |
| |
| class Users(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin, CreateModelMixin, UpdateModelMixin): |
| |
| filter_backends = [DjangoFilterBackend] |
| filterset_class = MyFilterSet |
3.复杂搜索
| class MyFilterSet(FilterSet): |
| |
| |
| min_id = filters.NumberFilter(field_name='id',lookup_expr='gte') |
| |
| |
| name = filters.CharFilter(field_name='username',lookup_expr='exact',exclude=True) |
| |
| |
| departs = filters.CharFilter(field_name='departs__title', lookup_expr='contains') |
| |
| |
| |
| level = filters.BooleanFilter(field_name='level',lookup_expr='isnull') |
| |
| |
| |
| levels = filters.MultipleChoiceFilter(field_name='level',lookup_expr='exact',choices=models.User.level_choices) |
| |
| |
| |
| ids = filters.BaseInFilter(field_name='id',lookup_expr='in') |
| |
| |
| |
| range_id = filters.NumericRangeFilter(field_name='id',lookup_expr='range') |
| |
| |
| |
| |
| |
| ord = filters.OrderingFilter(fields=['id']) |
| |
| |
| |
| |
| |
| |
| |
| |
| size = filters.CharFilter(method='filter_size',distinct=False,required=False) |
| |
| class Meta: |
| model = models.User |
| fields = ['id','min_id','name','departs','level','levels','ids','range_id','ord','size'] |
| |
| |
| |
| def filter_size(self,queryset,name,value): |
| ''' |
| :param queryset: 查询的对象 |
| :param name: |
| :param value: url 中的get对应的查询参数 |
| :return: 根据参数处理过的返回的queryset值 |
| ''' |
| int_value = int(value) |
| return queryset[0:int_value] |
内置Filter
| 第三方组件fiter已经将内置的全部的方法给包含了 |
| 内置存在两个 OrderingFilter(排序的filter搜索) SearchFilter(支持模糊搜索) |
| 1.导入包 |
| |
| from rest_framework.filters import OrderingFilter |
| |
| 2.在api中进行配置 |
| |
| class Users(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin, CreateModelMixin, UpdateModelMixin): |
| filter_backends = [OrderingFilter] |
| # 在url中get方式 必须传入的参数就是order 其他参数不行 |
| # ?order=id 正序 |
| # ?order = -id 倒叙 |
| ordering_fields = ['id','username'] |
| |
| |
| # 这个内置的内置的搜索 SearchFilter 还是可能会用到的 |
| 1.导包 |
| from rest_framework.filters import SearchFilter |
| |
| 2.api 配置 |
| class Users(GenericViewSet, ListModelMixin, RetrieveModelMixin, DestroyModelMixin, CreateModelMixin, UpdateModelMixin): |
| filter_backends = [SearchFilter] |
| # 这个方式是模糊搜索, search_fields 根据这设置的字段,会从表中的字段进行搜索 |
| # 例如:?search='wkx' 那么先从id中找,在到username中找 找到就返回queryset对象 |
| search_fields = ['id', 'username'] |
全局配置
| |
| REST_FRAMEWORK = { |
| 'DEFAULT_FILTER_BACKENDS':['搜索filter类路径1','搜索filter类路径2'] |
| } |
分页
| 分页提供了多条方式 3中方式 |
| 如果使用的apiview视图 |
| 那么需要自己手动实例化和调用相关的方法进行分页 |
| 如果使用apiview的派生类 |
| 不需要进行实例和调用,内部已经处理好了 |
继承apiview使用分页
| ******** 第一个PageNumberPagination ******** |
| |
| 1.导包 |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| from rest_framework import serializers |
| from rest_framework.pagination import PageNumberPagination |
| |
| 2.在api中进行使用 |
| class MySerializers(serializers.ModelSerializer): |
| class Meta: |
| model = models.User |
| fields = ['id', 'username', 'pwd'] |
| |
| |
| class Users(APIView): |
| def get(self, request, *args, **kwargs): |
| queryset = models.User.objects.all().order_by('id') |
| |
| # 2.实例化分页类 |
| page = PageNumberPagination() |
| # 3.调用分页类中的paginate_queryset方法将queryset传入返回一个分完页queryset对象 |
| paginaet_queryset = page.paginate_queryset(queryset, request, self) |
| # 4.进行校验(完成后就是当前根据url get传入参数的分页的对象显示在前端) |
| ser = MySerializers(instance=paginaet_queryset, many=True) |
| return Response(ser.data) |
| |
| 3.url怎么传入分页数据的(如果没有对PageNumberPagination进行重新设置)那么默认get参数就是page |
| 当配置上当前的分页组件后,http://127.0.0.1:8000/api/v1/user/ 访问也是第一页的数据 |
| http://127.0.0.1:8000/api/v1/user/?page=1 # 当前就是访问第一页 |
| |
| 4.怎么设置分页显示的数量需要写配置文件 |
| REST_FRAMEWORK = { |
| # 配置当前参数,当前进行传入url get参数时显示的的数据数量 |
| 'PAGE_SIZE':2 |
| } |
| 上面的基本用法都是固定设置设置的,如何控制最大方便的开放用户的使用 让用户设置显示多少条数据 |
| |
| |
| from rest_framework.pagination import PageNumberPagination |
| |
| |
| |
| url/?page=1&size=10 |
| class MyPageNumberPagination(PageNumberPagination): |
| page_size = 2 |
| page_size_query_param = 'size' |
| max_page_size = 100 |
| |
| |
| |
| 传入参数的方式是limit的方式,从querset中获取limit指定的数量 |
| url?limit=2 # 那么当前就是从querset中获取2条数据 和sql中的limit性质是一样的 |
| url?limit=2&offset=1 # 那么就是从offset定位到querset的第一条数据往后获取两条 |
| |
| # 1.导入类 |
| from rest_framework.pagination import LimitOffsetPagination |
| from app import models |
| from rest_framework import serializers |
| from rest_framework.views import APIView |
| from rest_framework.response import Response |
| |
| # 在api中进行使用 |
| class MySerializers(serializers.ModelSerializer): |
| class Meta: |
| model = models.User |
| fields = ['id', 'username', 'pwd'] |
| |
| |
| class Users(APIView): |
| def get(self, request, *args, **kwargs): |
| queryset = models.User.objects.all().order_by('id') |
| |
| # 2.实例化分页类 |
| page = LimitOffsetPagination() |
| # 3.调用当前的分页方法(处理queryset对象) |
| paginaet_queryset = page.paginate_queryset(queryset, request, self) # 返回分完的queryset |
| print(paginaet_queryset) |
| # 4.进行校验 |
| ser = MySerializers(instance=paginaet_queryset, many=True) |
| return Response(ser.data) |
| |
| |
| 3.在配置文件中设置的参数 |
| REST_FRAMEWORK = { |
| # 配置当前参数,当前进行传入url get参数时显示的的数据数量 |
| 'PAGE_SIZE':2 默认limit的显示多少条数据 |
| } |
| CursorPagination不能直接使用,需要自定义类进行配置使用 |
| |
| 1.导入类 |
| from rest_framework.pagination import CursorPagination |
| from app import models |
| from rest_framework import serializers |
| from rest_framework.views import APIView |
| |
| 2.自定义类 |
| class MyCursorPagination(CursorPagination): |
| ordering = 'id' |
| page_size_query_param = 'size' |
| page_size = 2 |
| max_page_size = 100 |
| |
| 3.api类中进行使用 |
| class MySerializers(serializers.ModelSerializer): |
| class Meta: |
| model = models.User |
| fields = ['id', 'username', 'pwd'] |
| |
| |
| class Users(APIView): |
| def get(self, request, *args, **kwargs): |
| queryset = models.User.objects.all().order_by('id') |
| |
| |
| page = MyCursorPagination() |
| |
| paginaet_queryset = page.paginate_queryset(queryset, request, self) |
| |
| ser = MySerializers(instance=paginaet_queryset, many=True) |
| |
| return page.get_paginated_response(ser.data) |
| |
| |
| 注意: |
| http://127.0.0.1:8000/api/v1/user/?cursor=1 |
| 会返回 next=url 上一页的url previous= url 下一页的url,只能访问给你url,按照drf的规则进行访问上一页和下一页内容(不能自己去cursor 随意去写) |
| |
| 例如: |
| { |
| "next": "http://127.0.0.1:8000/api/v1/user/?cursor=cD0z", |
| "previous": null, |
| "results": [ |
| { |
| "id": 2, |
| "username": "嘿嘿", |
| "pwd": "12sss3" |
| }, |
| { |
| "id": 3, |
| "username": "wkxxxxx", |
| "pwd": "123" |
| } |
| ] |
| } |
继承派生类使用分页
| 主要通过GenericViewSet中间商类进行实现的效果 |
| 1.需要进行自定义分页类 |
| 2.在api中进行使用 |
| 1.导入包 |
| from rest_framework.viewsets import GenericViewSet |
| from app import models |
| from rest_framework import serializers |
| from rest_framework.mixins import ListModelMixin |
| from rest_framework.pagination import PageNumberPagination |
| from collections import OrderedDict |
| from rest_framework.response import Response |
| |
| 2.定义自定义的分页类 |
| class MyCursorPagination(PageNumberPagination): |
| |
| page_size_query_param = 'size' |
| page_size = 10 |
| max_page_size = 100 |
| |
| def get_paginated_response(self, data): |
| '''可以重写分页父类中的方法,根据自定义进行返回数据内容''' |
| ''' |
| data :返回的数据 |
| ''' |
| return Response(OrderedDict([ |
| ('code',1002), |
| ('count', self.page.paginator.count), |
| ('next', self.get_next_link()), |
| ('previous', self.get_previous_link()), |
| ('results', data) |
| |
| ])) |
| |
| 3.api类使用 |
| class MySerializers(serializers.ModelSerializer): |
| '''序列化类''' |
| class Meta: |
| model = models.User |
| fields = ['id', 'username', 'pwd'] |
| |
| |
| class Users(ListModelMixin, GenericViewSet): |
| |
| queryset = models.User.objects.all().order_by('-id') |
| serializer_class = MySerializers |
| |
| |
| ''' |
| { |
| "count": 6, |
| "next": null, |
| "previous": null, |
| "results": [数据] |
| } |
| ''' |
| pagination_class = MyCursorPagination |
| |
| 4.全局配置 |
| REST_FRAMEWORK = { |
| 'DEFAULT_PAGINATION_CLASS':'分页的类的路径' |
| } |
路由
| 1.继承apiview使用的是传统的方式使用 |
| path('api/<str:varsion>/user', views.Users.as_view()) |
| |
| 2.继承派生类的路由需要填写对应关系 |
| path('api/<str:version>/user/', views.Users.as_view({'get': 'list'})), |
| |
| 3.drf 内部的自动生成路由的组件(仅对派生类启作用) |
| 1.导包 |
| from rest_framework import routers |
| 2.实例化生成对应接口 |
| |
| router = routers.SimpleRouter() |
| |
| router.register(r'api/user', views.Users) |
| |
| 3.加入到路由中 |
| urlpatterns = [] |
| urlpatterns += router.urls |
| |
| 4.使用路由分发 |
| router = routers.SimpleRouter() |
| |
| router.register(r'user', views.Users) |
| urlpatterns = [ |
| path('api/',include(router.urls)) |
| ] |
解析器
| from rest_framework.parsers import JSONParser,FormParser,MultiPartParser,FileUploadParser |
| |
| JSONParser: 必须携带 application/json请求头 |
| FormParser: 必须携带 application/x-www-form-urlencoded 表单的请求头 |
| MultiPartParser 是multipart/form-data 请求头 可以携带文件也可以携带数据 |
| FileUploadParser 只能上传文件,不能上传数据 需要进行上传2进制文件 需要对请求头进行修改 |
| Content-Type : */* |
| Content-Disposition : attachment;filename=文件名.jpg |
| |
| from rest_framework.response import Response |
| from rest_framework.views import APIView |
| from rest_framework.parsers import JSONParser, FormParser, MultiPartParser,FileUploadParser |
| |
| |
| class Users(APIView): |
| |
| parser_classes = [JSONParser, FormParser, MultiPartParser] |
| |
| def get(self, request, *args, **kwargs): |
| print(request.content_type) |
| print(request.data) |
| return Response({'data': 1001}) |
| |
| 默认解析器: |
| JSONParser, FormParser, MultiPartParser |
| |
| 如果需要处理特殊的格式,可以继承 |
| 内部BaseParser进行解析自定义类进行特殊格式的解析 |
备注
| 关于下列4中组件只需要配置一次就可以 |
| 1.版本控制 |
| 2.用户认证 |
| 3.权限 |
| 4.限流 |
| |
| 常用 |
| 数据校验serializer组件 |
| 视图组件 |
| 搜索组件 |
| |
关闭调试功能
| 使用rest_framework 一定要在django app程序中进行注册,不然会报错 |
| 尽量的使用序列化器,非常好用,认证渲染都可以。底层继承了modelfrom类 |
| |
| |
| |
| REST_FRAMEWORK = { |
| 'DEFAULT_RENDERER_CLASSES':('rest_framework.renderers.JSONRenderer', ) |
| } |
| |
API处理响应数据的工具类
| 通过类的dict属性,可以将init初始化的变量整合为一个字段进行返回 |
| |
| |
| class BaseResponse: |
| |
| def __init__(self): |
| self.code = None |
| self.data = None |
| self.error = None |
| |
| @property |
| def dict(self): |
| '''__dict__ 会将全部的初始化属性当成一个字典返回''' |
| return self.__dict__ |
| |
| res = BaseResponse() |
| res.dict |
| |
| |
| |
| def post(self, request, *args, **kwargs): |
| res = BaseResponse() |
| ser_obj = RegisterSerializers(data=request.data) |
| if ser_obj.is_valid(): |
| ser_obj.save() |
| res.code = 200 |
| res.data = ser_obj.data |
| else: |
| res.code = 401 |
| res.error = ser_obj.errors |
| |
| return Response(res.data) |
| |
| |
| |
个人配置设置
| 1.创建一个专门存放配置信息的(mysql,redis) |
| local_settings.py |
| 例如存放 数据库的信息和缓存信息(数据库的密码与缓存的密码信息) |
| |
| DATABASES = { |
| 'default': { |
| 'ENGINE': 'django.db.backends.sqlite3', |
| 'NAME': BASE_DIR / 'db.sqlite5', |
| } |
| } |
| |
| 2.在django的配置文件中导入当前文件原来的配置文件的配置覆盖 |
| |
| try: |
| from .local_settings import * |
| except ImportError: |
| pass |
| |
| |
链接mysql
| |
| django3 比较支持 mysqlclient |
| |
| pip install mysqlclient |
| |
| 创建数据库时一定设置数据的字节码,不然存放中文报错 |
| CREATE DATABASE 数据库名字 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; |
可以选择注销没有的APP

对QueryDict值进行修改
| data = request.data |
| _mutable = data._mutable |
| data._mutable = True |
| data['excerpt'] = 'chenxinming' |
| data._mutable = _mutable |
| |
| summary = re.sub(r'<.*?>', u'', data['content'])[0:400] |
| ''.join(summary.split()).replace(' ','') |
分离模式下用户认证
| 前后端分离没有办法像前后端不分离一样进行将用户登录的状态存放在session或者cookie中,生成特殊的token进行记录,证明用户登录状态。 |
| |
| 1.将token(令牌)写到redis中 |
| uuid 或者 token携带或者token 写的如redis中 |
| 将token令牌放到redis中,再将token放在请求头中,每次携带进行认证。 |
| 2.将toekn(令牌)写在数据库中 |
| |
| 3.将token(令牌)携带到请求头中,每次都会携带 |
| jwt携带 最简单的一种方式 |
| |
| 代码案例: |
| 基于redis请求池存放token并且认证token的基于djangoAPI的认证组件 |
| conn = redis.Redis(connection_pool=POOL) |
| class MyAuth(BaseAuthentication): |
| def authenticate(self, request): |
| |
| token = request.META.get('HTTP_AUTHENTICATE', '') |
| |
| if not token: |
| raise AuthenticationFailed('没有携带token') |
| user_id = conn.get(str(token)) |
| |
| if user_id is None: |
| raise AuthenticationFailed('token已经过期') |
| |
| user_obj = Account.objects.filter(id=user_id).first() |
| |
| return user_obj, token |
| |
| |
| |
| 在登录时,登录通过 |
| 1.将 用户的id和token存放在redis中 |
| token : id |
| 会将token返回给前端 |
| 2.前端会在请求头中携带token,在认证时 |
| 通过携带的token从redis中获取redis存放的token, |
| 将获取的用户id在从mdoel中获取对象 |
| 组件返回 |
| |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人