• rest framework之渲染器


    一、内置渲染器

    REST框架包括许多内置的Renderer类,它们允许你使用各种媒体类型返回响应。还支持定义你自己的自定义渲染器。

    • 内置渲染器的使用

    1、全局设置

    可以使用DEFAULT_RENDERER_CLASSES设置全局默认的渲染器集。例如,以下设置将使用JSON作为主要媒体类型:

    REST_FRAMEWORK = {
        'DEFAULT_RENDERER_CLASSES': (
            'rest_framework.renderers.JSONRenderer',
            'rest_framework.renderers.BrowsableAPIRenderer',
        )
    }

    2、局部设置

    from rest_framework.pagination import PageNumberPagination
    from rest_framework.parsers import JSONParser
    from rest_framework.renderers import JSONRenderer
    
    class BookView(GenericViewSet):
        """
            该视图只接受JSON数据的post请求
        """
        queryset = models.Book.objects.all()
        serializer_class = BookModelSerializer
        parser_classes = [JSONParser,]
    
        """
        返回JSON数据格式
        """
        renderer_classes = [JSONRenderer,]
    
    
        def list(self,request):
    
            queryset= self.get_queryset()
    
            pg = PageNumberPagination()
            paginate_queryset = pg.paginate_queryset(queryset=queryset,request=request,view=self)
            #序列化分页数据
            bs=self.get_serializer(paginate_queryset,many=True)
            # return Response(bs.data)
            return pg.get_paginated_response(bs.data)
    • 内置渲染器API种类

    1、JSONRenderer

    返回的就只是单纯的JSON类型的数据格式

    class JSONRenderer(BaseRenderer):
        """
        Renderer which serializes to JSON.
        """
        media_type = 'application/json'
        format = 'json'
        encoder_class = encoders.JSONEncoder
        ensure_ascii = not api_settings.UNICODE_JSON
        compact = api_settings.COMPACT_JSON
        strict = api_settings.STRICT_JSON
    
        # We don't set a charset because JSON is a binary encoding,
        # that can be encoded as utf-8, utf-16 or utf-32.
        # See: https://www.ietf.org/rfc/rfc4627.txt
        # Also: http://lucumr.pocoo.org/2013/7/19/application-mimetypes-and-encodings/
        charset = None
    
        def get_indent(self, accepted_media_type, renderer_context):
            if accepted_media_type:
                # If the media type looks like 'application/json; indent=4',
                # then pretty print the result.
                # Note that we coerce `indent=0` into `indent=None`.
                base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
                try:
                    return zero_as_none(max(min(int(params['indent']), 8), 0))
                except (KeyError, ValueError, TypeError):
                    pass
    
            # If 'indent' is provided in the context, then pretty print the result.
            # E.g. If we're being called by the BrowsableAPIRenderer.
            return renderer_context.get('indent', None)
    
        def render(self, data, accepted_media_type=None, renderer_context=None):
            """
            Render `data` into JSON, returning a bytestring.
            """
            if data is None:
                return bytes()
    
            renderer_context = renderer_context or {}
            indent = self.get_indent(accepted_media_type, renderer_context)
    
            if indent is None:
                separators = SHORT_SEPARATORS if self.compact else LONG_SEPARATORS
            else:
                separators = INDENT_SEPARATORS
    
            ret = json.dumps(
                data, cls=self.encoder_class,
                indent=indent, ensure_ascii=self.ensure_ascii,
                allow_nan=not self.strict, separators=separators
            )
    
            # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True,
            # but if ensure_ascii=False, the return type is underspecified,
            # and may (or may not) be unicode.
            # On python 3.x json.dumps() returns unicode strings.
            if isinstance(ret, six.text_type):
                # We always fully escape u2028 and u2029 to ensure we output JSON
                # that is a strict javascript subset. If bytes were returned
                # by json.dumps() then we don't have these characters in any case.
                # See: http://timelessrepo.com/json-isnt-a-javascript-subset
                ret = ret.replace('u2028', '\u2028').replace('u2029', '\u2029')
                return bytes(ret.encode('utf-8'))
            return ret
    JSONRenderer

    2、TemplateHTMLRenderer

    使用 Django 的标准模板将数据呈现为 HTML。与其他渲染器不同,传递给 Response 的数据不需要序列化。

    class TemplateHTMLRenderer(BaseRenderer):
        """
        An HTML renderer for use with templates.
    
        The data supplied to the Response object should be a dictionary that will
        be used as context for the template.
    
        The template name is determined by (in order of preference):
    
        1. An explicit `.template_name` attribute set on the response.
        2. An explicit `.template_name` attribute set on this class.
        3. The return result of calling `view.get_template_names()`.
    
        For example:
            data = {'users': User.objects.all()}
            return Response(data, template_name='users.html')
    
        For pre-rendered HTML, see StaticHTMLRenderer.
        """
        media_type = 'text/html'
        format = 'html'
        template_name = None
        exception_template_names = [
            '%(status_code)s.html',
            'api_exception.html'
        ]
        charset = 'utf-8'
    
        def render(self, data, accepted_media_type=None, renderer_context=None):
            """
            Renders data to HTML, using Django's standard template rendering.
    
            The template name is determined by (in order of preference):
    
            1. An explicit .template_name set on the response.
            2. An explicit .template_name set on this class.
            3. The return result of calling view.get_template_names().
            """
            renderer_context = renderer_context or {}
            view = renderer_context['view']
            request = renderer_context['request']
            response = renderer_context['response']
    
            if response.exception:
                template = self.get_exception_template(response)
            else:
                template_names = self.get_template_names(response, view)
                template = self.resolve_template(template_names)
    
            if hasattr(self, 'resolve_context'):
                # Fallback for older versions.
                context = self.resolve_context(data, request, response)
            else:
                context = self.get_template_context(data, renderer_context)
            return template.render(context, request=request)
    
        def resolve_template(self, template_names):
            return loader.select_template(template_names)
    
        def get_template_context(self, data, renderer_context):
            response = renderer_context['response']
            if response.exception:
                data['status_code'] = response.status_code
            return data
    
        def get_template_names(self, response, view):
            if response.template_name:
                return [response.template_name]
            elif self.template_name:
                return [self.template_name]
            elif hasattr(view, 'get_template_names'):
                return view.get_template_names()
            elif hasattr(view, 'template_name'):
                return [view.template_name]
            raise ImproperlyConfigured(
                'Returned a template response with no `template_name` attribute set on either the view or response'
            )
    
        def get_exception_template(self, response):
            template_names = [name % {'status_code': response.status_code}
                              for name in self.exception_template_names]
    
            try:
                # Try to find an appropriate error template
                return self.resolve_template(template_names)
            except Exception:
                # Fall back to using eg '404 Not Found'
                body = '%d %s' % (response.status_code, response.status_text.title())
                template = engines['django'].from_string(body)
                return template
    TemplateHTMLRenderer
    class UserDetail(generics.RetrieveAPIView):
        """
        A view that returns a templated HTML representation of a given user.
        """
        queryset = User.objects.all()
        renderer_classes = (TemplateHTMLRenderer,)
    
        def get(self, request, *args, **kwargs):
            self.object = self.get_object()
            return Response({'user': self.object}, template_name='user_detail.html')
    实例

    详情参考:https://www.django-rest-framework.org/api-guide/renderers/#templatehtmlrenderer

    3、BrowsableAPIRenderer

    将数据呈现为可浏览的 HTML API

    class BrowsableAPIRenderer(BaseRenderer):
        """
        HTML renderer used to self-document the API.
        """
        media_type = 'text/html'
        format = 'api'
        template = 'rest_framework/api.html'
        filter_template = 'rest_framework/filters/base.html'
        code_style = 'emacs'
        charset = 'utf-8'
        form_renderer_class = HTMLFormRenderer
    
        def get_default_renderer(self, view):
            """
            Return an instance of the first valid renderer.
            (Don't use another documenting renderer.)
            """
            renderers = [renderer for renderer in view.renderer_classes
                         if not issubclass(renderer, BrowsableAPIRenderer)]
            non_template_renderers = [renderer for renderer in renderers
                                      if not hasattr(renderer, 'get_template_names')]
    
            if not renderers:
                return None
            elif non_template_renderers:
                return non_template_renderers[0]()
            return renderers[0]()
    
        def get_content(self, renderer, data,
                        accepted_media_type, renderer_context):
            """
            Get the content as if it had been rendered by the default
            non-documenting renderer.
            """
            if not renderer:
                return '[No renderers were found]'
    
            renderer_context['indent'] = 4
            content = renderer.render(data, accepted_media_type, renderer_context)
    
            render_style = getattr(renderer, 'render_style', 'text')
            assert render_style in ['text', 'binary'], 'Expected .render_style ' 
                '"text" or "binary", but got "%s"' % render_style
            if render_style == 'binary':
                return '[%d bytes of binary content]' % len(content)
    
            return content
    
        def show_form_for_method(self, view, method, request, obj):
            """
            Returns True if a form should be shown for this method.
            """
            if method not in view.allowed_methods:
                return  # Not a valid method
    
            try:
                view.check_permissions(request)
                if obj is not None:
                    view.check_object_permissions(request, obj)
            except exceptions.APIException:
                return False  # Doesn't have permissions
            return True
    
        def _get_serializer(self, serializer_class, view_instance, request, *args, **kwargs):
            kwargs['context'] = {
                'request': request,
                'format': self.format,
                'view': view_instance
            }
            return serializer_class(*args, **kwargs)
    
        def get_rendered_html_form(self, data, view, method, request):
            """
            Return a string representing a rendered HTML form, possibly bound to
            either the input or output data.
    
            In the absence of the View having an associated form then return None.
            """
            # See issue #2089 for refactoring this.
            serializer = getattr(data, 'serializer', None)
            if serializer and not getattr(serializer, 'many', False):
                instance = getattr(serializer, 'instance', None)
                if isinstance(instance, Page):
                    instance = None
            else:
                instance = None
    
            # If this is valid serializer data, and the form is for the same
            # HTTP method as was used in the request then use the existing
            # serializer instance, rather than dynamically creating a new one.
            if request.method == method and serializer is not None:
                try:
                    kwargs = {'data': request.data}
                except ParseError:
                    kwargs = {}
                existing_serializer = serializer
            else:
                kwargs = {}
                existing_serializer = None
    
            with override_method(view, request, method) as request:
                if not self.show_form_for_method(view, method, request, instance):
                    return
    
                if method in ('DELETE', 'OPTIONS'):
                    return True  # Don't actually need to return a form
    
                has_serializer = getattr(view, 'get_serializer', None)
                has_serializer_class = getattr(view, 'serializer_class', None)
    
                if (
                    (not has_serializer and not has_serializer_class) or
                    not any(is_form_media_type(parser.media_type) for parser in view.parser_classes)
                ):
                    return
    
                if existing_serializer is not None:
                    try:
                        return self.render_form_for_serializer(existing_serializer)
                    except TypeError:
                        pass
    
                if has_serializer:
                    if method in ('PUT', 'PATCH'):
                        serializer = view.get_serializer(instance=instance, **kwargs)
                    else:
                        serializer = view.get_serializer(**kwargs)
                else:
                    # at this point we must have a serializer_class
                    if method in ('PUT', 'PATCH'):
                        serializer = self._get_serializer(view.serializer_class, view,
                                                          request, instance=instance, **kwargs)
                    else:
                        serializer = self._get_serializer(view.serializer_class, view,
                                                          request, **kwargs)
    
                return self.render_form_for_serializer(serializer)
    
        def render_form_for_serializer(self, serializer):
            if hasattr(serializer, 'initial_data'):
                serializer.is_valid()
    
            form_renderer = self.form_renderer_class()
            return form_renderer.render(
                serializer.data,
                self.accepted_media_type,
                {'style': {'template_pack': 'rest_framework/horizontal'}}
            )
    
        def get_raw_data_form(self, data, view, method, request):
            """
            Returns a form that allows for arbitrary content types to be tunneled
            via standard HTML forms.
            (Which are typically application/x-www-form-urlencoded)
            """
            # See issue #2089 for refactoring this.
            serializer = getattr(data, 'serializer', None)
            if serializer and not getattr(serializer, 'many', False):
                instance = getattr(serializer, 'instance', None)
                if isinstance(instance, Page):
                    instance = None
            else:
                instance = None
    
            with override_method(view, request, method) as request:
                # Check permissions
                if not self.show_form_for_method(view, method, request, instance):
                    return
    
                # If possible, serialize the initial content for the generic form
                default_parser = view.parser_classes[0]
                renderer_class = getattr(default_parser, 'renderer_class', None)
                if hasattr(view, 'get_serializer') and renderer_class:
                    # View has a serializer defined and parser class has a
                    # corresponding renderer that can be used to render the data.
    
                    if method in ('PUT', 'PATCH'):
                        serializer = view.get_serializer(instance=instance)
                    else:
                        serializer = view.get_serializer()
    
                    # Render the raw data content
                    renderer = renderer_class()
                    accepted = self.accepted_media_type
                    context = self.renderer_context.copy()
                    context['indent'] = 4
    
                    # strip HiddenField from output
                    data = serializer.data.copy()
                    for name, field in serializer.fields.items():
                        if isinstance(field, serializers.HiddenField):
                            data.pop(name, None)
                    content = renderer.render(data, accepted, context)
                    # Renders returns bytes, but CharField expects a str.
                    content = content.decode('utf-8')
                else:
                    content = None
    
                # Generate a generic form that includes a content type field,
                # and a content field.
                media_types = [parser.media_type for parser in view.parser_classes]
                choices = [(media_type, media_type) for media_type in media_types]
                initial = media_types[0]
    
                class GenericContentForm(forms.Form):
                    _content_type = forms.ChoiceField(
                        label='Media type',
                        choices=choices,
                        initial=initial,
                        widget=forms.Select(attrs={'data-override': 'content-type'})
                    )
                    _content = forms.CharField(
                        label='Content',
                        widget=forms.Textarea(attrs={'data-override': 'content'}),
                        initial=content,
                        required=False
                    )
    
                return GenericContentForm()
    
        def get_name(self, view):
            return view.get_view_name()
    
        def get_description(self, view, status_code):
            if status_code in (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN):
                return ''
            return view.get_view_description(html=True)
    
        def get_breadcrumbs(self, request):
            return get_breadcrumbs(request.path, request)
    
        def get_extra_actions(self, view):
            if hasattr(view, 'get_extra_action_url_map'):
                return view.get_extra_action_url_map()
            return None
    
        def get_filter_form(self, data, view, request):
            if not hasattr(view, 'get_queryset') or not hasattr(view, 'filter_backends'):
                return
    
            # Infer if this is a list view or not.
            paginator = getattr(view, 'paginator', None)
            if isinstance(data, list):
                pass
            elif paginator is not None and data is not None:
                try:
                    paginator.get_results(data)
                except (TypeError, KeyError):
                    return
            elif not isinstance(data, list):
                return
    
            queryset = view.get_queryset()
            elements = []
            for backend in view.filter_backends:
                if hasattr(backend, 'to_html'):
                    html = backend().to_html(request, queryset, view)
                    if html:
                        elements.append(html)
    
            if not elements:
                return
    
            template = loader.get_template(self.filter_template)
            context = {'elements': elements}
            return template.render(context)
    
        def get_context(self, data, accepted_media_type, renderer_context):
            """
            Returns the context used to render.
            """
            view = renderer_context['view']
            request = renderer_context['request']
            response = renderer_context['response']
    
            renderer = self.get_default_renderer(view)
    
            raw_data_post_form = self.get_raw_data_form(data, view, 'POST', request)
            raw_data_put_form = self.get_raw_data_form(data, view, 'PUT', request)
            raw_data_patch_form = self.get_raw_data_form(data, view, 'PATCH', request)
            raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form
    
            response_headers = OrderedDict(sorted(response.items()))
            renderer_content_type = ''
            if renderer:
                renderer_content_type = '%s' % renderer.media_type
                if renderer.charset:
                    renderer_content_type += ' ;%s' % renderer.charset
            response_headers['Content-Type'] = renderer_content_type
    
            if getattr(view, 'paginator', None) and view.paginator.display_page_controls:
                paginator = view.paginator
            else:
                paginator = None
    
            csrf_cookie_name = settings.CSRF_COOKIE_NAME
            csrf_header_name = settings.CSRF_HEADER_NAME
            if csrf_header_name.startswith('HTTP_'):
                csrf_header_name = csrf_header_name[5:]
            csrf_header_name = csrf_header_name.replace('_', '-')
    
            context = {
                'content': self.get_content(renderer, data, accepted_media_type, renderer_context),
                'code_style': pygments_css(self.code_style),
                'view': view,
                'request': request,
                'response': response,
                'user': request.user,
                'description': self.get_description(view, response.status_code),
                'name': self.get_name(view),
                'version': VERSION,
                'paginator': paginator,
                'breadcrumblist': self.get_breadcrumbs(request),
                'allowed_methods': view.allowed_methods,
                'available_formats': [renderer_cls.format for renderer_cls in view.renderer_classes],
                'response_headers': response_headers,
    
                'put_form': self.get_rendered_html_form(data, view, 'PUT', request),
                'post_form': self.get_rendered_html_form(data, view, 'POST', request),
                'delete_form': self.get_rendered_html_form(data, view, 'DELETE', request),
                'options_form': self.get_rendered_html_form(data, view, 'OPTIONS', request),
    
                'extra_actions': self.get_extra_actions(view),
    
                'filter_form': self.get_filter_form(data, view, request),
    
                'raw_data_put_form': raw_data_put_form,
                'raw_data_post_form': raw_data_post_form,
                'raw_data_patch_form': raw_data_patch_form,
                'raw_data_put_or_patch_form': raw_data_put_or_patch_form,
    
                'display_edit_forms': bool(response.status_code != 403),
    
                'api_settings': api_settings,
                'csrf_cookie_name': csrf_cookie_name,
                'csrf_header_name': csrf_header_name
            }
            return context
    
        def render(self, data, accepted_media_type=None, renderer_context=None):
            """
            Render the HTML for the browsable API representation.
            """
            self.accepted_media_type = accepted_media_type or ''
            self.renderer_context = renderer_context or {}
    
            template = loader.get_template(self.template)
            context = self.get_context(data, accepted_media_type, renderer_context)
            ret = template.render(context, request=renderer_context['request'])
    
            # Munge DELETE Response code to allow us to return content
            # (Do this *after* we've rendered the template so that we include
            # the normal deletion response code in the output)
            response = renderer_context['response']
            if response.status_code == status.HTTP_204_NO_CONTENT:
                response.status_code = status.HTTP_200_OK
    
            return ret
    BrowsableAPIRenderer

    4、AdminRenderer

    将数据呈现为 HTML,以显示类似管理员的内容,该渲染器适用于 CRUD 风格的 Web API,该 API 还应提供用于管理数据的用户友好界面。

    class AdminRenderer(BrowsableAPIRenderer):
        template = 'rest_framework/admin.html'
        format = 'admin'
    
        def render(self, data, accepted_media_type=None, renderer_context=None):
            self.accepted_media_type = accepted_media_type or ''
            self.renderer_context = renderer_context or {}
    
            response = renderer_context['response']
            request = renderer_context['request']
            view = self.renderer_context['view']
    
            if response.status_code == status.HTTP_400_BAD_REQUEST:
                # Errors still need to display the list or detail information.
                # The only way we can get at that is to simulate a GET request.
                self.error_form = self.get_rendered_html_form(data, view, request.method, request)
                self.error_title = {'POST': 'Create', 'PUT': 'Edit'}.get(request.method, 'Errors')
    
                with override_method(view, request, 'GET') as request:
                    response = view.get(request, *view.args, **view.kwargs)
                data = response.data
    
            template = loader.get_template(self.template)
            context = self.get_context(data, accepted_media_type, renderer_context)
            ret = template.render(context, request=renderer_context['request'])
    
            # Creation and deletion should use redirects in the admin style.
            if response.status_code == status.HTTP_201_CREATED and 'Location' in response:
                response.status_code = status.HTTP_303_SEE_OTHER
                response['Location'] = request.build_absolute_uri()
                ret = ''
    
            if response.status_code == status.HTTP_204_NO_CONTENT:
                response.status_code = status.HTTP_303_SEE_OTHER
                try:
                    # Attempt to get the parent breadcrumb URL.
                    response['Location'] = self.get_breadcrumbs(request)[-2][1]
                except KeyError:
                    # Otherwise reload current URL to get a 'Not Found' page.
                    response['Location'] = request.full_path
                ret = ''
    
            return ret
    
        def get_context(self, data, accepted_media_type, renderer_context):
            """
            Render the HTML for the browsable API representation.
            """
            context = super(AdminRenderer, self).get_context(
                data, accepted_media_type, renderer_context
            )
    
            paginator = getattr(context['view'], 'paginator', None)
            if paginator is not None and data is not None:
                try:
                    results = paginator.get_results(data)
                except (TypeError, KeyError):
                    results = data
            else:
                results = data
    
            if results is None:
                header = {}
                style = 'detail'
            elif isinstance(results, list):
                header = results[0] if results else {}
                style = 'list'
            else:
                header = results
                style = 'detail'
    
            columns = [key for key in header if key != 'url']
            details = [key for key in header if key != 'url']
    
            if isinstance(results, list) and 'view' in renderer_context:
                for result in results:
                    url = self.get_result_url(result, context['view'])
                    if url is not None:
                        result.setdefault('url', url)
    
            context['style'] = style
            context['columns'] = columns
            context['details'] = details
            context['results'] = results
            context['error_form'] = getattr(self, 'error_form', None)
            context['error_title'] = getattr(self, 'error_title', None)
            return context
    
        def get_result_url(self, result, view):
            """
            Attempt to reverse the result's detail view URL.
    
            This only works with views that are generic-like (has `.lookup_field`)
            and viewset-like (has `.basename` / `.reverse_action()`).
            """
            if not hasattr(view, 'reverse_action') or 
               not hasattr(view, 'lookup_field'):
                return
    
            lookup_field = view.lookup_field
            lookup_url_kwarg = getattr(view, 'lookup_url_kwarg', None) or lookup_field
    
            try:
                kwargs = {lookup_url_kwarg: result[lookup_field]}
                return view.reverse_action('detail', kwargs=kwargs)
            except (KeyError, NoReverseMatch):
                return
    AdminRenderer

    更多请参考:https://www.django-rest-framework.org/api-guide/renderers/#api-reference

    二、源码

    首先,请求进来后还是会走到APIView的dispatch方法,这在之前的认证权限中已经详细说明:

    1、dispatch

        def dispatch(self, request, *args, **kwargs):
            """
            `.dispatch()` is pretty much the same as Django's regular dispatch,
            but with extra hooks for startup, finalize, and exception handling.
            """
            self.args = args
            self.kwargs = kwargs
            #rest-framework重构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
                #这里和CBV一样进行方法的分发
                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

    2、initial

        def initial(self, request, *args, **kwargs):
            """
            Runs anything that needs to occur prior to calling the method handler.
            """
            self.format_kwarg = self.get_format_suffix(**kwargs)
    
            # Perform content negotiation and store the accepted info on the request
            neg = self.perform_content_negotiation(request)
            #与渲染器相关
            request.accepted_renderer, request.accepted_media_type = neg
    
            # Determine the API version, if versioning is in use.
            version, scheme = self.determine_version(request, *args, **kwargs)
            request.version, request.versioning_scheme = version, scheme
    
            # Ensure that the incoming request is permitted
            self.perform_authentication(request) #进行认证
            self.check_permissions(request)
            self.check_throttles(request)

    3、perform_content_negotiation

    在这个方法中获取视图中配置的渲染器,并且通过select_renderer返回合适的渲染器

        def perform_content_negotiation(self, request, force=False):
            """
            Determine which renderer and media type to use render the response.
            """
            renderers = self.get_renderers()
            conneg = self.get_content_negotiator()
    
            try:
                return conneg.select_renderer(request, renderers, self.format_kwarg)
            except Exception:
                if force:
                    return (renderers[0], renderers[0].media_type)
                raise
    perform_content_negotiation
        def select_renderer(self, request, renderers, format_suffix=None):
            """
            Given a request and a list of renderers, return a two-tuple of:
            (renderer, media type).
            """
            # Allow URL style format override.  eg. "?format=json
            format_query_param = self.settings.URL_FORMAT_OVERRIDE
            format = format_suffix or request.query_params.get(format_query_param)
    
            if format:
                renderers = self.filter_renderers(renderers, format)
    
            accepts = self.get_accept_list(request)
    
            # Check the acceptable media types against each renderer,
            # attempting more specific media types first
            # NB. The inner loop here isn't as bad as it first looks :)
            #     Worst case is we're looping over len(accept_list) * len(self.renderers)
            for media_type_set in order_by_precedence(accepts):
                for renderer in renderers:
                    for media_type in media_type_set:
                        if media_type_matches(renderer.media_type, media_type):
                            # Return the most specific media type as accepted.
                            media_type_wrapper = _MediaType(media_type)
                            if (
                                _MediaType(renderer.media_type).precedence >
                                media_type_wrapper.precedence
                            ):
                                # Eg client requests '*/*'
                                # Accepted media type is 'application/json'
                                full_media_type = ';'.join(
                                    (renderer.media_type,) +
                                    tuple('{0}={1}'.format(
                                        key, value.decode(HTTP_HEADER_ENCODING))
                                        for key, value in media_type_wrapper.params.items()))
                                return renderer, full_media_type
                            else:
                                # Eg client requests 'application/json; indent=8'
                                # Accepted media type is 'application/json; indent=8'
                                return renderer, media_type
    
            raise exceptions.NotAcceptable(available_renderers=renderers)
    select_renderer

    4、将渲染器赋值给request对象

    在initial方法中将获取的渲染器赋值给request对象

          # Perform content negotiation and store the accepted info on the request
            neg = self.perform_content_negotiation(request)
            #与渲染器相关
            request.accepted_renderer, request.accepted_media_type = neg

    5、调用Response

    在给客户端返回数据过程中使用Response对象

    class Response(SimpleTemplateResponse):
        """
        An HttpResponse that allows its data to be rendered into
        arbitrary media types.
        """
    
        def __init__(self, data=None, status=None,
                     template_name=None, headers=None,
                     exception=False, content_type=None):
            """
            Alters the init arguments slightly.
            For example, drop 'template_name', and instead use 'data'.
    
            Setting 'renderer' and 'media_type' will typically be deferred,
            For example being set automatically by the `APIView`.
            """
            super(Response, self).__init__(None, status=status)
    
            if isinstance(data, Serializer):
                msg = (
                    'You passed a Serializer instance as data, but '
                    'probably meant to pass serialized `.data` or '
                    '`.error`. representation.'
                )
                raise AssertionError(msg)
    
            self.data = data
            self.template_name = template_name
            self.exception = exception
            self.content_type = content_type
    
            if headers:
                for name, value in six.iteritems(headers):
                    self[name] = value
    
        @property
        def rendered_content(self):
            renderer = getattr(self, 'accepted_renderer', None)
            accepted_media_type = getattr(self, 'accepted_media_type', None)
            context = getattr(self, 'renderer_context', None)
    
            assert renderer, ".accepted_renderer not set on Response"
            assert accepted_media_type, ".accepted_media_type not set on Response"
            assert context is not None, ".renderer_context not set on Response"
            context['response'] = self
    
            media_type = renderer.media_type
            charset = renderer.charset
            content_type = self.content_type
    
            if content_type is None and charset is not None:
                content_type = "{0}; charset={1}".format(media_type, charset)
            elif content_type is None:
                content_type = media_type
            self['Content-Type'] = content_type
    
            ret = renderer.render(self.data, accepted_media_type, context)
            if isinstance(ret, six.text_type):
                assert charset, (
                    'renderer returned unicode, and did not specify '
                    'a charset value.'
                )
                return bytes(ret.encode(charset))
    
            if not ret:
                del self['Content-Type']
    
            return ret
    
        @property
        def status_text(self):
            """
            Returns reason text corresponding to our HTTP response status code.
            Provided for convenience.
            """
            return responses.get(self.status_code, '')
    
        def __getstate__(self):
            """
            Remove attributes from the response that shouldn't be cached.
            """
            state = super(Response, self).__getstate__()
            for key in (
                'accepted_renderer', 'renderer_context', 'resolver_match',
                'client', 'request', 'json', 'wsgi_request'
            ):
                if key in state:
                    del state[key]
            state['_closable_objects'] = []
            return state
    Response

    而在Response中使用了赋值给request对象的渲染器,通过反射取到request中的渲染器:

     renderer = getattr(self, 'accepted_renderer', None)

    然后调用渲染器中的render方法:

    ret = renderer.render(self.data, accepted_media_type, context)

    假设使用的是JSONRender渲染器:

     def render(self, data, accepted_media_type=None, renderer_context=None):
            """
            Render `data` into JSON, returning a bytestring.
            """
            if data is None:
                return bytes()
    
            renderer_context = renderer_context or {}
            indent = self.get_indent(accepted_media_type, renderer_context)
    
            if indent is None:
                separators = SHORT_SEPARATORS if self.compact else LONG_SEPARATORS
            else:
                separators = INDENT_SEPARATORS
    
            ret = json.dumps(
                data, cls=self.encoder_class,
                indent=indent, ensure_ascii=self.ensure_ascii,
                allow_nan=not self.strict, separators=separators
            )
    
            # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True,
            # but if ensure_ascii=False, the return type is underspecified,
            # and may (or may not) be unicode.
            # On python 3.x json.dumps() returns unicode strings.
            if isinstance(ret, six.text_type):
                # We always fully escape u2028 and u2029 to ensure we output JSON
                # that is a strict javascript subset. If bytes were returned
                # by json.dumps() then we don't have these characters in any case.
                # See: http://timelessrepo.com/json-isnt-a-javascript-subset
                ret = ret.replace('u2028', '\u2028').replace('u2029', '\u2029')
                return bytes(ret.encode('utf-8'))
            return ret

    通过json.dumps进行序列化返回JSON类型的数据结果。

    总结:

    • 在dispatch方法中使用initial方法初始化渲染器,并且将其赋值给request对象
    • 在返回结果使用Response时,调用request中被赋值的渲染器对象,使用其render方法进行处理,最后返回结果

    参考文档:https://q1mi.github.io/Django-REST-framework-documentation/api-guide/renderers_zh/#_1

  • 相关阅读:
    为什么不使用CSS expression?
    关于ol有序列表的小事儿...
    绝对定位的元素在IE6下莫名丢失解决办法
    C#操作XML
    .NET MSChart应用的一个简单例子 (转)
    微软图表控件MsChart使用初探(转)
    使用OleDbParameter来写Access的更新没反应的解决办法
    获取真实IP
    XML操作类转
    Model与XML互相转换
  • 原文地址:https://www.cnblogs.com/shenjianping/p/11509860.html
Copyright © 2020-2023  润新知