- 介绍
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字段类型和选项参数
参数说明:
1、null = True - 数据库字段是否可为空,True可为空,默认情况下为:False不可为空。
注意:
如果在定义字段的时候,没有指定null=True,那么默认情况下,null=False,就是不能为空,否则将会报错!
2、blank = True - django的Admin中添加数据时是否允许为空值。与null的区别为:null是一个纯数据库级别的,而blank是表单验证级别的。
3、primary_key = False - 主键,对AutoField类型的字段设置主键后,就会代替原来自增id列。
如果您没有为模型中的任何字段指定primary_key=True, Django将自动添加一个IntegerField来保存主键,所以您不需要在任何字段上设置primary_key=True,除非您想要覆盖默认的主键行为。
4、auto_now= True - 每次保存或添加都会创建当前时间(可用于修改/编辑时间字段)
5、auto_now_add = True- 每次保存或添加都会保存第一次创建的时间(可用于创建时间字段)
6、max_length - 定义字符串最大长度。
7、default - 给字段定义默认值,注意:如果你想设置一个默认值,前提需要设置null=True。如果没传值,没设置null=True,即使你设置了默认值存到数据库中也是null。
8、unique = True - 不允许重复,如:用户密码等等。
9、auto_create = False - 自动创建。
10、upload_to - 文件上传指定的目录。
11、db_column - 可更改数据库中字段的名字,如果没有设置此字段,那么将会使用模型中属性的名字。
注意:更改完之后,需要执行迁移命令:
python manage.py makemigrations
python manage.py migrate
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常见属性
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)