Drop out resources & mixins

This commit is contained in:
Tom Christie 2012-08-24 22:11:00 +01:00
parent 87b363f7bc
commit aed26b218e
12 changed files with 874 additions and 1991 deletions

View File

@ -1,388 +0,0 @@
"""
The :mod:`mixins` module provides a set of reusable `mixin`
classes that can be added to a `View`.
"""
from django.core.paginator import Paginator
from django.db.models.fields.related import ForeignKey
from urlobject import URLObject
from djangorestframework import status
from djangorestframework.renderers import BaseRenderer
from djangorestframework.resources import Resource, FormResource, ModelResource
from djangorestframework.response import Response, ImmediateResponse
__all__ = (
# Base behavior mixins
'ResourceMixin',
# Model behavior mixins
'ReadModelMixin',
'CreateModelMixin',
'UpdateModelMixin',
'DeleteModelMixin',
'ListModelMixin',
'PaginatorMixin'
)
########## Resource Mixin ##########
class ResourceMixin(object):
"""
Provides request validation and response filtering behavior.
Should be a class as described in the :mod:`resources` module.
The :obj:`resource` is an object that maps a view onto it's representation on the server.
It provides validation on the content of incoming requests,
and filters the object representation into a serializable object for the response.
"""
resource = None
@property
def CONTENT(self):
"""
Returns the cleaned, validated request content.
May raise an :class:`response.ImmediateResponse` with status code 400 (Bad Request).
"""
if not hasattr(self, '_content'):
self._content = self.validate_request(self.request.DATA, self.request.FILES)
return self._content
@property
def PARAMS(self):
"""
Returns the cleaned, validated query parameters.
May raise an :class:`response.ImmediateResponse` with status code 400 (Bad Request).
"""
return self.validate_request(self.request.GET)
@property
def _resource(self):
if self.resource:
return self.resource(self)
elif getattr(self, 'model', None):
return ModelResource(self)
elif getattr(self, 'form', None):
return FormResource(self)
elif getattr(self, '%s_form' % self.request.method.lower(), None):
return FormResource(self)
return Resource(self)
def validate_request(self, data, files=None):
"""
Given the request *data* and optional *files*, return the cleaned, validated content.
May raise an :class:`response.ImmediateResponse` with status code 400 (Bad Request) on failure.
"""
return self._resource.validate_request(data, files)
def filter_response(self, obj):
"""
Given the response content, filter it into a serializable object.
"""
return self._resource.filter_response(obj)
def get_bound_form(self, content=None, method=None):
if hasattr(self._resource, 'get_bound_form'):
return self._resource.get_bound_form(content, method=method)
else:
return None
########## Model Mixins ##########
class ModelMixin(object):
""" Implements mechanisms used by other classes (like *ModelMixin group) to
define a query that represents Model instances the Mixin is working with.
If a *ModelMixin is going to retrive an instance (or queryset) using args and kwargs
passed by as URL arguments, it should provied arguments to objects.get and objects.filter
methods wrapped in by `build_query`
If a *ModelMixin is going to create/update an instance get_instance_data
handles the instance data creation/preaparation.
"""
queryset = None
def get_query_kwargs(self, *args, **kwargs):
"""
Return a dict of kwargs that will be used to build the
model instance retrieval or to filter querysets.
"""
kwargs = dict(kwargs)
# If the URLconf includes a .(?P<format>\w+) pattern to match against
# a .json, .xml suffix, then drop the 'format' kwarg before
# constructing the query.
if BaseRenderer._FORMAT_QUERY_PARAM in kwargs:
del kwargs[BaseRenderer._FORMAT_QUERY_PARAM]
return kwargs
def get_instance_data(self, model, content, **kwargs):
"""
Returns the dict with the data for model instance creation/update.
Arguments:
- model: model class (django.db.models.Model subclass) to work with
- content: a dictionary with instance data
- kwargs: a dict of URL provided keyword arguments
The create/update queries are created basicly with the contet provided
with POST/PUT HTML methods and kwargs passed in the URL. This methods
simply merges the URL data and the content preaparing the ready-to-use
data dictionary.
"""
tmp = dict(kwargs)
for field in model._meta.fields:
if isinstance(field, ForeignKey) and field.name in tmp:
# translate 'related_field' kwargs into 'related_field_id'
tmp[field.name + '_id'] = tmp[field.name]
del tmp[field.name]
all_kw_args = dict(content.items() + tmp.items())
return all_kw_args
def get_instance(self, **kwargs):
"""
Get a model instance for read/update/delete requests.
"""
return self.get_queryset().get(**kwargs)
def get_queryset(self):
"""
Return the queryset for this view.
"""
return getattr(self.resource, 'queryset',
self.resource.model.objects.all())
def get_ordering(self):
"""
Return the ordering for this view.
"""
return getattr(self.resource, 'ordering', None)
class ReadModelMixin(ModelMixin):
"""
Behavior to read a `model` instance on GET requests
"""
def get(self, request, *args, **kwargs):
model = self.resource.model
query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
try:
self.model_instance = self.get_instance(**query_kwargs)
except model.DoesNotExist:
raise ImmediateResponse(status=status.HTTP_404_NOT_FOUND)
return Response(self.model_instance)
class CreateModelMixin(ModelMixin):
"""
Behavior to create a `model` instance on POST requests
"""
def post(self, request, *args, **kwargs):
model = self.resource.model
# Copy the dict to keep self.CONTENT intact
content = dict(self.CONTENT)
m2m_data = {}
for field in model._meta.many_to_many:
if field.name in content:
m2m_data[field.name] = (
field.m2m_reverse_field_name(), content[field.name]
)
del content[field.name]
instance = model(**self.get_instance_data(model, content, *args, **kwargs))
instance.save()
for fieldname in m2m_data:
manager = getattr(instance, fieldname)
if hasattr(manager, 'add'):
manager.add(*m2m_data[fieldname][1])
else:
data = {}
data[manager.source_field_name] = instance
for related_item in m2m_data[fieldname][1]:
data[m2m_data[fieldname][0]] = related_item
manager.through(**data).save()
response = Response(instance, status=status.HTTP_201_CREATED)
# Set headers
if hasattr(self.resource, 'url'):
response['Location'] = self.resource(self).url(instance)
return response
class UpdateModelMixin(ModelMixin):
"""
Behavior to update a `model` instance on PUT requests
"""
def put(self, request, *args, **kwargs):
model = self.resource.model
query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
# TODO: update on the url of a non-existing resource url doesn't work
# correctly at the moment - will end up with a new url
try:
self.model_instance = self.get_instance(**query_kwargs)
for (key, val) in self.CONTENT.items():
setattr(self.model_instance, key, val)
except model.DoesNotExist:
self.model_instance = model(**self.get_instance_data(model, self.CONTENT, *args, **kwargs))
self.model_instance.save()
return Response(self.model_instance)
class DeleteModelMixin(ModelMixin):
"""
Behavior to delete a `model` instance on DELETE requests
"""
def delete(self, request, *args, **kwargs):
model = self.resource.model
query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
try:
instance = self.get_instance(**query_kwargs)
except model.DoesNotExist:
raise ImmediateResponse(status=status.HTTP_404_NOT_FOUND)
instance.delete()
return Response()
class ListModelMixin(ModelMixin):
"""
Behavior to list a set of `model` instances on GET requests
"""
def get(self, request, *args, **kwargs):
queryset = self.get_queryset()
ordering = self.get_ordering()
query_kwargs = self.get_query_kwargs(request, *args, **kwargs)
queryset = queryset.filter(**query_kwargs)
if ordering:
queryset = queryset.order_by(*ordering)
return Response(queryset)
########## Pagination Mixins ##########
class PaginatorMixin(object):
"""
Adds pagination support to GET requests
Obviously should only be used on lists :)
A default limit can be set by setting `limit` on the object. This will also
be used as the maximum if the client sets the `limit` GET param
"""
limit = 20
def get_limit(self):
"""
Helper method to determine what the `limit` should be
"""
try:
limit = int(self.request.GET.get('limit', self.limit))
return min(limit, self.limit)
except ValueError:
return self.limit
def url_with_page_number(self, page_number):
"""
Constructs a url used for getting the next/previous urls
"""
url = URLObject(self.request.get_full_path())
url = url.set_query_param('page', str(page_number))
limit = self.get_limit()
if limit != self.limit:
url = url.set_query_param('limit', str(limit))
return url
def next(self, page):
"""
Returns a url to the next page of results (if any)
"""
if not page.has_next():
return None
return self.url_with_page_number(page.next_page_number())
def previous(self, page):
""" Returns a url to the previous page of results (if any) """
if not page.has_previous():
return None
return self.url_with_page_number(page.previous_page_number())
def serialize_page_info(self, page):
"""
This is some useful information that is added to the response
"""
return {
'next': self.next(page),
'page': page.number,
'pages': page.paginator.num_pages,
'per_page': self.get_limit(),
'previous': self.previous(page),
'total': page.paginator.count,
}
def filter_response(self, obj):
"""
Given the response content, paginate and then serialize.
The response is modified to include to useful data relating to the number
of objects, number of pages, next/previous urls etc. etc.
The serialised objects are put into `results` on this new, modified
response
"""
# We don't want to paginate responses for anything other than GET requests
if self.request.method.upper() != 'GET':
return self._resource.filter_response(obj)
paginator = Paginator(obj, self.get_limit())
try:
page_num = int(self.request.GET.get('page', '1'))
except ValueError:
raise ImmediateResponse(
{'detail': 'That page contains no results'},
status=status.HTTP_404_NOT_FOUND)
if page_num not in paginator.page_range:
raise ImmediateResponse(
{'detail': 'That page contains no results'},
status=status.HTTP_404_NOT_FOUND)
page = paginator.page(page_num)
serialized_object_list = self._resource.filter_response(page.object_list)
serialized_page_info = self.serialize_page_info(page)
serialized_page_info['results'] = serialized_object_list
return serialized_page_info

View File

@ -18,7 +18,6 @@ __all__ = (
'IsUserOrIsAnonReadOnly',
'PerUserThrottling',
'PerViewThrottling',
'PerResourceThrottling'
)
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
@ -253,16 +252,3 @@ class PerViewThrottling(BaseThrottle):
def get_cache_key(self):
return 'throttle_view_%s' % self.view.__class__.__name__
class PerResourceThrottling(BaseThrottle):
"""
Limits the rate of API calls that may be used against all views on
a given resource.
The class name of the resource is used as a unique identifier to
throttle against.
"""
def get_cache_key(self):
return 'throttle_resource_%s' % self.view.resource.__class__.__name__

View File

@ -1,343 +0,0 @@
from django import forms
from djangorestframework.response import ImmediateResponse
from djangorestframework.serializer import Serializer
from djangorestframework.utils import as_tuple
class BaseResource(Serializer):
"""
Base class for all Resource classes, which simply defines the interface
they provide.
"""
fields = None
include = None
exclude = None
def __init__(self, view=None, depth=None, stack=[], **kwargs):
super(BaseResource, self).__init__(depth, stack, **kwargs)
self.view = view
self.request = getattr(view, 'request', None)
def validate_request(self, data, files=None):
"""
Given the request content return the cleaned, validated content.
Typically raises a :exc:`response.ImmediateResponse` with status code
400 (Bad Request) on failure.
"""
return data
def filter_response(self, obj):
"""
Given the response content, filter it into a serializable object.
"""
return self.serialize(obj)
class Resource(BaseResource):
"""
A Resource determines how a python object maps to some serializable data.
Objects that a resource can act on include plain Python object instances,
Django Models, and Django QuerySets.
"""
# The model attribute refers to the Django Model which this Resource maps to.
# (The Model's class, rather than an instance of the Model)
model = None
# By default the set of returned fields will be the set of:
#
# 0. All the fields on the model, excluding 'id'.
# 1. All the properties on the model.
# 2. The absolute_url of the model, if a get_absolute_url method exists for the model.
#
# If you wish to override this behaviour,
# you should explicitly set the fields attribute on your class.
fields = None
class FormResource(Resource):
"""
Resource class that uses forms for validation.
Also provides a :meth:`get_bound_form` method which may be used by some renderers.
On calling :meth:`validate_request` this validator may set a :attr:`bound_form_instance` attribute on the
view, which may be used by some renderers.
"""
form = None
"""
The :class:`Form` class that should be used for request validation.
This can be overridden by a :attr:`form` attribute on the :class:`views.View`.
"""
allow_unknown_form_fields = False
"""
Flag to check for unknown fields when validating a form. If set to false and
we receive request data that is not expected by the form it raises an
:exc:`response.ImmediateResponse` with status code 400. If set to true, only
expected fields are validated.
"""
def validate_request(self, data, files=None):
"""
Given some content as input return some cleaned, validated content.
Raises a :exc:`response.ImmediateResponse` 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
if :attr:`self.allow_unknown_form_fields` is ``False``.
On failure the :exc:`response.ImmediateResponse` content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
If the :obj:`'errors'` key exists it is a list of strings of non-field errors.
If the :obj:`'field-errors'` key exists it is a dict of ``{'field name as string': ['errors as strings', ...]}``.
"""
return self._validate(data, files)
def _validate(self, data, files, allowed_extra_fields=(), fake_data=None):
"""
Wrapped by validate to hide the extra flags that are used in the implementation.
allowed_extra_fields is a list of fields which are not defined by the form, but which we still
expect to see on the input.
fake_data is a string that should be used as an extra key, as a kludge to force .errors
to be populated when an empty dict is supplied in `data`
"""
# We'd like nice error messages even if no content is supplied.
# Typically if an empty dict is given to a form Django will
# return .is_valid() == False, but .errors == {}
#
# To get around this case we revalidate with some fake data.
if fake_data:
data[fake_data] = '_fake_data'
allowed_extra_fields = tuple(allowed_extra_fields) + ('_fake_data',)
bound_form = self.get_bound_form(data, files)
if bound_form is None:
return data
self.view.bound_form_instance = bound_form
data = data and data or {}
files = files and files or {}
seen_fields_set = set(data.keys())
form_fields_set = set(bound_form.fields.keys())
allowed_extra_fields_set = set(allowed_extra_fields)
# In addition to regular validation we also ensure no additional fields are being passed in...
unknown_fields = seen_fields_set - (form_fields_set | allowed_extra_fields_set)
unknown_fields = unknown_fields - set(('csrfmiddlewaretoken', '_accept', '_method')) # TODO: Ugh.
# Check using both regular validation, and our stricter no additional fields rule
if bound_form.is_valid() and (self.allow_unknown_form_fields or not unknown_fields):
# Validation succeeded...
cleaned_data = bound_form.cleaned_data
# Add in any extra fields to the cleaned content...
for key in (allowed_extra_fields_set & seen_fields_set) - set(cleaned_data.keys()):
cleaned_data[key] = data[key]
return cleaned_data
# Validation failed...
detail = {}
if not bound_form.errors and not unknown_fields:
# is_valid() was False, but errors was empty.
# If we havn't already done so attempt revalidation with some fake data
# to force django to give us an errors dict.
if fake_data is None:
return self._validate(data, files, allowed_extra_fields, '_fake_data')
# If we've already set fake_dict and we're still here, fallback gracefully.
detail = {u'errors': [u'No content was supplied.']}
else:
# Add any non-field errors
if bound_form.non_field_errors():
detail[u'errors'] = bound_form.non_field_errors()
# Add standard field errors
field_errors = dict(
(key, map(unicode, val))
for (key, val)
in bound_form.errors.iteritems()
if not key.startswith('__')
)
# Add any unknown field errors
for key in unknown_fields:
field_errors[key] = [u'This field does not exist.']
if field_errors:
detail[u'field_errors'] = field_errors
# Return HTTP 400 response (BAD REQUEST)
raise ImmediateResponse(detail, status=400)
def get_form_class(self, method=None):
"""
Returns the form class used to validate this resource.
"""
# A form on the view overrides a form on the resource.
form = getattr(self.view, 'form', None) or 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)
return form
def get_bound_form(self, data=None, files=None, method=None):
"""
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`.
"""
form = self.get_form_class(method)
if not form:
return None
if data is not None or files is not None:
return form(data, files)
return form()
class ModelResource(FormResource):
"""
Resource class that uses forms for validation and otherwise falls back to a model form if no form is set.
Also provides a :meth:`get_bound_form` method which may be used by some renderers.
"""
form = None
"""
The form class that should be used for request validation.
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:`views.View`.
"""
model = None
"""
The model class which this resource maps to.
This can be overridden by a :attr:`model` attribute on the :class:`views.View`.
"""
fields = None
"""
The list of fields to use on the output.
May be any of:
The name of a model field. To view nested resources, give the field as a tuple of ("fieldName", resource) where `resource` may be any of ModelResource reference, the name of a ModelResourc reference as a string or a tuple of strings representing fields on the nested model.
The name of an attribute on the model.
The name of an attribute on the resource.
The name of a method on the model, with a signature like ``func(self)``.
The name of a method on the resource, with a signature like ``func(self, instance)``.
"""
exclude = ('id', 'pk')
"""
The list of fields to exclude. This is only used if :attr:`fields` is not set.
"""
include = ()
"""
The list of extra fields to include. This is only used if :attr:`fields` is not set.
"""
def __init__(self, view=None, depth=None, stack=[], **kwargs):
"""
Allow :attr:`form` and :attr:`model` attributes set on the
:class:`View` to override the :attr:`form` and :attr:`model`
attributes set on the :class:`Resource`.
"""
super(ModelResource, self).__init__(view, depth, stack, **kwargs)
self.model = getattr(view, 'model', None) or self.model
def validate_request(self, data, files=None):
"""
Given some content as input return some cleaned, validated content.
Raises a :exc:`response.ImmediateResponse` 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 ImmediateResponse content is a dict which may contain :obj:`'errors'` and :obj:`'field-errors'` keys.
If the :obj:`'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(data, files, allowed_extra_fields=self._property_fields_set)
def get_bound_form(self, data=None, files=None, method=None):
"""
Given some content return a ``Form`` instance bound to that content.
If the :attr:`form` class attribute has been explicitly set then that class will be used
to create the Form, otherwise the model will be used to create a ModelForm.
"""
form = self.get_form_class(method)
if not form and self.model:
# Fall back to ModelForm which we create on the fly
class OnTheFlyModelForm(forms.ModelForm):
class Meta:
model = self.model
#fields = tuple(self._model_fields_set)
form = OnTheFlyModelForm
# Both form and model not set? Okay bruv, whatevs...
if not form:
return None
# Instantiate the ModelForm as appropriate
if data is not None or files is not None:
if issubclass(form, forms.ModelForm) and hasattr(self.view, 'model_instance'):
# Bound to an existing model instance
return form(data, files, instance=self.view.model_instance)
else:
return form(data, files)
return form()
@property
def _model_fields_set(self):
"""
Return a set containing the names of validated fields on the model.
"""
model_fields = set(field.name for field in self.model._meta.fields)
if self.fields:
return model_fields & set(as_tuple(self.fields))
return model_fields - set(as_tuple(self.exclude))
@property
def _property_fields_set(self):
"""
Returns a set containing the names of validated properties on the model.
"""
property_fields = set(attr for attr in dir(self.model) if
isinstance(getattr(self.model, attr, None), property)
and not attr.startswith('_'))
if self.fields:
return property_fields & set(as_tuple(self.fields))
return property_fields.union(set(as_tuple(self.include))) - set(as_tuple(self.exclude))

View File

@ -1,283 +0,0 @@
"""
Customizable serialization.
"""
from django.db import models
from django.db.models.query import QuerySet
from django.utils.encoding import smart_unicode, is_protected_type, smart_str
import inspect
import types
# We register serializer classes, so that we can refer to them by their
# class names, if there are cyclical serialization heirachys.
_serializers = {}
def _field_to_tuple(field):
"""
Convert an item in the `fields` attribute into a 2-tuple.
"""
if isinstance(field, (tuple, list)):
return (field[0], field[1])
return (field, None)
def _fields_to_list(fields):
"""
Return a list of field tuples.
"""
return [_field_to_tuple(field) for field in fields or ()]
class _SkipField(Exception):
"""
Signals that a serialized field should be ignored.
We use this mechanism as the default behavior for ensuring
that we don't infinitely recurse when dealing with nested data.
"""
pass
class _RegisterSerializer(type):
"""
Metaclass to register serializers.
"""
def __new__(cls, name, bases, attrs):
# Build the class and register it.
ret = super(_RegisterSerializer, cls).__new__(cls, name, bases, attrs)
_serializers[name] = ret
return ret
class Serializer(object):
"""
Converts python objects into plain old native types suitable for
serialization. In particular it handles models and querysets.
The output format is specified by setting a number of attributes
on the class.
You may also override any of the serialization methods, to provide
for more flexible behavior.
Valid output types include anything that may be directly rendered into
json, xml etc...
"""
__metaclass__ = _RegisterSerializer
fields = ()
"""
Specify the fields to be serialized on a model or dict.
Overrides `include` and `exclude`.
"""
include = ()
"""
Fields to add to the default set to be serialized on a model/dict.
"""
exclude = ()
"""
Fields to remove from the default set to be serialized on a model/dict.
"""
rename = {}
"""
A dict of key->name to use for the field keys.
"""
related_serializer = None
"""
The default serializer class to use for any related models.
"""
depth = None
"""
The maximum depth to serialize to, or `None`.
"""
def __init__(self, depth=None, stack=[], **kwargs):
if depth is not None:
self.depth = depth
self.stack = stack
def get_fields(self, obj):
fields = self.fields
# If `fields` is not set, we use the default fields and modify
# them with `include` and `exclude`
if not fields:
default = self.get_default_fields(obj)
include = self.include or ()
exclude = self.exclude or ()
fields = set(default + list(include)) - set(exclude)
return fields
def get_default_fields(self, obj):
"""
Return the default list of field names/keys for a model instance/dict.
These are used if `fields` is not given.
"""
if isinstance(obj, models.Model):
opts = obj._meta
return [field.name for field in opts.fields + opts.many_to_many]
else:
return obj.keys()
def get_related_serializer(self, info):
# If an element in `fields` is a 2-tuple of (str, tuple)
# then the second element of the tuple is the fields to
# set on the related serializer
if isinstance(info, (list, tuple)):
class OnTheFlySerializer(self.__class__):
fields = info
return OnTheFlySerializer
# If an element in `fields` is a 2-tuple of (str, Serializer)
# then the second element of the tuple is the Serializer
# class to use for that field.
elif isinstance(info, type) and issubclass(info, Serializer):
return info
# If an element in `fields` is a 2-tuple of (str, str)
# then the second element of the tuple is the name of the Serializer
# class to use for that field.
#
# Black magic to deal with cyclical Serializer dependancies.
# Similar to what Django does for cyclically related models.
elif isinstance(info, str) and info in _serializers:
return _serializers[info]
# Otherwise use `related_serializer` or fall back to `Serializer`
return getattr(self, 'related_serializer') or Serializer
def serialize_key(self, key):
"""
Keys serialize to their string value,
unless they exist in the `rename` dict.
"""
return self.rename.get(smart_str(key), smart_str(key))
def serialize_val(self, key, obj, related_info):
"""
Convert a model field or dict value into a serializable representation.
"""
related_serializer = self.get_related_serializer(related_info)
if self.depth is None:
depth = None
elif self.depth <= 0:
return self.serialize_max_depth(obj)
else:
depth = self.depth - 1
if any([obj is elem for elem in self.stack]):
return self.serialize_recursion(obj)
else:
stack = self.stack[:]
stack.append(obj)
return related_serializer(depth=depth, stack=stack).serialize(obj)
def serialize_max_depth(self, obj):
"""
Determine how objects should be serialized once `depth` is exceeded.
The default behavior is to ignore the field.
"""
raise _SkipField
def serialize_recursion(self, obj):
"""
Determine how objects should be serialized if recursion occurs.
The default behavior is to ignore the field.
"""
raise _SkipField
def serialize_model(self, instance):
"""
Given a model instance or dict, serialize it to a dict..
"""
data = {}
fields = self.get_fields(instance)
# serialize each required field
for fname, related_info in _fields_to_list(fields):
try:
# we first check for a method 'fname' on self,
# 'fname's signature must be 'def fname(self, instance)'
meth = getattr(self, fname, None)
if (inspect.ismethod(meth) and
len(inspect.getargspec(meth)[0]) == 2):
obj = meth(instance)
elif hasattr(instance, '__contains__') and fname in instance:
# then check for a key 'fname' on the instance
obj = instance[fname]
elif hasattr(instance, smart_str(fname)):
# finally check for an attribute 'fname' on the instance
obj = getattr(instance, fname)
else:
continue
key = self.serialize_key(fname)
val = self.serialize_val(fname, obj, related_info)
data[key] = val
except _SkipField:
pass
return data
def serialize_iter(self, obj):
"""
Convert iterables into a serializable representation.
"""
return [self.serialize(item) for item in obj]
def serialize_func(self, obj):
"""
Convert no-arg methods and functions into a serializable representation.
"""
return self.serialize(obj())
def serialize_manager(self, obj):
"""
Convert a model manager into a serializable representation.
"""
return self.serialize_iter(obj.all())
def serialize_fallback(self, obj):
"""
Convert any unhandled object into a serializable representation.
"""
return smart_unicode(obj, strings_only=True)
def serialize(self, obj):
"""
Convert any object into a serializable representation.
"""
if isinstance(obj, (dict, models.Model)):
# Model instances & dictionaries
return self.serialize_model(obj)
elif isinstance(obj, (tuple, list, set, QuerySet, types.GeneratorType)):
# basic iterables
return self.serialize_iter(obj)
elif isinstance(obj, models.Manager):
# Manager objects
return self.serialize_manager(obj)
elif inspect.isfunction(obj) and not inspect.getargspec(obj)[0]:
# function with no args
return self.serialize_func(obj)
elif inspect.ismethod(obj) and len(inspect.getargspec(obj)[0]) <= 1:
# bound method
return self.serialize_func(obj)
# Protected types are passed through as is.
# (i.e. Primitives like None, numbers, dates, and Decimals.)
if is_protected_type(obj):
return obj
# All other values are converted to string.
return self.serialize_fallback(obj)

View File

@ -1,35 +1,34 @@
from django.test import TestCase
from django import forms
# from django.test import TestCase
# from django import forms
from djangorestframework.compat import RequestFactory
from djangorestframework.views import View
from djangorestframework.resources import FormResource
from djangorestframework.response import Response
# from djangorestframework.compat import RequestFactory
# from djangorestframework.views import View
# from djangorestframework.response import Response
import StringIO
# import StringIO
class UploadFilesTests(TestCase):
"""Check uploading of files"""
def setUp(self):
self.factory = RequestFactory()
def test_upload_file(self):
# class UploadFilesTests(TestCase):
# """Check uploading of files"""
# def setUp(self):
# self.factory = RequestFactory()
class FileForm(forms.Form):
file = forms.FileField()
# def test_upload_file(self):
class MockView(View):
permissions = ()
form = FileForm
# class FileForm(forms.Form):
# file = forms.FileField()
def post(self, request, *args, **kwargs):
return Response({'FILE_NAME': self.CONTENT['file'].name,
'FILE_CONTENT': self.CONTENT['file'].read()})
# class MockView(View):
# permissions = ()
# form = FileForm
file = StringIO.StringIO('stuff')
file.name = 'stuff.txt'
request = self.factory.post('/', {'file': file})
view = MockView.as_view()
response = view(request)
self.assertEquals(response.raw_content, {"FILE_CONTENT": "stuff", "FILE_NAME": "stuff.txt"})
# def post(self, request, *args, **kwargs):
# return Response({'FILE_NAME': self.CONTENT['file'].name,
# 'FILE_CONTENT': self.CONTENT['file'].read()})
# file = StringIO.StringIO('stuff')
# file.name = 'stuff.txt'
# request = self.factory.post('/', {'file': file})
# view = MockView.as_view()
# response = view(request)
# self.assertEquals(response.raw_content, {"FILE_CONTENT": "stuff", "FILE_NAME": "stuff.txt"})

View File

@ -1,286 +1,285 @@
"""Tests for the mixin module"""
from django.test import TestCase
from django.utils import simplejson as json
from djangorestframework import status
from djangorestframework.compat import RequestFactory
from django.contrib.auth.models import Group, User
from djangorestframework.mixins import CreateModelMixin, PaginatorMixin, ReadModelMixin
from djangorestframework.resources import ModelResource
from djangorestframework.response import Response, ImmediateResponse
from djangorestframework.tests.models import CustomUser
from djangorestframework.tests.testcases import TestModelsTestCase
from djangorestframework.views import View
# """Tests for the mixin module"""
# from django.test import TestCase
# from djangorestframework import status
# from djangorestframework.compat import RequestFactory
# from django.contrib.auth.models import Group, User
# from djangorestframework.mixins import CreateModelMixin, PaginatorMixin, ReadModelMixin
# from djangorestframework.resources import ModelResource
# from djangorestframework.response import Response, ImmediateResponse
# from djangorestframework.tests.models import CustomUser
# from djangorestframework.tests.testcases import TestModelsTestCase
# from djangorestframework.views import View
class TestModelRead(TestModelsTestCase):
"""Tests on ReadModelMixin"""
# class TestModelRead(TestModelsTestCase):
# """Tests on ReadModelMixin"""
def setUp(self):
super(TestModelRead, self).setUp()
self.req = RequestFactory()
# def setUp(self):
# super(TestModelRead, self).setUp()
# self.req = RequestFactory()
def test_read(self):
Group.objects.create(name='other group')
group = Group.objects.create(name='my group')
# def test_read(self):
# Group.objects.create(name='other group')
# group = Group.objects.create(name='my group')
class GroupResource(ModelResource):
model = Group
# class GroupResource(ModelResource):
# model = Group
request = self.req.get('/groups')
mixin = ReadModelMixin()
mixin.resource = GroupResource
# request = self.req.get('/groups')
# mixin = ReadModelMixin()
# mixin.resource = GroupResource
response = mixin.get(request, id=group.id)
self.assertEquals(group.name, response.raw_content.name)
# response = mixin.get(request, id=group.id)
# self.assertEquals(group.name, response.raw_content.name)
def test_read_404(self):
class GroupResource(ModelResource):
model = Group
# def test_read_404(self):
# class GroupResource(ModelResource):
# model = Group
request = self.req.get('/groups')
mixin = ReadModelMixin()
mixin.resource = GroupResource
# request = self.req.get('/groups')
# mixin = ReadModelMixin()
# mixin.resource = GroupResource
self.assertRaises(ImmediateResponse, mixin.get, request, id=12345)
# self.assertRaises(ImmediateResponse, mixin.get, request, id=12345)
class TestModelCreation(TestModelsTestCase):
"""Tests on CreateModelMixin"""
# class TestModelCreation(TestModelsTestCase):
# """Tests on CreateModelMixin"""
def setUp(self):
super(TestModelsTestCase, self).setUp()
self.req = RequestFactory()
# def setUp(self):
# super(TestModelsTestCase, self).setUp()
# self.req = RequestFactory()
def test_creation(self):
self.assertEquals(0, Group.objects.count())
# def test_creation(self):
# self.assertEquals(0, Group.objects.count())
class GroupResource(ModelResource):
model = Group
# class GroupResource(ModelResource):
# model = Group
form_data = {'name': 'foo'}
request = self.req.post('/groups', data=form_data)
mixin = CreateModelMixin()
mixin.resource = GroupResource
mixin.CONTENT = form_data
# form_data = {'name': 'foo'}
# request = self.req.post('/groups', data=form_data)
# mixin = CreateModelMixin()
# mixin.resource = GroupResource
# mixin.CONTENT = form_data
response = mixin.post(request)
self.assertEquals(1, Group.objects.count())
self.assertEquals('foo', response.raw_content.name)
# response = mixin.post(request)
# self.assertEquals(1, Group.objects.count())
# self.assertEquals('foo', response.raw_content.name)
def test_creation_with_m2m_relation(self):
class UserResource(ModelResource):
model = User
# def test_creation_with_m2m_relation(self):
# class UserResource(ModelResource):
# model = User
def url(self, instance):
return "/users/%i" % instance.id
# def url(self, instance):
# return "/users/%i" % instance.id
group = Group(name='foo')
group.save()
# group = Group(name='foo')
# group.save()
form_data = {
'username': 'bar',
'password': 'baz',
'groups': [group.id]
}
request = self.req.post('/groups', data=form_data)
cleaned_data = dict(form_data)
cleaned_data['groups'] = [group]
mixin = CreateModelMixin()
mixin.resource = UserResource
mixin.CONTENT = cleaned_data
# form_data = {
# 'username': 'bar',
# 'password': 'baz',
# 'groups': [group.id]
# }
# request = self.req.post('/groups', data=form_data)
# cleaned_data = dict(form_data)
# cleaned_data['groups'] = [group]
# mixin = CreateModelMixin()
# mixin.resource = UserResource
# mixin.CONTENT = cleaned_data
response = mixin.post(request)
self.assertEquals(1, User.objects.count())
self.assertEquals(1, response.raw_content.groups.count())
self.assertEquals('foo', response.raw_content.groups.all()[0].name)
# response = mixin.post(request)
# self.assertEquals(1, User.objects.count())
# self.assertEquals(1, response.raw_content.groups.count())
# self.assertEquals('foo', response.raw_content.groups.all()[0].name)
def test_creation_with_m2m_relation_through(self):
"""
Tests creation where the m2m relation uses a through table
"""
class UserResource(ModelResource):
model = CustomUser
# def test_creation_with_m2m_relation_through(self):
# """
# Tests creation where the m2m relation uses a through table
# """
# class UserResource(ModelResource):
# model = CustomUser
def url(self, instance):
return "/customusers/%i" % instance.id
# def url(self, instance):
# return "/customusers/%i" % instance.id
form_data = {'username': 'bar0', 'groups': []}
request = self.req.post('/groups', data=form_data)
cleaned_data = dict(form_data)
cleaned_data['groups'] = []
mixin = CreateModelMixin()
mixin.resource = UserResource
mixin.CONTENT = cleaned_data
# form_data = {'username': 'bar0', 'groups': []}
# request = self.req.post('/groups', data=form_data)
# cleaned_data = dict(form_data)
# cleaned_data['groups'] = []
# mixin = CreateModelMixin()
# mixin.resource = UserResource
# mixin.CONTENT = cleaned_data
response = mixin.post(request)
self.assertEquals(1, CustomUser.objects.count())
self.assertEquals(0, response.raw_content.groups.count())
# response = mixin.post(request)
# self.assertEquals(1, CustomUser.objects.count())
# self.assertEquals(0, response.raw_content.groups.count())
group = Group(name='foo1')
group.save()
# group = Group(name='foo1')
# group.save()
form_data = {'username': 'bar1', 'groups': [group.id]}
request = self.req.post('/groups', data=form_data)
cleaned_data = dict(form_data)
cleaned_data['groups'] = [group]
mixin = CreateModelMixin()
mixin.resource = UserResource
mixin.CONTENT = cleaned_data
# form_data = {'username': 'bar1', 'groups': [group.id]}
# request = self.req.post('/groups', data=form_data)
# cleaned_data = dict(form_data)
# cleaned_data['groups'] = [group]
# mixin = CreateModelMixin()
# mixin.resource = UserResource
# mixin.CONTENT = cleaned_data
response = mixin.post(request)
self.assertEquals(2, CustomUser.objects.count())
self.assertEquals(1, response.raw_content.groups.count())
self.assertEquals('foo1', response.raw_content.groups.all()[0].name)
# response = mixin.post(request)
# self.assertEquals(2, CustomUser.objects.count())
# self.assertEquals(1, response.raw_content.groups.count())
# self.assertEquals('foo1', response.raw_content.groups.all()[0].name)
group2 = Group(name='foo2')
group2.save()
# group2 = Group(name='foo2')
# group2.save()
form_data = {'username': 'bar2', 'groups': [group.id, group2.id]}
request = self.req.post('/groups', data=form_data)
cleaned_data = dict(form_data)
cleaned_data['groups'] = [group, group2]
mixin = CreateModelMixin()
mixin.resource = UserResource
mixin.CONTENT = cleaned_data
# form_data = {'username': 'bar2', 'groups': [group.id, group2.id]}
# request = self.req.post('/groups', data=form_data)
# cleaned_data = dict(form_data)
# cleaned_data['groups'] = [group, group2]
# mixin = CreateModelMixin()
# mixin.resource = UserResource
# mixin.CONTENT = cleaned_data
response = mixin.post(request)
self.assertEquals(3, CustomUser.objects.count())
self.assertEquals(2, response.raw_content.groups.count())
self.assertEquals('foo1', response.raw_content.groups.all()[0].name)
self.assertEquals('foo2', response.raw_content.groups.all()[1].name)
# response = mixin.post(request)
# self.assertEquals(3, CustomUser.objects.count())
# self.assertEquals(2, response.raw_content.groups.count())
# self.assertEquals('foo1', response.raw_content.groups.all()[0].name)
# self.assertEquals('foo2', response.raw_content.groups.all()[1].name)
class MockPaginatorView(PaginatorMixin, View):
total = 60
# class MockPaginatorView(PaginatorMixin, View):
# total = 60
def get(self, request):
return Response(range(0, self.total))
# def get(self, request):
# return Response(range(0, self.total))
def post(self, request):
return Response({'status': 'OK'}, status=status.HTTP_201_CREATED)
# def post(self, request):
# return Response({'status': 'OK'}, status=status.HTTP_201_CREATED)
class TestPagination(TestCase):
def setUp(self):
self.req = RequestFactory()
# class TestPagination(TestCase):
# def setUp(self):
# self.req = RequestFactory()
def test_default_limit(self):
""" Tests if pagination works without overwriting the limit """
request = self.req.get('/paginator')
response = MockPaginatorView.as_view()(request)
content = response.raw_content
# def test_default_limit(self):
# """ Tests if pagination works without overwriting the limit """
# request = self.req.get('/paginator')
# response = MockPaginatorView.as_view()(request)
# content = response.raw_content
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(MockPaginatorView.total, content['total'])
self.assertEqual(MockPaginatorView.limit, content['per_page'])
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(MockPaginatorView.total, content['total'])
# self.assertEqual(MockPaginatorView.limit, content['per_page'])
self.assertEqual(range(0, MockPaginatorView.limit), content['results'])
# self.assertEqual(range(0, MockPaginatorView.limit), content['results'])
def test_overwriting_limit(self):
""" Tests if the limit can be overwritten """
limit = 10
# def test_overwriting_limit(self):
# """ Tests if the limit can be overwritten """
# limit = 10
request = self.req.get('/paginator')
response = MockPaginatorView.as_view(limit=limit)(request)
content = response.raw_content
# request = self.req.get('/paginator')
# response = MockPaginatorView.as_view(limit=limit)(request)
# content = response.raw_content
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(content['per_page'], limit)
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(content['per_page'], limit)
self.assertEqual(range(0, limit), content['results'])
# self.assertEqual(range(0, limit), content['results'])
def test_limit_param(self):
""" Tests if the client can set the limit """
from math import ceil
# def test_limit_param(self):
# """ Tests if the client can set the limit """
# from math import ceil
limit = 5
num_pages = int(ceil(MockPaginatorView.total / float(limit)))
# limit = 5
# num_pages = int(ceil(MockPaginatorView.total / float(limit)))
request = self.req.get('/paginator/?limit=%d' % limit)
response = MockPaginatorView.as_view()(request)
content = response.raw_content
# request = self.req.get('/paginator/?limit=%d' % limit)
# response = MockPaginatorView.as_view()(request)
# content = response.raw_content
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(MockPaginatorView.total, content['total'])
self.assertEqual(limit, content['per_page'])
self.assertEqual(num_pages, content['pages'])
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(MockPaginatorView.total, content['total'])
# self.assertEqual(limit, content['per_page'])
# self.assertEqual(num_pages, content['pages'])
def test_exceeding_limit(self):
""" Makes sure the client cannot exceed the default limit """
from math import ceil
# def test_exceeding_limit(self):
# """ Makes sure the client cannot exceed the default limit """
# from math import ceil
limit = MockPaginatorView.limit + 10
num_pages = int(ceil(MockPaginatorView.total / float(limit)))
# limit = MockPaginatorView.limit + 10
# num_pages = int(ceil(MockPaginatorView.total / float(limit)))
request = self.req.get('/paginator/?limit=%d' % limit)
response = MockPaginatorView.as_view()(request)
content = response.raw_content
# request = self.req.get('/paginator/?limit=%d' % limit)
# response = MockPaginatorView.as_view()(request)
# content = response.raw_content
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(MockPaginatorView.total, content['total'])
self.assertNotEqual(limit, content['per_page'])
self.assertNotEqual(num_pages, content['pages'])
self.assertEqual(MockPaginatorView.limit, content['per_page'])
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(MockPaginatorView.total, content['total'])
# self.assertNotEqual(limit, content['per_page'])
# self.assertNotEqual(num_pages, content['pages'])
# self.assertEqual(MockPaginatorView.limit, content['per_page'])
def test_only_works_for_get(self):
""" Pagination should only work for GET requests """
request = self.req.post('/paginator', data={'content': 'spam'})
response = MockPaginatorView.as_view()(request)
content = response.raw_content
# def test_only_works_for_get(self):
# """ Pagination should only work for GET requests """
# request = self.req.post('/paginator', data={'content': 'spam'})
# response = MockPaginatorView.as_view()(request)
# content = response.raw_content
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(None, content.get('per_page'))
self.assertEqual('OK', content['status'])
# self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# self.assertEqual(None, content.get('per_page'))
# self.assertEqual('OK', content['status'])
def test_non_int_page(self):
""" Tests that it can handle invalid values """
request = self.req.get('/paginator/?page=spam')
response = MockPaginatorView.as_view()(request)
# def test_non_int_page(self):
# """ Tests that it can handle invalid values """
# request = self.req.get('/paginator/?page=spam')
# response = MockPaginatorView.as_view()(request)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
# self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_page_range(self):
""" Tests that the page range is handle correctly """
request = self.req.get('/paginator/?page=0')
response = MockPaginatorView.as_view()(request)
content = response.raw_content
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
# def test_page_range(self):
# """ Tests that the page range is handle correctly """
# request = self.req.get('/paginator/?page=0')
# response = MockPaginatorView.as_view()(request)
# content = response.raw_content
# self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
request = self.req.get('/paginator/')
response = MockPaginatorView.as_view()(request)
content = response.raw_content
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(range(0, MockPaginatorView.limit), content['results'])
# request = self.req.get('/paginator/')
# response = MockPaginatorView.as_view()(request)
# content = response.raw_content
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(range(0, MockPaginatorView.limit), content['results'])
num_pages = content['pages']
# num_pages = content['pages']
request = self.req.get('/paginator/?page=%d' % num_pages)
response = MockPaginatorView.as_view()(request)
content = response.raw_content
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(range(MockPaginatorView.limit*(num_pages-1), MockPaginatorView.total), content['results'])
# request = self.req.get('/paginator/?page=%d' % num_pages)
# response = MockPaginatorView.as_view()(request)
# content = response.raw_content
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertEqual(range(MockPaginatorView.limit*(num_pages-1), MockPaginatorView.total), content['results'])
request = self.req.get('/paginator/?page=%d' % (num_pages + 1,))
response = MockPaginatorView.as_view()(request)
content = response.raw_content
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
# request = self.req.get('/paginator/?page=%d' % (num_pages + 1,))
# response = MockPaginatorView.as_view()(request)
# content = response.raw_content
# self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
def test_existing_query_parameters_are_preserved(self):
""" Tests that existing query parameters are preserved when
generating next/previous page links """
request = self.req.get('/paginator/?foo=bar&another=something')
response = MockPaginatorView.as_view()(request)
content = response.raw_content
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue('foo=bar' in content['next'])
self.assertTrue('another=something' in content['next'])
self.assertTrue('page=2' in content['next'])
# def test_existing_query_parameters_are_preserved(self):
# """ Tests that existing query parameters are preserved when
# generating next/previous page links """
# request = self.req.get('/paginator/?foo=bar&another=something')
# response = MockPaginatorView.as_view()(request)
# content = response.raw_content
# self.assertEqual(response.status_code, status.HTTP_200_OK)
# self.assertTrue('foo=bar' in content['next'])
# self.assertTrue('another=something' in content['next'])
# self.assertTrue('page=2' in content['next'])
def test_duplicate_parameters_are_not_created(self):
""" Regression: ensure duplicate "page" parameters are not added to
paginated URLs. So page 1 should contain ?page=2, not ?page=1&page=2 """
request = self.req.get('/paginator/?page=1')
response = MockPaginatorView.as_view()(request)
content = response.raw_content
self.assertTrue('page=2' in content['next'])
self.assertFalse('page=1' in content['next'])
# def test_duplicate_parameters_are_not_created(self):
# """ Regression: ensure duplicate "page" parameters are not added to
# paginated URLs. So page 1 should contain ?page=2, not ?page=1&page=2 """
# request = self.req.get('/paginator/?page=1')
# response = MockPaginatorView.as_view()(request)
# content = response.raw_content
# self.assertTrue('page=2' in content['next'])
# self.assertFalse('page=1' in content['next'])

View File

@ -1,90 +1,90 @@
from django.conf.urls.defaults import patterns, url
from django.forms import ModelForm
from django.contrib.auth.models import Group, User
from djangorestframework.resources import ModelResource
from djangorestframework.views import ListOrCreateModelView, InstanceModelView
from djangorestframework.tests.models import CustomUser
from djangorestframework.tests.testcases import TestModelsTestCase
# from django.conf.urls.defaults import patterns, url
# from django.forms import ModelForm
# from django.contrib.auth.models import Group, User
# from djangorestframework.resources import ModelResource
# from djangorestframework.views import ListOrCreateModelView, InstanceModelView
# from djangorestframework.tests.models import CustomUser
# from djangorestframework.tests.testcases import TestModelsTestCase
class GroupResource(ModelResource):
model = Group
# class GroupResource(ModelResource):
# model = Group
class UserForm(ModelForm):
class Meta:
model = User
exclude = ('last_login', 'date_joined')
# class UserForm(ModelForm):
# class Meta:
# model = User
# exclude = ('last_login', 'date_joined')
class UserResource(ModelResource):
model = User
form = UserForm
# class UserResource(ModelResource):
# model = User
# form = UserForm
class CustomUserResource(ModelResource):
model = CustomUser
# class CustomUserResource(ModelResource):
# model = CustomUser
urlpatterns = patterns('',
url(r'^users/$', ListOrCreateModelView.as_view(resource=UserResource), name='users'),
url(r'^users/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=UserResource)),
url(r'^customusers/$', ListOrCreateModelView.as_view(resource=CustomUserResource), name='customusers'),
url(r'^customusers/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=CustomUserResource)),
url(r'^groups/$', ListOrCreateModelView.as_view(resource=GroupResource), name='groups'),
url(r'^groups/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=GroupResource)),
)
# urlpatterns = patterns('',
# url(r'^users/$', ListOrCreateModelView.as_view(resource=UserResource), name='users'),
# url(r'^users/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=UserResource)),
# url(r'^customusers/$', ListOrCreateModelView.as_view(resource=CustomUserResource), name='customusers'),
# url(r'^customusers/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=CustomUserResource)),
# url(r'^groups/$', ListOrCreateModelView.as_view(resource=GroupResource), name='groups'),
# url(r'^groups/(?P<id>[0-9]+)/$', InstanceModelView.as_view(resource=GroupResource)),
# )
class ModelViewTests(TestModelsTestCase):
"""Test the model views djangorestframework provides"""
urls = 'djangorestframework.tests.modelviews'
# class ModelViewTests(TestModelsTestCase):
# """Test the model views djangorestframework provides"""
# urls = 'djangorestframework.tests.modelviews'
def test_creation(self):
"""Ensure that a model object can be created"""
self.assertEqual(0, Group.objects.count())
# def test_creation(self):
# """Ensure that a model object can be created"""
# self.assertEqual(0, Group.objects.count())
response = self.client.post('/groups/', {'name': 'foo'})
# response = self.client.post('/groups/', {'name': 'foo'})
self.assertEqual(response.status_code, 201)
self.assertEqual(1, Group.objects.count())
self.assertEqual('foo', Group.objects.all()[0].name)
# self.assertEqual(response.status_code, 201)
# self.assertEqual(1, Group.objects.count())
# self.assertEqual('foo', Group.objects.all()[0].name)
def test_creation_with_m2m_relation(self):
"""Ensure that a model object with a m2m relation can be created"""
group = Group(name='foo')
group.save()
self.assertEqual(0, User.objects.count())
# def test_creation_with_m2m_relation(self):
# """Ensure that a model object with a m2m relation can be created"""
# group = Group(name='foo')
# group.save()
# self.assertEqual(0, User.objects.count())
response = self.client.post('/users/', {'username': 'bar', 'password': 'baz', 'groups': [group.id]})
# response = self.client.post('/users/', {'username': 'bar', 'password': 'baz', 'groups': [group.id]})
self.assertEqual(response.status_code, 201)
self.assertEqual(1, User.objects.count())
# self.assertEqual(response.status_code, 201)
# self.assertEqual(1, User.objects.count())
user = User.objects.all()[0]
self.assertEqual('bar', user.username)
self.assertEqual('baz', user.password)
self.assertEqual(1, user.groups.count())
# user = User.objects.all()[0]
# self.assertEqual('bar', user.username)
# self.assertEqual('baz', user.password)
# self.assertEqual(1, user.groups.count())
group = user.groups.all()[0]
self.assertEqual('foo', group.name)
# group = user.groups.all()[0]
# self.assertEqual('foo', group.name)
def test_creation_with_m2m_relation_through(self):
"""
Ensure that a model object with a m2m relation can be created where that
relation uses a through table
"""
group = Group(name='foo')
group.save()
self.assertEqual(0, User.objects.count())
# def test_creation_with_m2m_relation_through(self):
# """
# Ensure that a model object with a m2m relation can be created where that
# relation uses a through table
# """
# group = Group(name='foo')
# group.save()
# self.assertEqual(0, User.objects.count())
response = self.client.post('/customusers/', {'username': 'bar', 'groups': [group.id]})
# response = self.client.post('/customusers/', {'username': 'bar', 'groups': [group.id]})
self.assertEqual(response.status_code, 201)
self.assertEqual(1, CustomUser.objects.count())
# self.assertEqual(response.status_code, 201)
# self.assertEqual(1, CustomUser.objects.count())
user = CustomUser.objects.all()[0]
self.assertEqual('bar', user.username)
self.assertEqual(1, user.groups.count())
# user = CustomUser.objects.all()[0]
# self.assertEqual('bar', user.username)
# self.assertEqual(1, user.groups.count())
group = user.groups.all()[0]
self.assertEqual('foo', group.name)
# group = user.groups.all()[0]
# self.assertEqual('foo', group.name)

View File

@ -1,160 +1,161 @@
"""Tests for the resource module"""
from django.db import models
from django.test import TestCase
from django.utils.translation import ugettext_lazy
from djangorestframework.serializer import Serializer
# """Tests for the resource module"""
# from django.db import models
# from django.test import TestCase
# from django.utils.translation import ugettext_lazy
# from djangorestframework.serializer import Serializer
import datetime
import decimal
class TestObjectToData(TestCase):
"""
Tests for the Serializer class.
"""
def setUp(self):
self.serializer = Serializer()
self.serialize = self.serializer.serialize
def test_decimal(self):
"""Decimals need to be converted to a string representation."""
self.assertEquals(self.serialize(decimal.Decimal('1.5')), decimal.Decimal('1.5'))
def test_function(self):
"""Functions with no arguments should be called."""
def foo():
return 1
self.assertEquals(self.serialize(foo), 1)
def test_method(self):
"""Methods with only a ``self`` argument should be called."""
class Foo(object):
def foo(self):
return 1
self.assertEquals(self.serialize(Foo().foo), 1)
def test_datetime(self):
"""datetime objects are left as-is."""
now = datetime.datetime.now()
self.assertEquals(self.serialize(now), now)
def test_dict_method_name_collision(self):
"""dict with key that collides with dict method name"""
self.assertEquals(self.serialize({'items': 'foo'}), {'items': u'foo'})
self.assertEquals(self.serialize({'keys': 'foo'}), {'keys': u'foo'})
self.assertEquals(self.serialize({'values': 'foo'}), {'values': u'foo'})
def test_ugettext_lazy(self):
self.assertEquals(self.serialize(ugettext_lazy('foobar')), u'foobar')
# import datetime
# import decimal
class TestFieldNesting(TestCase):
"""
Test nesting the fields in the Serializer class
"""
def setUp(self):
self.serializer = Serializer()
self.serialize = self.serializer.serialize
# class TestObjectToData(TestCase):
# """
# Tests for the Serializer class.
# """
class M1(models.Model):
field1 = models.CharField(max_length=256)
field2 = models.CharField(max_length=256)
# def setUp(self):
# self.serializer = Serializer()
# self.serialize = self.serializer.serialize
class M2(models.Model):
field = models.OneToOneField(M1)
# def test_decimal(self):
# """Decimals need to be converted to a string representation."""
# self.assertEquals(self.serialize(decimal.Decimal('1.5')), decimal.Decimal('1.5'))
class M3(models.Model):
field = models.ForeignKey(M1)
# def test_function(self):
# """Functions with no arguments should be called."""
# def foo():
# return 1
# self.assertEquals(self.serialize(foo), 1)
self.m1 = M1(field1='foo', field2='bar')
self.m2 = M2(field=self.m1)
self.m3 = M3(field=self.m1)
# def test_method(self):
# """Methods with only a ``self`` argument should be called."""
# class Foo(object):
# def foo(self):
# return 1
# self.assertEquals(self.serialize(Foo().foo), 1)
# def test_datetime(self):
# """datetime objects are left as-is."""
# now = datetime.datetime.now()
# self.assertEquals(self.serialize(now), now)
# def test_dict_method_name_collision(self):
# """dict with key that collides with dict method name"""
# self.assertEquals(self.serialize({'items': 'foo'}), {'items': u'foo'})
# self.assertEquals(self.serialize({'keys': 'foo'}), {'keys': u'foo'})
# self.assertEquals(self.serialize({'values': 'foo'}), {'values': u'foo'})
# def test_ugettext_lazy(self):
# self.assertEquals(self.serialize(ugettext_lazy('foobar')), u'foobar')
def test_tuple_nesting(self):
"""
Test tuple nesting on `fields` attr
"""
class SerializerM2(Serializer):
fields = (('field', ('field1',)),)
# class TestFieldNesting(TestCase):
# """
# Test nesting the fields in the Serializer class
# """
# def setUp(self):
# self.serializer = Serializer()
# self.serialize = self.serializer.serialize
class SerializerM3(Serializer):
fields = (('field', ('field2',)),)
# class M1(models.Model):
# field1 = models.CharField(max_length=256)
# field2 = models.CharField(max_length=256)
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
# class M2(models.Model):
# field = models.OneToOneField(M1)
# class M3(models.Model):
# field = models.ForeignKey(M1)
# self.m1 = M1(field1='foo', field2='bar')
# self.m2 = M2(field=self.m1)
# self.m3 = M3(field=self.m1)
def test_serializer_class_nesting(self):
"""
Test related model serialization
"""
class NestedM2(Serializer):
fields = ('field1', )
# def test_tuple_nesting(self):
# """
# Test tuple nesting on `fields` attr
# """
# class SerializerM2(Serializer):
# fields = (('field', ('field1',)),)
class NestedM3(Serializer):
fields = ('field2', )
# class SerializerM3(Serializer):
# fields = (('field', ('field2',)),)
class SerializerM2(Serializer):
fields = [('field', NestedM2)]
# self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
# self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
class SerializerM3(Serializer):
fields = [('field', NestedM3)]
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
# def test_serializer_class_nesting(self):
# """
# Test related model serialization
# """
# class NestedM2(Serializer):
# fields = ('field1', )
def test_serializer_no_fields(self):
"""
Test related serializer works when the fields attr isn't present. Fix for
#178.
"""
class NestedM2(Serializer):
fields = ('field1', )
# class NestedM3(Serializer):
# fields = ('field2', )
class NestedM3(Serializer):
fields = ('field2', )
# class SerializerM2(Serializer):
# fields = [('field', NestedM2)]
class SerializerM2(Serializer):
include = [('field', NestedM2)]
exclude = ('id', )
# class SerializerM3(Serializer):
# fields = [('field', NestedM3)]
class SerializerM3(Serializer):
fields = [('field', NestedM3)]
# self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
# self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
# def test_serializer_no_fields(self):
# """
# Test related serializer works when the fields attr isn't present. Fix for
# #178.
# """
# class NestedM2(Serializer):
# fields = ('field1', )
def test_serializer_classname_nesting(self):
"""
Test related model serialization
"""
class SerializerM2(Serializer):
fields = [('field', 'NestedM2')]
# class NestedM3(Serializer):
# fields = ('field2', )
class SerializerM3(Serializer):
fields = [('field', 'NestedM3')]
# class SerializerM2(Serializer):
# include = [('field', NestedM2)]
# exclude = ('id', )
class NestedM2(Serializer):
fields = ('field1', )
# class SerializerM3(Serializer):
# fields = [('field', NestedM3)]
class NestedM3(Serializer):
fields = ('field2', )
# self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
# self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
# def test_serializer_classname_nesting(self):
# """
# Test related model serialization
# """
# class SerializerM2(Serializer):
# fields = [('field', 'NestedM2')]
def test_serializer_overridden_hook_method(self):
"""
Test serializing a model instance which overrides a class method on the
serializer. Checks for correct behaviour in odd edge case.
"""
class SerializerM2(Serializer):
fields = ('overridden', )
# class SerializerM3(Serializer):
# fields = [('field', 'NestedM3')]
def overridden(self):
return False
# class NestedM2(Serializer):
# fields = ('field1', )
self.m2.overridden = True
self.assertEqual(SerializerM2().serialize_model(self.m2),
{'overridden': True})
# class NestedM3(Serializer):
# fields = ('field2', )
# self.assertEqual(SerializerM2().serialize(self.m2), {'field': {'field1': u'foo'}})
# self.assertEqual(SerializerM3().serialize(self.m3), {'field': {'field2': u'bar'}})
# def test_serializer_overridden_hook_method(self):
# """
# Test serializing a model instance which overrides a class method on the
# serializer. Checks for correct behaviour in odd edge case.
# """
# class SerializerM2(Serializer):
# fields = ('overridden', )
# def overridden(self):
# return False
# self.m2.overridden = True
# self.assertEqual(SerializerM2().serialize_model(self.m2),
# {'overridden': True})

View File

@ -8,8 +8,7 @@ from django.core.cache import cache
from djangorestframework.compat import RequestFactory
from djangorestframework.views import View
from djangorestframework.permissions import PerUserThrottling, PerViewThrottling, PerResourceThrottling
from djangorestframework.resources import FormResource
from djangorestframework.permissions import PerUserThrottling, PerViewThrottling
from djangorestframework.response import Response
@ -25,11 +24,6 @@ class MockView_PerViewThrottling(MockView):
permission_classes = (PerViewThrottling,)
class MockView_PerResourceThrottling(MockView):
permission_classes = (PerResourceThrottling,)
resource = FormResource
class MockView_MinuteThrottling(MockView):
throttle = '3/min'
@ -98,12 +92,6 @@ class ThrottlingTests(TestCase):
"""
self.ensure_is_throttled(MockView_PerViewThrottling, 503)
def test_request_throttling_is_per_resource(self):
"""
Ensure request rate is limited globally per Resource for PerResourceThrottles
"""
self.ensure_is_throttled(MockView_PerResourceThrottling, 503)
def ensure_response_header_contains_proper_throttle_field(self, view, expected_headers):
"""
Ensure the response returns an X-Throttle field with status and next attributes

View File

@ -1,330 +1,329 @@
from django import forms
from django.db import models
from django.test import TestCase
from djangorestframework.resources import FormResource, ModelResource
from djangorestframework.response import ImmediateResponse
from djangorestframework.views import View
# from django import forms
# from django.db import models
# from django.test import TestCase
# from djangorestframework.response import ImmediateResponse
# from djangorestframework.views import View
class TestDisabledValidations(TestCase):
"""Tests on FormValidator with validation disabled by setting form to None"""
def test_disabled_form_validator_returns_content_unchanged(self):
"""If the view's form attribute is None then FormValidator(view).validate_request(content, None)
should just return the content unmodified."""
class DisabledFormResource(FormResource):
form = None
class MockView(View):
resource = DisabledFormResource
view = MockView()
content = {'qwerty': 'uiop'}
self.assertEqual(FormResource(view).validate_request(content, None), content)
def test_disabled_form_validator_get_bound_form_returns_none(self):
"""If the view's form attribute is None on then
FormValidator(view).get_bound_form(content) should just return None."""
class DisabledFormResource(FormResource):
form = None
class MockView(View):
resource = DisabledFormResource
view = MockView()
content = {'qwerty': 'uiop'}
self.assertEqual(FormResource(view).get_bound_form(content), None)
def test_disabled_model_form_validator_returns_content_unchanged(self):
"""If the view's form is None and does not have a Resource with a model set then
ModelFormValidator(view).validate_request(content, None) should just return the content unmodified."""
class DisabledModelFormView(View):
resource = ModelResource
view = DisabledModelFormView()
content = {'qwerty': 'uiop'}
self.assertEqual(ModelResource(view).get_bound_form(content), None)
def test_disabled_model_form_validator_get_bound_form_returns_none(self):
"""If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
class DisabledModelFormView(View):
resource = ModelResource
view = DisabledModelFormView()
content = {'qwerty': 'uiop'}
self.assertEqual(ModelResource(view).get_bound_form(content), None)
class TestNonFieldErrors(TestCase):
"""Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
def test_validate_failed_due_to_non_field_error_returns_appropriate_message(self):
"""If validation fails with a non-field error, ensure the response a non-field error"""
class MockForm(forms.Form):
field1 = forms.CharField(required=False)
field2 = forms.CharField(required=False)
ERROR_TEXT = 'You may not supply both field1 and field2'
def clean(self):
if 'field1' in self.cleaned_data and 'field2' in self.cleaned_data:
raise forms.ValidationError(self.ERROR_TEXT)
return self.cleaned_data
class MockResource(FormResource):
form = MockForm
class MockView(View):
pass
view = MockView()
content = {'field1': 'example1', 'field2': 'example2'}
try:
MockResource(view).validate_request(content, None)
except ImmediateResponse, exc:
response = exc.response
self.assertEqual(response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
else:
self.fail('ImmediateResponse was not raised')
class TestFormValidation(TestCase):
"""Tests which check basic form validation.
Also includes the same set of tests with a ModelFormValidator for which the form has been explicitly set.
(ModelFormValidator should behave as FormValidator if a form is set rather than relying on the default ModelForm)"""
def setUp(self):
class MockForm(forms.Form):
qwerty = forms.CharField(required=True)
class MockFormResource(FormResource):
form = MockForm
class MockModelResource(ModelResource):
form = MockForm
class MockFormView(View):
resource = MockFormResource
class MockModelFormView(View):
resource = MockModelResource
self.MockFormResource = MockFormResource
self.MockModelResource = MockModelResource
self.MockFormView = MockFormView
self.MockModelFormView = MockModelFormView
def validation_returns_content_unchanged_if_already_valid_and_clean(self, validator):
"""If the content is already valid and clean then validate(content) should just return the content unmodified."""
content = {'qwerty': 'uiop'}
self.assertEqual(validator.validate_request(content, None), content)
def validation_failure_raises_response_exception(self, validator):
"""If form validation fails a ResourceException 400 (Bad Request) should be raised."""
content = {}
self.assertRaises(ImmediateResponse, validator.validate_request, content, None)
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(ImmediateResponse, validator.validate_request, content, None)
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."""
content = {'qwerty': 'uiop', 'extra': 'extra'}
validator._validate(content, None, allowed_extra_fields=('extra',))
def validation_allows_unknown_fields_if_explicitly_allowed(self, validator):
"""If we set ``unknown_form_fields`` on the form resource, then don't
raise errors on unexpected request data"""
content = {'qwerty': 'uiop', 'extra': 'extra'}
validator.allow_unknown_form_fields = True
self.assertEqual({'qwerty': u'uiop'},
validator.validate_request(content, None),
"Resource didn't accept unknown fields.")
validator.allow_unknown_form_fields = False
def validation_does_not_require_extra_fields_if_explicitly_set(self, validator):
"""If we include an allowed_extra_fields paramater on _validate, then do not fail if we do not have fields with those names."""
content = {'qwerty': 'uiop'}
self.assertEqual(validator._validate(content, None, allowed_extra_fields=('extra',)), content)
def validation_failed_due_to_no_content_returns_appropriate_message(self, validator):
"""If validation fails due to no content, ensure the response contains a single non-field error"""
content = {}
try:
validator.validate_request(content, None)
except ImmediateResponse, exc:
response = exc.response
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
else:
self.fail('ResourceException was not raised')
def validation_failed_due_to_field_error_returns_appropriate_message(self, validator):
"""If validation fails due to a field error, ensure the response contains a single field error"""
content = {'qwerty': ''}
try:
validator.validate_request(content, None)
except ImmediateResponse, exc:
response = exc.response
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
else:
self.fail('ResourceException was not raised')
def validation_failed_due_to_invalid_field_returns_appropriate_message(self, validator):
"""If validation fails due to an invalid field, ensure the response contains a single field error"""
content = {'qwerty': 'uiop', 'extra': 'extra'}
try:
validator.validate_request(content, None)
except ImmediateResponse, exc:
response = exc.response
self.assertEqual(response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}})
else:
self.fail('ResourceException was not raised')
def validation_failed_due_to_multiple_errors_returns_appropriate_message(self, validator):
"""If validation for multiple reasons, ensure the response contains each error"""
content = {'qwerty': '', 'extra': 'extra'}
try:
validator.validate_request(content, None)
except ImmediateResponse, exc:
response = exc.response
self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.'],
'extra': ['This field does not exist.']}})
else:
self.fail('ResourceException was not raised')
# Tests on FormResource
def test_form_validation_returns_content_unchanged_if_already_valid_and_clean(self):
validator = self.MockFormResource(self.MockFormView())
self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
def test_form_validation_failure_raises_response_exception(self):
validator = self.MockFormResource(self.MockFormView())
self.validation_failure_raises_response_exception(validator)
def test_validation_does_not_allow_extra_fields_by_default(self):
validator = self.MockFormResource(self.MockFormView())
self.validation_does_not_allow_extra_fields_by_default(validator)
def test_validation_allows_extra_fields_if_explicitly_set(self):
validator = self.MockFormResource(self.MockFormView())
self.validation_allows_extra_fields_if_explicitly_set(validator)
def test_validation_allows_unknown_fields_if_explicitly_allowed(self):
validator = self.MockFormResource(self.MockFormView())
self.validation_allows_unknown_fields_if_explicitly_allowed(validator)
def test_validation_does_not_require_extra_fields_if_explicitly_set(self):
validator = self.MockFormResource(self.MockFormView())
self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
def test_validation_failed_due_to_no_content_returns_appropriate_message(self):
validator = self.MockFormResource(self.MockFormView())
self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
def test_validation_failed_due_to_field_error_returns_appropriate_message(self):
validator = self.MockFormResource(self.MockFormView())
self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
def test_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
validator = self.MockFormResource(self.MockFormView())
self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
def test_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
validator = self.MockFormResource(self.MockFormView())
self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
# Same tests on ModelResource
def test_modelform_validation_returns_content_unchanged_if_already_valid_and_clean(self):
validator = self.MockModelResource(self.MockModelFormView())
self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
def test_modelform_validation_failure_raises_response_exception(self):
validator = self.MockModelResource(self.MockModelFormView())
self.validation_failure_raises_response_exception(validator)
def test_modelform_validation_does_not_allow_extra_fields_by_default(self):
validator = self.MockModelResource(self.MockModelFormView())
self.validation_does_not_allow_extra_fields_by_default(validator)
def test_modelform_validation_allows_extra_fields_if_explicitly_set(self):
validator = self.MockModelResource(self.MockModelFormView())
self.validation_allows_extra_fields_if_explicitly_set(validator)
def test_modelform_validation_does_not_require_extra_fields_if_explicitly_set(self):
validator = self.MockModelResource(self.MockModelFormView())
self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
def test_modelform_validation_failed_due_to_no_content_returns_appropriate_message(self):
validator = self.MockModelResource(self.MockModelFormView())
self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
def test_modelform_validation_failed_due_to_field_error_returns_appropriate_message(self):
validator = self.MockModelResource(self.MockModelFormView())
self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
def test_modelform_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
validator = self.MockModelResource(self.MockModelFormView())
self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
def test_modelform_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
validator = self.MockModelResource(self.MockModelFormView())
self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
class TestModelFormValidator(TestCase):
"""Tests specific to ModelFormValidatorMixin"""
def setUp(self):
"""Create a validator for a model with two fields and a property."""
class MockModel(models.Model):
qwerty = models.CharField(max_length=256)
uiop = models.CharField(max_length=256, blank=True)
@property
def readonly(self):
return 'read only'
class MockResource(ModelResource):
model = MockModel
# class TestDisabledValidations(TestCase):
# """Tests on FormValidator with validation disabled by setting form to None"""
class MockView(View):
resource = MockResource
# def test_disabled_form_validator_returns_content_unchanged(self):
# """If the view's form attribute is None then FormValidator(view).validate_request(content, None)
# should just return the content unmodified."""
# class DisabledFormResource(FormResource):
# form = None
# class MockView(View):
# resource = DisabledFormResource
self.validator = MockResource(MockView)
# view = MockView()
# content = {'qwerty': 'uiop'}
# self.assertEqual(FormResource(view).validate_request(content, None), content)
# def test_disabled_form_validator_get_bound_form_returns_none(self):
# """If the view's form attribute is None on then
# FormValidator(view).get_bound_form(content) should just return None."""
# class DisabledFormResource(FormResource):
# form = None
# class MockView(View):
# resource = DisabledFormResource
# view = MockView()
# content = {'qwerty': 'uiop'}
# self.assertEqual(FormResource(view).get_bound_form(content), None)
# def test_disabled_model_form_validator_returns_content_unchanged(self):
# """If the view's form is None and does not have a Resource with a model set then
# ModelFormValidator(view).validate_request(content, None) should just return the content unmodified."""
# class DisabledModelFormView(View):
# resource = ModelResource
# view = DisabledModelFormView()
# content = {'qwerty': 'uiop'}
# self.assertEqual(ModelResource(view).get_bound_form(content), None)
# def test_disabled_model_form_validator_get_bound_form_returns_none(self):
# """If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None."""
# class DisabledModelFormView(View):
# resource = ModelResource
# view = DisabledModelFormView()
# content = {'qwerty': 'uiop'}
# self.assertEqual(ModelResource(view).get_bound_form(content), None)
# class TestNonFieldErrors(TestCase):
# """Tests against form validation errors caused by non-field errors. (eg as might be caused by some custom form validation)"""
# def test_validate_failed_due_to_non_field_error_returns_appropriate_message(self):
# """If validation fails with a non-field error, ensure the response a non-field error"""
# class MockForm(forms.Form):
# field1 = forms.CharField(required=False)
# field2 = forms.CharField(required=False)
# ERROR_TEXT = 'You may not supply both field1 and field2'
# def clean(self):
# if 'field1' in self.cleaned_data and 'field2' in self.cleaned_data:
# raise forms.ValidationError(self.ERROR_TEXT)
# return self.cleaned_data
# class MockResource(FormResource):
# form = MockForm
# class MockView(View):
# pass
# view = MockView()
# content = {'field1': 'example1', 'field2': 'example2'}
# try:
# MockResource(view).validate_request(content, None)
# except ImmediateResponse, exc:
# response = exc.response
# self.assertEqual(response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
# else:
# self.fail('ImmediateResponse was not raised')
# class TestFormValidation(TestCase):
# """Tests which check basic form validation.
# Also includes the same set of tests with a ModelFormValidator for which the form has been explicitly set.
# (ModelFormValidator should behave as FormValidator if a form is set rather than relying on the default ModelForm)"""
# def setUp(self):
# class MockForm(forms.Form):
# qwerty = forms.CharField(required=True)
# class MockFormResource(FormResource):
# form = MockForm
# class MockModelResource(ModelResource):
# form = MockForm
# class MockFormView(View):
# resource = MockFormResource
# class MockModelFormView(View):
# resource = MockModelResource
# self.MockFormResource = MockFormResource
# self.MockModelResource = MockModelResource
# self.MockFormView = MockFormView
# self.MockModelFormView = MockModelFormView
# def validation_returns_content_unchanged_if_already_valid_and_clean(self, validator):
# """If the content is already valid and clean then validate(content) should just return the content unmodified."""
# content = {'qwerty': 'uiop'}
# self.assertEqual(validator.validate_request(content, None), content)
# def validation_failure_raises_response_exception(self, validator):
# """If form validation fails a ResourceException 400 (Bad Request) should be raised."""
# content = {}
# self.assertRaises(ImmediateResponse, validator.validate_request, content, None)
# 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(ImmediateResponse, validator.validate_request, content, None)
# 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."""
# content = {'qwerty': 'uiop', 'extra': 'extra'}
# validator._validate(content, None, allowed_extra_fields=('extra',))
# def validation_allows_unknown_fields_if_explicitly_allowed(self, validator):
# """If we set ``unknown_form_fields`` on the form resource, then don't
# raise errors on unexpected request data"""
# content = {'qwerty': 'uiop', 'extra': 'extra'}
# validator.allow_unknown_form_fields = True
# self.assertEqual({'qwerty': u'uiop'},
# validator.validate_request(content, None),
# "Resource didn't accept unknown fields.")
# validator.allow_unknown_form_fields = False
# def validation_does_not_require_extra_fields_if_explicitly_set(self, validator):
# """If we include an allowed_extra_fields paramater on _validate, then do not fail if we do not have fields with those names."""
# content = {'qwerty': 'uiop'}
# self.assertEqual(validator._validate(content, None, allowed_extra_fields=('extra',)), content)
# def validation_failed_due_to_no_content_returns_appropriate_message(self, validator):
# """If validation fails due to no content, ensure the response contains a single non-field error"""
# content = {}
# try:
# validator.validate_request(content, None)
# except ImmediateResponse, exc:
# response = exc.response
# self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
# else:
# self.fail('ResourceException was not raised')
# def validation_failed_due_to_field_error_returns_appropriate_message(self, validator):
# """If validation fails due to a field error, ensure the response contains a single field error"""
# content = {'qwerty': ''}
# try:
# validator.validate_request(content, None)
# except ImmediateResponse, exc:
# response = exc.response
# self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
# else:
# self.fail('ResourceException was not raised')
# def validation_failed_due_to_invalid_field_returns_appropriate_message(self, validator):
# """If validation fails due to an invalid field, ensure the response contains a single field error"""
# content = {'qwerty': 'uiop', 'extra': 'extra'}
# try:
# validator.validate_request(content, None)
# except ImmediateResponse, exc:
# response = exc.response
# self.assertEqual(response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}})
# else:
# self.fail('ResourceException was not raised')
# def validation_failed_due_to_multiple_errors_returns_appropriate_message(self, validator):
# """If validation for multiple reasons, ensure the response contains each error"""
# content = {'qwerty': '', 'extra': 'extra'}
# try:
# validator.validate_request(content, None)
# except ImmediateResponse, exc:
# response = exc.response
# self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.'],
# 'extra': ['This field does not exist.']}})
# else:
# self.fail('ResourceException was not raised')
# # Tests on FormResource
# def test_form_validation_returns_content_unchanged_if_already_valid_and_clean(self):
# validator = self.MockFormResource(self.MockFormView())
# self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
# def test_form_validation_failure_raises_response_exception(self):
# validator = self.MockFormResource(self.MockFormView())
# self.validation_failure_raises_response_exception(validator)
# def test_validation_does_not_allow_extra_fields_by_default(self):
# validator = self.MockFormResource(self.MockFormView())
# self.validation_does_not_allow_extra_fields_by_default(validator)
# def test_validation_allows_extra_fields_if_explicitly_set(self):
# validator = self.MockFormResource(self.MockFormView())
# self.validation_allows_extra_fields_if_explicitly_set(validator)
# def test_validation_allows_unknown_fields_if_explicitly_allowed(self):
# validator = self.MockFormResource(self.MockFormView())
# self.validation_allows_unknown_fields_if_explicitly_allowed(validator)
# def test_validation_does_not_require_extra_fields_if_explicitly_set(self):
# validator = self.MockFormResource(self.MockFormView())
# self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
# def test_validation_failed_due_to_no_content_returns_appropriate_message(self):
# validator = self.MockFormResource(self.MockFormView())
# self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
# def test_validation_failed_due_to_field_error_returns_appropriate_message(self):
# validator = self.MockFormResource(self.MockFormView())
# self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
# def test_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
# validator = self.MockFormResource(self.MockFormView())
# self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
# def test_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
# validator = self.MockFormResource(self.MockFormView())
# self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
# # Same tests on ModelResource
# def test_modelform_validation_returns_content_unchanged_if_already_valid_and_clean(self):
# validator = self.MockModelResource(self.MockModelFormView())
# self.validation_returns_content_unchanged_if_already_valid_and_clean(validator)
# def test_modelform_validation_failure_raises_response_exception(self):
# validator = self.MockModelResource(self.MockModelFormView())
# self.validation_failure_raises_response_exception(validator)
# def test_modelform_validation_does_not_allow_extra_fields_by_default(self):
# validator = self.MockModelResource(self.MockModelFormView())
# self.validation_does_not_allow_extra_fields_by_default(validator)
# def test_modelform_validation_allows_extra_fields_if_explicitly_set(self):
# validator = self.MockModelResource(self.MockModelFormView())
# self.validation_allows_extra_fields_if_explicitly_set(validator)
# def test_modelform_validation_does_not_require_extra_fields_if_explicitly_set(self):
# validator = self.MockModelResource(self.MockModelFormView())
# self.validation_does_not_require_extra_fields_if_explicitly_set(validator)
# def test_modelform_validation_failed_due_to_no_content_returns_appropriate_message(self):
# validator = self.MockModelResource(self.MockModelFormView())
# self.validation_failed_due_to_no_content_returns_appropriate_message(validator)
# def test_modelform_validation_failed_due_to_field_error_returns_appropriate_message(self):
# validator = self.MockModelResource(self.MockModelFormView())
# self.validation_failed_due_to_field_error_returns_appropriate_message(validator)
# def test_modelform_validation_failed_due_to_invalid_field_returns_appropriate_message(self):
# validator = self.MockModelResource(self.MockModelFormView())
# self.validation_failed_due_to_invalid_field_returns_appropriate_message(validator)
# def test_modelform_validation_failed_due_to_multiple_errors_returns_appropriate_message(self):
# validator = self.MockModelResource(self.MockModelFormView())
# self.validation_failed_due_to_multiple_errors_returns_appropriate_message(validator)
# class TestModelFormValidator(TestCase):
# """Tests specific to ModelFormValidatorMixin"""
# def setUp(self):
# """Create a validator for a model with two fields and a property."""
# class MockModel(models.Model):
# qwerty = models.CharField(max_length=256)
# uiop = models.CharField(max_length=256, blank=True)
# @property
# def readonly(self):
# return 'read only'
# class MockResource(ModelResource):
# model = MockModel
def test_property_fields_are_allowed_on_model_forms(self):
"""Validation on ModelForms may include property fields that exist on the Model to be included in the input."""
content = {'qwerty': 'example', 'uiop': 'example', 'readonly': 'read only'}
self.assertEqual(self.validator.validate_request(content, None), content)
# class MockView(View):
# resource = MockResource
def test_property_fields_are_not_required_on_model_forms(self):
"""Validation on ModelForms does not require property fields that exist on the Model to be included in the input."""
content = {'qwerty': 'example', 'uiop': 'example'}
self.assertEqual(self.validator.validate_request(content, None), content)
# self.validator = MockResource(MockView)
def test_extra_fields_not_allowed_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 = {'qwerty': 'example', 'uiop': 'example', 'readonly': 'read only', 'extra': 'extra'}
self.assertRaises(ImmediateResponse, self.validator.validate_request, content, None)
# def test_property_fields_are_allowed_on_model_forms(self):
# """Validation on ModelForms may include property fields that exist on the Model to be included in the input."""
# content = {'qwerty': 'example', 'uiop': 'example', 'readonly': 'read only'}
# self.assertEqual(self.validator.validate_request(content, None), 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(ImmediateResponse, self.validator.validate_request, content, None)
# def test_property_fields_are_not_required_on_model_forms(self):
# """Validation on ModelForms does not require property fields that exist on the Model to be included in the input."""
# content = {'qwerty': 'example', 'uiop': 'example'}
# self.assertEqual(self.validator.validate_request(content, None), 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."""
content = {'qwerty': 'example', 'readonly': 'read only'}
self.validator.validate_request(content, None)
# def test_extra_fields_not_allowed_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 = {'qwerty': 'example', 'uiop': 'example', 'readonly': 'read only', 'extra': 'extra'}
# self.assertRaises(ImmediateResponse, self.validator.validate_request, content, None)
def test_model_form_validator_uses_model_forms(self):
self.assertTrue(isinstance(self.validator.get_bound_form(), forms.ModelForm))
# 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(ImmediateResponse, self.validator.validate_request, content, None)
# def test_validate_does_not_require_blankable_fields_on_model_forms(self):
# """Test standard ModelForm validation behaviour - fields with blank=True are not required."""
# content = {'qwerty': 'example', 'readonly': 'read only'}
# self.validator.validate_request(content, None)
# def test_model_form_validator_uses_model_forms(self):
# self.assertTrue(isinstance(self.validator.get_bound_form(), forms.ModelForm))

View File

@ -1,135 +1,128 @@
from django.core.urlresolvers import reverse
from django.conf.urls.defaults import patterns, url, include
from django.http import HttpResponse
from django.test import TestCase
from django import forms
from django.db import models
from django.utils import simplejson as json
# from django.core.urlresolvers import reverse
# from django.conf.urls.defaults import patterns, url, include
# from django.http import HttpResponse
# from django.test import TestCase
# from django.utils import simplejson as json
from djangorestframework.resources import ModelResource
from djangorestframework.views import (
View,
ListOrCreateModelView,
InstanceModelView
)
# from djangorestframework.views import View
class MockView(View):
"""This is a basic mock view"""
pass
# class MockView(View):
# """This is a basic mock view"""
# pass
class MockViewFinal(View):
"""View with final() override"""
# class MockViewFinal(View):
# """View with final() override"""
def final(self, request, response, *args, **kwargs):
return HttpResponse('{"test": "passed"}', content_type="application/json")
# def final(self, request, response, *args, **kwargs):
# return HttpResponse('{"test": "passed"}', content_type="application/json")
class ResourceMockView(View):
"""This is a resource-based mock view"""
# # class ResourceMockView(View):
# # """This is a resource-based mock view"""
class MockForm(forms.Form):
foo = forms.BooleanField(required=False)
bar = forms.IntegerField(help_text='Must be an integer.')
baz = forms.CharField(max_length=32)
# # class MockForm(forms.Form):
# # foo = forms.BooleanField(required=False)
# # bar = forms.IntegerField(help_text='Must be an integer.')
# # baz = forms.CharField(max_length=32)
form = MockForm
# # form = MockForm
class MockResource(ModelResource):
"""This is a mock model-based resource"""
# # class MockResource(ModelResource):
# # """This is a mock model-based resource"""
class MockResourceModel(models.Model):
foo = models.BooleanField()
bar = models.IntegerField(help_text='Must be an integer.')
baz = models.CharField(max_length=32, help_text='Free text. Max length 32 chars.')
# # class MockResourceModel(models.Model):
# # foo = models.BooleanField()
# # bar = models.IntegerField(help_text='Must be an integer.')
# # baz = models.CharField(max_length=32, help_text='Free text. Max length 32 chars.')
model = MockResourceModel
fields = ('foo', 'bar', 'baz')
# # model = MockResourceModel
# # fields = ('foo', 'bar', 'baz')
urlpatterns = patterns('',
url(r'^mock/$', MockView.as_view()),
url(r'^mock/final/$', MockViewFinal.as_view()),
url(r'^resourcemock/$', ResourceMockView.as_view()),
url(r'^model/$', ListOrCreateModelView.as_view(resource=MockResource)),
url(r'^model/(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=MockResource)),
url(r'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
)
# urlpatterns = patterns('',
# url(r'^mock/$', MockView.as_view()),
# url(r'^mock/final/$', MockViewFinal.as_view()),
# # url(r'^resourcemock/$', ResourceMockView.as_view()),
# # url(r'^model/$', ListOrCreateModelView.as_view(resource=MockResource)),
# # url(r'^model/(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=MockResource)),
# url(r'^restframework/', include('djangorestframework.urls', namespace='djangorestframework')),
# )
class BaseViewTests(TestCase):
"""Test the base view class of djangorestframework"""
urls = 'djangorestframework.tests.views'
# class BaseViewTests(TestCase):
# """Test the base view class of djangorestframework"""
# urls = 'djangorestframework.tests.views'
def test_view_call_final(self):
response = self.client.options('/mock/final/')
self.assertEqual(response['Content-Type'].split(';')[0], "application/json")
data = json.loads(response.content)
self.assertEqual(data['test'], 'passed')
# def test_view_call_final(self):
# response = self.client.options('/mock/final/')
# self.assertEqual(response['Content-Type'].split(';')[0], "application/json")
# data = json.loads(response.content)
# self.assertEqual(data['test'], 'passed')
def test_options_method_simple_view(self):
response = self.client.options('/mock/')
self._verify_options_response(response,
name='Mock',
description='This is a basic mock view')
# def test_options_method_simple_view(self):
# response = self.client.options('/mock/')
# self._verify_options_response(response,
# name='Mock',
# description='This is a basic mock view')
def test_options_method_resource_view(self):
response = self.client.options('/resourcemock/')
self._verify_options_response(response,
name='Resource Mock',
description='This is a resource-based mock view',
fields={'foo': 'BooleanField',
'bar': 'IntegerField',
'baz': 'CharField',
})
# def test_options_method_resource_view(self):
# response = self.client.options('/resourcemock/')
# self._verify_options_response(response,
# name='Resource Mock',
# description='This is a resource-based mock view',
# fields={'foo': 'BooleanField',
# 'bar': 'IntegerField',
# 'baz': 'CharField',
# })
def test_options_method_model_resource_list_view(self):
response = self.client.options('/model/')
self._verify_options_response(response,
name='Mock List',
description='This is a mock model-based resource',
fields={'foo': 'BooleanField',
'bar': 'IntegerField',
'baz': 'CharField',
})
# def test_options_method_model_resource_list_view(self):
# response = self.client.options('/model/')
# self._verify_options_response(response,
# name='Mock List',
# description='This is a mock model-based resource',
# fields={'foo': 'BooleanField',
# 'bar': 'IntegerField',
# 'baz': 'CharField',
# })
def test_options_method_model_resource_detail_view(self):
response = self.client.options('/model/0/')
self._verify_options_response(response,
name='Mock Instance',
description='This is a mock model-based resource',
fields={'foo': 'BooleanField',
'bar': 'IntegerField',
'baz': 'CharField',
})
# def test_options_method_model_resource_detail_view(self):
# response = self.client.options('/model/0/')
# self._verify_options_response(response,
# name='Mock Instance',
# description='This is a mock model-based resource',
# fields={'foo': 'BooleanField',
# 'bar': 'IntegerField',
# 'baz': 'CharField',
# })
def _verify_options_response(self, response, name, description, fields=None, status=200,
mime_type='application/json'):
self.assertEqual(response.status_code, status)
self.assertEqual(response['Content-Type'].split(';')[0], mime_type)
data = json.loads(response.content)
self.assertTrue('application/json' in data['renders'])
self.assertEqual(name, data['name'])
self.assertEqual(description, data['description'])
if fields is None:
self.assertFalse(hasattr(data, 'fields'))
else:
self.assertEqual(data['fields'], fields)
# def _verify_options_response(self, response, name, description, fields=None, status=200,
# mime_type='application/json'):
# self.assertEqual(response.status_code, status)
# self.assertEqual(response['Content-Type'].split(';')[0], mime_type)
# data = json.loads(response.content)
# self.assertTrue('application/json' in data['renders'])
# self.assertEqual(name, data['name'])
# self.assertEqual(description, data['description'])
# if fields is None:
# self.assertFalse(hasattr(data, 'fields'))
# else:
# self.assertEqual(data['fields'], fields)
class ExtraViewsTests(TestCase):
"""Test the extra views djangorestframework provides"""
urls = 'djangorestframework.tests.views'
# class ExtraViewsTests(TestCase):
# """Test the extra views djangorestframework provides"""
# urls = 'djangorestframework.tests.views'
def test_login_view(self):
"""Ensure the login view exists"""
response = self.client.get(reverse('djangorestframework:login'))
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
# def test_login_view(self):
# """Ensure the login view exists"""
# response = self.client.get(reverse('djangorestframework:login'))
# self.assertEqual(response.status_code, 200)
# self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
def test_logout_view(self):
"""Ensure the logout view exists"""
response = self.client.get(reverse('djangorestframework:logout'))
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')
# def test_logout_view(self):
# """Ensure the logout view exists"""
# response = self.client.get(reverse('djangorestframework:logout'))
# self.assertEqual(response.status_code, 200)
# self.assertEqual(response['Content-Type'].split(';')[0], 'text/html')

View File

@ -13,8 +13,7 @@ from django.views.decorators.csrf import csrf_exempt
from djangorestframework.compat import View as DjangoView, apply_markdown
from djangorestframework.response import Response, ImmediateResponse
from djangorestframework.request import Request
from djangorestframework.mixins import *
from djangorestframework import resources, renderers, parsers, authentication, permissions, status
from djangorestframework import renderers, parsers, authentication, permissions, status
__all__ = (
@ -29,7 +28,7 @@ __all__ = (
def _remove_trailing_string(content, trailing):
"""
Strip trailing component `trailing` from `content` if it exists.
Used when generating names from view/resource classes.
Used when generating names from view classes.
"""
if content.endswith(trailing) and content != trailing:
return content[:-len(trailing)]
@ -54,40 +53,26 @@ def _remove_leading_indent(content):
def _camelcase_to_spaces(content):
"""
Translate 'CamelCaseNames' to 'Camel Case Names'.
Used when generating names from view/resource classes.
Used when generating names from view classes.
"""
camelcase_boundry = '(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))'
return re.sub(camelcase_boundry, ' \\1', content).strip()
_resource_classes = (
None,
resources.Resource,
resources.FormResource,
resources.ModelResource
)
class View(ResourceMixin, DjangoView):
class View(DjangoView):
"""
Handles incoming requests and maps them to REST operations.
Performs request deserialization, response serialization, authentication and input validation.
"""
resource = None
"""
The resource to use when validating requests and filtering responses,
or `None` to use default behaviour.
"""
renderers = renderers.DEFAULT_RENDERERS
"""
List of renderer classes the resource can serialize the response with, ordered by preference.
List of renderer classes the view can serialize the response with, ordered by preference.
"""
parsers = parsers.DEFAULT_PARSERS
"""
List of parser classes the resource can parse the request with.
List of parser classes the view can parse the request with.
"""
authentication = (authentication.UserLoggedInAuthentication,
@ -132,17 +117,8 @@ class View(ResourceMixin, DjangoView):
Return the resource or view class name for use as this view's name.
Override to customize.
"""
# If this view has a resource that's been overridden, then use that resource for the name
if getattr(self, 'resource', None) not in _resource_classes:
name = self.resource.__name__
name = _remove_trailing_string(name, 'Resource')
name += getattr(self, '_suffix', '')
# If it's a view class with no resource then grok the name from the class name
else:
name = self.__class__.__name__
name = _remove_trailing_string(name, 'View')
name = self.__class__.__name__
name = _remove_trailing_string(name, 'View')
return _camelcase_to_spaces(name)
def get_description(self, html=False):
@ -150,20 +126,8 @@ class View(ResourceMixin, DjangoView):
Return the resource or view docstring for use as this view's description.
Override to customize.
"""
description = None
# If this view has a resource that's been overridden,
# then try to use the resource's docstring
if getattr(self, 'resource', None) not in _resource_classes:
description = self.resource.__doc__
# Otherwise use the view docstring
if not description:
description = self.__doc__ or ''
description = self.__doc__ or ''
description = _remove_leading_indent(description)
if html:
return self.markup_description(description)
return description
@ -184,7 +148,7 @@ class View(ResourceMixin, DjangoView):
a handler method.
"""
content = {
'detail': "Method '%s' not allowed on this resource." % request.method
'detail': "Method '%s' not allowed." % request.method
}
raise ImmediateResponse(content, status.HTTP_405_METHOD_NOT_ALLOWED)
@ -283,10 +247,6 @@ class View(ResourceMixin, DjangoView):
response = handler(request, *args, **kwargs)
if isinstance(response, Response):
# Pre-serialize filtering (eg filter complex objects into natively serializable types)
response.raw_content = self.filter_response(response.raw_content)
except ImmediateResponse, exc:
response = exc.response
@ -307,31 +267,3 @@ class View(ResourceMixin, DjangoView):
field_name_types[name] = field.__class__.__name__
content['fields'] = field_name_types
raise ImmediateResponse(content, status=status.HTTP_200_OK)
class ModelView(View):
"""
A RESTful view that maps to a model in the database.
"""
resource = resources.ModelResource
class InstanceModelView(ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView):
"""
A view which provides default operations for read/update/delete against a model instance.
"""
_suffix = 'Instance'
class ListModelView(ListModelMixin, ModelView):
"""
A view which provides default operations for list, against a model in the database.
"""
_suffix = 'List'
class ListOrCreateModelView(ListModelMixin, CreateModelMixin, ModelView):
"""
A view which provides default operations for list and create, against a model in the database.
"""
_suffix = 'List'