diff options
| -rw-r--r-- | djangorestframework/mixins.py | 23 | ||||
| -rw-r--r-- | djangorestframework/renderers.py | 19 | ||||
| -rw-r--r-- | djangorestframework/resources.py | 56 | ||||
| -rw-r--r-- | djangorestframework/templates/renderer.html | 22 | ||||
| -rw-r--r-- | djangorestframework/views.py | 5 |
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): |
