Django学习

date
Sep 14, 2024
slug
django
author
status
Public
tags
python
summary
type
Post
thumbnail
category
updatedAt
Sep 14, 2024 07:50 AM

Serializer

作用: 将模型实例(Model)序列化和反序列化成json之列的形式但一般情况下你不需要把所有字段信息以JSON格式数据返回给用户。序列化器定义了需要对一个模型实例的哪些字段进行序列化/反序列化, 并可对客户端发送过来的数据进行验证和存储
from .models import Article

from rest_framework import serializers

class ArticleSerializer(serializers.ModelSerializer):
    author = serializers.HiddenField(default=serializers.CurrentUserDefault())

    class Meta:
        model = Article
        fields = '__all__'
        read_only_fields = ('id','create_date')

Serializer用在哪里?

在通过models( article = Article.objects.get(pk=pk))获取数据库数据后,通过类似serializer = ArticleSerializer(article) , return Response(serializer.data) 完成http返回json body的操作。 类似golang中
type User struct {
  ID           uint           // Standard field for the primary key
  Name         string         // 一个常规字符串字段
}
var user User

// works because destination struct is passed in
db.First(&user)
不过可以看出django中不需要手动去定义User结构,直接用model的定义,然后做一些二次修改完善。但实际上定义的活在model中干了,总归有一个地方要去定义,但是serializers的功能貌似更丰富,可以在不修改model的情况下去更改response结构,当然这也是python动态语言的优势,而golang则需要一开始就定义好需要的字段结构
serializers在django中的使用方式如下:
from apiproject.blog.models import Article
from apiproject.blog.serializers import ArticleSerializer

@api_view(['GET', 'PUT', 'DELETE'])
def article_detail(request, pk):
"""    Retrieve,update or delete an article instance。"""try:
        article = Article.objects.get(pk=pk)
    except Article.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = ArticleSerializer(article, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

view的优化

从上面view的写法可以看出,用户需要判断是get还是post,这样一个大型项目的代码量就很大了。可以使用以下几种方式优化写法:
  • 使用基础的APIView
  • 使用Mixins类和GenericAPI类混配
  • 使用通用视图generics.*类, 比如generics.ListCreateAPIView
  • 使用视图集ViewSetModelViewSet
比如使用APIView
class ArticleList(APIView):"""
    List all articles, or create a new article.
    """
    def get(self, request, format=None):
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)

该视图可以自动将不同请求转发到相应处理方法,逻辑上也更清晰。需要调用as_view()的方法才能在视图中实现查找指定方法, 比如GET请求执行get方法。
    re_path(r'^articles/$', views.ArticleList.as_view()),
    re_path(r'^articles/(?P<pk>[0-9]+)$', views.ArticleDetail.as_view()),

用Mixin类和GenericAPI类混配

# blog/views.py
# 使用Generic APIView & Mixins
from rest_framework import mixins
from rest_framework import generics

from apiproject.blog.models import Article
from apiproject.blog.serializers import ArticleSerializer


class ArticleList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    # get 是GenericAPIView转发
    def get(self, request, *args, **kwargs):
    #self.list 为mixins.ListModelMixin提供
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)
GenericAPIView从APIVIew继承,对用户请求进行转发,它还可以通过querysetserializer_class属性指定需要序列化与反序列化的模型或queryset及所用到的序列化器类。
这里的 ListModelMixinCreateModelMixin类则分别引入了.list() .create() 方法,当用户发送get请求时调用Mixin提供的list()方法,将指定queryset序列化后输出,发送post请求时调用Mixin提供的create()方法,创建新的实例对象。
使用Mixin类后,Seralizer貌似被写死了。没关系mixin类提供perform_create方法可以实现对serializer的自定义
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

更简化的用法

DRF还提供了一套常用的将 Mixin 类与 GenericAPI类已经组合好了的视图(不用def get ->self.list),开箱即用,可以进一步简化我们的代码,如下:

from apiproject.blog.models import Article
from apiproject.blog.serializers import ArticleSerializer

# generic class-based views
from rest_framework import generics

class ArticleList(generics.ListCreateAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    # 将request.user与author绑定
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

class ArticleDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Article.objects.all()
    serializer_class =ArticleSerializer
generics.ListCreateAPIView类支持List、Create两种视图功能,分别对应GET和POST请求。generics.RetrieveUpdateDestroyAPIView支持Retrieve、Update、Destroy操作,其对应方法分别是GET、PUT和DELETE。 屌不屌

ViewSet视图集

ArticleListArticleDetail两个类中querysetserializer_class属性依然存在代码重复。使用视图集可以将两个类视图进一步合并,一次性提供List、Create、Retrieve、Update、Destroy这5种常见操作,这样querysetseralizer_class属性也只需定义一次就好, 这就变成了视图集(viewset)。
from apiproject.blog.models import Article
from apiproject.blog.serializers import ArticleSerializer

# generic class-based views
from rest_framework import viewsets


class ArticleViewSet(viewsets.ModelViewSet):
    # 用一个视图集替代ArticleList和ArticleDetail两个视图
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    # 自行添加,将request.user与author绑定
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)
这时ArticleViewSet已经包含了List、Create、Retrieve、Update、Destroy这5种操作,如何实现只要其中一种呢:答案是在urls.py中指定方法映射即可,如下所示:
from django import views
from django.contrib import admin
from django.urls import path, re_path
from rest_framework.urlpatterns import format_suffix_patterns

article_list = views.ArticleViewSet.as_view(
    {
        'get': 'list',
        'post': 'create'
    })

article_detail = views.ArticleViewSet.as_view({
    'get': 'retrieve', # 只处理get请求,获取单个记录
})


urlpatterns = [
    re_path(r'^articles/$', article_list),
    re_path(r'^articles/(?P<pk>[0-9]+)$', article_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

SerializerMethodField

看名字就知道大概是什么功能了,其实就是Serializer使用额外的方法来定义字段,而不是通过
author = serializers.ReadOnlyField(source="author.username")
这种方式,method可以封装额外的逻辑,比如定义了cn_status 就可以通过方法get_cn_status获取值, 类似的函数都以get_变量名 命名
from .models import Article

from rest_framework import serializers

class ArticleSerializer(serializers.ModelSerializer):
    author = serializers.ReadOnlyField(source="author.username")
    status = serializers.ReadOnlyField(source="get_status_display")
    cn_status = serializers.SerializerMethodField()

    class Meta:
        model = Article
        fields = '__all__'
        read_only_fields = ('id', 'author', 'create_date')

    def get_cn_status(self, obj):
        if obj.status == 'p':
            return "已发表"
        elif obj.status == 'd':
            return "草稿"
        else:
            return ''
不过需要注意的是SerializerMethodField通常用于显示模型中原本不存在的字段,类似可读字段,你不能通过反序列化对其直接进行修改。

to_representation

除了指定source和自定义序列化方法,to_representation() 也允许我们改变序列化的输出内容, 给其添加额外的数据。