mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-03 13:14:30 +03:00
Rename mixins into Mixin class, rename ResponseException to ErrorResponse, remove NoContent
This commit is contained in:
parent
a1ed565081
commit
349ffcaf5f
|
@ -4,11 +4,10 @@ by serializing the output along with documentation regarding the Resource, outpu
|
|||
and providing forms and links depending on the allowed methods, emitters and parsers on the Resource.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from django.template import RequestContext, loader
|
||||
from django import forms
|
||||
|
||||
from djangorestframework.response import NoContent, ResponseException
|
||||
from djangorestframework.response import ErrorResponse
|
||||
from djangorestframework.utils import dict2xml, url_resolves
|
||||
from djangorestframework.markdownwrapper import apply_markdown
|
||||
from djangorestframework.breadcrumbs import get_breadcrumbs
|
||||
|
@ -18,7 +17,6 @@ from djangorestframework import status
|
|||
from urllib import quote_plus
|
||||
import string
|
||||
import re
|
||||
from decimal import Decimal
|
||||
|
||||
try:
|
||||
import json
|
||||
|
@ -26,132 +24,9 @@ except ImportError:
|
|||
import simplejson as json
|
||||
|
||||
|
||||
_MSIE_USER_AGENT = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )')
|
||||
|
||||
|
||||
class EmitterMixin(object):
|
||||
"""Adds behaviour for pluggable Emitters to a :class:`.Resource` or Django :class:`View`. class.
|
||||
|
||||
Default behaviour is to use standard HTTP Accept header content negotiation.
|
||||
Also supports overidding the content type by specifying an _accept= parameter in the URL.
|
||||
Ignores Accept headers from Internet Explorer user agents and uses a sensible browser Accept header instead."""
|
||||
|
||||
ACCEPT_QUERY_PARAM = '_accept' # Allow override of Accept header in URL query params
|
||||
REWRITE_IE_ACCEPT_HEADER = True
|
||||
|
||||
request = None
|
||||
response = None
|
||||
emitters = ()
|
||||
|
||||
def emit(self, response):
|
||||
"""Takes a :class:`Response` object and returns a Django :class:`HttpResponse`."""
|
||||
self.response = response
|
||||
|
||||
try:
|
||||
emitter = self._determine_emitter(self.request)
|
||||
except ResponseException, exc:
|
||||
emitter = self.default_emitter
|
||||
response = exc.response
|
||||
|
||||
# Serialize the response content
|
||||
if response.has_content_body:
|
||||
content = emitter(self).emit(output=response.cleaned_content)
|
||||
else:
|
||||
content = emitter(self).emit()
|
||||
|
||||
# Munge DELETE Response code to allow us to return content
|
||||
# (Do this *after* we've rendered the template so that we include the normal deletion response code in the output)
|
||||
if response.status == 204:
|
||||
response.status = 200
|
||||
|
||||
# Build the HTTP Response
|
||||
# TODO: Check if emitter.mimetype is underspecified, or if a content-type header has been set
|
||||
resp = HttpResponse(content, mimetype=emitter.media_type, status=response.status)
|
||||
for (key, val) in response.headers.items():
|
||||
resp[key] = val
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
def _determine_emitter(self, request):
|
||||
"""Return the appropriate emitter for the output, given the client's 'Accept' header,
|
||||
and the content types that this Resource knows how to serve.
|
||||
|
||||
See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html"""
|
||||
|
||||
if self.ACCEPT_QUERY_PARAM and request.GET.get(self.ACCEPT_QUERY_PARAM, None):
|
||||
# Use _accept parameter override
|
||||
accept_list = [request.GET.get(self.ACCEPT_QUERY_PARAM)]
|
||||
elif self.REWRITE_IE_ACCEPT_HEADER and request.META.has_key('HTTP_USER_AGENT') and _MSIE_USER_AGENT.match(request.META['HTTP_USER_AGENT']):
|
||||
accept_list = ['text/html', '*/*']
|
||||
elif request.META.has_key('HTTP_ACCEPT'):
|
||||
# Use standard HTTP Accept negotiation
|
||||
accept_list = request.META["HTTP_ACCEPT"].split(',')
|
||||
else:
|
||||
# No accept header specified
|
||||
return self.default_emitter
|
||||
|
||||
# Parse the accept header into a dict of {qvalue: set of media types}
|
||||
# We ignore mietype parameters
|
||||
accept_dict = {}
|
||||
for token in accept_list:
|
||||
components = token.split(';')
|
||||
mimetype = components[0].strip()
|
||||
qvalue = Decimal('1.0')
|
||||
|
||||
if len(components) > 1:
|
||||
# Parse items that have a qvalue eg text/html;q=0.9
|
||||
try:
|
||||
(q, num) = components[-1].split('=')
|
||||
if q == 'q':
|
||||
qvalue = Decimal(num)
|
||||
except:
|
||||
# Skip malformed entries
|
||||
continue
|
||||
|
||||
if accept_dict.has_key(qvalue):
|
||||
accept_dict[qvalue].add(mimetype)
|
||||
else:
|
||||
accept_dict[qvalue] = set((mimetype,))
|
||||
|
||||
# Convert to a list of sets ordered by qvalue (highest first)
|
||||
accept_sets = [accept_dict[qvalue] for qvalue in sorted(accept_dict.keys(), reverse=True)]
|
||||
|
||||
for accept_set in accept_sets:
|
||||
# Return any exact match
|
||||
for emitter in self.emitters:
|
||||
if emitter.media_type in accept_set:
|
||||
return emitter
|
||||
|
||||
# Return any subtype match
|
||||
for emitter in self.emitters:
|
||||
if emitter.media_type.split('/')[0] + '/*' in accept_set:
|
||||
return emitter
|
||||
|
||||
# Return default
|
||||
if '*/*' in accept_set:
|
||||
return self.default_emitter
|
||||
|
||||
|
||||
raise ResponseException(status.HTTP_406_NOT_ACCEPTABLE,
|
||||
{'detail': 'Could not statisfy the client\'s Accept header',
|
||||
'available_types': self.emitted_media_types})
|
||||
|
||||
@property
|
||||
def emitted_media_types(self):
|
||||
"""Return an list of all the media types that this resource can emit."""
|
||||
return [emitter.media_type for emitter in self.emitters]
|
||||
|
||||
@property
|
||||
def default_emitter(self):
|
||||
"""Return the resource's most prefered emitter.
|
||||
(This emitter is used if the client does not send and Accept: header, or sends Accept: */*)"""
|
||||
return self.emitters[0]
|
||||
|
||||
|
||||
|
||||
# TODO: Rename verbose to something more appropriate
|
||||
# TODO: NoContent could be handled more cleanly. It'd be nice if it was handled by default,
|
||||
# TODO: Maybe None could be handled more cleanly. It'd be nice if it was handled by default,
|
||||
# and only have an emitter output anything if it explicitly provides support for that.
|
||||
|
||||
class BaseEmitter(object):
|
||||
|
@ -162,10 +37,10 @@ class BaseEmitter(object):
|
|||
def __init__(self, resource):
|
||||
self.resource = resource
|
||||
|
||||
def emit(self, output=NoContent, verbose=False):
|
||||
def emit(self, output=None, verbose=False):
|
||||
"""By default emit simply returns the ouput as-is.
|
||||
Override this method to provide for other behaviour."""
|
||||
if output is NoContent:
|
||||
if output is None:
|
||||
return ''
|
||||
|
||||
return output
|
||||
|
@ -177,8 +52,8 @@ class TemplateEmitter(BaseEmitter):
|
|||
media_type = None
|
||||
template = None
|
||||
|
||||
def emit(self, output=NoContent, verbose=False):
|
||||
if output is NoContent:
|
||||
def emit(self, output=None, verbose=False):
|
||||
if output is None:
|
||||
return ''
|
||||
|
||||
context = RequestContext(self.request, output)
|
||||
|
@ -276,7 +151,7 @@ class DocumentingTemplateEmitter(BaseEmitter):
|
|||
return GenericContentForm(resource)
|
||||
|
||||
|
||||
def emit(self, output=NoContent):
|
||||
def emit(self, output=None):
|
||||
content = self._get_content(self.resource, self.resource.request, output)
|
||||
form_instance = self._get_form_instance(self.resource)
|
||||
|
||||
|
@ -324,8 +199,8 @@ class JSONEmitter(BaseEmitter):
|
|||
"""Emitter which serializes to JSON"""
|
||||
media_type = 'application/json'
|
||||
|
||||
def emit(self, output=NoContent, verbose=False):
|
||||
if output is NoContent:
|
||||
def emit(self, output=None, verbose=False):
|
||||
if output is None:
|
||||
return ''
|
||||
if verbose:
|
||||
return json.dumps(output, indent=4, sort_keys=True)
|
||||
|
@ -336,8 +211,8 @@ class XMLEmitter(BaseEmitter):
|
|||
"""Emitter which serializes to XML."""
|
||||
media_type = 'application/xml'
|
||||
|
||||
def emit(self, output=NoContent, verbose=False):
|
||||
if output is NoContent:
|
||||
def emit(self, output=None, verbose=False):
|
||||
if output is None:
|
||||
return ''
|
||||
return dict2xml(output)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.db.models import Model
|
|||
from django.db.models.query import QuerySet
|
||||
from django.db.models.fields.related import RelatedField
|
||||
|
||||
from djangorestframework.response import Response, ResponseException
|
||||
from djangorestframework.response import Response, ErrorResponse
|
||||
from djangorestframework.resource import Resource
|
||||
from djangorestframework import status, validators
|
||||
|
||||
|
@ -370,7 +370,7 @@ class ModelResource(Resource):
|
|||
# Otherwise assume the kwargs uniquely identify the model
|
||||
instance = self.model.objects.get(**kwargs)
|
||||
except self.model.DoesNotExist:
|
||||
raise ResponseException(status.HTTP_404_NOT_FOUND)
|
||||
raise ErrorResponse(status.HTTP_404_NOT_FOUND)
|
||||
|
||||
return instance
|
||||
|
||||
|
@ -402,7 +402,7 @@ class ModelResource(Resource):
|
|||
# Otherwise assume the kwargs uniquely identify the model
|
||||
instance = self.model.objects.get(**kwargs)
|
||||
except self.model.DoesNotExist:
|
||||
raise ResponseException(status.HTTP_404_NOT_FOUND, None, {})
|
||||
raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
|
||||
|
||||
instance.delete()
|
||||
return
|
||||
|
|
|
@ -9,7 +9,7 @@ We need a method to be able to:
|
|||
and multipart/form-data. (eg also handle multipart/json)
|
||||
"""
|
||||
from django.http.multipartparser import MultiPartParser as DjangoMPParser
|
||||
from djangorestframework.response import ResponseException
|
||||
from djangorestframework.response import ErrorResponse
|
||||
from djangorestframework import status
|
||||
from djangorestframework.utils import as_tuple
|
||||
from djangorestframework.mediatypes import MediaType
|
||||
|
@ -59,7 +59,7 @@ class JSONParser(BaseParser):
|
|||
try:
|
||||
return json.load(stream)
|
||||
except ValueError, exc:
|
||||
raise ResponseException(status.HTTP_400_BAD_REQUEST, {'detail': 'JSON parse error - %s' % str(exc)})
|
||||
raise ErrorResponse(status.HTTP_400_BAD_REQUEST, {'detail': 'JSON parse error - %s' % str(exc)})
|
||||
|
||||
|
||||
class DataFlatener(object):
|
||||
|
|
|
@ -2,9 +2,8 @@ from django.core.urlresolvers import set_script_prefix
|
|||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from djangorestframework.compat import View
|
||||
from djangorestframework.emitters import EmitterMixin
|
||||
from djangorestframework.response import Response, ResponseException
|
||||
from djangorestframework.request import RequestMixin, AuthMixin
|
||||
from djangorestframework.response import Response, ErrorResponse
|
||||
from djangorestframework.mixins import RequestMixin, ResponseMixin, AuthMixin
|
||||
from djangorestframework import emitters, parsers, authenticators, validators, status
|
||||
|
||||
|
||||
|
@ -16,7 +15,7 @@ from djangorestframework import emitters, parsers, authenticators, validators, s
|
|||
__all__ = ['Resource']
|
||||
|
||||
|
||||
class Resource(EmitterMixin, AuthMixin, RequestMixin, View):
|
||||
class Resource(RequestMixin, ResponseMixin, AuthMixin, View):
|
||||
"""Handles incoming requests and maps them to REST operations,
|
||||
performing authentication, input deserialization, input validation, output serialization."""
|
||||
|
||||
|
@ -81,7 +80,7 @@ class Resource(EmitterMixin, AuthMixin, RequestMixin, View):
|
|||
def not_implemented(self, operation):
|
||||
"""Return an HTTP 500 server error if an operation is called which has been allowed by
|
||||
allowed_methods, but which has not been implemented."""
|
||||
raise ResponseException(status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
raise ErrorResponse(status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
{'detail': '%s operation on this resource has not been implemented' % (operation, )})
|
||||
|
||||
|
||||
|
@ -89,15 +88,15 @@ class Resource(EmitterMixin, AuthMixin, RequestMixin, View):
|
|||
"""Ensure the request method is permitted for this resource, raising a ResourceException if it is not."""
|
||||
|
||||
if not method in self.callmap.keys():
|
||||
raise ResponseException(status.HTTP_501_NOT_IMPLEMENTED,
|
||||
raise ErrorResponse(status.HTTP_501_NOT_IMPLEMENTED,
|
||||
{'detail': 'Unknown or unsupported method \'%s\'' % method})
|
||||
|
||||
if not method in self.allowed_methods:
|
||||
raise ResponseException(status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
raise ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
{'detail': 'Method \'%s\' not allowed on this resource.' % method})
|
||||
|
||||
if auth is None and not method in self.anon_allowed_methods:
|
||||
raise ResponseException(status.HTTP_403_FORBIDDEN,
|
||||
raise ErrorResponse(status.HTTP_403_FORBIDDEN,
|
||||
{'detail': 'You do not have permission to access this resource. ' +
|
||||
'You may need to login or otherwise authenticate the request.'})
|
||||
|
||||
|
@ -172,7 +171,7 @@ class Resource(EmitterMixin, AuthMixin, RequestMixin, View):
|
|||
# Pre-serialize filtering (eg filter complex objects into natively serializable types)
|
||||
response.cleaned_content = self.cleanup_response(response.raw_content)
|
||||
|
||||
except ResponseException, exc:
|
||||
except ErrorResponse, exc:
|
||||
response = exc.response
|
||||
|
||||
except:
|
||||
|
@ -183,8 +182,12 @@ class Resource(EmitterMixin, AuthMixin, RequestMixin, View):
|
|||
#
|
||||
# TODO - this isn't actually the correct way to set the vary header,
|
||||
# also it's currently sub-obtimal for HTTP caching - need to sort that out.
|
||||
response.headers['Allow'] = ', '.join(self.allowed_methods)
|
||||
response.headers['Vary'] = 'Authenticate, Accept'
|
||||
try:
|
||||
response.headers['Allow'] = ', '.join(self.allowed_methods)
|
||||
response.headers['Vary'] = 'Authenticate, Accept'
|
||||
|
||||
return self.emit(response)
|
||||
return self.emit(response)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
|
|
@ -1,24 +1,16 @@
|
|||
from django.core.handlers.wsgi import STATUS_CODE_TEXT
|
||||
|
||||
__all__ =['NoContent', 'Response', ]
|
||||
|
||||
|
||||
|
||||
class NoContent(object):
|
||||
"""Used to indicate no body in http response.
|
||||
(We cannot just use None, as that is a valid, serializable response object.)
|
||||
|
||||
TODO: On reflection I'm going to get rid of this and just not support serialized 'None' responses.
|
||||
"""
|
||||
pass
|
||||
__all__ =['Response', 'ErrorResponse']
|
||||
|
||||
# TODO: remove raw_content/cleaned_content and just use content?
|
||||
|
||||
class Response(object):
|
||||
def __init__(self, status=200, content=NoContent, headers={}):
|
||||
"""An HttpResponse that may include content that hasn't yet been serialized."""
|
||||
def __init__(self, status=200, content=None, headers={}):
|
||||
self.status = status
|
||||
self.has_content_body = not content is NoContent # TODO: remove and just use content
|
||||
self.raw_content = content # content prior to filtering - TODO: remove and just use content
|
||||
self.cleaned_content = content # content after filtering TODO: remove and just use content
|
||||
self.has_content_body = content is not None
|
||||
self.raw_content = content # content prior to filtering
|
||||
self.cleaned_content = content # content after filtering
|
||||
self.headers = headers
|
||||
|
||||
@property
|
||||
|
@ -28,6 +20,7 @@ class Response(object):
|
|||
return STATUS_CODE_TEXT.get(self.status, '')
|
||||
|
||||
|
||||
class ResponseException(BaseException):
|
||||
def __init__(self, status, content=NoContent, headers={}):
|
||||
class ErrorResponse(BaseException):
|
||||
"""An exception representing an HttpResponse that should be returned immediatley."""
|
||||
def __init__(self, status, content=None, headers={}):
|
||||
self.response = Response(status, content=content, headers=headers)
|
||||
|
|
|
@ -3,7 +3,7 @@ Tests for content parsing, and form-overloaded content parsing.
|
|||
"""
|
||||
from django.test import TestCase
|
||||
from djangorestframework.compat import RequestFactory
|
||||
from djangorestframework.request import RequestMixin
|
||||
from djangorestframework.mixins import RequestMixin
|
||||
from djangorestframework.parsers import FormParser, MultipartParser, PlainTextParser
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ from django.conf.urls.defaults import patterns, url
|
|||
from django import http
|
||||
from django.test import TestCase
|
||||
from djangorestframework.compat import View
|
||||
from djangorestframework.emitters import EmitterMixin, BaseEmitter
|
||||
from djangorestframework.emitters import BaseEmitter
|
||||
from djangorestframework.mixins import ResponseMixin
|
||||
from djangorestframework.response import Response
|
||||
|
||||
DUMMYSTATUS = 200
|
||||
|
@ -11,7 +12,7 @@ DUMMYCONTENT = 'dummycontent'
|
|||
EMITTER_A_SERIALIZER = lambda x: 'Emitter A: %s' % x
|
||||
EMITTER_B_SERIALIZER = lambda x: 'Emitter B: %s' % x
|
||||
|
||||
class MockView(EmitterMixin, View):
|
||||
class MockView(ResponseMixin, View):
|
||||
def get(self, request):
|
||||
response = Response(DUMMYSTATUS, DUMMYCONTENT)
|
||||
return self.emit(response)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.test import TestCase
|
||||
from djangorestframework.compat import RequestFactory
|
||||
from djangorestframework.request import RequestMixin
|
||||
from djangorestframework.mixins import RequestMixin
|
||||
|
||||
|
||||
class TestMethodOverloading(TestCase):
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.db import models
|
|||
from django.test import TestCase
|
||||
from djangorestframework.compat import RequestFactory
|
||||
from djangorestframework.validators import BaseValidator, FormValidator, ModelFormValidator
|
||||
from djangorestframework.response import ResponseException
|
||||
from djangorestframework.response import ErrorResponse
|
||||
|
||||
|
||||
class TestValidatorMixinInterfaces(TestCase):
|
||||
|
@ -81,7 +81,7 @@ class TestNonFieldErrors(TestCase):
|
|||
content = {'field1': 'example1', 'field2': 'example2'}
|
||||
try:
|
||||
FormValidator(view).validate(content)
|
||||
except ResponseException, exc:
|
||||
except ErrorResponse, exc:
|
||||
self.assertEqual(exc.response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
|
||||
else:
|
||||
self.fail('ResourceException was not raised') #pragma: no cover
|
||||
|
@ -115,14 +115,14 @@ class TestFormValidation(TestCase):
|
|||
def validation_failure_raises_response_exception(self, validator):
|
||||
"""If form validation fails a ResourceException 400 (Bad Request) should be raised."""
|
||||
content = {}
|
||||
self.assertRaises(ResponseException, validator.validate, content)
|
||||
self.assertRaises(ErrorResponse, validator.validate, content)
|
||||
|
||||
def validation_does_not_allow_extra_fields_by_default(self, validator):
|
||||
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
|
||||
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
|
||||
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
||||
self.assertRaises(ResponseException, validator.validate, content)
|
||||
self.assertRaises(ErrorResponse, validator.validate, content)
|
||||
|
||||
def validation_allows_extra_fields_if_explicitly_set(self, validator):
|
||||
"""If we include an allowed_extra_fields paramater on _validate, then allow fields with those names."""
|
||||
|
@ -139,7 +139,7 @@ class TestFormValidation(TestCase):
|
|||
content = {}
|
||||
try:
|
||||
validator.validate(content)
|
||||
except ResponseException, exc:
|
||||
except ErrorResponse, exc:
|
||||
self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.']}})
|
||||
else:
|
||||
self.fail('ResourceException was not raised') #pragma: no cover
|
||||
|
@ -149,7 +149,7 @@ class TestFormValidation(TestCase):
|
|||
content = {'qwerty': ''}
|
||||
try:
|
||||
validator.validate(content)
|
||||
except ResponseException, exc:
|
||||
except ErrorResponse, exc:
|
||||
self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.']}})
|
||||
else:
|
||||
self.fail('ResourceException was not raised') #pragma: no cover
|
||||
|
@ -159,7 +159,7 @@ class TestFormValidation(TestCase):
|
|||
content = {'qwerty': 'uiop', 'extra': 'extra'}
|
||||
try:
|
||||
validator.validate(content)
|
||||
except ResponseException, exc:
|
||||
except ErrorResponse, exc:
|
||||
self.assertEqual(exc.response.raw_content, {'field-errors': {'extra': ['This field does not exist.']}})
|
||||
else:
|
||||
self.fail('ResourceException was not raised') #pragma: no cover
|
||||
|
@ -169,7 +169,7 @@ class TestFormValidation(TestCase):
|
|||
content = {'qwerty': '', 'extra': 'extra'}
|
||||
try:
|
||||
validator.validate(content)
|
||||
except ResponseException, exc:
|
||||
except ErrorResponse, exc:
|
||||
self.assertEqual(exc.response.raw_content, {'field-errors': {'qwerty': ['This field is required.'],
|
||||
'extra': ['This field does not exist.']}})
|
||||
else:
|
||||
|
@ -286,14 +286,14 @@ class TestModelFormValidator(TestCase):
|
|||
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
|
||||
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||
content = {'qwerty': 'example', 'uiop':'example', 'readonly': 'read only', 'extra': 'extra'}
|
||||
self.assertRaises(ResponseException, self.validator.validate, content)
|
||||
self.assertRaises(ErrorResponse, self.validator.validate, content)
|
||||
|
||||
def test_validate_requires_fields_on_model_forms(self):
|
||||
"""If some (otherwise valid) content includes fields that are not in the form then validation should fail.
|
||||
It might be okay on normal form submission, but for Web APIs we oughta get strict, as it'll help show up
|
||||
broken clients more easily (eg submitting content with a misnamed field)"""
|
||||
content = {'readonly': 'read only'}
|
||||
self.assertRaises(ResponseException, self.validator.validate, content)
|
||||
self.assertRaises(ErrorResponse, self.validator.validate, content)
|
||||
|
||||
def test_validate_does_not_require_blankable_fields_on_model_forms(self):
|
||||
"""Test standard ModelForm validation behaviour - fields with blank=True are not required."""
|
||||
|
|
|
@ -14,6 +14,7 @@ except ImportError:
|
|||
# """Adds the ADMIN_MEDIA_PREFIX to the request context."""
|
||||
# return {'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX}
|
||||
|
||||
MSIE_USER_AGENT_REGEX = re.compile(r'^Mozilla/[0-9]+\.[0-9]+ \([^)]*; MSIE [0-9]+\.[0-9]+[a-z]?;[^)]*\)(?!.* Opera )')
|
||||
|
||||
def as_tuple(obj):
|
||||
"""Given obj return a tuple"""
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""Mixin classes that provide a validate(content) function to validate and cleanup request content"""
|
||||
from django import forms
|
||||
from django.db import models
|
||||
from djangorestframework.response import ResponseException
|
||||
from djangorestframework.response import ErrorResponse
|
||||
from djangorestframework.utils import as_tuple
|
||||
|
||||
|
||||
|
@ -13,7 +13,7 @@ class BaseValidator(object):
|
|||
|
||||
def validate(self, content):
|
||||
"""Given some content as input return some cleaned, validated content.
|
||||
Typically raises a ResponseException with status code 400 (Bad Request) on failure.
|
||||
Typically raises a ErrorResponse with status code 400 (Bad Request) on failure.
|
||||
|
||||
Must be overridden to be implemented."""
|
||||
raise NotImplementedError()
|
||||
|
@ -32,11 +32,11 @@ class FormValidator(BaseValidator):
|
|||
|
||||
def validate(self, content):
|
||||
"""Given some content as input return some cleaned, validated content.
|
||||
Raises a ResponseException with status code 400 (Bad Request) on failure.
|
||||
Raises a ErrorResponse with status code 400 (Bad Request) on failure.
|
||||
|
||||
Validation is standard form validation, with an additional constraint that no extra unknown fields may be supplied.
|
||||
|
||||
On failure the ResponseException content is a dict which may contain 'errors' and 'field-errors' keys.
|
||||
On failure the ErrorResponse content is a dict which may contain 'errors' and 'field-errors' keys.
|
||||
If the 'errors' key exists it is a list of strings of non-field errors.
|
||||
If the 'field-errors' key exists it is a dict of {field name as string: list of errors as strings}."""
|
||||
return self._validate(content)
|
||||
|
@ -97,7 +97,7 @@ class FormValidator(BaseValidator):
|
|||
detail[u'field-errors'] = field_errors
|
||||
|
||||
# Return HTTP 400 response (BAD REQUEST)
|
||||
raise ResponseException(400, detail)
|
||||
raise ErrorResponse(400, detail)
|
||||
|
||||
|
||||
def get_bound_form(self, content=None):
|
||||
|
@ -139,14 +139,14 @@ class ModelFormValidator(FormValidator):
|
|||
# TODO: be really strict on fields - check they match in the handler methods. (this isn't a validator thing tho.)
|
||||
def validate(self, content):
|
||||
"""Given some content as input return some cleaned, validated content.
|
||||
Raises a ResponseException with status code 400 (Bad Request) on failure.
|
||||
Raises a ErrorResponse with status code 400 (Bad Request) on failure.
|
||||
|
||||
Validation is standard form or model form validation,
|
||||
with an additional constraint that no extra unknown fields may be supplied,
|
||||
and that all fields specified by the fields class attribute must be supplied,
|
||||
even if they are not validated by the form/model form.
|
||||
|
||||
On failure the ResponseException content is a dict which may contain 'errors' and 'field-errors' keys.
|
||||
On failure the ErrorResponse content is a dict which may contain 'errors' and 'field-errors' keys.
|
||||
If the 'errors' key exists it is a list of strings of non-field errors.
|
||||
If the 'field-errors' key exists it is a dict of {field name as string: list of errors as strings}."""
|
||||
return self._validate(content, allowed_extra_fields=self._property_fields_set)
|
||||
|
|
Loading…
Reference in New Issue
Block a user