DRF入门
Django请求生命周期流程图
下载安装
pip3 install djangorestframework
- pycharm下载
- drf安装默认安装的最新版本,如果django版本过低会自动升级到3.x版本
- 版本支持对应关系
restful规范
前后端交互使用api接口
符合某种规范
restful规范
定义:restful定义了web api接口的设计风格,尤其适用于前后端分离的应用模式中
前后端分离的标准
1.保证数据的安全
API与用户的通信协议,总是使用HTTPs协议。
2.用api关键字标识接口
https://api.example.com 尽量将API部署在专用域名(会存在跨域问题)
https://example.org/api/ API很简单
3.多版本共存---》在接口地址中带版本号
URL 如:https://api.example.com/v1/
请求头 跨域时,引发发送多次请求
4.路径,视网络上任何东西都是资源,均使用名词表示(可复数)
https://api.example.com/v1/animals
5.操作资源由请求方式决定:method
GET:从服务器取出资源(一项或多项)
POST:在服务器新建一个资源
PUT:在服务器更新资源(客户端提供改变后的完整资源)
PATCH:在服务器更新资源(客户端提供改变的属性)
DELETE:从服务器删除资源
6.过滤,通过在url上传参的形式传递搜索条件
https://api.example.com/v1/zoos?limit=10:指定返回记录的数量
https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置
7.状态码
HTTP状态响应码(200、403、404)
json数据自带状态码,服务端自定义的
8.错误处理,应返回错误信息,error当做key。
{
error: "Invalid API key"
}
9.返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范。
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
10.Hypermedia API,RESTful 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"
}}
- HTTP状态响应码
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 - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。 更多看这里:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
序列化和反序列化
API接口开发,最核心最常见的一个过程就是序列化,所谓序列化就是把数据转换格式,序列化可以分两个阶段:
- 序列化:把我们语言识别的数据转换成指定的格式提交给别人(前端)
- 比如python中的字典,列表,对象等转json,xml,prop····
- 反序列化:把别人提供的数据转换成我们所需的格式
- 最常见的比如我们使用json模块来对数据进行处理····
在Djangorestframework中的序列化反序列化又是如何?
- 序列化: 在Django中获取到的数据默认是模型对象(QuerySet对象),但是模型对象数据无法直接提供给前端或别的平台使用,我们需要把数据进行序列化,变成字符串或json数据提供给前端或其他平台;
- 反序列化: 前端或其他平台传入数据到后台,比如是json格式字符串,后端需要存入数据库,需要转换成python中的对象,然后处理存入数据库;
drf快速使用
快速写5个接口
使用Django写五个接口得配5个路由,5个视图函数去处理,现在使用drf不需要了,如下:
-查询所有---》get->http://127.0.0.1:8000/books/
-查询一个---》get->http://127.0.0.1:8000/books/1/
-新增一个---》post->http://127.0.0.1:8000/books/ # 在body中带数据
-修改-----》put,patch--->实际编码中,基本都用put,全量修改;patch改部分
http://127.0.0.1:8000/books/1/ body体中传入修改的数据
-删除一个---》delete-->http://127.0.0.1:8000/books/1/
'''所有的接口都是这五个的变形'''
登录本质就是查询一个,注册本质就是新增一个
'''
在我们使用postman的时候,地址严格写,不能缺少/
'''
- models.py
from django.db import models class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=5,decimal_places=2) authors = models.CharField(max_length=32)
- views.py
from rest_framework.viewsets import ModelViewSet from app import models from app.serializer import BookSerializer class BookView(ModelViewSet): serializer_class =BookSerializer queryset = models.Book.objects.all()
- serializer.py
from rest_framework.serializers import ModelSerializer from app import models class BookSerializer(ModelSerializer): class Meta: model = models.Book fields = '__all__'
- urls.py
from rest_framework.routers import SimpleRouter from app import views router = SimpleRouter() router.register('books', views.BookView) ''' register(prefix, viewset, base_name) prefix 该视图集的路由前缀 viewset 视图集 base_name 路由名称的前缀 ''' urlpatterns = [ path('admin/', admin.site.urls), ] urlpatterns += router.urls # 千万注意别把注释写到urlpatterns列表中,那样就不是注释了,成字符串了!!!
- 在settings的app中注册
INSTALLED_APPS = [ 'rest_framework' ]
CBV源码流程分析
因为DRF框架里大部分都是基于CBV(视图类)写,所以流程是什么,如何执行需要了解,同时也方便理解APIView,顺便提一嘴APIView也是继承了View ---->
class APIView(View)
这里需要强调一下,CBV路由归根结底还是FBV都是函数的内存地址,比如views.类.as_view()底层仍然是函数的内存地址
- CBV源码执行流程
'''views.py''' from django.views import View from django.http import HttpResponse class TestView(View): def get(self,request): return HttpResponse('get请求') # post csrf认证注释掉就好了 def post(self,request): return HttpResponse('post请求') '''urls.py''' path('test/',views.TestView.as_view())
- 写一个视图类,写了get方法和post方法,来了get请求就走get方法,来了post请求就走post方法,过程如何?且看分析源码执行过程~
'''请求来了在不考虑中间件的情况下,从路由的匹配关系和视图函数来看''' 1、cbv路由写法:path('test/', views.TestView.as_view()) # 第二个参数是函数内存地址,CBV的底层也是FBV,as_view是类的绑定方法,自己的类中没有去父类(View)找,as_view()执行完,也是一个内存地址,内存地址是谁的?是闭包函数view的如下源码👇 @classonlymethod def as_view(cls, **initkwargs): ··· def view(request, *args, **kwargs): ··· return self.dispatch(request, *args, **kwargs) ··· return view # @classonlymethod通过描述符自定义装饰器 2、请求来了,路由也匹配成功,执行上边返回的view(requets),加括号调用,并且传入了当次请求的request对象 3、然后又返回了当前对象的dispatch方法,自己的名称空间内没有,那么去父类中找,然后发现父类(View)的dispatch方法返回了return handler(request, *args, **kwargs) 4、dispatch方法处理请求,什么请求对应什么方法 # 父类dispatch方法: def dispatch(self, request, *args, **kwargs): # 请求方法小写如果在当前对象的http_method_names中(八个请求方法) ''' http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] ''' if request.method.lower() in self.http_method_names: # 如果成立,那么执行下面的反射,从当前对象(视图类的对象)拿到请求方法,如果是get请求就拿到get方法,post请求就拿到post方法,然后赋给handler,handler就是相应请求对应的方法,最后返回handler(request),本质其实是get(request) handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: # 不成立,也就是没有该请求对应的方法,执行http_method_not_allowed方法,弹出警告 handler = self.http_method_not_allowed return handler(request, *args, **kwargs) # http_method_not_allowed源码 def http_method_not_allowed(self, request, *args, **kwargs): logger.warning( 'Method Not Allowed (%s): %s', request.method, request.path, extra={'status_code': 405, 'request': request} ) return HttpResponseNotAllowed(self._allowed_methods())
DRF之APIView和Request对象分析
Django View和DRF APIView的小插曲
-
DRF中最“牛逼”的视图类是APIView,不过也继承了View
-
导入:from rest_framework.views import APIView
-
Django中最“牛逼”的视图类是View
-
导入:
from django.views import View # 最简 from django.views.generic import View from django.views.generic.base import View # 最完整 '''这三个导入的结果都是一个View,想知道缘由还得看源码和目录结构,如下:''' # 目录结构 -django -··· # 此处省略n个包 -views -- __init__.py -- generic ---- __init__.py ---- base.py # 源码分析 '''问题:为什么上述不同的py文件都可以指向同一个类?''' 我的思路是羊毛出在羊身上,既然都指向类,那么从结果出发是不是更好 1、View类在base.py中,那么显而易见最完整的导入是没问题的,重点是为什么简写也可以 2、base.py所在的包generic,如果从包中导入py文件我们知道可以通过“白名单”__all__来指定谁可以被其他文件导入,我们发现generic包中指定了: from django.views.generic.base import RedirectView, TemplateView, View和__all__ = [ 'View',·····],到此第二个导入解决 # 那么第一个这么短这么简单又是为啥? 3、同样在views包的__init__.py文件中发现: from django.views.generic.base import View __all__ = ['View'] View类被__all__变量包裹了
APIView的执行流程
# 同样和Django中一样写一个视图类,只不过DRF中用APIView底层还是View
'''views.py'''
from rest_framework.response import Response
class Test(APIView):
def get(self, request):
data = {'status': 200, 'msg': 'success'}
return Response(data)
def post(self, request):
data = {'status': 200, 'msg': 'success'}
return Response(data)
'''urls.py'''
path('test/', views.Test.as_view())
APIView流程源码分析
1、路由:path('test/', views.Test.as_view()),path第二个参数任然返回函数内存地址,也还是类绑定方法,Test没有as_view方法,去继承的APIView中找,这次不需要和Django一样去View中找了,庆幸的是APIView中有as_view方法,核心源码如下:
@classmethod
def as_view(cls, **initkwargs):
# 校验反射的结果
if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
def force_evaluation():
raise RuntimeError(
'Do not evaluate the `.queryset` attribute directly, '····
)
···
# 核心代码
# 这里调用了父类的as_view,查看源码发现又回到了Django中的View类,所以本质还是和上面一样,用到了闭包返回的view
view = super().as_view(**initkwargs)
···
# 局部去掉了csrf校验和加装饰器的效果是一样的
return csrf_exempt(view)
2、view = super().as_view(**initkwargs),这里跳转了一下,其实看了父类(View)的源码是和上面Django中写视图类继承的View是一样的,这里的(APIView)的as_view只是进行了简单处理和去掉了csrf中间件校验,真实使用的还是View类中的as_view
3、然后还是闭包函数的返回值view加括号调用,传入了当前对象的request,继续执行了self.dicpatch(),当前类(Test)没有去父类(APIview)找dispatch方法,发现APIView类中有,千万注意了这里可不是View中的dispatch方法了
4、APIView类中的dispatch主要源码:
# APIView的dispatch
def dispatch(self, request, *args, **kwargs):
# request是新的drf提供的request,它是由老的django的request得到的
# 通过老request,生成一个新request,drf的Request的对象
request = self.initialize_request(request, *args, **kwargs)
# 把新的request,放到了视图类对象中,可以通过self调用新的request和传入的request是一个,因为放到了self中
self.request = request
try:
# 执行了三大认证(认证,权限,频率)
self.initial(request, *args, **kwargs)
# self.http_method_names是个列表
if request.method.lower() in self.http_method_names:
# 原来dispatch的核心代码
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# 原来dispatch写的,但是request已经不是老request了,是新的
response = 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
总结:
1、路由:path('test/', views.Test.as_view())
2、APIView类的as_view执行,最终使用View类的as_view
3、执行闭包返回view加括号调用到此就是as_view加括号调用
4、调用执行了view()返回dispatch,但是这里的父类不是View,是APIview所以执行的dispatch是APIView中的dispatch方法
5、dispatch方法中包装了新的Request对象,以后视图类中的方法传入的request都是新的,无论三大认证还是视图函数的方法,执行过程中出了异常,都会被处理掉
6、dispatch执行完毕返回reponse对象,跳转回进入视图函数继续执行as_view去掉了csrf校验
如何包装了新的request?
# APIView中的dispatch方法处理老的request
request = self.initialize_request(request, *args, **kwargs)
# initialize_request方法,当前类(自己写的)没有去父类找,还是APIView
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
return Request(
request, # 老的request,传入的非处理的,返回后就是新的了
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
注意:上面返回的Request对象是rest_framework导入的,然后实例化后返回的,实例化就少不了__init__构造函数
from rest_framework.request import Request
'''Request类'''
class Request:
# 这里即将要初始化的request是传入的老的request
def __init__(self, request, parsers=None, authenticators=None,negotiator=None, parser_context=None):
···
# 初始化,将传入的老的request放入新的_request中,所以request._request是老的,self.request._request等价于request._request
self._request = request
···
# 验证一下新老request
class Test(APIView):
def get(self, request):
data = {'status': 200, 'msg': 'success'}
# 新的request
print(type(request)) # <class 'rest_framework.request.Request'>
print(type(self.request))# <class 'rest_framework.request.Request'>
# 老的request
print(type(self.request._request)) # <class 'django.core.handlers.wsgi.WSGIRequest'>
print(type(request._request)) # <class 'django.core.handlers.wsgi.WSGIRequest'>
return Response(data)
新的:<class 'rest_framework.request.Request'>
老的:<class 'django.core.handlers.wsgi.WSGIRequest'>
三大认证如何执行?
# APIView类处理三大认证
self.initial(request, *args, **kwargs)
# 核心源码
def initial(self, request, *args, **kwargs):
····
self.perform_authentication(request) # 认证
self.check_permissions(request) # 权限
self.check_throttles(request) # 频率
···
# 经过了三大认证才进入了视图函数
中间件---路由---···---三大认证---视图函数····
# 类似二次校验
Request对象分析
rest_framework.request.Request常用属性和方法
这里的request和原来的Django使用request一样,只是多了一个request.data
- request.data:前端POST提交的数据,可以处理多种格式的数据,无论前端传什么编码post提交的数据都在data中
ps:原来提交的数据在request.POST里,有局限性只能处理urlencoded和formdata编码格式,json格式不能处理,是没有request.data的,request其余使用方法的都一样
总结:新的request当老的用即可,只是多了个data前端post请求传入的数据,三种编码格式都可以获取# 如果用过新包装过的request来调用原来的方法呢?这样给使用者的感觉确实没什么变化源码如下: '''重写了getattr''' def __getattr__(self, attr): try: return getattr(self._request, attr) except AttributeError: return self.__getattribute__(attr) 重写getattr的结果是,新的request.method执行的时候本质是request._request.method执行了原来老的request的方法,通过这句getattr(self._request, attr)反射,所以才一样
验证处理三种编码格式
- json格式,只有request.data可以获取,结果是字典
- form-data格式和urlencode格式都可以获取并且是QueryDict对象
from rest_framework.response import Response class Test(APIView): def post(self, request): data = {'status': 200, 'msg': 'success'} print(request.POST) print(request._request.POST) print(request.data) return Response(data) # form-data格式结果 <QueryDict: {'name': ['alan']}> <QueryDict: {'name': ['alan']}> <QueryDict: {'name': ['alan']}> # urlencode格式结果 <QueryDict: {'name': ['alan']}> <QueryDict: {'name': ['alan']}> <QueryDict: {'name': ['alan']}> # json格式结果 <QueryDict: {}> <QueryDict: {}> {'name': 'alan'}
ps:如果想查看QueryDict的源码怎么看?
1、type这个对象找到他属于哪个类
2、导入该类,点进去查看
'''demo'''
print(type(request.POST)) # 老的POST
from django.http.request import QueryDict
注意:如果前端提交过来多个同名数据也就是form表单中input标签的name属性设置了多个重名数据就不能使用request.POST.get('user')来获取了,这样只能获取到一个,需要使用getlist来获取,取出全部
原来的django中没有request.data,造一个!
# 原来的django的request对象中没有data,使得request.data-->无论什么编码格式,post提交数据,data都有值
from django.views import View
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from functools import wraps
def outter(func):
@wraps(func)
def inner(request,*args,**kwargs):
import json
if request.POST:
request.data = request.POST
else:
# 将request.body从json对象转换为字典
request.data = json.loads(request.body)
res = func(request,*args,**kwargs)
return res
return inner
@method_decorator(outter,name='post')
class TestView(View):
def get(self, request):
return HttpResponse('get请求')
def post(self, request):
# 只测试了json格式其余都可以也测了
print(request.data) # {'name': 'Hammer'}
print(request.POST) # <QueryDict: {}>
return HttpResponse('post请求')
序列化器serializer
什么是序列化和反序列化?
- 序列化:序列化器会把模型对象(QuerySet对象,比如book)转换成字典,经过response以后变成了json字符串
- 反序列化:将客户端(前端)发送过来的数据,经过request以后变成字典(data),序列化器可以把字典转换成模型存到数据库中
- 存数据库需要校验,反序列化就可以帮我们完成数据的校验功能
序列化
- 序列化demo
- 在app中新建serializer.py,自定义类,继承DRF框架的Serializer及其子类
- 在类中写要序列化的字段(序列化哪些就写哪些,不序列化的不写)
- 使用序列化类,视图类中用,得到序列化类对象,对象.data,通过Response返回给前端
- serializer.py:序列化类
from rest_framework import serializers # 继承Serializer class BookSerializer(serializers.Serializer): ''' max_length=32 min_length=3 反序列化保存校验数据的时候用,序列化不用 ''' # 写要序列化的字段 title = serializers.CharField() # models中使用了DecimalField,这个位置使用了CharField会把小数类型转成字符串,使用CharField或者DecimalField都可以 # 这里不需要担心反序列化存的问题 price = serializers.CharField() authors = serializers.CharField()
- views.py:视图类
from rest_framework.views import APIView from .models import Book from .serializer import BookSerializer from rest_framework.response import Response # Create your views here. class BookView(APIView): def get(self, request): # 从数据库查数据,做序列化 book_list = Book.objects.all() # 实例化类,传入初始化的参数,instance和many ''' instance:要序列化的对象 qs,单个对象 many:如果是qs对象,many=True,如果是单个对象many=False ''' ser = BookSerializer(instance=book_list, many=True) # ser.data使用模型类的对象得到序列化后的字典 return Response(ser.data)
- urls.py:路由
path('books/', views.BookView.as_view()),
- model.py:模型类
from django.db import models class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=5,decimal_places=2) authors = models.CharField(max_length=32)
- 注意
- 视图类中的参数instance和many的使用,instance是要序列化的对象,一般从数据库中获取到的,many=True代表要序列化多个对象,如果是单个对象就等于False
- 序列化器中不要写max_length等参数,反序列化验证字段用
- 在对BookSerializer类实例化传入的参数不知道传什么,由于我们没有写构造函数,去父类看需要什么参数传什么就可以了
- 使用浏览器测得时候一定要注册rest_framework
字段类型
- serializer.py文件中常用的字段
字段 字段构造方式 BooleanField BooleanField() NullBooleanField NullBooleanField() CharField CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) EmailField EmailField(max_length=None, min_length=None, allow_blank=False) RegexField RegexField(regex, max_length=None, min_length=None, allow_blank=False) SlugField SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+ URLField URLField(max_length=200, min_length=None, allow_blank=False) UUIDField UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose'
如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
2)'hex'
如"5ce0e9a55ffa654bcee01238041fb31a"
3)'int'
- 如:"123456789012312313134124512351145145114"
4)'urn'
如:"urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"
IPAddressField IPAddressField(protocol=’both’, unpack_ipv4=False, **options) IntegerField IntegerField(max_value=None, min_value=None) FloatField FloatField(max_value=None, min_value=None) DecimalField DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置 DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None) DateField DateField(format=api_settings.DATE_FORMAT, input_formats=None) TimeField TimeField(format=api_settings.TIME_FORMAT, input_formats=None) DurationField DurationField() ChoiceField ChoiceField(choices) choices与Django的用法相同 MultipleChoiceField MultipleChoiceField(choices) FileField FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) ImageField ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) ListField ListField(child=, min_length=None, max_length=None) DictField DictField(child=) - 常用的有:
CharField IntegerField FloatField DecimalField DateTimeField DateField 注意 ''' ListField和DictField原来的models是没有的,主要用来做反序列,处理前端传入的数据 ''' 比如我们从前端接收json格式数据 "hobby":["篮球","足球"] 可以用ListField处理 "wife":{"name":"ll","age":20} DictField类似使用
字段参数
写在类中的参数
- 选项参数:
参数名称 作用 max_length 最大长度(CharField) min_lenght 最小长度(CharField) allow_blank 是否允许为空(CharField) trim_whitespace 是否截断空白字符(CharField) max_value 最小值 (IntegerField) min_value 最大值(IntegerField) - 通用参数:
参数名称 说明 read_only 表明该字段仅用于序列化输出,默认False write_only 表明该字段仅用于反序列化输入,默认False required 表明该字段在反序列化时必须输入,默认True default 反序列化时使用的默认值 allow_null 表明该字段是否允许传入None,默认False validators 该字段使用的验证器(不太用) error_messages 包含错误编号与错误信息的字典 label 用于HTML展示API页面时,显示的字段名称 help_text 用于HTML展示API页面时,显示的字段帮助提示信息 - 总结:
# 重点记忆: read_only:表示该字段仅用于序列化输出,默认为Fasle,如果read_only = True,这个字段只用来做序列化(对象---json---前端) write_only:表明该字段仅用于反序列化输入,默认False,如果write_only = True,那么这个字段只用来做反序列化(前端---json---存数据库) # 注意 如果不写read_only和write_only表示及序列化又反序列化 千万不要同时写read_only=True和write_only=True逻辑矛盾了,都要实现直接省略即可
- demo
title=serializers.CharField(max_length=32,min_length=3) price=serializers.CharField(write_only=True,) author=serializers.CharField(write_only=True) # 上面title字段及序列化也反序列化,price,author字段只反序列化 # 序列化给前端,前端看到的字段样子---》只能看到name # 反序列化,前端需要传什么过name,price,author都传
序列化自定制返回字段
-
方法一:在序列化类(serializers.py)中写
自定义返回的key或者value
'''serializer.py''' from rest_framework import serializers # 继承Serializer class BookSerializer(serializers.Serializer): # 写要序列化的字段 title = serializers.CharField(read_only=True) price = serializers.CharField(write_only=True) authors = serializers.CharField(write_only=True) # 自定制返回字段 author_info = serializers.SerializerMethodField() # 搭配方法,方法名必须是get_字段名,该方法返回什么字段,显示什么 def get_author_info(self,obj): # obj是当前数据库book对象 return obj.authors+'牛掰' # 注意字符串拼接的问题 price = serializers.SerializerMethodField() def get_price(self,obj): return "价格是:"+str(obj.price)
-
方法二:在表模型(models.py)中写
'''models.py''' from django.db import models # Create your models here. class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=5,decimal_places=2) authors = models.CharField(max_length=32) @property def price_info(self): return '价格是:'+str(self.price)
'''serializer.py''' from rest_framework import serializers # 继承Serializer class BookSerializer(serializers.Serializer): # 写要序列化的字段 title = serializers.CharField(read_only=True) price = serializers.CharField(write_only=True) authors = serializers.CharField(write_only=True) # 该字段是从models的price_info返回值获取到的,price_info方法返回什么,这个字段就是什么 price_info = serializers.CharField()
- ListField的应用
'''models.py''' def authors(self): return [{"name":"Hammer","age":18},{"name":"Hans","age":28}] '''serializer.py''' authors = serializers.ListField()
- ListField的应用
反序列化
-
把前端传入的数据,放到Serializer对象中:ser=BookSerializer(data=request.data)
-
校验数据:ser.is_valid()
-
保存,ser.save(),但是必须重写create,在序列化类中
-
反序列化新增
POST请求处理新增
'''views.py''' from rest_framework.views import APIView from .models import Book from rest_framework.response import Response from app01.serializer import BookSerializer class BookView(APIView): def post(self,request): # 反序列化,保存到数据库使用data参数 deser = BookSerializer(data=request.data) # 校验数据 if deser.is_valid(): # 保存需要重写create方法,不然不知道存到哪个表 deser.save() return Response(deser.data) return Response({'code':101,'msg':'校验不通过','errors':deser.errors})
重写create方法
'''serializer.py''' def create(self, validated_data): # validated_data是校验通过的数据,将校验通过的数据打散存入数据库 book = Book.objects.create(**validated_data) return book
-
反序列化修改
# 处理修改再写一个视图类,防止get冲突 class BookDetailView(APIView): # 获取一条的 def get(self,request,pk): book = Book.objects.filter(pk=pk).first() ser = BookSerializer(instance=book) # 这里设置了主键值,单条记录many不需要写 return Response(ser.data) # 删除一条的 def delete(self,request,pk): res = Book.objects.filter(pk=pk).delete() print(res) # (1, {'app01.Book': 1}) # res是影响的行数 if res[0]>0: return Response({'code': 100, 'msg': '删除成功'}) else: return Response({'code': 103, 'msg': '要删除的数据不存在'}) # 反序列化修改 def put(self,request,pk): # 修改处理单条数据用过pk确定求改哪条数据 book = Book.objects.filter(pk=pk).first() # 序列化器类实例化需要传入instance,data才表示修改 ser = BookSerializer(instance=book,data=request.data) if ser.is_valid(): # 重写update方法才能存入 ser.save() return Response(ser.data) return Response({'code':101,'msg':'校验未通过','error':ser.errors})
重写update方法
'''serializer.py''' def update(self, instance, validated_data): ''' :param instance: 表示要修改的对象 :param validated_data: 校验通过的数据 :return: instance ''' # 如果只修改一个的情况,从校验通过的数据中get到其他数据是none,做一层校验 instance.title = validated_data.get('title') instance.price = validated_data.get('price') instance.authors = validated_data.get('authors') instance.save() # 保存到数据库中 return instance # 返回instance对象
路由
urlpatterns = [ path('admin/', admin.site.urls), path('books/', views.BookView.as_view()), path('books/<int:pk>', views.BookDetailView.as_view()), ]
-
反序列化之局部和全局钩子
'''serializer.py''' # 局部钩子 def validate_title(self,attr): # attr就是前端传入的数据 # 局部校验书名 if attr.startswith('sb'): from django.core.exceptions import ValidationError raise ValidationError("名字不能以sb开头") else: return attr # 没有问题,正常返回 ''' 校验顺序:先走字段自己规则,再走局部钩子,再走全局钩子 ''' # 全局钩子 def validate(self,attrs): # attrs校验过后的数据,通过了前面校验的规则 if attrs.get('title') == attrs.get('authors'): from django.core.exceptions import ValidationError raise ValidationError('作者名不能等于书名') else: return attrs
ModelSerializer模型类序列化器
ps:视图类,路由处理方式和Serializer是一样的
# ModelSerializer和表模型有绑定关系
class BookSerializer1(serializers.ModelSerializer):
class Meta:
model = Book # 指定和哪个表有关系
# 所有字段
# fields = '__all__'
# 这里注意id字段是从表模型映射过来的,auto自增的,不传也可以
# 自定制的字段不传必须注册,在列表中
fields = ['id', 'title', 'price', 'price_info'] # 指定字段
extra_kwargs = {
'title': {'write_only': True, 'max_length': 8, 'min_length': 3}
}
# 指定序列化的字段:两种写法:在序列化类中写;models中写
price_info = serializers.SerializerMethodField()
def get_price_info(self, obj):
return "价格是:" + str(obj.price)
'''
注意:自定制字段如果和表模型获取到的字段是同名,那么自定制返回给前端的字段值就被自定制覆盖了,比如:
title = serializers.SerializerMethodField()
def get_title(self, obj):
return "书名是:" + str(obj.title)
'''
# 局部和全局钩子,跟之前一样,但是要注意写在Meta外
- ModelSerializer类序列化器不需要重写create方法和update方法了,因为明确指定了操作哪个表
- 固定写法,ModelSerializer类内写Meta类,用来指定一些字段和表模型
ModelSerializer序列化器实战
单表操作
-
序列化器类
# ModelSerializer和表模型有绑定关系 class BookSerializer1(serializers.ModelSerializer): class Meta: model = Book # 指定和哪个表有关系 # 所有字段 # fields = '__all__' # 这里注意id字段是从表模型映射过来的,auto自增的,不传也可以 # 自定制的字段不传必须注册,在列表中 fields = ['id', 'title', 'price', 'price_info'] # 指定字段 extra_kwargs = { 'title': {'write_only': True, 'max_length': 8, 'min_length': 3} } # 指定序列化的字段:两种写法:在序列化类中写;models中写 price_info = serializers.SerializerMethodField() def get_price_info(self, obj): return "价格是:" + str(obj.price) ''' 注意:自定制字段如果和表模型获取到的字段是同名,那么自定制返回给前端的字段值就被自定制覆盖了,比如: title = serializers.SerializerMethodField() def get_title(self, obj): return "书名是:" + str(obj.title) ''' # 局部和全局钩子,跟之前一样,但是要注意写在Meta外
-
视图类
from rest_framework.views import APIView from .models import Book from rest_framework.response import Response from app01.serializer import BookSerializer1 class BookView1(APIView): def get(self, request): # 从数据库查数据,做序列化 book_list = Book.objects.all() # 实例化类,传入初始化的参数,instance和many ''' instance:要序列化的对象 qs,单个对象 many:如果是qs对象,many=True,如果是单个对象many=False ''' ser = BookSerializer1(instance=book_list, many=True) # ser.data使用模型类的对象得到序列化后的字典 return Response(ser.data) def post(self,request): # 反序列化,保存到数据库使用data参数 deser = BookSerializer1(data=request.data) # 校验数据 if deser.is_valid(): # 保存需要重写create方法,不然不知道存到哪个表 deser.save() return Response(deser.data) return Response({'code':101,'msg':'校验不通过','errors':deser.errors}) # 处理修改再写一个视图类,防止get冲突 class BookDetailView1(APIView): def get(self,request,pk): book = Book.objects.filter(pk=pk).first() ser = BookSerializer1(instance=book) # 这里设置了主键值,单条记录many不需要写 return Response(ser.data) def delete(self,request,pk): res = Book.objects.filter(pk=pk).delete() print(res) # (1, {'app01.Book': 1}) # res是影响的行数 if res[0]>0: return Response({'code': 100, 'msg': '删除成功'}) else: return Response({'code': 103, 'msg': '要删除的数据不存在'}) # 反序列化修改 def put(self,request,pk): # 修改处理单条数据用过pk确定求改哪条数据 book = Book.objects.filter(pk=pk).first() # 序列化器类实例化需要传入instance,data才表示修改 ser = BookSerializer1(instance=book,data=request.data) if ser.is_valid(): # 重写update方法才能存入 ser.save() return Response(ser.data) return Response({'code':101,'msg':'校验未通过','error':ser.errors})
-
路由
path('books1/', views.BookView1.as_view()), path('books1/<int:pk>', views.BookDetailView1.as_view()),
-
模型
from django.db import models class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=5,decimal_places=2) authors = models.CharField(max_length=32)
多表操作
-
models.py
from django.db import models # build four model tables class Book(models.Model): name = models.CharField(max_length=32) price = models.DecimalField(decimal_places=2, max_digits=5) publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE) authors = models.ManyToManyField(to='Author') def __str__(self): return self.name # 自定制字段 @property def publish_detail(self): return {'name': self.publish.name, 'addr': self.publish.city} @property def author_list(self): l = [] print(self.authors.all()) # <QuerySet [<Author: Author object (1)>, <Author: Author object (2)>]> for author in self.authors.all(): print(author.author_detail) # AuthorDetail object (1) l.append({'name': author.name, 'age': author.age, 'addr': author.author_detail.addr}) return l class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE) def __str__(self): return self.name # @property # def authordetail_info(self): # return {'phone': self.author_detail.telephone, 'addr': self.author_detail.addr} class AuthorDetail(models.Model): telephone = models.BigIntegerField() addr = models.CharField(max_length=64) class Publish(models.Model): name = models.CharField(max_length=32) city = models.CharField(max_length=32) email = models.EmailField()
-
serializer.py
from src.index import models from rest_framework import serializers # 书序列化器 class BookSerializer(serializers.ModelSerializer): class Meta: # 指定和哪个表有关系 model = models.Book # fields = '__all__' fields = ['id', 'name', 'publish', 'price', 'authors', 'publish_detail', 'author_list'] # 将关联表的信息全部取出来,不推荐使用 # depth = 1 extra_kwargs = { 'publish': {'write_only': True}, 'authors': {'write_only': True}, } # 作者序列化器 class AuthorSerializer(serializers.ModelSerializer): class Meta: # 指定和哪个表有关系 model = models.Author fields = '__all__' # fields = ['id', 'name', 'age', 'author_detail', 'authordetail_info'] extra_kwargs = { 'author_detail': {'write_only': True}, } # 作者详情序列化器 class AuthorDetailSerializer(serializers.ModelSerializer): class Meta: # 指定和哪个表有关系 model = models.AuthorDetail fields = '__all__' # 出版社序列化器 class PublishSerializer(serializers.ModelSerializer): class Meta: # 指定和哪个表有关系 model = models.Publish fields = '__all__'
-
views.py
from django.shortcuts import render from src.index import models from src.index import serializer from rest_framework.response import Response from rest_framework.views import APIView def base(request): return render(request, 'base.html') # 书视图类 class BookView(APIView): # 获取所有 def get(self, requets): # 序列化 book_list = models.Book.objects.all() # 序列化多条数据many=True ser = serializer.BookSerializer(instance=book_list, many=True) return Response(ser.data) # 新增一条 def post(self, request): # 获取反序列化数据 ser = serializer.BookSerializer(data=request.data) if ser.is_valid(): # 校验通过存入数据库,不需要重写create方法了 ser.save() return Response({'code': 100, 'msg': '新增成功', 'data': ser.data}) # 校验失败 return Response({'code': 101, 'msg': '校验未通过', 'error': ser.errors}) class BookViewDetail(APIView): def get(self, request, pk): book = models.Book.objects.filter(pk=pk).first() ser = serializer.BookSerializer(instance=book) return Response(ser.data) def put(self, request, pk): book = models.Book.objects.filter(pk=pk).first() # 修改,instance和data都要传 ser = serializer.BookSerializer(instance=book, data=request.data) if ser.is_valid(): # 校验通过修改,不需要重写update ser.save() return Response({'code:': 100, 'msg': '修改成功', 'data': ser.data}) # 校验不通过 return Response({'code:': 102, 'msg': '校验未通过,修改失败', 'error': ser.errors}) def delete(self, request, pk): models.Book.objects.filter(pk=pk).delete() return Response({'code': 100, 'msg': '删除成功'}) # 作者视图类 class AuthorView(APIView): def get(self, requets): # 序列化 author_list = models.Author.objects.all() # 序列化多条数据many=True ser = serializer.AuthorSerializer(instance=author_list, many=True) return Response(ser.data) def post(self, request): # 获取反序列化数据 ser = serializer.AuthorSerializer(data=request.data) if ser.is_valid(): # 校验通过存入数据库,不需要重写create方法了 ser.save() return Response({'code': 100, 'msg': '新增成功', 'data': ser.data}) # 校验失败 return Response({'code': 101, 'msg': '校验未通过', 'error': ser.errors}) class AuthorViewDetail(APIView): def get(self, request, pk): book = models.Author.objects.filter(pk=pk).first() ser = serializer.AuthorSerializer(instance=book) return Response(ser.data) def put(self, request, pk): book = models.Author.objects.filter(pk=pk).first() # 修改,instance和data都要传 ser = serializer.AuthorSerializer(instance=book, data=request.data) if ser.is_valid(): # 校验通过修改,不需要重写update ser.save() return Response({'code:': 100, 'msg': '修改成功', 'data': ser.data}) # 校验不通过 return Response({'code:': 102, 'msg': '校验未通过,修改失败', 'error': ser.errors}) def delete(self, request, pk): models.Author.objects.filter(pk=pk).delete() return Response({'code': 100, 'msg': '删除成功'}) # 作者详情视图类 class AuthorDetailView(APIView): def get(self, requets): # 序列化 author_list = models.AuthorDetail.objects.all() # 序列化多条数据many=True ser = serializer.AuthorDetailSerializer(instance=author_list, many=True) return Response(ser.data) def post(self, request): # 获取反序列化数据 ser = serializer.AuthorDetailSerializer(data=request.data) if ser.is_valid(): # 校验通过存入数据库,不需要重写create方法了 ser.save() return Response({'code': 100, 'msg': '新增成功', 'data': ser.data}) # 校验失败 return Response({'code': 101, 'msg': '校验未通过', 'error': ser.errors}) class OneAuthorViewDetail(APIView): def get(self, request, pk): book = models.AuthorDetail.objects.filter(pk=pk).first() ser = serializer.AuthorDetailSerializer(instance=book) return Response(ser.data) def put(self, request, pk): book = models.AuthorDetail.objects.filter(pk=pk).first() # 修改,instance和data都要传 ser = serializer.AuthorDetailSerializer(instance=book, data=request.data) if ser.is_valid(): # 校验通过修改,不需要重写update ser.save() return Response({'code:': 100, 'msg': '修改成功', 'data': ser.data}) # 校验不通过 return Response({'code:': 102, 'msg': '校验未通过,修改失败', 'error': ser.errors}) def delete(self, request, pk): models.AuthorDetail.objects.filter(pk=pk).delete() return Response({'code': 100, 'msg': '删除成功'}) # 出版社视图类 class PublishView(APIView): def get(self, requets): # 序列化 author_list = models.Publish.objects.all() # 序列化多条数据many=True ser = serializer.PublishSerializer(instance=author_list, many=True) return Response(ser.data) def post(self, request): # 获取反序列化数据 ser = serializer.PublishSerializer(data=request.data) if ser.is_valid(): # 校验通过存入数据库,不需要重写create方法了 ser.save() return Response({'code': 100, 'msg': '新增成功', 'data': ser.data}) # 校验失败 return Response({'code': 101, 'msg': '校验未通过', 'error': ser.errors}) class PublishViewDetail(APIView): def get(self, request, pk): book = models.Publish.objects.filter(pk=pk).first() ser = serializer.PublishSerializer(instance=book) return Response(ser.data) def put(self, request, pk): book = models.Publish.objects.filter(pk=pk).first() # 修改,instance和data都要传 ser = serializer.PublishSerializer(instance=book, data=request.data) if ser.is_valid(): # 校验通过修改,不需要重写update ser.save() return Response({'code:': 100, 'msg': '修改成功', 'data': ser.data}) # 校验不通过 return Response({'code:': 102, 'msg': '校验未通过,修改失败', 'error': ser.errors}) def delete(self, request, pk): models.Publish.objects.filter(pk=pk).delete() return Response({'code': 100, 'msg': '删除成功'})
-
urls.py
# 二级路由,路由分发 from django.urls import path from src.index import views from rest_framework.routers import SimpleRouter # router = SimpleRouter() # router.register('books', views.BookView) urlpatterns = [ # 书 path('books/', views.BookView.as_view()), path('books/<int:pk>', views.BookViewDetail.as_view()), # 作者 path('authors/', views.AuthorView.as_view()), path('authors/<int:pk>', views.AuthorViewDetail.as_view()), # 作者详情 path('authorsdetail/', views.AuthorDetailView.as_view()), path('authorsdetail/<int:pk>', views.OneAuthorViewDetail.as_view()), # 出版社 path('publish/', views.PublishView.as_view()), path('publishdetail/<int:pk>', views.PublishViewDetail.as_view()), ] # urlpatterns += router.urls
-
优化操作
我们知道作者表和作者详情表的表关系是一对一的关系,那么新增数据的时候,就得先新增作者详情表,再增作者表的数据,但是在实际生活中,用户不知道表关系这码事,为了体验更好,可以重写create方法,同时存两个表的内容,给用户的感觉就是操作了一张表
# 优化作者表的序列化器 class AuthorSerializer(serializers.ModelSerializer): class Meta: # 指定和哪个表有关系 model = models.Author # fields = '__all__' fields = ['id', 'name', 'age', 'telephone', 'addr', 'authordetail_info'] # 重写字段telephone和addr telephone = serializers.CharField(write_only=True) addr = serializers.CharField(write_only=True, max_length=8, required=False) # 重写create,操作两个表 def create(self, validated_data): # 先存作者详情 authordetail = models.AuthorDetail.objects.create(telephone=validated_data.get('telephone'), addr=validated_data.get('addr')) # 存作者表 author = models.Author.objects.create(author_detail=authordetail, name=validated_data.get('name'), age=validated_data.get('age')) # 这样只返回author对象就行,直接存了两个表,返回反序列化的对象 return author
admin后台使用
-
建造超级用户
manage.py文件下
createsuperuser
命令- 输入账户密码即可
-
admin.py配置
from django.contrib import admin from .models import * # Register your models here. admin.site.register(Book) admin.site.register(Author) admin.site.register(AuthorDetail) admin.site.register(Publish)
-
语言时间国际化
"""settings.py""" LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_TZ = False