TOP

DRF 基本功能梳理 demo

模型类

from django.db import models


# Create your models here.

class Book(models.Model):
    title = models.CharField(max_length=50, verbose_name="标题", help_text="标题")  # help_text : 文档的显示信息
    collect_num = models.IntegerField(default=0, verbose_name="收藏数", help_text="收藏数")
    author = models.ForeignKey('Author', on_delete=models.CASCADE, related_name='books', help_text="作者")

    def __str__(self):
        return self.title


class Author(models.Model):
    name = models.CharField(max_length=30, verbose_name="姓名", help_text="姓名")
    age = models.IntegerField(default=18, verbose_name="年龄", help_text="年龄")
    publish = models.ManyToManyField('Publish', help_text="出版社")

    def __str__(self):
        return self.name


class Publish(models.Model):
    name = models.CharField(max_length=30, verbose_name="出版社名", help_text="出版社名")
    create_dt = models.DateField(verbose_name="成立时间", default="1995-06-01", help_text="成立时间")

    def __str__(self):
        return self.name

模型类拓展字段

在模型类中中可以以  property  修饰方法为类变量, 然后即可在序列化器中作为逻辑数据库字段使用

通常可以进行系列数据的拓展属性展示, 但是此字段不可带有任何参数装饰器本身就是去除到 () 执行, 因此参数是无从传递

倘若需要从request 传递的动态参数, 则只能在 序列化器中操作

class XXX(models.Model):
  .....
  .....
@property def xxx(self): if self.x1: return { "x2": self.x2, "x3": self.x3, } return { "x2": None, "x3": None } class Meta: verbose_name = "某某模型" verbose_name_plural = verbose_name

 

序列化类

from rest_framework import serializers
from .models import Book, Author, Publish



# 自定义校验方法
def check_name(value):
    if "羊驼" not in value:
        raise serializers.ValidationError("必须要有羊驼")

    return value


class AuthorSerializer(serializers.ModelSerializer):
    # 常规方式对字段进行校验, 将 model 的字段在这里重复写一遍
    name = serializers.CharField(max_length=30, label="姓名", validators=[check_name])  # validators 自定义校验方法
   """Meta 相关属性"""
    class Meta:
        model = Author
        # fields = ["id", "name"] # 自己指定要序列化的字段
        """
        fields 和 exclude 不能同时存在
        Cannot set both 'fields' and 'exclude' options on serializer AuthorSerializer.
        这两个字段的源码位置 ModelSerializer.get_field_names
        
        # exclude = ["id"]  # 排除某些字段
        """
        # 生成指定字段
        fields = "__all__"  # 所有的字段都加进来
        # 设置只读字段
        read_only_fields = []  # 源码位置 ModelSerializer.get_extra_kwargs
        # 给字段添加额外约束
        extra_kwargs = {  # # 源码位置 ModelSerializer.get_extra_kwargs
            "name": {
                "max_length": 30,
                "min_length": 1,
            }

        }

    """多对一的复写"""
    # 多对一的地方需要使用反向字段 如果未设置则为 关联对象表名+ _set 如果设置了 related_name, 则直接使用 related_name
    # 多的一方无法自定义字段国扩展
    # books = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
    # 使用 关联对象的  __str__ 方法作为填充
    books = serializers.StringRelatedField(read_only=True, many=True)


class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = "__all__"

    """一对多字段的复写, 若不复写取出的是 id (int) """""
    # 0. 默认的外键字段序列化方式, 取出的是 关联对象的 ID/ PK
    # author = serializers.PrimaryKeyRelatedField(read_only=True)
    # 1. source 对序列化方式进行复写
    author_id = serializers.CharField(source="author.id", read_only=True)  # str(obj.publish.id) 取 pk 也是一样
    # 2. 可实现自定义拓展字段, 一对多 在 "一" 的一方可以使用, 在 "多" 的一方不可使用
    author_name = serializers.CharField(source="author.name", read_only=True)
    # 3. 返回 关联对象的 __str__ 方法 - 必须基于已有的字段
    # author = serializers.StringRelatedField(read_only=True)
    # 4. 使用序列化器作为关联 - 必须基于已有的字段
    author = AuthorSerializer(read_only=True)  # 取出所有的子字段

    """
    单字段校验 - 实现对字段的额外要求的校验
    固定格式: validate(可自动提示) + _ + 校验字段名
    自定义逻辑在主校验逻辑之后, 相当于额外校验, 而非完全替换校验
    """""

    @staticmethod
    def validate_title(value):
        """自定义逻辑"""
        if "羊驼" not in value:
            # return False # return 不管是什么都是通过
            # 必须抛出异常才可以表示校验不通过
            """
            HTTP 400 Bad Request
            Allow: GET, POST, HEAD, OPTIONS
            Content-Type: application/json
            Vary: Accept

            {
                "title": [
                    "不赞美羊驼不通过"
                ]
            }
            """
            raise serializers.ValidationError("不赞美羊驼不通过")

        return value  # 通过返回 value 即可

    """
    多字段校验 - 实现对字段的额外要求的校验
    validate
    自定义逻辑在主校验逻辑之后, 相当于额外校验, 而非完全替换校验
    """""

    def validate(self, attrs):
        """
        bs = BookSerializer(data=request.data)
        attrs: 就是外界穿过来的数据  request.data
        
        多字段的校验里面也可以对单字段分别校验
        """""
        # 自定义逻辑
        title = attrs["title"]
        collect_num = attrs["collect_num"]
        if str(collect_num) not in title:
            """
            HTTP 400 Bad Request
            Allow: GET, POST, HEAD, OPTIONS
            Content-Type: application/json
            Vary: Accept
            
            {
                "non_field_errors": [
                    "书名里面要有收藏数里面的数字"
                ]
            }
            """
            raise serializers.ValidationError("书名里面要有收藏数里面的数字")

        return attrs  # 校验通过返回原参数

    """
    数据入库 - 创建 create
    数据入库 - 更新 update
    
    """

    def create(self, validated_data):
        """

        :param validated_data:  校验后的数据
        :return:
        """
        book = Book.objects.create(**validated_data)

        return book

    def update(self, instance, validated_data):
        """

        :param instance: 待更新的对象
        :param validated_data: 校验后的数据
        :return:
        """
        instance.title = validated_data["title"]
        instance.collect_num = validated_data["collect_num"]
        instance.save()
        return instance


class PublishSerializer(serializers.ModelSerializer):
    class Meta:
        model = Publish
        fields = "__all__"

序列化中拓展字段

部分场景可能存在需要动态参数, 即从request 中的数据作为参数对某些字段控制

如以下场景. 在订单序列化器中加入 is_flavor 字段用于标识当前用户对此订单是否已收藏

在序列化中,  self 的 context 上下文会存储 request , 从而实现此操作

# 工单列表序列化器
class OrderListModelSerializer(serializers.ModelSerializer):
    is_flavor = serializers.SerializerMethodField()

    def get_is_flavor(self, obj):
        user = self.context['request'].user
        return obj.is_flavor(user.u_name or user.username)

    class Meta:
        model = models.Order
        fields = (
        ....."is_flavor",
        .....
        )

 

路由

主路由

"""untitled1 URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from rest_framework.documentation import include_docs_urls

urlpatterns = [
    path('admin/', admin.site.urls),
    path('yangtuo/', include('yangtuo.urls')),
    path('docs/', include_docs_urls(title='yangtuo API')),
]

子路由

from rest_framework import routers
from .views import BooksView, AuthorsView, PublishesView
from django.urls import path

routers = routers.DefaultRouter()
routers.register('books', viewset=BooksView)
routers.register('author', viewset=AuthorsView)
routers.register('publish', viewset=PublishesView)
#action 装饰器替换
"""
# 自定义路由
path("publish/get_yangtuo_publish/", PublishesView.as_view({"get": "get_yangtuo_publish"})),
# 自定义部分更新路由
path("publish/change_publish/<int:pk>/", PublishesView.as_view({"put": "change_publish"}))
"""
urlpatterns = [

]
urlpatterns += routers.urls
print(urlpatterns)

过滤器

详细的文档这里 这里这里

常用过滤器

   CharFilter  字符串过滤器  

   DateTimeFilter  时间过滤器

   BooleanFilter   布尔过滤器

   NumberFilter  数字过滤器

   DateRangeFilter  时间范围过滤器

内部属性

    field_name  查询字段

    lookup_expr  匹配模式(与orm 的运算符一致)

    label  显示字段 (用于 drf 渲染器中的字段)

    method  指定函数

指定函数的时候必须传递 三个固定参数 

   queryset  在视图中的 get_queryset 函数的返回结果

   name 

   value  request 指定过滤参数传递的值

meta 字段

   model  指定模型, 类, 不是字符串

   fields  过滤参数, 不加入这里, 上面定义了也是可以使用的. 此处是用于指定数据库字段所用

   exclude  排除字段, 不能用作过滤的参数

示例

class OrderFilter(django_filters.rest_framework.FilterSet):
    i_followers = django_filters.CharFilter(field_name='followers', lookup_expr='icontains', label="关注人(包含)")
    start_date = django_filters.rest_framework.DateTimeFilter(field_name='create_time', lookup_expr='gte',
                                                              label="开始时间")
    end_date = django_filters.rest_framework.DateTimeFilter(field_name='create_time', lookup_expr='lte',
                                                            label="结束时间")
    handler = django_filters.CharFilter(method='handler_filter', label="待处理人")
    is_flavor = django_filters.CharFilter(method='is_flavor_filter', label="收藏工单")

    @staticmethod
    def is_flavor_filter(queryset, name, value):
        return queryset.filter(flavors__operator=value)

    @staticmethod
    def handler_filter(queryset, name, value):
        return queryset.filter(Q(cur_nodes_o__operator__icontains=value) | (Q(creator=value, status="entering"))) \
            .distinct()

    class Meta:
        model = models.Order
        fields = ('creator', 'tenant', 'cur_slot', 'status', 'recheck', 'mould', 'order_no')

生效

class xxxxx(ReadOnlyViewSet):
    queryset = xxxxx.objects.all()
    serializer_class = ......
    filter_class = xxxxxFilter
    search_fields = (....)
    filter_fields = (....)
    ordering_fields = (....)
    ordering = (....)

视图类

from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.filters import OrderingFilter
from rest_framework.pagination import LimitOffsetPagination, PageNumberPagination
from rest_framework.permissions import IsAuthenticated
from rest_framework.throttling import UserRateThrottle
from rest_framework.views import exception_handler

from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from rest_framework.decorators import action

from .models import Book, Author, Publish

from .serializers import BookSerializer, AuthorSerializer, PublishSerializer


# 序列化相关的注释解析
class BooksView(ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def list(self, request, *args, **kwargs):
        books = Book.objects.all()
        # 多个对象的时候 要 many=True
        bs = BookSerializer(instance=books, many=True)  # instance 要被序列化的对象
        return Response(bs.data)

    def create(self, request, *args, **kwargs):
        """序列化器对数据类型的校验
        1. 字段类型 int/char/data/bool/mail/uuid....
        2. 字段属性 max_length/required/read_only
        3. 单字段 (方法)
        4. 多字段 (方法)
        5. 自定义 (方法)
        """""
        print(request.data)
        """
        {
            "csrfmiddlewaretoken": "o1XJ6weot4dx4fXwgIC2goYW3aeHUXer4IsIVoL7oyw31gfAi7TTUsTjnxBn9X0F",
            "title": "海洋之歌",
            "collect_num": "999"
        }
        """
        bs = BookSerializer(data=request.data)  # 把数据序列化 用 data 参数传递
        if bs.is_valid(raise_exception=True):  # True / False
            # raise_exception 控制是否提示校验不通过的信息报错
            """ 报错回传示例
            HTTP 400 Bad Request
            Allow: GET, POST, HEAD, OPTIONS
            Content-Type: application/json
            Vary: Accept
            
            {
                "title": [
                    "该字段不能为空。"
                ],
                "collect_num": [
                    "请填写合法的整数值。"
                ]
            }
            """
            bs.save()  # 调用序列化器里面执行 create 方法
            return Response(bs.data)

    def update(self, request, *args, **kwargs):
        book = Book.objects.get(id=kwargs["pk"])
        # instance 要更新的对象, data 更新的数据
        bs = BookSerializer(instance=book, data=request.data)
        if bs.is_valid(raise_exception=True):  # 校验
            bs.save()  # 入库
            return Response(bs.data)

# 视图类的相关注解
# 0.视图类继承关系
"""
from rest_framework.viewsets import ModelViewSet
ModelViewSet(mixins.CreateModelMixin,
             mixins.RetrieveModelMixin,
             mixins.UpdateModelMixin,
             mixins.DestroyModelMixin,
             mixins.ListModelMixin,
             GenericViewSet)

from rest_framework.viewsets import ReadOnlyModelViewSet
ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
                     mixins.ListModelMixin,
                     GenericViewSet)             

from rest_framework.generics import CreateAPIView.....
CreateAPIView(mixins.CreateModelMixin, GenericAPIView)
ListAPIView(mixins.ListModelMixin, GenericAPIView)
RetrieveAPIView(mixins.RetrieveModelMixin, GenericAPIView)
.....


                  
    from rest_framework import mixins
    mixins
        CreateModelMixin - >    create()
        CreateModelMixin - >    list()
        RetrieveModelMixin - >    retrieve()
        UpdateModelMixin - >    update()
        DestroyModelMixin - >    destroy()
        
    from rest_framework.viewsets import GenericViewSet        
    GenericViewSet(ViewSetMixin, generics.GenericAPIView):                   

        ViewSetMixin.as_view()       
                    self.request = request
                    self.args = args
                    self.kwargs = kwargs
                    return self.dispatch(request, *args, **kwargs)
        
        GenericAPIView.(views.APIView)
                    get_queryset()
                    get_object()
                    get_serializer()
                    filter_queryset()
                    paginate_queryset()
                    
            APIView(View):
                as_view()   
                check_permissions()
                ...
            
            
"""
# 1. request 的参数获取
"""
request.GET - > request.query_params
request.POST / body - > request.data
"""
# 2. 视图返回封装
"""
HttpResponse/JsonResponse..... -> 
    Response(data=None, status=None, template_name=None, headers=None, exception=False, content_type=None)
"""
# 3. 返回状态码封装
"""
from rest_framework import status
status.HTTP_100_CONTINUE....
"""
# 4. 方法封装
"""
get - > list / retrieve
post - > create
put - > update
delete - > destroy
"""
# 5. 常用属性行为对象封装
"""
GenericAPIView: 
    queryset = None 
    serializer_class = None
    lookup_field = 'pk' # 默认查询字段
    lookup_url_kwarg = None
    
    
    # 使用示例
    # queryset = Author.objects.all()
    # serializer_class = AuthorSerializer
    
    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS # 过滤器
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS # 分页器
    
    get_queryset()
    get_serializer()
    get_object() # 基于 lookup_field  / lookup_url_kwarg
"""
# 6. CURD 封装
"""
ModelViewSet(mixins.CreateModelMixin,
             mixins.RetrieveModelMixin,
             mixins.UpdateModelMixin,
             mixins.DestroyModelMixin,
             mixins.ListModelMixin,
             GenericViewSet)
"""


class AuthorsView(ModelViewSet):
    queryset = Author.objects.all()  # 可以被使用 self.get_queryset() 的地方调用到
    serializer_class = AuthorSerializer  # 可以被使用 self.get_serializer() 的地方调用到
    lookup_url_kwarg = "id"  # 优先级比 lookup_field 高, 源码: lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
    # 默认就是 pk
    lookup_field = "pk"  # lookup_url_kwarg / lookup_field 设定的值必须是数据库有的字段

    def list(self, request, *args, **kwargs):
        # 动态查询
        """
        不要使用 
        authors = self.queryset 
        动态查询是没法自动拿到 .all() 的, 因此调用的时候还需要自己再加上 .all()
        要不这样 
        authors = self.queryset.all()
        或者这样
        authors = self.get_queryset()
        """""
        authors = self.get_queryset()  # self.queryset
        authors_serializer = self.get_serializer()  # self.serializer_class

    def retrieve(self, request, *args, **kwargs):
        # author = self.queryset.get(kwargs["id"])
        author = self.get_object()  # 等价上面


# 自定义分页器
class MyPageNumberPagination(PageNumberPagination):
    page_size = 1000  # 复写全局的页面大小
    page_size_query_param = 'page_size'  # 前段指定每页大小的定义字段
    max_page_size = 10000  # 每页最大可以指定的数量, 为了防止前段穿过来 page_size=100000000000000这样的情况


# 自定义异常处理
def my_exception_handler(exc, context):
    response = exception_handler(exc, context)

    if response is not None:
        # APIException 类的异常
        response.data['status_code'] = response.status_code
    # 其他异常类型自定义处理
    else:
        from django.db import DatabaseError
        if isinstance(exc, DatabaseError):
            return Response("数据库我特么裂开")
        else:
            return Response("其他异常")

    return Response("假设这里是个很好看的 404页面")


# 自定义方法/权限/认证/频率/分页/过滤/排序/异常处理/文档 相关注释
class PublishesView(ModelViewSet):
    """ # 文档里面的注释信息
    list:
        获取所有出版社
    """
    queryset = Publish.objects.all()
    serializer_class = PublishSerializer
    """局部认证是完全替换全局, 而非取并集, 即两种同时配置, 局部生效, 全局失效"""
    # 局部认证
    authentication_classes = [SessionAuthentication, BasicAuthentication]
    # 局部权限
    permission_classes = [IsAuthenticated]
    # 局部限流
    throttle_classes = [UserRateThrottle]
    # throttle_scope = "yangtuo"    # 限流器使用 ScopedRateThrottle 时的映射字段
    # 局部分页器
    pagination_class = MyPageNumberPagination  # 可使用自定义分页器, 复写 PageNumberPagination 的属性实现自定义字段生效
    # 过滤器 搜索 / 排序
    filter_backends = [DjangoFilterBackend,
                       OrderingFilter]  # 注意导入的时候  from rest_framework.filters import OrderingFilter
    filterset_fields = ['name', 'create_dt', 'id']
    ordering_fields = ['id', 'create_dt']  # ordering 前段参数 倒叙加个 -

    # 自定义接口方法, 以及生成路由
    @action(methods=['GET'], detail=False)  # detail 表示是否有参数
    def get_yangtuo_publish(self, request):
        publishes = Publish.objects.filter(name__contains="羊驼")
        publish_serializer = self.serializer_class(instance=publishes, many=True)
        return Response(publish_serializer.data)

    # 额外添加参数, 更新局部信息
    @action(methods=['PUT'], detail=True)
    def change_publish(self, request, pk):
        publish = self.get_object()
        data = request.data
        # partial 参数携带表示只对部分数据进行校验保存
        publish_serializer = self.serializer_class(instance=publish, data=data, partial=True)
        if publish_serializer.is_valid(raise_exception=True):
            publish_serializer.save()
            return Response(publish_serializer.data)

 

posted @ 2021-06-14 17:07  羊驼之歌  阅读(371)  评论(0编辑  收藏  举报