mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-03 13:14:30 +03:00
Allow .form specified on view. Allow get_form, put_form, post_form. Add .PARAMS.
This commit is contained in:
parent
9e9ae60949
commit
21d2dcc294
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,22 +180,24 @@ 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 = 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'):
|
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:
|
||||||
form_instance = None
|
form_instance = None
|
||||||
|
|
||||||
# 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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user