Add override_method context manager and cleanup.

This commit is contained in:
Tom Christie 2013-08-29 12:55:56 +01:00
parent 18007d6846
commit 37e2720a40
2 changed files with 73 additions and 99 deletions

View File

@ -21,7 +21,7 @@ from rest_framework.compat import six
from rest_framework.compat import smart_text from rest_framework.compat import smart_text
from rest_framework.compat import yaml from rest_framework.compat import yaml
from rest_framework.settings import api_settings 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 import encoders
from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework.utils.breadcrumbs import get_breadcrumbs
from rest_framework import exceptions, status, VERSION from rest_framework import exceptions, status, VERSION
@ -456,18 +456,6 @@ class BrowsableAPIRenderer(BaseRenderer):
return False # Doesn't have permissions return False # Doesn't have permissions
return True 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): def get_rendered_html_form(self, view, method, request):
""" """
Return a string representing a rendered HTML form, possibly bound to 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. In the absence of the View having an associated form then return None.
""" """
obj = getattr(view, 'object', None) with override_method(view, request, method) as request:
if not self.show_form_for_method(view, method, request, obj): obj = getattr(view, 'object', None)
return if not self.show_form_for_method(view, method, request, obj):
return
if method in ('DELETE', 'OPTIONS'): if method in ('DELETE', 'OPTIONS'):
return True # Don't actually need to return a form 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): if (not getattr(view, 'get_serializer', None)
return or not any(is_form_media_type(parser.media_type) for parser in view.parser_classes)):
return
serializer = view.get_serializer(instance=obj) serializer = view.get_serializer(instance=obj)
data = serializer.data data = serializer.data
form_renderer = self.form_renderer_class() form_renderer = self.form_renderer_class()
return form_renderer.render(data, self.accepted_media_type, self.renderer_context) 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): def get_raw_data_form(self, view, method, request, media_types):
""" """
@ -508,39 +486,39 @@ class BrowsableAPIRenderer(BaseRenderer):
via standard HTML forms. via standard HTML forms.
(Which are typically application/x-www-form-urlencoded) (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 not (api_settings.FORM_CONTENT_OVERRIDE
and api_settings.FORM_CONTENTTYPE_OVERRIDE):
return None
# If we're not using content overloading there's no point in supplying a generic form, # Check permissions
# as the view won't treat the form's value as the content of the request. obj = getattr(view, 'object', None)
if not (api_settings.FORM_CONTENT_OVERRIDE if not self.show_form_for_method(view, method, request, obj):
and api_settings.FORM_CONTENTTYPE_OVERRIDE): return
return None
# Check permissions content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE
obj = getattr(view, 'object', None) content_field = api_settings.FORM_CONTENT_OVERRIDE
if not self.show_form_for_method(view, method, request, obj): choices = [(media_type, media_type) for media_type in media_types]
return initial = media_types[0]
content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE # NB. http://jacobian.org/writing/dynamic-form-generation/
content_field = api_settings.FORM_CONTENT_OVERRIDE class GenericContentForm(forms.Form):
choices = [(media_type, media_type) for media_type in media_types] def __init__(self):
initial = media_types[0] super(GenericContentForm, self).__init__()
# NB. http://jacobian.org/writing/dynamic-form-generation/ self.fields[content_type_field] = forms.ChoiceField(
class GenericContentForm(forms.Form): label='Media type',
def __init__(self): choices=choices,
super(GenericContentForm, self).__init__() initial=initial
)
self.fields[content_field] = forms.CharField(
label='Content',
widget=forms.Textarea
)
self.fields[content_type_field] = forms.ChoiceField( return GenericContentForm()
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): def get_name(self, view):
return view.get_view_name() return view.get_view_name()
@ -562,47 +540,20 @@ class BrowsableAPIRenderer(BaseRenderer):
request = renderer_context['request'] request = renderer_context['request']
response = renderer_context['response'] 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] media_types = [parser.media_type for parser in view.parser_classes]
renderer = self.get_default_renderer(view) renderer = self.get_default_renderer(view)
content = self.get_content(renderer, data, accepted_media_type, renderer_context) content = self.get_content(renderer, data, accepted_media_type, renderer_context)
put_form = self._get_rendered_html_form(view, 'PUT', request) put_form = self.get_rendered_html_form(view, 'PUT', request)
post_form = self._get_rendered_html_form(view, 'POST', request) post_form = self.get_rendered_html_form(view, 'POST', request)
patch_form = self._get_rendered_html_form(view, 'PATCH', request) patch_form = self.get_rendered_html_form(view, 'PATCH', request)
delete_form = self._get_rendered_html_form(view, 'DELETE', request) delete_form = self.get_rendered_html_form(view, 'DELETE', request)
options_form = self._get_rendered_html_form(view, 'OPTIONS', 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_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_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_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 raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form
name = self.get_name(view) name = self.get_name(view)

View File

@ -28,6 +28,29 @@ def is_form_media_type(media_type):
base_media_type == 'multipart/form-data') base_media_type == 'multipart/form-data')
class override_method(object):
"""
A context manager that temporarily overrides the method on a request,
additionally setting the `view.request` attribute.
Usage:
with override_method(view, request, 'POST') as request:
... # Do stuff with `view` and `request`
"""
def __init__(self, view, request, method):
self.view = view
self.request = request
self.method = method
def __enter__(self):
self.view.request = clone_request(self.request, self.method)
return self.view.request
def __exit__(self, *args, **kwarg):
self.view.request = self.request
class Empty(object): class Empty(object):
""" """
Placeholder for unset attributes. Placeholder for unset attributes.