DRF框架学习
- 介绍
Django REST framework 框架是一个用于构建Web API 的强大而又灵活的工具。通常简称为DRF框架 或 REST framework。
- 环境搭建
2.1 安装
安装django
pip install django
安装DRF
pip install djangorestframework
2.2 创建工程
django-admin startproject 工程名称
2.3创建子应用
2.3.1
python manage.py startapp 子应用名称
2.3.2
子应用目录说明
查看此时的工程目录,结构如下:
子应用目录
admin.py 文件跟网站的后台管理站点配置相关。
apps.py 文件用于配置当前子应用的相关信息。
migrations 目录用于存放数据库迁移历史文件。
models.py 文件用户保存数据库模型类。
tests.py 文件用于开发测试用例,编写单元测试。
views.py 文件用于编写Web应用视图。
2.3.3 注册安装子应用
在工程配置文件settings.py中,INSTALLED_APPS项保存了工程中已经注册安装的子应用,初始工程中的INSTALLED_APPS如下
2.4 REST接口开发的核心任务
将请求的数据(如JSON格式)转换为模型类对象
操作数据库
将模型类对象转换为响应的数据(如JSON格式)
- 序列化器 (数据从外面进来数据库和数据库出去展示给用户必备)
3.1序列化器的定义
from?rest_framework import?serializers
class?序列化器类名(serializers.Serializer):
????# 序列化器字段 = serializers.字段类型(选项参数)
????# ...
3.2序列化器的使用
使用序列化器时需要先创建一个序列化器类的对象。
创建:
序列化器类(instance=None, data=empty, **kwarg)
说明:
- 用于序列化时,将实例对象传给instance参数
# 创建User对象
user = User(name='smart', age=18)
# 使用UserSerializer将user对象序列化为如下字段数据:{'name': 'smart', 'age': 18}
serializer = UserSerializer(user)
# 获取序列化之后的数据
serializer.data
- 用于反序列化时,将要被反序列化的数据传给data参数
# 准备数据
data = {'name': 'admin', 'age': 30}
# 使用UserSerializer对data中的数据进行反序列化校验
serializer = UserSerializer(data=data)
# 调用is_valid进行数据校验,成功返回True,失败返回False
serializer.is_valid()
# 获取校验失败之后的错误提示信息
serializer.errors
# 获取校验通过之后的数据
serializer.validated_data
3.3字段类型和选项参数
3.4 序列化器操作
3.4.1 序列化单个对象
from?booktest.models import?BookInfofrom?booktest.serializers import?BookInfoSerializer# 查询获取图书对象
book = BookInfo.objects.get(id=2)# 创建序列化器对象
serializer = BookInfoSerializer(book)# 获取序列化之后的数据
serializer.data?# {'id': 2, 'btitle': '天龙八部', 'bpub_date': '1986-07-24', 'bread': 36, 'bcomment': 40}
3.4.2 序列化多个对象
如果要被序列化的是包含多条数据的查询集QuerySet或list,可以通过添加many=True参数说明。
book_qs = BookInfo.objects.all()# 创建序列化器对象
serializer = BookInfoSerializer(book_qs, many=True)# 获取序列化之后的数据
serializer.data# [# ??OrderedDict([('id', 2), ('btitle', '天龙八部'), ('bpub_date', '1986-07-24'), ('bread', 36), ('bcomment', 40)),# ??OrderedDict([('id', 3), ('btitle', '笑傲江湖'), ('bpub_date', '1995-12-24'), ('bread', 20), ('bcomment', 80)),# ??OrderedDict([('id', 4), ('btitle', '雪山飞狐'), ('bpub_date', '1987-11-11'), ('bread', 58), ('bcomment', 24)),# ??OrderedDict([('id', 5), ('btitle', '西游记'), ('bpub_date', '1988-01-01'), ('bread', 10), ('bcomment', 10)])# ]
3.4.3反序列化操作
基本使用
# 1. 创建序列化器对象
serializer = 序列化器类(data=<待校验字典数据>)
# 2. 数据校验:成功返回True,失败返回False
serializer.is_valid()
# 3. 获取校验成功之后的数据
serializer.validated_data
数据保存
1)在数据校验通过之后,想要基于validated_data完成数据对象的创建,可以通过序列化器对象.save()进行数据的保存。
2)在save方法内部会调用序列化器类的create或update方法,可以在create方法中实现数据新增,update方法中实现数据更新。
- 创建序列化器对象的时候,如果没有传递instance实例,则调用save()方法的时候,create()被调用,相反,如果传递了instance实例,则调用save()方法的时候,update()被调用。
from?booktest.serializers import?BookInfoSerializerfrom?booktest.models import?BookInfo
# 1. 图书新增
data = {'btitle': '封神演义'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid() ?# True
serializer.save() ?# 调用序列化器类的create方法,实现图书的新增
# 2. 图书更新
book = BookInfo.objects.get(id=2)
data = {'btitle': '倚天剑'}
serializer = BookInfoSerializer(book, data=data)
serializer.is_valid() ?# True
serializer.save() ?# 调用序列化器类的update方法,实现图书的更新
3.5 ModelSerializer使用
如果序列化器类对应的是Django的某个模型类,则定义序列化器类时,可以直接继承于ModelSerializer。
ModelSerializer是Serializer类的子类,相对于Serializer,提供了以下功能:
基于模型类字段自动生成序列化器类的字段
包含默认的create()和update()方法的实现
3.5.1 基本使用
创建一个BookInfoSerializer类:
class?BookInfoSerializer(serializers.ModelSerializer):
????"""图书数据序列化器"""
????class?Meta:
????????model = BookInfo
????????fields = '__all__'
model:指明序列化器类对应的模型类
fields:指明依据模型类的哪些字段生成序列化器类的字段
可以在python manage.py shell中查看的BookInfoSerializer自动生成的具体字段:
3.5.2 指定序列化的字段
1) 使用fields来指明依据模型类的哪些字段生成序列化器类的字段,__all__表明包含所有字段,也可以指明具体哪些字段,如:
class?BookInfoSerializer(serializers.ModelSerializer):
????"""图书数据序列化器"""
????class?Meta:
????????model = BookInfo
????????fields = ('id', 'btitle', 'bpub_date')
2) 使用exclude可以指明排除哪些字段,如:
class?BookInfoSerializer(serializers.ModelSerializer):
????"""图书数据序列化器"""
????class?Meta:
????????model = BookInfo
????????exclude = ('image',)
3) 指明只读字段
可以通过read_only_fields指明只读字段,即仅用于序列化的字段。
class?BookInfoSerializer(serializers.ModelSerializer):
????"""图书数据序列化器"""
????class?Meta:
????????model = BookInfo
????????fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment')
????????read_only_fields = ('id', 'bread', 'bcomment')
3.5.3 添加额外参数
可以使用extra_kwargs参数为自动生成的序列化器类字段添加或修改原有的选项参数。
3.6 反序列化方法
3.6.1 保存数据
1、保存并创建新的记录到model里面
输入是字典格式数据,一般是从request.data获取输入,输出是数据模型的对象类型数据
serializer = MyModelSerializer(data={'field1': 'new_value1'})
if serializer.is_valid():
????serializer.save()
2、保存数据到指定的记录到model里面
from myapp.models import MyModel
# 假设要更新的数据对象的主键为 1
my_object = MyModel.objects.get(pk=1)
# 创建序列化器实例,并传入要更新的数据对象和部分字段数据
serializer = MyModelSerializer(instance=my_object, data={'field1': 'new_value1'})
if serializer.is_valid():
????serializer.save()
- 模型相关
4.1 模型定义
from django.db import models
#定义图书模型类BookInfo
class BookInfo(models.Model):
????btitle = models.CharField(max_length=20)#图书名称
????bpub_date = models.DateField()#发布日期
????bread = models.IntegerField(default=0)#阅读量
????bcomment = models.IntegerField(default=0)#评论量
isDelete = models.BooleanField(default=False)#逻辑删除
btitle ?和 bpub_date 是模型的 字段。每个字段都被指定为一个类属性,并且每个属性映射为一个数据库列。
模型中每一个字段都应该是某个 Field 类的实例
4.2 迁移
python manage.py makemigrations
python manage.py migrate
4.3 模型类
4.3.1 字段类型
列表的数据类型:
方法一:
from django.db import models
from django.contrib.postgres.fields import ArrayField
class YourModel(models.Model):
your_list_field?= ArrayField(models.CharField(max_length=100)) ?# 以字符字段为例,您可以使用其他字段类型
方法二:
from django.db import models
class YourModel(models.Model):
? ? your_list_field?= models.JSONField() ? # 其他字段
4.3.2 字段选项
4.3.2.1 Choice字段选项的介绍
使用一:
from?django.db?import?models
class?Person(models.Model):
? ? SHIRT_SIZES?=?[
? ? ? ? ("S", "Small"),
? ? ? ? ("M", "Medium"),
? ? ? ? ("L", "Large"),
? ? ]
? ? name?=?models.CharField(max_length=60)
? ? shirt_size?=?models.CharField(max_length=1, choices=SHIRT_SIZES)
使用二:
from?django.db?import?models
class?Runner(models.Model):
? ? MedalType?=?models.TextChoices("MedalType", "GOLD SILVER BRONZE")
? ? name?=?models.CharField(max_length=60)
? ? medal?=?models.CharField(blank=True, choices=MedalType.choices, max_length=10)
使用三:
class?Task(TimeStamped):
? ? class?TaskAgentType(models.TextChoices):
? ? ? ? NO_MAGIC =?"NO_MAGIC", "No magic"
? ? ? ? TASK_SET =?"TASK_SET", "Task set"
? ? ? ? FC_ZONE =?"FC_ZONE", "Falcon zone"
? ? ? ? TS_TASK =?"TS_TASK", "TaskSys task"
? ? ? ? MIXC_JOINER =?"MIXC_JOINER", "MixC-styled joiner"
? ? agent_type =?models.CharField(max_length=20,choices=TaskAgentType.choices)
4.3.3 models的入参和结果类型
入参:
返回:返回是一个列表集合,如果要获取其中一个对象就要用列表切片,如果要获取对象的某个字段值就直接 对象.字段名称
unit_id?=?Round.objects.filter(id=round).first().unit
4.3.4 models的方法
model转字典
from?django.forms?import?model_to_dict
4.3.5 model属性
objects
模型当中最重要的属性是 Manager。它是 Django 模型和数据库查询操作之间的接口,并且它被用作从数据库当中 获取实例,如果没有指定自定义的 Manager 默认名称是 objects。Manager 只能通过模型类来访问,不能通过模型实例来访问。
4.3.6
https://docs.djangoproject.com/zh-hans/4.2/ref/models/options/#verbose-name
4.4条件查询
说明:属性名称和比较运算符间使用两个下划线,所以属性名不能包括多个下划线。
属性名称__比较运算符=值
4.4.1 条件运算符
exact:表示判等。
list=BookInfo.objects.filter(id__exact=1)
可简写为:
list=BookInfo.objects.filter(id=1)
模糊查询
contains:是否包含。
说明:如果要包含%无需转义,直接写即可。
startswith、endswith:以指定值开头或结尾。
空查询
isnull:是否为null。
list = BookInfo.objects.filter(btitle__isnull=False)
范围查询
in:是否包含在范围内。
list = BookInfo.objects.filter(id__in=[1, 3, 5])
比较查询
gt、gte、lt、lte:大于、大于等于、小于、小于等于。
不等于的运算符,使用exclude()过滤器。
日期查询
year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。
list = BookInfo.objects.filter(bpub_date__year=1980)
4.4.2 属性F比较
之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢? 答:使用F对象,被定义在django.db.models中。
语法如下:
F(属性名)
list = BookInfo.objects.filter(bread__gt=F('bcomment') * 2)
F(属性名)
4.4.3 Q逻辑判断
多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。
list=BookInfo.objects.filter(bread__gt=20,id__lt=3)
或
list=BookInfo.objects.filter(bread__gt=20).filter(id__lt=3)
Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或。
from django.db.models import Q
list = BookInfo.objects.filter(Q(bread__gt=20) | Q(pk__lt=3))
Q对象前可以使用~操作符,表示非not。
list = BookInfo.objects.filter(~Q(pk=3))
4.4.4 聚合函数
使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg,Count,Max,Min,Sum,被定义在django.db.models中。
from django.db.models import Sum
...
list = BookInfo.objects.aggregate(Sum('bread'))
注意aggregate的返回值是一个字典类型,格式如下:
?{'聚合类小写__属性名':值}
??如:{'sum__bread':3}
4.5 查询集
4.6关联
4.6.1 一对多关系
#定义图书模型类BookInfo
class BookInfo(models.Model):
????btitle = models.CharField(max_length=20)#图书名称
????bpub_date = models.DateField()#发布日期
????bread = models.IntegerField(default=0)#阅读量
????bcomment = models.IntegerField(default=0)#评论量
????isDelete = models.BooleanField(default=False)#逻辑删除
#定义英雄模型类HeroInfo
class HeroInfo(models.Model):
????hname = models.CharField(max_length=20)#英雄姓名
????hgender = models.BooleanField(default=True)#英雄性别
????isDelete = models.BooleanField(default=False)#逻辑删除
????hcomment = models.CharField(max_length=200)#英雄描述信息
????hbook = models.ForeignKey('BookInfo')#英雄与图书表的关系为一对多,所以属性定义在英雄模型类中
4.6.2 多对多关系
class TypeInfo(models.Model):
??tname = models.CharField(max_length=20) #新闻类别
class NewsInfo(models.Model):
??ntitle = models.CharField(max_length=60) #新闻标题
??ncontent = models.TextField() #新闻内容
??npub_date = models.DateTimeField(auto_now_add=True) #新闻发布时间
??ntype = models.ManyToManyField('TypeInfo') #通过ManyToManyField建立TypeInfo类和NewsInfo类之间多对多的关系
4.6.3 关联查询
通过对象执行关联查询
在定义模型类时,可以指定三种关联关系,最常用的是一对多关系,如本例中的"图书-英雄"就为一对多关系,接下来进入shell练习关系的查询。
由一到多的访问语法:
一对应的模型类对象.多对应的模型类名小写_set
例:
b = BookInfo.objects.get(id=1)
b.heroinfo_set.all()
由多到一的访问语法:
多对应的模型类对象.多对应的模型类中的关系类属性名
例:
h = HeroInfo.objects.get(id=1)
h.hbook
访问一对应的模型类关联对象的id语法:
多对应的模型类对象.关联类属性_id
例:
h = HeroInfo.objects.get(id=1)
h.book_id
通过模型类执行关联查询
由多模型类条件查询一模型类数据:
语法如下:
关联模型类名小写__属性名__条件运算符=值
list = BookInfo.objects.filter(heroinfo__hcontent__contains='八')
由一模型类条件查询多模型类数据: 语法如下:
一模型类关联属性名__一模型类属性名__条件运算符=值
例:查询书名为“天龙八部”的所有英雄。
list = HeroInfo.objects.filter(hbook__btitle='天龙八部')
4.7模型类扩展Meta选项
元选项
4.8模型执行方法
4.8.1 创建对象
1、create创建方法
p?= Person.objects.create(first_name="Bruce", last_name="Springsteen")
Or
p?= Person(first_name="Bruce", last_name="Springsteen")
p.save(force_insert=True)
2、get_or_create()
如果找到多个对象,get_or_create() 会引发 MultipleObjectsReturned。如果没有找到对象,get_or_create()
4.8.2查询对象
1、acount()
Asynchronous version: acount()
返回一个整数,表示数据库中与 QuerySet 匹配的对象数量。
# Returns the total number of entries in the database.
Entry.objects.count()
# Returns the number of entries whose headline contains 'Lennon'
Entry.objects.filter(headline__contains="Lennon").count()
4.8.3 删除对象
b?=?Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
4.8.5?复制模型实例
在这个特定的情况下,blog._state.adding 被设置为 True,表示该 blog 对象即将被添加到数据库中,而不是已经存在于数据库中。这意味着该对象尚未在数据库中持久化(保存),并且在调用 save() 方法后,会被创建为新的数据库记录
blog?=?Blog(name="My blog", tagline="Blogging is easy")
blog.save() ?# blog.pk == 1
blog.pk =?None
blog._state.adding =?True
blog.save() ?# blog.pk == 2
4.8.6 model转字典
from?django.forms?import?model_to_dict
- 视图定义相关
5.1 APIView?和request
APIView是REST framework提供的所有视图的基类,继承自Django的View类。
APIView与View的不同之处在于:
传入到视图中的request对象是REST framework的Request对象,而不再是Django原始的HttpRequest对象;
视图可以直接返回REST framework的Response对象,响应数据会根据客户端请求头Accpet自动转换为对应的格式进行返回;
任何APIException异常都会被捕获到,并且处理成合适的响应信息返回给客户端;
在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。
5.1.1 requests常见属性
.user??
requests.user.email
from?rest_framework.views import?APIViewfrom?rest_framework.response import?Responsefrom?django.http import?Http404
# url(r'^goods/$', views.GoodsView.as_view()),class?GoodsView(APIView):
????def?get(self, request):
????????# print(request.data)
????????# print(request.query_params)
????????# 抛出异常
????????raise?Http404
????????return?Response({'msg': 'hello'})
5.2 构造request请求体
? ? ? ? ? ? from?django.http?import?HttpRequest
? ? ? ? ? ? from?rest_framework.request import?Request
http_request?=?HttpRequest()
? ? ? ? ? ? http_request.method?=?'POST'
? ? ? ? ? ? http_request.POST?=?{'workflow': 'ff_fit'} ?
# 设置请求数据,字典形式传递
? ? ? ? ? ? # 创建一个 DRF Request 实例,将 HttpRequest 对象作为参数传递
? ? ? ? ? ? drf_request?=?Request(http_request)
? ? ? ? ? ? print(request.data)
? ? ? ? ? ? res?=?self.submit_workflow(drf_request, node=node_fit.id)
5.2 GenericAPIView
序列器相关属性和方法
属性:
serializer_class:指明视图使用的序列化器
方法:
get_serializer_class(self)
返回序列化器类,默认返回serializer_class,可以重写。
get_serializer(self, args, **kwargs)
返回创建序列化器类的对象,如果我们在视图中想要创建序列化器对象,可以直接调用此方法。------常用
def?get(self, request):
????????"""
????????获取所有的图书数据
????????"""
????????queryset = self.get_queryset()
????????# 序列化所有图书数据
????????serializer = self.get_serializer(queryset, many=True)
????????return?Response(serializer.data)
????def?post(self, request):
????????"""
????????新增一个图书数据
????????"""
????????# 反序列化-数据校验
????????serializer = self.get_serializer(data=request.data)
????????serializer.is_valid(raise_exception=True)
????????# 反序列化-数据保存(save内部会调用序列化器类的create方法)
????????serializer.save()
????????return?Response(serializer.data, status=status.HTTP_201_CREATED)
数据库查询相关属性和方法
属性:
queryset:指明使用的数据查询集
方法:
get_queryset(self)
返回视图使用的查询集
????def?get(self, request):
????????"""
????????获取所有的图书数据
????????"""
????????queryset = self.get_queryset()
????????# 序列化所有图书数据
????????serializer = self.get_serializer(queryset, many=True)
????????return?Response(serializer.data)
get_object(self)
返回从视图使用的查询集中查询指定的对象(默认根据pk进行查询),如查询不到,此方法会抛出Http404异常。
def?delete(self, request, pk):
????????"""
????????删除指定图书:
????????"""
????????instance = self.get_object()
????????instance.delete()
????????return?Response(status=status.HTTP_204_NO_CONTENT)
其他设置属性
pagination_class:指明分页控制类
filter_backends:指明过滤控制后端
5.3 Mixin扩展类
DRF已经将GenericAPIView这些代码做了封装,就是5个Mixin扩展类。
5.3.1 ListModelMixin
列表视图扩展类,提供list(request, *args, **kwargs)方法快速实现列表视图,返回200状态码。
该Mixin的list方法会对数据进行过滤和分页。
from?rest_framework.mixins import?ListModelMixin
class?BookListView(ListModelMixin, GenericAPIView):
????queryset = BookInfo.objects.all()
????serializer_class = BookInfoSerializer
????def?get(self, request):
????????return?self.list(request)
5.3.2 CreateModelMixin
创建视图扩展类,提供create(request, *args, **kwargs)方法快速实现创建资源的视图,成功返回201状态码。
如果序列化器对前端发送的数据验证失败,返回400错误。
5.3.3 RetrieveAPIView
详情视图扩展类,提供retrieve(request, *args, **kwargs)方法,可以快速实现返回一个存在的数据对象。
如果存在,返回200, 否则返回404
5.3.4UpdateModelMixin
更新视图扩展类,提供update(request, *args, **kwargs)方法,可以快速实现更新一个存在的数据对象。
成功返回200,序列化器校验数据失败时,返回400错误。
5.3.5DestroyModelMixin
删除视图扩展类,提供destroy(request, *args, **kwargs)方法,可以快速实现删除一个存在的数据对象。
成功返回204,不存在返回404。
子类视图简介
1.1 ListAPIView
提供 get 方法
继承自:GenericAPIView、ListModelMixin
1.2 CreateAPIView
提供 post 方法
继承自: GenericAPIView、CreateModelMixin
1.3 RetrieveAPIView
提供 get 方法
继承自: GenericAPIView、RetrieveModelMixin
1.4 DestoryAPIView
提供 delete 方法
继承自:GenericAPIView、DestoryModelMixin
1.5 UpdateAPIView
提供 put 和 patch 方法
继承自:GenericAPIView、UpdateModelMixin
1.6 ListCreateAPIView
提供 get 和 post 方法
继承自:GenericAPIView、ListModelMixin、CreateModelMixin
1.7 RetrieveUpdateAPIView
提供 get、put、patch方法
继承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin
1.8 RetrieveDestroyAPIView
提供 get 和 delete 方法
继承自 GenericAPIView、RetrieveModelMixin、UpdateModelMixin
1.9 RetrieveUpdateDestoryAPIView
提供 get、put、patch、delete方法
继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin
5.2 视图集viewset
视图集:将操作同一组资源的处理方法放在同一个类中,这个类叫做视图集。
5.2.1 基本使用
1)继承视图集父类ViewSet(继承自ViewSetMixin和APIView)。
2)视图集中的处理方法不再以对应个请求方式(get、post等)命名,而是以对应的操作(action)命名。
list:提供一组数据
retrieve:提供单个数据
create:创建数据
update:保存数据
destory:删除数据
- 在进行URL配置时,需要明确指明某个请求方式请求某个URL地址时,对应的是视图集中的哪个处理函数。
5.2.2 常用的视图集父类
1) ViewSet
继承自APIView与ViewSetMixin,作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。
2)GenericViewSet
继承自GenericAPIView与ViewSetMixin,可以直接搭配Mixin扩展类使用。
from?rest_framework import?mixinsfrom?rest_framework.viewsets import?GenericViewSet
class?BookInfoViewSet(mixins.ListModelMixin,
??????????????????????mixins.CreateModelMixin,
??????????????????????mixins.RetrieveModelMixin,
??????????????????????mixins.UpdateModelMixin,
??????????????????????mixins.DestoryModelMixin,
??????????????????????GenericViewSet):
????queryset = BookInfo.objects.all()
????serializer_class = BookInfoSerializer
url的配置:
from?booktest import?views
urlpatterns = [
????url(r'^books/$', views.BookInfoViewSet.as_view({
????????'get':'list',
????????'post': 'create'
????})),
????url(r'^books/(?P<pk>\d+)/$', views.BookInfoViewSet.as_view({
????????'get': 'retrieve',
????????'put': 'update',
????????'delete': 'destory'
????}))
]
- ModelViewSet
继承自GenericViewSet,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。
from?rest_framework?import?viewsets
from?rest_framework?import?permissions
from?quickstart.serializers?import?UserSerializer, GroupSerializer
class?UserViewSet(viewsets.ModelViewSet):
? ? """
? ? API endpoint that allows users to be viewed or edited.
? ? """
? ? queryset?=?User.objects.all().order_by('-date_joined')
? ? serializer_class?=?UserSerializer
? ? permission_classes?=?[permissions.IsAuthenticated]
? ? ? ? # 重写 allowed_methods 属性,只允许 GET 和 POST 方法
? ? allowed_methods?=?['GET', 'POST']
url的配置:
from?booktest import?views
urlpatterns = [
????url(r'^books/$', views.BookInfoViewSet.as_view({
????????'get':'list',
????????'post': 'create'
????})),
????url(r'^books/(?P<pk>\d+)/$', views.BookInfoViewSet.as_view({
????????'get': 'retrieve',
????????'put': 'update',
????????'delete': 'destory'
????}))
]
5.4 视图集自定义对象action属性
- 其他功能
6.1关于URL
6.1.1 Django 如何处理一个请求
当一个用户请求 Django 站点的一个页面,下面是 Django 系统决定执行哪个 Python 代码使用的算法:
- Django 确定使用根 URLconf 模块。通常,这是 ROOT_URLCONF 设置的值,但如果传入 HttpRequest 对象拥有 urlconf 属性(通过中间件设置),它的值将被用来代替 ROOT_URLCONF 设置。
- Django 加载该 Python 模块并寻找可用的 urlpatterns?。它是 django.urls.path() 和(或) django.urls.re_path() 实例的序列(sequence)。
- Django 会按顺序遍历每个 URL 模式,然后会在所请求的URL匹配到第一个模式后停止,并与 path_info 匹配。
- 一旦有 URL 匹配成功,Djagno 导入并调用相关的视图,这个视图是一个Python 函数(或基于类的视图 class-based view )。视图会获得如下参数:
- 一个 HttpRequest 实例。
- 如果匹配的 URL 包含未命名组,那么来自正则表达式中的匹配项将作为位置参数提供。
- 关键字参数由路径表达式匹配的任何命名部分组成,并由 django.urls.path() 或 django.urls.re_path() 的可选 kwargs 参数中指定的任何参数覆盖。
- 如果没有 URL 被匹配,或者匹配过程中出现了异常,Django 会调用一个适当的错误处理视图。参加下面的错误处理( Error handling )。
6.1.2 使用url.resolve去解析URL
# 导入 resolve 函数和视图函数
from?django.urls?import?resolve
from?myapp.views import?hello_view
# 要测试的 URL
test_url?=?'/hello/'
# 使用 resolve 函数查找与 URL 匹配的 URL 模式
match?=?resolve(test_url)
# 打印匹配的 URL 模式和视图函数
print("匹配的 URL 模式:", match.url_name)?# hello
print("匹配的视图函数:", match.func)?# <function hello_view at 0x0EAEED60>
6.1.3 关于include
在任何时候,你的 urlpatterns 都可以 "include" 其它URLconf 模块。这实际上将一部分URL 放置于其它URL 下面。
- g 例如前面定义了一堆path,现在想把前面的url都放到api的/api/urls的下一级目录,可以用include
from?django.urls?import?path, include
from?rest_framework.routers?import?DefaultRouter
router?=?DefaultRouter()
router.register('ml-collection',import_string('csp_auto.views.MLCollectionViewSet'))
urlpatterns?=?[
path('api/', include(router.urls)),
path(
? ? ? ? 'api/gtask/fission-webhook/<str:token>/',
? ? ? ? import_string('gtask.views.ReceiveFissionTaskResult').as_view(),
? ? ? ? name="fission-task-webhook"
? ? ),??# 对应非视图集合,要使用.as_view()
urlpatterns =?[
? ? path('forgot-password/', ForgotPasswordFormView.as_view()),
? ? path('api/', include((router.urls, 'app_name'))),
]
6.1.4 Django的router.register是用于快速注册视图集(ViewSets)到URL路由的方法
在DRF中,通常使用视图集(ViewSets)来处理资源的CRUD操作(创建、读取、更新、删除)。ViewSets是一组处理特定数据模型或查询集的视图,它们提供了一系列默认的动作,如list、create、retrieve、update和destroy等。这样,你只需要定义一个ViewSet,并将其绑定到相应的URL路由上,就可以自动处理所有这些操作。
router.register就是用来实现这个绑定过程的。它接受两个参数:第一个参数是URL路由中的前缀(通常是资源的名称),第二个参数是对应的视图集。router.register方法会自动为视图集生成URL配置,包含了所有默认动作的URL映射。
# views.py
from rest_framework import viewsets
from .models import MyModel
from .serializers import MyModelSerializer
class MyModelViewSet(viewsets.ModelViewSet):
? ? queryset?= MyModel.objects.all()
? ? serializer_class?= MyModelSerializer
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import MyModelViewSet
# 创建一个默认的路由器
router?= DefaultRouter()
# 注册MyModelViewSet到路由器中
router.register(r'mymodel', MyModelViewSet)
# 将路由器的URL配置包含到Django的URL配置中
urlpatterns?= [
? ? path('', include(router.urls)),
]
通过这样的配置,MyModelViewSet中默认的动作(list、create、retrieve、update、destroy等)都会自动映射到对应的URL上,例如:
GET /mymodel/:对应list动作,获取所有资源列表。
POST /mymodel/:对应create动作,创建新的资源。
GET /mymodel/{pk}/:对应retrieve动作,获取特定ID的资源详情。
PUT /mymodel/{pk}/:对应update动作,更新特定ID的资源。
DELETE /mymodel/{pk}/:对应destroy动作,删除特定ID的资源。
这样,使用router.register可以极大地简化视图集的URL配置过程,并提高了代码的可读性和维护性。
6.1.5 动态加载视图函数
from django.utils.module_loading import import_string 是Django中用于动态导入模块的辅助函数。
import_string 函数允许你通过字符串指定的模块路径来动态导入相应的模块或对象。这在编写可配置的代码或在运行时动态加载模块时非常有用
from?django.utils.module_loading?import?import_string
from?rest_framework.routers?import?DefaultRouter
view_class_string?=?'myapp.views.MyView'
view_class?=?import_string(view_class_string)
from?django.urls?import?path, include
from?rest_framework.routers?import?DefaultRouter
router?=?DefaultRouter()
router.register('ml-collection',import_string('csp_auto.views.MLCollectionViewSet'))
urlpatterns?=?[
? ? path('api/', include(router.urls)),
6.2 认证
认证相关资料
https://docs.djangoproject.com/zh-hans/4.2/topics/auth/
修改权限认证方案
可以在DRF项目的settings.py文件中修改DRF框架的全局认证方案,如:
REST_FRAMEWORK = {
????'DEFAULT_AUTHENTICATION_CLASSES': (
????????'rest_framework.authentication.SessionAuthentication'
????)
}
指定视图认证方案设置
可以在每个视图中通过设置authentication_classess属性来设置视图的认证方案,如:
from?rest_framework.viewsets import?ReadOnlyModelViewSetfrom?rest_framework.authentication import?SessionAuthentication
from?booktest.serializers import?BookInfoSerializerfrom?booktest.models import?BookInfo
class?BookInfoViewSet(ReadOnlyModelViewSet):
????serializer_class = BookInfoSerializer
????queryset = BookInfo.objects.all()
????# 指定当前视图自己的认证方案,不再使用全局认证方案
????authentication_classess = [SessionAuthentication]
配合权限,如果认证失败会有两种可能的返回值:
401 Unauthorized 未认证
403 Permission Denied 权限被禁止
6.2.1 自定义认证
auth0_utils.py
def?get_token_from_auth0(name, password):
? ? logger.info("Try authenticating user ({0}) via auth0-agent.".format(name))
? ? token_resp?=?requests.post('{0}/token'.format(settings.AUTH0_AGENT_BASE_URL), json={
? ? ? ? 'username': name,
? ? ? ? 'password': password,
? ? })
? ? if?token_resp.ok:
? ? ? ? return?token_resp.json()
def?invalidate_access_token(token):
? ? """Request auth0-agent to mark a access token invalid"""
? ? resp?=?requests.post('{0}/logout'.format(settings.AUTH0_AGENT_BASE_URL), headers={
? ? ? ? 'Authorization': 'Bearer {0}'.format(token),
? ? })
? ? resp.raise_for_status()
def?validate_access_token_via_auth0(token) -> Tuple[bool, str]:
? ? logger.info("Try validating access token via auth0-agent.")
? ? resp?=?requests.post('{0}/token/check'.format(settings.AUTH0_AGENT_BASE_URL), headers={
? ? ? ? 'Authorization': 'Bearer {0}'.format(token),
? ? })
? ? if?resp.ok:
? ? ? ? return?True, 'OK'
? ? return?False, f'[{resp.status_code}] {resp.text}'
auth_backends.py (里面有全局认证的类)
class?DrfUserCenterAuth0Backend(BackendMixin, BaseRestAuthBackend):
? ? """
? ? See https://www.django-rest-framework.org/api-guide/authentication/
? ? and rest_framework.authentication.TokenAuthentication
? ? and c3po.server.auth0_auth.drf_backends.AuthBackend
? ? and auth0c.connector.Auth0Connector
? ? """
? ? keyword?=?'Bearer'
? ? jwks_cache_timeout?=?60?*?10
? ? def?authenticate(self, request):
? ? ? ? auth?=?get_authorization_header(request).split()
? ? ? ? if?not?auth?or?auth[0].lower() !=?self.keyword.lower().encode():
? ? ? ? ? ? return?None
? ? ? ? if?not?len(auth) ==?2:
? ? ? ? ? ? raise?rest_framework.exceptions.AuthenticationFailed(_('Invalid token header.'))
? ? ? ? token?=?auth[1].decode(HTTP_HEADER_ENCODING)
? ? ? ? return?self.authenticate_credentials(token)
? ? @property
? ? def?jwks_cache_key(self):
? ? ? ? return?'https://{}/.well-known/jwks.json'.format(settings.AUTH0_DOMAIN)
? ? def?fetch_jwks(self):
? ? ? ? jwks?=?cache.get(self.jwks_cache_key)
? ? ? ? if?jwks:
? ? ? ? ? ? return?jwks
? ? ? ? jsonurl?=?urlopen('https://{}/.well-known/jwks.json'.format(settings.AUTH0_DOMAIN))
? ? ? ? jwks?=?json.loads(jsonurl.read().decode())
? ? ? ? cache.set(self.jwks_cache_key, jwks, self.jwks_cache_timeout)
? ? ? ? return?jwks
? ? def?authenticate_credentials(self, raw_token):
? ? ? ? """
? ? ? ? See https://auth0.com/docs/quickstart/backend/python/01-authorization
? ? ? ? """
? ? ? ? try:
? ? ? ? ? ? unverified_header?=?jwt.get_unverified_header(raw_token)
? ? ? ? except?jwt.JWTError:
? ? ? ? ? ? raise?rest_framework.exceptions.AuthenticationFailed('Invalid header. Use an RS256 signed JWT Access Token')
? ? ? ? if?unverified_header['alg'] ==?'HS256':
? ? ? ? ? ? raise?rest_framework.exceptions.AuthenticationFailed('Invalid header. Use an RS256 signed JWT Access Token')
? ? ? ? jwks?=?self.fetch_jwks()
? ? ? ? rsa_jwk?=?None
? ? ? ? for?key?in?jwks["keys"]:
? ? ? ? ? ? if?key["kid"] ==?unverified_header["kid"]:
? ? ? ? ? ? ? ? rsa_jwk?=?{
? ? ? ? ? ? ? ? ? ? "kty": key["kty"],
? ? ? ? ? ? ? ? ? ? "kid": key["kid"],
? ? ? ? ? ? ? ? ? ? "use": key["use"],
? ? ? ? ? ? ? ? ? ? "n": key["n"],
? ? ? ? ? ? ? ? ? ? "e": key["e"]
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? break
? ? ? ? if?not?rsa_jwk:
? ? ? ? ? ? raise?rest_framework.exceptions.AuthenticationFailed('Invalid token kid.')
? ? ? ? for?aud?in?settings.AUTH0_ACCEPTED_AUDS:
? ? ? ? ? ? try:
? ? ? ? ? ? ? ? ak_claims?=?jwt.decode(
? ? ? ? ? ? ? ? ? ? raw_token,
? ? ? ? ? ? ? ? ? ? rsa_jwk,
? ? ? ? ? ? ? ? ? ? algorithms=['RS256'],
? ? ? ? ? ? ? ? ? ? audience=aud,
? ? ? ? ? ? ? ? ? ? issuer="https://{0}/".format(settings.AUTH0_DOMAIN)
? ? ? ? ? ? ? ? )
? ? ? ? ? ? ? ? xtp_meta?=?ak_claims['https://auth.xtalpi.com/Metadata']
? ? ? ? ? ? except?jwt.ExpiredSignatureError as?e:
? ? ? ? ? ? ? ? raise?rest_framework.exceptions.AuthenticationFailed("token is expired") from?e
? ? ? ? ? ? except?jwt.JWTClaimsError as?e:
? ? ? ? ? ? ? ? if?not?aud?==?settings.AUTH0_ACCEPTED_AUDS[-1]:
? ? ? ? ? ? ? ? ? ? # try until the last accepted aud
? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ? raise?rest_framework.exceptions.AuthenticationFailed(
? ? ? ? ? ? ? ? ? ? "incorrect claims, please check the audience and issuer"
? ? ? ? ? ? ? ? ) from?e
? ? ? ? ? ? except?Exception?as?e:
? ? ? ? ? ? ? ? raise?rest_framework.exceptions.AuthenticationFailed(
? ? ? ? ? ? ? ? ? ? "Unable to parse authentication token."
? ? ? ? ? ? ? ? ) from?e
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? break
? ? ? ? # Do extra centric token check via auth0-agent service,
? ? ? ? # since a "valid" access token can be marked invalidated in auth0-agent's database.
? ? ? ? # this update is to comply "360渗透测试安全规范".
? ? ? ? valid, message?=?validate_access_token_via_auth0(raw_token)
? ? ? ? if?not?valid:
? ? ? ? ? ? raise?rest_framework.exceptions.AuthenticationFailed(message)
? ? ? ? xtp_meta?=?ak_claims['https://auth.xtalpi.com/Metadata']
? ? ? ? uc_id?=?ak_claims['sub']
? ? ? ? user_attrs?=?dict(
? ? ? ? ? ? uc_id=uc_id,
? ? ? ? ? ? email=xtp_meta['userPrincipalName'],
? ? ? ? ? ? username=xtp_meta['userPrincipalName'],
? ? ? ? )
? ? ? ? prev_user_state?=?{} ?# for check if user dirty
? ? ? ? try:
? ? ? ? ? ? user?=?User.objects.get(email=user_attrs['email'])
? ? ? ? ? ? prev_user_state?=?model_to_dict(user)
? ? ? ? except?User.DoesNotExist:
? ? ? ? ? ? raise?rest_framework.exceptions.AuthenticationFailed(no_access_user_message)
? ? ? ? for?k, v?in?user_attrs.items():
? ? ? ? ? ? setattr(user, k, v)
? ? ? ? if?not?user.is_active:
? ? ? ? ? ? raise?rest_framework.exceptions.AuthenticationFailed(no_access_user_message)
? ? ? ? cur_user_state?=?model_to_dict(user)
? ? ? ? if?not?cur_user_state?==?prev_user_state:
? ? ? ? ? ? user.save()
? ? ? ? ? ? logger.info("Saved user instance for {}".format(user.username))
? ? ? ? return?user, None
? ? def?authenticate_header(self, request):
Setting.py
REST_FRAMEWORK?=?{
? ? 'EXCEPTION_HANDLER': 'xtalcase.views.custom_exception_handler',
? ? 'DEFAULT_PARSER_CLASSES': [
? ? ? ? 'rest_framework.parsers.JSONParser',
? ? ],
? ? 'DEFAULT_SCHEMA_CLASS': 'xtalcase.schemas.CustomAutoSchema',
? ? 'DEFAULT_AUTHENTICATION_CLASSES': [
? ? ? ? 'accounts.auth_backends.DrfUserCenterAuth0Backend', ?# 配置全局认证类
? ? ? ? 'rest_framework.authentication.SessionAuthentication',
? ? ],
? ? 'DEFAULT_PERMISSION_CLASSES': [
? ? ? ? 'rest_framework.permissions.IsAuthenticated'
? ? ],
? ? 'DEFAULT_FILTER_BACKENDS': [
? ? ? ? 'django_filters.rest_framework.DjangoFilterBackend',
? ? ],
? ? 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
? ? 'PAGE_SIZE': 20,
}
AUTHENTICATION_BACKENDS?=?[
? ? 'accounts.auth_backends.DjUserCenterAuth0Backend',
? ? # 'django.contrib.auth.backends.ModelBackend',
]
AUTH_USER_MODEL?=?'accounts.User'
Views.py
from?rest_framework.views?import?APIView
from?rest_framework?import?permissions
from?rest_framework?import?parsers?as?rest_parsers
@extend_schema(tags=["Accounts"])
class?LoginView(APIView):
? ? permission_classes?=?[permissions.AllowAny]
? ? parser_classes?=?[
? ? ? ? rest_parsers.JSONParser,
? ? ]
? ? def?post(self, request):
? ? ? ? serializer?=?GTaskHookRequest(data=request.data)
? ? ? ? serializer.is_valid(raise_exception=True)
? ? ? ? username?=?request.data['username']
? ? ? ? password?=?request.data['password']
? ? ? ? auth?=?DjUserCenterAuth0Backend()
? ? ? ? res?=?auth.authenticate(request, username=username, password=password)
? ? ? ? return?HttpResponse(json.dumps(res))
Url.py
? ? path('login/', import_string('accounts.views.LoginView').as_view()),
]
6.3 权限
权限控制可以限制用户对于视图API的访问和对于具体数据对象的访问。
在执行视图的dispatch()方法前,会先进行视图访问权限的判断
在通过get_object()获取具体对象时,会进行对象访问权限的判断
DRF框架提供了四个权限控制类:
AllowAny 允许所有用户
IsAuthenticated 仅通过认证的用户
IsAdminUser 仅管理员用户
IsAuthenticatedOrReadOnly 认证的用户可以完全操作,否则只能get读取
可以在DRF项目的settings.py文件中修改DRF框架的全局权限控制方案,如:
REST_FRAMEWORK = {
????'DEFAULT_PERMISSION_CLASSES': (
????????'rest_framework.permissions.IsAuthenticated', # 允许认证用户
????)
}
指定视图权限控制方案
可以在具体的视图中通过permission_classes属性来指定某个视图所使用的权限控制类
from?rest_framework.permissions import?IsAuthenticatedfrom?rest_framework.viewsets import?ReadOnlyModelViewSet
from?booktest.serializers import?BookInfoSerializerfrom?booktest.models import?BookInfo
class?BookInfoViewSet(ReadOnlyModelViewSet):
????serializer_class = BookInfoSerializer
????queryset = BookInfo.objects.all()
????# 指定当前视图自己的权限控制方案,不再使用全局权限控制方案
????permission_classes = [IsAuthenticated]
6.4限流
限流设置
REST_FRAMEWORK = {
????'DEFAULT_THROTTLE_CLASSES': (
????????# 针对未登录(匿名)用户的限流控制类
????????'rest_framework.throttling.AnonRateThrottle',
????????# 针对登录(认证)用户的限流控制类
????????'rest_framework.throttling.UserRateThrottle'
????),
????# 指定限流频次
????'DEFAULT_THROTTLE_RATES': {
????????# 认证用户的限流频次
????????'user': '5/minute',
????????# 匿名用户的限流频次
????????'anon': '3/minute',
????},
}
DEFAULT_THROTTLE_RATES 可以使用 second, minute, hour 或day来指明周期。
6.5过滤
对于列表数据可能需要根据字段进行过滤,我们可以通过添加django-fitlter扩展来增强支持。
pip install django-filter
在配置文件中增加过滤后端的设置:
INSTALLED_APPS = [
????...
????'django_filters', ?# 需要注册应用,
]
REST_FRAMEWORK = {
????'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
在视图中添加filter_fields属性,指定可以过滤的字段
import?django_filters
from?.models?import?Isomerss
class?IsomersFilter(django_filters.FilterSet):
? ? name__in?=?django_filters.CharFilter(field_name='name',lookup_expr="iconrtains")
? ? ording?=?django_filters.OrderingFilter(fields=('created_time','updated_time'))
? ? class?Meta:
? ? ? ? model?=?Isomerss
? ? ? ? fields?=?['name', 'ording', 'name__in','label']
from?.models?import?Isomerss
from?.serializers?import?IsomerSerializers
from?rest_framework?import?viewsets
from?.filters?import?IsomersFilter
# Create your views here.
class?IsomerssViewset(viewsets.ModelViewSet):
? ? queryset?=?Isomerss.objects.all()
? ? serializer_class?=?IsomerSerializers
? ? filterset_class?=?IsomersFilter
6.6排序
对于列表数据,REST framework提供了OrderingFilter过滤器来帮助我们快速指明数据按照指定字段进行排序。
使用方法:
在类视图中设置filter_backends,使用rest_framework.filters.OrderingFilter过滤器,REST framework会在请求的查询字符串参数中检查是否包含了ordering参数,如果包含了ordering参数,则按照ordering参数指明的排序字段对数据集进行排序。
前端可以传递的ordering参数的可选字段值需要在ordering_fields中指明。
class?BookListView(ListAPIView):
????queryset = BookInfo.objects.all()
????serializer_class = BookInfoSerializer
????filter_backends = [OrderingFilter]
????ordering_fields = ('id', 'bread', 'bpub_date')
6.7分页
REST framework提供了分页的支持。
我们可以在配置文件中设置全局的分页方式,如:
REST_FRAMEWORK = {
????'DEFAULT_PAGINATION_CLASS': ?'rest_framework.pagination.PageNumberPagination',
????'PAGE_SIZE': 5??# 每页数目
}
可选分页类
1) PageNumberPagination
前端访问网址形式:
GET ?http://api.example.org/books/?page=4
可以在子类中定义的属性:
page_size 每页数目
page_query_param 前端发送的页数关键字名,默认为"page"
page_size_query_param 前端发送的每页数目关键字名,默认为None
max_page_size 前端最多能设置的每页数量
2)LimitOffsetPagination
前端访问网址形式:
GET http://api.example.org/books/?limit=100&offset=400
可以在子类中定义的属性:
default_limit 默认限制,默认值与PAGE_SIZE设置一直
limit_query_param limit参数名,默认'limit'
offset_query_param offset参数名,默认'offset'
max_limit 最大limit限制,默认None
注意:如果在视图内关闭分页功能,只需在视图内设置
6.8异常处理
REST framework提供了异常处理,可以出来以下异常:
APIException 所有异常的父类
ParseError 解析错误
AuthenticationFailed 认证失败
NotAuthenticated 尚未认证
PermissionDenied 权限决绝
NotFound 未找到
MethodNotAllowed 请求方式不支持
NotAcceptable 要获取的数据格式不支持
Throttled 超过限流次数
ValidationError 校验失败
REST_FRAMEWORK = {
????'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}
6.9 admin站点注册
6.10 cache缓存使用
from?django.core.cache?import?cache
class?CocryPredictor:
? ? cache_key_prefix?=?"ccs:cocry-predict:"
? ? def?__init__(self) -> None:
? ? ? ? pass
? ? def?get_cache_key(self, api_smi, coformer_smi):
? ? ? ? return?"{0}:api_smi={1},coformer_smi={2}".format(
? ? ? ? ? ? self.cache_key_prefix,
? ? ? ? ? ? api_smi,
? ? ? ? ? ? coformer_smi
? ? ? ? )
? ? def?has_in_cache(self, api_smi, coformer_smi):
? ? ? ? cache_key?=?self.get_cache_key(api_smi, coformer_smi)
? ? ? ? return?cache_key?in?cache
? ? def?get_from_cache(self, api_smi, coformer_smi):
? ? ? ? cache_key?=?self.get_cache_key(api_smi, coformer_smi)
? ? ? ? data?=?cache.get(cache_key)
cache_key?=?self.get_cache_key(api_smi, cs)
? ? ? ? ? ? ? ? cache.set(cache_key, data, timeout=3600?*?24)
Cache常用方法
- cache.get(key, default=None, version=None): 获取缓存中指定键的值。如果键不存在,可以通过 default 参数指定默认值。
- cache.set(key, value, timeout=None, version=None): 将一个键值对存储到缓存中,可以设置过期时间(以秒为单位)。
- cache.add(key, value, timeout=None, version=None): 添加一个键值对到缓存中,但只在键不存在时才生效。
- cache.delete(key, version=None): 删除缓存中指定键的值。
- cache.clear(version=None): 清除缓存中的所有键值对。
- cache.get_or_set(key, default, timeout=None, version=None): 获取缓存中指定键的值,如果键不存在,则设置为指定的默认值并返回。
- cache.get_many(keys, version=None): 获取多个键对应的值,返回一个字典。
- cache.set_many(data, timeout=None, version=None): 批量设置多个键值对到缓存中。
- cache.delete_many(keys, version=None): 批量删除多个键值对。
- cache.incr(key, delta=1, version=None): 对一个键的整数值进行增加操作。
- cache.decr(key, delta=1, version=None): 对一个键的整数值进行减少操作。
6.11 配置swagger
Documenting your API - Django REST framework
Install django-rest-swagger
pip install django-rest-swagger
Update your settings.py
INSTALLED_APPS?=?[
? ? # ...
? ? 'polls',
? ? 'rest_framework_swagger',
]
Add swagger to your urls.
from?rest_framework_swagger.views?import?get_swagger_view
schema_view?=?get_swagger_view(title='Polls API')
# ...
urlpatterns?=?[
? ? # ...
? ? path(r'swagger-docs/', schema_view),
]
7、具体功能
7.1 文件上传和下载
安装工具
pip install Pillow==3.4.1
Model配置
from?django.db?import?models
# Create your models here.
class?PictureModel(models.Model):
? ? name?=?models.CharField(max_length=100)
? ? pic?=?models.FileField(upload_to='images/')
Serializers设置
from?rest_framework?import?serializers
from?.models?import?PictureModel
class?PicSerializer(serializers.ModelSerializer):
? ? class?Meta:
? ? ? ? model?=?PictureModel
? ? ? ? fields?=?"__all__"
View设置
from?django.shortcuts?import?render
from?rest_framework?import?viewsets
from?.models?import?PictureModel
from?.serializers?import?PicSerializer
from?django.http?import?FileResponse
from?rest_framework.decorators?import?action
# Create your views here.
class?PicSetviews(viewsets.ModelViewSet):
? ? queryset?=?PictureModel.objects.all()
? ? serializer_class?=?PicSerializer
? ? # 下载功能
? ? @action(methods=['get'],url_path='download',detail=True)
? ? def?download(self, request,*args,**kwargs):
? ? ? ? filed?=?self.get_object()
? ? ? ? # 注意下面的filed.pic的pic是models定义的对应的文件字段的名称
? ? ? ? response?=?FileResponse(open(filed.pic.path, 'rb')) ?
? ? ? ? return?response
Url设置
router.register('pic', import_string('document.views.PicSetviews'))
Setting设置
#文件上传存储位置
MEDIA_ROOT=os.path.join(BASE_DIR,"static/media")
7.2 克隆数据
方法一:传统的deepcopy
from?django.shortcuts?import?render
from?rest_framework?import?viewsets
from?.models?import?Moleculars
from?isomerss.models?import?Isomerss
from?.serializers?import?MolecularsSerializer,MolecularsFreeIsoSerializer
from?rest_framework.decorators?import?action
from?rest_framework.response?import??Response
from?django.forms?import?model_to_dict
from?copy?import?deepcopy
# Create your views here.
class?MolecularsViewset(viewsets.ModelViewSet):
? ? queryset?=?Moleculars.objects.all()
? ? serializer_class?=?MolecularsSerializer
? ? @action(url_path="_free_isomers_form",detail=False,methods=['POST'],serializer_class=MolecularsFreeIsoSerializer)
? ? def?_free_isomers_form_create(self,request,*args,**kwargs):
? ? ? ? isomers_ids?=?request.data.get('isomers_ids')
? ? ? ? name?=?request.data.get('name')
? ? ? ? molecular?=?request.data.get('Molecular')
? ? ? ? if?molecular?==?0:
? ? ? ? ? ? origin_data?=?Isomerss.objects.get(pk=isomers_ids[0])
? ? ? ? ? ? print("123---------------",origin_data.molecular)
? ? ? ? ? ? molecular?=?origin_data.molecular
? ? ? ? ? ? project_id?=?molecular.project
? ? ? ? ? ? new_molecular?=?Moleculars.objects.create(name=name, project=project_id)
? ? ? ? ? ? for?isomer_id?in?isomers_ids:
? ? ? ? ? ? ? ? origin_data?=?Isomerss.objects.get(pk=isomer_id)
? ? ? ? ? ? ? ? isomer_data?=?deepcopy(origin_data)
? ? ? ? ? ? ? ? print(model_to_dict(isomer_data))
? ? ? ? ? ? ? ? isomer_data.id =?None
? ? ? ? ? ? ? ? isomer_data.molecular?=?new_molecular
? ? ? ? ? ? ? ? isomer_data.save()
? ? ? ? else:
? ? ? ? ? ? for?isomer_id?in?isomers_ids:
? ? ? ? ? ? ? ? origin_data?=?Isomerss.objects.get(pk=isomer_id)
? ? ? ? ? ? ? ? isomer_data?=?deepcopy(origin_data)
? ? ? ? ? ? ? ? print(model_to_dict(isomer_data))
? ? ? ? ? ? ? ? isomer_data.id =?None
? ? ? ? ? ? ? ? isomer_data.molecular?=?Moleculars.objects.get(pk=molecular)
? ? ? ? ? ? ? ? isomer_data.save()
? ? ? ? return?Response({"status": 200,"result": {"isomer_data": "创建成功"}})
方法二:复制模型实例
在这个特定的情况下,blog._state.adding 被设置为 True,表示该 blog 对象即将被添加到数据库中,而不是已经存在于数据库中。这意味着该对象尚未在数据库中持久化(保存),并且在调用 save() 方法后,会被创建为新的数据库记录
blog?=?Blog(name="My blog", tagline="Blogging is easy")
blog.save() ?# blog.pk == 1
blog.pk =?None
blog._state.adding =?True
blog.save() ?# blog.pk == 2
7.3 自定义过滤参数和使用关联的上上级参数去过滤当前模型数据
方法一:Filters.py
from?rest_framework import?filters as?f
from?django_filters import?rest_framework as?filters
class?RoundFilter(filters.FilterSet):
? ? class?Meta:
? ? ? ? # 选择model中哪些字段作为可以过滤的参数
? ? ? ? model =?Round
? ? ? ? fields =?['name', 'unit', 'type', 'status', 'sequence']
? ? unit =?filters.ModelChoiceFilter(
? ? ? ? field_name='unit',
? ? ? ? to_field_name='id',
? ? ? ? queryset=Unit.objects.all(),
? ? )
? ? #自定义字段
# project = filters.NumberFilter()
# 从round模型关联的上一级unit的上一级的project去过滤round的数据
class?RoundProjectFilter(f.BaseFilterBackend):
? ? def?filter_queryset(self,request,queryset,view):
? ? ? ? p_id =?request.query_params.get("project")
? ? ? ? if?p_id:
? ? ? ? ? ? queryset =?queryset.filter(unit__project_id=p_id)
? ? ? ? return?queryset
方法二:自建一个过滤器类
View.py
from?csp_auto.filters import?(
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? RoundFilter,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? RoundProjectFilter)
class?RoundViewSet(
? ? rest_mixins.CreateModelMixin,
? ? rest_mixins.ListModelMixin,
? ? PartialUpdateModelMixin,
? ? viewsets.GenericViewSet
):
? ? queryset =?Round.objects.all()
? ? serializer_class =?RoundSerializer
? ? filter_backends =?[*api_settings.DEFAULT_FILTER_BACKENDS,RoundProjectFilter]
? ? filterset_class =?RoundFilter
? ? def?get_serializer_class(self):
? ? ? ? if?self.action ==?"partial_update":
? ? ? ? ? ? return?RoundUpdateSerializer
? ? ? ? return?self.serializer_class
方法三:给过滤器新增一个自定义过滤字段和方法
7.4 drf复制已有的request参数并且修改其中的data
? ? ? ? ? ? from?rest_framework.request?import?Request
? ? ? ? ? ? # 复制现有请求对象
? ? ? ? ? ? new_request?=?Request(request._request)
? ? ? ? ? ? # 在新请求对象上修改数据
? ? ? ? ? ? new_data?=?{"workflow": "ff_fit"}
? ? ? ? ? ? new_request._full_data?=?new_data
res?=?self.submit_workflow(new_request, node_fit.id)
# 修改新请求对象的 URL
new_request._request.path?=?f'/api/task-node/_submit_workflow/{node_fit.id}/'
7.5 自定义serializer字段
7.5 自定义一个请求视图函数
方法一:针对单独的视图函数
from?rest_framework.decorators?import?api_view, permission_classes, authentication_classes
@authentication_classes([]) ?# 此视图将不进行认证
@permission_classes([])
@api_view(['post'])
def?login_view(request):
? ? username?=?request.data['username']
? ? password?=?request.data['password']
? ? auth?=?DjUserCenterAuth0Backend()
? ? res?=?auth.authenticate(request, username=username, password=password)
? ? return?HttpResponse(json.dumps(res))
方法二:针对类的里面的视图函数
@action(detail=False,
? ? ? ? ? ? methods=['POST'],
? ? ? ? ? ? url_path="_collect_ff_fit/(?P<node_id>\\d+)")
? ? def?collect_ff_fit_result(self, request, node_id):
? ? ? ? task_sign?=?request.data.get('task_sign')
? ? ? ? collect_ff_fit_results(task_sign, TaskNode.objects.filter(id=node_id).first())
? ? ? ? return?Response(data=f"collect ff_fit result for {task_sign}?successful")
? ? @action(detail=False, methods=['GET'], url_path='look_workflow/(?P<name>\\w+)')
? ? def?get_workflow(self, request, name, **kwargs):
? ? ? ? token?=?request.headers.get('Authorization')
? ? ? ? sff?=?BaseSffService(token=token)
? ? ? ? print(123)
? ? ? ? res?=?sff.get(url="/csprober/help/parameter",
? ? ? ? ? ? ? ? ? ? ? params={"workflow": name})
? ? ? ? return?Response(res)
方法三:继承继承视图APIview,自写权限或者是请求方法等
from?rest_framework.views?import?APIView
from?rest_framework?import?permissions
from?rest_framework?import?parsers?as?rest_parsers
@extend_schema(tags=["Accounts"])
class?LoginView(APIView):
? ? permission_classes?=?[permissions.AllowAny]
? ? parser_classes?=?[
? ? ? ? rest_parsers.JSONParser,
? ? ]
? ? def?post(self, request):
? ? ? ? serializer?=?GTaskHookRequest(data=request.data)
? ? ? ? serializer.is_valid(raise_exception=True)
? ? ? ? username?=?request.data['username']
? ? ? ? password?=?request.data['password']
? ? ? ? auth?=?DjUserCenterAuth0Backend()
? ? ? ? res?=?auth.authenticate(request, username=username, password=password)
? ? ? ? return?HttpResponse(json.dumps(res))
如果使用的是apiview,对应的URL使用
path(
? ? ? ? 'api/ccs/cocry-predict/',
? ? ? ? import_string('ccs.views.CocryPredictView').as_view()),
7.6 在DRF中封装一个第三方服务请求
Sff.py
import?requests
from?django.conf?import?settings
class?BaseSffService:
? ? def?__init__(self, token=None):
? ? ? ? self.base_url?=?settings.SFF_BACKEND
? ? ? ? # self.base_url = "https://alpha-cn.xtalpi.xyz/sff.Development"
? ? ? ? self.headers?=?{"authorization": f"{token}"}
? ? def?_make_request(self, method, url, params=None, data=None):
? ? ? ? try:
? ? ? ? ? ? response?=?requests.request(method, url, headers=self.headers,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? params=params, data=data)
? ? ? ? ? ? response.raise_for_status()
? ? ? ? ? ? return?response.json()
? ? ? ? except?requests.RequestException?as?e:
? ? ? ? ? ? print("请求出错:", e)
? ? ? ? ? ? return?None
? ? def?get(self, url, params=None):
? ? ? ? url?=?f"{self.base_url}/{url}"
? ? ? ? print("12345")
? ? ? ? return?self._make_request('GET', url, params=params)
? ? def?post(self, url, data=None):
? ? ? ? url?=?f"{self.base_url}/{url}"
? ? ? ? return?self._make_request('POST', url, data=data)
? ? def?put(self, url, data=None):
? ? ? ? url?=?f"{self.base_url}/{url}"
? ? ? ? return?self._make_request('PUT', url, data=data)
? ? def?delete(self, url, params=None):
? ? ? ? url?=?f"{self.base_url}/{url}"
? ? ? ? return?self._make_request('DELETE', url, params=params)
view.py
? ? @action(detail=False, methods=['GET'], url_path='look_workflow/(?P<name>\\w+)')
? ? def?get_workflow(self, request, name, **kwargs):
? ? ? ? token?=?request.headers.get('Authorization')
? ? ? ? sff?=?BaseSffService(token=token)
? ? ? ? print(123)
? ? ? ? res?=?sff.get(url="/csprober/help/parameter",
? ? ? ? ? ? ? ? ? ? ? params={"workflow": name})
? ? ? ? return?Response(res)
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!