aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--djangorestframework/mixins.py23
-rw-r--r--djangorestframework/renderers.py19
-rw-r--r--djangorestframework/resources.py56
-rw-r--r--djangorestframework/templates/renderer.html22
-rw-r--r--djangorestframework/views.py5
5 files changed, 72 insertions, 53 deletions
diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py
index 3c05bef0..11e3bb38 100644
--- a/djangorestframework/mixins.py
+++ b/djangorestframework/mixins.py
@@ -401,12 +401,23 @@ class ResourceMixin(object):
def CONTENT(self):
"""
Returns the cleaned, validated request content.
+
+ May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request).
"""
if not hasattr(self, '_content'):
self._content = self.validate_request(self.DATA, self.FILES)
return self._content
@property
+ def PARAMS(self):
+ """
+ Returns the cleaned, validated query parameters.
+
+ May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request).
+ """
+ return self.validate_request(self.request.GET)
+
+ @property
def _resource(self):
if self.resource:
return self.resource(self)
@@ -414,12 +425,14 @@ class ResourceMixin(object):
return ModelResource(self)
elif getattr(self, 'form', None):
return FormResource(self)
+ elif getattr(self, '%s_form' % self.method.lower(), None):
+ return FormResource(self)
return Resource(self)
- def validate_request(self, data, files):
+ def validate_request(self, data, files=None):
"""
- Given the request *data* return the cleaned, validated content.
- Typically raises an :class:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
+ Given the request *data* and optional *files*, return the cleaned, validated content.
+ May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
"""
return self._resource.validate_request(data, files)
@@ -429,8 +442,8 @@ class ResourceMixin(object):
"""
return self._resource.filter_response(obj)
- def get_bound_form(self, content=None):
- return self._resource.get_bound_form(content)
+ def get_bound_form(self, content=None, method=None):
+ return self._resource.get_bound_form(content, method=method)
diff --git a/djangorestframework/renderers.py b/djangorestframework/renderers.py
index 245bfdfe..9834ba5e 100644
--- a/djangorestframework/renderers.py
+++ b/djangorestframework/renderers.py
@@ -172,7 +172,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
return content
- def _get_form_instance(self, view):
+ def _get_form_instance(self, view, method):
"""
Get a form, possibly bound to either the input or output data.
In the absence on of the Resource having an associated form then
@@ -180,22 +180,24 @@ class DocumentingTemplateRenderer(BaseRenderer):
"""
# Get the form instance if we have one bound to the input
- form_instance = getattr(view, 'bound_form_instance', None)
+ form_instance = None
+ if method == view.method.lower():
+ form_instance = getattr(view, 'bound_form_instance', None)
if not form_instance and hasattr(view, 'get_bound_form'):
# Otherwise if we have a response that is valid against the form then use that
if view.response.has_content_body:
try:
- form_instance = view.get_bound_form(view.response.cleaned_content)
+ form_instance = view.get_bound_form(view.response.cleaned_content, method=method)
if form_instance and not form_instance.is_valid():
form_instance = None
except:
form_instance = None
-
+
# If we still don't have a form instance then try to get an unbound form
if not form_instance:
try:
- form_instance = view.get_bound_form()
+ form_instance = view.get_bound_form(method=method)
except:
pass
@@ -250,7 +252,9 @@ class DocumentingTemplateRenderer(BaseRenderer):
needed to self-document the response to this request.
"""
content = self._get_content(self.view, self.view.request, obj, media_type)
- form_instance = self._get_form_instance(self.view)
+
+ put_form_instance = self._get_form_instance(self.view, 'put')
+ post_form_instance = self._get_form_instance(self.view, 'post')
if url_resolves(settings.LOGIN_URL) and url_resolves(settings.LOGOUT_URL):
login_url = "%s?next=%s" % (settings.LOGIN_URL, quote_plus(self.view.request.path))
@@ -282,7 +286,8 @@ class DocumentingTemplateRenderer(BaseRenderer):
'markeddown': markeddown,
'breadcrumblist': breadcrumb_list,
'available_media_types': self.view._rendered_media_types,
- 'form': form_instance,
+ 'put_form': put_form_instance,
+ 'post_form': post_form_instance,
'login_url': login_url,
'logout_url': logout_url,
'ACCEPT_PARAM': getattr(self.view, '_ACCEPT_QUERY_PARAM', None),
diff --git a/djangorestframework/resources.py b/djangorestframework/resources.py
index de8e1351..1ac6eeac 100644
--- a/djangorestframework/resources.py
+++ b/djangorestframework/resources.py
@@ -122,7 +122,7 @@ class BaseResource(object):
def __init__(self, view):
self.view = view
- def validate_request(self, data, files):
+ def validate_request(self, data, files=None):
"""
Given the request content return the cleaned, validated content.
Typically raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
@@ -168,22 +168,12 @@ class FormResource(Resource):
"""
The :class:`Form` class that should be used for request validation.
- This can be overridden by a :attr:`form` attribute on the :class:`.View`.
+ This can be overridden by a :attr:`form` attribute on the :class:`views.View`.
"""
form = None
- def __init__(self, view):
- """
- Allow a :attr:`form` attributes set on the :class:`View` to override
- the :attr:`form` attribute set on the :class:`Resource`.
- """
- super(FormResource, self).__init__(view)
-
- if getattr(view, 'form', None):
- self.form = view.form
-
- def validate_request(self, data, files):
+ def validate_request(self, data, files=None):
"""
Given some content as input return some cleaned, validated content.
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
@@ -285,18 +275,34 @@ class FormResource(Resource):
raise ErrorResponse(400, detail)
- def get_bound_form(self, data=None, files=None):
+ def get_bound_form(self, data=None, files=None, method=None):
"""
Given some content return a Django form bound to that content.
If form validation is turned off (:attr:`form` class attribute is :const:`None`) then returns :const:`None`.
"""
- if not self.form:
+
+ # A form on the view overrides a form on the resource.
+ form = getattr(self.view, 'form', self.form)
+
+ # Use the requested method or determine the request method
+ if method is None and hasattr(self.view, 'request') and hasattr(self.view, 'method'):
+ method = self.view.method
+ elif method is None and hasattr(self.view, 'request'):
+ method = self.view.request.method
+
+ # A method form on the view or resource overrides the general case.
+ # Method forms are attributes like `get_form` `post_form` `put_form`.
+ if method:
+ form = getattr(self, '%s_form' % method.lower(), form)
+ form = getattr(self.view, '%s_form' % method.lower(), form)
+
+ if not form:
return None
if data is not None:
- return self.form(data, files)
+ return form(data, files)
- return self.form()
+ return form()
@@ -326,14 +332,14 @@ class ModelResource(FormResource):
The form class that should be used for request validation.
If set to :const:`None` then the default model form validation will be used.
- This can be overridden by a :attr:`form` attribute on the :class:`.View`.
+ This can be overridden by a :attr:`form` attribute on the :class:`views.View`.
"""
form = None
"""
The model class which this resource maps to.
- This can be overridden by a :attr:`model` attribute on the :class:`.View`.
+ This can be overridden by a :attr:`model` attribute on the :class:`views.View`.
"""
model = None
@@ -372,7 +378,7 @@ class ModelResource(FormResource):
if getattr(view, 'model', None):
self.model = view.model
- def validate_request(self, data, files):
+ def validate_request(self, data, files=None):
"""
Given some content as input return some cleaned, validated content.
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
@@ -389,7 +395,7 @@ class ModelResource(FormResource):
return self._validate(data, files, allowed_extra_fields=self._property_fields_set)
- def get_bound_form(self, data=None, files=None):
+ def get_bound_form(self, data=None, files=None, method=None):
"""
Given some content return a ``Form`` instance bound to that content.
@@ -397,9 +403,11 @@ class ModelResource(FormResource):
to create the Form, otherwise the model will be used to create a ModelForm.
"""
- if self.form:
- # Use explict Form
- return super(ModelResource, self).get_bound_form(data, files)
+ form = super(ModelResource, self).get_bound_form(data, files, method=method)
+
+ # Use an explict Form if it exists
+ if form:
+ return form
elif self.model:
# Fall back to ModelForm which we create on the fly
diff --git a/djangorestframework/templates/renderer.html b/djangorestframework/templates/renderer.html
index 3010d712..09b17df7 100644
--- a/djangorestframework/templates/renderer.html
+++ b/djangorestframework/templates/renderer.html
@@ -58,19 +58,16 @@
</form>
{% endif %}
- {% comment %} *** Only display the POST/PUT/DELETE forms if we have a bound form, and if method ***
- *** tunneling via POST forms is enabled. ***
- *** (We could display only the POST form if method tunneling is disabled, but I think ***
- *** the user experience would be confusing, so we simply turn all forms off. *** {% endcomment %}
-
- {% if METHOD_PARAM and form %}
+ {# Only display the POST/PUT/DELETE forms if method tunneling via POST forms is enabled. #}
+ {% if METHOD_PARAM %}
+
{% if 'POST' in view.allowed_methods %}
- <form action="{{ request.path }}" method="post" {% if form.is_multipart %}enctype="multipart/form-data"{% endif %}>
+ <form action="{{ request.path }}" method="post" {% if post_form.is_multipart %}enctype="multipart/form-data"{% endif %}>
<fieldset class='module aligned'>
<h2>POST {{ name }}</h2>
{% csrf_token %}
- {{ form.non_field_errors }}
- {% for field in form %}
+ {{ post_form.non_field_errors }}
+ {% for field in post_form %}
<div class='form-row'>
{{ field.label_tag }}
{{ field }}
@@ -86,13 +83,13 @@
{% endif %}
{% if 'PUT' in view.allowed_methods %}
- <form action="{{ request.path }}" method="post" {% if form.is_multipart %}enctype="multipart/form-data"{% endif %}>
+ <form action="{{ request.path }}" method="post" {% if put_form.is_multipart %}enctype="multipart/form-data"{% endif %}>
<fieldset class='module aligned'>
<h2>PUT {{ name }}</h2>
<input type="hidden" name="{{ METHOD_PARAM }}" value="PUT" />
{% csrf_token %}
- {{ form.non_field_errors }}
- {% for field in form %}
+ {{ put_form.non_field_errors }}
+ {% for field in put_form %}
<div class='form-row'>
{{ field.label_tag }}
{{ field }}
@@ -119,6 +116,7 @@
</fieldset>
</form>
{% endif %}
+
{% endif %}
</div>
</div>
diff --git a/djangorestframework/views.py b/djangorestframework/views.py
index 1315229a..5b3cc855 100644
--- a/djangorestframework/views.py
+++ b/djangorestframework/views.py
@@ -64,11 +64,6 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
"""
permissions = ( permissions.FullAnonAccess, )
- # Allow name and description for the Resource to be set explicitly,
- # overiding the default classname/docstring behaviour.
- # These are used for documentation in the standard html and text renderers.
- name = None
- description = None
@classmethod
def as_view(cls, **initkwargs):