From e6662d434f0214d21d38e4388a40fd63e1f9dcc6 Mon Sep 17 00:00:00 2001 From: Christopher Paolini Date: Sat, 17 Aug 2013 17:44:51 -0400 Subject: Improved view/description function setting Now supports each View having its own name and description function and overriding the global default. --- rest_framework/renderers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 3a03ca33..1006e26c 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -24,7 +24,6 @@ from rest_framework.settings import api_settings from rest_framework.request import clone_request from rest_framework.utils import encoders from rest_framework.utils.breadcrumbs import get_breadcrumbs -from rest_framework.utils.formatting import get_view_name, get_view_description from rest_framework import exceptions, parsers, status, VERSION @@ -498,10 +497,10 @@ class BrowsableAPIRenderer(BaseRenderer): return GenericContentForm() def get_name(self, view): - return get_view_name(view.__class__, getattr(view, 'suffix', None)) + return view.get_view_name() def get_description(self, view): - return get_view_description(view.__class__, html=True) + return view.get_view_description(html=True) def get_breadcrumbs(self, request): return get_breadcrumbs(request.path) -- cgit v1.2.3 From 0966a2680ba02e6a4586bd2777ed593fcc66a453 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Aug 2013 14:38:31 +0100 Subject: First pass at HTMLFormRenderer --- rest_framework/renderers.py | 97 +++++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 43 deletions(-) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 1006e26c..a73b2d73 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -316,6 +316,59 @@ class StaticHTMLRenderer(TemplateHTMLRenderer): return data +class HTMLFormRenderer(BaseRenderer): + template = 'rest_framework/form.html' + + def serializer_to_form_fields(self, serializer): + fields = {} + for k, v in serializer.get_fields().items(): + if getattr(v, 'read_only', True): + continue + + kwargs = {} + kwargs['required'] = v.required + + #if getattr(v, 'queryset', None): + # kwargs['queryset'] = v.queryset + + if getattr(v, 'choices', None) is not None: + kwargs['choices'] = v.choices + + if getattr(v, 'regex', None) is not None: + kwargs['regex'] = v.regex + + if getattr(v, 'widget', None): + widget = copy.deepcopy(v.widget) + kwargs['widget'] = widget + + if getattr(v, 'default', None) is not None: + kwargs['initial'] = v.default + + if getattr(v, 'label', None) is not None: + kwargs['label'] = v.label + + if getattr(v, 'help_text', None) is not None: + kwargs['help_text'] = v.help_text + + fields[k] = v.form_field_class(**kwargs) + + return fields + + def render(self, serializer, obj, request): + fields = self.serializer_to_form_fields(serializer) + + # Creating an on the fly form see: + # http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python + OnTheFlyForm = type(str("OnTheFlyForm"), (forms.Form,), fields) + data = (obj is not None) and serializer.data or None + form_instance = OnTheFlyForm(data) + + template = loader.get_template(self.template) + context = RequestContext(request, {'form': form_instance}) + + return template.render(context) + + class BrowsableAPIRenderer(BaseRenderer): """ HTML renderer used to self-document the API. @@ -371,41 +424,6 @@ class BrowsableAPIRenderer(BaseRenderer): return False # Doesn't have permissions return True - def serializer_to_form_fields(self, serializer): - fields = {} - for k, v in serializer.get_fields().items(): - if getattr(v, 'read_only', True): - continue - - kwargs = {} - kwargs['required'] = v.required - - #if getattr(v, 'queryset', None): - # kwargs['queryset'] = v.queryset - - if getattr(v, 'choices', None) is not None: - kwargs['choices'] = v.choices - - if getattr(v, 'regex', None) is not None: - kwargs['regex'] = v.regex - - if getattr(v, 'widget', None): - widget = copy.deepcopy(v.widget) - kwargs['widget'] = widget - - if getattr(v, 'default', None) is not None: - kwargs['initial'] = v.default - - if getattr(v, 'label', None) is not None: - kwargs['label'] = v.label - - if getattr(v, 'help_text', None) is not None: - kwargs['help_text'] = v.help_text - - fields[k] = v.form_field_class(**kwargs) - - return fields - def _get_form(self, view, method, request): # We need to impersonate a request with the correct method, # so that eg. any dynamic get_serializer_class methods return the @@ -447,14 +465,7 @@ class BrowsableAPIRenderer(BaseRenderer): return serializer = view.get_serializer(instance=obj) - fields = self.serializer_to_form_fields(serializer) - - # Creating an on the fly form see: - # http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python - OnTheFlyForm = type(str("OnTheFlyForm"), (forms.Form,), fields) - data = (obj is not None) and serializer.data or None - form_instance = OnTheFlyForm(data) - return form_instance + return HTMLFormRenderer().render(serializer, obj, request) def get_raw_data_form(self, view, method, request, media_types): """ -- cgit v1.2.3 From 10d386ec6a4822402b5ffea46bdd9e7d72db519b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Aug 2013 16:10:20 +0100 Subject: Cleanup and dealing with empty form data. --- rest_framework/renderers.py | 103 +++++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 49 deletions(-) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index a73b2d73..a8670546 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -319,52 +319,53 @@ class StaticHTMLRenderer(TemplateHTMLRenderer): class HTMLFormRenderer(BaseRenderer): template = 'rest_framework/form.html' - def serializer_to_form_fields(self, serializer): + def data_to_form_fields(self, data): fields = {} - for k, v in serializer.get_fields().items(): - if getattr(v, 'read_only', True): + for key, val in data.fields.items(): + if getattr(val, 'read_only', True): continue kwargs = {} - kwargs['required'] = v.required + kwargs['required'] = val.required #if getattr(v, 'queryset', None): # kwargs['queryset'] = v.queryset - if getattr(v, 'choices', None) is not None: - kwargs['choices'] = v.choices + if getattr(val, 'choices', None) is not None: + kwargs['choices'] = val.choices - if getattr(v, 'regex', None) is not None: - kwargs['regex'] = v.regex + if getattr(val, 'regex', None) is not None: + kwargs['regex'] = val.regex - if getattr(v, 'widget', None): - widget = copy.deepcopy(v.widget) + if getattr(val, 'widget', None): + widget = copy.deepcopy(val.widget) kwargs['widget'] = widget - if getattr(v, 'default', None) is not None: - kwargs['initial'] = v.default + if getattr(val, 'default', None) is not None: + kwargs['initial'] = val.default - if getattr(v, 'label', None) is not None: - kwargs['label'] = v.label + if getattr(val, 'label', None) is not None: + kwargs['label'] = val.label - if getattr(v, 'help_text', None) is not None: - kwargs['help_text'] = v.help_text + if getattr(val, 'help_text', None) is not None: + kwargs['help_text'] = val.help_text - fields[k] = v.form_field_class(**kwargs) + fields[key] = val.form_field_class(**kwargs) return fields - def render(self, serializer, obj, request): - fields = self.serializer_to_form_fields(serializer) + def render(self, data, accepted_media_type=None, renderer_context=None): + self.renderer_context = renderer_context or {} + request = renderer_context['request'] # Creating an on the fly form see: # http://stackoverflow.com/questions/3915024/dynamically-creating-classes-python - OnTheFlyForm = type(str("OnTheFlyForm"), (forms.Form,), fields) - data = (obj is not None) and serializer.data or None - form_instance = OnTheFlyForm(data) + fields = self.data_to_form_fields(data) + DynamicForm = type(str('DynamicForm'), (forms.Form,), fields) + data = None if data.empty else data template = loader.get_template(self.template) - context = RequestContext(request, {'form': form_instance}) + context = RequestContext(request, {'form': DynamicForm(data)}) return template.render(context) @@ -377,6 +378,7 @@ class BrowsableAPIRenderer(BaseRenderer): format = 'api' template = 'rest_framework/api.html' charset = 'utf-8' + form_renderer_class = HTMLFormRenderer def get_default_renderer(self, view): """ @@ -424,19 +426,7 @@ class BrowsableAPIRenderer(BaseRenderer): return False # Doesn't have permissions return True - def _get_form(self, view, method, request): - # We need to impersonate a request with the correct method, - # so that eg. any dynamic get_serializer_class methods return the - # correct form for each method. - restore = view.request - request = clone_request(request, method) - view.request = request - try: - return self.get_form(view, method, request) - finally: - view.request = restore - - def _get_raw_data_form(self, view, method, request, media_types): + def _get_rendered_html_form(self, view, method, request): # We need to impersonate a request with the correct method, # so that eg. any dynamic get_serializer_class methods return the # correct form for each method. @@ -444,15 +434,16 @@ class BrowsableAPIRenderer(BaseRenderer): request = clone_request(request, method) view.request = request try: - return self.get_raw_data_form(view, method, request, media_types) + return self.get_rendered_html_form(view, method, request) finally: view.request = restore - def get_form(self, view, method, request): + def get_rendered_html_form(self, view, method, request): """ - Get a form, possibly bound to either the input or output data. - In the absence on of the Resource having an associated form then - provide a form that can be used to submit arbitrary content. + 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. """ obj = getattr(view, 'object', None) if not self.show_form_for_method(view, method, request, obj): @@ -465,7 +456,21 @@ class BrowsableAPIRenderer(BaseRenderer): return serializer = view.get_serializer(instance=obj) - return HTMLFormRenderer().render(serializer, obj, request) + data = serializer.data + form_renderer = self.form_renderer_class() + return form_renderer.render(data, self.accepted_media_type, self.renderer_context) + + def _get_raw_data_form(self, view, method, request, media_types): + # We need to impersonate a request with the correct method, + # so that eg. any dynamic get_serializer_class methods return the + # correct form for each method. + restore = view.request + request = clone_request(request, method) + view.request = request + try: + return self.get_raw_data_form(view, method, request, media_types) + finally: + view.request = restore def get_raw_data_form(self, view, method, request, media_types): """ @@ -520,8 +525,8 @@ class BrowsableAPIRenderer(BaseRenderer): """ Render the HTML for the browsable API representation. """ - accepted_media_type = accepted_media_type or '' - renderer_context = renderer_context or {} + self.accepted_media_type = accepted_media_type or '' + self.renderer_context = renderer_context or {} view = renderer_context['view'] request = renderer_context['request'] @@ -531,11 +536,11 @@ class BrowsableAPIRenderer(BaseRenderer): renderer = self.get_default_renderer(view) content = self.get_content(renderer, data, accepted_media_type, renderer_context) - put_form = self._get_form(view, 'PUT', request) - post_form = self._get_form(view, 'POST', request) - patch_form = self._get_form(view, 'PATCH', request) - delete_form = self._get_form(view, 'DELETE', request) - options_form = self._get_form(view, 'OPTIONS', request) + put_form = self._get_rendered_html_form(view, 'PUT', request) + post_form = self._get_rendered_html_form(view, 'POST', request) + patch_form = self._get_rendered_html_form(view, 'PATCH', request) + delete_form = self._get_rendered_html_form(view, 'DELETE', request) + options_form = self._get_rendered_html_form(view, 'OPTIONS', request) raw_data_put_form = self._get_raw_data_form(view, 'PUT', request, media_types) raw_data_post_form = self._get_raw_data_form(view, 'POST', request, media_types) -- cgit v1.2.3 From e23d5888522f98c30418452c0f833cf11589e0c1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Aug 2013 16:16:41 +0100 Subject: Adding standard renderer attributes and documenting --- rest_framework/renderers.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index a8670546..9885c8dd 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -317,7 +317,18 @@ class StaticHTMLRenderer(TemplateHTMLRenderer): class HTMLFormRenderer(BaseRenderer): + """ + Renderers serializer data into an HTML form. + + If the serializer was instantiated without an object then this will + return an HTML form not bound to any object, + otherwise it will return an HTML form with the appropriate initial data + populated from the object. + """ + media_type = 'text/html' + format = 'form' template = 'rest_framework/form.html' + charset = 'utf-8' def data_to_form_fields(self, data): fields = {} -- cgit v1.2.3 From 436e66a42db21b52fd5e1582011d2f0f7f81f9c7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Aug 2013 16:45:55 +0100 Subject: JSON responses should not include a charset --- rest_framework/renderers.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 1006e26c..c87014e2 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -36,6 +36,7 @@ class BaseRenderer(object): media_type = None format = None charset = 'utf-8' + render_style = 'text' def render(self, data, accepted_media_type=None, renderer_context=None): raise NotImplemented('Renderer class requires .render() to be implemented') @@ -51,16 +52,17 @@ class JSONRenderer(BaseRenderer): format = 'json' encoder_class = encoders.JSONEncoder ensure_ascii = True - charset = 'utf-8' - # Note that JSON encodings must be utf-8, utf-16 or utf-32. + charset = None + # JSON is a binary encoding, that can be encoded as utf-8, utf-16 or utf-32. # See: http://www.ietf.org/rfc/rfc4627.txt + # Also: http://lucumr.pocoo.org/2013/7/19/application-mimetypes-and-encodings/ def render(self, data, accepted_media_type=None, renderer_context=None): """ Render `data` into JSON. """ if data is None: - return '' + return bytes() # If 'indent' is provided in the context, then pretty print the result. # E.g. If we're being called by the BrowsableAPIRenderer. @@ -85,13 +87,12 @@ class JSONRenderer(BaseRenderer): # and may (or may not) be unicode. # On python 3.x json.dumps() returns unicode strings. if isinstance(ret, six.text_type): - return bytes(ret.encode(self.charset)) + return bytes(ret.encode('utf-8')) return ret class UnicodeJSONRenderer(JSONRenderer): ensure_ascii = False - charset = 'utf-8' """ Renderer which serializes to JSON. Does *not* apply JSON's character escaping for non-ascii characters. @@ -108,6 +109,7 @@ class JSONPRenderer(JSONRenderer): format = 'jsonp' callback_parameter = 'callback' default_callback = 'callback' + charset = 'utf-8' def get_callback(self, renderer_context): """ @@ -348,7 +350,10 @@ class BrowsableAPIRenderer(BaseRenderer): renderer_context['indent'] = 4 content = renderer.render(data, accepted_media_type, renderer_context) - if renderer.charset is None: + 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 -- cgit v1.2.3 From be0f5850c398b7f7397d66eaed26d6b78163b259 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 23 Aug 2013 16:51:34 +0100 Subject: Extra docs --- rest_framework/renderers.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index c07b1652..b30f2ea9 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -326,6 +326,8 @@ class HTMLFormRenderer(BaseRenderer): return an HTML form not bound to any object, otherwise it will return an HTML form with the appropriate initial data populated from the object. + + Note that rendering of field and form errors is not currently supported. """ media_type = 'text/html' format = 'form' @@ -368,6 +370,18 @@ class HTMLFormRenderer(BaseRenderer): return fields def render(self, data, accepted_media_type=None, renderer_context=None): + """ + Render serializer data and return an HTML form, as a string. + """ + # The HTMLFormRenderer currently uses something of a hack to render + # the content, by translating each of the serializer fields into + # an html form field, creating a dynamic form using those fields, + # and then rendering that form. + + # This isn't strictly neccessary, as we could render the serilizer + # fields to HTML directly. The implementation is historical and will + # likely change at some point. + self.renderer_context = renderer_context or {} request = renderer_context['request'] -- cgit v1.2.3 From 8d590ebfded0968e458f8e3a87efabec8384586e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 27 Aug 2013 11:22:19 +0100 Subject: First hacky pass at displaying raw data --- rest_framework/renderers.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index b30f2ea9..cc8de959 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -24,7 +24,7 @@ from rest_framework.settings import api_settings from rest_framework.request import clone_request from rest_framework.utils import encoders from rest_framework.utils.breadcrumbs import get_breadcrumbs -from rest_framework import exceptions, parsers, status, VERSION +from rest_framework import exceptions, status, VERSION class BaseRenderer(object): @@ -482,7 +482,7 @@ class BrowsableAPIRenderer(BaseRenderer): if method in ('DELETE', 'OPTIONS'): return True # Don't actually need to return a form - if not getattr(view, 'get_serializer', None) or not parsers.FormParser in view.parser_classes: + if not getattr(view, 'get_serializer', None) or not any(parser.supports_html_forms for parser in view.parser_classes): return serializer = view.get_serializer(instance=obj) @@ -561,6 +561,29 @@ class BrowsableAPIRenderer(BaseRenderer): view = renderer_context['view'] request = renderer_context['request'] response = renderer_context['response'] + + obj = getattr(view, 'object', None) + if getattr(view, 'get_serializer', None): + serializer = view.get_serializer(instance=obj) + else: + serializer = None + + parsers = [] + for parser_class in view.parser_classes: + content = None + renderer_class = getattr(parser_class, 'renderer_class', None) + if renderer_class and serializer: + renderer = renderer_class() + context = renderer_context.copy() + context['indent'] = 4 + content = renderer.render(serializer.data, accepted_media_type, context) + print content + parsers.append({ + 'media_type': parser_class.media_type, + 'content': content + }) + + media_types = [parser.media_type for parser in view.parser_classes] renderer = self.get_default_renderer(view) -- cgit v1.2.3 From 18007d68464b0cfab970e2a60aed0d41c4de4dac Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 28 Aug 2013 21:52:56 +0100 Subject: Simplifying raw data renderering support --- rest_framework/renderers.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index cc8de959..cd55c783 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -21,7 +21,7 @@ from rest_framework.compat import six from rest_framework.compat import smart_text from rest_framework.compat import yaml from rest_framework.settings import api_settings -from rest_framework.request import clone_request +from rest_framework.request import clone_request, is_form_media_type from rest_framework.utils import encoders from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework import exceptions, status, VERSION @@ -482,7 +482,7 @@ class BrowsableAPIRenderer(BaseRenderer): if method in ('DELETE', 'OPTIONS'): return True # Don't actually need to return a form - if not getattr(view, 'get_serializer', None) or not any(parser.supports_html_forms for parser in view.parser_classes): + if not getattr(view, 'get_serializer', None) or not any(is_form_media_type(parser.media_type) for parser in view.parser_classes): return serializer = view.get_serializer(instance=obj) @@ -565,11 +565,16 @@ class BrowsableAPIRenderer(BaseRenderer): obj = getattr(view, 'object', None) if getattr(view, 'get_serializer', None): serializer = view.get_serializer(instance=obj) + for field_name, field in serializer.fields.items(): + if field.read_only: + del serializer.fields[field_name] else: serializer = None parsers = [] for parser_class in view.parser_classes: + if is_form_media_type(parser_class.media_type): + continue content = None renderer_class = getattr(parser_class, 'renderer_class', None) if renderer_class and serializer: @@ -650,3 +655,4 @@ class MultiPartRenderer(BaseRenderer): def render(self, data, accepted_media_type=None, renderer_context=None): return encode_multipart(self.BOUNDARY, data) + -- cgit v1.2.3 From 37e2720a40d39688f5e6ebb3b5c5aad68b8c25d4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Aug 2013 12:55:56 +0100 Subject: Add `override_method` context manager and cleanup. --- rest_framework/renderers.py | 165 ++++++++++++++++---------------------------- 1 file changed, 58 insertions(+), 107 deletions(-) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index cd55c783..34860f6a 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -21,7 +21,7 @@ from rest_framework.compat import six from rest_framework.compat import smart_text from rest_framework.compat import yaml from rest_framework.settings import api_settings -from rest_framework.request import clone_request, is_form_media_type +from rest_framework.request import is_form_media_type, override_method from rest_framework.utils import encoders from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework import exceptions, status, VERSION @@ -456,18 +456,6 @@ class BrowsableAPIRenderer(BaseRenderer): return False # Doesn't have permissions return True - def _get_rendered_html_form(self, view, method, request): - # We need to impersonate a request with the correct method, - # so that eg. any dynamic get_serializer_class methods return the - # correct form for each method. - restore = view.request - request = clone_request(request, method) - view.request = request - try: - return self.get_rendered_html_form(view, method, request) - finally: - view.request = restore - def get_rendered_html_form(self, view, method, request): """ Return a string representing a rendered HTML form, possibly bound to @@ -475,32 +463,22 @@ class BrowsableAPIRenderer(BaseRenderer): In the absence of the View having an associated form then return None. """ - obj = getattr(view, 'object', None) - if not self.show_form_for_method(view, method, request, obj): - return - - if method in ('DELETE', 'OPTIONS'): - return True # Don't actually need to return a form - - if not getattr(view, 'get_serializer', None) or not any(is_form_media_type(parser.media_type) for parser in view.parser_classes): - return - - serializer = view.get_serializer(instance=obj) - data = serializer.data - form_renderer = self.form_renderer_class() - return form_renderer.render(data, self.accepted_media_type, self.renderer_context) - - def _get_raw_data_form(self, view, method, request, media_types): - # We need to impersonate a request with the correct method, - # so that eg. any dynamic get_serializer_class methods return the - # correct form for each method. - restore = view.request - request = clone_request(request, method) - view.request = request - try: - return self.get_raw_data_form(view, method, request, media_types) - finally: - view.request = restore + with override_method(view, request, method) as request: + obj = getattr(view, 'object', None) + if not self.show_form_for_method(view, method, request, obj): + return + + if method in ('DELETE', 'OPTIONS'): + return True # Don't actually need to return a form + + if (not getattr(view, 'get_serializer', None) + or not any(is_form_media_type(parser.media_type) for parser in view.parser_classes)): + return + + serializer = view.get_serializer(instance=obj) + data = serializer.data + form_renderer = self.form_renderer_class() + return form_renderer.render(data, self.accepted_media_type, self.renderer_context) def get_raw_data_form(self, view, method, request, media_types): """ @@ -508,39 +486,39 @@ class BrowsableAPIRenderer(BaseRenderer): via standard HTML forms. (Which are typically application/x-www-form-urlencoded) """ - - # If we're not using content overloading there's no point in supplying a generic form, - # as the view won't treat the form's value as the content of the request. - if not (api_settings.FORM_CONTENT_OVERRIDE - and api_settings.FORM_CONTENTTYPE_OVERRIDE): - return None - - # Check permissions - obj = getattr(view, 'object', None) - if not self.show_form_for_method(view, method, request, obj): - return - - content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE - content_field = api_settings.FORM_CONTENT_OVERRIDE - choices = [(media_type, media_type) for media_type in media_types] - initial = media_types[0] - - # NB. http://jacobian.org/writing/dynamic-form-generation/ - class GenericContentForm(forms.Form): - def __init__(self): - super(GenericContentForm, self).__init__() - - self.fields[content_type_field] = forms.ChoiceField( - label='Media type', - choices=choices, - initial=initial - ) - self.fields[content_field] = forms.CharField( - label='Content', - widget=forms.Textarea - ) - - return GenericContentForm() + with override_method(view, request, method) as request: + # If we're not using content overloading there's no point in supplying a generic form, + # as the view won't treat the form's value as the content of the request. + if not (api_settings.FORM_CONTENT_OVERRIDE + and api_settings.FORM_CONTENTTYPE_OVERRIDE): + return None + + # Check permissions + obj = getattr(view, 'object', None) + if not self.show_form_for_method(view, method, request, obj): + return + + content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE + content_field = api_settings.FORM_CONTENT_OVERRIDE + choices = [(media_type, media_type) for media_type in media_types] + initial = media_types[0] + + # NB. http://jacobian.org/writing/dynamic-form-generation/ + class GenericContentForm(forms.Form): + def __init__(self): + super(GenericContentForm, self).__init__() + + self.fields[content_type_field] = forms.ChoiceField( + label='Media type', + choices=choices, + initial=initial + ) + self.fields[content_field] = forms.CharField( + label='Content', + widget=forms.Textarea + ) + + return GenericContentForm() def get_name(self, view): return view.get_view_name() @@ -562,47 +540,20 @@ class BrowsableAPIRenderer(BaseRenderer): request = renderer_context['request'] response = renderer_context['response'] - obj = getattr(view, 'object', None) - if getattr(view, 'get_serializer', None): - serializer = view.get_serializer(instance=obj) - for field_name, field in serializer.fields.items(): - if field.read_only: - del serializer.fields[field_name] - else: - serializer = None - - parsers = [] - for parser_class in view.parser_classes: - if is_form_media_type(parser_class.media_type): - continue - content = None - renderer_class = getattr(parser_class, 'renderer_class', None) - if renderer_class and serializer: - renderer = renderer_class() - context = renderer_context.copy() - context['indent'] = 4 - content = renderer.render(serializer.data, accepted_media_type, context) - print content - parsers.append({ - 'media_type': parser_class.media_type, - 'content': content - }) - - media_types = [parser.media_type for parser in view.parser_classes] renderer = self.get_default_renderer(view) content = self.get_content(renderer, data, accepted_media_type, renderer_context) - put_form = self._get_rendered_html_form(view, 'PUT', request) - post_form = self._get_rendered_html_form(view, 'POST', request) - patch_form = self._get_rendered_html_form(view, 'PATCH', request) - delete_form = self._get_rendered_html_form(view, 'DELETE', request) - options_form = self._get_rendered_html_form(view, 'OPTIONS', request) + put_form = self.get_rendered_html_form(view, 'PUT', request) + post_form = self.get_rendered_html_form(view, 'POST', request) + patch_form = self.get_rendered_html_form(view, 'PATCH', request) + delete_form = self.get_rendered_html_form(view, 'DELETE', request) + options_form = self.get_rendered_html_form(view, 'OPTIONS', request) - raw_data_put_form = self._get_raw_data_form(view, 'PUT', request, media_types) - raw_data_post_form = self._get_raw_data_form(view, 'POST', request, media_types) - raw_data_patch_form = self._get_raw_data_form(view, 'PATCH', request, media_types) + raw_data_put_form = self.get_raw_data_form(view, 'PUT', request, media_types) + raw_data_post_form = self.get_raw_data_form(view, 'POST', request, media_types) + raw_data_patch_form = self.get_raw_data_form(view, 'PATCH', request, media_types) raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form name = self.get_name(view) -- cgit v1.2.3 From c7f3b8bebef33093d4e949f797565c4cbcd2695d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Aug 2013 17:23:26 +0100 Subject: Include serialized content in raw data form. --- rest_framework/renderers.py | 43 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 9 deletions(-) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 34860f6a..077d6ebe 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -480,15 +480,16 @@ class BrowsableAPIRenderer(BaseRenderer): form_renderer = self.form_renderer_class() return form_renderer.render(data, self.accepted_media_type, self.renderer_context) - def get_raw_data_form(self, view, method, request, media_types): + def get_raw_data_form(self, 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) """ with override_method(view, request, method) as request: - # If we're not using content overloading there's no point in supplying a generic form, - # as the view won't treat the form's value as the content of the request. + # If we're not using content overloading there's no point in + # supplying a generic form, as the view won't treat the form's + # value as the content of the request. if not (api_settings.FORM_CONTENT_OVERRIDE and api_settings.FORM_CONTENTTYPE_OVERRIDE): return None @@ -498,8 +499,33 @@ class BrowsableAPIRenderer(BaseRenderer): if not self.show_form_for_method(view, method, request, obj): 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. + + # Get a read-only version of the serializer + serializer = view.get_serializer(instance=obj) + for field_name, field in serializer.fields.items(): + if field.read_only: + del serializer.fields[field_name] + + # Render the raw data content + renderer = renderer_class() + accepted = self.accepted_media_type + context = self.renderer_context.copy().update({'indent': 4}) + content = renderer.render(serializer.data, accepted, context) + else: + content = None + + # Generate a generic form that includes a content type field, + # and a content field. content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE content_field = api_settings.FORM_CONTENT_OVERRIDE + + 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] @@ -515,7 +541,8 @@ class BrowsableAPIRenderer(BaseRenderer): ) self.fields[content_field] = forms.CharField( label='Content', - widget=forms.Textarea + widget=forms.Textarea, + initial=content ) return GenericContentForm() @@ -540,8 +567,6 @@ class BrowsableAPIRenderer(BaseRenderer): request = renderer_context['request'] response = renderer_context['response'] - media_types = [parser.media_type for parser in view.parser_classes] - renderer = self.get_default_renderer(view) content = self.get_content(renderer, data, accepted_media_type, renderer_context) @@ -551,9 +576,9 @@ class BrowsableAPIRenderer(BaseRenderer): delete_form = self.get_rendered_html_form(view, 'DELETE', request) options_form = self.get_rendered_html_form(view, 'OPTIONS', request) - raw_data_put_form = self.get_raw_data_form(view, 'PUT', request, media_types) - raw_data_post_form = self.get_raw_data_form(view, 'POST', request, media_types) - raw_data_patch_form = self.get_raw_data_form(view, 'PATCH', request, media_types) + raw_data_put_form = self.get_raw_data_form(view, 'PUT', request) + raw_data_post_form = self.get_raw_data_form(view, 'POST', request) + raw_data_patch_form = self.get_raw_data_form(view, 'PATCH', request) raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form name = self.get_name(view) -- cgit v1.2.3 From e4d2f54529bcf538be93da5770e05b88a32da1c7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 29 Aug 2013 20:39:05 +0100 Subject: Fix indenting on raw data forms --- rest_framework/renderers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 077d6ebe..525e44d5 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -515,7 +515,8 @@ class BrowsableAPIRenderer(BaseRenderer): # Render the raw data content renderer = renderer_class() accepted = self.accepted_media_type - context = self.renderer_context.copy().update({'indent': 4}) + context = self.renderer_context.copy() + context['indent'] = 4 content = renderer.render(serializer.data, accepted, context) else: content = None -- cgit v1.2.3 From 3fba60e99c75dda4e14f7fe4f941d6fc84e4c986 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 30 Aug 2013 09:02:54 +0100 Subject: Drop broken placeholder serializations. --- rest_framework/renderers.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'rest_framework/renderers.py') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 525e44d5..fca67eee 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -338,6 +338,11 @@ class HTMLFormRenderer(BaseRenderer): fields = {} for key, val in data.fields.items(): if getattr(val, 'read_only', True): + # Don't include read-only fields. + continue + + if getattr(val, 'fields', None): + # Nested data not supported by HTML forms. continue kwargs = {} @@ -476,6 +481,7 @@ class BrowsableAPIRenderer(BaseRenderer): return serializer = view.get_serializer(instance=obj) + data = serializer.data form_renderer = self.form_renderer_class() return form_renderer.render(data, self.accepted_media_type, self.renderer_context) @@ -508,9 +514,10 @@ class BrowsableAPIRenderer(BaseRenderer): # Get a read-only version of the serializer serializer = view.get_serializer(instance=obj) - for field_name, field in serializer.fields.items(): - if field.read_only: - del serializer.fields[field_name] + if obj is None: + for name, field in serializer.fields.items(): + if getattr(field, 'read_only', None): + del serializer.fields[name] # Render the raw data content renderer = renderer_class() -- cgit v1.2.3