From aed26b218ea39110489e85abc6f412399a1774a1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 24 Aug 2012 22:11:00 +0100 Subject: [PATCH] Drop out resources & mixins --- djangorestframework/mixins.py | 388 -------------- djangorestframework/permissions.py | 14 - djangorestframework/resources.py | 343 ------------- djangorestframework/serializer.py | 283 ----------- djangorestframework/tests/files.py | 51 +- djangorestframework/tests/mixins.py | 443 ++++++++-------- djangorestframework/tests/modelviews.py | 132 ++--- djangorestframework/tests/serializer.py | 261 +++++----- djangorestframework/tests/throttling.py | 14 +- djangorestframework/tests/validators.py | 639 ++++++++++++------------ djangorestframework/tests/views.py | 209 ++++---- djangorestframework/views.py | 88 +--- 12 files changed, 874 insertions(+), 1991 deletions(-) delete mode 100644 djangorestframework/resources.py delete mode 100644 djangorestframework/serializer.py diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py index 28fa58471..e69de29bb 100644 --- a/djangorestframework/mixins.py +++ b/djangorestframework/mixins.py @@ -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\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 diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py index 4a2b1b00d..ec008bd99 100644 --- a/djangorestframework/permissions.py +++ b/djangorestframework/permissions.py @@ -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__ diff --git a/djangorestframework/resources.py b/djangorestframework/resources.py deleted file mode 100644 index 3f2e5a091..000000000 --- a/djangorestframework/resources.py +++ /dev/null @@ -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)) diff --git a/djangorestframework/serializer.py b/djangorestframework/serializer.py deleted file mode 100644 index 5dea37e81..000000000 --- a/djangorestframework/serializer.py +++ /dev/null @@ -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) diff --git a/djangorestframework/tests/files.py b/djangorestframework/tests/files.py index bbdff70bd..90a613b91 100644 --- a/djangorestframework/tests/files.py +++ b/djangorestframework/tests/files.py @@ -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"}) diff --git a/djangorestframework/tests/mixins.py b/djangorestframework/tests/mixins.py index 25c57bd6e..05ce655dc 100644 --- a/djangorestframework/tests/mixins.py +++ b/djangorestframework/tests/mixins.py @@ -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']) diff --git a/djangorestframework/tests/modelviews.py b/djangorestframework/tests/modelviews.py index ccd8513fd..73cb0b2b1 100644 --- a/djangorestframework/tests/modelviews.py +++ b/djangorestframework/tests/modelviews.py @@ -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[0-9]+)/$', InstanceModelView.as_view(resource=UserResource)), - url(r'^customusers/$', ListOrCreateModelView.as_view(resource=CustomUserResource), name='customusers'), - url(r'^customusers/(?P[0-9]+)/$', InstanceModelView.as_view(resource=CustomUserResource)), - url(r'^groups/$', ListOrCreateModelView.as_view(resource=GroupResource), name='groups'), - url(r'^groups/(?P[0-9]+)/$', InstanceModelView.as_view(resource=GroupResource)), -) +# urlpatterns = patterns('', +# url(r'^users/$', ListOrCreateModelView.as_view(resource=UserResource), name='users'), +# url(r'^users/(?P[0-9]+)/$', InstanceModelView.as_view(resource=UserResource)), +# url(r'^customusers/$', ListOrCreateModelView.as_view(resource=CustomUserResource), name='customusers'), +# url(r'^customusers/(?P[0-9]+)/$', InstanceModelView.as_view(resource=CustomUserResource)), +# url(r'^groups/$', ListOrCreateModelView.as_view(resource=GroupResource), name='groups'), +# url(r'^groups/(?P[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) diff --git a/djangorestframework/tests/serializer.py b/djangorestframework/tests/serializer.py index 834a60d09..7e9f4149f 100644 --- a/djangorestframework/tests/serializer.py +++ b/djangorestframework/tests/serializer.py @@ -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}) diff --git a/djangorestframework/tests/throttling.py b/djangorestframework/tests/throttling.py index 8c5457d35..d307cd32d 100644 --- a/djangorestframework/tests/throttling.py +++ b/djangorestframework/tests/throttling.py @@ -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 diff --git a/djangorestframework/tests/validators.py b/djangorestframework/tests/validators.py index bf2bf8b70..80ad2b17c 100644 --- a/djangorestframework/tests/validators.py +++ b/djangorestframework/tests/validators.py @@ -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)) diff --git a/djangorestframework/tests/views.py b/djangorestframework/tests/views.py index 00bce0021..d4e4098a1 100644 --- a/djangorestframework/tests/views.py +++ b/djangorestframework/tests/views.py @@ -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[^/]+)/$', 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[^/]+)/$', 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') diff --git a/djangorestframework/views.py b/djangorestframework/views.py index 2ce36a9ae..be8f08ae9 100644 --- a/djangorestframework/views.py +++ b/djangorestframework/views.py @@ -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'