Commit 15f9e7c5 by Tom Christie

refactoring resource specfic stuff into ResourceMixin - validators now defunct

parent 4d126796
...@@ -85,9 +85,9 @@ class UserLoggedInAuthenticaton(BaseAuthenticaton): ...@@ -85,9 +85,9 @@ class UserLoggedInAuthenticaton(BaseAuthenticaton):
if getattr(request, 'user', None) and request.user.is_active: if getattr(request, 'user', None) and request.user.is_active:
# If this is a POST request we enforce CSRF validation. # If this is a POST request we enforce CSRF validation.
if request.method.upper() == 'POST': if request.method.upper() == 'POST':
# Temporarily replace request.POST with .RAW_CONTENT, # Temporarily replace request.POST with .DATA,
# so that we use our more generic request parsing # so that we use our more generic request parsing
request._post = self.view.RAW_CONTENT request._post = self.view.DATA
resp = CsrfViewMiddleware().process_view(request, None, (), {}) resp = CsrfViewMiddleware().process_view(request, None, (), {})
del(request._post) del(request._post)
if resp is not None: # csrf failed if resp is not None: # csrf failed
......
"""""" """
The mixins module provides a set of reusable mixin classes that can be added to a ``View``.
"""
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
...@@ -18,9 +20,12 @@ from StringIO import StringIO ...@@ -18,9 +20,12 @@ from StringIO import StringIO
__all__ = ( __all__ = (
# Base behavior mixins
'RequestMixin', 'RequestMixin',
'ResponseMixin', 'ResponseMixin',
'AuthMixin', 'AuthMixin',
'ResourceMixin',
# Model behavior mixins
'ReadModelMixin', 'ReadModelMixin',
'CreateModelMixin', 'CreateModelMixin',
'UpdateModelMixin', 'UpdateModelMixin',
...@@ -36,13 +41,12 @@ class RequestMixin(object): ...@@ -36,13 +41,12 @@ class RequestMixin(object):
Mixin class to provide request parsing behavior. Mixin class to provide request parsing behavior.
""" """
USE_FORM_OVERLOADING = True _USE_FORM_OVERLOADING = True
METHOD_PARAM = "_method" _METHOD_PARAM = '_method'
CONTENTTYPE_PARAM = "_content_type" _CONTENTTYPE_PARAM = '_content_type'
CONTENT_PARAM = "_content" _CONTENT_PARAM = '_content'
parsers = () parsers = ()
validators = ()
def _get_method(self): def _get_method(self):
""" """
...@@ -137,62 +141,58 @@ class RequestMixin(object): ...@@ -137,62 +141,58 @@ class RequestMixin(object):
self._stream = stream self._stream = stream
def _get_raw_content(self): def _load_data_and_files(self):
""" (self._data, self._files) = self._parse(self.stream, self.content_type)
Returns the parsed content of the request
"""
if not hasattr(self, '_raw_content'):
self._raw_content = self.parse(self.stream, self.content_type)
return self._raw_content
def _get_data(self):
if not hasattr(self, '_data'):
self._load_data_and_files()
return self._data
def _get_content(self): def _get_files(self):
""" if not hasattr(self, '_files'):
Returns the parsed and validated content of the request self._load_data_and_files()
""" return self._files
if not hasattr(self, '_content'):
self._content = self.validate(self.RAW_CONTENT)
return self._content
# TODO: Modify this so that it happens implictly, rather than being called explicitly # TODO: Modify this so that it happens implictly, rather than being called explicitly
# ie accessing any of .DATA, .FILES, .content_type, .stream or .method will force # ie accessing any of .DATA, .FILES, .content_type, .method will force
# form overloading. # form overloading.
def perform_form_overloading(self): def _perform_form_overloading(self):
""" """
Check the request to see if it is using form POST '_method'/'_content'/'_content_type' overrides. Check the request to see if it is using form POST '_method'/'_content'/'_content_type' overrides.
If it is then alter self.method, self.content_type, self.CONTENT to reflect that rather than simply If it is then alter self.method, self.content_type, self.CONTENT to reflect that rather than simply
delegating them to the original request. delegating them to the original request.
""" """
if not self.USE_FORM_OVERLOADING or self.method != 'POST' or not is_form_media_type(self.content_type): if not self._USE_FORM_OVERLOADING or self.method != 'POST' or not is_form_media_type(self.content_type):
return return
# Temporarily switch to using the form parsers, then parse the content # Temporarily switch to using the form parsers, then parse the content
parsers = self.parsers parsers = self.parsers
self.parsers = (FormParser, MultiPartParser) self.parsers = (FormParser, MultiPartParser)
content = self.RAW_CONTENT content = self.DATA
self.parsers = parsers self.parsers = parsers
# Method overloading - change the method and remove the param from the content # Method overloading - change the method and remove the param from the content
if self.METHOD_PARAM in content: if self._METHOD_PARAM in content:
self.method = content[self.METHOD_PARAM].upper() self.method = content[self._METHOD_PARAM].upper()
del self._raw_content[self.METHOD_PARAM] del self._data[self._METHOD_PARAM]
# Content overloading - rewind the stream and modify the content type # Content overloading - rewind the stream and modify the content type
if self.CONTENT_PARAM in content and self.CONTENTTYPE_PARAM in content: if self._CONTENT_PARAM in content and self._CONTENTTYPE_PARAM in content:
self._content_type = content[self.CONTENTTYPE_PARAM] self._content_type = content[self._CONTENTTYPE_PARAM]
self._stream = StringIO(content[self.CONTENT_PARAM]) self._stream = StringIO(content[self._CONTENT_PARAM])
del(self._raw_content) del(self._data)
def parse(self, stream, content_type): def _parse(self, stream, content_type):
""" """
Parse the request content. Parse the request content.
May raise a 415 ErrorResponse (Unsupported Media Type), or a 400 ErrorResponse (Bad Request). May raise a 415 ErrorResponse (Unsupported Media Type), or a 400 ErrorResponse (Bad Request).
""" """
if stream is None or content_type is None: if stream is None or content_type is None:
return None return (None, None)
parsers = as_tuple(self.parsers) parsers = as_tuple(self.parsers)
...@@ -206,48 +206,28 @@ class RequestMixin(object): ...@@ -206,48 +206,28 @@ class RequestMixin(object):
content_type}) content_type})
# TODO: Acutally this needs to go into Resource
def validate(self, content):
"""
Validate, cleanup, and type-ify the request content.
"""
for validator_cls in self.validators:
validator = validator_cls(self)
content = validator.validate(content)
return content
# TODO: Acutally this needs to go into Resource
def get_bound_form(self, content=None):
"""
Return a bound form instance for the given content,
if there is an appropriate form validator attached to the view.
"""
for validator_cls in self.validators:
if hasattr(validator_cls, 'get_bound_form'):
validator = validator_cls(self)
return validator.get_bound_form(content)
return None
@property @property
def parsed_media_types(self): def parsed_media_types(self):
"""Return an list of all the media types that this view can parse.""" """
Return an list of all the media types that this view can parse.
"""
return [parser.media_type for parser in self.parsers] return [parser.media_type for parser in self.parsers]
@property @property
def default_parser(self): def default_parser(self):
"""Return the view's most preferred parser. """
(This has no behavioral effect, but is may be used by documenting renderers)""" Return the view's most preferred parser.
(This has no behavioral effect, but is may be used by documenting renderers)
"""
return self.parsers[0] return self.parsers[0]
method = property(_get_method, _set_method) method = property(_get_method, _set_method)
content_type = property(_get_content_type, _set_content_type) content_type = property(_get_content_type, _set_content_type)
stream = property(_get_stream, _set_stream) stream = property(_get_stream, _set_stream)
RAW_CONTENT = property(_get_raw_content) DATA = property(_get_data)
CONTENT = property(_get_content) FILES = property(_get_files)
########## ResponseMixin ########## ########## ResponseMixin ##########
...@@ -422,6 +402,28 @@ class AuthMixin(object): ...@@ -422,6 +402,28 @@ class AuthMixin(object):
permission.check_permission(user) permission.check_permission(user)
########## Resource Mixin ##########
class ResourceMixin(object):
@property
def CONTENT(self):
if not hasattr(self, '_content'):
self._content = self._get_content(self.DATA, self.FILES)
return self._content
def _get_content(self, data, files):
resource = self.resource(self)
return resource.validate(data, files)
def get_bound_form(self, content=None):
resource = self.resource(self)
return resource.get_bound_form(content)
def object_to_data(self, obj):
resource = self.resource(self)
return resource.object_to_data(obj)
########## Model Mixins ########## ########## Model Mixins ##########
class ReadModelMixin(object): class ReadModelMixin(object):
......
...@@ -41,7 +41,7 @@ class BaseParser(object): ...@@ -41,7 +41,7 @@ class BaseParser(object):
""" """
self.view = view self.view = view
def can_handle_request(self, media_type): def can_handle_request(self, content_type):
""" """
Returns `True` if this parser is able to deal with the given media type. Returns `True` if this parser is able to deal with the given media type.
...@@ -52,12 +52,12 @@ class BaseParser(object): ...@@ -52,12 +52,12 @@ class BaseParser(object):
This may be overridden to provide for other behavior, but typically you'll This may be overridden to provide for other behavior, but typically you'll
instead want to just set the ``media_type`` attribute on the class. instead want to just set the ``media_type`` attribute on the class.
""" """
return media_type_matches(media_type, self.media_type) return media_type_matches(content_type, self.media_type)
def parse(self, stream): def parse(self, stream):
""" """
Given a stream to read from, return the deserialized output. Given a stream to read from, return the deserialized output.
The return value may be of any type, but for many parsers it might typically be a dict-like object. Should return a 2-tuple of (data, files).
""" """
raise NotImplementedError("BaseParser.parse() Must be overridden to be implemented.") raise NotImplementedError("BaseParser.parse() Must be overridden to be implemented.")
...@@ -67,7 +67,7 @@ class JSONParser(BaseParser): ...@@ -67,7 +67,7 @@ class JSONParser(BaseParser):
def parse(self, stream): def parse(self, stream):
try: try:
return json.load(stream) return (json.load(stream), None)
except ValueError, exc: except ValueError, exc:
raise ErrorResponse(status.HTTP_400_BAD_REQUEST, raise ErrorResponse(status.HTTP_400_BAD_REQUEST,
{'detail': 'JSON parse error - %s' % unicode(exc)}) {'detail': 'JSON parse error - %s' % unicode(exc)})
...@@ -107,7 +107,7 @@ class PlainTextParser(BaseParser): ...@@ -107,7 +107,7 @@ class PlainTextParser(BaseParser):
media_type = 'text/plain' media_type = 'text/plain'
def parse(self, stream): def parse(self, stream):
return stream.read() return (stream.read(), None)
class FormParser(BaseParser, DataFlatener): class FormParser(BaseParser, DataFlatener):
...@@ -139,7 +139,7 @@ class FormParser(BaseParser, DataFlatener): ...@@ -139,7 +139,7 @@ class FormParser(BaseParser, DataFlatener):
if key in self.RESERVED_FORM_PARAMS: if key in self.RESERVED_FORM_PARAMS:
data.pop(key) data.pop(key)
return data return (data, None)
def remove_empty_val(self, val_list): def remove_empty_val(self, val_list):
""" """ """ """
...@@ -152,11 +152,6 @@ class FormParser(BaseParser, DataFlatener): ...@@ -152,11 +152,6 @@ class FormParser(BaseParser, DataFlatener):
val_list.pop(ind) val_list.pop(ind)
class MultipartData(dict):
def __init__(self, data, files):
dict.__init__(self, data)
self.FILES = files
class MultiPartParser(BaseParser, DataFlatener): class MultiPartParser(BaseParser, DataFlatener):
media_type = 'multipart/form-data' media_type = 'multipart/form-data'
RESERVED_FORM_PARAMS = ('csrfmiddlewaretoken',) RESERVED_FORM_PARAMS = ('csrfmiddlewaretoken',)
...@@ -175,4 +170,4 @@ class MultiPartParser(BaseParser, DataFlatener): ...@@ -175,4 +170,4 @@ class MultiPartParser(BaseParser, DataFlatener):
if key in self.RESERVED_FORM_PARAMS: if key in self.RESERVED_FORM_PARAMS:
data.pop(key) data.pop(key)
return MultipartData(data, files) return (data, files)
...@@ -150,7 +150,7 @@ class DocumentingTemplateRenderer(BaseRenderer): ...@@ -150,7 +150,7 @@ class DocumentingTemplateRenderer(BaseRenderer):
# If we're not using content overloading there's no point in supplying a generic form, # If we're not using content overloading there's no point in supplying a generic form,
# as the view won't treat the form's value as the content of the request. # as the view won't treat the form's value as the content of the request.
if not getattr(view, 'USE_FORM_OVERLOADING', False): if not getattr(view, '_USE_FORM_OVERLOADING', False):
return None return None
# NB. http://jacobian.org/writing/dynamic-form-generation/ # NB. http://jacobian.org/writing/dynamic-form-generation/
...@@ -164,14 +164,14 @@ class DocumentingTemplateRenderer(BaseRenderer): ...@@ -164,14 +164,14 @@ class DocumentingTemplateRenderer(BaseRenderer):
contenttype_choices = [(media_type, media_type) for media_type in view.parsed_media_types] contenttype_choices = [(media_type, media_type) for media_type in view.parsed_media_types]
initial_contenttype = view.default_parser.media_type initial_contenttype = view.default_parser.media_type
self.fields[view.CONTENTTYPE_PARAM] = forms.ChoiceField(label='Content Type', self.fields[view._CONTENTTYPE_PARAM] = forms.ChoiceField(label='Content Type',
choices=contenttype_choices, choices=contenttype_choices,
initial=initial_contenttype) initial=initial_contenttype)
self.fields[view.CONTENT_PARAM] = forms.CharField(label='Content', self.fields[view._CONTENT_PARAM] = forms.CharField(label='Content',
widget=forms.Textarea) widget=forms.Textarea)
# If either of these reserved parameters are turned off then content tunneling is not possible # If either of these reserved parameters are turned off then content tunneling is not possible
if self.view.CONTENTTYPE_PARAM is None or self.view.CONTENT_PARAM is None: if self.view._CONTENTTYPE_PARAM is None or self.view._CONTENT_PARAM is None:
return None return None
# Okey doke, let's do it # Okey doke, let's do it
......
...@@ -14,14 +14,14 @@ class TestContentParsing(TestCase): ...@@ -14,14 +14,14 @@ class TestContentParsing(TestCase):
def ensure_determines_no_content_GET(self, view): def ensure_determines_no_content_GET(self, view):
"""Ensure view.RAW_CONTENT returns None for GET request with no content.""" """Ensure view.RAW_CONTENT returns None for GET request with no content."""
view.request = self.req.get('/') view.request = self.req.get('/')
self.assertEqual(view.RAW_CONTENT, None) self.assertEqual(view.DATA, None)
def ensure_determines_form_content_POST(self, view): def ensure_determines_form_content_POST(self, view):
"""Ensure view.RAW_CONTENT returns content for POST request with form content.""" """Ensure view.RAW_CONTENT returns content for POST request with form content."""
form_data = {'qwerty': 'uiop'} form_data = {'qwerty': 'uiop'}
view.parsers = (FormParser, MultiPartParser) view.parsers = (FormParser, MultiPartParser)
view.request = self.req.post('/', data=form_data) view.request = self.req.post('/', data=form_data)
self.assertEqual(view.RAW_CONTENT, form_data) self.assertEqual(view.DATA, form_data)
def ensure_determines_non_form_content_POST(self, view): def ensure_determines_non_form_content_POST(self, view):
"""Ensure view.RAW_CONTENT returns content for POST request with non-form content.""" """Ensure view.RAW_CONTENT returns content for POST request with non-form content."""
...@@ -29,14 +29,14 @@ class TestContentParsing(TestCase): ...@@ -29,14 +29,14 @@ class TestContentParsing(TestCase):
content_type = 'text/plain' content_type = 'text/plain'
view.parsers = (PlainTextParser,) view.parsers = (PlainTextParser,)
view.request = self.req.post('/', content, content_type=content_type) view.request = self.req.post('/', content, content_type=content_type)
self.assertEqual(view.RAW_CONTENT, content) self.assertEqual(view.DATA, content)
def ensure_determines_form_content_PUT(self, view): def ensure_determines_form_content_PUT(self, view):
"""Ensure view.RAW_CONTENT returns content for PUT request with form content.""" """Ensure view.RAW_CONTENT returns content for PUT request with form content."""
form_data = {'qwerty': 'uiop'} form_data = {'qwerty': 'uiop'}
view.parsers = (FormParser, MultiPartParser) view.parsers = (FormParser, MultiPartParser)
view.request = self.req.put('/', data=form_data) view.request = self.req.put('/', data=form_data)
self.assertEqual(view.RAW_CONTENT, form_data) self.assertEqual(view.DATA, form_data)
def ensure_determines_non_form_content_PUT(self, view): def ensure_determines_non_form_content_PUT(self, view):
"""Ensure view.RAW_CONTENT returns content for PUT request with non-form content.""" """Ensure view.RAW_CONTENT returns content for PUT request with non-form content."""
...@@ -44,36 +44,36 @@ class TestContentParsing(TestCase): ...@@ -44,36 +44,36 @@ class TestContentParsing(TestCase):
content_type = 'text/plain' content_type = 'text/plain'
view.parsers = (PlainTextParser,) view.parsers = (PlainTextParser,)
view.request = self.req.post('/', content, content_type=content_type) view.request = self.req.post('/', content, content_type=content_type)
self.assertEqual(view.RAW_CONTENT, content) self.assertEqual(view.DATA, content)
def test_standard_behaviour_determines_no_content_GET(self): def test_standard_behaviour_determines_no_content_GET(self):
"""Ensure view.RAW_CONTENT returns None for GET request with no content.""" """Ensure view.DATA returns None for GET request with no content."""
self.ensure_determines_no_content_GET(RequestMixin()) self.ensure_determines_no_content_GET(RequestMixin())
def test_standard_behaviour_determines_form_content_POST(self): def test_standard_behaviour_determines_form_content_POST(self):
"""Ensure view.RAW_CONTENT returns content for POST request with form content.""" """Ensure view.DATA returns content for POST request with form content."""
self.ensure_determines_form_content_POST(RequestMixin()) self.ensure_determines_form_content_POST(RequestMixin())
def test_standard_behaviour_determines_non_form_content_POST(self): def test_standard_behaviour_determines_non_form_content_POST(self):
"""Ensure view.RAW_CONTENT returns content for POST request with non-form content.""" """Ensure view.DATA returns content for POST request with non-form content."""
self.ensure_determines_non_form_content_POST(RequestMixin()) self.ensure_determines_non_form_content_POST(RequestMixin())
def test_standard_behaviour_determines_form_content_PUT(self): def test_standard_behaviour_determines_form_content_PUT(self):
"""Ensure view.RAW_CONTENT returns content for PUT request with form content.""" """Ensure view.DATA returns content for PUT request with form content."""
self.ensure_determines_form_content_PUT(RequestMixin()) self.ensure_determines_form_content_PUT(RequestMixin())
def test_standard_behaviour_determines_non_form_content_PUT(self): def test_standard_behaviour_determines_non_form_content_PUT(self):
"""Ensure view.RAW_CONTENT returns content for PUT request with non-form content.""" """Ensure view.DATA returns content for PUT request with non-form content."""
self.ensure_determines_non_form_content_PUT(RequestMixin()) self.ensure_determines_non_form_content_PUT(RequestMixin())
def test_overloaded_behaviour_allows_content_tunnelling(self): def test_overloaded_behaviour_allows_content_tunnelling(self):
"""Ensure request.RAW_CONTENT returns content for overloaded POST request""" """Ensure request.DATA returns content for overloaded POST request"""
content = 'qwerty' content = 'qwerty'
content_type = 'text/plain' content_type = 'text/plain'
view = RequestMixin() view = RequestMixin()
form_data = {view.CONTENT_PARAM: content, form_data = {view._CONTENT_PARAM: content,
view.CONTENTTYPE_PARAM: content_type} view._CONTENTTYPE_PARAM: content_type}
view.request = self.req.post('/', form_data) view.request = self.req.post('/', form_data)
view.parsers = (PlainTextParser,) view.parsers = (PlainTextParser,)
view.perform_form_overloading() view._perform_form_overloading()
self.assertEqual(view.RAW_CONTENT, content) self.assertEqual(view.DATA, content)
...@@ -2,6 +2,7 @@ from django.test import TestCase ...@@ -2,6 +2,7 @@ from django.test import TestCase
from django import forms from django import forms
from djangorestframework.compat import RequestFactory from djangorestframework.compat import RequestFactory
from djangorestframework.views import BaseView from djangorestframework.views import BaseView
from djangorestframework.resource import FormResource
import StringIO import StringIO
class UploadFilesTests(TestCase): class UploadFilesTests(TestCase):
...@@ -15,9 +16,12 @@ class UploadFilesTests(TestCase): ...@@ -15,9 +16,12 @@ class UploadFilesTests(TestCase):
class FileForm(forms.Form): class FileForm(forms.Form):
file = forms.FileField file = forms.FileField
class MockResource(FormResource):
form = FileForm
class MockView(BaseView): class MockView(BaseView):
permissions = () permissions = ()
form = FileForm resource = MockResource
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
return {'FILE_NAME': self.CONTENT['file'].name, return {'FILE_NAME': self.CONTENT['file'].name,
......
...@@ -22,6 +22,6 @@ class TestMethodOverloading(TestCase): ...@@ -22,6 +22,6 @@ class TestMethodOverloading(TestCase):
def test_overloaded_POST_behaviour_determines_overloaded_method(self): def test_overloaded_POST_behaviour_determines_overloaded_method(self):
"""POST requests can be overloaded to another method by setting a reserved form field""" """POST requests can be overloaded to another method by setting a reserved form field"""
view = RequestMixin() view = RequestMixin()
view.request = self.req.post('/', {view.METHOD_PARAM: 'DELETE'}) view.request = self.req.post('/', {view._METHOD_PARAM: 'DELETE'})
view.perform_form_overloading() view._perform_form_overloading()
self.assertEqual(view.method, 'DELETE') self.assertEqual(view.method, 'DELETE')
...@@ -24,7 +24,8 @@ Here is some example data, which would eventually be sent along with a post requ ...@@ -24,7 +24,8 @@ Here is some example data, which would eventually be sent along with a post requ
Default behaviour for :class:`parsers.FormParser`, is to return a single value for each parameter : Default behaviour for :class:`parsers.FormParser`, is to return a single value for each parameter :
>>> FormParser(some_view).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': 'blo1'} >>> (data, files) = FormParser(some_view).parse(StringIO(inpt))
>>> data == {'key1': 'bla1', 'key2': 'blo1'}
True True
However, you can customize this behaviour by subclassing :class:`parsers.FormParser`, and overriding :meth:`parsers.FormParser.is_a_list` : However, you can customize this behaviour by subclassing :class:`parsers.FormParser`, and overriding :meth:`parsers.FormParser.is_a_list` :
...@@ -36,7 +37,8 @@ However, you can customize this behaviour by subclassing :class:`parsers.FormPar ...@@ -36,7 +37,8 @@ However, you can customize this behaviour by subclassing :class:`parsers.FormPar
This new parser only flattens the lists of parameters that contain a single value. This new parser only flattens the lists of parameters that contain a single value.
>>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': ['blo1', 'blo2']} >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
>>> data == {'key1': 'bla1', 'key2': ['blo1', 'blo2']}
True True
.. note:: The same functionality is available for :class:`parsers.MultiPartParser`. .. note:: The same functionality is available for :class:`parsers.MultiPartParser`.
...@@ -61,7 +63,8 @@ The browsers usually strip the parameter completely. A hack to avoid this, and t ...@@ -61,7 +63,8 @@ The browsers usually strip the parameter completely. A hack to avoid this, and t
:class:`parsers.FormParser` strips the values ``_empty`` from all the lists. :class:`parsers.FormParser` strips the values ``_empty`` from all the lists.
>>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'blo1'} >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
>>> data == {'key1': 'blo1'}
True True
Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a list, so the parser just stripped it. Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a list, so the parser just stripped it.
...@@ -71,7 +74,8 @@ Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a lis ...@@ -71,7 +74,8 @@ Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a lis
... def is_a_list(self, key, val_list): ... def is_a_list(self, key, val_list):
... return key == 'key2' ... return key == 'key2'
... ...
>>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'blo1', 'key2': []} >>> (data, files) = MyFormParser(some_view).parse(StringIO(inpt))
>>> data == {'key1': 'blo1', 'key2': []}
True True
Better like that. Note that you can configure something else than ``_empty`` for the empty value by setting :attr:`parsers.FormParser.EMPTY_VALUE`. Better like that. Note that you can configure something else than ``_empty`` for the empty value by setting :attr:`parsers.FormParser.EMPTY_VALUE`.
...@@ -123,7 +127,7 @@ class TestMultiPartParser(TestCase): ...@@ -123,7 +127,7 @@ class TestMultiPartParser(TestCase):
post_req = RequestFactory().post('/', self.body, content_type=self.content_type) post_req = RequestFactory().post('/', self.body, content_type=self.content_type)
view = BaseView() view = BaseView()
view.request = post_req view.request = post_req
parsed = MultiPartParser(view).parse(StringIO(self.body)) (data, files) = MultiPartParser(view).parse(StringIO(self.body))
self.assertEqual(parsed['key1'], 'val1') self.assertEqual(data['key1'], 'val1')
self.assertEqual(parsed.FILES['file1'].read(), 'blablabla') self.assertEqual(files['file1'].read(), 'blablabla')
...@@ -17,7 +17,7 @@ __all__ = ( ...@@ -17,7 +17,7 @@ __all__ = (
class BaseView(RequestMixin, ResponseMixin, AuthMixin, View): class BaseView(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, View):
"""Handles incoming requests and maps them to REST operations. """Handles incoming requests and maps them to REST operations.
Performs request deserialization, response serialization, authentication and input validation.""" Performs request deserialization, response serialization, authentication and input validation."""
...@@ -46,9 +46,6 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View): ...@@ -46,9 +46,6 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View):
# List of all permissions that must be checked. # List of all permissions that must be checked.
permissions = ( permissions.FullAnonAccess, ) permissions = ( permissions.FullAnonAccess, )
# Optional form for input validation and presentation of HTML formatted responses.
form = None
# Allow name and description for the Resource to be set explicitly, # Allow name and description for the Resource to be set explicitly,
# overiding the default classname/docstring behaviour. # overiding the default classname/docstring behaviour.
# These are used for documentation in the standard html and text renderers. # These are used for documentation in the standard html and text renderers.
...@@ -60,22 +57,13 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View): ...@@ -60,22 +57,13 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View):
return [method.upper() for method in self.http_method_names if hasattr(self, method)] return [method.upper() for method in self.http_method_names if hasattr(self, method)]
def http_method_not_allowed(self, request, *args, **kwargs): def http_method_not_allowed(self, request, *args, **kwargs):
"""Return an HTTP 405 error if an operation is called which does not have a handler method.""" """
Return an HTTP 405 error if an operation is called which does not have a handler method.
"""
raise ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED, raise ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED,
{'detail': 'Method \'%s\' not allowed on this resource.' % self.method}) {'detail': 'Method \'%s\' not allowed on this resource.' % self.method})
def cleanup_response(self, data):
"""Perform any resource-specific data filtering prior to the standard HTTP
content-type serialization.
Eg filter complex objects that cannot be serialized by json/xml/etc into basic objects that can.
TODO: This is going to be removed. I think that the 'fields' behaviour is going to move into
the RendererMixin and Renderer classes."""
return data
# Note: session based authentication is explicitly CSRF validated, # Note: session based authentication is explicitly CSRF validated,
# all other authentication is CSRF exempt. # all other authentication is CSRF exempt.
@csrf_exempt @csrf_exempt
...@@ -92,7 +80,7 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View): ...@@ -92,7 +80,7 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View):
try: try:
# If using a form POST with '_method'/'_content'/'_content_type' overrides, then alter # If using a form POST with '_method'/'_content'/'_content_type' overrides, then alter
# self.method, self.content_type, self.RAW_CONTENT & self.CONTENT appropriately. # self.method, self.content_type, self.RAW_CONTENT & self.CONTENT appropriately.
self.perform_form_overloading() self._perform_form_overloading()
# Authenticate and check request is has the relevant permissions # Authenticate and check request is has the relevant permissions
self._check_permissions() self._check_permissions()
...@@ -114,13 +102,14 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View): ...@@ -114,13 +102,14 @@ class BaseView(RequestMixin, ResponseMixin, AuthMixin, View):
response = Response(status.HTTP_204_NO_CONTENT) response = Response(status.HTTP_204_NO_CONTENT)
# Pre-serialize filtering (eg filter complex objects into natively serializable types) # Pre-serialize filtering (eg filter complex objects into natively serializable types)
response.cleaned_content = self.resource.object_to_serializable(response.raw_content) response.cleaned_content = self.object_to_data(response.raw_content)
except ErrorResponse, exc: except ErrorResponse, exc:
response = exc.response response = exc.response
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
raise
# Always add these headers. # Always add these headers.
# #
......
"""The root view for the examples provided with Django REST framework""" """The root view for the examples provided with Django REST framework"""
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from djangorestframework.resource import Resource from djangorestframework.views import BaseView
class Sandbox(Resource): class Sandbox(BaseView):
"""This is the sandbox for the examples provided with [Django REST framework](http://django-rest-framework.org). """This is the sandbox for the examples provided with [Django REST framework](http://django-rest-framework.org).
These examples are provided to help you get a better idea of the some of the features of RESTful APIs created using the framework. These examples are provided to help you get a better idea of the some of the features of RESTful APIs created using the framework.
......
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