一、内置渲染器
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 retJSONRenderer
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 templateTemplateHTMLRenderer
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 retBrowsableAPIRenderer
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): returnAdminRenderer
更多请参考: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) raiseperform_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 stateResponse
而在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