django-rest-framework

web开发模式

  前后端混合开发(前后端不分离):返回的是html的内容,需要写模板
  前后端分离:只专注于写后端接口,返回json,xml格式数据

api接口 

  通过网络,规定了前后台信息交互规则的url链接,也就是前后台信息交互的媒介

Restful规范

  RESTful是一种定义Web API接口的设计风格,尤其适用于前后端分离的应用模式中。

# 10条规范
1  数据的安全保障:url链接一般都采用https协议进行传输 注:采用https协议,可以提高数据交互过程中的安全性
2 接口特征表现,一看就知道是个api接口
    - 用api关键字标识接口url:
      - [https://api.baidu.com](https://api.baidu.com/)
      - https://www.baidu.com/api
      注:看到api字眼,就代表该请求url链接是完成前后台数据交互的
      -路飞的接口:https://api.luffycity.com/api/v1/course/free/
3 多数据版本共存
    - 在url链接中标识数据版本
    - https://api.baidu.com/v1
    - https://api.baidu.com/v2
    注:url链接中的v1、v2就是不同数据版本的体现(只有在一种数据资源有多版本情况下)
4 数据即是资源,均使用名词(可复数)
    - 接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
      - https://api.baidu.com/users
      - https://api.baidu.com/books
      - https://api.baidu.com/book

      注:一般提倡用资源的复数形式,在url链接中奖励不要出现操作资源的动词,错误示范:https://api.baidu.com/delete-user
    - 特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义

      - https://api.baidu.com/place/search
      - https://api.baidu.com/login
5 资源操作由请求方式决定(method)
    - 操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
      - https://api.baidu.com/books - get请求:获取所有书
      - https://api.baidu.com/books/1 - get请求:获取主键为1的书
      - https://api.baidu.com/books - post请求:新增一本书书
      - https://api.baidu.com/books/1 - put请求:整体修改主键为1的书
      - https://api.baidu.com/books/1 - patch请求:局部修改主键为1的书
      - https://api.baidu.com/books/1 - delete请求:删除主键为1的书
6 过滤,通过在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:指定筛选条件
        
7 响应状态码
   7.1 正常响应
    - 响应状态码2xx
      - 200:常规请求
      - 201:创建成功
   7.2 重定向响应
    - 响应状态码3xx
      - 301:永久重定向
      - 302:暂时重定向
   7.3 客户端异常
    - 响应状态码4xx
      - 403:请求无权限
      - 404:请求路径不存在
      - 405:请求方法不存在
    7.4 服务器异常
    - 响应状态码5xx
      - 500:服务器异常
8 错误处理,应返回错误信息,error当做key { error: "无权限操作" } 9 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范 GET /collection:返回资源对象的列表(数组) GET /collection/resource:返回单个资源对象 POST /collection:返回新生成的资源对象 PUT /collection/resource:返回完整的资源对象 PATCH /collection/resource:返回完整的资源对象 DELETE /collection/resource:返回一个空文档 10 需要url请求的资源需要访问资源的请求链接 # Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么 { "status": 0, "msg": "ok", "results":[ { "name":"肯德基(罗餐厅)", "img": "https://image.baidu.com/kfc/001.png" } ... ] }

drf的安装和简单使用

# 安装:pip install djangorestframework==3.10.3
# 使用
    1 在setting.py 的app中注册
        INSTALLED_APPS = [
        'rest_framework'
        ]
    2 在models.py中写表模型
        class Book(models.Model):
            nid=models.AutoField(primary_key=True)
            name=models.CharField(max_length=32)
            price=models.DecimalField(max_digits=5,decimal_places=2)
            author=models.CharField(max_length=32)
    3 新建一个序列化类
        from rest_framework.serializers import ModelSerializer
        from app01.models import  Book
        class BookModelSerializer(ModelSerializer):
            class Meta:
                model = Book
                fields = "__all__"
    4 在视图中写视图类
        from rest_framework.viewsets import ModelViewSet
        from .models import Book
        from .ser import BookModelSerializer
        class BooksViewSet(ModelViewSet):
            queryset = Book.objects.all()
            serializer_class = BookModelSerializer
    5 写路由关系
        from app01 import views
        from rest_framework.routers import DefaultRouter
        router = DefaultRouter()  # 可以处理视图的路由器
        router.register('book', views.BooksViewSet)  # 向路由器中注册视图集
          # 将路由器中的所以路由信息追到到django的路由列表中
        urlpatterns = [
            path('admin/', admin.site.urls),
        ]
        #这是什么意思?两个列表相加
        # router.urls  列表
        urlpatterns += router.urls
        
    6 启动,在postman中测试即可

cbv源码

# ModelViewSet继承View(django原生View)
# APIView继承了View

# 先读View的源码
from django.views import View

# urls.py
path('books1/', views.Books.as_view()),  #在这个地方应该写个函数内存地址,views.Books.as_view()执行完,是个函数内存地址,as_view是一个类方法,类直接来调用,会把类自动传入
放了一个view的内存地址(View--》as_view--》内层函数)

# 请求来了,如果路径匹配,会执行,  函数内存地址(request)
def view(request, *args, **kwargs):
    #request是当次请求的request
    self = cls(**initkwargs)  #实例化得到一个对象,Book对象
    if hasattr(self, 'get') and not hasattr(self, 'head'):
        self.head = self.get
        self.request = request
        self.args = args
        self.kwargs = kwargs
        return self.dispatch(request, *args, **kwargs)

 
def dispatch(self, request, *args, **kwargs):
        #request是当次请求的request   self是book对象
        if request.method.lower() in self.http_method_names:
            #handler现在是:
            handler=getattr(self,'get'),你写的Book类的get方法的内存地址
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)  #执行get(request)

APIView源码分析

#from rest_framework.views import APIView
# urls.py
path('booksapiview/', views.BooksAPIView.as_view()),  #在这个地方应该写个函数内存地址

#APIView的as_view方法(类的绑定方法)
   def as_view(cls, **initkwargs):
        view = super().as_view(**initkwargs)  # 调用父类(View)的as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs
        # 以后所有的请求,都没有csrf认证了,只要继承了APIView,就没有csrf的认证
        return csrf_exempt(view)
 

#请求来了---》路由匹配上---》view(request)---》调用了self.dispatch(),会执行apiview的dispatch
    
# APIView的dispatch方法
    def dispatch(self, request, *args, **kwargs):

        self.args = args
        self.kwargs = kwargs
        # 重新包装成一个request对象,以后再用的request对象,就是新的request对象了
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            # 三大认证模块
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            # 响应模块
            response = handler(request, *args, **kwargs)

        except Exception as exc:
            # 异常模块
            response = self.handle_exception(exc)

        # 渲染模块
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
   
# APIView的initial方法
     def initial(self, request, *args, **kwargs):
        # 认证组件:校验用户 - 游客、合法用户、非法用户
        # 游客:代表校验通过,直接进入下一步校验(权限校验)
        # 合法用户:代表校验通过,将用户存储在request.user中,再进入下一步校验(权限校验)
        # 非法用户:代表校验失败,抛出异常,返回403权限异常结果
        self.perform_authentication(request)
        # 权限组件:校验用户权限 - 必须登录、所有用户、登录读写游客只读、自定义用户角色
        # 认证通过:可以进入下一步校验(频率认证)
        # 认证失败:抛出异常,返回403权限异常结果
        self.check_permissions(request)
        # 频率组件:限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数(3/s)
        # 没有达到限次:正常访问接口
        # 达到限次:限制时间内不能访问,限制时间达到后,可以重新访问
        self.check_throttles(request)
from rest_framework.request import Request
# 只要继承了APIView,视图类中的request对象,都是新的,也就是上面那个request的对象了
# 老的request在新的request._request
# 以后使用reqeust对象,就像使用之前的request是一模一样(因为重写了__getattr__方法)
  def __getattr__(self, attr):
        try:
            return getattr(self._request, attr) #通过反射,取原生的request对象,取出属性或方法
        except AttributeError:
            return self.__getattribute__(attr)

 # request.data 感觉是个数据属性,其实是个方法,@property,修饰了
    它是一个字典,post请求不管使用什么编码,传过来的数据,都在request.data
 #get请求传过来数据,从哪取?
    request.GET
    @property
    def query_params(self):
        """
        More semantically correct name for request.GET.
        """
        return self._request.GET
    
    #视图类中
     print(request.query_params)  #get请求,地址中的参数
     # 原来在
     print(request.GET)

局部禁用csrf

# 在视图函数上加装饰器@csrf_exempt
# csrf_exempt(view)这么写和在视图函数上加装饰器是一毛一样的

#urls.py中看到这种写法
path('tset/', csrf_exempt(views.test))

序列化组件

  1. 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串
  2. 反序列化,把客户端发送过来的数据,经过request以后变成字典,序列化器可以把字典转成模型
  3. 反序列化,完成数据校验功能

使用

# ser.py
class BookSerializer(serializers.Serializer):
    # id=serializers.CharField()
    name=serializers.CharField()
    # price=serializers.DecimalField()
    price=serializers.CharField()
    author=serializers.CharField()  
    publish=serializers.CharField()
    
# views.py
class BookView(APIView):
    def get(self,request,pk):
        book=Book.objects.filter(id=pk).first()
        #用一个类,毫无疑问,一定要实例化
        #要序列化谁,就把谁传过来
        book_ser=BookSerializer(book)  # 调用类的__init__
        # book_ser.data   序列化对象.data就是序列化后的字典
        return Response(book_ser.data)
    
# urls.py
re_path('books/(?P<pk>d+)', views.BookView.as_view())

序列化组件修改数据

1 写一个序列化的类,继承Serializer
2 在类中写要反序列化的字段,想反序列化哪个字段,就在类中写哪个字段,字段的属性(max_lenth......)
    max_length    最大长度
    min_lenght    最小长度
    allow_blank    是否允许为空
    trim_whitespace    是否截断空白字符
    max_value    最小值
    min_value    最大值
3 在视图类中使用,导入--》实例化得到序列化类的对象,把要要修改的对象传入,修改的数据传入
    boo_ser=BookSerializer(book,request.data)
    boo_ser=BookSerializer(instance=book,data=request.data)
4 数据校验 if boo_ser.is_valid()
5 如果校验通过,就保存
    boo_ser.save()  # 注意不是book.save()
6 如果不通过,逻辑自己写


7 如果字段的校验规则不够,可以写钩子函数(局部和全局)
        # 局部钩子
        def validate_price(self, data):   # validate_字段名  接收一个参数
            #如果价格小于10,就校验不通过
            # print(type(data))
            # print(data)
            if float(data)>10:
                return data
            else:
                #校验失败,抛异常
                raise ValidationError('价格太低')
         # 全局钩子
            def validate(self, validate_data):   # 全局钩子
                print(validate_data)
                author=validate_data.get('author')
                publish=validate_data.get('publish')
                if author == publish:
                    raise ValidationError('作者名字跟出版社一样')
                else:
                    return validate_data
8 可以使用字段的author=serializers.CharField(validators=[check_author]) ,来校验
    -写一个函数
        def check_author(data):
            if data.startswith('sb'):
                raise ValidationError('作者名字不能以sb开头')
            else:
                return data
     -配置:validators=[check_author]
# models.py
class Book(models.Model):
    id=models.AutoField(primary_key=True)
    name=models.CharField(max_length=32)
    price=models.DecimalField(max_digits=5,decimal_places=2)
    author=models.CharField(max_length=32)
    publish=models.CharField(max_length=32)

# ser.py

# from rest_framework.serializers import Serializer  # 就是一个类
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
# 需要继承 Serializer


def check_author(data):
    if data.startswith('sb'):
        raise ValidationError('作者名字不能以sb开头')
    else:
        return data


class BookSerializer(serializers.Serializer):
    # id=serializers.CharField()
    name=serializers.CharField(max_length=16,min_length=4)
    # price=serializers.DecimalField()
    price=serializers.CharField()
    author=serializers.CharField(validators=[check_author])  # validators=[] 列表中写函数内存地址
    publish=serializers.CharField()

    def validate_price(self, data):   # validate_字段名  接收一个参数
        #如果价格小于10,就校验不通过
        # print(type(data))
        # print(data)
        if float(data)>10:
            return data
        else:
            #校验失败,抛异常
            raise ValidationError('价格太低')
    def validate(self, validate_data):   # 全局钩子
        print(validate_data)
        author=validate_data.get('author')
        publish=validate_data.get('publish')
        if author == publish:
            raise ValidationError('作者名字跟出版社一样')
        else:
            return validate_data
    def update(self, instance, validated_data):
        #instance是book这个对象
        #validated_data是校验后的数据
        instance.name=validated_data.get('name')
        instance.price=validated_data.get('price')
        instance.author=validated_data.get('author')
        instance.publish=validated_data.get('publish')
        instance.save()  #book.save()   django 的orm提供的
        return instance

    
 #views.py
class BookView(APIView):
    def get(self,request,pk):
        book=Book.objects.filter(id=pk).first()
        #用一个类,毫无疑问,一定要实例化
        #要序列化谁,就把谁传过来
        book_ser=BookSerializer(book)  # 调用类的__init__
        # book_ser.data   序列化对象.data就是序列化后的字典
        return Response(book_ser.data)
        # return JsonResponse(book_ser.data)

    def put(self,request,pk):
        response_msg={'status':100,'msg':'成功'}
        # 找到这个对象
        book = Book.objects.filter(id=pk).first()
        # 得到一个序列化类的对象
        # boo_ser=BookSerializer(book,request.data)
        boo_ser=BookSerializer(instance=book,data=request.data)

        # 要数据验证(回想form表单的验证)
        if boo_ser.is_valid():  # 返回True表示验证通过
            boo_ser.save()  # 报错
            response_msg['data']=boo_ser.data
        else:
            response_msg['status']=101
            response_msg['msg']='数据校验失败'
            response_msg['data']=boo_ser.errors

        return Response(response_msg)
# urls.py
re_path('books/(?P<pk>d+)', views.BookView.as_view()),

read_only和write_only

read_only    表明该字段仅用于序列化输出,默认False,如果设置成True,postman中可以看到该字段,修改时,不需要传该字段
write_only    表明该字段仅用于反序列化输入,默认False,如果设置成True,postman中看不到该字段,修改时,该字段需要传

查询所有

# views.py
class BooksView(APIView):
    def get(self,request):
        response_msg = {'status': 100, 'msg': '成功'}
        books=Book.objects.all()
        book_ser=BookSerializer(books,many=True)  #序列化多条,如果序列化一条,不需要写
        response_msg['data']=book_ser.data
        return Response(response_msg)
    
#urls.py
path('books/', views.BooksView.as_view()),

新增数据

# views.py
class BooksView(APIView):

    # 新增
    def post(self,request):
        response_msg = {'status': 100, 'msg': '成功'}
        #修改才有instance,新增没有instance,只有data
        book_ser = BookSerializer(data=request.data)
        # book_ser = BookSerializer(request.data)  # 这个按位置传request.data会给instance,就报错了
        # 校验字段
        if book_ser.is_valid():
            book_ser.save()
            response_msg['data']=book_ser.data
        else:
            response_msg['status']=102
            response_msg['msg']='数据校验失败'
            response_msg['data']=book_ser.errors
        return Response(response_msg)
#ser.py 序列化类重写create方法
    def create(self, validated_data):
        instance=Book.objects.create(**validated_data)
        return instance
# urls.py
path('books/', views.BooksView.as_view()),

删除一个数据

# views.pyclass BookView(APIView):    def delete(self,request,pk):        ret=Book.objects.filter(pk=pk).delete()        return Response({'status':100,'msg':'删除成功'})# urls.pyre_path('books/(?P<pk>d+)', views.BookView.as_view()),

模型类序列化器

class BookModelSerializer(serializers.ModelSerializer):
    class Meta:
        model=Book  # 对应上models.py中的模型
        fields='__all__'
        # fields=('name','price','id','author') # 只序列化指定的字段
        # exclude=('name',) #跟fields不能都写,写谁,就表示排除谁
        # read_only_fields=('price',)
        # write_only_fields=('id',) #弃用了,使用extra_kwargs
        extra_kwargs = {  # 类似于这种形式name=serializers.CharField(max_length=16,min_length=4)
            'price': {'write_only': True},
        }

many=True

# 序列化多条,需要传many=True
# 
book_ser=BookModelSerializer(books,many=True)
book_one_ser=BookModelSerializer(book)
print(type(book_ser))
#<class 'rest_framework.serializers.ListSerializer'>
print(type(book_one_ser))
#<class 'app01.ser.BookModelSerializer'>

# 对象的生成--》先调用类的__new__方法,生成空对象
# 对象=类名(name=lqz),触发类的__init__()
# 类的__new__方法控制对象的生成


def __new__(cls, *args, **kwargs):
    if kwargs.pop('many', False):
        return cls.many_init(*args, **kwargs)
    # 没有传many=True,走下面,正常的对象实例化
    return super().__new__(cls, *args, **kwargs)

Serializer用法

# source的使用
    1 可以改字段名字  xxx=serializers.CharField(source='title')
    2 可以.跨表publish=serializers.CharField(source='publish.email')
    3 可以执行方法pub_date=serializers.CharField(source='test') test是Book表模型中的方法
    

# SerializerMethodField()的使用
    1 它需要有个配套方法,方法名叫get_字段名,返回值就是要显示的东西
    authors=serializers.SerializerMethodField() #它需要有个配套方法,方法名叫get_字段名,返回值就是要显示的东西
    def get_authors(self,instance):
        # book对象
        authors=instance.authors.all()  # 取出所有作者
        ll=[]
        for author in authors:
            ll.append({'name':author.name,'age':author.age})
        return ll

自己封装Respons对象

class MyResponse():
    def __init__(self):
        self.status=100
        self.msg='成功'
    @property
    def get_dict(self):
        return self.__dict__

if __name__ == '__main__':
    res=MyResponse()
    res.status=101
    res.msg='查询失败'
    # res.data={'name':'lqz'}
    print(res.get_dict)
原文地址:https://www.cnblogs.com/zhenghuiwen/p/13266996.html