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

This commit is contained in:
Tom Christie 2011-05-27 14:40:19 +01:00
parent 9e9ae60949
commit 21d2dcc294
5 changed files with 72 additions and 53 deletions

View File

@ -401,11 +401,22 @@ 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
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 @property
def _resource(self): def _resource(self):
if self.resource: if self.resource:
@ -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):
""" """
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)

View File

@ -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):
""" """
# 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):
# 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):
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):
'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),

View File

@ -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):
""" """
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): def validate_request(self, data, files=None):
self.form = view.form
def validate_request(self, data, files):
""" """
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):
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):
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):
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):
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):
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

View File

@ -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 @@
{% 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 @@
</fieldset> </fieldset>
</form> </form>
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -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):