Commit 21d2dcc2 by Tom Christie

Allow .form specified on view. Allow get_form, put_form, post_form. Add .PARAMS.

parent 9e9ae609
...@@ -401,12 +401,23 @@ class ResourceMixin(object): ...@@ -401,12 +401,23 @@ class ResourceMixin(object):
def CONTENT(self): def CONTENT(self):
""" """
Returns the cleaned, validated request content. Returns the cleaned, validated request content.
May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request).
""" """
if not hasattr(self, '_content'): if not hasattr(self, '_content'):
self._content = self.validate_request(self.DATA, self.FILES) self._content = self.validate_request(self.DATA, self.FILES)
return self._content return self._content
@property @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): def _resource(self):
if self.resource: if self.resource:
return self.resource(self) return self.resource(self)
...@@ -414,12 +425,14 @@ class ResourceMixin(object): ...@@ -414,12 +425,14 @@ class ResourceMixin(object):
return ModelResource(self) return ModelResource(self)
elif getattr(self, 'form', None): elif getattr(self, 'form', None):
return FormResource(self) return FormResource(self)
elif getattr(self, '%s_form' % self.method.lower(), None):
return FormResource(self)
return Resource(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. Given the request *data* and optional *files*, return the cleaned, validated content.
Typically raises an :class:`response.ErrorResponse` with status code 400 (Bad Request) on failure. May raise an :class:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
""" """
return self._resource.validate_request(data, files) return self._resource.validate_request(data, files)
...@@ -429,8 +442,8 @@ class ResourceMixin(object): ...@@ -429,8 +442,8 @@ class ResourceMixin(object):
""" """
return self._resource.filter_response(obj) return self._resource.filter_response(obj)
def get_bound_form(self, content=None): def get_bound_form(self, content=None, method=None):
return self._resource.get_bound_form(content) return self._resource.get_bound_form(content, method=method)
......
...@@ -172,7 +172,7 @@ class DocumentingTemplateRenderer(BaseRenderer): ...@@ -172,7 +172,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
return content 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. Get a form, possibly bound to either the input or output data.
In the absence on of the Resource having an associated form then In the absence on of the Resource having an associated form then
...@@ -180,13 +180,15 @@ class DocumentingTemplateRenderer(BaseRenderer): ...@@ -180,13 +180,15 @@ class DocumentingTemplateRenderer(BaseRenderer):
""" """
# Get the form instance if we have one bound to the input # Get the form instance if we have one bound to the input
form_instance = None
if method == view.method.lower():
form_instance = getattr(view, 'bound_form_instance', None) form_instance = getattr(view, 'bound_form_instance', None)
if not form_instance and hasattr(view, 'get_bound_form'): 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 # Otherwise if we have a response that is valid against the form then use that
if view.response.has_content_body: if view.response.has_content_body:
try: 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(): if form_instance and not form_instance.is_valid():
form_instance = None form_instance = None
except: except:
...@@ -195,7 +197,7 @@ class DocumentingTemplateRenderer(BaseRenderer): ...@@ -195,7 +197,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
# If we still don't have a form instance then try to get an unbound form # If we still don't have a form instance then try to get an unbound form
if not form_instance: if not form_instance:
try: try:
form_instance = view.get_bound_form() form_instance = view.get_bound_form(method=method)
except: except:
pass pass
...@@ -250,7 +252,9 @@ class DocumentingTemplateRenderer(BaseRenderer): ...@@ -250,7 +252,9 @@ class DocumentingTemplateRenderer(BaseRenderer):
needed to self-document the response to this request. needed to self-document the response to this request.
""" """
content = self._get_content(self.view, self.view.request, obj, media_type) 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): 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)) login_url = "%s?next=%s" % (settings.LOGIN_URL, quote_plus(self.view.request.path))
...@@ -282,7 +286,8 @@ class DocumentingTemplateRenderer(BaseRenderer): ...@@ -282,7 +286,8 @@ class DocumentingTemplateRenderer(BaseRenderer):
'markeddown': markeddown, 'markeddown': markeddown,
'breadcrumblist': breadcrumb_list, 'breadcrumblist': breadcrumb_list,
'available_media_types': self.view._rendered_media_types, '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, 'login_url': login_url,
'logout_url': logout_url, 'logout_url': logout_url,
'ACCEPT_PARAM': getattr(self.view, '_ACCEPT_QUERY_PARAM', None), 'ACCEPT_PARAM': getattr(self.view, '_ACCEPT_QUERY_PARAM', None),
......
...@@ -122,7 +122,7 @@ class BaseResource(object): ...@@ -122,7 +122,7 @@ class BaseResource(object):
def __init__(self, view): def __init__(self, view):
self.view = 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. Given the request content return the cleaned, validated content.
Typically raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure. Typically raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
...@@ -168,22 +168,12 @@ class FormResource(Resource): ...@@ -168,22 +168,12 @@ class FormResource(Resource):
""" """
The :class:`Form` class that should be used for request validation. 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 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. Given some content as input return some cleaned, validated content.
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure. Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
...@@ -285,18 +275,34 @@ class FormResource(Resource): ...@@ -285,18 +275,34 @@ class FormResource(Resource):
raise ErrorResponse(400, detail) 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. 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 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 return None
if data is not 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): ...@@ -326,14 +332,14 @@ class ModelResource(FormResource):
The form class that should be used for request validation. The form class that should be used for request validation.
If set to :const:`None` then the default model form validation will be used. 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 form = None
""" """
The model class which this resource maps to. 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 model = None
...@@ -372,7 +378,7 @@ class ModelResource(FormResource): ...@@ -372,7 +378,7 @@ class ModelResource(FormResource):
if getattr(view, 'model', None): if getattr(view, 'model', None):
self.model = view.model 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. Given some content as input return some cleaned, validated content.
Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure. Raises a :exc:`response.ErrorResponse` with status code 400 (Bad Request) on failure.
...@@ -389,7 +395,7 @@ class ModelResource(FormResource): ...@@ -389,7 +395,7 @@ class ModelResource(FormResource):
return self._validate(data, files, allowed_extra_fields=self._property_fields_set) 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. Given some content return a ``Form`` instance bound to that content.
...@@ -397,9 +403,11 @@ class ModelResource(FormResource): ...@@ -397,9 +403,11 @@ class ModelResource(FormResource):
to create the Form, otherwise the model will be used to create a ModelForm. to create the Form, otherwise the model will be used to create a ModelForm.
""" """
if self.form: form = super(ModelResource, self).get_bound_form(data, files, method=method)
# Use explict Form
return super(ModelResource, self).get_bound_form(data, files) # Use an explict Form if it exists
if form:
return form
elif self.model: elif self.model:
# Fall back to ModelForm which we create on the fly # Fall back to ModelForm which we create on the fly
......
...@@ -58,19 +58,16 @@ ...@@ -58,19 +58,16 @@
</form> </form>
{% endif %} {% endif %}
{% comment %} *** Only display the POST/PUT/DELETE forms if we have a bound form, and if method *** {# Only display the POST/PUT/DELETE forms if method tunneling via POST forms is enabled. #}
*** tunneling via POST forms is enabled. *** {% if METHOD_PARAM %}
*** (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 %}
{% if 'POST' in view.allowed_methods %} {% 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'> <fieldset class='module aligned'>
<h2>POST {{ name }}</h2> <h2>POST {{ name }}</h2>
{% csrf_token %} {% csrf_token %}
{{ form.non_field_errors }} {{ post_form.non_field_errors }}
{% for field in form %} {% for field in post_form %}
<div class='form-row'> <div class='form-row'>
{{ field.label_tag }} {{ field.label_tag }}
{{ field }} {{ field }}
...@@ -86,13 +83,13 @@ ...@@ -86,13 +83,13 @@
{% endif %} {% endif %}
{% if 'PUT' in view.allowed_methods %} {% 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'> <fieldset class='module aligned'>
<h2>PUT {{ name }}</h2> <h2>PUT {{ name }}</h2>
<input type="hidden" name="{{ METHOD_PARAM }}" value="PUT" /> <input type="hidden" name="{{ METHOD_PARAM }}" value="PUT" />
{% csrf_token %} {% csrf_token %}
{{ form.non_field_errors }} {{ put_form.non_field_errors }}
{% for field in form %} {% for field in put_form %}
<div class='form-row'> <div class='form-row'>
{{ field.label_tag }} {{ field.label_tag }}
{{ field }} {{ field }}
...@@ -119,6 +116,7 @@ ...@@ -119,6 +116,7 @@
</fieldset> </fieldset>
</form> </form>
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
</div> </div>
......
...@@ -64,11 +64,6 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView): ...@@ -64,11 +64,6 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
""" """
permissions = ( permissions.FullAnonAccess, ) 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 @classmethod
def as_view(cls, **initkwargs): def as_view(cls, **initkwargs):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment