Django让web开发更简单(十二):rest_framewwork的类视图详解

Request && Response

前面章节我们有提到视图用于整合请求和响应,因此,我们有必要先了解一下rest_framework框架的请求和响应。

1、Request是基于django的HttpRequest扩展,但并非继承关系,因此在使用的时候,可以组合使用。而对于HttpRequest的标准属性方法,也是可以使用的。对于请求,提供了一些兼容,如兼容了POST请求的表单请求和JSON请求,也就是直接能够解析,不需要在代码里写逻辑。

总之,Request相对于django自带的HttpRequest,方便了一些,正如源码所述:

"""
The Request class is used as a wrapper around the standard request object.

The wrapped request then offers a richer API, in particular :

    - content automatically parsed according to `Content-Type` header,
      and available as `request.data`
    - full support of PUT method, including support for file uploads
    - form overloading of HTTP method, content type and content
"""

对于request的解析,总结如下:

方法能够解析的请求
request.dataPOST\PATCH\PUT,不仅仅支持表单,也支持JSON
request.query_params任何HTTP method类型都可能包含查询参数,而不止GET

因此,在取数据的时候,一般调用这2个方法,如request.data整合了POST请求的表单或JSON,不再需要判断数据是在request.body还是在 request.FILES中,这样就很方便的使用了。

2、Response与普通HttpResponse对象不同,不会使用渲染的内容实例化Response对象。相反,可以传递的是未渲染的数据,可能包含任何Python对象。由于Response类使用的渲染器不能处理复杂的数据类型(比如Django 的模型实例),所以需要在创建Response对象之前将数据序列化为基本的数据类型。

参数作用
data响应的序列化数据
status响应的状态代码。默认为200
template_name选择HTMLRenderer时使用的模板名称。
header设置 HTTP header,字典类型
content_type响应的内容类型,通常渲染器会根据内容协商的结果自动设置,但有些时候 需要手动指定。

Response可以跟HttpResponse一样使用,因为它提供了所有常用的属性和方法。

APIview

REST framework 提供了一个 APIView类,它继承于Django的View类。APIView类与不同的View类有所不同:

该视图将管理内容协商,并在响应中设置正确的渲染器。 任何APIException异常都会被捕获并进行适当的响应。 传入的请求会进行认证,在请求分派给处理方法之前将进行适当的权限检查(允许或限制)。
像往常一样,使用APIView类与使用常规View类非常相似,传入的请求被分派到适当的处 理方法,如.get()或.post()。此外,可以在类上设置许多属性(AOP)。

class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

    # Allow dependency injection of other settings to make testing easier.
    settings = api_settings

    schema = DefaultSchema()

如上是APIview的部分源码,确实继承了如下django的view:

class View:
    """
    Intentionally simple parent class for all views. Only implements
    dispatch-by-method and simple sanity checking.
    """

    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    def __init__(self, **kwargs):
        """
        Constructor. Called in the URLconf; can contain helpful extra
        keyword arguments, and other things.
        """
        # Go through keyword arguments, and either save their values to our
        # instance, or raise an error.
        for key, value in kwargs.items():
            setattr(self, key, value)

    @classonlymethod
    def as_view(cls, **initkwargs):
        """Main entry point for a request-response process."""
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError(
                    'The method name %s is not accepted as a keyword argument '
                    'to %s().' % (key, cls.__name__)
                )
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            self.setup(request, *args, **kwargs)
            if not hasattr(self, 'request'):
                raise AttributeError(
                    "%s instance has no 'request' attribute. Did you override "
                    "setup() and forget to call super()?" % cls.__name__
                )
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

    def setup(self, request, *args, **kwargs):
        """Initialize attributes shared by all view methods."""
        if hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get
        self.request = request
        self.args = args
        self.kwargs = kwargs

    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn't exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn't on the approved list.
        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
        return handler(request, *args, **kwargs)

    def http_method_not_allowed(self, request, *args, **kwargs):
        logger.warning(
            'Method Not Allowed (%s): %s', request.method, request.path,
            extra={'status_code': 405, 'request': request}
        )
        return HttpResponseNotAllowed(self._allowed_methods())

    def options(self, request, *args, **kwargs):
        """Handle responding to requests for the OPTIONS HTTP verb."""
        response = HttpResponse()
        response['Allow'] = ', '.join(self._allowed_methods())
        response['Content-Length'] = '0'
        return response

    def _allowed_methods(self):
        return [m.upper() for m in self.http_method_names if hasattr(self, m)]

和django一样,as_view作为请求-响应的入口和出口,也就是说,这个方法需要加载到路由里。

在视图里,可以重写请求方法(get/put等),如:

class dataView(APIView):

    def post(self,request):
        data = request.data
        
	def get(self,request):
        data = request.query_params
class UsernameIsExistedView(APIView):

    def get(self, request, username):
        count = User.objects.filter(username=username).count()
        # count = user.count()
        one_dict = {
            'username': username,
            'count': count
        }

        return Response(one_dict)

类视图给我们做了什么呢?我们先看看不用类视图的实现方式:

def add(request):
    #POST请求
    name = request.POST.get('name','')

如上,编写接口时,继承的是request。而APIview中是将request作为参数引入了进来。

可见,APIview对request进行了封装,可以重写post等方法,使其用起来更加方便。

除了基于类的视图,基于方法的视图也被提供,api_view装饰器用 http_method_names来设置视图允许响应的 HTTP 方法列表,如:@api_view(http_method_names=[‘GET’])

使用方式:

from rest_framework.decorators import api_view 

@api_view() 
def	hello_world(request): 
	return	Response({"message":"Hello,	world!"})

这样就可以让方法具有类视图的功能。

通用视图

基于类的视图的一个主要优点是它们允许你编写可重复使用的行为。REST framework 通过提供大量预构建视图来提供常用模式,从而充分利用了这一点。REST framework 提供的通用视图允许您快速构建紧密映射到数据库模型的 API 视图。如果通用视图不符合需求,可以使用常规的APIView类,或者利用mixin特性和基类组合出可重用的视图。

这里关注点是预置,即写好的视图。

1、GenericAPIView:
继承自APIVIew,主要增加了操作序列化器和数据库查询的方法,作用是为Mixin扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类。

class GenericAPIView(views.APIView):
    """
    Base class for all other generic views.
    """
    # You'll need to either set these attributes,
    # or override `get_queryset()`/`get_serializer_class()`.
    # If you are overriding a view method, it is important that you call
    # `get_queryset()` instead of accessing the `queryset` property directly,
    # as `queryset` will get evaluated only once, and those results are cached
    # for all subsequent requests.
    queryset = None
    serializer_class = None

    # If you want to use object lookups other than pk, set 'lookup_field'.
    # For more complex lookup requirements override `get_object()`.
    lookup_field = 'pk'
    lookup_url_kwarg = None

    # The filter backend classes to use for queryset filtering
    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS

    # The style to use for queryset pagination.
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS

    def get_queryset(self):
        """
        Get the list of items for this view.
        This must be an iterable, and may be a queryset.
        Defaults to using `self.queryset`.

        This method should always be used rather than accessing `self.queryset`
        directly, as `self.queryset` gets evaluated only once, and those results
        are cached for all subsequent requests.

        You may want to override this if you need to provide different
        querysets depending on the incoming request.

        (Eg. return a list of items that is specific to the user)
        """
        assert self.queryset is not None, (
            "'%s' should either include a `queryset` attribute, "
            "or override the `get_queryset()` method."
            % self.__class__.__name__
        )

        queryset = self.queryset
        if isinstance(queryset, QuerySet):
            # Ensure queryset is re-evaluated on each request.
            queryset = queryset.all()
        return queryset

    def get_object(self):
        """
        Returns the object the view is displaying.

        You may want to override this if you need to provide non-standard
        queryset lookups.  Eg if objects are referenced using multiple
        keyword arguments in the url conf.
        """
        queryset = self.filter_queryset(self.get_queryset())

        # Perform the lookup filtering.
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

        assert lookup_url_kwarg in self.kwargs, (
            'Expected view %s to be called with a URL keyword argument '
            'named "%s". Fix your URL conf, or set the `.lookup_field` '
            'attribute on the view correctly.' %
            (self.__class__.__name__, lookup_url_kwarg)
        )

        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
        obj = get_object_or_404(queryset, **filter_kwargs)

        # May raise a permission denied
        self.check_object_permissions(self.request, obj)

        return obj

    def get_serializer(self, *args, **kwargs):
        """
        Return the serializer instance that should be used for validating and
        deserializing input, and for serializing output.
        """
        serializer_class = self.get_serializer_class()
        kwargs.setdefault('context', self.get_serializer_context())
        return serializer_class(*args, **kwargs)

    def get_serializer_class(self):
        """
        Return the class to use for the serializer.
        Defaults to using `self.serializer_class`.

        You may want to override this if you need to provide different
        serializations depending on the incoming request.

        (Eg. admins get full serialization, others get basic serialization)
        """
        assert self.serializer_class is not None, (
            "'%s' should either include a `serializer_class` attribute, "
            "or override the `get_serializer_class()` method."
            % self.__class__.__name__
        )

        return self.serializer_class

    def get_serializer_context(self):
        """
        Extra context provided to the serializer class.
        """
        return {
            'request': self.request,
            'format': self.format_kwarg,
            'view': self
        }

    def filter_queryset(self, queryset):
        """
        Given a queryset, filter it with whichever filter backend is in use.

        You are unlikely to want to override this method, although you may need
        to call it either from a list view, or from a custom `get_object`
        method if you want to apply the configured filtering backend to the
        default queryset.
        """
        for backend in list(self.filter_backends):
            queryset = backend().filter_queryset(self.request, queryset, self)
        return queryset

    @property
    def paginator(self):
        """
        The paginator instance associated with the view, or `None`.
        """
        if not hasattr(self, '_paginator'):
            if self.pagination_class is None:
                self._paginator = None
            else:
                self._paginator = self.pagination_class()
        return self._paginator

    def paginate_queryset(self, queryset):
        """
        Return a single page of results, or `None` if pagination is disabled.
        """
        if self.paginator is None:
            return None
        return self.paginator.paginate_queryset(queryset, self.request, view=self)

    def get_paginated_response(self, data):
        """
        Return a paginated style `Response` object for the given output data.
        """
        assert self.paginator is not None
        return self.paginator.get_paginated_response(data)

如源码描述,GenericAPIView是所有其他通用视图的基类。

这里需要设置这些属性,或重写’ get_queryset() ’ / ’ get_serializer_class() ‘。如果重写一个视图方法,调用它是很重要的’ get_queryset() ‘不直接访问’ queryset '属性,像 ’ queryset '只会被计算一次,并且这些结果会被缓存
用于所有后续请求。

GenericAPIView类继承于REST framework的APIView类,为标准列表和详细视图添加 了常见的行为。
内置的每一个具体的通用视图都是通过将GenericAPIView类和一个或多个 minxin 类相互结合 来构建的。
以下属性控制基本视图行为:
(1)queryset - 用于从此视图返回对象的查询集。通常,您必须设置此属性,或覆盖 get_queryset()方法。如果你重写了一个视图方法,在视图方法中,你应该调用 get_queryset()而不是直接访问这个属性,这一点很重要!因为 REST 会在内部对 queryset的结果进行缓存用于后续所有请求。
(2)serializer_class - 用于验证和反序列化输入以及序列化输出的序列化类。通常,您必须设 置此属性,或覆盖get_serializer_class()方法。
(3)lookup_field - 用于执行各个模型实例的对象查找的模型字段。默认为’pk’。请注意, 使用 hyperlinked API 时,如果需要使用自定义值,则需要确保 API 视图和序列化类设置 了 lookup field。 (4)lookup_url_kwarg - 用于对象查找的 URL 关键字参数。URL conf 应该包含与此值相对应 的关键字参数。如果未设置,则默认使用与 lookup_field 相同的值。

还有其他行为,详见源码。

2、CreateAPIView:

class CreateAPIView(mixins.CreateModelMixin,
                    GenericAPIView):
    """
    Concrete view for creating a model instance.
    """
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

如源码描述,CreateAPIView继承了GenericAPIView和mixins.CreateModelMixin,从这里也可以简单看得出,CreateAPIView只支持POST方法请求。其中,CreateModelMixin提供.create(request, *args, **kwargs) 方法,实现创建和保存新模型实例。如果创建了一个对象,则会返回一个201 Created响应,并将该对象的序列化表示形式作为响应 的主体。如果表示包含名为url的键,则响应的Location header将填充该值。如果为创建对象提供的请求数据无效,则将返回400 Bad Request 响应,并将错误细节作为响应的主体。

也就是说,创建过程已经由CreateModelMixin来完成。

这是一个套娃的过程,还挺有趣的哈。

除了CreateAPIView,还有其他视图,总结如下:

类视图请求方法
GenericAPIView自定义
CreateAPIViewpost
ListAPIViewget
RetrieveAPIViewget
DestroyAPIViewdelete
UpdateAPIViewput/patch
ListCreateAPIViewget/post
RetrieveUpdateAPIViewget/put/patch
RetrieveDestroyAPIViewget/delete
RetrieveUpdateDestroyAPIViewget/put/patch/delete

根据需求,选择适当视图,可以提高开发效率。

3、视图集

除此之外,还有视图集,相关文档请查阅:

http://www.cdrf.co/

使用 ViewSet 类比使用 View 类有两个主要优点:
(1)重复的逻辑可以合并成一个类。在上面的例子中,我们只需要指定一次查询集,它将在多个视图 中使用。
(2) 通过使用 routers,我们不再需要处理自己的 URL 配置。
视图集
这两者各有优缺点。使用常规视图和 URL 配置文件更加明确,并为您提供更多控制。如果想要更快速 的开发出一个应用,或者需要使大型 API 的 URL 配置始终保持一致,视图集会非常有用。

ViewSet 类继承自 APIView (这一点与GenericAPIView一致)。您可以使用任何标准属性(如 permission_classes , authentication_classes )来控制视图上的 API 策略。 ViewSet 类不提供任何 action 的实现。为了使用 ViewSet 类,必须继承该类并明确定义 action 实现。

GenericViewSet 类继承自 GenericAPIView ,并提供默认的 get_object , get_queryset 方法和其他通用视图基础行为,但默认情况下不包含任何操作。为了使用 GenericViewSet 类,必须继承该类并混合所需的 mixin 类,或明确定义操作实现。

ModelViewSet 类继承自 GenericAPIView ,并通过混合各种 mixin 类的行为来包含各种操 作的实现。 ModelViewSet 提供的操作有 .list() , .retrieve() , .create() , .update() , .partial_update() , 和 .destroy() 。

ReadOnlyModelViewSet 类也从 GenericAPIView 继承。与 ModelViewSet 一样,它也包 含各种操作的实现,但与 ModelViewSet 不同的是它只提供 “只读” 操作, .list() 和 .retrieve()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion King

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值