Django Rest Framework
什么是rest framework?
|
1
|
rest framework是django中的一个组件,帮助我们开发一些符合restful规范的api接口 |
restful协议回顾
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
---- 一切皆是资源,操作只是请求方式----book表增删改查 /books/ books /books/add/ addbook /books/(\d+)/change/ changebook /books/(\d+)/delete/ delbook ----book表增删改查 /books/ -----get books ----- 返回当前所有数据 /books/ -----post books ----- 返回提交数据 /books/(\d+)-----get bookdetail ----- 返回当前查看的单条数据 /books/(\d+)-----put bookdetail ----- 返回更新数据 /books/(\d+)-----delete bookdetail ----- 返回空 class Books(View): def get(self,request): pass # 查看所有书籍 def post(self,request): pass # 添加书籍 class BooksDetail(View): def get(self,request,id): pass # 查看具体书籍 def put(self,request,id): pass # 更新某本书籍 def delete(self,request,id): pass # 删除某本书籍 |
初识rest_framework
models.py
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
from django.db import modelsclass Book(models.Model): title = models.CharField(max_length=32) price = models.IntegerField() pub_date = models.DateField() publish = models.ForeignKey("Publish", on_delete=models.CASCADE) authors = models.ManyToManyField("Author") def __str__(self): return self.titleclass Publish(models.Model): name = models.CharField(max_length=32) email = models.EmailField() def __str__(self): return self.nameclass Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() def __str__(self): return self.name |
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('publishes/', views.PublishView.as_view()),
]
from rest_framework import serializers
from app01.models import *
# 为queryset,model对象做序列化
class PublishSerializers(serializers.Serializer):
name = serializers.CharField()
email = serializers.CharField()
class PublishModelSerializers(serializers.ModelSerializer):
class Meta:
model = Publish
fields = "__all__"
from rest_framework.response import Response
from rest_framework.views import APIView
from app01.models import *
from app01.serilizer import PublishModelSerializers, BookModelSerializers
class PublishView(APIView):
def get(self, request):
# restframework
# 取数据
# print("request.data", request.data)
# print("request.data type", type(request.data))
# print(request._request.GET)
# print(request.GET)
# 序列化
# 方式1:
# publish_list = list(Publish.objects.all().values("name","email"))
# return HttpResponse(json.dumps(publish_list,ensure_ascii=False))
# 方式2:
# from django.forms.models import model_to_dict
# publish_list = Publish.objects.all()
# temp = []
# for obj in publish_list:
# temp.append(model_to_dict(obj))
# return HttpResponse(json.dumps(temp, ensure_ascii=False))
# 方式3:
# from django.core import serializers
# publish_list = Publish.objects.all()
# ret = serializers.serialize("json",publish_list)
# return HttpResponse(ret)
# 序列组件
publish_list = Publish.objects.all()
ps = PublishModelSerializers(publish_list, many=True)
return Response(ps.data)
def post(self, request):
# 取数据
# 原生request支持的操作
# print("POST",request.POST)
# print("body",request.body)
# # print(request)
# print(type(request))
# from django.core.handlers.wsgi import WSGIRequest
# 新的request支持的操作
# print("request.data",request.data)
# print("request.data type",type(request.data))
#
# post请求的数据
ps = PublishModelSerializers(data=request.data)
if ps.is_valid():
print(ps.validated_data)
ps.save() # create方法
return Response(ps.data)
else:
return Response(ps.errors)
Django的原生request:
浏览器 ------------- 服务器
"GET url?a=1&b=2 http/1.1\r\user_agent:Google\r\ncontentType:urlencoded\r\n\r\n"
"POST url http/1.1\r\user_agent:Google\r\ncontentType:urlencoded\r\n\r\na=1&b=2"
request.body: a=1&b=2
request.POST:
if contentType:urlencoded:
# 只有在contentType==urlencoded的时候,request.POST才有数据,数据为字典(将请求体的字节转化为字典)
a=1&b=2----->{"a":1,"b":2}
request._request 原生request
request.data POST数据
request._request.GET GET数据
request.GET GET数据
实现流程
|
1
2
|
执行流程:首先进行路由匹配,匹配到指定的视图类,执行self.dispatch方法,将请求相关信息封装,并经过版本、认证、权限、频率组件之后,将request传入视图函数,并执行视图函数,进行解析,序列化,和分页, 返回response,经过渲染器之后返回页面 |
urls.py
|
1
2
3
4
5
6
|
from django.conf.urls import url, includefrom web.views.api import TestView urlpatterns = [ url(r'^test/', TestView.as_view()),] |
views.py
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
from rest_framework.views import APIViewfrom rest_framework.response import Response class TestView(APIView): def dispatch(self, request, *args, **kwargs): """ 请求到来之后,都要执行dispatch方法,dispatch方法根据请求方式不同触发 get/post/put等方法 注意:APIView中的dispatch方法有好多好多的功能 """ return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return Response('GET请求,响应内容') def post(self, request, *args, **kwargs): return Response('POST请求,响应内容') def put(self, request, *args, **kwargs): return Response('PUT请求,响应内容') |
上述是rest framework框架基本流程,重要的功能是在APIView的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.
"""
# 经过路由和视图之后来到dispatch方法
self.args = args
self.kwargs = kwargs
# 对request进行封装, 读取解析器对请求数据进行解析
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
# self.request._request
# self.request.GET
# self.request.data
try:
# 版本组件,认证组件,权限组件,频率组件
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
# 分发, 执行请求方法对应的视图函数
if 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_allowed
# 分页器、序列器:执行视图函数,对返回结果进行分页,并序列化
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
# 渲染器: 对response进行渲染
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
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 request
neg = 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
# 认证组件
self.perform_authentication(request)
# 权限组件
self.check_permissions(request)
# 频率组件
self.check_throttles(request)
认证与权限组件
|
1
|
http://127.0.0.1:8000/authors/?token=95631b025a67a0d640c33862d1788293 |
局部视图认证
用户url传入的token认证
from django.contrib import admin
from django.urls import path, re_path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^authors/$', views.AuthorModelView.as_view({"get": "list", "post": "create"}), name="author"),
re_path(r'^authors/(?P<pk>\d+)/$',
views.AuthorModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"}), name="detailauthor"),
re_path(r'^login/$', views.LoginView.as_view(), name="login"),
]
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from api import models
class LuffyAuth(BaseAuthentication):
def authenticate(self, request):
"""
用户请求进行认正
:param request:
:return:
"""
token = request.query_params.get('token')
# request._request.GET.get('token')
obj = models.UserAuthToken.objects.filter(token=token).first()
if not obj:
raise AuthenticationFailed({'code':1001,'error':'认证失败'})
# request.user = obj.user.username request.auth = obj
return (obj.user.username, obj)
class AuthorModelView(viewsets.ModelViewSet):
authentication_classes = [TokenAuth, ]
permission_classes = []
throttle_classes = [] # 限制某个IP每分钟访问次数不能超过20次
queryset = Author.objects.all()
serializer_class = AuthorModelSerializers
def get_random_str(user):
import hashlib, time
ctime = str(time.time())
md5 = hashlib.md5(bytes(user, encoding="utf8"))
md5.update(bytes(ctime, encoding="utf8"))
return md5.hexdigest()
from app01.models import User
class LoginView(APIView):
def post(self, request):
self.dispatch
name = request.data.get("name")
pwd = request.data.get("pwd")
user = User.objects.filter(name=name, pwd=pwd).first()
res = {"state_code": 1000, "msg": None}
if user:
random_str = get_random_str(user.name)
token = Token.objects.update_or_create(user=user, defaults={"token": random_str})
res["token"] = random_str
else:
res["state_code"] = 1001 # 错误状态码
res["msg"] = "用户名或者密码错误"
return Response(res)
# return Response(json.dumps(res, ensure_ascii=False))
请求头认证
from django.conf.urls import url, include
from web.viewsimport TestView
urlpatterns = [
url(r'^test/', TestView.as_view()),
]
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.request import Request
from rest_framework import exceptions
token_list = [
'sfsfss123kuf3j123',
'asijnfowerkkf9812',
]
class TestAuthentication(BaseAuthentication):
def authenticate(self, request):
"""
用户认证,如果验证成功后返回元组: (用户,用户Token)
:param request:
:return:
None,表示跳过该验证;
如果跳过了所有认证,默认用户和Token和使用配置文件进行设置
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
(user,token)表示验证通过并设置用户名和Token;
AuthenticationFailed异常
"""
import base64
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if auth:
auth = auth.encode('utf-8')
auth = auth.split()
if not auth or auth[0].lower() != b'basic':
raise exceptions.AuthenticationFailed('验证失败')
if len(auth) != 2:
raise exceptions.AuthenticationFailed('验证失败')
username, part, password = base64.b64decode(auth[1]).decode('utf-8').partition(':')
if username == 'alex' and password == '123':
return ('登录用户', '用户token')
else:
raise exceptions.AuthenticationFailed('用户名或密码错误')
def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
return 'Basic realm=api'
class TestView(APIView):
authentication_classes = [TestAuthentication, ]
permission_classes = []
def get(self, request, *args, **kwargs):
print(request.user)
print(request.auth)
return Response('GET请求,响应内容')
def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')
def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')
多个认证规则
from django.conf.urls import url, include
from web.views.s2_auth import TestView
urlpatterns = [
url(r'^test/', TestView.as_view()),
]
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.request import Request
from rest_framework import exceptions
token_list = [
'sfsfss123kuf3j123',
'asijnfowerkkf9812',
]
class Test1Authentication(BaseAuthentication):
def authenticate(self, request):
"""
用户认证,如果验证成功后返回元组: (用户,用户Token)
:param request:
:return:
None,表示跳过该验证;
如果跳过了所有认证,默认用户和Token和使用配置文件进行设置
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER() # 默认值为:匿名用户
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()# 默认值为:None
else:
self.auth = None
(user,token)表示验证通过并设置用户名和Token;
AuthenticationFailed异常
"""
import base64
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if auth:
auth = auth.encode('utf-8')
else:
return None
print(auth,'xxxx')
auth = auth.split()
if not auth or auth[0].lower() != b'basic':
raise exceptions.AuthenticationFailed('验证失败')
if len(auth) != 2:
raise exceptions.AuthenticationFailed('验证失败')
username, part, password = base64.b64decode(auth[1]).decode('utf-8').partition(':')
if username == 'alex' and password == '123':
return ('登录用户', '用户token')
else:
raise exceptions.AuthenticationFailed('用户名或密码错误')
def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
# return 'Basic realm=api'
pass
class Test2Authentication(BaseAuthentication):
def authenticate(self, request):
"""
用户认证,如果验证成功后返回元组: (用户,用户Token)
:param request:
:return:
None,表示跳过该验证;
如果跳过了所有认证,默认用户和Token和使用配置文件进行设置
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER() # 默认值为:匿名用户
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()# 默认值为:None
else:
self.auth = None
(user,token)表示验证通过并设置用户名和Token;
AuthenticationFailed异常
"""
val = request.query_params.get('token')
if val not in token_list:
raise exceptions.AuthenticationFailed("用户认证失败")
return ('登录用户', '用户token')
def authenticate_header(self, request):
"""
Return a string to be used as the value of the `WWW-Authenticate`
header in a `401 Unauthenticated` response, or `None` if the
authentication scheme should return `403 Permission Denied` responses.
"""
pass
class TestView(APIView):
authentication_classes = [Test1Authentication, Test2Authentication]
permission_classes = []
def get(self, request, *args, **kwargs):
print(request.user)
print(request.auth)
return Response('GET请求,响应内容')
def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')
def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')
认证和权限
request.META
{'#ENVTSLOGSHELLEXT4044': '436098784',
'ALLUSERSPROFILE': 'C:\\ProgramData',
'APPDATA': 'C:\\Users\\fei\\AppData\\Roaming',
'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files',
'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files',
'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files',
'COMPUTERNAME': 'HOME-FEI',
'COMSPEC': 'C:\\WINDOWS\\system32\\cmd.exe',
'DJANGO_SETTINGS_MODULE': 'djngo_rest_demo.settings',
'DOCKER_TOOLBOX_INSTALL_PATH': 'D:\\Docker Toolbox',
'FP_NO_HOST_CHECK': 'NO',
'HOMEDRIVE': 'C:',
'HOMEPATH': '\\Users\\fei',
'JAVA_HOME': 'D:\\java',
'LANG': 'zh_CN',
'LOCALAPPDATA': 'C:\\Users\\fei\\AppData\\Local',
'LOGONSERVER': '\\\\MicrosoftAccount',
'NODE_PATH': 'D:\\nodes\\node_global\\node_modules',
'NUMBER_OF_PROCESSORS': '4', 'OS': 'Windows_NT',
'PATH': 'C:\\Program Files (x86)\\Common Files\\Oracle\\Java\\javapath;C:\\Program Files\\MySQL\\MySQL Server 5.7\\bin;D:\\app\\fei\\product\\11.2.0\\dbhome_1\\bin;;:\\Program Files (x86)\\Intel\\iCLS Client\\;C:\\Program Files\\Intel\\iCLS Client\\;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files\\Intel\\Intel(R) Management Engine Components\\DAL;C:\\Program Files (x86)\\Intel\\Intel(R) Management Engine Components\\DAL;C:\\Program Files\\Intel\\Intel(R) Management Engine Components\\IPT;C:\\Program Files (x86)\\Intel\\Intel(R) Management Engine Components\\IPT;C:\\Program Files\\Intel\\WiFi\\bin\\;C:\\Program Files\\Common Files\\Intel\\WirelessCommon\\;C:\\Program Files\\MySQL\\MySQL Utilities 1.6\\;C:\\Program Files\\MySQL\\MySQL Server 5.7\\bin;D:\\Git\\cmd;D:\\postresql\\pg96\\bin;C:\\salt;D:\\Microsoft VS Code\\bin;D:\\nodes\\;D:\\cmd_markdown_win64;D:\\FastStone Capture;D:\\Anaconda\\Library\\mingw-w64\\bin;D:\\Anaconda\\Library\\usr\\bin;D:\\Anaconda\\Library\\bin;D:\\pytho3.6\\Scripts\\;D:\\pytho3.6\\;D:\\Ruby25-x64\\bin;D:\\python3\\;D:\\java\\bin;D:\\java\\jre\\bin;C:\\Program Files\\MySQL\\MySQL Server 5.7\\bin;D:\\python;D:\\Sublime Text 3;D:\\Vim\\vim81;D:\\Redis;D:\\MINGW\\bin;D:\\MongoDb\\bin;D:\\Ruby25-x64\\bin;D:\\mitmiproxy\\bin;D:\\Docker Toolbox;D:\\postresql\\pg96\\bin;D:\\cmder;D:\\python3\\Scripts;D:\\python\\Scripts;D:\\adb;D:\\Graphviz\\bin;D:\\Vim\\vim74;D:\\nodes\\node_cache;D:\\nodes\\node_cache\\node_modules;D:\\nodes\\node_global;D:\\pytho3.6\\lib\\site-packages\\pywin32_system32;D:\\pytho3.6\\lib\\site-packages\\pywin32_system32',
'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.RB;.RBW',
'PROCESSOR_ARCHITECTURE': 'AMD64',
'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 69 Stepping 1, GenuineIntel',
'PROCESSOR_LEVEL': '6',
'PROCESSOR_REVISION': '4501',
'PROGRAMDATA': 'C:\\ProgramData',
'PROGRAMFILES': 'C:\\Program Files',
'PROGRAMFILES(X86)': 'C:\\Program Files (x86)',
'PROGRAMW6432': 'C:\\Program Files',
'PSMODULEPATH': 'C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules\\', 'PUBLIC': 'C:\\Users\\Public', 'PYCHARM_DJANGO_MANAGE_MODULE': 'manage', 'PYCHARM_TRACK_FILES_PATTERN': 'migrations',
'PYTHONDONTWRITEBYTECODE': '1',
'PYTHONIOENCODING': 'UTF-8