• RESTful API介绍,基于Django实现RESTful API,DRF 序列化


    1.rest framework serializer(序列化)的简单使用
    	QuerySet([obj,obj,obj])	--> JSON格式数据
    	
    	0.安装和导入:
    		pip3 install djangorestframework
    		
    		from rest_framework import serializers
    	
    	1.简单使用
    		1.创建一个类,类一定要继承serializers.Serializer
    		
    		2.chocie字段和FK字段都可以通过使用source来获取对应的值
    		
    		3.多对多字段可以使用serializers.SerializerMethodField
    			def get_tag(self,obj):
    				tag_list = []
    				for i in obj.tag.all():
    					tag_list.append(i.name)
    				return tag_list
    				
    	2.使用ModelSerializer
    		
    		通过配置class Meta:
    					model = 表名
    					fields = ['字段',...]
    					depth = 1
    					
    			
    

    一、RESTful API介绍

    什么是RESTful 

    REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

    RESTful API设计

    API与用户的通信协议

    总是使用HTTPs协议。

    现在互联网企业,都开始关注安全了。所以,一般暴露的接口,都是使用https协议。前提是,需要购买SSL域名证书,一年花费,少则几千,多则上万。

    域名 

    https://api.example.com                         尽量将API部署在专用域名
    
    https://example.org/api/                        API很简单

    使用第一种,可能会有跨域问题。为了避免这种问题,可以采用第二种。

    版本

    1.  将版本信息放在URL中,如:https://api.example.com/v1/
    
    2. 将版本信息放在请求头中。

    国内的公司,一般使用第一种。前端代码不用大改动,后端开发好v2版本后。将url切换一下,就可以实现平滑迁移。

    国外公司,使用会第二种,因为这样比较安全!

    路径

    视网络上任何东西都是资源,均使用名词表示(可复数)
    
    https://api.example.com/v1/zoos
    
    https://api.example.com/v1/animals
    
    https://api.example.com/v1/employees
    

     上面的3个url分别表示:动物园、动物、员工

    GET      :从服务器取出资源(一项或多项)
    
    POST    :在服务器新建一个资源
    
    PUT      :在服务器更新资源(客户端提供改变后的完整资源)
    
    PATCH  :在服务器更新资源(客户端提供改变的属性)
    
    DELETE :从服务器删除资源

    PATCH很少用到

    过滤

    通过在url上传参的形式传递搜索条件
    
    https://api.example.com/v1/zoos?limit=10:指定返回记录的数量
    
    https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置
    
    https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数
    
    https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
    
    https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件

    上面的url参数,一眼望过去,就大概知道啥意思了。所以命令规范,很重要!

    程序员写代码,不只是完成功能而已!写代码的时候,必须按照规范来。比如python,遵循PEP8。

    已经写好的代码,有空的时候,将代码优化一下!这样,当你把项目交接给别人的时候,不至于,让人看着代码,晦涩难懂!一万匹马在内心崩腾,有木有?

    状态码

    200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
    201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
    202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
    204 NO CONTENT - [DELETE]:用户删除数据成功。
    400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
    401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
    403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
    404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
    406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
    410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
    422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
    500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
    

      

    几个常用的http状态码,需要知道。因为面试必问!

    错误处理

    状态码是4xx时,应返回错误信息,error当做key。

    {
        error: "Invalid API key"
    }

    尽量将代码写的健全一点,遇到错误时,抛出error

    返回结果

    针对不同操作,服务器向用户返回的结果应该符合以下规范

    GET /collection:返回资源对象的列表(数组)
    GET /collection/resource:返回单个资源对象
    POST /collection:返回新生成的资源对象
    PUT /collection/resource:返回完整的资源对象
    PATCH /collection/resource:返回完整的资源对象
    DELETE /collection/resource:返回一个空文档

    比如url:  http://127.0.0.1/api/comment/2 表示id为2的详细信息

    Hypermedia API

    RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

    {"link": {
      "rel":   "collection https://www.example.com/zoos",
      "href":  "https://api.example.com/zoos",
      "title": "List of zoos",
      "type":  "application/vnd.yourformat+json"
    }}

    这个,要看需求了,有需求,可以做一下!

    更多内容,请参考:阮一峰的Blog

    二、基于Django实现RESTful API

    在昨天项目about_drf的基础上, 增加一个评论表

    修改model.py,增加表comment

    # 评论表
    class Comment(models.Model):
        content = models.CharField(max_length=128)
        article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
    

    使用2个命令,生成表

    python manage.py makemigrations
    python manage.py migrate

    现在需要对评论表,做增删改查,按照常规来讲,会这么写url

    url(r'comment_list/',...),
    url(r'add_comment/',...),
    url(r'delete_comment/',...),
    url(r'edit_comment/',...),

    现在还只是一个表的增删改查,如果有80个表呢?写320个url ?

    RESTful API之url设计

    路由分发

    在app01(应用名)目录下,创建文件app01_urls.py

     1 from django.conf.urls import url
     2 from app01 import views
     3 
     4 urlpatterns = [
     5     url(r'^article_list/', views.article_list),
     6     url(r'article_detail/(d+)', views.article_detail),
     7 
     8     # 评论
     9     url(r'comment/', views.Comment.as_view()),
    10 ]

    include表示导入py文件

    RESTful API之视图

    CBV

    针对4种请求方式,使用FBV

    def comment(request):
        if request.method == "GET":
            return HttpResponse("获取评论")
        elif request.method == "POST":
            return HttpResponse("创建新评论")
        elif request.method == "PUT":
            return HttpResponse("修改评论")
        elif request.method == "DELETE":
            return HttpResponse("删除评论")
    

    这样写的话,一个4层if判断,就会有很冗长的代码。不利与维护,推荐使用CBV方式

    CBV方式

    from django import views
    class Comment(views.View):
        def get(self, request):
            return HttpResponse("获取评论")
    
        def post(self, request):
            return HttpResponse("创建新评论")
    
        def put(self, request):
            return HttpResponse("修改评论")
    
        def delete(self, request):
            return HttpResponse("删除评论")
    

    代码看着,就很清晰了。知道哪一种请求方式,该做哪些操作

    APIView

    之前学习django用的都是View,APIView它是什么呢?

    APIView与View的区别

    APIView是View的子类
    传递给请求处理程序的request实例是REST框架的请求实例,而不是Django的HttpRequest实例
    
    处理程序返回的基于REST框架的Response,而不是Django的HttpResponse,视图函数将会管理内容协商,然后设置正确的渲染方式
    
    任何APIException将会被捕捉,然后转换成合适的response对象
    
    接收到的请求首先被认证,然后赋予相应的权限,然后通过节流器分发给相应的请求处理函数,类似.get()和.post() 

    APIView是专门写API的视图函数,结合serializers,非常方便做序列化!

    关于APIView的源码解析,请参考文章:

    https://blog.csdn.net/u013210620/article/details/79857654

    APIView的请求书,都在data里面,

    但是对于GET,参数在self.request.query_params里面  

    修改views.py,完整代码如下:

     1 from django.shortcuts import render,HttpResponse
     2 from django.http import JsonResponse
     3 from app01 import models
     4 import json
     5 from rest_framework import serializers
     6 from rest_framework.views import APIView
     7 
     8 
     9 # Create your views here.
    10 #第二种方式
    11 # def article_list(request):
    12 #     #去数据库查询所有的文章数据,返回queryset,每一个元素都是字典
    13 #     query_set = models.Article.objects.all().values("id","title","create_time","type","school")
    14 #     print(query_set)
    15 #     for i in query_set:
    16 #         print(i)
    17 #
    18 #         #学校对象
    19 #         school_obj = models.School.objects.filter(id=i['school']).first()
    20 #         #学校id
    21 #         id = school_obj.id
    22 #         #学校的名字
    23 #         name = school_obj.name
    24 #         #修改字典,key为school的值
    25 #         i['school'] = {"id":id,"name":name}
    26 #
    27 #         #返回json对象,safe=False表示任何能转换为json格式的对象
    28 #         return JsonResponse(list(query_set),safe=False)
    29 #
    30 
    31 
    32 class DBG(serializers.Serializer):  # 声明序列化器
    33     id = serializers.IntegerField()
    34     title = serializers.CharField()
    35     create_time = serializers.DateField()
    36     type = serializers.IntegerField()
    37     school = serializers.CharField(source="school.name")
    38 
    39 
    40 class CYM(serializers.ModelSerializer):  # 声明ModelSerializer
    41     #
    42     type = serializers.CharField(source='get_type_display')
    43 
    44     class Meta:
    45         model = models.Article
    46         fields = "__all__"  # ("id", "title", "type")
    47         depth = 1  # 官方推荐不超过10层
    48 
    49 def article_list(request):  # 查询所有
    50     # 去数据库查询所有的文章数据
    51     query_set = models.Article.objects.all()
    52     xbg = CYM(query_set, many=True)
    53     print(xbg.data)
    54     # 返回
    55     return JsonResponse(xbg.data, safe=False)
    56 
    57 def article_detail(request,id): #查询单条数据
    58     article_obj = models.objects.filter(id=id).first()
    59     xcym = CYM(article_obj)
    60     return JsonResponse(xcym.data)
    61 
    62 class Comment(APIView):
    63     def get(self,request):
    64         print(request)
    65         print(self.request.query_params)    #get请求参数
    66         print(self.request.data)            #其他请求方式参数,比如POST,PUT
    67         return HttpResponse("获取评论")
    68 
    69     def post(self,request):
    70         print(self.request.query_params)    #get请求参数
    71         print(self.request.data)        #其他请求方式参数,比如:post,put
    72         return HttpResponse("创建新评论")
    73 
    74     def put(self,request):
    75         return HttpResponse("修改评论")
    76 
    77     def delate(self,request):
    78         return HttpResponse("删除评论")
    79     
    Views.py

    get请求

    举例:get方式

    点击Parsms设置参数age为18,那么url会自动变成http://127.0.0.1:8000/api/comment/?age=18

    点击send,结果如下:

    查看Pycharm控制台输出:

    <rest_framework.request.Request object at 0x0000027A15ED9E80>
    <QueryDict: {'age': ['18']}>
    {}

    post请求

    post请求参数,是在请求体中的,点击Body,默认数据格式为form-data,也就是表单提交

    点击send,效果如下:

    查看Pycharm控制台输出:

    <QueryDict: {}>
    <QueryDict: {'age': ['18']}>

    可以发现get参数是空的,但是post是有参数的!

    注意:针对提交类型不同,获取参数也是不同的!不一定就在self.request.data里面,详情请参考:

    https://www.jianshu.com/p/f2f73c426623

    三、DRF 序列化

    DRF是django rest framework的简称

    表结构:

    还是以about_drf项目为例,增加几个表,models.py完整代码如下:

    还是以about_drf项目为例,增加几个表,models.py完整代码如下:

    from django.db import models

    # Create your models here.


    # 文章表
    class Article(models.Model):
    title = models.CharField(max_length=32, unique=True, error_messages={"unique": "文章标题不能重复"})
    # 文章发布时间
    # auto_now每次更新的时候会把当前时间保存
    create_time = models.DateField(auto_now_add=True)
    # auto_now_add 第一次创建的时候把当前时间保存
    update_time = models.DateField(auto_now=True)
    # 文章的类型
    type = models.SmallIntegerField(
    choices=((1, "原创"), (2, "转载")),
    default=1
    )
    # 来源
    school = models.ForeignKey(to='School', on_delete=models.CASCADE)
    # 标签
    tag = models.ManyToManyField(to='Tag')


    # 文章来源表
    class School(models.Model):
    name = models.CharField(max_length=16)


    # 文章标签表
    class Tag(models.Model):
    name = models.CharField(max_length=16)


    # 评论表
    class Comment(models.Model):
    content = models.CharField(max_length=128)
    article = models.ForeignKey(to='Article', on_delete=models.CASCADE)


    复制代码
    from django.db import models
    
    # Create your models here.
    
    
    # 文章表
    class Article(models.Model):
        title = models.CharField(max_length=32, unique=True, error_messages={"unique": "文章标题不能重复"})
        # 文章发布时间
        # auto_now每次更新的时候会把当前时间保存
        create_time = models.DateField(auto_now_add=True)
        # auto_now_add 第一次创建的时候把当前时间保存
        update_time = models.DateField(auto_now=True)
        # 文章的类型
        type = models.SmallIntegerField(
            choices=((1, "原创"), (2, "转载")),
            default=1
        )
        # 来源
        school = models.ForeignKey(to='School', on_delete=models.CASCADE)
        # 标签
        tag = models.ManyToManyField(to='Tag')
    
    
    # 文章来源表
    class School(models.Model):
        name = models.CharField(max_length=16)
    
    
    # 文章标签表
    class Tag(models.Model):
        name = models.CharField(max_length=16)
    
    
    # 评论表
    class Comment(models.Model):
        content = models.CharField(max_length=128)
        article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
    复制代码

    使用2个命令,生成表

    python manage.py makemigrations
    python manage.py migrate

    使用navicat打开sqlite3数据库,增加几条数据

    # 评论表
    INSERT INTO app01_comment ("id", "content", "article_id") VALUES (1, '呵呵', 1);
    INSERT INTO app01_comment ("id", "content", "article_id") VALUES (2, '哈哈', 2);
    INSERT INTO app01_comment ("id", "content", "article_id") VALUES (3, '嘿嘿', 3);
    INSERT INTO app01_comment ("id", "content", "article_id") VALUES (4, '嘻嘻', 2);
    INSERT INTO app01_comment ("id", "content", "article_id") VALUES (5, '呜呜', 1);
    
    # 文章表
    INSERT INTO app01_article ("id", "create_time", "update_time", "type", "school_id", "title") VALUES (4, '2018-08-01', '2018-08-01', 1, 1, 'Linux全都是记不住的命令');
    
    # 文章和标签关系表
    INSERT INTO app01_article_tag ("id", "article_id", "tag_id") VALUES (3, 2, 1);
    INSERT INTO app01_article_tag ("id", "article_id", "tag_id") VALUES (4, 4, 1);
    INSERT INTO app01_article_tag ("id", "article_id", "tag_id") VALUES (5, 4, 2);
    INSERT INTO app01_article_tag ("id", "article_id", "tag_id") VALUES (6, 4, 3);

    复制代码
    # 评论表
    INSERT INTO app01_comment ("id", "content", "article_id") VALUES (1, '呵呵', 1);
    INSERT INTO app01_comment ("id", "content", "article_id") VALUES (2, '哈哈', 2);
    INSERT INTO app01_comment ("id", "content", "article_id") VALUES (3, '嘿嘿', 3);
    INSERT INTO app01_comment ("id", "content", "article_id") VALUES (4, '嘻嘻', 2);
    INSERT INTO app01_comment ("id", "content", "article_id") VALUES (5, '呜呜', 1);
    
    # 文章表
    INSERT INTO app01_article ("id", "create_time", "update_time", "type", "school_id", "title") VALUES (4, '2018-08-01', '2018-08-01', 1, 1, 'Linux全都是记不住的命令');
    
    # 文章和标签关系表
    INSERT INTO app01_article_tag ("id", "article_id", "tag_id") VALUES (3, 2, 1);
    INSERT INTO app01_article_tag ("id", "article_id", "tag_id") VALUES (4, 4, 1);
    INSERT INTO app01_article_tag ("id", "article_id", "tag_id") VALUES (5, 4, 2);
    INSERT INTO app01_article_tag ("id", "article_id", "tag_id") VALUES (6, 4, 3);
    复制代码

    查看评论表记录

    查看文章表记录

    查看文章和标签关系表

    单表的GET和POST:

    使用serializers序列化,针对每一个表,需要单独写函数。一般会写在views.py里面,但是这样做,会导致整个文件代码过长。需要分离出来!

    在app01(应用名)目录下,创建文件app01_serialization.py,表示自定义序列化

    使用serializers序列化,针对每一个表,需要单独写函数。一般会写在views.py里面,但是这样做,会导致整个文件代码过长。需要分离出来!

    在app01(应用名)目录下,创建文件app01_serialization.py,表示自定义序列化

    from app01 import models
    from rest_framework import serializers
    
    # 序列化评论的类
    class CommentSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Comment  # Comment表
            fields = "__all__"  # 序列化所有字段
            depth = 2  # 深度为2

    复制代码
    from app01 import models
    from rest_framework import serializers
    
    # 序列化评论的类
    class CommentSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Comment  # Comment表
            fields = "__all__"  # 序列化所有字段
            depth = 2  # 深度为2
    复制代码

    修改app01_urls.py,注释掉多余的路径

    from django.conf.urls import url
    from app01 import views

    urlpatterns = [
    # url(r'^article_list/', views.article_list),
    # url(r'article_detail/(d+)', views.article_detail),

    # 评论
    url(r'comment/', views.Comment.as_view()),
    ]

    复制代码
    from django.conf.urls import url
    from app01 import views
    
    urlpatterns = [
        # url(r'^article_list/', views.article_list),
        # url(r'article_detail/(d+)', views.article_detail),
    
        # 评论
        url(r'comment/', views.Comment.as_view()),
    ]
    复制代码

    修改views.py,删除多余的代码,并使用app01_serialization

    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from app01 import models
    import json
    from rest_framework import serializers
    from django import views
    from rest_framework.views import APIView
    from app01 import app01_serialization # 导入自定义的序列化

    # Create your views here.

    class Comment(APIView):
    def get(self, request):
    res = {"code":0} # 默认状态
    all_comment = models.Comment.objects.all()
    # print(all_comment)
    # 序列化,many=True表示返回多条
    ser_obj = app01_serialization.CommentSerializer(all_comment, many=True)
    res["data"] = ser_obj.data

    return JsonResponse(res)

    def post(self, request):
    return HttpResponse("创建新评论")

    def put(self, request):
    return HttpResponse("修改评论")

    def delete(self, request):
    return HttpResponse("删除评论")


    复制代码
    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from app01 import models
    import json
    from rest_framework import serializers
    from django import views
    from rest_framework.views import APIView
    from app01 import app01_serialization  # 导入自定义的序列化
    
    # Create your views here.
    
    class Comment(APIView):
        def get(self, request):
            res = {"code":0}  # 默认状态
            all_comment = models.Comment.objects.all()
            # print(all_comment)
            # 序列化,many=True表示返回多条
            ser_obj = app01_serialization.CommentSerializer(all_comment, many=True)
            res["data"] = ser_obj.data
    
            return JsonResponse(res)
    
        def post(self, request):
            return HttpResponse("创建新评论")
    
        def put(self, request):
            return HttpResponse("修改评论")
    
        def delete(self, request):
            return HttpResponse("删除评论")
    复制代码

    使用postman发送get请求,不带参数。效果如下:

    Response

     Rest framework 引入了Response 对象,它是一个TemplateResponse类型,并根据客户端需求正确返回需要的类型。

    使用前,需要导入模块

    from rest_framework.response import Response
    

    语法:

    return Response(data)    #根据客户端的需求返回不同的类型
    

    举例:

    修改视图Comment中的get方法,将JsonResponse改成Response

     1 from django.shortcuts import render,HttpResponse
     2 from django.http import JsonResponse
     3 from app01 import models
     4 import json
     5 from rest_framework import serializers
     6 from django import views
     7 from rest_framework.views import APIView
     8 from app01 import app01_serializers  # 导入自定义的序列化
     9 from rest_framework.response import Response
    10 
    11 # Create your views here.
    12 
    13 class Comment(APIView):
    14     def get(self, request):
    15         res = {"code":0}  # 默认状态
    16         all_comment = models.Comment.objects.all()
    17         # print(all_comment)
    18         # 序列化,many=True表示返回多条
    19         ser_obj = app01_serializers.CommentSerializer(all_comment, many=True)
    20         res["data"] = ser_obj.data
    21 
    22         return Response(res)
    23 
    24     def post(self, request):
    25         return HttpResponse("创建新评论")
    26 
    27     def put(self, request):
    28         return HttpResponse("修改评论")
    29 
    30     def delete(self, request):
    31         return HttpResponse("删除评论")
    Views

    使用浏览器访问url:  http://127.0.0.1:8000/api/comment/

    出现错误,找不到模板文件

     

    注意:使用postman,访问get请求,是不会报错的。但是使用浏览器访问,会报错。因为浏览器要渲染页面!

    修改settings.py,在INSTALLED_APPS配置项中,最后一行添加rest_framework

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'app01.apps.App01Config',
        'rest_framework',
    ]
    

    刷新页面,效果如下:

    这个页面,可以发送post请求

    serializers校验

    之前学到了form组件校验,那么serializers也可以校验。

    校验空数据

    举例:判断空数据

    修改views.py,添加post逻辑代码。注意:使用is_valid校验

     1 from django.shortcuts import render,HttpResponse
     2 from django.http import JsonResponse
     3 from app01 import models
     4 import json
     5 from rest_framework import serializers
     6 from django import views
     7 from rest_framework.views import APIView
     8 from app01 import app01_serialization  # 导入自定义的序列化
     9 from rest_framework.response import Response
    10 
    11 # Create your views here.
    12 
    13 class Comment(APIView):
    14     def get(self, request):
    15         res = {"code":0}  # 默认状态
    16         all_comment = models.Comment.objects.all()
    17         # print(all_comment)
    18         # 序列化,many=True表示返回多条
    19         ser_obj = app01_serialization.CommentSerializer(all_comment, many=True)
    20         res["data"] = ser_obj.data
    21 
    22         return Response(res)
    23 
    24     def post(self, request):
    25         res = {"code": 0}
    26         # 去提交的数据
    27         comment_data = self.request.data
    28         # 对用户提交的数据做校验
    29         ser_obj = app01_serialization.CommentSerializer(data=comment_data)
    30         if ser_obj.is_valid():
    31             # 表示数据没问题,可以创建
    32             pass
    33         else:
    34             # 表示数据有问题
    35             res["code"] = 1
    36             res["error"] = ser_obj.errors
    37         return Response(res)
    38 
    39         # return HttpResponse("创建新评论")
    40 
    41     def put(self, request):
    42         return HttpResponse("修改评论")
    43 
    44     def delete(self, request):
    45         return HttpResponse("删除评论")
    Views

    使用postman发送一个空数据的post请求

    它返回This field is required,表示次字段不能为空!

    它返回This field is required,表示次字段不能为空!

    错误信息中文显示

    修改app01_serializers.py,使用extra_kwargs指定错误信息

    重启django,重新发送空的post请求

    发送一个带参数的post请求,注意key为content

    code为0表示成功了!看一下app01_comment表记录,发现并没有增加数据?为什么呢?因为成功时,执行了pass,并没有执行插入操作!

    外键的GET和POST

    序列化校验

    上面虽然只发送了content参数,就让通过了,显然不合理!为什么呢?

    因为app01_comment表有2个字段,content和article。这2个字段都应该校验才对!

    因为serializers默认校验时,排除了外键字段。比如article

    要对外键进行校验,必须在extra_kwargs中指定外键字段

    修改app01_serializers.py,注意关闭depth参数

    当序列化类MATE中定义了depth时,这个序列化类中引用字段(外键)则自动变为只读,所以进行更新或者创建操作的时候不能使用此序列化类

    大概意思就是,使用了depth参数,会忽略外键字段

    完整代码如下:

    from app01 import models
    from rest_framework import serializers

    # 序列化评论的类
    class CommentSerializer(serializers.ModelSerializer):
    class Meta:
    model = models.Comment # Comment表
    fields = "__all__" # 序列化所有字段
    # depth = 2 # 深度为2
    # 定义额外的参数
    extra_kwargs = {
    "content": {
    "error_messages": {
    "required": '内容不能为空',
    }
    },
    "article": {
    "error_messages": {
    "required": '文章不能为空'
    }
    }
    }


    复制代码
    from app01 import models
    from rest_framework import serializers
    
    # 序列化评论的类
    class CommentSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Comment  # Comment表
            fields = "__all__"  # 序列化所有字段
            # depth = 2  # 深度为2
            # 定义额外的参数
            extra_kwargs = {
                "content": {
                    "error_messages": {
                        "required": '内容不能为空',
                    }
                },
                "article": {
                    "error_messages": {
                        "required": '文章不能为空'
                    }
                }
            }
    复制代码

    再次发送post请求,还是只有一个参数content

    查看执行结果:

     发送正确的2个参数

    查看结果

    read_only=True

     read_only:True表示不允许用户自己上传,只能用于api的输出。如果某个字段设置了read_only=True,那么就不需要进行数据验证,只会在返回时,将这个字段序列化后返回

     举例:允许article不校验

    修改app01_serializers.py,加入一行代码

    article = serializers.SerializerMethodField(read_only=True)

    完整代码如下:

    from app01 import models
    from rest_framework import serializers
    
    # 序列化评论的类
    class CommentSerializer(serializers.ModelSerializer):
        article = serializers.SerializerMethodField(read_only=True)
    
        class Meta:
            model = models.Comment  # Comment表
            fields = "__all__"  # 序列化所有字段
            # depth = 2  # 深度为2
            # 定义额外的参数
            extra_kwargs = {
                "content": {
                    "error_messages": {
                        "required": '内容不能为空',
                    }
                },
                "article": {
                    "error_messages": {
                        "required": '文章不能为空'
                    }
                }
            }
    

    只发content参数

    查看结果,发现通过了!

    这个参数在什么场景下,使用呢?

    举个简单的例子:在用户进行购物的时候,用户post订单时,肯定会产生一个订单号,而这个订单号应该由后台逻辑完成,而不应该由用户post过来,如果不设置read_only=True,那么验证的时候就会报错 

    保存post数据

    修改views.py,在post方法中,将pass改成ser_obj.save(),完整代码如下:

    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from app01 import models
    import json
    from rest_framework import serializers
    from django import views
    from rest_framework.views import APIView
    from app01 import app01_serializers  # 导入自定义的序列化
    from rest_framework.response import Response
    
    # Create your views here.
    
    class Comment(APIView):
        def get(self, request):
            res = {"code":0}  # 默认状态
            all_comment = models.Comment.objects.all()
            # print(all_comment)
            # 序列化,many=True表示返回多条
            ser_obj = app01_serializers.CommentSerializer(all_comment, many=True)
            res["data"] = ser_obj.data
    
            return Response(res)
    
        def post(self, request):
            res = {"code": 0}
            # 去提交的数据
            comment_data = self.request.data
            # 对用户提交的数据做校验
            ser_obj = app01_serializers.CommentSerializer(data=comment_data)
            if ser_obj.is_valid():
                # 表示数据没问题,可以创建
                ser_obj.save()
            else:
                # 表示数据有问题
                res["code"] = 1
                res["error"] = ser_obj.errors
            return Response(res)
    
            # return HttpResponse("创建新评论")
    
        def put(self, request):
            return HttpResponse("修改评论")
    
        def delete(self, request):
            return HttpResponse("删除评论")
    Views

    修改app01_serializers.py,注释掉read_only=True

    from app01 import models
    from rest_framework import serializers
    
    # 序列化评论的类
    class CommentSerializer(serializers.ModelSerializer):
        # article = serializers.SerializerMethodField(read_only=True)
    
        class Meta:
            model = models.Comment  # Comment表
            fields = "__all__"  # 序列化所有字段
            # depth = 2  # 深度为2
            # 定义额外的参数
            extra_kwargs = {
                "content": {
                    "error_messages": {
                        "required": '内容不能为空',
                    }
                },
                "article": {
                    "error_messages": {
                        "required": '文章不能为空'
                    }
                }
            }
    

    发送2个正确的参数

    查看返回结果

    查看app01_comment表记录,发现多了一条记录

    为什么直接save,就可以保存了呢?

    因为它将校验过的数据传过去了,就好像form组件中的self.cleaned_data一样

    非serializer 的验证条件

    比如重置密码、修改密码都需要手机验证码。但是用户 model 里面并没有验证码这个选项

    需要使用validate,用于做校验的钩子函数,类似于form组件的clean_字段名

    使用时,需要导入模块,用来输出错误信息

    from rest_framework.validators import ValidationError

    局部钩子

    validate_字段名,表示局部钩子。

    举例:评论的内容中,不能包含 "草"

    修改app01_serializers.py,校验评论内容

    from app01 import models
    from rest_framework import serializers
    from rest_framework.validators import ValidationError
    
    # 序列化评论的类
    class CommentSerializer(serializers.ModelSerializer):
        # article = serializers.SerializerMethodField(read_only=True)
    
        # 用于做校验的钩子函数,类似于form组件的clean_字段名
        def validate_content(self, value):
            if '草' in value:
                raise ValidationError('不符合社会主义核心价值观!')
            else:
                return value
    
        class Meta:
            model = models.Comment  # Comment表
            fields = "__all__"  # 序列化所有字段
            # depth = 2  # 深度为2
            # 定义额外的参数
            extra_kwargs = {
                "content": {
                    "error_messages": {
                        "required": '内容不能为空',
                    }
                },
                "article": {
                    "error_messages": {
                        "required": '文章不能为空'
                    }
                }
            }

    使用postman发送包含关键字的评论

    查看返回结果:

    全局钩子

    validate,表示全局钩子。

    比如在用户注册时,我们需要填写验证码,这个验证码只需要验证,不需要保存到用户这个Model中:

    def validate(self, attrs):
            del attrs["code"]
            return attrs

    这里就不演示了,about_drf项目还没有验证码功能!

    多对多的GET和POST

    路由

    修改app01_urls.py,增加路由

    from django.conf.urls import url
    from app01 import views
    
    urlpatterns = [
        # url(r'^article_list/', views.article_list),
        # url(r'article_detail/(d+)', views.article_detail),
    
        #文章
        url(r'article/',views.APIView.as_view()),
    
        # 评论
        url(r'comment/', views.Comment.as_view()),
    ]
    

    GET

    修改views.py, 增加视图函数

    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from app01 import models
    import json
    from rest_framework import serializers
    from django import views
    from rest_framework.views import APIView
    from app01 import app01_serializers  # 导入自定义的序列化
    from rest_framework.response import Response
    
    # Create your views here.
    
    class Comment(APIView):
        def get(self, request):
            res = {"code":0}  # 默认状态
            all_comment = models.Comment.objects.all()
            # print(all_comment)
            # 序列化,many=True表示返回多条
            ser_obj = app01_serializers.CommentSerializer(all_comment, many=True)
            res["data"] = ser_obj.data
    
            return Response(res)
    
        def post(self, request):
            res = {"code": 0}
            # 去提交的数据
            comment_data = self.request.data
            # 对用户提交的数据做校验
            ser_obj = app01_serializers.CommentSerializer(data=comment_data)
            if ser_obj.is_valid():
                # 表示数据没问题,可以创建
                ser_obj.save()
            else:
                # 表示数据有问题
                res["code"] = 1
                res["error"] = ser_obj.errors
            return Response(res)
    
            # return HttpResponse("创建新评论")
    
        def put(self, request):
            return HttpResponse("修改评论")
    
        def delete(self, request):
            return HttpResponse("删除评论")
    
    # 文章CBV
    class Article(APIView):
        def get(self, request):
            res = {"code": 0}
            all_article = models.Article.objects.all()
            ser_obj = app01_serializers.ArticleModelSerializer(all_article, many=True)
            res["data"] = ser_obj.data
            return Response(res)

    修改app01_serializers.py,增加文章的序列化类

    from app01 import models
    from rest_framework import serializers
    from rest_framework.validators import ValidationError
    
    # 序列化评论的类
    class CommentSerializer(serializers.ModelSerializer):
        # article = serializers.SerializerMethodField(read_only=True)
    
        # 用于做校验的钩子函数,类似于form组件的clean_字段名
        def validate_content(self, value):
            if '草' in value:
                raise ValidationError('不符合社会主义核心价值观!')
            else:
                return value
    
        #全局的钩子
        def validate(self, attrs):
            # self.validated_data  # 经过校验的数据 类似于form组件中的cleaned_data
            # 全局钩子
            pass
    
        class Meta:
            model = models.Comment  # Comment表
            fields = "__all__"  # 序列化所有字段
            # depth = 2  # 深度为2
            # 定义额外的参数
            extra_kwargs = {
                "content": {
                    "error_messages": {
                        "required": '内容不能为空',
                    }
                },
                "article": {
                    "error_messages": {
                        "required": '文章不能为空'
                    }
                }
            }
            
    # 文章的序列化类
    class ArticleModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Article  # 绑定的ORM类是哪一个
            fields = "__all__"  # ["id", "title", "type"]
            # depth = 1  # 官方推荐不超过10层

      发送get请求

    POST

    修改views.py,增加post

    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from app01 import models
    import json
    from rest_framework import serializers
    from django import views
    from rest_framework.views import APIView
    from app01 import app01_serialization  # 导入自定义的序列化
    from rest_framework.response import Response
    
    # Create your views here.
    
    class Comment(APIView):
        def get(self, request):
            res = {"code":0}  # 默认状态
            all_comment = models.Comment.objects.all()
            # print(all_comment)
            # 序列化,many=True表示返回多条
            ser_obj = app01_serialization.CommentSerializer(all_comment, many=True)
            res["data"] = ser_obj.data
    
            return Response(res)
    
        def post(self, request):
            res = {"code": 0}
            # 去提交的数据
            comment_data = self.request.data
            # 对用户提交的数据做校验
            ser_obj = app01_serialization.CommentSerializer(data=comment_data)
            if ser_obj.is_valid():
                # 表示数据没问题,可以创建
                ser_obj.save()
            else:
                # 表示数据有问题
                res["code"] = 1
                res["error"] = ser_obj.errors
            return Response(res)
    
            # return HttpResponse("创建新评论")
    
        def put(self, request):
            return HttpResponse("修改评论")
    
        def delete(self, request):
            return HttpResponse("删除评论")
    
    #文章CBV
    class Article(APIView):
        def get(self,request):
            res = {"code":0}
            all_article = models.Article.objects.all()
            ser_obj = app01_serialization.ValidationError(all_article,many=True)
            res["data"] = ser_obj.data
            return Response(res)
    
        def post(self,request):
            res = {"code":0}
            ser_obj = app01_serialization.ArticleModelSerializer(data=self.request.data)
            if ser_obj.is_valid():
                ser_obj.save()
            else:
                res["code"] = 1
                res["error"] = ser_obj.errors
            return Response(res)
    

    发送post请求,增加参数type

    查看结果

    提示不是一个有效的选项,为什么呢?因为article表的type字段的值,只有1和2。不能是其他的值!

    增加正确的值

    先使用postman发送get请求,然后复制一段json数据

    再使用postman发送post请求,参数如下

    {
        "type":1,
        "title": "Python是世界上最好的语言",
        "create_time": "2018-08-01",
        "update_time": "2018-08-01",
        "school":1,
        "tag":[
            1,
            2,
            3
        ]
    }
    

    注意参数类型为raw,表示未经过加工的。选择json格式

    查看返回状态,提示成功了

    查看app01_article表,发现多了一条记录

     

    唯一校验

    article表的titie加了唯一属性,在添加时,需要判断标题是否存在。

    那么serializers是不能做唯一校验的,为什么呢?

    针对这个问题,有人向官方提问了,官方回复,ORM可以做,这里就没有必要做了!

    重新提交上面的post请求,结果输出:

     

    这个中文提示,是在ORM的error_messages参数定义的

    title = models.CharField(max_length=32, unique=True, error_messages={"unique": "文章标题不能重复"})

    它还可以定义其他错误,比如长度,为空,唯一...

    这个中文提示,是在ORM的error_messages参数定义的

    title = models.CharField(max_length=32, unique=True, error_messages={"unique": "文章标题不能重复"})

    它还可以定义其他错误,比如长度,为空,唯一...

    需求:要求api返回结果中,school展示的是超链接

    路由

    修改app01_urls.py,增加路由

    urlpatterns = [
        # 文章
        url(r'article/', views.Article.as_view()),
        url(r'article/(?P<pk>d+)', views.ArticleDetail.as_view(), name='article-detail'),
        # 学校
        url(r'school/(?P<id>d+)', views.SchoolDetail.as_view(), name='school-detail'),
        # 评论
        url(r'comment/', views.Comment.as_view()),
    ]
    

    指定name是为做反向链接,它能解析出绝对url

    序列化

    修改app01_serializers.py

    from app01 import models
    from rest_framework import serializers
    from rest_framework.validators import ValidationError
    
    # 序列化评论的类
    class CommentSerializer(serializers.ModelSerializer):
        # article = serializers.SerializerMethodField(read_only=True)
    
        # 用于做校验的钩子函数,类似于form组件的clean_字段名
        def validate_content(self, value):
            if '' in value:
                raise ValidationError('不符合社会主义核心价值观!')
            else:
                return value
    
        #全局的钩子
        def validate(self, attrs):
            #self.validate_data #经过校验的数据 类似于form组件中的cleaned_data
            #全局钩子
            pass
    
        class Meta:
            model = models.Comment  # Comment表
            fields = "__all__"  # 序列化所有字段
            # depth = 2  # 深度为2
            # 定义额外的参数
            extra_kwargs = {
                "content": {
                    "error_messages": {
                        "required": '内容不能为空',
                    }
                },
                "article": {
                    "error_messages": {
                        "required": '文章不能为空'
                    }
                }
            }
    
    #文章的序列化类
    class ArticleModelSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.Article  #绑定的ORM类是哪一个
            fields = "__all__"
    
    #学校的序列化
    class SchoolSerializer(serializers.ModelSerializer):
        class Meta:
            model = models.School
            fields = "__all__"
    Views.py

    参数解释:

    source 表示来源

    lookup_field 表示查找字段

    lookup_url_kwarg 表示路由查找的参数,pk表示主键

    view_name  它是指urls定义的name值,一定要一一对应。

    修改views.py,增加视图函数

    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from app01 import models
    import json
    from rest_framework import serializers
    from django import views
    from rest_framework.views import APIView
    from app01 import app01_serializers  # 导入自定义的序列化
    from rest_framework.response import Response
    
    # Create your views here.
    
    class Comment(APIView):
        def get(self, request):
            res = {"code":0}  # 默认状态
            all_comment = models.Comment.objects.all()
            # print(all_comment)
            # 序列化,many=True表示返回多条
            ser_obj = app01_serializers.CommentSerializer(all_comment, many=True)
            res["data"] = ser_obj.data
    
            return Response(res)
    
        def post(self, request):
            res = {"code": 0}
            # 去提交的数据
            comment_data = self.request.data
            # 对用户提交的数据做校验
            ser_obj = app01_serializers.CommentSerializer(data=comment_data)
            if ser_obj.is_valid():
                # 表示数据没问题,可以创建
                ser_obj.save()
            else:
                # 表示数据有问题
                res["code"] = 1
                res["error"] = ser_obj.errors
            return Response(res)
    
            # return HttpResponse("创建新评论")
    
        def put(self, request):
            return HttpResponse("修改评论")
    
        def delete(self, request):
            return HttpResponse("删除评论")
    
    # 文章CBV
    class Article(APIView):
        def get(self, request):
            res = {"code": 0}
            all_article = models.Article.objects.all()
            ser_obj = app01_serializers.ArticleHyperLinkedSerializer(all_article, many=True, context={'request': request})
            res["data"] = ser_obj.data
            return Response(res)
    
        def post(self, request):
            res = {"code": 0}
            ser_obj = app01_serializers.ArticleModelSerializer(data=self.request.data)
            if ser_obj.is_valid():
                ser_obj.save()
            else:
                res["code"] = 1
                res["error"] = ser_obj.errors
            return Response(res)
    
    
    # 文章详情CBV
    class ArticleDetail(APIView):
        def get(self, request, pk):
            res = {"code": 0}
            article_obj = models.Article.objects.filter(pk=pk).first()
            # 序列化
            ser_obj = app01_serializers.ArticleHyperLinkedSerializer(article_obj, context={'request': request})
            res["data"] = ser_obj.data
            return Response(res)
    
    
    # 学校详情CBV
    class SchoolDetail(APIView):
        def get(self, request, id):
            res = {"code": 0}
            school_obj = models.School.objects.filter(pk=id).first()
            ser_obj = app01_serializers.SchoolSerializer(school_obj, context={'request': request})
            res["data"] = ser_obj.data
            return Response(res)
    Views.py

    参数解释: 

    id 表示参数,它和url的参数,是一一对应的

    content 表示上下文

    重启django项目,访问网页:

    http://127.0.0.1:8000/api/article/1

    效果如下:

    点击第一个链接,效果如下:

    今日内容总结:

    1. RESTful风格API介绍
    2. from rest_framework import APIView
    
    
    class Comment(views.View):
        pass
        
    --------  之前的写法 ↑  ----------
        
    class APIView(views.View):
        扩展的功能
        self.request = Resquest(
            self._request = request(Django的request)
        )
        self.request.data  封装好的数据属性,POSTPUT请求携带的数据都可以从这里取
        pass
    
    
    class Comment(APIView):
        pass
        
        
    3. serializer
        1. ModelSerializer
            1. 基于APIView实现的GET和POST
            2. POST过来的数据进行校验
            3. 保存数据
        2. 超链接的URL
    

      

     

      

      

      

      

      

      

      

     

     

     

      

  • 相关阅读:
    通过GUID生成可持久化的PID
    使用redisTemplate存储数据,出现xACxEDx00x05tx00
    二分查找法c语言实现
    请求路径@PathVariable注释中有点.英文句号的问题(忽略英文句号后面的后缀)
    windows下根据tcp端口查询对应的进程id(端口被占用)
    解决gradle项目每次编译都下载gradle-x.x-all.zip gradle-x.x-bin.zip
    HideTcpip.c
    ANSI C遍历二维数组指针地址
    sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    centos7安装VuePress
  • 原文地址:https://www.cnblogs.com/haowen980/p/9402679.html
Copyright © 2020-2023  润新知