1.RESTful规范
-
REST风格
-- 资源 网页中能看到的都是资源 -- URI 统一资源标识符 -- URL 统一资源定位符 -- 统一资源接口 对资源的操作根据HTTP请求方式的不同来进行不同操作 遵循HTTP请求方式的语义 -- 前后端传输的是资源的表述 -- 展现的是资源的状态
-
凡是遵循REST风格实现的前后端交互都叫RESTful架构
-- 核心思想 -- 面向资源去编程 url中尽量名词不要用动词 -- 根据HTTP请求方式的不同对资源进行不同的操作 -- 在url中体现的 -- 体现版本 https://v2.bootcss.com/ https://bootcss.com/v2 -- 体现是否是API https://v2.bootcss.com/api -- 有过滤条件 https://v2.bootcss.com/course?page=1 -- 尽量用https -- 在返回值中 -- 携带状态码 -- 返回值 -- get 返回查看的所有或者单条数据 -- post 返回新增的这条数据 -- put/patch 返回更新的这条数据 -- delete 返回值空 -- 携带错误信息 -- 携带超链接
2.FBV与CBV的区别
- 继续往下看APIView有必要再了解一下(看之前的笔记:django进阶/CBV流程) 可以跟着代码走一遍
- 这里我就不再写了
3.APIView
- 博客
- 建议跟着图和源码一起看
urls.py:
from .views import BookView
urlpatterns = [
url(r'^book_list/', BookView.as_view()),
]
views.py:
from rest_framework.views import APIView
class BookView(APIView): # 继承APIView
def get(self, request):
pass
- 第四步也没干嘛(当时有点糊涂了)
- 第四步也是返回了一个view(你可以点进去看看源码)
- 第13步弄错了,重点在上面的下面这句!!!
- request = self.initialize_request(request, *args, **kwargs) 这里会重新封装request
- 点进去看源码
- 此时request是一个实例化对象
- request = Request(...),所以我们现在request.GET等方法也拿不到什么东西,那怎么办呢?
- 往下继续看
4.序列化
models.py:
from django.db import models
# Create your models here.
__all__ = ["Book", "Publisher", "Author"]
class Book(models.Model):
title = models.CharField(max_length=32)
CHOICES = ((1, "Python"), (2, "Linux"), (3, "go"))
category = models.IntegerField(choices=CHOICES)
pub_time = models.DateField()
publisher = models.ForeignKey(to="Publisher")
authors = models.ManyToManyField(to="Author")
class Publisher(models.Model):
title = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
- 在admin注册model,便于添加数据
admin.py:
from django.contrib import admin
# Register your models here.
from .models import __all__
from . import models
for table in __all__:
admin.site.register(getattr(models, table))
4.1 django的序列化
- 建议看一下笔记为什么需要序列化(django进阶/JsonResponse)
- 如果不传一个json的数据,页面只能拿到key
- 用values方法获取数据(id、title)
views.py:
class BookView(View):
"""
book_list = [
{
id: 1,
"title": "xx",
"publisher": {
"id": 1,
"title": "xx"
}
}
]
"""
def get(self, request):
book_queryset = Book.objects.values("id", "title")
book_list = list(book_queryset)
return HttpResponse(book_list)
2.用values方法获取数据(id、title、pub_time)
book_queryset = Book.objects.values("id", "title", "pub_time")
3.用values获取外键关系
book_queryset = Book.objects.values("id", "title", "pub_time", "publisher")
- 但我们要的并不是id,我们需要的是
publisher:{id:xx, 'title':xxx}
,重点是title
book_queryset = Book.objects.values("id", "title", "pub_time", "publisher")
book_list = list(book_queryset)
for book in book_list:
book["publisher"] = {
"id": book["publisher"],
"title": Publisher.objects.filter(id=book["publisher"]).first().title,
}
4.2 使用json序列化
1.使用json序列化
class BookView(View):
def get(self, request):
book_queryset = Book.objects.values("id", "title", "pub_time", "publisher")
book_list = list(book_queryset)
ret = []
for book in book_list:
book["publisher"] = {
"id": book["publisher"],
"title": Publisher.objects.filter(id=book["publisher"]).first().title,
}
ret.append(book)
ret = json.dumps(ret, ensure_ascii=False) # ensure_ascii设置为False是为了不被乱码
return HttpResponse(ret)
2.使用JsonResponse来返回页面数据
class BookView(View):
def get(self, request):
book_queryset = Book.objects.values("id", "title", "pub_time", "publisher")
book_list = list(book_queryset)
ret = []
for book in book_list:
book["publisher"] = {
"id": book["publisher"],
"title": Publisher.objects.filter(id=book["publisher"]).first().title,
}
ret.append(book)
return JsonResponse(ret)
return JsonResponse(ret, safe=False) # 此时ret是一个列表,所以要设置safe为False
class BookView(View):
def get(self, request):
book_queryset = Book.objects.values("id", "title", "pub_time", "publisher")
book_list = list(book_queryset)
ret = []
for book in book_list:
book["publisher"] = {
"id": book["publisher"],
"title": Publisher.objects.filter(id=book["publisher"]).first().title,
}
ret.append(book)
# 给json_dumps_params赋值
return JsonResponse(ret, safe=False, json_dumps_params={"ensure_ascii": False})
- 问题,我们现在是因为数据不多,还不太复杂拿出序列化数据,但万一数据量过大呢,还有外键关系...
- 所以,我们下面介绍另外一种序列化方式:DRF序列化
- 哦,对了,含有django自带的序列化,不过通过外键只能拿到id....所以我们还是直接看下面吧
4.3 DRF序列化
-
声明序列化器
- serializers.py
# 1.从rest_framework导入序列化器 from rest_framework import serializers # 2.声明一个个序列化器对象 (字段名必须与models中的字段一样,才能进行序列化) class PublisherSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) class AuthorSerializer(serializers.Serializer): id = serializers.IntegerField() name = serializers.CharField(max_length=32) class BookSerializer(serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField(max_length=32) pub_time = serializers.DateField() category = serializers.CharField(source="get_category_display") # 当设置source时,序列化该字段会执行该方法 # 当有外键关系时,利用字段等于序列化器对象,之后会通过反向查询拿到所对应的对象进行序列化 publisher = PublisherSerializer() authors = AuthorSerializer(many=True) # authors拿到多个对象,所以要将many设置为True #注意:字段必须与app中model中的字段名相同,否则会报错
-
在views.py中使用
from rest_framework.views import APIView from .serializers import BookSerializer from rest_framework.response import Response class BookView(APIView): def get(self, request): book_queryset = Book.objects.all() # [book_obj, ] # 用序列化器进行序列化,序列化器接收一个模型对象或queryset对象 # many=True说明对象是可迭代的,序列化器会将对象一个一个进行序列化 ser_obj = BookSerializer(book_queryset, many=True) return Response(ser_obj.data)
- BookSerializer序列化器会将我们的queryset对象一个一个序列化
- 序列化好的数据在ser_obj.data中
- 利用rest_framework返回页面,它会自己返回自带的页面
- 对了,一定要记得将
rest_framework
注册到app中,否则会报错 - 启动项目可能会报错,可能是因为rest_framework与python版本不兼容导致的(自行百度)
- 当我们也写了post方法时:
5.反序列化
5.1 post请求增加数据
- 当我们需要增加数据时,又应该构造怎样的数据进行提交呢?
get获取到数据为:
{
"id": 1,
"title": "据运营跑路记",
"pub_time": "2021-08-13",
"category": "go",
"publisher": {
"id": 1,
"title": "南山出版社"
},
"authors": [
{
"id": 1,
"name": "康成"
},
{
"id": 2,
"name": "据运营"
}
]
}
post提交的数据格式为:
{
"title": "书名",
"pub_time": "2021-08-13",
"category": 1,
"publisher":1,
"authors": [1, 2]
}
# 具体为什么看下面,我觉得你就懂了(结合你的model)
'书的id我们不需要,因为书的id是自增的,category分类我们只需要分类的号码,publisher也只需要id,authors也一样需要id'
views.py:
class BookView(APIView):
def get(self, request):
book_queryset = Book.objects.all()
# [book_obj, ]
# 用序列化器进行序列化
ser_obj = BookSerializer(book_queryset, many=True)
return Response(ser_obj.data)
def post(self, request):
# 1.将post发送过来的数据接收
book_obj = request.data
# 2.将前端发过来的数据丢进序列化器进行校验
ser_obj = BookSerializer(data=book_obj)
# 3.判断校验是否成功
if ser_obj.is_valid():
ser_obj.save()
# 4.校验成功返回提交的数据
return Response(ser_obj.data)
# 5.校验不成功返回错误
Response(ser_obj.errors)
- 在第二步校验数据就将出现问题:
- 一些字段类型变了,肯定校验不成功,而且某些字段根本不需要校验,比如书籍id
- 所以我们需要重新在序列化器中定义一些字段用于反序列化
- 这也势必让一些字段反序列化用不上,则会报错
serializers.py:
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
title = serializers.CharField(max_length=32)
pub_time = serializers.DateField()
category = serializers.CharField(source="get_category_display", read_only=True)
# 当有外键关系时
publisher = PublisherSerializer(read_only=True)
authors = AuthorSerializer(many=True, read_only=True)
# 反序列化用字段
category_id = serializers.IntegerField(write_only=True)
publisher_id = serializers.IntegerField(write_only=True)
authors_list = serializers.ListField(write_only=True)
-
required=False:该字段序列化,但不走校验
-
read_only=True:该字段只用于序列化
-
write_only=True:该字段只用于反序列化
-
而定义新字段进行校验后,我们post发送过来的数据key也要变成相应的字段
{ "title": "书名", "pub_time": "2021-08-13", "category_id": 1, "publisher_id":1, "authors_list": [1, 2] }
-
当我们提交数据后:
-
源码解析:点进save去
- 在序列化器中重写create方法:
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
title = serializers.CharField(max_length=32)
pub_time = serializers.DateField()
category = serializers.CharField(source="get_category_display", read_only=True)
# 当有外键关系时
publisher = PublisherSerializer(read_only=True)
authors = AuthorSerializer(many=True, read_only=True)
# 反序列化用字段
category_id = serializers.IntegerField(write_only=True)
publisher_id = serializers.IntegerField(write_only=True)
authors_list = serializers.ListField(write_only=True)
# 该方法接收校验过的数据,然后我们可以进行ORM操作将数据存入数据库
def create(self, validated_data):
book_obj = Book.objects.create(
title=validated_data['title'],
pub_time=validated_data['pub_time'],
category=validated_data['category_id'],
publisher_id=validated_data['publisher_id']
)
book_obj.authors.add(*validated_data['authors_list'])
book_obj.save()
# 将对象返回
return book_obj
-
提交数据:
-
查看数据:
5.2 put/patch更改数据
-
views.py:
# 获取某本数据的信息,get获取,put更改信息,url就不写了,自己写 class BookEditView(APIView): def get(self, request, id): book_obj = Book.objects.filter(id=id).first() ser_obj = BookSerializer(book_obj) return Response(ser_obj.data) def put(self, request, id): # 1.根据id获取到需要更改信息的书籍对象 book_obj = Book.objects.filter(id=id).first() # 2.将提交的数据和要修改的实例交给序列化器对象 # partial=True是部分校验(就是只校验你更改的数据字段) ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True) # 3.判断校验数据是否成功 if ser_obj.is_valid(): ser_obj.save() # 4.校验成功返回校验的数据 return Response(ser_obj.data) # 5.出错就返回校验错误信息 return Response(ser_obj.errors)
- 当我们put之后:
-
源码解析:
-
在序列化器中重写update方法
def update(self, instance, validated_data): instance.title = validated_data.get('title', instance.title) instance.pub_time = validated_data.get('pub_time', instance.pub_time) instance.category = validated_data.get('category_id', instance.category) instance.publisher_id = validated_data.get('publisher_id', instance.publisher_id) if validated_data.get("author_list"): instance.authors.set(validated_data["author_list"]) instance.save() return instance
- 因为我们不知道要修该哪个值,所以我们get时没有就用默认值
-
发送put请求
6.校验数据
6.1 自定义校验器
def check(value):
if '大哥哥' in value:
raise serializers.ValidationError('不符合社会主义核心价值观')
return value
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
title = serializers.CharField(max_length=32, validators=[check]) # validators可接收多个校验器,可查看源码
6.2 单个字段的校验
-
def validate_字段(value):
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
title = serializers.CharField(max_length=32)
# def validate_字段(value)
def validate_title(self, value):
if '大哥哥' in value:
raise serializers.ValidationError('不符合社会主义核心价值观')
return value
6.3 多个字段的校验
- def validate(self, attrs):
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
title = serializers.CharField(max_length=32)
pub_time = serializers.DateField()
category = serializers.CharField(source="get_category_display", read_only=True)
# 当有外键关系时
publisher = PublisherSerializer(read_only=True)
authors = AuthorSerializer(many=True, read_only=True)
# 反序列化用字段
category_id = serializers.IntegerField(write_only=True)
publisher_id = serializers.IntegerField(write_only=True)
authors_list = serializers.ListField(write_only=True)
def validate(self, attrs):
# attrs 是所有字段组成的字典
if ('大哥哥' in attrs.get('title')) or attrs.get('category_id') == 2:
raise serializers.ValidationError('不符合社会主义核心价值观')
return attrs
6.4 总结
-
三个校验的权重:自定义--->单个字段校验---->多个字段校验
-
验证:可依次将三个校验放开(记得都要能通过校验),依次打印111、222、333
-
源码这一块emmmm,当时看是看了,就是没有找到局部的钩子....
-
看源码记得从is_valid()开始看(这次视频没有讲源码)
-
谨记:任何方法都从自身开始找,没有就从父类依次找下去,直到找到随后开始执行该方法
-
刚开始看连全局钩子都没找到,还是在第三层(BaseSerializer)找到了
-
自己的序列化器--->serializers.Serializer--->BaseSerializer--->Field
-
7.ModelSerializer
7.1 定义一个ModelSerializer序列化器
- 像上面那样自己写字段是不是太麻烦了
- 所以这里引出一个新的东西:ModelSerializer
- 与ModelForm用法差不多(可以进行参考)
- 重新写一个序列化器,看页面返回
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
7.2 外键关系的序列化
- 使用depth
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
# fields = [....]
# exclude = [....]
# 通过外键往下再找一层,最多4层。但用了depth会让外键关系变成 read_only = True(只能进行序列化,不能进行反序列化)
depth = 1
7.3 自定义字段
class BookSerializer(serializers.ModelSerializer):
# 这里你可以理解为我重写了category字段
# 因为这里的字段要与model中的字段相对应,或者与要提交的数据字段相对应
# 我这里设置了read_only = True ---> 只序列化
category = serializers.CharField(source='get_category_display', read_only=True)
class Meta:
model = Book
fields = "__all__"
# fields = [....]
# exclude = [....]
# 通过外键往下再找一层,最多4层。但用了depth会让外键关系变成 read_only = True(只能进行序列化,不能进行反序列化)
depth = 1
7.3 Meta中其它关键字参数
class BookSerializer(serializers.ModelSerializer):
category = serializers.CharField(source='get_category_display', read_only=True)
class Meta:
model = Book
fields = "__all__"
# fields = ["id", "title", "pub_time"]
# exclude = ["user"]
# 分别是所有字段 包含某些字段 排除某些字段
depth = 1
read_only_fields = ["id"] # 将只序列化的字段放在该列表中,不用一个一个写了
extra_kwargs = {"title": {"validators": [my_validate,]}}
extra_kwargs = {"publisher": {"write_only": True}, "authors": {"write_only": True}}
7.4 SerializerMethodField
- 因为depth会将外键所有字段拿出
- 并且会将外键变成只读,所以我们不使用depth
- 外键关联的对象有很多字段我们是用不到的都传给前端会有数据冗余就需要我们自己去定制序列化外键对象的哪些字段
class BookSerializer(serializers.ModelSerializer):
category_display = serializers.SerializerMethodField(read_only=True)
publisher_info = serializers.SerializerMethodField(read_only=True)
authors_info = serializers.SerializerMethodField(read_only=True)
# 提供的钩子函数:get_字段(self,obj):
def get_category_display(self, obj):
# obj 就是序列化的每个Book对象
return obj.get_category_display()
def get_authors_info(self, obj):
authors_querset = obj.authors.all()
return [{"id": author.id, "name": author.name} for author in authors_querset]
def get_publisher_info(self, obj):
publisher_obj = obj.publisher
return {"id": publisher_obj.id, "title": publisher_obj.title}
class Meta:
model = Book
fields = "__all__"
extra_kwargs = {"publisher": {"write_only": True}, "authors": {"write_only": True},
"category": {"write_only": True}}
- SerializerMethodField提供了钩子函数:get_自定义字段名(self,obj)
- obj就是views.py传过来需要序列化的每一个对象
- 返回值将作为字段值,然后展现在页面中
4.看页面
7.5 总结
-- ModelSerializer
-- class Meta:
model = 表名
fields = "__all__"
# exclude = [xxxx]
depth = 1
# depth 会让所有的外键关系字段变成read_only=True
extra_kwargs={"默认的字段名称":{自定义的参数配置信息}}
-- SerializerMethodField() 方法字段
def get_字段名称(self, obj):
obj 每次序列化的模型对象
return 自定义的数据
- DRF在写ModelSerializers时,如果你自定义了字段那么必须重写create方法
8.视图的封装
-
写笔记的时候比较明白,如果不明白看视频吧,也就半个小时
-
看视频一下就能明白
8.1 视图的第一次封装
- 当表多时,视图的内容基本一样,
- 无非就是增删改查,但是所获取的queryset和serializer_class是不一样的
- 所以我们可以把一些单独抽取出来
- 最初的视图:
class BookView(APIView):
def get(self, request):
query_set = Book.objects.all()
book_ser = BookSerializer(query_set, many=True)
return Response(book_ser.data)
def post(self, request):
query_set = request.data
book_ser = BookSerializer(data=query_set)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.validated_data)
else:
return Response(book_ser.errors)
class BookEditView(APIView):
def get(self, request, id):
query_set = Book.objects.filter(id=id).first()
book_ser = BookSerializer(query_set)
return Response(book_ser.data)
def patch(self, request, id):
query_set = Book.objects.filter(id=id).first()
book_ser = BookSerializer(query_set, data=request.data, partial=True)
if book_ser.is_valid():
book_ser.save()
return Response(book_ser.validated_data)
else:
return Response(book_ser.errors)
def delete(self, request, id):
query_set = Book.objects.filter(id=id).first()
if query_set:
query_set.delete()
return Response("")
else:
return Response("删除的书籍不存在")
-
第一次封装:
class GenericAPIView(APIView): queryset = None serializer_class = None def get_queryset(self): return self.queryset.all() def get_serializer(self, *args, **kwargs): return self.serializer_class(*args, **kwargs) class ListModelMixin(object): def list(self, request, *args, **kwargs): queryset = self.get_queryset() serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) class CreateModelMixin(object): def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.validated_data) else: return Response(serializer.errors) class RetrieveModelMixin(object): def retrieve(self, request, id, *args, **kwargs): book_obj = self.get_queryset().filter(pk=id).first() book_ser = self.get_serializer(book_obj) return Response(book_ser.data) class UpdateModelMixin(object): def update(self, request, id, *args, **kwargs): book_obj = self.get_queryset().filter(pk=id).first() book_ser = self.get_serializer(book_obj, data=request.data, partial=True) if book_ser.is_valid(): book_ser.save() return Response(book_ser.validated_data) else: return Response(book_ser.errors) class DestroyModelMixin(object): def destroy(self, request, id, *args, **kwargs): queryset = self.get_queryset() try: queryset.get(pk=id).delete() return Response("") except Exception as e: return Response("信息有误") # 我们把公共的部分抽出来 这样不管写多少表的增删改查都变的很简单 # 这样封装后我们的视图会变成这样 class BookView(GenericAPIView, ListModelMixin, CreateModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs) class BookEditView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin): queryset = Book.objects.all() serializer_class = BookSerializer def get(self, request, id, *args, **kwargs): return self.retrieve(request, id, *args, **kwargs) def patch(self, request, id, *args, **kwargs): return self.update(request, id, *args, **kwargs) def destroy(self, request, id, *args, **kwargs): return self.delete(request, id, *args, **kwargs)
- 我们封装的GenericAPIView,包括封装每个方法的类,其实框架都帮我们封装好了
- 我们可以直接继承这些类来实现上面的视图可是还有没有更简单的方法呢~我们再次封装一下
8.2 视图的第二次封装
class ListCreateAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):
pass
class RetrieveUpdateDestroyAPIView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
pass
class BookView(ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
class BookEditView(RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request, id, *args, **kwargs):
return self.retrieve(request, id, *args, **kwargs)
def patch(self, request, id, *args, **kwargs):
return self.update(request, id, *args, **kwargs)
def delete(self, request, id, *args, **kwargs):
return self.delete(request, id, *args, **kwargs)
- 这次我们只是让继承变的简单了一点而已,好像并没有什么大的进步
- 我们可不可以把这两个视图合并成一个视图呢 -----> 根据请求方式来做相应的操作
- get请求--->list post请求--->create.....
- 框架给我们提供了一个路由传参的方法
8.3 视图的第三次封装
-
urls.py:
urlpatterns = [ url(r'^book$', BookModelView.as_view({"get": "list", "post": "create"})), url(r'^book/(?P<pk>\d+)', BookModelView.as_view({"get": "retrieve", "put": "update", "delete":"destroy"})), ]
- url本不可以传参的,但我们的view视图继承一个类就可以了
- 这个类在
from rest_framework.viewsets import ViewSetMixin
中
源码解析:
- actions =
第三次封装
# 如果我们再定义一个类
class ModelViewSet(ViewSetMixin, ListCreateAPIView, RetrieveUpdateDestroyAPIView):
pass
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
1. ViewSetMixin被继承,as_view方法被重写,可以传参---》get请求就是方式list请求
2. ListCreateAPIView该类中含有list方法和create方法
3. RetrieveUpdateDestroyAPIView 含有update,delete等方法
4. BookView通过继承ModelViewSet而有了这些方法,然后通过路由分发进行相应的操作
rest_framework
自己的封装:- 我们现在的视图就只要写两行就可以了~~~
- 其实我们写的所有的视图框架都帮我们封装好了
- 注意一点用框架封装的视图我们url上的那个关键字参数要用`pk`系统默认的~
from rest_framework import viewsets
class BookModelView(viewsets.ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 就这样?对的,这是系统封装好的,我们可以直接用
# 点进 viewsets 源码,和我们一模一样
8.4 总结
- 以上这些是当作了解,因为上面的封装已经封装好了
- 我们只需要继承就好了
- 了解这些是为了让你
知其所以然
- 要不然我直接继承已经封装好的你一脸懵逼
- 我们需要的类都可以在下面⬇找到
from rest_framework import views # APIView
from rest_framework import viewsets # 重写了ass_view方法
from rest_framework import generics # 与我们的类一致,就是严谨了点,但核心是一样的
from rest_framework import mixins
# ctrl + shift + '-' 快捷键,将代码全部收拢,容易看出来
- 奉献一张图来看下我们的继承顺序~~~
9.路由组件
urls.py:
# 1.导入
from rest_framework.routers import DefaultRouter
# 2.实例化该路由类
router = DefaultRouter()
# 3.注册路由
router.register('路由',视图函数)
# 4.将生成的路由添加到路由列表中
urlpatterns += router.urls # 所有生成的路由都在实例化对象.urls中
-- 生成的路由都是带参数的,你的视图必须支持路由传参
-
示例:
from django.conf.urls import url, include from .views import BookModelView # 帮助我们生成带参数的路由 from rest_framework.routers import DefaultRouter # 实例化DefaultRouter对象 router = DefaultRouter() # 注册我们的路由以及视图 router.register(r'^book', BookModelView) urlpatterns = [ ] urlpatterns += router.urls
10.版本控制
-
回到最初:CBV
urls.py: from .views import BookView urlpatterns = [ url(r'^book_list/', BookView.as_view()), ]
views.py: from rest_framework.views import APIView class BookView(APIView): # 继承APIView def get(self, request): pass
-
走源码解析版本控制:
-
我们可以自己写一个版本控制类
# 我们可以和前端商量把版本放在哪 # 这里我们把版本号放在路由 version.py: class MyVersion(object): # 该方法是源码暴露给我们的钩子,定义了版本控制类一定要有该方法,并且返回版本 def determine_version(self, request, *args, **kwargs): # 该方法返回值是版本号 # 获取前端传过来的版本号,并且把版本号返回 version = request.query_params.get('version') if not version: # 如果通过路由获取不到version,默认版本为v1 version = 'v1' return version # 看上面源码,我们将版本返回,另一个返回值在源码中,是将版本控制类给返回
views.py: class VersionDemo(APIView): # versioning_class = version.MyVersion 只在该视图使用版本控制类 def get(self, request): print(request.version) # 打印版本 print(request.versioning_scheme) # 打印版本控制类 if request.version == 'v2': return Response('这是v2版本的返回信息') return Response("这是v1版本的返回信息")
-
全局视图使用
settings.py: REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "utils.version.MyVersion", # 版本控制类所在的路径 }
-
示例:
11.认证组件
-
同样看源码:
- 这里的self是Request() ----> request
- 走完11步继续向下执行:self.user, self.auth = 我们认证组件所返回的
- 即request.user,request.auth = 我们所返回的
- 而这里可以为权限组件做准备(哈哈哈,好好看下去)
-
自己写一个认证组件:
# 此处是为了验证,所以建表 models.py: from django.db import models # Create your models here. class User(models.Model): name = models.CharField(max_length=32) pwd = models.CharField(max_length=32) token = models.UUIDField(null=True, blank=True) '记得数据库迁移':makemigrations、migrate
views.py: # 建表登录之后就会有token class LoginView(APIView): def post(self, request): name = request.data.get('name', '') pwd = request.data.get('pwd', '') user_obj = models.User.objects.filter(name=name, pwd=pwd).first() if user_obj: user_obj.token = uuid.uuid4() user_obj.save() return Response(user_obj.token) else: return Response('用户名或密码错误') # 测试认证组件 class TestView(APIView): # authentication_classes是一个列表,可接收所有的认证组件 authentication_classes = [MyAuth, ] # 只有该视图走认证组件 def get(self, request): return Response('测试认证组件')
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from authDemo import models # 这里同样将token放在路由 class MyAuth(BaseAuthentication): # 必须要继承该类,否则会报错,说你没有authenticate_header方法 def authenticate(self, request): # 认证逻辑 # 拿到前端传来的token # 判断token是否存在 token = request.query_params.get('token', '') if not token: raise AuthenticationFailed('缺少token') user_obj = models.User.objects.filter(token=token).first() if not user_obj: raise AuthenticationFailed('token不合法') return (user_obj, token)
-
示例:
-
token不正确时:
-
缺少token:
12.权限组件
-
还是看源码:
- 提供执行该钩子知晓该方法应该返回一个布尔值
- 而如果条件成立,也就是返回False,则代码继续向下执行
- 执行self.permisson_denied(.....)
- message通过反射从实例化对象中获取,所以我们可以给message赋值,如下:
-
自定义权限组件:
- 以下不做解释了,你如果看了认证就大概知道了
moedls.py: from django.db import models # Create your models here. class User(models.Model): name = models.CharField(max_length=32) pwd = models.CharField(max_length=32) token = models.UUIDField(null=True, blank=True) type = models.IntegerField(choices=((1, "普通用户"), (2, "vip"), (3, "svip")), default=1)
views.py:
class TestPermission(APIView):
permission_classes = [MyAllow, ]
def get(self, request):
return Response('vip能看的电影')
# 权限组件
class MyAllow(object):
message = '权限不足' # 自定义权限不足信息,若返回false,则执行permisson_denied(.....)方法
def has_permission(self, request, view):
user_obj = request.user
# print(user_obj)
# if user_obj.type == 1:
# return False
return True
-
示例:
views.py:
class MyAllow(object):
message = '权限不足'
def has_permission(self, request, view):
user_obj = request.user # 这里就是认证组件那里的准备了(哈哈哈)
print(user_obj)
if user_obj.type == 1:
return False
return True
- 认证组件那的图:
views.py:
class TestPermission(APIView):
authentication_classes = [MyAuth, ]
permission_classes = [MyAllow, ]
def get(self, request):
return Response('vip能看的电影')
13.频率组件
13.1 自定义频率组件
-
还是看源码:
-
自定义频率类组件:
class MyThrottle(): def __init__(self): self.history = [] def allow_request(self, request, view): ip = request.META.get('REMOTE_ADDR', '') if ip not in VISIT_RECORD: VISIT_RECORD[ip] = [time.time(), ] else: history = VISIT_RECORD[ip] self.history = history history.insert(0, time.time()) while self.history[0] - self.history[-1] > 60: self.history.pop() if not len(self.history) <= 5: return False return True # 该频率类组件效果:一分钟只能访问五次
views.py: class TestPermission(APIView): throttle_classes = [MyThrottle,] def get(self, request): return Response('vip能看的电影')
-
但单单这样还是不行,另一个钩子还没有写:wait(),继续第4步看第5步
-
下面有点跳跃:
- throttle_durations是一个列表,里面装着执行钩子函数wait的返回值
- 之后循环该列表,取出不是空值的返回值组成一个新的列表 ---> 也就是说该列表也存储钩子函数wait方法的返回值
- 再然后拿出最大的值将值当作参数传入throttled方法
- 执行throttled方法---> duration = wait方法返回值中的最大值
- duration将值给wait
- Throttled类接收wait值并进行实例化
- 通过格式化字符串我们可以知晓wait是一个整数值
- 通过反推,我们也知道了wait钩子应该返回一个整数值
- 如果继续看着源码下去emmmm,有时间再看吧,我现在在做笔记,再往下看笔记都做不完了
- 大概知道wait会返回什么就行
-
代码进一步优化示例:
class MyThrottle(): def __init__(self): self.history = [] def allow_request(self, request, view): ip = request.META.get('REMOTE_ADDR', '') if ip not in VISIT_RECORD: VISIT_RECORD[ip] = [time.time(), ] else: history = VISIT_RECORD[ip] self.history = history history.insert(0, time.time()) while self.history[0] - self.history[-1] > 60: self.history.pop() if not len(self.history) <= 5: return False return True # 需要等待的时间 def wait(self): return 60 - (self.history[0] - self.history[-1])
-
结果:
13.2 DRF自带的频率组件的运用
from rest_framework import throttling
# 进去看看有哪些频率类
# 看源码
class Throttle(throttling.SimpleRateThrottle):
scope = 'PL' # 频率
def get_cache_key(self, request, view):
return self.get_ident(request) # 获取ip地址去了 ---> ip = request.META.get('REMOTE_ADDR', '')
- 第二步应该拿到访问的ip,可以调用父类的方法
BaseThrottle
- 第四步暂时忘掉吧...有点急了
- 应该先执行
__init__
方法的 - 第四步和我们的自定义中的
history = VISIT_RECORD[ip]
是一样的,是去获取该ip访问的历史记录去了 - 最后看代码,在这最后最后面
- value是一个字符串,只不过是有一个可以转化为数字的
class Throttle(throttling.SimpleRateThrottle):
scope = 'PL' # 频率
def get_cache_key(self, request, view):
return self.get_ident(request) # 获取ip地址去了 ---> ip = request.META.get('REMOTE_ADDR', '')
settings.py:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES':{
"PL":'3/m' # 设置访问频率:一分钟只能访问三次
}
}
views.py:
class TestPermission(APIView):
throttle_classes = [Throttle, ]
def get(self, request):
return Response('vip能看的电影')
- 这次记得有点乱.....
14.分页组件
from rest_framework import pagination
-
DRF提供三种分页
14.1 -- ///////?page=1&size=5
- 第几页有多少数据
- page:页数 size:数据量
# 自定义分页类:
from rest_framework import pagination
class PageNumberPaginator(pagination.PageNumberPagination):
page_size = 2 # 每页的数据量
max_page_size = 3 # 每页中最大的数据量(做约束)
page_size_query_param = 'size' # 关键字
page_query_param = 'page' # 关键字
# 在视图中的运用(这次我们是在get对应list方式下运用):
# 是为了了解之后我们在普通模式下怎么运用分页类
from utils.pagination import PageNumberPaginator
class BookModelView(viewsets.ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = BookSerilize
pagination_class = PageNumberPaginator # 配置分页类
-
这次我们需要从视图的第三次封装开始(为了了解第三步为什么那样干)
- 注意执行方法时所传的参数!!!(有大用处)
- 还是注意传入的参数,有大用
-
看结果:
-
在APIView中使用:
- 如果你看了上面的图的话,应该知道为什么调用什么方法,传入什么参数
views.py: class PageView(APIView): def get(self, request): queryset = models.Book.objects.all() # 实例化分页器对象 page_obj = PageNumberPaginator() # 看上面,要实例化分页类 # 用我自己的分页器调用父类的分页方法进行分页 page_data = page_obj.paginate_queryset(queryset, request) # 然后调用分页类进行分页(注意参数)上面有 # 序列化分页好的数据 ser_obj = BookSerilize(page_data, many=True) # 给响应添加上一页下一页的链接 return page_obj.get_paginated_response(ser_obj.data)
14.2 -- ///////?limit=2&offset=3
- offset:从第几条数据开始
- limit:限制数据条数
-
分页类:
# 导入模块 from rest_framework import pagination # 继承 pagination.LimitOffsetPagination class LimitOffsetPaginator(pagination.LimitOffsetPagination): default_limit = 1 limit_query_param = 'limit' offset_query_param = 'offset' # 与上面一样,关键字可自己定义
-
views.py:
class PageView(APIView): def get(self, request): queryset = models.Book.objects.all() # 实例化分页器对象 page_obj = LimitOffsetPaginator() # 用我自己的分页器调用父类的分页方法进行分页 page_data = page_obj.paginate_queryset(queryset, request) # 序列化分页好的数据 ser_obj = BookSerilize(page_data, many=True) # 给响应添加上一页下一页的链接 return page_obj.get_paginated_response(ser_obj.data)
-
结果:
14.3 -- ///////?cursor=xxxx
- 该分页只有上一页下一页
- cursor是加密的
-
分页类:
class CursorPaginator(pagination.CursorPagination): ordering = '-id' # 按照id倒序排序 cursor_query_param = 'cursor' # 关键字 page_size = 2 # 每页显示的数据量
-
views.py:
class PageView(APIView): def get(self, request): queryset = models.Book.objects.all() # 实例化分页器对象 page_obj = CursorPaginator() # 用我自己的分页器调用父类的分页方法进行分页 page_data = page_obj.paginate_queryset(queryset, request) # 序列化分页好的数据 ser_obj = BookSerilize(page_data, many=True) # 给响应添加上一页下一页的链接 return page_obj.get_paginated_response(ser_obj.data)
-
结果:
14.4 简单总结
- 如果只需要知道如何使用,可以直接copy代码
- 不过注意参数和调用的方法
- 还想知其所以然的可以看14.1中的图,就知道为什么这么使用了
15.解析器
# 解析器的作用就是服务端接收客户端传过来的数据,把数据解析成自己想要的数据类型的过程。
# 在了解解析器之前我们需要知道Accept以及ContentType请求头。
Accept:是告诉对方我能解析什么样的数据,通常也可以表示我想要什么样的数据。
ContentType:是告诉对方我给你的是什么样的数据类型。
from django.core.handlers.wsgi import WSGIRequest
# 这个是视图继承View时打印的request的类型 --> type(request)
# 也就是django的request
from rest_framework.request import Request
# 这个是视图继承APIView时打印的request -->type(request)
# 也就是DRF的request
15.1 django的解析器
request.POST Form表单的数据
request.FILES 文件的数据
reqeust.body 例如json格式的数据
-
看源码:从request开始看 -->
from django.core.handlers.wsgi import WSGIRequest
- 还记得上传文件吗?form表单应该指定
enctype="multipart/form-data"
application/x-www-form-urlencoded不是不能上传文件,是只能上传文本格式的文件, multipart/form-data是将文件以二进制的形式上传,这样可以实现多种类型的文件上传
- 还记得上传文件吗?form表单应该指定
15.2 DRF的解析器
我们想一个问题~什么时候我们的解析器会被调用呢~~ 是不是在request.data拿数据的时候~
我们说请求数据都在request.data中,那我们看下这个Request类里的data~~
-
negotiator = rest_framework.negotiation.DefaultContentNegotiation() self.parsers = [ 'rest_framework.parsers.JSONParser','rest_framework.parsers.FormParser', 'rest_framework.parsers.MultiPartParser']
-
这里就不贴图了.... 跟着源码走过去就可以找到
-
于是我就直接把值拿出来了....继续往下执行代码~~
简单概括
-- DRF解析器
-- 拿前端传过来的Content-Type 跟我自己所有的解析器进行匹配
-- 匹配上了返回这个解析器
-- 并且调用这个解析器的parse方法 进行解析数据
16.渲染器
渲染器就是友好的展示数据~~
DRF给我们提供的渲染器有~~
json
浏览器的
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
),
17. 同源策略
17.1 一个源的定义
如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。
举个例子:
下表给出了相对http://a.xyz.com/dir/page.html同源检测的示例:
URL | 结果 | 原因 |
---|---|---|
http://a.xyz.com/dir2/other.html | 成功 | |
http://a.xyz.com/dir/inner/another.html | 成功 | |
https://a.xyz.com/secure.html | 失败 | 不同协议 ( https和http ) |
http://a.xyz.com:81/dir/etc.html | 失败 | 不同端口 ( 81和80) |
http://a.opq.com/dir/other.html | 失败 | 不同域名 ( xyz和opq) |
17.2 示例
- 代码:
urls.py:
urlpatterns = [
url(r'^test/', TestView.as_view()),
]
views.py:
class TestView(View):
def get(self, request):
return HttpResponse('ok')
def post(self, request):
return HttpResponse('post~~ ok')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<button id="d1">点我发送请求</button>
</body>
<script>
$('#d1').click(function () {
$.ajax({
url:'http://127.0.0.1:8000/test/',
type:'get',
success: function (data) {
console.log(data)
}
})
})
</script>
</html>
-
结果:
-
但是后端接收到了请求,并且是200,这是为什么还是不成功呢?
-
这都是浏览器做的手脚~~~
-
同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以xyz.com下的js脚本采用ajax读取abc.com里面的文件数据是会被拒绝的。
-
但我们为什么可以拿到jQuery文件呢? ---> 因为浏览器的同源策略阻止ajax请求不阻止src请求,虽然src也是get请求
17.3 JSONP解决跨域问题
'浏览器不是不阻止src请求吗,那我们将数据请求地址放入src'
-
示例:
views.py: class TestView(View): def get(self, request): return HttpResponse("test('ok')")
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script> </head> <body> <button id="d1">点我发送请求</button> </body> <script> function test(res) { alert(res) } </script> <script src="http://127.0.0.1:8000/test/"></script> </html>
-
这样我们果然拿到了数据
-
再一个示例:
import json class TestView(View): def get(self, request): res = {"code": 0, "data": ["SNIS-561", "SNIS-517", "SNIS-539"]} return HttpResponse("test({})".format(json.dumps(res)))
<script> function test(res) { console.log(res) } </script> <script src="http://127.0.0.1:8000/test/"></script>
17.4 CORS解决跨域问题
17.4.1 CORS简介
CORS需要浏览器和服务器同时支持。目前基本上主流的浏览器都支持CORS。所以只要后端服务支持CORS,就能够实现跨域。
17.4.2 简单请求和非简单请求介绍
- 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
- 一个请求需要同时满足以下两大条件才属于简单请求。
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2) HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
17.4.3 简单请求的处理
- 对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个
Origin
字段。 - 结合17.2示例的代码,然后启动服务
上面的头信息中,'Origin'字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果'Origin'指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。但浏览器发现,这个回应的头信息没有包含'Access-Control-Allow-Origin',就会报错....
所以响应头也应该含有该值,也就是在值返回时服务器在响应头设置该值
# 写一个中间件,服务器返回时要经过中间件
from django.utils.deprecation import MiddlewareMixin
class My_Ware(MiddlewareMixin):
def process_response(self, request, response):
response['Access-Control-Allow-Origin'] = 'http://localhost:63342' # 指定可以跨域的源
# response['Access-Control-Allow-Origin'] = '*' # 允许所有请求跨域请求数据
return response
17.4.4 非简单请求的处理
- 当我们的请求发送或者数据格式不在简单请求内时,就会发送非简单请求
<body>
<button id="d1"> 点我发送请求 </button>
</body>
<script>
$("#d1").click(function () {
$.ajax({
url:'http://127.0.0.1:8000/test/',
type:"put", // 请求发送为put,发送复杂请求
contentType:'application/json', // 数据格式为json
success:function (res) {
console.log(res)
}
})
});
</script>
- 当请求方式不在简单请求内时:
- 当数据格式不再简单请求三种内时:
-
后端接收到的请求方式:
对于非简单请求,浏览器通常都会在请求之前发送一次 'OPTIONS' 预检 请求。该请求会向后端服务询问是否允许从当前源发送请求并且询问允许的 请求方法 和 请求头字段。
-
解决方式:
from django.utils.deprecation import MiddlewareMixin class My_Ware(MiddlewareMixin): def process_response(self, request, response): # 给响应头加上 Access-Control-Allow-Origin 字段 并简单的设置为 * response['Access-Control-Allow-Origin'] = '*' if request.method == 'OPTIONS': # 先判断是否是复杂请求 # 允许发送 PUT 请求 response['Access-Control-Allow-Methods'] = 'PUT, DELETE' # 允许在请求头中携带 Content-type字段,从而支持发送json数据 response['Access-Control-Allow-Headers'] = 'Content-type' return response
17.5 使用django-cors-headers
django-cors-headers处理跨域请求,一个为响应添加跨源资源共享(CORS)头的Django应用。这允许从其他源向Django应用程序发出浏览器内请求。
18.ContentType组件
18.1 运用
在生活中,我们可以在商场或者小贩那看见某些商品为了促销,而搞优惠策略
如果我们需要用表进行设计怎么弄?
某个商品都会有自己相对应的优惠,而一些商品也是属于同一类,比如西瓜和香蕉都是水果
18.1.1 第一版设计
# 设计表
class Food(models.Model):
name = models.CharField(max_length=32)
class Fruit(models.Model):
name = models.CharField(max_length=32)
# 优惠表
class CouPon(models.Model):
title = models.CharField(max_length=32)
food = models.ForeignKey(to='Food', on_delete=models.CASCADE)
fruit = models.ForeignKey(to='Fruit', on_delete=models.CASCADE)
-
直观感受一下:
-
有没有发现什么问题?
优惠表一些是空的,而且我们现在不过是两张表,如果很多怎么办....那后面全是null... 所以这样设计不合理
18.1.2 第二版设计
class CouPon(models.Model):
title = models.CharField(max_length=32)
table = models.ForeignKey(to="MyTables", on_delete=models.CASCADE)
object_id = models.IntegerField() # 这个字段是为了定位到具体优惠的对象
class MyTables(models.Model):
app_name = models.CharField(max_length=32)
table_name = models.CharField(max_length=32)
重新设计了表,并且添加了一张对应表的表(因为生成的表名是app名_表名),可能两个不同的app中的表一样,所以新添加的表也要有对应表的app名字段
现在感觉怎么样?是不是这样一下就感觉好多了?(不过可能有点绕,慢慢捋一下)
不过这样是不是也稍微有点麻烦?一是我们要新定义一张表,二是该表还要对应的app名字段和表名字段,是不是...
所以:现在引出我们的主角——>contenttype组件,这个组件将我们要做的事干了
18.2.3 第三版设计
与第二版一样的操作效果,不过该干的事contenttype组件帮我们干了(生成一张对应的表的表,而且会自动生成该有的字段值)
from django.contrib.contenttypes.models import ContentType # 该表在这
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
# 通用的外键(GenericForeignKey) (GenericRelation)只用于反向查询的--->可以理解为related_name
class Food(models.Model):
name = models.CharField(max_length=32)
# 只用于反向查询
coupons = GenericRelation(to="CouPon")
class Fruit(models.Model):
name = models.CharField(max_length=32)
coupons = GenericRelation(to="CouPon")
class CouPon(models.Model):
title = models.CharField(max_length=32)
# 第三版 用django自带的ContentType表
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
obj_id = models.IntegerField() # 用来定位到具体优惠的对象
# 不会生成字段,只用于关联对象的 --> 让这两个字段建立关系
content_object = GenericForeignKey("content_type", "object_id") # 不知道与顺序是否有关
18.2 在视图中的使用
class Test(APIView):
def get(self, request):
# 找到表id以及表对象
# content_type_obj = ContentType.objects.filter(app_label="JsonP", model='food').first()
# print(content_type_obj)
# model_class = content_type_obj.model_class() 可以打印 type
# print(model_class) 可以拿到具体的model表,比如:food、fruit
# print(content_type_obj.id)
# 给酱香饼添加优惠卷
# food_obj= models.Food.objects.filter(id=1).first()
# models.CouPon.objects.create(title='酱香饼买一送一', content_object=food_obj)
# models.CouPon.objects.create(title='酱香饼买一送二', content_object=food_obj)
# 给黑美人添加优惠卷
# fruit_obj = models.Fruit.objects.get(id=2)
# 第一种:直接用content_object=具体的表中对象来创建优惠
# models.CouPon.objects.create(title='黑美人西瓜打2折', content_object=fruit_obj)
# 第二种:常规操作,找到对应的表id和具体的对象id然后来创建优惠
# models.CouPon.objects.create(title='黑美人西瓜打2折', content_type_id=8, object_id=2)
# 查询优惠卷绑定对象
# coupon_obj = models.CouPon.objects.get(id=1)
# print(coupon_obj.content_object.name)
obj = models.Fruit.objects.filter(id=2).first()
print(obj.coupons.all())
return HttpResponse("ok")
标签:obj,self,request,id,book,class,DRF
From: https://www.cnblogs.com/WWW-ZZZ/p/17180215.html