zoukankan      html  css  js  c++  java
  • Python 29 Restful规范

    预备知识:

      -- 遵循rest风格实现的前后端交互都叫RESTful架构

      -- URI:统一资源标识符,相当于身份证号

      -- URL:统一资源定位符,相当于姓名

    RESTful规范:

    一、核心思想

    1、面向资源编程,url中尽量用名词而不是动词

    2、根据HTTP请求方式的不同对资源进行不同操作。

    二、在url中体现的规范

    1、体现版本

    2、体现是否是API

    3、有过滤条件

    4、尽量用https

    三、在返回值中的规范

    1、携带状态码

    2、返回值:get返回查看的所有或单条数据;post返回新增的这条数据;put / patch 返回更新的这条数据;delete 返回值空

    3、携带错误信息

    4、携带超链接

    RestFramework

    使用:

    1、pip install djangorestframework

    2、在settings的app中注册rest_framework

    一、序列化

    from SerDemo import models
    from SerDemo.serializers import BookSerializer
    from rest_framework.response import Response
    from rest_framework.views import APIView
    
    # Create your views here.
    
    class BookView(APIView):
        def get(self, request, book_id=0):
            if book_id == 0:
                books_query = models.Books.objects.all()
            else:
                books_query = models.Books.objects.filter(id=book_id)
    
            ret = BookSerializer(books_query, many=True)
            return Response(ret.data)  # 序列化后的数据保存在.data里
    
        def post(self, request):
            book_obj = request.data
            ser_obj = BookSerializer(data=book_obj)
            if ser_obj.is_valid():
                ser_obj.save()
                return Response(ser_obj.validated_data)
            return Response(ser_obj.errors)
    
        def put(self, request, book_id):
            book_obj = models.Books.objects.filter(id=book_id).first()
            ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True)  # 表示部分校验
            if ser_obj.is_valid():
                ser_obj.save()
                return Response(ser_obj.validated_data)
            return Response(ser_obj.errors)
    views.py
    from SerDemo import models
    from rest_framework import serializers
    
    class AuthorsSerializer(serializers.Serializer):
        id = serializers.IntegerField()
        name = serializers.CharField(max_length=32)
    
    class PublisherSerializer(serializers.Serializer):
        id = serializers.IntegerField()
        name = serializers.CharField(max_length=32)
    
    class BookSerializer(serializers.Serializer):
        id = serializers.IntegerField(required=False)  # 表示验证时不需要传
        tittle = serializers.CharField(max_length=32)
        publisher = PublisherSerializer(read_only=True)  # 只在取值时验证
        publisher_id = serializers.IntegerField(write_only=True)  # 只在传值时验证
        authors = AuthorsSerializer(many=True, read_only=True)  # many表示需要循环
        authors_list = serializers.ListField(write_only=True)
    
        def create(self, validated_data):  # 创建数据需要自定义create方法
            book_obj = models.Books.objects.create(tittle=validated_data["tittle"], publisher_id=validated_data["publisher_id"])
            book_obj.authors.add(*validated_data["authors_list"])
            return book_obj
    
        def update(self, instance, validated_data):  # 更新数据需要自定义update方法
            instance.tittle = validated_data.get("tittle", instance.tittle)
            instance.publisher_id = validated_data.get("publisher_id", instance.publisher_id)
            if validated_data.get("authors_list"):
                instance.authors.set(validated_data["authors_list"])
            instance.save()  # 注意这一步
            return instance
    serializers.py

    restframework的序列化也提供了钩子函数,用法和forms一样,不过是validate_tittle形式

    def validate_tittle(self, value):
        # value就是tittle的值 对value处理
        if "python" not in value.lower():
            raise serializers.ValidationError("标题必须含有python")
        return value
    def validate(self, attrs):
        # attrs 字典有你传过来的所有的字段
        print(attrs)
        if "python" in attrs["title"].lower() or attrs["post_category"] == 1:
            return attrs
        else:
            raise serializers.ValidationError("分类或标题不合符要求")
    class BookSerializer(serializers.ModelSerializer):
    
        publisher_info = serializers.SerializerMethodField(read_only=True)  # 会在查看时新增加一组键值
        authors_info = serializers.SerializerMethodField(read_only=True)
    
        def get_authors_info(self, obj):  # obj就是传入的Book对象
            authors_querset = obj.authors.all()
            return [{"id": author.id, "name": author.name} for author in authors_querset]
    
        def get_publisher_info(self, obj):
            publisher_obj = obj.publisher
            return {"id": publisher_obj.id, "title": publisher_obj.title}
    
        class Meta:
            model = models.Books
            fields = "__all__"
            # exclude=["id"]
            
            # depth = 1   # 会让你这些所有的外键关系变成read_only = True,并且会显示所有字段,一般不用,会自定义
            extra_kwargs = {"publisher": {"write_only": True}, "authors": {"write_only": True}}
    ModelSerializers

    二、视图

     url(r'^book$', BookModelView.as_view({"get": "list", "post": "create"})),
     url(r'^book/(?P<pk>d+)', BookModelView.as_view({"get": "retrieve", "put": "update", "delete": "destroy"})),
    from rest_framework import viewsets
    class
    BookModelView(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer
    class ModelViewSet(mixins.CreateModelMixin,
                       mixins.RetrieveModelMixin,
                       mixins.UpdateModelMixin,
                       mixins.DestroyModelMixin,
                       mixins.ListModelMixin,
                       GenericViewSet):
    ModelViewSets
    GenericViewSet 继承了ViewSetMixin,里面重写了as_view方法,让as_view可以接受参数,并且将请求方式与对应的方法对应起来,所以继承了ModelViewSet方法就能给as_view传参。
    GenericViewSet

    一共可以说有三次封装,第一次封装将获取queryset对象以及获取序列化器的两个方法封装到GenericAPIView

    class GenericAPIView(views.APIView):
        queryset = None
        serializer_class = None
    
        def get_queryset(self):
            queryset = self.queryset
            if isinstance(queryset, QuerySet):
                # Ensure queryset is re-evaluated on each request.
                queryset = queryset.all()
            return queryset
    
        def get_serializer(self, *args, **kwargs):
            serializer_class = self.get_serializer_class()
            kwargs['context'] = self.get_serializer_context()
            return serializer_class(*args, **kwargs)
    
        def get_serializer_class(self):
            return self.serializer_class
    GenericAPIView

    第二次封装,将各个视图方法封装成各个类ListModelMixin,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,DestroyModelMin

    地三次封装,重写as_view方法,让as_view可以接受参数,在self.dispatch之前,将self.get = self.list......

    三、路由组件

    rest_framework提供了自动生成传参路由的功能

    from rest_framework.routers import DefaultRouter
    
    router = DefaultRouter()
    router.register(r'^books')
    urlpatterns += router.urls

    这样会自动生成多个路由,包括books/(d+),且都as_view中都带有参数,对应视图必须可以接受参数,但是一般不使用,因为生成的路由多也是一种浪费,也会有风险。

    四、版本控制

    在APIView的dispatch方法中调用了initial方法,其中包含了版本控制的代码:

    # Determine the API version, if versioning is in use.
    version, scheme = self.determine_version(request, *args, **kwargs)
    request.version, request.versioning_scheme = version, scheme  # 版本和使用的类

    如果没有version_class类,将把None赋值给上面两个属性,默认的version_class类是api.settings.DEFAULT_VERSIONING_CLASS,默认是None。

    restframework给我们提供了一些类,用来做版本控制,可以通过url匹配、params等方法获取版本信息:

    # coding: utf-8
    from __future__ import unicode_literals
    
    import re
    
    from django.utils.translation import ugettext_lazy as _
    
    from rest_framework import exceptions
    from rest_framework.compat import unicode_http_header
    from rest_framework.reverse import _reverse
    from rest_framework.settings import api_settings
    from rest_framework.templatetags.rest_framework import replace_query_param
    from rest_framework.utils.mediatypes import _MediaType
    
    
    class BaseVersioning(object):
        default_version = api_settings.DEFAULT_VERSION
        allowed_versions = api_settings.ALLOWED_VERSIONS
        version_param = api_settings.VERSION_PARAM
    
        def determine_version(self, request, *args, **kwargs):
            msg = '{cls}.determine_version() must be implemented.'
            raise NotImplementedError(msg.format(
                cls=self.__class__.__name__
            ))
    
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            return _reverse(viewname, args, kwargs, request, format, **extra)
    
        def is_allowed_version(self, version):
            if not self.allowed_versions:
                return True
            return ((version is not None and version == self.default_version) or
                    (version in self.allowed_versions))
    
    
    class AcceptHeaderVersioning(BaseVersioning):
        """
        GET /something/ HTTP/1.1
        Host: example.com
        Accept: application/json; version=1.0
        """
        invalid_version_message = _('Invalid version in "Accept" header.')
    
        def determine_version(self, request, *args, **kwargs):
            media_type = _MediaType(request.accepted_media_type)
            version = media_type.params.get(self.version_param, self.default_version)
            version = unicode_http_header(version)
            if not self.is_allowed_version(version):
                raise exceptions.NotAcceptable(self.invalid_version_message)
            return version
    
        # We don't need to implement `reverse`, as the versioning is based
        # on the `Accept` header, not on the request URL.
    
    
    class URLPathVersioning(BaseVersioning):
        """
        To the client this is the same style as `NamespaceVersioning`.
        The difference is in the backend - this implementation uses
        Django's URL keyword arguments to determine the version.
    
        An example URL conf for two views that accept two different versions.
    
        urlpatterns = [
            url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
            url(r'^(?P<version>[v1|v2]+)/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail')
        ]
    
        GET /1.0/something/ HTTP/1.1
        Host: example.com
        Accept: application/json
        """
        invalid_version_message = _('Invalid version in URL path.')
    
        def determine_version(self, request, *args, **kwargs):
            version = kwargs.get(self.version_param, self.default_version)
            if version is None:
                version = self.default_version
    
            if not self.is_allowed_version(version):
                raise exceptions.NotFound(self.invalid_version_message)
            return version
    
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            if request.version is not None:
                kwargs = {} if (kwargs is None) else kwargs
                kwargs[self.version_param] = request.version
    
            return super(URLPathVersioning, self).reverse(
                viewname, args, kwargs, request, format, **extra
            )
    
    
    class NamespaceVersioning(BaseVersioning):
        """
        To the client this is the same style as `URLPathVersioning`.
        The difference is in the backend - this implementation uses
        Django's URL namespaces to determine the version.
    
        An example URL conf that is namespaced into two separate versions
    
        # users/urls.py
        urlpatterns = [
            url(r'^/users/$', users_list, name='users-list'),
            url(r'^/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail')
        ]
    
        # urls.py
        urlpatterns = [
            url(r'^v1/', include('users.urls', namespace='v1')),
            url(r'^v2/', include('users.urls', namespace='v2'))
        ]
    
        GET /1.0/something/ HTTP/1.1
        Host: example.com
        Accept: application/json
        """
        invalid_version_message = _('Invalid version in URL path. Does not match any version namespace.')
    
        def determine_version(self, request, *args, **kwargs):
            resolver_match = getattr(request, 'resolver_match', None)
            if resolver_match is None or not resolver_match.namespace:
                return self.default_version
    
            # Allow for possibly nested namespaces.
            possible_versions = resolver_match.namespace.split(':')
            for version in possible_versions:
                if self.is_allowed_version(version):
                    return version
            raise exceptions.NotFound(self.invalid_version_message)
    
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            if request.version is not None:
                viewname = self.get_versioned_viewname(viewname, request)
            return super(NamespaceVersioning, self).reverse(
                viewname, args, kwargs, request, format, **extra
            )
    
        def get_versioned_viewname(self, viewname, request):
            return request.version + ':' + viewname
    
    
    class HostNameVersioning(BaseVersioning):
        """
        GET /something/ HTTP/1.1
        Host: v1.example.com
        Accept: application/json
        """
        hostname_regex = re.compile(r'^([a-zA-Z0-9]+).[a-zA-Z0-9]+.[a-zA-Z0-9]+$')
        invalid_version_message = _('Invalid version in hostname.')
    
        def determine_version(self, request, *args, **kwargs):
            hostname, separator, port = request.get_host().partition(':')
            match = self.hostname_regex.match(hostname)
            if not match:
                return self.default_version
            version = match.group(1)
            if not self.is_allowed_version(version):
                raise exceptions.NotFound(self.invalid_version_message)
            return version
    
        # We don't need to implement `reverse`, as the hostname will already be
        # preserved as part of the REST framework `reverse` implementation.
    
    
    class QueryParameterVersioning(BaseVersioning):
        """
        GET /something/?version=0.1 HTTP/1.1
        Host: example.com
        Accept: application/json
        """
        invalid_version_message = _('Invalid version in query parameter.')
    
        def determine_version(self, request, *args, **kwargs):
            version = request.query_params.get(self.version_param, self.default_version)
            if not self.is_allowed_version(version):
                raise exceptions.NotFound(self.invalid_version_message)
            return version
    
        def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
            url = super(QueryParameterVersioning, self).reverse(
                viewname, args, kwargs, request, format, **extra
            )
            if request.version is not None:
                return replace_query_param(url, self.version_param, request.version)
            return url
    rest_framework.versioning

    使用方式:

    # 在settings中配置要使用的版本控制类
    REST_FRAMEWORK = {
        # 默认使用的版本控制类
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
        # 允许的版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],
        # 版本使用的参数名称
        'VERSION_PARAM': 'version',
        # 默认使用的版本
        'DEFAULT_VERSION': 'v1',
    }
    urlpatterns = [
        url(r"^versions", MyView.as_view()),
        url(r"^(?P<version>[v1|v2]+)/test01", TestView.as_view()),
    ]
    class TestView(APIView):
        def get(self, request, *args, **kwargs):
            print(request.versioning_scheme)
            ret = request.version
            if ret == "v1":
                return Response("版本v1的信息")
            elif ret == "v2":
                return Response("版本v2的信息")
            else:
                return Response("根本就匹配不到这个路由")

    五、认证组件

    在APIView中有一个认证组件,在initial方法里,版本控制的数据处理后进行认证:

    self.perform_authentication(request)  # 执行request.user
    def perform_authentication(self, request):
        request.user

    在Request类中:

    @property
    def user(self):
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user
    def _authenticate(self):
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise
    
            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return
    
        self._not_authenticated()
    _authenticate

    self.authoenticators是在实例化Request类时传递进来的,authenticators=self.get_authenticators()

    def get_authenticators(self):
        return [auth() for auth in self.authentication_classes]

    authentication_classes默认是空的,所以需要自己定义,传递的就是每个认证类的实例化对象,对象中需要定义authenticate方法,返回一个元祖,分别赋值给request.user和request.auth。

    使用:

    这里认证的方式是通过token,在用户表中有一个token字段,每次登陆成功都会重新生成一个uuid,将这个uuid传递给前端,作为当前用户的token,后面每次访问都需要携带这个token。

    from rest_framework.exception import AuthenticationFailed
    
    class MyAuth(BaseAuthentication):
    
        def authenticate(self, request):
            request_token = request.query_params.get("token", None)
            if not request_token:
                raise AuthenticationFailed({"code": 1001, "error": "缺少token"})
            token_obj = UserInfo.objects.filter(token=request_token).first()
            if not token_obj:
                raise AuthenticationFailed({"code": 1001, "error": "无效的token"})
            return token_obj.username, token_obj
    MyAuth类
    class TestAuthView(APIView):
        authentication_classes = [MyAuth, ]
    
        def get(self, request, *args, **kwargs):
            return Response("测试认证")
    视图类

    如果需要全局配置:

    REST_FRAMEWORK = {
        # 默认使用的版本控制类
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
        # 允许的版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],
        # 版本使用的参数名称
        'VERSION_PARAM': 'version',
        # 默认使用的版本
        'DEFAULT_VERSION': 'v1',
        # 配置全局认证
        'DEFAULT_AUTHENTICATION_CLASSES': ["BRQP.utils.MyAuth", ]
    }
    settings.py

    六、权限组件

    权限组件的源码和认证组件几乎一样,需要自定义权限校验的类

    class MyPermission(BasePermission):
        message = "VIP用户才能访问"
    
        def has_permission(self, request, view):
            """
            自定义权限只有vip用户能访问,
            注意我们初始化时候的顺序是认证在权限前面的,所以只要认证通过~
            我们这里就可以通过request.user,拿到我们用户信息
            request.auth就能拿到用户对象
            """
            if request.user and request.auth.type == 2:
                return True
            else:
                return False
    class TestAuthView(APIView):
        authentication_classes = [MyAuth, ]
        permission_classes = [MyPermission, ]
    
        def get(self, request, *args, **kwargs):
            print(request.user)
            print(request.auth)
            username = request.user
            return Response(username)
    REST_FRAMEWORK = {
        # 默认使用的版本控制类
        'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
        # 允许的版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],
        # 版本使用的参数名称
        'VERSION_PARAM': 'version',
        # 默认使用的版本
        'DEFAULT_VERSION': 'v1',
        # 配置全局认证
        # 'DEFAULT_AUTHENTICATION_CLASSES': ["BRQP.utils.MyAuth", ]
        # 配置全局权限
        "DEFAULT_PERMISSION_CLASSES": ["BROP.utils.MyPermission"]
    }
    全局使用

    七、频率组件

    # by gaoxin
    from rest_framework.throttling import BaseThrottle
    import time
    
    VISIT_RECORD = {}
    
    
    class MyThrottle(BaseThrottle):
    
        def __init__(self):
            self.history = None
    
        def allow_request(self, request, view):
            # 实现限流的逻辑
            # 以IP限流
            # 访问列表 {IP: [time1, time2, time3]}
            # 1, 获取请求的IP地址
            ip = request.META.get("REMOTE_ADDR")
            # 2,判断IP地址是否在访问列表
            now = time.time()
            if ip not in VISIT_RECORD:
                # --1, 不在 需要给访问列表添加key,value
                VISIT_RECORD[ip] = [now,]
                return True
                # --2 在 需要把这个IP的访问记录 把当前时间加入到列表
            history = VISIT_RECORD[ip]
            history.insert(0, now)
            # 3, 确保列表里最新访问时间以及最老的访问时间差 是1分钟
            while history and history[0] - history[-1] > 60:
                history.pop()
            self.history = history
            # 4,得到列表长度,判断是否是允许的次数
            if len(history) > 3:
                return False
            else:
                return True
    
        def wait(self):
            # 返回需要再等多久才能访问
            time = 60 - (self.history[0] - self.history[-1])
            return time
    自定义的权限类
    REST_FRAMEWORK = {
        # ......
        # 频率限制的配置
        "DEFAULT_THROTTLE_CLASSES": ["Throttle.throttle.MyThrottle"],
        }
    }
    配置自定义权限类
    from rest_framework.throttling import SimpleRateThrottle
    
    
    class MyVisitThrottle(SimpleRateThrottle):
        scope = "WD"
    
        def get_cache_key(self, request, view):
            return self.get_ident(request)
    使用自带的频率组件
    REST_FRAMEWORK = {
        # 频率限制的配置
        # "DEFAULT_THROTTLE_CLASSES": ["Throttle.throttle.MyVisitThrottle"],
        "DEFAULT_THROTTLE_CLASSES": ["Throttle.throttle.MyThrottle"],
        "DEFAULT_THROTTLE_RATES":{
            'WD':'5/m',         #速率配置每分钟不能超过5次访问,WD是scope定义的值,
    
        }
    }
    配置频率限制

    八、分页

    https://www.cnblogs.com/GGGG-XXXX/articles/9867882.html

    九、解析器

    content-type告诉对方是什么样的数据类型。

    • multipart/form-data 对应文件类型数据
    • application/x-www-form-urlencoded 对应表单数据类型
    • application/json 对应json数据类型

    accept告诉对方我能解析什么样的数据类型。

    十、渲染器

    rest_framework提供了两种测试接口的渲染器,一种是默认的、有页面样式的模板,一种是纯json数据,但是无法自定义请求头,所以还是需要postman来测试接口。

  • 相关阅读:
    HFUT 1356.转啊转(安徽省2016“京胜杯”程序设计大赛 E)
    HFUT 1354.砝码称重(安徽省2016“京胜杯”程序设计大赛 A)
    AOJ 331.汉诺塔
    AOJ 763.过河卒
    AOJ 762.分数数列
    AOJ 761.Fibonacci序列
    AOJ 760.尾数相等的数
    POJ 1035.Spell checker
    POJ 2299.Ultra-QuickSort
    POJ 2503.Babelfish
  • 原文地址:https://www.cnblogs.com/yinwenjie/p/11301219.html
Copyright © 2011-2022 走看看