mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-30 23:47:53 +03:00 
			
		
		
		
	Decouple views and resources
This commit is contained in:
		
							parent
							
								
									8756664e06
								
							
						
					
					
						commit
						d373b3a067
					
				|  | @ -1,6 +1,6 @@ | ||||||
| """The :mod:`authentication` modules provides for pluggable authentication behaviour. | """The :mod:`authentication` modules provides for pluggable authentication behaviour. | ||||||
| 
 | 
 | ||||||
| Authentication behaviour is provided by adding the mixin class :class:`AuthenticatorMixin` to a :class:`.Resource` or Django :class:`View` class. | Authentication behaviour is provided by adding the mixin class :class:`AuthenticatorMixin` to a :class:`.BaseView` or Django :class:`View` class. | ||||||
| 
 | 
 | ||||||
| The set of authentication which are use is then specified by setting the :attr:`authentication` attribute on the class, and listing a set of authentication classes. | The set of authentication which are use is then specified by setting the :attr:`authentication` attribute on the class, and listing a set of authentication classes. | ||||||
| """ | """ | ||||||
|  | @ -25,10 +25,10 @@ class BaseAuthenticator(object): | ||||||
|         be some more complicated token, for example authentication tokens which are signed |         be some more complicated token, for example authentication tokens which are signed | ||||||
|         against a particular set of permissions for a given user, over a given timeframe. |         against a particular set of permissions for a given user, over a given timeframe. | ||||||
| 
 | 
 | ||||||
|         The default permission checking on Resource will use the allowed_methods attribute |         The default permission checking on View will use the allowed_methods attribute | ||||||
|         for permissions if the authentication context is not None, and use anon_allowed_methods otherwise. |         for permissions if the authentication context is not None, and use anon_allowed_methods otherwise. | ||||||
| 
 | 
 | ||||||
|         The authentication context is available to the method calls eg Resource.get(request) |         The authentication context is available to the method calls eg View.get(request) | ||||||
|         by accessing self.auth in order to allow them to apply any more fine grained permission |         by accessing self.auth in order to allow them to apply any more fine grained permission | ||||||
|         checking at the point the response is being generated. |         checking at the point the response is being generated. | ||||||
|          |          | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | """""" | ||||||
| from djangorestframework.utils.mediatypes import MediaType | from djangorestframework.utils.mediatypes import MediaType | ||||||
| from djangorestframework.utils import as_tuple, MSIE_USER_AGENT_REGEX | from djangorestframework.utils import as_tuple, MSIE_USER_AGENT_REGEX | ||||||
| from djangorestframework.response import ErrorResponse | from djangorestframework.response import ErrorResponse | ||||||
|  | @ -12,6 +13,14 @@ from decimal import Decimal | ||||||
| import re | import re | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | __all__ = ['RequestMixin', | ||||||
|  |            'ResponseMixin', | ||||||
|  |            'AuthMixin', | ||||||
|  |            'ReadModelMixin', | ||||||
|  |            'CreateModelMixin', | ||||||
|  |            'UpdateModelMixin', | ||||||
|  |            'DeleteModelMixin', | ||||||
|  |            'ListModelMixin'] | ||||||
| 
 | 
 | ||||||
| ########## Request Mixin ########## | ########## Request Mixin ########## | ||||||
| 
 | 
 | ||||||
|  | @ -250,7 +259,7 @@ class RequestMixin(object): | ||||||
| ########## ResponseMixin ########## | ########## ResponseMixin ########## | ||||||
| 
 | 
 | ||||||
| class ResponseMixin(object): | class ResponseMixin(object): | ||||||
|     """Adds behaviour for pluggable Renderers to a :class:`.Resource` or Django :class:`View`. class. |     """Adds behaviour for pluggable Renderers to a :class:`.BaseView` or Django :class:`View`. class. | ||||||
|      |      | ||||||
|     Default behaviour is to use standard HTTP Accept header content negotiation. |     Default behaviour is to use standard HTTP Accept header content negotiation. | ||||||
|     Also supports overidding the content type by specifying an _accept= parameter in the URL. |     Also supports overidding the content type by specifying an _accept= parameter in the URL. | ||||||
|  | @ -259,32 +268,8 @@ class ResponseMixin(object): | ||||||
|     ACCEPT_QUERY_PARAM = '_accept'        # Allow override of Accept header in URL query params |     ACCEPT_QUERY_PARAM = '_accept'        # Allow override of Accept header in URL query params | ||||||
|     REWRITE_IE_ACCEPT_HEADER = True |     REWRITE_IE_ACCEPT_HEADER = True | ||||||
| 
 | 
 | ||||||
|     #request = None |  | ||||||
|     #response = None |  | ||||||
|     renderers = () |     renderers = () | ||||||
| 
 | 
 | ||||||
|     #def render_to_response(self, obj): |  | ||||||
|     #    if isinstance(obj, Response): |  | ||||||
|     #        response = obj |  | ||||||
|     #    elif response_obj is not None: |  | ||||||
|     #        response = Response(status.HTTP_200_OK, obj) |  | ||||||
|     #    else: |  | ||||||
|     #        response = Response(status.HTTP_204_NO_CONTENT) |  | ||||||
| 
 |  | ||||||
|     #    response.cleaned_content = self._filter(response.raw_content) |  | ||||||
|          |  | ||||||
|     #    self._render(response) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     #def filter(self, content): |  | ||||||
|     #    """ |  | ||||||
|     #    Filter the response content. |  | ||||||
|     #    """ |  | ||||||
|     #    for filterer_cls in self.filterers: |  | ||||||
|     #        filterer = filterer_cls(self) |  | ||||||
|     #        content = filterer.filter(content) |  | ||||||
|     #    return content |  | ||||||
| 
 |  | ||||||
|          |          | ||||||
|     def render(self, response): |     def render(self, response): | ||||||
|         """Takes a :class:`Response` object and returns a Django :class:`HttpResponse`.""" |         """Takes a :class:`Response` object and returns a Django :class:`HttpResponse`.""" | ||||||
|  | @ -318,7 +303,7 @@ class ResponseMixin(object): | ||||||
| 
 | 
 | ||||||
|     def _determine_renderer(self, request): |     def _determine_renderer(self, request): | ||||||
|         """Return the appropriate renderer for the output, given the client's 'Accept' header, |         """Return the appropriate renderer for the output, given the client's 'Accept' header, | ||||||
|         and the content types that this Resource knows how to serve. |         and the content types that this mixin knows how to serve. | ||||||
|          |          | ||||||
|         See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html""" |         See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html""" | ||||||
| 
 | 
 | ||||||
|  | @ -415,17 +400,6 @@ class AuthMixin(object): | ||||||
|                 return auth |                 return auth | ||||||
|         return None |         return None | ||||||
| 
 | 
 | ||||||
|     # TODO? |  | ||||||
|     #@property |  | ||||||
|     #def user(self): |  | ||||||
|     #    if not has_attr(self, '_user'): |  | ||||||
|     #        auth = self.auth |  | ||||||
|     #        if isinstance(auth, User...): |  | ||||||
|     #            self._user = auth |  | ||||||
|     #        else: |  | ||||||
|     #            self._user = getattr(auth, 'user', None) |  | ||||||
|     #    return self._user |  | ||||||
| 
 |  | ||||||
|     def check_permissions(self): |     def check_permissions(self): | ||||||
|         if not self.permissions: |         if not self.permissions: | ||||||
|             return |             return | ||||||
|  | @ -443,14 +417,15 @@ class AuthMixin(object): | ||||||
| class ReadModelMixin(object): | class ReadModelMixin(object): | ||||||
|     """Behaviour to read a model instance on GET requests""" |     """Behaviour to read a model instance on GET requests""" | ||||||
|     def get(self, request, *args, **kwargs): |     def get(self, request, *args, **kwargs): | ||||||
|  |         model = self.resource.model | ||||||
|         try: |         try: | ||||||
|             if args: |             if args: | ||||||
|                 # If we have any none kwargs then assume the last represents the primrary key |                 # If we have any none kwargs then assume the last represents the primrary key | ||||||
|                 instance = self.model.objects.get(pk=args[-1], **kwargs) |                 instance = model.objects.get(pk=args[-1], **kwargs) | ||||||
|             else: |             else: | ||||||
|                 # Otherwise assume the kwargs uniquely identify the model |                 # Otherwise assume the kwargs uniquely identify the model | ||||||
|                 instance = self.model.objects.get(**kwargs) |                 instance = model.objects.get(**kwargs) | ||||||
|         except self.model.DoesNotExist: |         except model.DoesNotExist: | ||||||
|             raise ErrorResponse(status.HTTP_404_NOT_FOUND) |             raise ErrorResponse(status.HTTP_404_NOT_FOUND) | ||||||
| 
 | 
 | ||||||
|         return instance |         return instance | ||||||
|  | @ -459,17 +434,18 @@ class ReadModelMixin(object): | ||||||
| class CreateModelMixin(object): | class CreateModelMixin(object): | ||||||
|     """Behaviour to create a model instance on POST requests""" |     """Behaviour to create a model instance on POST requests""" | ||||||
|     def post(self, request, *args, **kwargs):         |     def post(self, request, *args, **kwargs):         | ||||||
|  |         model = self.resource.model | ||||||
|         # translated 'related_field' kwargs into 'related_field_id' |         # translated 'related_field' kwargs into 'related_field_id' | ||||||
|         for related_name in [field.name for field in self.model._meta.fields if isinstance(field, RelatedField)]: |         for related_name in [field.name for field in model._meta.fields if isinstance(field, RelatedField)]: | ||||||
|             if kwargs.has_key(related_name): |             if kwargs.has_key(related_name): | ||||||
|                 kwargs[related_name + '_id'] = kwargs[related_name] |                 kwargs[related_name + '_id'] = kwargs[related_name] | ||||||
|                 del kwargs[related_name] |                 del kwargs[related_name] | ||||||
| 
 | 
 | ||||||
|         all_kw_args = dict(self.CONTENT.items() + kwargs.items()) |         all_kw_args = dict(self.CONTENT.items() + kwargs.items()) | ||||||
|         if args: |         if args: | ||||||
|             instance = self.model(pk=args[-1], **all_kw_args) |             instance = model(pk=args[-1], **all_kw_args) | ||||||
|         else: |         else: | ||||||
|             instance = self.model(**all_kw_args) |             instance = model(**all_kw_args) | ||||||
|         instance.save() |         instance.save() | ||||||
|         headers = {} |         headers = {} | ||||||
|         if hasattr(instance, 'get_absolute_url'): |         if hasattr(instance, 'get_absolute_url'): | ||||||
|  | @ -480,19 +456,20 @@ class CreateModelMixin(object): | ||||||
| class UpdateModelMixin(object): | class UpdateModelMixin(object): | ||||||
|     """Behaviour to update a model instance on PUT requests""" |     """Behaviour to update a model instance on PUT requests""" | ||||||
|     def put(self, request, *args, **kwargs): |     def put(self, request, *args, **kwargs): | ||||||
|  |         model = self.resource.model | ||||||
|         # 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  |         # 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: |         try: | ||||||
|             if args: |             if args: | ||||||
|                 # If we have any none kwargs then assume the last represents the primrary key |                 # If we have any none kwargs then assume the last represents the primrary key | ||||||
|                 instance = self.model.objects.get(pk=args[-1], **kwargs) |                 instance = model.objects.get(pk=args[-1], **kwargs) | ||||||
|             else: |             else: | ||||||
|                 # Otherwise assume the kwargs uniquely identify the model |                 # Otherwise assume the kwargs uniquely identify the model | ||||||
|                 instance = self.model.objects.get(**kwargs) |                 instance = model.objects.get(**kwargs) | ||||||
| 
 | 
 | ||||||
|             for (key, val) in self.CONTENT.items(): |             for (key, val) in self.CONTENT.items(): | ||||||
|                 setattr(instance, key, val) |                 setattr(instance, key, val) | ||||||
|         except self.model.DoesNotExist: |         except model.DoesNotExist: | ||||||
|             instance = self.model(**self.CONTENT) |             instance = model(**self.CONTENT) | ||||||
|             instance.save() |             instance.save() | ||||||
| 
 | 
 | ||||||
|         instance.save() |         instance.save() | ||||||
|  | @ -502,14 +479,15 @@ class UpdateModelMixin(object): | ||||||
| class DeleteModelMixin(object): | class DeleteModelMixin(object): | ||||||
|     """Behaviour to delete a model instance on DELETE requests""" |     """Behaviour to delete a model instance on DELETE requests""" | ||||||
|     def delete(self, request, *args, **kwargs): |     def delete(self, request, *args, **kwargs): | ||||||
|  |         model = self.resource.model | ||||||
|         try: |         try: | ||||||
|             if args: |             if args: | ||||||
|                 # If we have any none kwargs then assume the last represents the primrary key |                 # If we have any none kwargs then assume the last represents the primrary key | ||||||
|                 instance = self.model.objects.get(pk=args[-1], **kwargs) |                 instance = model.objects.get(pk=args[-1], **kwargs) | ||||||
|             else: |             else: | ||||||
|                 # Otherwise assume the kwargs uniquely identify the model |                 # Otherwise assume the kwargs uniquely identify the model | ||||||
|                 instance = self.model.objects.get(**kwargs) |                 instance = model.objects.get(**kwargs) | ||||||
|         except self.model.DoesNotExist: |         except model.DoesNotExist: | ||||||
|             raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {}) |             raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {}) | ||||||
| 
 | 
 | ||||||
|         instance.delete() |         instance.delete() | ||||||
|  |  | ||||||
|  | @ -1,356 +0,0 @@ | ||||||
| from django.forms import ModelForm |  | ||||||
| from django.db.models import Model |  | ||||||
| from django.db.models.query import QuerySet |  | ||||||
| from django.db.models.fields.related import RelatedField |  | ||||||
| 
 |  | ||||||
| from djangorestframework.response import Response, ErrorResponse |  | ||||||
| from djangorestframework.resource import Resource |  | ||||||
| from djangorestframework import status, validators |  | ||||||
| 
 |  | ||||||
| import decimal |  | ||||||
| import inspect |  | ||||||
| import re |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class ModelResource(Resource): |  | ||||||
|     """A specialized type of Resource, for resources that map directly to a Django Model. |  | ||||||
|     Useful things this provides: |  | ||||||
| 
 |  | ||||||
|     0. Default input validation based on ModelForms. |  | ||||||
|     1. Nice serialization of returned Models and QuerySets. |  | ||||||
|     2. A default set of create/read/update/delete operations.""" |  | ||||||
|      |  | ||||||
|     # List of validators to validate, cleanup and type-ify the request content     |  | ||||||
|     validators = (validators.ModelFormValidator,) |  | ||||||
|      |  | ||||||
|     # 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 |  | ||||||
|      |  | ||||||
|     # By default the form used with be a ModelForm for self.model |  | ||||||
|     # If you wish to override this behaviour or provide a sub-classed ModelForm |  | ||||||
|     # you should explicitly set the form attribute on your class. |  | ||||||
|     form = None |  | ||||||
|      |  | ||||||
|     # By default the set of input fields will be the same as the set of output fields |  | ||||||
|     # If you wish to override this behaviour you should explicitly set the |  | ||||||
|     # form_fields attribute on your class.  |  | ||||||
|     #form_fields = None |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     #def get_form(self, content=None): |  | ||||||
|     #    """Return a form that may be used in validation and/or rendering an html renderer""" |  | ||||||
|     #    if self.form: |  | ||||||
|     #        return super(self.__class__, self).get_form(content) |  | ||||||
|     # |  | ||||||
|     #    elif self.model: |  | ||||||
|     # |  | ||||||
|     #        class NewModelForm(ModelForm): |  | ||||||
|     #            class Meta: |  | ||||||
|     #                model = self.model |  | ||||||
|     #                fields = self.form_fields if self.form_fields else None |  | ||||||
|     # |  | ||||||
|     #        if content and isinstance(content, Model): |  | ||||||
|     #            return NewModelForm(instance=content) |  | ||||||
|     #        elif content: |  | ||||||
|     #            return NewModelForm(content) |  | ||||||
|     #         |  | ||||||
|     #        return NewModelForm() |  | ||||||
|     # |  | ||||||
|     #    return None |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     #def cleanup_request(self, data, form_instance): |  | ||||||
|     #    """Override cleanup_request to drop read-only fields from the input prior to validation. |  | ||||||
|     #    This ensures that we don't error out with 'non-existent field' when these fields are supplied, |  | ||||||
|     #    and allows for a pragmatic approach to resources which include read-only elements. |  | ||||||
|     # |  | ||||||
|     #    I would actually like to be strict and verify the value of correctness of the values in these fields, |  | ||||||
|     #    although that gets tricky as it involves validating at the point that we get the model instance. |  | ||||||
|     #     |  | ||||||
|     #    See here for another example of this approach: |  | ||||||
|     #    http://fedoraproject.org/wiki/Cloud_APIs_REST_Style_Guide |  | ||||||
|     #    https://www.redhat.com/archives/rest-practices/2010-April/thread.html#00041""" |  | ||||||
|     #    read_only_fields = set(self.fields) - set(self.form_instance.fields) |  | ||||||
|     #    input_fields = set(data.keys()) |  | ||||||
|     # |  | ||||||
|     #    clean_data = {} |  | ||||||
|     #    for key in input_fields - read_only_fields: |  | ||||||
|     #        clean_data[key] = data[key] |  | ||||||
|     # |  | ||||||
|     #    return super(ModelResource, self).cleanup_request(clean_data, form_instance) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     def cleanup_response(self, data): |  | ||||||
|         """A munging of Piston's pre-serialization.  Returns a dict""" |  | ||||||
| 
 |  | ||||||
|         def _any(thing, fields=()): |  | ||||||
|             """ |  | ||||||
|             Dispatch, all types are routed through here. |  | ||||||
|             """ |  | ||||||
|             ret = None |  | ||||||
|              |  | ||||||
|             if isinstance(thing, QuerySet): |  | ||||||
|                 ret = _qs(thing, fields=fields) |  | ||||||
|             elif isinstance(thing, (tuple, list)): |  | ||||||
|                 ret = _list(thing) |  | ||||||
|             elif isinstance(thing, dict): |  | ||||||
|                 ret = _dict(thing) |  | ||||||
|             elif isinstance(thing, int): |  | ||||||
|                 ret = thing |  | ||||||
|             elif isinstance(thing, bool): |  | ||||||
|                 ret = thing |  | ||||||
|             elif isinstance(thing, type(None)): |  | ||||||
|                 ret = thing |  | ||||||
|             elif isinstance(thing, decimal.Decimal): |  | ||||||
|                 ret = str(thing) |  | ||||||
|             elif isinstance(thing, Model): |  | ||||||
|                 ret = _model(thing, fields=fields) |  | ||||||
|             #elif isinstance(thing, HttpResponse):    TRC |  | ||||||
|             #    raise HttpStatusCode(thing) |  | ||||||
|             elif inspect.isfunction(thing): |  | ||||||
|                 if not inspect.getargspec(thing)[0]: |  | ||||||
|                     ret = _any(thing()) |  | ||||||
|             elif hasattr(thing, '__rendertable__'): |  | ||||||
|                 f = thing.__rendertable__ |  | ||||||
|                 if inspect.ismethod(f) and len(inspect.getargspec(f)[0]) == 1: |  | ||||||
|                     ret = _any(f()) |  | ||||||
|             else: |  | ||||||
|                 ret = unicode(thing)  # TRC  TODO: Change this back! |  | ||||||
| 
 |  | ||||||
|             return ret |  | ||||||
| 
 |  | ||||||
|         def _fk(data, field): |  | ||||||
|             """ |  | ||||||
|             Foreign keys. |  | ||||||
|             """ |  | ||||||
|             return _any(getattr(data, field.name)) |  | ||||||
|          |  | ||||||
|         def _related(data, fields=()): |  | ||||||
|             """ |  | ||||||
|             Foreign keys. |  | ||||||
|             """ |  | ||||||
|             return [ _model(m, fields) for m in data.iterator() ] |  | ||||||
|          |  | ||||||
|         def _m2m(data, field, fields=()): |  | ||||||
|             """ |  | ||||||
|             Many to many (re-route to `_model`.) |  | ||||||
|             """ |  | ||||||
|             return [ _model(m, fields) for m in getattr(data, field.name).iterator() ] |  | ||||||
|          |  | ||||||
| 
 |  | ||||||
|         def _method_fields(data, fields): |  | ||||||
|             if not data: |  | ||||||
|                 return { } |  | ||||||
|      |  | ||||||
|             has = dir(data) |  | ||||||
|             ret = dict() |  | ||||||
|                  |  | ||||||
|             for field in fields: |  | ||||||
|                 if field in has: |  | ||||||
|                     ret[field] = getattr(data, field) |  | ||||||
|              |  | ||||||
|             return ret |  | ||||||
| 
 |  | ||||||
|         def _model(data, fields=()): |  | ||||||
|             """ |  | ||||||
|             Models. Will respect the `fields` and/or |  | ||||||
|             `exclude` on the handler (see `typemapper`.) |  | ||||||
|             """ |  | ||||||
|             ret = { } |  | ||||||
|             #handler = self.in_typemapper(type(data), self.anonymous)  # TRC |  | ||||||
|             handler = None                                             # TRC |  | ||||||
|             get_absolute_url = False |  | ||||||
|              |  | ||||||
|             if handler or fields: |  | ||||||
|                 v = lambda f: getattr(data, f.attname) |  | ||||||
| 
 |  | ||||||
|                 if not fields: |  | ||||||
|                     """ |  | ||||||
|                     Fields was not specified, try to find teh correct |  | ||||||
|                     version in the typemapper we were sent. |  | ||||||
|                     """ |  | ||||||
|                     mapped = self.in_typemapper(type(data), self.anonymous) |  | ||||||
|                     get_fields = set(mapped.fields) |  | ||||||
|                     exclude_fields = set(mapped.exclude).difference(get_fields) |  | ||||||
|                  |  | ||||||
|                     if not get_fields: |  | ||||||
|                         get_fields = set([ f.attname.replace("_id", "", 1) |  | ||||||
|                             for f in data._meta.fields ]) |  | ||||||
|                  |  | ||||||
|                     # sets can be negated. |  | ||||||
|                     for exclude in exclude_fields: |  | ||||||
|                         if isinstance(exclude, basestring): |  | ||||||
|                             get_fields.discard(exclude) |  | ||||||
|                              |  | ||||||
|                         elif isinstance(exclude, re._pattern_type): |  | ||||||
|                             for field in get_fields.copy(): |  | ||||||
|                                 if exclude.match(field): |  | ||||||
|                                     get_fields.discard(field) |  | ||||||
|                      |  | ||||||
|                     get_absolute_url = True |  | ||||||
| 
 |  | ||||||
|                 else: |  | ||||||
|                     get_fields = set(fields) |  | ||||||
|                     if 'absolute_url' in get_fields:   # MOVED (TRC) |  | ||||||
|                         get_absolute_url = True |  | ||||||
| 
 |  | ||||||
|                 met_fields = _method_fields(handler, get_fields)  # TRC |  | ||||||
| 
 |  | ||||||
|                 for f in data._meta.local_fields: |  | ||||||
|                     if f.serialize and not any([ p in met_fields for p in [ f.attname, f.name ]]): |  | ||||||
|                         if not f.rel: |  | ||||||
|                             if f.attname in get_fields: |  | ||||||
|                                 ret[f.attname] = _any(v(f)) |  | ||||||
|                                 get_fields.remove(f.attname) |  | ||||||
|                         else: |  | ||||||
|                             if f.attname[:-3] in get_fields: |  | ||||||
|                                 ret[f.name] = _fk(data, f) |  | ||||||
|                                 get_fields.remove(f.name) |  | ||||||
|                  |  | ||||||
|                 for mf in data._meta.many_to_many: |  | ||||||
|                     if mf.serialize and mf.attname not in met_fields: |  | ||||||
|                         if mf.attname in get_fields: |  | ||||||
|                             ret[mf.name] = _m2m(data, mf) |  | ||||||
|                             get_fields.remove(mf.name) |  | ||||||
|                  |  | ||||||
|                 # try to get the remainder of fields |  | ||||||
|                 for maybe_field in get_fields: |  | ||||||
|                      |  | ||||||
|                     if isinstance(maybe_field, (list, tuple)): |  | ||||||
|                         model, fields = maybe_field |  | ||||||
|                         inst = getattr(data, model, None) |  | ||||||
| 
 |  | ||||||
|                         if inst: |  | ||||||
|                             if hasattr(inst, 'all'): |  | ||||||
|                                 ret[model] = _related(inst, fields) |  | ||||||
|                             elif callable(inst): |  | ||||||
|                                 if len(inspect.getargspec(inst)[0]) == 1: |  | ||||||
|                                     ret[model] = _any(inst(), fields) |  | ||||||
|                             else: |  | ||||||
|                                 ret[model] = _model(inst, fields) |  | ||||||
| 
 |  | ||||||
|                     elif maybe_field in met_fields: |  | ||||||
|                         # Overriding normal field which has a "resource method" |  | ||||||
|                         # so you can alter the contents of certain fields without |  | ||||||
|                         # using different names. |  | ||||||
|                         ret[maybe_field] = _any(met_fields[maybe_field](data)) |  | ||||||
| 
 |  | ||||||
|                     else:                     |  | ||||||
|                         maybe = getattr(data, maybe_field, None) |  | ||||||
|                         if maybe: |  | ||||||
|                             if callable(maybe): |  | ||||||
|                                 if len(inspect.getargspec(maybe)[0]) == 1: |  | ||||||
|                                     ret[maybe_field] = _any(maybe()) |  | ||||||
|                             else: |  | ||||||
|                                 ret[maybe_field] = _any(maybe) |  | ||||||
|                         else: |  | ||||||
|                             pass   # TRC |  | ||||||
|                             #handler_f = getattr(handler or self.handler, maybe_field, None) |  | ||||||
|                             # |  | ||||||
|                             #if handler_f: |  | ||||||
|                             #    ret[maybe_field] = _any(handler_f(data)) |  | ||||||
| 
 |  | ||||||
|             else: |  | ||||||
|                 # Add absolute_url if it exists |  | ||||||
|                 get_absolute_url = True |  | ||||||
|                  |  | ||||||
|                 # Add all the fields |  | ||||||
|                 for f in data._meta.fields: |  | ||||||
|                     if f.attname != 'id': |  | ||||||
|                         ret[f.attname] = _any(getattr(data, f.attname)) |  | ||||||
|                  |  | ||||||
|                 # Add all the propertiess |  | ||||||
|                 klass = data.__class__ |  | ||||||
|                 for attr in dir(klass): |  | ||||||
|                     if not attr.startswith('_') and not attr in ('pk','id') and isinstance(getattr(klass, attr, None), property): |  | ||||||
|                         #if attr.endswith('_url') or attr.endswith('_uri'): |  | ||||||
|                         #    ret[attr] = self.make_absolute(_any(getattr(data, attr))) |  | ||||||
|                         #else: |  | ||||||
|                         ret[attr] = _any(getattr(data, attr)) |  | ||||||
|                 #fields = dir(data.__class__) + ret.keys() |  | ||||||
|                 #add_ons = [k for k in dir(data) if k not in fields and not k.startswith('_')] |  | ||||||
|                 #print add_ons |  | ||||||
|                 ###print dir(data.__class__) |  | ||||||
|                 #from django.db.models import Model |  | ||||||
|                 #model_fields = dir(Model) |  | ||||||
| 
 |  | ||||||
|                 #for attr in dir(data): |  | ||||||
|                 ##    #if attr.startswith('_'): |  | ||||||
|                 ##    #    continue |  | ||||||
|                 #    if (attr in fields) and not (attr in model_fields) and not attr.startswith('_'): |  | ||||||
|                 #        print attr, type(getattr(data, attr, None)), attr in fields, attr in model_fields |  | ||||||
|                  |  | ||||||
|                 #for k in add_ons: |  | ||||||
|                 #    ret[k] = _any(getattr(data, k)) |  | ||||||
|              |  | ||||||
|             # TRC |  | ||||||
|             # resouce uri |  | ||||||
|             #if self.in_typemapper(type(data), self.anonymous): |  | ||||||
|             #    handler = self.in_typemapper(type(data), self.anonymous) |  | ||||||
|             #    if hasattr(handler, 'resource_uri'): |  | ||||||
|             #        url_id, fields = handler.resource_uri() |  | ||||||
|             #        ret['resource_uri'] = permalink( lambda: (url_id,  |  | ||||||
|             #            (getattr(data, f) for f in fields) ) )() |  | ||||||
|              |  | ||||||
|             # TRC |  | ||||||
|             #if hasattr(data, 'get_api_url') and 'resource_uri' not in ret: |  | ||||||
|             #    try: ret['resource_uri'] = data.get_api_url() |  | ||||||
|             #    except: pass |  | ||||||
|              |  | ||||||
|             # absolute uri |  | ||||||
|             if hasattr(data, 'get_absolute_url') and get_absolute_url: |  | ||||||
|                 try: ret['absolute_url'] = data.get_absolute_url() |  | ||||||
|                 except: pass |  | ||||||
|              |  | ||||||
|             #for key, val in ret.items(): |  | ||||||
|             #    if key.endswith('_url') or key.endswith('_uri'): |  | ||||||
|             #        ret[key] = self.add_domain(val) |  | ||||||
| 
 |  | ||||||
|             return ret |  | ||||||
|          |  | ||||||
|         def _qs(data, fields=()): |  | ||||||
|             """ |  | ||||||
|             Querysets. |  | ||||||
|             """ |  | ||||||
|             return [ _any(v, fields) for v in data ] |  | ||||||
|                  |  | ||||||
|         def _list(data): |  | ||||||
|             """ |  | ||||||
|             Lists. |  | ||||||
|             """ |  | ||||||
|             return [ _any(v) for v in data ] |  | ||||||
|              |  | ||||||
|         def _dict(data): |  | ||||||
|             """ |  | ||||||
|             Dictionaries. |  | ||||||
|             """ |  | ||||||
|             return dict([ (k, _any(v)) for k, v in data.iteritems() ]) |  | ||||||
|              |  | ||||||
|         # Kickstart the seralizin'. |  | ||||||
|         return _any(data, self.fields) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|          |  | ||||||
| 
 |  | ||||||
| class InstanceModelResource(ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelResource): |  | ||||||
|     """A view which provides default operations for read/update/delete against a model instance.""" |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| class ListOrCreateModelResource(CreateModelMixin, ListModelMixin, ModelResource): |  | ||||||
|     """A Resource which provides default operations for list and create.""" |  | ||||||
|     pass |  | ||||||
| 
 |  | ||||||
| class ListModelResource(ListModelMixin, ModelResource): |  | ||||||
|     """Resource with default operations for list.""" |  | ||||||
|     pass |  | ||||||
|  | @ -11,6 +11,12 @@ class BasePermission(object): | ||||||
|     def has_permission(self, auth): |     def has_permission(self, auth): | ||||||
|         return True |         return True | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | class FullAnonAccess(BasePermission): | ||||||
|  |     """""" | ||||||
|  |     def has_permission(self, auth): | ||||||
|  |         return True | ||||||
|  | 
 | ||||||
| class IsAuthenticated(BasePermission): | class IsAuthenticated(BasePermission): | ||||||
|     """""" |     """""" | ||||||
|     def has_permission(self, auth): |     def has_permission(self, auth): | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| """Renderers are used to serialize a Resource's output into specific media types. | """Renderers are used to serialize a View's output into specific media types. | ||||||
| django-rest-framework also provides HTML and PlainText renderers that help self-document the API, | django-rest-framework also provides HTML and PlainText renderers that help self-document the API, | ||||||
| by serializing the output along with documentation regarding the Resource, output status and headers, | by serializing the output along with documentation regarding the Resource, output status and headers, | ||||||
| and providing forms and links depending on the allowed methods, renderers and parsers on the Resource.  | and providing forms and links depending on the allowed methods, renderers and parsers on the Resource.  | ||||||
|  |  | ||||||
|  | @ -1,130 +1,251 @@ | ||||||
| from django.core.urlresolvers import set_script_prefix | from django.db.models import Model | ||||||
| from django.views.decorators.csrf import csrf_exempt | from django.db.models.query import QuerySet | ||||||
|  | from django.db.models.fields.related import RelatedField | ||||||
| 
 | 
 | ||||||
| from djangorestframework.compat import View | import decimal | ||||||
| from djangorestframework.response import Response, ErrorResponse | import inspect | ||||||
| from djangorestframework.mixins import RequestMixin, ResponseMixin, AuthMixin | import re | ||||||
| from djangorestframework import renderers, parsers, authentication, permissions, validators, status |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # TODO: Figure how out references and named urls need to work nicely | class Resource(object): | ||||||
| # TODO: POST on existing 404 URL, PUT on existing 404 URL |     """A Resource determines how an object maps to a serializable entity. | ||||||
| # |     Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets.""" | ||||||
| # NEXT: Exceptions on func() -> 500, tracebacks renderted if settings.DEBUG |  | ||||||
|      |      | ||||||
| __all__ = ['Resource'] |     # 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 | ||||||
|  | 
 | ||||||
|  |     @classmethod | ||||||
|  |     def object_to_serializable(self, data): | ||||||
|  |         """A (horrible) munging of Piston's pre-serialization.  Returns a dict""" | ||||||
|  | 
 | ||||||
|  |         def _any(thing, fields=()): | ||||||
|  |             """ | ||||||
|  |             Dispatch, all types are routed through here. | ||||||
|  |             """ | ||||||
|  |             ret = None | ||||||
|  |              | ||||||
|  |             if isinstance(thing, QuerySet): | ||||||
|  |                 ret = _qs(thing, fields=fields) | ||||||
|  |             elif isinstance(thing, (tuple, list)): | ||||||
|  |                 ret = _list(thing) | ||||||
|  |             elif isinstance(thing, dict): | ||||||
|  |                 ret = _dict(thing) | ||||||
|  |             elif isinstance(thing, int): | ||||||
|  |                 ret = thing | ||||||
|  |             elif isinstance(thing, bool): | ||||||
|  |                 ret = thing | ||||||
|  |             elif isinstance(thing, type(None)): | ||||||
|  |                 ret = thing | ||||||
|  |             elif isinstance(thing, decimal.Decimal): | ||||||
|  |                 ret = str(thing) | ||||||
|  |             elif isinstance(thing, Model): | ||||||
|  |                 ret = _model(thing, fields=fields) | ||||||
|  |             #elif isinstance(thing, HttpResponse):    TRC | ||||||
|  |             #    raise HttpStatusCode(thing) | ||||||
|  |             elif inspect.isfunction(thing): | ||||||
|  |                 if not inspect.getargspec(thing)[0]: | ||||||
|  |                     ret = _any(thing()) | ||||||
|  |             elif hasattr(thing, '__rendertable__'): | ||||||
|  |                 f = thing.__rendertable__ | ||||||
|  |                 if inspect.ismethod(f) and len(inspect.getargspec(f)[0]) == 1: | ||||||
|  |                     ret = _any(f()) | ||||||
|  |             else: | ||||||
|  |                 ret = unicode(thing)  # TRC  TODO: Change this back! | ||||||
|  | 
 | ||||||
|  |             return ret | ||||||
|  | 
 | ||||||
|  |         def _fk(data, field): | ||||||
|  |             """ | ||||||
|  |             Foreign keys. | ||||||
|  |             """ | ||||||
|  |             return _any(getattr(data, field.name)) | ||||||
|  |          | ||||||
|  |         def _related(data, fields=()): | ||||||
|  |             """ | ||||||
|  |             Foreign keys. | ||||||
|  |             """ | ||||||
|  |             return [ _model(m, fields) for m in data.iterator() ] | ||||||
|  |          | ||||||
|  |         def _m2m(data, field, fields=()): | ||||||
|  |             """ | ||||||
|  |             Many to many (re-route to `_model`.) | ||||||
|  |             """ | ||||||
|  |             return [ _model(m, fields) for m in getattr(data, field.name).iterator() ] | ||||||
|          |          | ||||||
| 
 | 
 | ||||||
| class Resource(RequestMixin, ResponseMixin, AuthMixin, View): |         def _method_fields(data, fields): | ||||||
|     """Handles incoming requests and maps them to REST operations. |             if not data: | ||||||
|     Performs request deserialization, response serialization, authentication and input validation.""" |                 return { } | ||||||
|      |      | ||||||
|     # List of renderers the resource can serialize the response with, ordered by preference. |             has = dir(data) | ||||||
|     renderers = ( renderers.JSONRenderer, |             ret = dict() | ||||||
|                   renderers.DocumentingHTMLRenderer, |  | ||||||
|                   renderers.DocumentingXHTMLRenderer, |  | ||||||
|                   renderers.DocumentingPlainTextRenderer, |  | ||||||
|                   renderers.XMLRenderer ) |  | ||||||
|                  |                  | ||||||
|     # List of parsers the resource can parse the request with. |             for field in fields: | ||||||
|     parsers = ( parsers.JSONParser, |                 if field in has: | ||||||
|                 parsers.FormParser, |                     ret[field] = getattr(data, field) | ||||||
|                 parsers.MultipartParser ) |  | ||||||
|              |              | ||||||
|     # List of validators to validate, cleanup and normalize the request content     |             return ret | ||||||
|     validators = ( validators.FormValidator, ) |  | ||||||
| 
 | 
 | ||||||
|     # List of all authenticating methods to attempt. |         def _model(data, fields=()): | ||||||
|     authentication = ( authentication.UserLoggedInAuthenticator, |             """ | ||||||
|                        authentication.BasicAuthenticator ) |             Models. Will respect the `fields` and/or | ||||||
|  |             `exclude` on the handler (see `typemapper`.) | ||||||
|  |             """ | ||||||
|  |             ret = { } | ||||||
|  |             #handler = self.in_typemapper(type(data), self.anonymous)  # TRC | ||||||
|  |             handler = None                                             # TRC | ||||||
|  |             get_absolute_url = False | ||||||
|              |              | ||||||
|     # List of all permissions required to access the resource |             if fields: | ||||||
|     permissions = () |                 v = lambda f: getattr(data, f.attname) | ||||||
| 
 | 
 | ||||||
|     # Optional form for input validation and presentation of HTML formatted responses. |                 get_fields = set(fields) | ||||||
|     form = None |                 if 'absolute_url' in get_fields:   # MOVED (TRC) | ||||||
|  |                     get_absolute_url = True | ||||||
| 
 | 
 | ||||||
|     # Allow name and description for the Resource to be set explicitly, |                 met_fields = _method_fields(handler, get_fields)  # TRC | ||||||
|     # overiding the default classname/docstring behaviour. |  | ||||||
|     # These are used for documentation in the standard html and text renderers. |  | ||||||
|     name = None |  | ||||||
|     description = None |  | ||||||
| 
 | 
 | ||||||
|     @property |                 for f in data._meta.local_fields: | ||||||
|     def allowed_methods(self): |                     if f.serialize and not any([ p in met_fields for p in [ f.attname, f.name ]]): | ||||||
|         return [method.upper() for method in self.http_method_names if hasattr(self, method)] |                         if not f.rel: | ||||||
|  |                             if f.attname in get_fields: | ||||||
|  |                                 ret[f.attname] = _any(v(f)) | ||||||
|  |                                 get_fields.remove(f.attname) | ||||||
|  |                         else: | ||||||
|  |                             if f.attname[:-3] in get_fields: | ||||||
|  |                                 ret[f.name] = _fk(data, f) | ||||||
|  |                                 get_fields.remove(f.name) | ||||||
|                  |                  | ||||||
|     def http_method_not_allowed(self, request, *args, **kwargs): |                 for mf in data._meta.many_to_many: | ||||||
|         """Return an HTTP 405 error if an operation is called which does not have a handler method.""" |                     if mf.serialize and mf.attname not in met_fields: | ||||||
|         raise ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED, |                         if mf.attname in get_fields: | ||||||
|                                 {'detail': 'Method \'%s\' not allowed on this resource.' % self.method}) |                             ret[mf.name] = _m2m(data, mf) | ||||||
|  |                             get_fields.remove(mf.name) | ||||||
|                  |                  | ||||||
|  |                 # try to get the remainder of fields | ||||||
|  |                 for maybe_field in get_fields: | ||||||
|                      |                      | ||||||
|     def cleanup_response(self, data): |                     if isinstance(maybe_field, (list, tuple)): | ||||||
|         """Perform any resource-specific data filtering prior to the standard HTTP |                         model, fields = maybe_field | ||||||
|         content-type serialization. |                         inst = getattr(data, model, None) | ||||||
| 
 | 
 | ||||||
|         Eg filter complex objects that cannot be serialized by json/xml/etc into basic objects that can. |                         if inst: | ||||||
|  |                             if hasattr(inst, 'all'): | ||||||
|  |                                 ret[model] = _related(inst, fields) | ||||||
|  |                             elif callable(inst): | ||||||
|  |                                 if len(inspect.getargspec(inst)[0]) == 1: | ||||||
|  |                                     ret[model] = _any(inst(), fields) | ||||||
|  |                             else: | ||||||
|  |                                 ret[model] = _model(inst, fields) | ||||||
| 
 | 
 | ||||||
|         TODO: This is going to be removed.  I think that the 'fields' behaviour is going to move into |                     elif maybe_field in met_fields: | ||||||
|         the RendererMixin and Renderer classes.""" |                         # Overriding normal field which has a "resource method" | ||||||
|         return data |                         # so you can alter the contents of certain fields without | ||||||
|  |                         # using different names. | ||||||
|  |                         ret[maybe_field] = _any(met_fields[maybe_field](data)) | ||||||
| 
 | 
 | ||||||
|  |                     else:                     | ||||||
|  |                         maybe = getattr(data, maybe_field, None) | ||||||
|  |                         if maybe: | ||||||
|  |                             if callable(maybe): | ||||||
|  |                                 if len(inspect.getargspec(maybe)[0]) == 1: | ||||||
|  |                                     ret[maybe_field] = _any(maybe()) | ||||||
|  |                             else: | ||||||
|  |                                 ret[maybe_field] = _any(maybe) | ||||||
|  |                         else: | ||||||
|  |                             pass   # TRC | ||||||
|  |                             #handler_f = getattr(handler or self.handler, maybe_field, None) | ||||||
|  |                             # | ||||||
|  |                             #if handler_f: | ||||||
|  |                             #    ret[maybe_field] = _any(handler_f(data)) | ||||||
| 
 | 
 | ||||||
|     # Note: session based authentication is explicitly CSRF validated, |             else: | ||||||
|     # all other authentication is CSRF exempt. |                 # Add absolute_url if it exists | ||||||
|     @csrf_exempt |                 get_absolute_url = True | ||||||
|     def dispatch(self, request, *args, **kwargs): |  | ||||||
|         try: |  | ||||||
|             self.request = request |  | ||||||
|             self.args = args |  | ||||||
|             self.kwargs = kwargs |  | ||||||
|                  |                  | ||||||
|             # Calls to 'reverse' will not be fully qualified unless we set the scheme/host/port here. |                 # Add all the fields | ||||||
|             prefix = '%s://%s' % (request.is_secure() and 'https' or 'http', request.get_host()) |                 for f in data._meta.fields: | ||||||
|             set_script_prefix(prefix) |                     if f.attname != 'id': | ||||||
|  |                         ret[f.attname] = _any(getattr(data, f.attname)) | ||||||
|                  |                  | ||||||
|             try: |                 # Add all the propertiess | ||||||
|                 # If using a form POST with '_method'/'_content'/'_content_type' overrides, then alter |                 klass = data.__class__ | ||||||
|                 # self.method, self.content_type, self.RAW_CONTENT & self.CONTENT appropriately. |                 for attr in dir(klass): | ||||||
|                 self.perform_form_overloading() |                     if not attr.startswith('_') and not attr in ('pk','id') and isinstance(getattr(klass, attr, None), property): | ||||||
|  |                         #if attr.endswith('_url') or attr.endswith('_uri'): | ||||||
|  |                         #    ret[attr] = self.make_absolute(_any(getattr(data, attr))) | ||||||
|  |                         #else: | ||||||
|  |                         ret[attr] = _any(getattr(data, attr)) | ||||||
|  |                 #fields = dir(data.__class__) + ret.keys() | ||||||
|  |                 #add_ons = [k for k in dir(data) if k not in fields and not k.startswith('_')] | ||||||
|  |                 #print add_ons | ||||||
|  |                 ###print dir(data.__class__) | ||||||
|  |                 #from django.db.models import Model | ||||||
|  |                 #model_fields = dir(Model) | ||||||
| 
 | 
 | ||||||
|                 # Authenticate and check request is has the relevant permissions |                 #for attr in dir(data): | ||||||
|                 self.check_permissions() |                 ##    #if attr.startswith('_'): | ||||||
|  |                 ##    #    continue | ||||||
|  |                 #    if (attr in fields) and not (attr in model_fields) and not attr.startswith('_'): | ||||||
|  |                 #        print attr, type(getattr(data, attr, None)), attr in fields, attr in model_fields | ||||||
|                  |                  | ||||||
|                 # Get the appropriate handler method |                 #for k in add_ons: | ||||||
|                 if self.method.lower() in self.http_method_names: |                 #    ret[k] = _any(getattr(data, k)) | ||||||
|                     handler = getattr(self, self.method.lower(), self.http_method_not_allowed) |  | ||||||
|                 else: |  | ||||||
|                     handler = self.http_method_not_allowed |  | ||||||
|              |              | ||||||
|                 response_obj = handler(request, *args, **kwargs) |             # TRC | ||||||
|  |             # resouce uri | ||||||
|  |             #if self.in_typemapper(type(data), self.anonymous): | ||||||
|  |             #    handler = self.in_typemapper(type(data), self.anonymous) | ||||||
|  |             #    if hasattr(handler, 'resource_uri'): | ||||||
|  |             #        url_id, fields = handler.resource_uri() | ||||||
|  |             #        ret['resource_uri'] = permalink( lambda: (url_id,  | ||||||
|  |             #            (getattr(data, f) for f in fields) ) )() | ||||||
|              |              | ||||||
|                 # Allow return value to be either Response, or an object, or None |             # TRC | ||||||
|                 if isinstance(response_obj, Response): |             #if hasattr(data, 'get_api_url') and 'resource_uri' not in ret: | ||||||
|                     response = response_obj |             #    try: ret['resource_uri'] = data.get_api_url() | ||||||
|                 elif response_obj is not None: |             #    except: pass | ||||||
|                     response = Response(status.HTTP_200_OK, response_obj) |  | ||||||
|                 else: |  | ||||||
|                     response = Response(status.HTTP_204_NO_CONTENT) |  | ||||||
|              |              | ||||||
|                 # Pre-serialize filtering (eg filter complex objects into natively serializable types) |             # absolute uri | ||||||
|                 response.cleaned_content = self.cleanup_response(response.raw_content) |             if hasattr(data, 'get_absolute_url') and get_absolute_url: | ||||||
|  |                 try: ret['absolute_url'] = data.get_absolute_url() | ||||||
|  |                 except: pass | ||||||
|              |              | ||||||
|             except ErrorResponse, exc: |             #for key, val in ret.items(): | ||||||
|                 response = exc.response |             #    if key.endswith('_url') or key.endswith('_uri'): | ||||||
|  |             #        ret[key] = self.add_domain(val) | ||||||
| 
 | 
 | ||||||
|             # Always add these headers. |             return ret | ||||||
|             # |  | ||||||
|             # TODO - this isn't actually the correct way to set the vary header, |  | ||||||
|             # also it's currently sub-obtimal for HTTP caching - need to sort that out.  |  | ||||||
|             response.headers['Allow'] = ', '.join(self.allowed_methods) |  | ||||||
|             response.headers['Vary'] = 'Authenticate, Accept' |  | ||||||
|          |          | ||||||
|             return self.render(response) |         def _qs(data, fields=()): | ||||||
|         except: |             """ | ||||||
|             import traceback |             Querysets. | ||||||
|             traceback.print_exc() |             """ | ||||||
|  |             return [ _any(v, fields) for v in data ] | ||||||
|                  |                  | ||||||
|  |         def _list(data): | ||||||
|  |             """ | ||||||
|  |             Lists. | ||||||
|  |             """ | ||||||
|  |             return [ _any(v) for v in data ] | ||||||
|              |              | ||||||
|  |         def _dict(data): | ||||||
|  |             """ | ||||||
|  |             Dictionaries. | ||||||
|  |             """ | ||||||
|  |             return dict([ (k, _any(v)) for k, v in data.iteritems() ]) | ||||||
|  |              | ||||||
|  |         # Kickstart the seralizin'. | ||||||
|  |         return _any(data, self.fields) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -21,6 +21,6 @@ class Response(object): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ErrorResponse(BaseException): | class ErrorResponse(BaseException): | ||||||
|     """An exception representing an HttpResponse that should be returned immediatley.""" |     """An exception representing an HttpResponse that should be returned immediately.""" | ||||||
|     def __init__(self, status, content=None, headers={}): |     def __init__(self, status, content=None, headers={}): | ||||||
|         self.response = Response(status, content=content, headers=headers) |         self.response = Response(status, content=content, headers=headers) | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ | ||||||
| <div id="content" class="colM"> | <div id="content" class="colM"> | ||||||
|          |          | ||||||
| <div id="content-main"> | <div id="content-main"> | ||||||
| <form method="post" action="{% url djangorestframework.views.api_login %}" id="login-form"> | <form method="post" action="{% url djangorestframework.utils.staticviews.api_login %}" id="login-form"> | ||||||
| {% csrf_token %} | {% csrf_token %} | ||||||
|   <div class="form-row"> |   <div class="form-row"> | ||||||
|     <label for="id_username">Username:</label> {{ form.username }} |     <label for="id_username">Username:</label> {{ form.username }} | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| from djangorestframework.compat import RequestFactory | from djangorestframework.compat import RequestFactory | ||||||
| from djangorestframework.resource import Resource | from djangorestframework.views import BaseView | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # See: http://www.useragentstring.com/ | # See: http://www.useragentstring.com/ | ||||||
|  | @ -19,15 +19,15 @@ class UserAgentMungingTest(TestCase): | ||||||
| 
 | 
 | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
| 
 | 
 | ||||||
|         class MockResource(Resource): |         class MockView(BaseView): | ||||||
|             permissions = () |             permissions = () | ||||||
| 
 | 
 | ||||||
|             def get(self, request): |             def get(self, request): | ||||||
|                 return {'a':1, 'b':2, 'c':3} |                 return {'a':1, 'b':2, 'c':3} | ||||||
| 
 | 
 | ||||||
|         self.req = RequestFactory() |         self.req = RequestFactory() | ||||||
|         self.MockResource = MockResource |         self.MockView = MockView | ||||||
|         self.view = MockResource.as_view() |         self.view = MockView.as_view() | ||||||
| 
 | 
 | ||||||
|     def test_munge_msie_accept_header(self): |     def test_munge_msie_accept_header(self): | ||||||
|         """Send MSIE user agent strings and ensure that we get an HTML response, |         """Send MSIE user agent strings and ensure that we get an HTML response, | ||||||
|  | @ -42,7 +42,7 @@ class UserAgentMungingTest(TestCase): | ||||||
|     def test_dont_rewrite_msie_accept_header(self): |     def test_dont_rewrite_msie_accept_header(self): | ||||||
|         """Turn off REWRITE_IE_ACCEPT_HEADER, send MSIE user agent strings and ensure |         """Turn off REWRITE_IE_ACCEPT_HEADER, send MSIE user agent strings and ensure | ||||||
|         that we get a JSON response if we set a */* accept header.""" |         that we get a JSON response if we set a */* accept header.""" | ||||||
|         view = self.MockResource.as_view(REWRITE_IE_ACCEPT_HEADER=False) |         view = self.MockView.as_view(REWRITE_IE_ACCEPT_HEADER=False) | ||||||
| 
 | 
 | ||||||
|         for user_agent in (MSIE_9_USER_AGENT, |         for user_agent in (MSIE_9_USER_AGENT, | ||||||
|                            MSIE_8_USER_AGENT, |                            MSIE_8_USER_AGENT, | ||||||
|  |  | ||||||
|  | @ -6,19 +6,19 @@ from django.test import Client, TestCase | ||||||
| from django.utils import simplejson as json | from django.utils import simplejson as json | ||||||
| 
 | 
 | ||||||
| from djangorestframework.compat import RequestFactory | from djangorestframework.compat import RequestFactory | ||||||
| from djangorestframework.resource import Resource | from djangorestframework.views import BaseView | ||||||
| from djangorestframework import permissions | from djangorestframework import permissions | ||||||
| 
 | 
 | ||||||
| import base64 | import base64 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class MockResource(Resource): | class MockView(BaseView): | ||||||
|     permissions = ( permissions.IsAuthenticated, ) |     permissions = ( permissions.IsAuthenticated, ) | ||||||
|     def post(self, request): |     def post(self, request): | ||||||
|         return {'a':1, 'b':2, 'c':3} |         return {'a':1, 'b':2, 'c':3} | ||||||
| 
 | 
 | ||||||
| urlpatterns = patterns('', | urlpatterns = patterns('', | ||||||
|     (r'^$', MockResource.as_view()), |     (r'^$', MockView.as_view()), | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,21 +1,21 @@ | ||||||
| from django.conf.urls.defaults import patterns, url | from django.conf.urls.defaults import patterns, url | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| from djangorestframework.utils.breadcrumbs import get_breadcrumbs | from djangorestframework.utils.breadcrumbs import get_breadcrumbs | ||||||
| from djangorestframework.resource import Resource | from djangorestframework.views import BaseView | ||||||
| 
 | 
 | ||||||
| class Root(Resource): | class Root(BaseView): | ||||||
|     pass |     pass | ||||||
| 
 | 
 | ||||||
| class ResourceRoot(Resource): | class ResourceRoot(BaseView): | ||||||
|     pass |     pass | ||||||
| 
 | 
 | ||||||
| class ResourceInstance(Resource): | class ResourceInstance(BaseView): | ||||||
|     pass |     pass | ||||||
| 
 | 
 | ||||||
| class NestedResourceRoot(Resource): | class NestedResourceRoot(BaseView): | ||||||
|     pass |     pass | ||||||
| 
 | 
 | ||||||
| class NestedResourceInstance(Resource): | class NestedResourceInstance(BaseView): | ||||||
|     pass |     pass | ||||||
| 
 | 
 | ||||||
| urlpatterns = patterns('', | urlpatterns = patterns('', | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| from djangorestframework.resource import Resource | from djangorestframework.views import BaseView | ||||||
| from djangorestframework.compat import apply_markdown | from djangorestframework.compat import apply_markdown | ||||||
| from djangorestframework.utils.description import get_name, get_description | from djangorestframework.utils.description import get_name, get_description | ||||||
| 
 | 
 | ||||||
|  | @ -32,23 +32,23 @@ MARKED_DOWN = """<h2>an example docstring</h2> | ||||||
| <h2 id="hash_style_header">hash style header</h2>""" | <h2 id="hash_style_header">hash style header</h2>""" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestResourceNamesAndDescriptions(TestCase): | class TestViewNamesAndDescriptions(TestCase): | ||||||
|     def test_resource_name_uses_classname_by_default(self): |     def test_resource_name_uses_classname_by_default(self): | ||||||
|         """Ensure Resource names are based on the classname by default.""" |         """Ensure Resource names are based on the classname by default.""" | ||||||
|         class MockResource(Resource): |         class MockView(BaseView): | ||||||
|             pass |             pass | ||||||
|         self.assertEquals(get_name(MockResource()), 'Mock Resource') |         self.assertEquals(get_name(MockView()), 'Mock View') | ||||||
| 
 | 
 | ||||||
|     def test_resource_name_can_be_set_explicitly(self): |     def test_resource_name_can_be_set_explicitly(self): | ||||||
|         """Ensure Resource names can be set using the 'name' class attribute.""" |         """Ensure Resource names can be set using the 'name' class attribute.""" | ||||||
|         example = 'Some Other Name' |         example = 'Some Other Name' | ||||||
|         class MockResource(Resource): |         class MockView(BaseView): | ||||||
|             name = example |             name = example | ||||||
|         self.assertEquals(get_name(MockResource()), example) |         self.assertEquals(get_name(MockView()), example) | ||||||
| 
 | 
 | ||||||
|     def test_resource_description_uses_docstring_by_default(self): |     def test_resource_description_uses_docstring_by_default(self): | ||||||
|         """Ensure Resource names are based on the docstring by default.""" |         """Ensure Resource names are based on the docstring by default.""" | ||||||
|         class MockResource(Resource): |         class MockView(BaseView): | ||||||
|             """an example docstring |             """an example docstring | ||||||
|             ==================== |             ==================== | ||||||
| 
 | 
 | ||||||
|  | @ -64,28 +64,28 @@ class TestResourceNamesAndDescriptions(TestCase): | ||||||
|              |              | ||||||
|             # hash style header #""" |             # hash style header #""" | ||||||
|          |          | ||||||
|         self.assertEquals(get_description(MockResource()), DESCRIPTION) |         self.assertEquals(get_description(MockView()), DESCRIPTION) | ||||||
| 
 | 
 | ||||||
|     def test_resource_description_can_be_set_explicitly(self): |     def test_resource_description_can_be_set_explicitly(self): | ||||||
|         """Ensure Resource descriptions can be set using the 'description' class attribute.""" |         """Ensure Resource descriptions can be set using the 'description' class attribute.""" | ||||||
|         example = 'Some other description' |         example = 'Some other description' | ||||||
|         class MockResource(Resource): |         class MockView(BaseView): | ||||||
|             """docstring""" |             """docstring""" | ||||||
|             description = example |             description = example | ||||||
|         self.assertEquals(get_description(MockResource()), example) |         self.assertEquals(get_description(MockView()), example) | ||||||
|   |   | ||||||
|     def test_resource_description_does_not_require_docstring(self): |     def test_resource_description_does_not_require_docstring(self): | ||||||
|         """Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'description' class attribute.""" |         """Ensure that empty docstrings do not affect the Resource's description if it has been set using the 'description' class attribute.""" | ||||||
|         example = 'Some other description' |         example = 'Some other description' | ||||||
|         class MockResource(Resource): |         class MockView(BaseView): | ||||||
|             description = example |             description = example | ||||||
|         self.assertEquals(get_description(MockResource()), example) |         self.assertEquals(get_description(MockView()), example) | ||||||
| 
 | 
 | ||||||
|     def test_resource_description_can_be_empty(self): |     def test_resource_description_can_be_empty(self): | ||||||
|         """Ensure that if a resource has no doctring or 'description' class attribute, then it's description is the empty string""" |         """Ensure that if a resource has no doctring or 'description' class attribute, then it's description is the empty string""" | ||||||
|         class MockResource(Resource): |         class MockView(BaseView): | ||||||
|             pass |             pass | ||||||
|         self.assertEquals(get_description(MockResource()), '') |         self.assertEquals(get_description(MockView()), '') | ||||||
|    |    | ||||||
|     def test_markdown(self): |     def test_markdown(self): | ||||||
|         """Ensure markdown to HTML works as expected""" |         """Ensure markdown to HTML works as expected""" | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| from django import forms | from django import forms | ||||||
| from djangorestframework.compat import RequestFactory | from djangorestframework.compat import RequestFactory | ||||||
| from djangorestframework.resource import Resource | from djangorestframework.views import BaseView | ||||||
| import StringIO | import StringIO | ||||||
| 
 | 
 | ||||||
| class UploadFilesTests(TestCase): | class UploadFilesTests(TestCase): | ||||||
|  | @ -15,7 +15,7 @@ class UploadFilesTests(TestCase): | ||||||
|         class FileForm(forms.Form): |         class FileForm(forms.Form): | ||||||
|             file = forms.FileField |             file = forms.FileField | ||||||
| 
 | 
 | ||||||
|         class MockResource(Resource): |         class MockView(BaseView): | ||||||
|             permissions = () |             permissions = () | ||||||
|             form = FileForm |             form = FileForm | ||||||
| 
 | 
 | ||||||
|  | @ -26,7 +26,7 @@ class UploadFilesTests(TestCase): | ||||||
|         file = StringIO.StringIO('stuff') |         file = StringIO.StringIO('stuff') | ||||||
|         file.name = 'stuff.txt' |         file.name = 'stuff.txt' | ||||||
|         request = self.factory.post('/', {'file': file}) |         request = self.factory.post('/', {'file': file}) | ||||||
|         view = MockResource.as_view() |         view = MockView.as_view() | ||||||
|         response = view(request) |         response = view(request) | ||||||
|         self.assertEquals(response.content, '{"FILE_CONTENT": "stuff", "FILE_NAME": "stuff.txt"}') |         self.assertEquals(response.content, '{"FILE_CONTENT": "stuff", "FILE_NAME": "stuff.txt"}') | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,12 +2,12 @@ | ||||||
| .. | .. | ||||||
|     >>> from djangorestframework.parsers import FormParser |     >>> from djangorestframework.parsers import FormParser | ||||||
|     >>> from djangorestframework.compat import RequestFactory |     >>> from djangorestframework.compat import RequestFactory | ||||||
|     >>> from djangorestframework.resource import Resource |     >>> from djangorestframework.views import BaseView | ||||||
|     >>> from StringIO import StringIO |     >>> from StringIO import StringIO | ||||||
|     >>> from urllib import urlencode |     >>> from urllib import urlencode | ||||||
|     >>> req = RequestFactory().get('/') |     >>> req = RequestFactory().get('/') | ||||||
|     >>> some_resource = Resource() |     >>> some_view = BaseView() | ||||||
|     >>> some_resource.request = req  # Make as if this request had been dispatched |     >>> some_view.request = req  # Make as if this request had been dispatched | ||||||
| 
 | 
 | ||||||
| FormParser | FormParser | ||||||
| ============ | ============ | ||||||
|  | @ -24,7 +24,7 @@ Here is some example data, which would eventually be sent along with a post requ | ||||||
| 
 | 
 | ||||||
| Default behaviour for :class:`parsers.FormParser`, is to return a single value for each parameter : | Default behaviour for :class:`parsers.FormParser`, is to return a single value for each parameter : | ||||||
| 
 | 
 | ||||||
|     >>> FormParser(some_resource).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': 'blo1'} |     >>> FormParser(some_view).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': 'blo1'} | ||||||
|     True |     True | ||||||
| 
 | 
 | ||||||
| However, you can customize this behaviour by subclassing :class:`parsers.FormParser`, and overriding :meth:`parsers.FormParser.is_a_list` : | However, you can customize this behaviour by subclassing :class:`parsers.FormParser`, and overriding :meth:`parsers.FormParser.is_a_list` : | ||||||
|  | @ -36,7 +36,7 @@ However, you can customize this behaviour by subclassing :class:`parsers.FormPar | ||||||
| 
 | 
 | ||||||
| This new parser only flattens the lists of parameters that contain a single value. | This new parser only flattens the lists of parameters that contain a single value. | ||||||
| 
 | 
 | ||||||
|     >>> MyFormParser(some_resource).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': ['blo1', 'blo2']} |     >>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'bla1', 'key2': ['blo1', 'blo2']} | ||||||
|     True |     True | ||||||
| 
 | 
 | ||||||
| .. note:: The same functionality is available for :class:`parsers.MultipartParser`. | .. note:: The same functionality is available for :class:`parsers.MultipartParser`. | ||||||
|  | @ -61,7 +61,7 @@ The browsers usually strip the parameter completely. A hack to avoid this, and t | ||||||
| 
 | 
 | ||||||
| :class:`parsers.FormParser` strips the values ``_empty`` from all the lists. | :class:`parsers.FormParser` strips the values ``_empty`` from all the lists. | ||||||
| 
 | 
 | ||||||
|     >>> MyFormParser(some_resource).parse(StringIO(inpt)) == {'key1': 'blo1'} |     >>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'blo1'} | ||||||
|     True |     True | ||||||
| 
 | 
 | ||||||
| Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a list, so the parser just stripped it. | Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a list, so the parser just stripped it. | ||||||
|  | @ -71,7 +71,7 @@ Oh ... but wait a second, the parameter ``key2`` isn't even supposed to be a lis | ||||||
|     ...     def is_a_list(self, key, val_list): |     ...     def is_a_list(self, key, val_list): | ||||||
|     ...         return key == 'key2' |     ...         return key == 'key2' | ||||||
|     ...  |     ...  | ||||||
|     >>> MyFormParser(some_resource).parse(StringIO(inpt)) == {'key1': 'blo1', 'key2': []} |     >>> MyFormParser(some_view).parse(StringIO(inpt)) == {'key1': 'blo1', 'key2': []} | ||||||
|     True |     True | ||||||
| 
 | 
 | ||||||
| Better like that. Note that you can configure something else than ``_empty`` for the empty value by setting :attr:`parsers.FormParser.EMPTY_VALUE`. | Better like that. Note that you can configure something else than ``_empty`` for the empty value by setting :attr:`parsers.FormParser.EMPTY_VALUE`. | ||||||
|  | @ -81,7 +81,7 @@ from tempfile import TemporaryFile | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| from djangorestframework.compat import RequestFactory | from djangorestframework.compat import RequestFactory | ||||||
| from djangorestframework.parsers import MultipartParser | from djangorestframework.parsers import MultipartParser | ||||||
| from djangorestframework.resource import Resource | from djangorestframework.views import BaseView | ||||||
| from djangorestframework.utils.mediatypes import MediaType | from djangorestframework.utils.mediatypes import MediaType | ||||||
| from StringIO import StringIO | from StringIO import StringIO | ||||||
| 
 | 
 | ||||||
|  | @ -122,9 +122,9 @@ class TestMultipartParser(TestCase): | ||||||
|     def test_multipartparser(self): |     def test_multipartparser(self): | ||||||
|         """Ensure that MultipartParser can parse multipart/form-data that contains a mix of several files and parameters.""" |         """Ensure that MultipartParser can parse multipart/form-data that contains a mix of several files and parameters.""" | ||||||
|         post_req = RequestFactory().post('/', self.body, content_type=self.content_type) |         post_req = RequestFactory().post('/', self.body, content_type=self.content_type) | ||||||
|         resource = Resource() |         view = BaseView() | ||||||
|         resource.request = post_req |         view.request = post_req | ||||||
|         parsed = MultipartParser(resource).parse(StringIO(self.body)) |         parsed = MultipartParser(view).parse(StringIO(self.body)) | ||||||
|         self.assertEqual(parsed['key1'], 'val1') |         self.assertEqual(parsed['key1'], 'val1') | ||||||
|         self.assertEqual(parsed.FILES['file1'].read(), 'blablabla') |         self.assertEqual(parsed.FILES['file1'].read(), 'blablabla') | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,10 +3,10 @@ from django.core.urlresolvers import reverse | ||||||
| from django.test import TestCase | from django.test import TestCase | ||||||
| from django.utils import simplejson as json | from django.utils import simplejson as json | ||||||
| 
 | 
 | ||||||
| from djangorestframework.resource import Resource | from djangorestframework.views import BaseView | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class MockResource(Resource): | class MockView(BaseView): | ||||||
|     """Mock resource which simply returns a URL, so that we can ensure that reversed URLs are fully qualified""" |     """Mock resource which simply returns a URL, so that we can ensure that reversed URLs are fully qualified""" | ||||||
|     permissions = () |     permissions = () | ||||||
| 
 | 
 | ||||||
|  | @ -14,8 +14,8 @@ class MockResource(Resource): | ||||||
|         return reverse('another') |         return reverse('another') | ||||||
| 
 | 
 | ||||||
| urlpatterns = patterns('', | urlpatterns = patterns('', | ||||||
|     url(r'^$', MockResource.as_view()), |     url(r'^$', MockView.as_view()), | ||||||
|     url(r'^another$', MockResource.as_view(), name='another'), |     url(r'^another$', MockView.as_view(), name='another'), | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,11 +3,11 @@ from django.test import TestCase | ||||||
| from django.utils import simplejson as json | from django.utils import simplejson as json | ||||||
| 
 | 
 | ||||||
| from djangorestframework.compat import RequestFactory | from djangorestframework.compat import RequestFactory | ||||||
| from djangorestframework.resource import Resource | from djangorestframework.views import BaseView | ||||||
| from djangorestframework.permissions import Throttling | from djangorestframework.permissions import Throttling | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class MockResource(Resource): | class MockView(BaseView): | ||||||
|     permissions = ( Throttling, ) |     permissions = ( Throttling, ) | ||||||
|     throttle = (3, 1) # 3 requests per second |     throttle = (3, 1) # 3 requests per second | ||||||
| 
 | 
 | ||||||
|  | @ -15,7 +15,7 @@ class MockResource(Resource): | ||||||
|         return 'foo' |         return 'foo' | ||||||
| 
 | 
 | ||||||
| urlpatterns = patterns('', | urlpatterns = patterns('', | ||||||
|     (r'^$', MockResource.as_view()), |     (r'^$', MockView.as_view()), | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,8 @@ from django.test import TestCase | ||||||
| from djangorestframework.compat import RequestFactory | from djangorestframework.compat import RequestFactory | ||||||
| from djangorestframework.validators import BaseValidator, FormValidator, ModelFormValidator | from djangorestframework.validators import BaseValidator, FormValidator, ModelFormValidator | ||||||
| from djangorestframework.response import ErrorResponse | from djangorestframework.response import ErrorResponse | ||||||
|  | from djangorestframework.views import BaseView | ||||||
|  | from djangorestframework.resource import Resource | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestValidatorMixinInterfaces(TestCase): | class TestValidatorMixinInterfaces(TestCase): | ||||||
|  | @ -20,7 +22,7 @@ class TestDisabledValidations(TestCase): | ||||||
|     def test_disabled_form_validator_returns_content_unchanged(self): |     def test_disabled_form_validator_returns_content_unchanged(self): | ||||||
|         """If the view's form attribute is None then FormValidator(view).validate(content) |         """If the view's form attribute is None then FormValidator(view).validate(content) | ||||||
|         should just return the content unmodified.""" |         should just return the content unmodified.""" | ||||||
|         class DisabledFormView(object): |         class DisabledFormView(BaseView): | ||||||
|             form = None |             form = None | ||||||
| 
 | 
 | ||||||
|         view = DisabledFormView() |         view = DisabledFormView() | ||||||
|  | @ -30,7 +32,7 @@ class TestDisabledValidations(TestCase): | ||||||
|     def test_disabled_form_validator_get_bound_form_returns_none(self): |     def test_disabled_form_validator_get_bound_form_returns_none(self): | ||||||
|         """If the view's form attribute is None on then |         """If the view's form attribute is None on then | ||||||
|         FormValidator(view).get_bound_form(content) should just return None.""" |         FormValidator(view).get_bound_form(content) should just return None.""" | ||||||
|         class DisabledFormView(object): |         class DisabledFormView(BaseView): | ||||||
|             form = None |             form = None | ||||||
| 
 | 
 | ||||||
|         view = DisabledFormView() |         view = DisabledFormView() | ||||||
|  | @ -39,11 +41,10 @@ class TestDisabledValidations(TestCase): | ||||||
| 
 | 
 | ||||||
|   |   | ||||||
|     def test_disabled_model_form_validator_returns_content_unchanged(self): |     def test_disabled_model_form_validator_returns_content_unchanged(self): | ||||||
|         """If the view's form and model attributes are None then |         """If the view's form is None and does not have a Resource with a model set then | ||||||
|         ModelFormValidator(view).validate(content) should just return the content unmodified.""" |         ModelFormValidator(view).validate(content) should just return the content unmodified.""" | ||||||
|         class DisabledModelFormView(object): |         class DisabledModelFormView(BaseView): | ||||||
|             form = None |             form = None | ||||||
|             model = None |  | ||||||
| 
 | 
 | ||||||
|         view = DisabledModelFormView() |         view = DisabledModelFormView() | ||||||
|         content = {'qwerty':'uiop'}        |         content = {'qwerty':'uiop'}        | ||||||
|  | @ -51,13 +52,12 @@ class TestDisabledValidations(TestCase): | ||||||
| 
 | 
 | ||||||
|     def test_disabled_model_form_validator_get_bound_form_returns_none(self): |     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.""" |         """If the form attribute is None on FormValidatorMixin then get_bound_form(content) should just return None.""" | ||||||
|         class DisabledModelFormView(object): |         class DisabledModelFormView(BaseView): | ||||||
|             form = None |  | ||||||
|             model = None |             model = None | ||||||
|   |   | ||||||
|         view = DisabledModelFormView() |         view = DisabledModelFormView() | ||||||
|         content = {'qwerty':'uiop'}        |         content = {'qwerty':'uiop'}        | ||||||
|         self.assertEqual(ModelFormValidator(view).get_bound_form(content), None)#  |         self.assertEqual(ModelFormValidator(view).get_bound_form(content), None)  | ||||||
| 
 | 
 | ||||||
| class TestNonFieldErrors(TestCase): | class TestNonFieldErrors(TestCase): | ||||||
|     """Tests against form validation errors caused by non-field errors.  (eg as might be caused by some custom form validation)""" |     """Tests against form validation errors caused by non-field errors.  (eg as might be caused by some custom form validation)""" | ||||||
|  | @ -84,7 +84,7 @@ class TestNonFieldErrors(TestCase): | ||||||
|         except ErrorResponse, exc:            |         except ErrorResponse, exc:            | ||||||
|             self.assertEqual(exc.response.raw_content, {'errors': [MockForm.ERROR_TEXT]}) |             self.assertEqual(exc.response.raw_content, {'errors': [MockForm.ERROR_TEXT]}) | ||||||
|         else: |         else: | ||||||
|             self.fail('ResourceException was not raised')  #pragma: no cover |             self.fail('ErrorResponse was not raised')  #pragma: no cover | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestFormValidation(TestCase): | class TestFormValidation(TestCase): | ||||||
|  | @ -95,11 +95,11 @@ class TestFormValidation(TestCase): | ||||||
|         class MockForm(forms.Form): |         class MockForm(forms.Form): | ||||||
|             qwerty = forms.CharField(required=True) |             qwerty = forms.CharField(required=True) | ||||||
| 
 | 
 | ||||||
|         class MockFormView(object): |         class MockFormView(BaseView): | ||||||
|             form = MockForm |             form = MockForm | ||||||
|             validators = (FormValidator,) |             validators = (FormValidator,) | ||||||
|   |   | ||||||
|         class MockModelFormView(object): |         class MockModelFormView(BaseView): | ||||||
|             form = MockForm |             form = MockForm | ||||||
|             validators = (ModelFormValidator,) |             validators = (ModelFormValidator,) | ||||||
|           |           | ||||||
|  | @ -265,9 +265,12 @@ class TestModelFormValidator(TestCase): | ||||||
|             def readonly(self): |             def readonly(self): | ||||||
|                 return 'read only' |                 return 'read only' | ||||||
|          |          | ||||||
|         class MockView(object): |         class MockResource(Resource): | ||||||
|             model = MockModel |             model = MockModel | ||||||
|   |   | ||||||
|  |         class MockView(BaseView): | ||||||
|  |             resource = MockResource | ||||||
|  |         | ||||||
|         self.validator = ModelFormValidator(MockView) |         self.validator = ModelFormValidator(MockView) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ from django.test import TestCase | ||||||
| from django.test import Client | from django.test import Client | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| urlpatterns = patterns('djangorestframework.views', | urlpatterns = patterns('djangorestframework.utils.staticviews', | ||||||
|     url(r'^robots.txt$', 'deny_robots'), |     url(r'^robots.txt$', 'deny_robots'), | ||||||
|     url(r'^favicon.ico$', 'favicon'), |     url(r'^favicon.ico$', 'favicon'), | ||||||
|     url(r'^accounts/login$', 'api_login'), |     url(r'^accounts/login$', 'api_login'), | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								djangorestframework/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								djangorestframework/urls.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | ||||||
|  | from django.conf.urls.defaults import patterns | ||||||
|  | from django.conf import settings | ||||||
|  | 
 | ||||||
|  | urlpatterns = patterns('djangorestframework.utils.staticviews', | ||||||
|  |     (r'robots.txt', 'deny_robots'), | ||||||
|  |     (r'^accounts/login/$', 'api_login'), | ||||||
|  |     (r'^accounts/logout/$', 'api_logout'), | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | # Only serve favicon in production because otherwise chrome users will pretty much | ||||||
|  | # permanantly have the django-rest-framework favicon whenever they navigate to | ||||||
|  | # 127.0.0.1:8000 or whatever, which gets annoying | ||||||
|  | if not settings.DEBUG: | ||||||
|  |     urlpatterns += patterns('djangorestframework.utils.staticviews', | ||||||
|  |         (r'favicon.ico', 'favicon'), | ||||||
|  |     ) | ||||||
							
								
								
									
										65
									
								
								djangorestframework/utils/staticviews.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								djangorestframework/utils/staticviews.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | ||||||
|  | from django.contrib.auth.views import * | ||||||
|  | from django.conf import settings | ||||||
|  | from django.http import HttpResponse | ||||||
|  | import base64 | ||||||
|  | 
 | ||||||
|  | def deny_robots(request): | ||||||
|  |     return HttpResponse('User-agent: *\nDisallow: /', mimetype='text/plain') | ||||||
|  | 
 | ||||||
|  | def favicon(request): | ||||||
|  |     data = 'AAABAAEAEREAAAEAIADwBAAAFgAAACgAAAARAAAAIgAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLy8tLy8vL3svLy1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy8vLBsvLywkAAAAATkZFS1xUVPqhn57/y8vL0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJmVlQ/GxcXiy8vL88vLy4FdVlXzTkZF/2RdXP/Ly8vty8vLtMvLy5DLy8vty8vLxgAAAAAAAAAAAAAAAAAAAABORkUJTkZF4lNMS/+Lh4f/cWtq/05GRf9ORkX/Vk9O/3JtbP+Ef3//Vk9O/2ljYv/Ly8v5y8vLCQAAAAAAAAAAAAAAAE5GRQlORkX2TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/UElI/8PDw5cAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRZZORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vLvQAAAAAAAAAAAAAAAAAAAADLy8tIaWNi805GRf9ORkX/YVpZ/396eV7Ly8t7qaen9lZOTu5ORkX/TkZF/25oZ//Ly8v/y8vLycvLy0gAAAAATkZFSGNcXPpORkX/TkZF/05GRf+ysLDzTkZFe1NLSv6Oior/raur805GRf9ORkX/TkZF/2hiYf+npaX/y8vL5wAAAABORkXnTkZF/05GRf9ORkX/VU1M/8vLy/9PR0b1TkZF/1VNTP/Ly8uQT0dG+E5GRf9ORkX/TkZF/1hRUP3Ly8tmAAAAAE5GRWBORkXkTkZF/05GRf9ORkX/t7a2/355eOpORkX/TkZFkISAf1BORkX/TkZF/05GRf9XT075TkZFZgAAAAAAAAAAAAAAAAAAAABORkXDTkZF/05GRf9lX17/ubi4/8vLy/+2tbT/Yltb/05GRf9ORkX/a2Vk/8vLy5MAAAAAAAAAAAAAAAAAAAAAAAAAAFNLSqNORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vL+cvLyw8AAAAAAAAAAAAAAABORkUSTkZF+U5GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/1BJSP/CwsLmy8vLDwAAAAAAAAAAAAAAAE5GRRJORkXtTkZF9FFJSJ1ORkXJTkZF/05GRf9ORkX/ZF5d9k5GRZ9ORkXtTkZF5HFsaxUAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRQxORkUJAAAAAAAAAABORkXhTkZF/2JbWv7Ly8tgAAAAAAAAAABORkUGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRWBORkX2TkZFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//+AAP9/gAD+P4AA4AOAAMADgADAA4AAwAOAAMMBgACCAIAAAAGAAIBDgADAA4AAwAOAAMADgADAB4AA/H+AAP7/gAA=' | ||||||
|  |     return HttpResponse(base64.b64decode(data), mimetype='image/vnd.microsoft.icon') | ||||||
|  | 
 | ||||||
|  | # BLERGH | ||||||
|  | # Replicate django.contrib.auth.views.login simply so we don't have get users to update TEMPLATE_CONTEXT_PROCESSORS | ||||||
|  | # to add ADMIN_MEDIA_PREFIX to the RequestContext.  I don't like this but really really want users to not have to | ||||||
|  | # be making settings changes in order to accomodate django-rest-framework | ||||||
|  | @csrf_protect | ||||||
|  | @never_cache | ||||||
|  | def api_login(request, template_name='api_login.html', | ||||||
|  |           redirect_field_name=REDIRECT_FIELD_NAME, | ||||||
|  |           authentication_form=AuthenticationForm): | ||||||
|  |     """Displays the login form and handles the login action.""" | ||||||
|  | 
 | ||||||
|  |     redirect_to = request.REQUEST.get(redirect_field_name, '') | ||||||
|  | 
 | ||||||
|  |     if request.method == "POST": | ||||||
|  |         form = authentication_form(data=request.POST) | ||||||
|  |         if form.is_valid(): | ||||||
|  |             # Light security check -- make sure redirect_to isn't garbage. | ||||||
|  |             if not redirect_to or ' ' in redirect_to: | ||||||
|  |                 redirect_to = settings.LOGIN_REDIRECT_URL | ||||||
|  | 
 | ||||||
|  |             # Heavier security check -- redirects to http://example.com should | ||||||
|  |             # not be allowed, but things like /view/?param=http://example.com | ||||||
|  |             # should be allowed. This regex checks if there is a '//' *before* a | ||||||
|  |             # question mark. | ||||||
|  |             elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to): | ||||||
|  |                     redirect_to = settings.LOGIN_REDIRECT_URL | ||||||
|  | 
 | ||||||
|  |             # Okay, security checks complete. Log the user in. | ||||||
|  |             auth_login(request, form.get_user()) | ||||||
|  | 
 | ||||||
|  |             if request.session.test_cookie_worked(): | ||||||
|  |                 request.session.delete_test_cookie() | ||||||
|  | 
 | ||||||
|  |             return HttpResponseRedirect(redirect_to) | ||||||
|  | 
 | ||||||
|  |     else: | ||||||
|  |         form = authentication_form(request) | ||||||
|  | 
 | ||||||
|  |     request.session.set_test_cookie() | ||||||
|  | 
 | ||||||
|  |     #current_site = get_current_site(request) | ||||||
|  | 
 | ||||||
|  |     return render_to_response(template_name, { | ||||||
|  |         'form': form, | ||||||
|  |         redirect_field_name: redirect_to, | ||||||
|  |         #'site': current_site, | ||||||
|  |         #'site_name': current_site.name, | ||||||
|  |         'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX, | ||||||
|  |     }, context_instance=RequestContext(request)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def api_logout(request, next_page=None, template_name='api_login.html', redirect_field_name=REDIRECT_FIELD_NAME): | ||||||
|  |     return logout(request, next_page, template_name, redirect_field_name) | ||||||
|  | @ -159,7 +159,7 @@ class ModelFormValidator(FormValidator): | ||||||
|         otherwise if model is set use that class to create a ModelForm, otherwise return None.""" |         otherwise if model is set use that class to create a ModelForm, otherwise return None.""" | ||||||
| 
 | 
 | ||||||
|         form_cls = getattr(self.view, 'form', None) |         form_cls = getattr(self.view, 'form', None) | ||||||
|         model_cls = getattr(self.view, 'model', None) |         model_cls = getattr(self.view.resource, 'model', None) | ||||||
| 
 | 
 | ||||||
|         if form_cls: |         if form_cls: | ||||||
|             # Use explict Form |             # Use explict Form | ||||||
|  | @ -189,9 +189,10 @@ class ModelFormValidator(FormValidator): | ||||||
|     @property |     @property | ||||||
|     def _model_fields_set(self): |     def _model_fields_set(self): | ||||||
|         """Return a set containing the names of validated fields on the model.""" |         """Return a set containing the names of validated fields on the model.""" | ||||||
|         model = getattr(self.view, 'model', None) |         resource = self.view.resource | ||||||
|         fields = getattr(self.view, 'fields', self.fields) |         model = getattr(resource, 'model', None) | ||||||
|         exclude_fields = getattr(self.view, 'exclude_fields', self.exclude_fields) |         fields = getattr(resource, 'fields', self.fields) | ||||||
|  |         exclude_fields = getattr(resource, 'exclude_fields', self.exclude_fields) | ||||||
| 
 | 
 | ||||||
|         model_fields = set(field.name for field in model._meta.fields) |         model_fields = set(field.name for field in model._meta.fields) | ||||||
| 
 | 
 | ||||||
|  | @ -203,9 +204,10 @@ class ModelFormValidator(FormValidator): | ||||||
|     @property |     @property | ||||||
|     def _property_fields_set(self): |     def _property_fields_set(self): | ||||||
|         """Returns a set containing the names of validated properties on the model.""" |         """Returns a set containing the names of validated properties on the model.""" | ||||||
|         model = getattr(self.view, 'model', None) |         resource = self.view.resource | ||||||
|         fields = getattr(self.view, 'fields', self.fields) |         model = getattr(resource, 'model', None) | ||||||
|         exclude_fields = getattr(self.view, 'exclude_fields', self.exclude_fields) |         fields = getattr(resource, 'fields', self.fields) | ||||||
|  |         exclude_fields = getattr(resource, 'exclude_fields', self.exclude_fields) | ||||||
| 
 | 
 | ||||||
|         property_fields = set(attr for attr in dir(model) if |         property_fields = set(attr for attr in dir(model) if | ||||||
|                               isinstance(getattr(model, attr, None), property) |                               isinstance(getattr(model, attr, None), property) | ||||||
|  |  | ||||||
|  | @ -1,66 +1,147 @@ | ||||||
| from django.contrib.auth.views import * | from django.core.urlresolvers import set_script_prefix | ||||||
| #from django.contrib.sites.models import get_current_site | from django.views.decorators.csrf import csrf_exempt | ||||||
| from django.conf import settings | 
 | ||||||
| from django.http import HttpResponse | from djangorestframework.compat import View | ||||||
| import base64 | from djangorestframework.response import Response, ErrorResponse | ||||||
|  | from djangorestframework.mixins import * | ||||||
|  | from djangorestframework import resource, renderers, parsers, authentication, permissions, validators, status | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | __all__ = ['BaseView', | ||||||
|  |            'ModelView', | ||||||
|  |            'InstanceModelView', | ||||||
|  |            'ListOrModelView', | ||||||
|  |            'ListOrCreateModelView'] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class BaseView(RequestMixin, ResponseMixin, AuthMixin, View): | ||||||
|  |     """Handles incoming requests and maps them to REST operations. | ||||||
|  |     Performs request deserialization, response serialization, authentication and input validation.""" | ||||||
|  | 
 | ||||||
|  |     # Use the base resource by default | ||||||
|  |     resource = resource.Resource | ||||||
|  | 
 | ||||||
|  |     # List of renderers the resource can serialize the response with, ordered by preference. | ||||||
|  |     renderers = ( renderers.JSONRenderer, | ||||||
|  |                   renderers.DocumentingHTMLRenderer, | ||||||
|  |                   renderers.DocumentingXHTMLRenderer, | ||||||
|  |                   renderers.DocumentingPlainTextRenderer, | ||||||
|  |                   renderers.XMLRenderer ) | ||||||
|  | 
 | ||||||
|  |     # List of parsers the resource can parse the request with. | ||||||
|  |     parsers = ( parsers.JSONParser, | ||||||
|  |                 parsers.FormParser, | ||||||
|  |                 parsers.MultipartParser ) | ||||||
|  | 
 | ||||||
|  |     # List of validators to validate, cleanup and normalize the request content     | ||||||
|  |     validators = ( validators.FormValidator, ) | ||||||
|  | 
 | ||||||
|  |     # List of all authenticating methods to attempt. | ||||||
|  |     authentication = ( authentication.UserLoggedInAuthenticator, | ||||||
|  |                        authentication.BasicAuthenticator ) | ||||||
|  |      | ||||||
|  |     # List of all permissions that must be checked. | ||||||
|  |     permissions = ( permissions.FullAnonAccess, ) | ||||||
|  | 
 | ||||||
|  |     # Optional form for input validation and presentation of HTML formatted responses. | ||||||
|  |     form = None | ||||||
|  | 
 | ||||||
|  |     # Allow name and description for the Resource to be set explicitly, | ||||||
|  |     # overiding the default classname/docstring behaviour. | ||||||
|  |     # These are used for documentation in the standard html and text renderers. | ||||||
|  |     name = None | ||||||
|  |     description = None | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def allowed_methods(self): | ||||||
|  |         return [method.upper() for method in self.http_method_names if hasattr(self, method)] | ||||||
|  | 
 | ||||||
|  |     def http_method_not_allowed(self, request, *args, **kwargs): | ||||||
|  |         """Return an HTTP 405 error if an operation is called which does not have a handler method.""" | ||||||
|  |         raise ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED, | ||||||
|  |                                 {'detail': 'Method \'%s\' not allowed on this resource.' % self.method}) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def cleanup_response(self, data): | ||||||
|  |         """Perform any resource-specific data filtering prior to the standard HTTP | ||||||
|  |         content-type serialization. | ||||||
|  | 
 | ||||||
|  |         Eg filter complex objects that cannot be serialized by json/xml/etc into basic objects that can. | ||||||
|  |          | ||||||
|  |         TODO: This is going to be removed.  I think that the 'fields' behaviour is going to move into | ||||||
|  |         the RendererMixin and Renderer classes.""" | ||||||
|  |         return data | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     # Note: session based authentication is explicitly CSRF validated, | ||||||
|  |     # all other authentication is CSRF exempt. | ||||||
|  |     @csrf_exempt | ||||||
|  |     def dispatch(self, request, *args, **kwargs): | ||||||
|  |         self.request = request | ||||||
|  |         self.args = args | ||||||
|  |         self.kwargs = kwargs | ||||||
|  | 
 | ||||||
|  |         # Calls to 'reverse' will not be fully qualified unless we set the scheme/host/port here. | ||||||
|  |         prefix = '%s://%s' % (request.is_secure() and 'https' or 'http', request.get_host()) | ||||||
|  |         set_script_prefix(prefix) | ||||||
|  | 
 | ||||||
|  |         try: | ||||||
|  |             # If using a form POST with '_method'/'_content'/'_content_type' overrides, then alter | ||||||
|  |             # self.method, self.content_type, self.RAW_CONTENT & self.CONTENT appropriately. | ||||||
|  |             self.perform_form_overloading() | ||||||
|  | 
 | ||||||
|  |             # Authenticate and check request is has the relevant permissions | ||||||
|  |             self.check_permissions() | ||||||
|  | 
 | ||||||
|  |             # Get the appropriate handler method | ||||||
|  |             if self.method.lower() in self.http_method_names: | ||||||
|  |                 handler = getattr(self, self.method.lower(), self.http_method_not_allowed) | ||||||
|  |             else: | ||||||
|  |                 handler = self.http_method_not_allowed | ||||||
|  | 
 | ||||||
|  |             response_obj = handler(request, *args, **kwargs) | ||||||
|  | 
 | ||||||
|  |             # Allow return value to be either Response, or an object, or None | ||||||
|  |             if isinstance(response_obj, Response): | ||||||
|  |                 response = response_obj | ||||||
|  |             elif response_obj is not None: | ||||||
|  |                 response = Response(status.HTTP_200_OK, response_obj) | ||||||
|  |             else: | ||||||
|  |                 response = Response(status.HTTP_204_NO_CONTENT) | ||||||
|  | 
 | ||||||
|  |             # Pre-serialize filtering (eg filter complex objects into natively serializable types) | ||||||
|  |             response.cleaned_content = self.resource.object_to_serializable(response.raw_content) | ||||||
|  | 
 | ||||||
|  |         except ErrorResponse, exc: | ||||||
|  |             response = exc.response | ||||||
|  | 
 | ||||||
|  |         # Always add these headers. | ||||||
|  |         # | ||||||
|  |         # TODO - this isn't actually the correct way to set the vary header, | ||||||
|  |         # also it's currently sub-obtimal for HTTP caching - need to sort that out.  | ||||||
|  |         response.headers['Allow'] = ', '.join(self.allowed_methods) | ||||||
|  |         response.headers['Vary'] = 'Authenticate, Accept' | ||||||
|  | 
 | ||||||
|  |         return self.render(response) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ModelView(BaseView): | ||||||
|  |     """A RESTful view that maps to a model in the database.""" | ||||||
|  |     validators = (validators.ModelFormValidator,) | ||||||
|  | 
 | ||||||
|  | class InstanceModelView(ReadModelMixin, UpdateModelMixin, DeleteModelMixin, ModelView): | ||||||
|  |     """A view which provides default operations for read/update/delete against a model instance.""" | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | class ListModelResource(ListModelMixin, ModelView): | ||||||
|  |     """A view which provides default operations for list, against a model in the database.""" | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
|  | class ListOrCreateModelResource(ListModelMixin, CreateModelMixin, ModelView): | ||||||
|  |     """A view which provides default operations for list and create, against a model in the database.""" | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def deny_robots(request): |  | ||||||
|     return HttpResponse('User-agent: *\nDisallow: /', mimetype='text/plain') |  | ||||||
| 
 |  | ||||||
| def favicon(request): |  | ||||||
|     data = 'AAABAAEAEREAAAEAIADwBAAAFgAAACgAAAARAAAAIgAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLy8tLy8vL3svLy1QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy8vLBsvLywkAAAAATkZFS1xUVPqhn57/y8vL0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJmVlQ/GxcXiy8vL88vLy4FdVlXzTkZF/2RdXP/Ly8vty8vLtMvLy5DLy8vty8vLxgAAAAAAAAAAAAAAAAAAAABORkUJTkZF4lNMS/+Lh4f/cWtq/05GRf9ORkX/Vk9O/3JtbP+Ef3//Vk9O/2ljYv/Ly8v5y8vLCQAAAAAAAAAAAAAAAE5GRQlORkX2TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/UElI/8PDw5cAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRZZORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vLvQAAAAAAAAAAAAAAAAAAAADLy8tIaWNi805GRf9ORkX/YVpZ/396eV7Ly8t7qaen9lZOTu5ORkX/TkZF/25oZ//Ly8v/y8vLycvLy0gAAAAATkZFSGNcXPpORkX/TkZF/05GRf+ysLDzTkZFe1NLSv6Oior/raur805GRf9ORkX/TkZF/2hiYf+npaX/y8vL5wAAAABORkXnTkZF/05GRf9ORkX/VU1M/8vLy/9PR0b1TkZF/1VNTP/Ly8uQT0dG+E5GRf9ORkX/TkZF/1hRUP3Ly8tmAAAAAE5GRWBORkXkTkZF/05GRf9ORkX/t7a2/355eOpORkX/TkZFkISAf1BORkX/TkZF/05GRf9XT075TkZFZgAAAAAAAAAAAAAAAAAAAABORkXDTkZF/05GRf9lX17/ubi4/8vLy/+2tbT/Yltb/05GRf9ORkX/a2Vk/8vLy5MAAAAAAAAAAAAAAAAAAAAAAAAAAFNLSqNORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf+Cfn3/y8vL+cvLyw8AAAAAAAAAAAAAAABORkUSTkZF+U5GRf9ORkX/TkZF/05GRf9ORkX/TkZF/05GRf9ORkX/TkZF/1BJSP/CwsLmy8vLDwAAAAAAAAAAAAAAAE5GRRJORkXtTkZF9FFJSJ1ORkXJTkZF/05GRf9ORkX/ZF5d9k5GRZ9ORkXtTkZF5HFsaxUAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRQxORkUJAAAAAAAAAABORkXhTkZF/2JbWv7Ly8tgAAAAAAAAAABORkUGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5GRWBORkX2TkZFYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//+AAP9/gAD+P4AA4AOAAMADgADAA4AAwAOAAMMBgACCAIAAAAGAAIBDgADAA4AAwAOAAMADgADAB4AA/H+AAP7/gAA=' |  | ||||||
|     return HttpResponse(base64.b64decode(data), mimetype='image/vnd.microsoft.icon') |  | ||||||
| 
 |  | ||||||
| # BLERGH |  | ||||||
| # Replicate django.contrib.auth.views.login simply so we don't have get users to update TEMPLATE_CONTEXT_PROCESSORS |  | ||||||
| # to add ADMIN_MEDIA_PREFIX to the RequestContext.  I don't like this but really really want users to not have to |  | ||||||
| # be making settings changes in order to accomodate django-rest-framework |  | ||||||
| @csrf_protect |  | ||||||
| @never_cache |  | ||||||
| def api_login(request, template_name='api_login.html', |  | ||||||
|           redirect_field_name=REDIRECT_FIELD_NAME, |  | ||||||
|           authentication_form=AuthenticationForm): |  | ||||||
|     """Displays the login form and handles the login action.""" |  | ||||||
| 
 |  | ||||||
|     redirect_to = request.REQUEST.get(redirect_field_name, '') |  | ||||||
| 
 |  | ||||||
|     if request.method == "POST": |  | ||||||
|         form = authentication_form(data=request.POST) |  | ||||||
|         if form.is_valid(): |  | ||||||
|             # Light security check -- make sure redirect_to isn't garbage. |  | ||||||
|             if not redirect_to or ' ' in redirect_to: |  | ||||||
|                 redirect_to = settings.LOGIN_REDIRECT_URL |  | ||||||
| 
 |  | ||||||
|             # Heavier security check -- redirects to http://example.com should |  | ||||||
|             # not be allowed, but things like /view/?param=http://example.com |  | ||||||
|             # should be allowed. This regex checks if there is a '//' *before* a |  | ||||||
|             # question mark. |  | ||||||
|             elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to): |  | ||||||
|                     redirect_to = settings.LOGIN_REDIRECT_URL |  | ||||||
| 
 |  | ||||||
|             # Okay, security checks complete. Log the user in. |  | ||||||
|             auth_login(request, form.get_user()) |  | ||||||
| 
 |  | ||||||
|             if request.session.test_cookie_worked(): |  | ||||||
|                 request.session.delete_test_cookie() |  | ||||||
| 
 |  | ||||||
|             return HttpResponseRedirect(redirect_to) |  | ||||||
| 
 |  | ||||||
|     else: |  | ||||||
|         form = authentication_form(request) |  | ||||||
| 
 |  | ||||||
|     request.session.set_test_cookie() |  | ||||||
| 
 |  | ||||||
|     #current_site = get_current_site(request) |  | ||||||
| 
 |  | ||||||
|     return render_to_response(template_name, { |  | ||||||
|         'form': form, |  | ||||||
|         redirect_field_name: redirect_to, |  | ||||||
|         #'site': current_site, |  | ||||||
|         #'site_name': current_site.name, |  | ||||||
|         'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX, |  | ||||||
|     }, context_instance=RequestContext(request)) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def api_logout(request, next_page=None, template_name='api_login.html', redirect_field_name=REDIRECT_FIELD_NAME): |  | ||||||
|     return logout(request, next_page, template_name, redirect_field_name) |  | ||||||
|  |  | ||||||
|  | @ -1,15 +1,15 @@ | ||||||
| from djangorestframework.modelresource import ModelResource, RootModelResource | from djangorestframework.modelresource import InstanceModelResource, ListOrCreateModelResource | ||||||
| from modelresourceexample.models import MyModel | from modelresourceexample.models import MyModel | ||||||
| 
 | 
 | ||||||
| FIELDS = ('foo', 'bar', 'baz', 'absolute_url') | FIELDS = ('foo', 'bar', 'baz', 'absolute_url') | ||||||
| 
 | 
 | ||||||
| class MyModelRootResource(RootModelResource): | class MyModelRootResource(ListOrCreateModelResource): | ||||||
|     """A create/list resource for MyModel. |     """A create/list resource for MyModel. | ||||||
|     Available for both authenticated and anonymous access for the purposes of the sandbox.""" |     Available for both authenticated and anonymous access for the purposes of the sandbox.""" | ||||||
|     model = MyModel |     model = MyModel | ||||||
|     fields = FIELDS |     fields = FIELDS | ||||||
| 
 | 
 | ||||||
| class MyModelResource(ModelResource): | class MyModelResource(InstanceModelResource): | ||||||
|     """A read/update/delete resource for MyModel. |     """A read/update/delete resource for MyModel. | ||||||
|     Available for both authenticated and anonymous access for the purposes of the sandbox.""" |     Available for both authenticated and anonymous access for the purposes of the sandbox.""" | ||||||
|     model = MyModel |     model = MyModel | ||||||
|  |  | ||||||
|  | @ -2,11 +2,8 @@ from django.conf.urls.defaults import patterns, include, url | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| from sandbox.views import Sandbox | from sandbox.views import Sandbox | ||||||
| 
 | 
 | ||||||
| urlpatterns = patterns('djangorestframework.views', | urlpatterns = patterns('', | ||||||
|     (r'robots.txt', 'deny_robots'), |  | ||||||
| 
 |  | ||||||
|     (r'^$', Sandbox.as_view()), |     (r'^$', Sandbox.as_view()), | ||||||
| 
 |  | ||||||
|     (r'^resource-example/', include('resourceexample.urls')), |     (r'^resource-example/', include('resourceexample.urls')), | ||||||
|     (r'^model-resource-example/', include('modelresourceexample.urls')), |     (r'^model-resource-example/', include('modelresourceexample.urls')), | ||||||
|     (r'^mixin/', include('mixin.urls')), |     (r'^mixin/', include('mixin.urls')), | ||||||
|  | @ -14,14 +11,6 @@ urlpatterns = patterns('djangorestframework.views', | ||||||
|     (r'^pygments/', include('pygments_api.urls')), |     (r'^pygments/', include('pygments_api.urls')), | ||||||
|     (r'^blog-post/', include('blogpost.urls')), |     (r'^blog-post/', include('blogpost.urls')), | ||||||
| 
 | 
 | ||||||
|     (r'^accounts/login/$', 'api_login'), |     (r'^', include('djangorestframework.urls')), | ||||||
|     (r'^accounts/logout/$', 'api_logout'), |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| # Only serve favicon in production because otherwise chrome users will pretty much |  | ||||||
| # permanantly have the django-rest-framework favicon whenever they navigate to |  | ||||||
| # 127.0.0.1:8000 or whatever, which gets annoying |  | ||||||
| if not settings.DEBUG: |  | ||||||
|     urlpatterns += patterns('djangorestframework.views', |  | ||||||
|         (r'favicon.ico', 'favicon'), |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user