77REST framework also provides an HTML renderer that renders the browsable API.
88"""
99
10- import base64
1110import contextlib
1211import datetime
13- from urllib import parse
1412
1513from django import forms
1614from django .conf import settings
1715from django .core .exceptions import ImproperlyConfigured
1816from django .core .paginator import Page
1917from django .template import engines , loader
2018from django .urls import NoReverseMatch
21- from django .utils .html import mark_safe
2219from django .utils .http import parse_header_parameters
2320from django .utils .safestring import SafeString
2421
2522from rest_framework import VERSION , exceptions , serializers , status
2623from rest_framework .compat import (
27- INDENT_SEPARATORS , LONG_SEPARATORS , SHORT_SEPARATORS , coreapi , coreschema ,
28- pygments_css , yaml
24+ INDENT_SEPARATORS , LONG_SEPARATORS , SHORT_SEPARATORS , pygments_css , yaml
2925)
3026from rest_framework .exceptions import ParseError
3127from rest_framework .request import is_form_media_type , override_method
@@ -418,7 +414,7 @@ def get_content(self, renderer, data,
418414
419415 render_style = getattr (renderer , 'render_style' , 'text' )
420416 assert render_style in ['text' , 'binary' ], 'Expected .render_style ' \
421- '"text" or "binary", but got "%s"' % render_style
417+ '"text" or "binary", but got "%s"' % render_style
422418 if render_style == 'binary' :
423419 return '[%d bytes of binary content]' % len (content )
424420
@@ -487,8 +483,8 @@ def get_rendered_html_form(self, data, view, method, request):
487483 has_serializer_class = getattr (view , 'serializer_class' , None )
488484
489485 if (
490- (not has_serializer and not has_serializer_class ) or
491- not any (is_form_media_type (parser .media_type ) for parser in view .parser_classes )
486+ (not has_serializer and not has_serializer_class ) or
487+ not any (is_form_media_type (parser .media_type ) for parser in view .parser_classes )
492488 ):
493489 return
494490
@@ -837,7 +833,7 @@ def get_result_url(self, result, view):
837833 and viewset-like (has `.basename` / `.reverse_action()`).
838834 """
839835 if not hasattr (view , 'reverse_action' ) or \
840- not hasattr (view , 'lookup_field' ):
836+ not hasattr (view , 'lookup_field' ):
841837 return
842838
843839 lookup_field = view .lookup_field
@@ -850,57 +846,6 @@ def get_result_url(self, result, view):
850846 return
851847
852848
853- class DocumentationRenderer (BaseRenderer ):
854- media_type = 'text/html'
855- format = 'html'
856- charset = 'utf-8'
857- template = 'rest_framework/docs/index.html'
858- error_template = 'rest_framework/docs/error.html'
859- code_style = 'emacs'
860- languages = ['shell' , 'javascript' , 'python' ]
861-
862- def get_context (self , data , request ):
863- return {
864- 'document' : data ,
865- 'langs' : self .languages ,
866- 'lang_htmls' : ["rest_framework/docs/langs/%s.html" % language for language in self .languages ],
867- 'lang_intro_htmls' : ["rest_framework/docs/langs/%s-intro.html" % language for language in self .languages ],
868- 'code_style' : pygments_css (self .code_style ),
869- 'request' : request
870- }
871-
872- def render (self , data , accepted_media_type = None , renderer_context = None ):
873- if isinstance (data , coreapi .Document ):
874- template = loader .get_template (self .template )
875- context = self .get_context (data , renderer_context ['request' ])
876- return template .render (context , request = renderer_context ['request' ])
877- else :
878- template = loader .get_template (self .error_template )
879- context = {
880- "data" : data ,
881- "request" : renderer_context ['request' ],
882- "response" : renderer_context ['response' ],
883- "debug" : settings .DEBUG ,
884- }
885- return template .render (context , request = renderer_context ['request' ])
886-
887-
888- class SchemaJSRenderer (BaseRenderer ):
889- media_type = 'application/javascript'
890- format = 'javascript'
891- charset = 'utf-8'
892- template = 'rest_framework/schema.js'
893-
894- def render (self , data , accepted_media_type = None , renderer_context = None ):
895- codec = coreapi .codecs .CoreJSONCodec ()
896- schema = base64 .b64encode (codec .encode (data )).decode ('ascii' )
897-
898- template = loader .get_template (self .template )
899- context = {'schema' : mark_safe (schema )}
900- request = renderer_context ['request' ]
901- return template .render (context , request = request )
902-
903-
904849class MultiPartRenderer (BaseRenderer ):
905850 media_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'
906851 format = 'multipart'
@@ -921,139 +866,6 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
921866 return encode_multipart (self .BOUNDARY , data )
922867
923868
924- class CoreJSONRenderer (BaseRenderer ):
925- media_type = 'application/coreapi+json'
926- charset = None
927- format = 'corejson'
928-
929- def __init__ (self ):
930- assert coreapi , 'Using CoreJSONRenderer, but `coreapi` is not installed.'
931-
932- def render (self , data , media_type = None , renderer_context = None ):
933- indent = bool (renderer_context .get ('indent' , 0 ))
934- codec = coreapi .codecs .CoreJSONCodec ()
935- return codec .dump (data , indent = indent )
936-
937-
938- class _BaseOpenAPIRenderer :
939- def get_schema (self , instance ):
940- CLASS_TO_TYPENAME = {
941- coreschema .Object : 'object' ,
942- coreschema .Array : 'array' ,
943- coreschema .Number : 'number' ,
944- coreschema .Integer : 'integer' ,
945- coreschema .String : 'string' ,
946- coreschema .Boolean : 'boolean' ,
947- }
948-
949- schema = {}
950- if instance .__class__ in CLASS_TO_TYPENAME :
951- schema ['type' ] = CLASS_TO_TYPENAME [instance .__class__ ]
952- schema ['title' ] = instance .title
953- schema ['description' ] = instance .description
954- if hasattr (instance , 'enum' ):
955- schema ['enum' ] = instance .enum
956- return schema
957-
958- def get_parameters (self , link ):
959- parameters = []
960- for field in link .fields :
961- if field .location not in ['path' , 'query' ]:
962- continue
963- parameter = {
964- 'name' : field .name ,
965- 'in' : field .location ,
966- }
967- if field .required :
968- parameter ['required' ] = True
969- if field .description :
970- parameter ['description' ] = field .description
971- if field .schema :
972- parameter ['schema' ] = self .get_schema (field .schema )
973- parameters .append (parameter )
974- return parameters
975-
976- def get_operation (self , link , name , tag ):
977- operation_id = "%s_%s" % (tag , name ) if tag else name
978- parameters = self .get_parameters (link )
979-
980- operation = {
981- 'operationId' : operation_id ,
982- }
983- if link .title :
984- operation ['summary' ] = link .title
985- if link .description :
986- operation ['description' ] = link .description
987- if parameters :
988- operation ['parameters' ] = parameters
989- if tag :
990- operation ['tags' ] = [tag ]
991- return operation
992-
993- def get_paths (self , document ):
994- paths = {}
995-
996- tag = None
997- for name , link in document .links .items ():
998- path = parse .urlparse (link .url ).path
999- method = link .action .lower ()
1000- paths .setdefault (path , {})
1001- paths [path ][method ] = self .get_operation (link , name , tag = tag )
1002-
1003- for tag , section in document .data .items ():
1004- for name , link in section .links .items ():
1005- path = parse .urlparse (link .url ).path
1006- method = link .action .lower ()
1007- paths .setdefault (path , {})
1008- paths [path ][method ] = self .get_operation (link , name , tag = tag )
1009-
1010- return paths
1011-
1012- def get_structure (self , data ):
1013- return {
1014- 'openapi' : '3.0.0' ,
1015- 'info' : {
1016- 'version' : '' ,
1017- 'title' : data .title ,
1018- 'description' : data .description
1019- },
1020- 'servers' : [{
1021- 'url' : data .url
1022- }],
1023- 'paths' : self .get_paths (data )
1024- }
1025-
1026-
1027- class CoreAPIOpenAPIRenderer (_BaseOpenAPIRenderer ):
1028- media_type = 'application/vnd.oai.openapi'
1029- charset = None
1030- format = 'openapi'
1031-
1032- def __init__ (self ):
1033- assert coreapi , 'Using CoreAPIOpenAPIRenderer, but `coreapi` is not installed.'
1034- assert yaml , 'Using CoreAPIOpenAPIRenderer, but `pyyaml` is not installed.'
1035-
1036- def render (self , data , media_type = None , renderer_context = None ):
1037- structure = self .get_structure (data )
1038- return yaml .dump (structure , default_flow_style = False ).encode ()
1039-
1040-
1041- class CoreAPIJSONOpenAPIRenderer (_BaseOpenAPIRenderer ):
1042- media_type = 'application/vnd.oai.openapi+json'
1043- charset = None
1044- format = 'openapi-json'
1045- ensure_ascii = not api_settings .UNICODE_JSON
1046-
1047- def __init__ (self ):
1048- assert coreapi , 'Using CoreAPIJSONOpenAPIRenderer, but `coreapi` is not installed.'
1049-
1050- def render (self , data , media_type = None , renderer_context = None ):
1051- structure = self .get_structure (data )
1052- return json .dumps (
1053- structure , indent = 4 ,
1054- ensure_ascii = self .ensure_ascii ).encode ('utf-8' )
1055-
1056-
1057869class OpenAPIRenderer (BaseRenderer ):
1058870 media_type = 'application/vnd.oai.openapi'
1059871 charset = None
@@ -1067,6 +879,7 @@ def render(self, data, media_type=None, renderer_context=None):
1067879 class Dumper (yaml .Dumper ):
1068880 def ignore_aliases (self , data ):
1069881 return True
882+
1070883 Dumper .add_representer (SafeString , Dumper .represent_str )
1071884 Dumper .add_representer (datetime .timedelta , encoders .CustomScalar .represent_timedelta )
1072885 return yaml .dump (data , default_flow_style = False , sort_keys = False , Dumper = Dumper ).encode ('utf-8' )
0 commit comments