mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 18:08:03 +03:00 
			
		
		
		
	Response as a subclass of HttpResponse - first draft, not quite there yet.
This commit is contained in:
		
							parent
							
								
									5f59d90645
								
							
						
					
					
						commit
						5bb6301b7f
					
				| 
						 | 
					@ -87,7 +87,7 @@ class UserLoggedInAuthentication(BaseAuthentication):
 | 
				
			||||||
        Returns a :obj:`User` if the request session currently has a logged in user.
 | 
					        Returns a :obj:`User` if the request session currently has a logged in user.
 | 
				
			||||||
        Otherwise returns :const:`None`.
 | 
					        Otherwise returns :const:`None`.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.view.DATA  # Make sure our generic parsing runs first
 | 
					        request.DATA  # Make sure our generic parsing runs first
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if getattr(request, 'user', None) and request.user.is_active:
 | 
					        if getattr(request, 'user', None) and request.user.is_active:
 | 
				
			||||||
            # Enforce CSRF validation for session based authentication.
 | 
					            # Enforce CSRF validation for session based authentication.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,6 @@ classes that can be added to a `View`.
 | 
				
			||||||
from django.contrib.auth.models import AnonymousUser
 | 
					from django.contrib.auth.models import AnonymousUser
 | 
				
			||||||
from django.core.paginator import Paginator
 | 
					from django.core.paginator import Paginator
 | 
				
			||||||
from django.db.models.fields.related import ForeignKey
 | 
					from django.db.models.fields.related import ForeignKey
 | 
				
			||||||
from django.http import HttpResponse
 | 
					 | 
				
			||||||
from urlobject import URLObject
 | 
					from urlobject import URLObject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from djangorestframework import status
 | 
					from djangorestframework import status
 | 
				
			||||||
| 
						 | 
					@ -14,8 +13,7 @@ from djangorestframework.renderers import BaseRenderer
 | 
				
			||||||
from djangorestframework.resources import Resource, FormResource, ModelResource
 | 
					from djangorestframework.resources import Resource, FormResource, ModelResource
 | 
				
			||||||
from djangorestframework.response import Response, ErrorResponse
 | 
					from djangorestframework.response import Response, ErrorResponse
 | 
				
			||||||
from djangorestframework.request import request_class_factory
 | 
					from djangorestframework.request import request_class_factory
 | 
				
			||||||
from djangorestframework.utils import as_tuple, MSIE_USER_AGENT_REGEX
 | 
					from djangorestframework.utils import as_tuple, allowed_methods
 | 
				
			||||||
from djangorestframework.utils.mediatypes import is_form_media_type, order_by_precedence
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = (
 | 
					__all__ = (
 | 
				
			||||||
| 
						 | 
					@ -34,6 +32,7 @@ __all__ = (
 | 
				
			||||||
    'ListModelMixin'
 | 
					    'ListModelMixin'
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#TODO: In RequestMixin and ResponseMixin : get_response_class/get_request_class are a bit ugly. Do we even want to be able to set the parameters on the view ?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
########## Request Mixin ##########
 | 
					########## Request Mixin ##########
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -88,9 +87,6 @@ class ResponseMixin(object):
 | 
				
			||||||
    Ignores Accept headers from Internet Explorer user agents and uses a sensible browser Accept header instead.
 | 
					    Ignores Accept headers from Internet Explorer user agents and uses a sensible browser Accept header instead.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _ACCEPT_QUERY_PARAM = '_accept'        # Allow override of Accept header in URL query params
 | 
					 | 
				
			||||||
    _IGNORE_IE_ACCEPT_HEADER = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    renderers = ()
 | 
					    renderers = ()
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    The set of response renderers that the view can handle.
 | 
					    The set of response renderers that the view can handle.
 | 
				
			||||||
| 
						 | 
					@ -98,79 +94,27 @@ class ResponseMixin(object):
 | 
				
			||||||
    Should be a tuple/list of classes as described in the :mod:`renderers` module.
 | 
					    Should be a tuple/list of classes as described in the :mod:`renderers` module.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # TODO: wrap this behavior around dispatch(), ensuring it works
 | 
					    response_class = Response
 | 
				
			||||||
    # out of the box with existing Django classes that use render_to_response.
 | 
					
 | 
				
			||||||
    def render(self, response):
 | 
					    def prepare_response(self, response):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Takes a :obj:`Response` object and returns an :obj:`HttpResponse`.
 | 
					        Prepares response for the response cycle. Sets some headers, sets renderers, ...
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					        if hasattr(response, 'request') and response.request is None:
 | 
				
			||||||
 | 
					            response.request = self.request
 | 
				
			||||||
 | 
					        # Always add these headers.
 | 
				
			||||||
 | 
					        response['Allow'] = ', '.join(allowed_methods(self))
 | 
				
			||||||
 | 
					        # sample to allow caching using Vary http header
 | 
				
			||||||
 | 
					        response['Vary'] = 'Authenticate, Accept'
 | 
				
			||||||
 | 
					        # merge with headers possibly set at some point in the view
 | 
				
			||||||
 | 
					        for name, value in self.headers.items():
 | 
				
			||||||
 | 
					            response[name] = value
 | 
				
			||||||
 | 
					        # set the views renderers on the response
 | 
				
			||||||
 | 
					        response.renderers = self.renderers
 | 
				
			||||||
 | 
					        # TODO: must disappear
 | 
				
			||||||
 | 
					        response.view = self
 | 
				
			||||||
        self.response = response
 | 
					        self.response = response
 | 
				
			||||||
 | 
					        return response
 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            renderer, media_type = self._determine_renderer(self.request)
 | 
					 | 
				
			||||||
        except ErrorResponse, exc:
 | 
					 | 
				
			||||||
            renderer = self._default_renderer(self)
 | 
					 | 
				
			||||||
            media_type = renderer.media_type
 | 
					 | 
				
			||||||
            response = exc.response
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Set the media type of the response
 | 
					 | 
				
			||||||
        # Note that the renderer *could* override it in .render() if required.
 | 
					 | 
				
			||||||
        response.media_type = renderer.media_type
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Serialize the response content
 | 
					 | 
				
			||||||
        if response.has_content_body:
 | 
					 | 
				
			||||||
            content = renderer.render(response.cleaned_content, media_type)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            content = renderer.render()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Build the HTTP Response
 | 
					 | 
				
			||||||
        resp = HttpResponse(content, mimetype=response.media_type, status=response.status)
 | 
					 | 
				
			||||||
        for (key, val) in response.headers.items():
 | 
					 | 
				
			||||||
            resp[key] = val
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return resp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _determine_renderer(self, request):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Determines the appropriate renderer for the output, given the client's 'Accept' header,
 | 
					 | 
				
			||||||
        and the :attr:`renderers` set on this class.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Returns a 2-tuple of `(renderer, media_type)`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if self._ACCEPT_QUERY_PARAM and request.GET.get(self._ACCEPT_QUERY_PARAM, None):
 | 
					 | 
				
			||||||
            # Use _accept parameter override
 | 
					 | 
				
			||||||
            accept_list = [request.GET.get(self._ACCEPT_QUERY_PARAM)]
 | 
					 | 
				
			||||||
        elif (self._IGNORE_IE_ACCEPT_HEADER and
 | 
					 | 
				
			||||||
              'HTTP_USER_AGENT' in request.META and
 | 
					 | 
				
			||||||
              MSIE_USER_AGENT_REGEX.match(request.META['HTTP_USER_AGENT'])):
 | 
					 | 
				
			||||||
            # Ignore MSIE's broken accept behavior and do something sensible instead
 | 
					 | 
				
			||||||
            accept_list = ['text/html', '*/*']
 | 
					 | 
				
			||||||
        elif 'HTTP_ACCEPT' in request.META:
 | 
					 | 
				
			||||||
            # Use standard HTTP Accept negotiation
 | 
					 | 
				
			||||||
            accept_list = [token.strip() for token in request.META['HTTP_ACCEPT'].split(',')]
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            # No accept header specified
 | 
					 | 
				
			||||||
            accept_list = ['*/*']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Check the acceptable media types against each renderer,
 | 
					 | 
				
			||||||
        # attempting more specific media types first
 | 
					 | 
				
			||||||
        # NB. The inner loop here isn't as bad as it first looks :)
 | 
					 | 
				
			||||||
        #     Worst case is we're looping over len(accept_list) * len(self.renderers)
 | 
					 | 
				
			||||||
        renderers = [renderer_cls(self) for renderer_cls in self.renderers]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for accepted_media_type_lst in order_by_precedence(accept_list):
 | 
					 | 
				
			||||||
            for renderer in renderers:
 | 
					 | 
				
			||||||
                for accepted_media_type in accepted_media_type_lst:
 | 
					 | 
				
			||||||
                    if renderer.can_handle_response(accepted_media_type):
 | 
					 | 
				
			||||||
                        return renderer, accepted_media_type
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # No acceptable renderers were found
 | 
					 | 
				
			||||||
        raise ErrorResponse(status.HTTP_406_NOT_ACCEPTABLE,
 | 
					 | 
				
			||||||
                                {'detail': 'Could not satisfy the client\'s Accept header',
 | 
					 | 
				
			||||||
                                 'available_types': self._rendered_media_types})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def _rendered_media_types(self):
 | 
					    def _rendered_media_types(self):
 | 
				
			||||||
| 
						 | 
					@ -193,6 +137,17 @@ class ResponseMixin(object):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return self.renderers[0]
 | 
					        return self.renderers[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def headers(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Dictionary of headers to set on the response.
 | 
				
			||||||
 | 
					        This is useful when the response doesn't exist yet, but you
 | 
				
			||||||
 | 
					        want to memorize some headers to set on it when it will exist.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if not hasattr(self, '_headers'):
 | 
				
			||||||
 | 
					            self._headers = {}
 | 
				
			||||||
 | 
					        return self._headers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
########## Auth Mixin ##########
 | 
					########## Auth Mixin ##########
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -429,7 +384,7 @@ class ReadModelMixin(ModelMixin):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            self.model_instance = self.get_instance(**query_kwargs)
 | 
					            self.model_instance = self.get_instance(**query_kwargs)
 | 
				
			||||||
        except model.DoesNotExist:
 | 
					        except model.DoesNotExist:
 | 
				
			||||||
            raise ErrorResponse(status.HTTP_404_NOT_FOUND)
 | 
					            raise ErrorResponse(status=status.HTTP_404_NOT_FOUND)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self.model_instance
 | 
					        return self.model_instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -468,10 +423,12 @@ class CreateModelMixin(ModelMixin):
 | 
				
			||||||
                    data[m2m_data[fieldname][0]] = related_item
 | 
					                    data[m2m_data[fieldname][0]] = related_item
 | 
				
			||||||
                    manager.through(**data).save()
 | 
					                    manager.through(**data).save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        headers = {}
 | 
					        response = Response(instance, status=status.HTTP_201_CREATED)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Set headers
 | 
				
			||||||
        if hasattr(instance, 'get_absolute_url'):
 | 
					        if hasattr(instance, 'get_absolute_url'):
 | 
				
			||||||
            headers['Location'] = self.resource(self).url(instance)
 | 
					            response['Location'] = self.resource(self).url(instance)
 | 
				
			||||||
        return Response(status.HTTP_201_CREATED, instance, headers)
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UpdateModelMixin(ModelMixin):
 | 
					class UpdateModelMixin(ModelMixin):
 | 
				
			||||||
| 
						 | 
					@ -492,7 +449,7 @@ class UpdateModelMixin(ModelMixin):
 | 
				
			||||||
        except model.DoesNotExist:
 | 
					        except model.DoesNotExist:
 | 
				
			||||||
            self.model_instance = model(**self.get_instance_data(model, self.CONTENT, *args, **kwargs))
 | 
					            self.model_instance = model(**self.get_instance_data(model, self.CONTENT, *args, **kwargs))
 | 
				
			||||||
        self.model_instance.save()
 | 
					        self.model_instance.save()
 | 
				
			||||||
        return self.model_instance
 | 
					        return Response(self.model_instance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DeleteModelMixin(ModelMixin):
 | 
					class DeleteModelMixin(ModelMixin):
 | 
				
			||||||
| 
						 | 
					@ -506,10 +463,10 @@ class DeleteModelMixin(ModelMixin):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            instance = self.get_instance(**query_kwargs)
 | 
					            instance = self.get_instance(**query_kwargs)
 | 
				
			||||||
        except model.DoesNotExist:
 | 
					        except model.DoesNotExist:
 | 
				
			||||||
            raise ErrorResponse(status.HTTP_404_NOT_FOUND, None, {})
 | 
					            raise ErrorResponse(status=status.HTTP_404_NOT_FOUND)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        instance.delete()
 | 
					        instance.delete()
 | 
				
			||||||
        return
 | 
					        return Response()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ListModelMixin(ModelMixin):
 | 
					class ListModelMixin(ModelMixin):
 | 
				
			||||||
| 
						 | 
					@ -526,7 +483,7 @@ class ListModelMixin(ModelMixin):
 | 
				
			||||||
        if ordering:
 | 
					        if ordering:
 | 
				
			||||||
            queryset = queryset.order_by(*ordering)
 | 
					            queryset = queryset.order_by(*ordering)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return queryset
 | 
					        return Response(queryset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
########## Pagination Mixins ##########
 | 
					########## Pagination Mixins ##########
 | 
				
			||||||
| 
						 | 
					@ -613,12 +570,14 @@ class PaginatorMixin(object):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            page_num = int(self.request.GET.get('page', '1'))
 | 
					            page_num = int(self.request.GET.get('page', '1'))
 | 
				
			||||||
        except ValueError:
 | 
					        except ValueError:
 | 
				
			||||||
            raise ErrorResponse(status.HTTP_404_NOT_FOUND,
 | 
					            raise ErrorResponse(
 | 
				
			||||||
                                {'detail': 'That page contains no results'})
 | 
					                content={'detail': 'That page contains no results'},
 | 
				
			||||||
 | 
					                status=status.HTTP_404_NOT_FOUND)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if page_num not in paginator.page_range:
 | 
					        if page_num not in paginator.page_range:
 | 
				
			||||||
            raise ErrorResponse(status.HTTP_404_NOT_FOUND,
 | 
					            raise ErrorResponse(
 | 
				
			||||||
                                {'detail': 'That page contains no results'})
 | 
					                content={'detail': 'That page contains no results'},
 | 
				
			||||||
 | 
					                status=status.HTTP_404_NOT_FOUND)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        page = paginator.page(page_num)
 | 
					        page = paginator.page(page_num)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -88,8 +88,9 @@ class JSONParser(BaseParser):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            return (json.load(stream), None)
 | 
					            return (json.load(stream), None)
 | 
				
			||||||
        except ValueError, exc:
 | 
					        except ValueError, exc:
 | 
				
			||||||
            raise ErrorResponse(status.HTTP_400_BAD_REQUEST,
 | 
					            raise ErrorResponse(
 | 
				
			||||||
                                {'detail': 'JSON parse error - %s' % unicode(exc)})
 | 
					                content={'detail': 'JSON parse error - %s' % unicode(exc)},
 | 
				
			||||||
 | 
					                status=status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if yaml:
 | 
					if yaml:
 | 
				
			||||||
| 
						 | 
					@ -110,8 +111,9 @@ if yaml:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                return (yaml.safe_load(stream), None)
 | 
					                return (yaml.safe_load(stream), None)
 | 
				
			||||||
            except ValueError, exc:
 | 
					            except ValueError, exc:
 | 
				
			||||||
                raise ErrorResponse(status.HTTP_400_BAD_REQUEST,
 | 
					                raise ErrorResponse(
 | 
				
			||||||
                                    {'detail': 'YAML parse error - %s' % unicode(exc)})
 | 
					                    content={'detail': 'YAML parse error - %s' % unicode(exc)},
 | 
				
			||||||
 | 
					                    status=status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
else:
 | 
					else:
 | 
				
			||||||
    YAMLParser = None
 | 
					    YAMLParser = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -170,8 +172,9 @@ class MultiPartParser(BaseParser):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            django_parser = DjangoMultiPartParser(self.view.META, stream, upload_handlers)
 | 
					            django_parser = DjangoMultiPartParser(self.view.META, stream, upload_handlers)
 | 
				
			||||||
        except MultiPartParserError, exc:
 | 
					        except MultiPartParserError, exc:
 | 
				
			||||||
            raise ErrorResponse(status.HTTP_400_BAD_REQUEST,
 | 
					            raise ErrorResponse(
 | 
				
			||||||
                                {'detail': 'multipart parse error - %s' % unicode(exc)})
 | 
					                content={'detail': 'multipart parse error - %s' % unicode(exc)},
 | 
				
			||||||
 | 
					                status=status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
        return django_parser.parse()
 | 
					        return django_parser.parse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,13 +22,13 @@ __all__ = (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_403_FORBIDDEN_RESPONSE = ErrorResponse(
 | 
					_403_FORBIDDEN_RESPONSE = ErrorResponse(
 | 
				
			||||||
    status.HTTP_403_FORBIDDEN,
 | 
					    content={'detail': 'You do not have permission to access this resource. ' +
 | 
				
			||||||
    {'detail': 'You do not have permission to access this resource. ' +
 | 
					               'You may need to login or otherwise authenticate the request.'},
 | 
				
			||||||
               'You may need to login or otherwise authenticate the request.'})
 | 
					    status=status.HTTP_403_FORBIDDEN)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_503_SERVICE_UNAVAILABLE = ErrorResponse(
 | 
					_503_SERVICE_UNAVAILABLE = ErrorResponse(
 | 
				
			||||||
    status.HTTP_503_SERVICE_UNAVAILABLE,
 | 
					    content={'detail': 'request was throttled'},
 | 
				
			||||||
    {'detail': 'request was throttled'})
 | 
					    status=status.HTTP_503_SERVICE_UNAVAILABLE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BasePermission(object):
 | 
					class BasePermission(object):
 | 
				
			||||||
| 
						 | 
					@ -152,7 +152,7 @@ class BaseThrottle(BasePermission):
 | 
				
			||||||
        self.history.insert(0, self.now)
 | 
					        self.history.insert(0, self.now)
 | 
				
			||||||
        cache.set(self.key, self.history, self.duration)
 | 
					        cache.set(self.key, self.history, self.duration)
 | 
				
			||||||
        header = 'status=SUCCESS; next=%s sec' % self.next()
 | 
					        header = 'status=SUCCESS; next=%s sec' % self.next()
 | 
				
			||||||
        self.view.add_header('X-Throttle', header)
 | 
					        self.view.headers['X-Throttle'] = header
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def throttle_failure(self):
 | 
					    def throttle_failure(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					@ -160,7 +160,7 @@ class BaseThrottle(BasePermission):
 | 
				
			||||||
        Raises a '503 service unavailable' response.
 | 
					        Raises a '503 service unavailable' response.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        header = 'status=FAILURE; next=%s sec' % self.next()
 | 
					        header = 'status=FAILURE; next=%s sec' % self.next()
 | 
				
			||||||
        self.view.add_header('X-Throttle', header)
 | 
					        self.view.headers['X-Throttle'] = header
 | 
				
			||||||
        raise _503_SERVICE_UNAVAILABLE
 | 
					        raise _503_SERVICE_UNAVAILABLE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def next(self):
 | 
					    def next(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,9 +60,13 @@ class BaseRenderer(object):
 | 
				
			||||||
        This may be overridden to provide for other behavior, but typically you'll
 | 
					        This may be overridden to provide for other behavior, but typically you'll
 | 
				
			||||||
        instead want to just set the :attr:`media_type` attribute on the class.
 | 
					        instead want to just set the :attr:`media_type` attribute on the class.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        format = self.view.kwargs.get(self._FORMAT_QUERY_PARAM, None)
 | 
					        # TODO: format overriding must go out of here
 | 
				
			||||||
        if format is None:
 | 
					        format = None
 | 
				
			||||||
 | 
					        if self.view is not None:
 | 
				
			||||||
 | 
					            format = self.view.kwargs.get(self._FORMAT_QUERY_PARAM, None)
 | 
				
			||||||
 | 
					        if format is None and self.view is not None:
 | 
				
			||||||
            format = self.view.request.GET.get(self._FORMAT_QUERY_PARAM, None)
 | 
					            format = self.view.request.GET.get(self._FORMAT_QUERY_PARAM, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if format is not None:
 | 
					        if format is not None:
 | 
				
			||||||
            return format == self.format
 | 
					            return format == self.format
 | 
				
			||||||
        return media_type_matches(self.media_type, accept)
 | 
					        return media_type_matches(self.media_type, accept)
 | 
				
			||||||
| 
						 | 
					@ -359,8 +363,8 @@ class DocumentingTemplateRenderer(BaseRenderer):
 | 
				
			||||||
        # Munge DELETE Response code to allow us to return content
 | 
					        # Munge DELETE Response code to allow us to return content
 | 
				
			||||||
        # (Do this *after* we've rendered the template so that we include
 | 
					        # (Do this *after* we've rendered the template so that we include
 | 
				
			||||||
        # the normal deletion response code in the output)
 | 
					        # the normal deletion response code in the output)
 | 
				
			||||||
        if self.view.response.status == 204:
 | 
					        if self.view.response.status_code == 204:
 | 
				
			||||||
            self.view.response.status = 200
 | 
					            self.view.response.status_code = 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return ret
 | 
					        return ret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -206,9 +206,9 @@ class Request(object):
 | 
				
			||||||
            if parser.can_handle_request(content_type):
 | 
					            if parser.can_handle_request(content_type):
 | 
				
			||||||
                return parser.parse(stream)
 | 
					                return parser.parse(stream)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        raise ErrorResponse(status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
 | 
					        raise ErrorResponse(content={'error':
 | 
				
			||||||
                            {'error': 'Unsupported media type in request \'%s\'.' %
 | 
					                            'Unsupported media type in request \'%s\'.' % content_type},
 | 
				
			||||||
                            content_type})
 | 
					                        status=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def _parsed_media_types(self):
 | 
					    def _parsed_media_types(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -174,7 +174,7 @@ class FormResource(Resource):
 | 
				
			||||||
                detail[u'field_errors'] = field_errors
 | 
					                detail[u'field_errors'] = field_errors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Return HTTP 400 response (BAD REQUEST)
 | 
					        # Return HTTP 400 response (BAD REQUEST)
 | 
				
			||||||
        raise ErrorResponse(400, detail)
 | 
					        raise ErrorResponse(content=detail, status=400)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_form_class(self, method=None):
 | 
					    def get_form_class(self, method=None):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,25 +5,62 @@ into a HTTP response depending on what renderers are set on your view and
 | 
				
			||||||
als depending on the accept header of the request.
 | 
					als depending on the accept header of the request.
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.template.response import SimpleTemplateResponse
 | 
				
			||||||
from django.core.handlers.wsgi import STATUS_CODE_TEXT
 | 
					from django.core.handlers.wsgi import STATUS_CODE_TEXT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from djangorestframework.utils.mediatypes import order_by_precedence
 | 
				
			||||||
 | 
					from djangorestframework.utils import MSIE_USER_AGENT_REGEX
 | 
				
			||||||
 | 
					from djangorestframework import status
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = ('Response', 'ErrorResponse')
 | 
					__all__ = ('Response', 'ErrorResponse')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO: remove raw_content/cleaned_content and just use content?
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Response(SimpleTemplateResponse):
 | 
				
			||||||
class Response(object):
 | 
					 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    An HttpResponse that may include content that hasn't yet been serialized.
 | 
					    An HttpResponse that may include content that hasn't yet been serialized.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, status=200, content=None, headers=None):
 | 
					    _ACCEPT_QUERY_PARAM = '_accept'        # Allow override of Accept header in URL query params
 | 
				
			||||||
        self.status = status
 | 
					    _IGNORE_IE_ACCEPT_HEADER = True
 | 
				
			||||||
        self.media_type = None
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, content=None, status=None, request=None, renderers=None):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        content is the raw content.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The set of renderers that the response can handle.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Should be a tuple/list of classes as described in the :mod:`renderers` module.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # First argument taken by `SimpleTemplateResponse.__init__` is template_name,
 | 
				
			||||||
 | 
					        # which we don't need
 | 
				
			||||||
 | 
					        super(Response, self).__init__(None, status=status)
 | 
				
			||||||
 | 
					        # We need to store our content in raw content to avoid overriding HttpResponse's
 | 
				
			||||||
 | 
					        # `content` property
 | 
				
			||||||
 | 
					        self.raw_content = content 
 | 
				
			||||||
        self.has_content_body = content is not None
 | 
					        self.has_content_body = content is not None
 | 
				
			||||||
        self.raw_content = content      # content prior to filtering
 | 
					        self.request = request
 | 
				
			||||||
        self.cleaned_content = content  # content after filtering
 | 
					        if renderers is not None:
 | 
				
			||||||
        self.headers = headers or {}
 | 
					            self.renderers = renderers
 | 
				
			||||||
 | 
					        # TODO: must go
 | 
				
			||||||
 | 
					        self.view = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO: wrap this behavior around dispatch(), ensuring it works
 | 
				
			||||||
 | 
					    # out of the box with existing Django classes that use render_to_response.
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def rendered_content(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        renderer, media_type = self._determine_renderer()
 | 
				
			||||||
 | 
					        # TODO: renderer *could* override media_type in .render() if required.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Set the media type of the response
 | 
				
			||||||
 | 
					        self['Content-Type'] = renderer.media_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Render the response content
 | 
				
			||||||
 | 
					        if self.has_content_body:
 | 
				
			||||||
 | 
					            return renderer.render(self.raw_content, media_type)
 | 
				
			||||||
 | 
					        return renderer.render()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def status_text(self):
 | 
					    def status_text(self):
 | 
				
			||||||
| 
						 | 
					@ -33,12 +70,92 @@ class Response(object):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return STATUS_CODE_TEXT.get(self.status, '')
 | 
					        return STATUS_CODE_TEXT.get(self.status, '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _determine_accept_list(self):
 | 
				
			||||||
 | 
					        request = self.request
 | 
				
			||||||
 | 
					        if request is None:
 | 
				
			||||||
 | 
					            return ['*/*']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ErrorResponse(BaseException):
 | 
					        if self._ACCEPT_QUERY_PARAM and request.GET.get(self._ACCEPT_QUERY_PARAM, None):
 | 
				
			||||||
 | 
					            # Use _accept parameter override
 | 
				
			||||||
 | 
					            return [request.GET.get(self._ACCEPT_QUERY_PARAM)]
 | 
				
			||||||
 | 
					        elif (self._IGNORE_IE_ACCEPT_HEADER and
 | 
				
			||||||
 | 
					              'HTTP_USER_AGENT' in request.META and
 | 
				
			||||||
 | 
					              MSIE_USER_AGENT_REGEX.match(request.META['HTTP_USER_AGENT'])):
 | 
				
			||||||
 | 
					            # Ignore MSIE's broken accept behavior and do something sensible instead
 | 
				
			||||||
 | 
					            return ['text/html', '*/*']
 | 
				
			||||||
 | 
					        elif 'HTTP_ACCEPT' in request.META:
 | 
				
			||||||
 | 
					            # Use standard HTTP Accept negotiation
 | 
				
			||||||
 | 
					            return [token.strip() for token in request.META['HTTP_ACCEPT'].split(',')]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # No accept header specified
 | 
				
			||||||
 | 
					            return ['*/*']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _determine_renderer(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Determines the appropriate renderer for the output, given the client's 'Accept' header,
 | 
				
			||||||
 | 
					        and the :attr:`renderers` set on this class.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Returns a 2-tuple of `(renderer, media_type)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        See: RFC 2616, Section 14 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # Check the acceptable media types against each renderer,
 | 
				
			||||||
 | 
					        # attempting more specific media types first
 | 
				
			||||||
 | 
					        # NB. The inner loop here isn't as bad as it first looks :)
 | 
				
			||||||
 | 
					        #     Worst case is we're looping over len(accept_list) * len(self.renderers)
 | 
				
			||||||
 | 
					        renderers = [renderer_cls(self.view) for renderer_cls in self.renderers]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for media_type_list in order_by_precedence(self._determine_accept_list()):
 | 
				
			||||||
 | 
					            for renderer in renderers:
 | 
				
			||||||
 | 
					                for media_type in media_type_list:
 | 
				
			||||||
 | 
					                    if renderer.can_handle_response(media_type):
 | 
				
			||||||
 | 
					                        return renderer, media_type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # No acceptable renderers were found
 | 
				
			||||||
 | 
					        raise ErrorResponse(content={'detail': 'Could not satisfy the client\'s Accept header',
 | 
				
			||||||
 | 
					                                 'available_types': self._rendered_media_types},
 | 
				
			||||||
 | 
					                        status=status.HTTP_406_NOT_ACCEPTABLE,
 | 
				
			||||||
 | 
					                        renderers=self.renderers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_renderers(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        This just provides a default when renderers havent' been set.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if hasattr(self, '_renderers'):
 | 
				
			||||||
 | 
					            return self._renderers
 | 
				
			||||||
 | 
					        return ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _set_renderers(self, value):
 | 
				
			||||||
 | 
					        self._renderers = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    renderers = property(_get_renderers, _set_renderers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def _rendered_media_types(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return an list of all the media types that this response can render.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return [renderer.media_type for renderer in self.renderers]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def _rendered_formats(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return a list of all the formats that this response can render.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return [renderer.format for renderer in self.renderers]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def _default_renderer(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return the response's default renderer class.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return self.renderers[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ErrorResponse(Response, BaseException):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    An exception representing an Response that should be returned immediately.
 | 
					    An exception representing an Response that should be returned immediately.
 | 
				
			||||||
    Any content should be serialized as-is, without being filtered.
 | 
					    Any content should be serialized as-is, without being filtered.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, status, content=None, headers={}):
 | 
					 | 
				
			||||||
        self.response = Response(status, content=content, headers=headers)
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,8 @@
 | 
				
			||||||
from django.test import TestCase
 | 
					from django.test import TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from djangorestframework.compat import RequestFactory
 | 
					from djangorestframework.compat import RequestFactory
 | 
				
			||||||
from djangorestframework.views import View
 | 
					from djangorestframework.views import View
 | 
				
			||||||
 | 
					from djangorestframework.response import Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# See: http://www.useragentstring.com/
 | 
					# See: http://www.useragentstring.com/
 | 
				
			||||||
| 
						 | 
					@ -23,7 +25,7 @@ class UserAgentMungingTest(TestCase):
 | 
				
			||||||
            permissions = ()
 | 
					            permissions = ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def get(self, request):
 | 
					            def get(self, request):
 | 
				
			||||||
                return {'a':1, 'b':2, 'c':3}
 | 
					                return Response({'a':1, 'b':2, 'c':3})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.req = RequestFactory()
 | 
					        self.req = RequestFactory()
 | 
				
			||||||
        self.MockView = MockView
 | 
					        self.MockView = MockView
 | 
				
			||||||
| 
						 | 
					@ -37,18 +39,22 @@ class UserAgentMungingTest(TestCase):
 | 
				
			||||||
                           MSIE_7_USER_AGENT):
 | 
					                           MSIE_7_USER_AGENT):
 | 
				
			||||||
            req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
 | 
					            req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
 | 
				
			||||||
            resp = self.view(req)
 | 
					            resp = self.view(req)
 | 
				
			||||||
 | 
					            resp.render()
 | 
				
			||||||
            self.assertEqual(resp['Content-Type'], 'text/html')
 | 
					            self.assertEqual(resp['Content-Type'], 'text/html')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    def test_dont_rewrite_msie_accept_header(self):
 | 
					    def test_dont_rewrite_msie_accept_header(self):
 | 
				
			||||||
        """Turn off _IGNORE_IE_ACCEPT_HEADER, send MSIE user agent strings and ensure
 | 
					        """Turn off _IGNORE_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.MockView.as_view(_IGNORE_IE_ACCEPT_HEADER=False)
 | 
					        class IgnoreIEAcceptResponse(Response):
 | 
				
			||||||
 | 
					            _IGNORE_IE_ACCEPT_HEADER=False
 | 
				
			||||||
 | 
					        view = self.MockView.as_view(response_class=IgnoreIEAcceptResponse)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for user_agent in (MSIE_9_USER_AGENT,
 | 
					        for user_agent in (MSIE_9_USER_AGENT,
 | 
				
			||||||
                           MSIE_8_USER_AGENT,
 | 
					                           MSIE_8_USER_AGENT,
 | 
				
			||||||
                           MSIE_7_USER_AGENT):
 | 
					                           MSIE_7_USER_AGENT):
 | 
				
			||||||
            req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
 | 
					            req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
 | 
				
			||||||
            resp = view(req)
 | 
					            resp = view(req)
 | 
				
			||||||
 | 
					            resp.render()
 | 
				
			||||||
            self.assertEqual(resp['Content-Type'], 'application/json')
 | 
					            self.assertEqual(resp['Content-Type'], 'application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_dont_munge_nice_browsers_accept_header(self):
 | 
					    def test_dont_munge_nice_browsers_accept_header(self):
 | 
				
			||||||
| 
						 | 
					@ -61,5 +67,6 @@ class UserAgentMungingTest(TestCase):
 | 
				
			||||||
                           OPERA_11_0_OPERA_USER_AGENT):
 | 
					                           OPERA_11_0_OPERA_USER_AGENT):
 | 
				
			||||||
            req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
 | 
					            req = self.req.get('/', HTTP_ACCEPT='*/*', HTTP_USER_AGENT=user_agent)
 | 
				
			||||||
            resp = self.view(req)
 | 
					            resp = self.view(req)
 | 
				
			||||||
 | 
					            resp.render()
 | 
				
			||||||
            self.assertEqual(resp['Content-Type'], 'application/json')
 | 
					            self.assertEqual(resp['Content-Type'], 'application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ from django.contrib.auth.models import User
 | 
				
			||||||
from django.test import Client, TestCase
 | 
					from django.test import Client, TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.utils import simplejson as json
 | 
					from django.utils import simplejson as json
 | 
				
			||||||
 | 
					from django.http import HttpResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from djangorestframework.views import View
 | 
					from djangorestframework.views import View
 | 
				
			||||||
from djangorestframework import permissions
 | 
					from djangorestframework import permissions
 | 
				
			||||||
| 
						 | 
					@ -14,10 +15,10 @@ class MockView(View):
 | 
				
			||||||
    permissions = (permissions.IsAuthenticated,)
 | 
					    permissions = (permissions.IsAuthenticated,)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post(self, request):
 | 
					    def post(self, request):
 | 
				
			||||||
        return {'a': 1, 'b': 2, 'c': 3}
 | 
					        return HttpResponse({'a': 1, 'b': 2, 'c': 3})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def put(self, request):
 | 
					    def put(self, request):
 | 
				
			||||||
        return {'a': 1, 'b': 2, 'c': 3}
 | 
					        return HttpResponse({'a': 1, 'b': 2, 'c': 3})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
urlpatterns = patterns('',
 | 
					urlpatterns = patterns('',
 | 
				
			||||||
    (r'^$', MockView.as_view()),
 | 
					    (r'^$', MockView.as_view()),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,11 @@
 | 
				
			||||||
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.views import View
 | 
					from djangorestframework.views import View
 | 
				
			||||||
from djangorestframework.resources import FormResource
 | 
					from djangorestframework.resources import FormResource
 | 
				
			||||||
 | 
					from djangorestframework.response import Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import StringIO
 | 
					import StringIO
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UploadFilesTests(TestCase):
 | 
					class UploadFilesTests(TestCase):
 | 
				
			||||||
| 
						 | 
					@ -20,13 +23,13 @@ class UploadFilesTests(TestCase):
 | 
				
			||||||
            form = FileForm
 | 
					            form = FileForm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def post(self, request, *args, **kwargs):
 | 
					            def post(self, request, *args, **kwargs):
 | 
				
			||||||
                return {'FILE_NAME': self.CONTENT['file'].name,
 | 
					                return Response({'FILE_NAME': self.CONTENT['file'].name,
 | 
				
			||||||
                        'FILE_CONTENT': self.CONTENT['file'].read()}
 | 
					                        'FILE_CONTENT': self.CONTENT['file'].read()})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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 = MockView.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.raw_content, {"FILE_CONTENT": "stuff", "FILE_NAME": "stuff.txt"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -65,7 +65,7 @@ class TestModelCreation(TestModelsTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        response = mixin.post(request)
 | 
					        response = mixin.post(request)
 | 
				
			||||||
        self.assertEquals(1, Group.objects.count())
 | 
					        self.assertEquals(1, Group.objects.count())
 | 
				
			||||||
        self.assertEquals('foo', response.cleaned_content.name)
 | 
					        self.assertEquals('foo', response.raw_content.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_creation_with_m2m_relation(self):
 | 
					    def test_creation_with_m2m_relation(self):
 | 
				
			||||||
        class UserResource(ModelResource):
 | 
					        class UserResource(ModelResource):
 | 
				
			||||||
| 
						 | 
					@ -91,8 +91,8 @@ class TestModelCreation(TestModelsTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        response = mixin.post(request)
 | 
					        response = mixin.post(request)
 | 
				
			||||||
        self.assertEquals(1, User.objects.count())
 | 
					        self.assertEquals(1, User.objects.count())
 | 
				
			||||||
        self.assertEquals(1, response.cleaned_content.groups.count())
 | 
					        self.assertEquals(1, response.raw_content.groups.count())
 | 
				
			||||||
        self.assertEquals('foo', response.cleaned_content.groups.all()[0].name)
 | 
					        self.assertEquals('foo', response.raw_content.groups.all()[0].name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_creation_with_m2m_relation_through(self):
 | 
					    def test_creation_with_m2m_relation_through(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					@ -114,7 +114,7 @@ class TestModelCreation(TestModelsTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        response = mixin.post(request)
 | 
					        response = mixin.post(request)
 | 
				
			||||||
        self.assertEquals(1, CustomUser.objects.count())
 | 
					        self.assertEquals(1, CustomUser.objects.count())
 | 
				
			||||||
        self.assertEquals(0, response.cleaned_content.groups.count())
 | 
					        self.assertEquals(0, response.raw_content.groups.count())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        group = Group(name='foo1')
 | 
					        group = Group(name='foo1')
 | 
				
			||||||
        group.save()
 | 
					        group.save()
 | 
				
			||||||
| 
						 | 
					@ -129,8 +129,8 @@ class TestModelCreation(TestModelsTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        response = mixin.post(request)
 | 
					        response = mixin.post(request)
 | 
				
			||||||
        self.assertEquals(2, CustomUser.objects.count())
 | 
					        self.assertEquals(2, CustomUser.objects.count())
 | 
				
			||||||
        self.assertEquals(1, response.cleaned_content.groups.count())
 | 
					        self.assertEquals(1, response.raw_content.groups.count())
 | 
				
			||||||
        self.assertEquals('foo1', response.cleaned_content.groups.all()[0].name)
 | 
					        self.assertEquals('foo1', response.raw_content.groups.all()[0].name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        group2 = Group(name='foo2')
 | 
					        group2 = Group(name='foo2')
 | 
				
			||||||
        group2.save()
 | 
					        group2.save()
 | 
				
			||||||
| 
						 | 
					@ -145,19 +145,19 @@ class TestModelCreation(TestModelsTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        response = mixin.post(request)
 | 
					        response = mixin.post(request)
 | 
				
			||||||
        self.assertEquals(3, CustomUser.objects.count())
 | 
					        self.assertEquals(3, CustomUser.objects.count())
 | 
				
			||||||
        self.assertEquals(2, response.cleaned_content.groups.count())
 | 
					        self.assertEquals(2, response.raw_content.groups.count())
 | 
				
			||||||
        self.assertEquals('foo1', response.cleaned_content.groups.all()[0].name)
 | 
					        self.assertEquals('foo1', response.raw_content.groups.all()[0].name)
 | 
				
			||||||
        self.assertEquals('foo2', response.cleaned_content.groups.all()[1].name)
 | 
					        self.assertEquals('foo2', response.raw_content.groups.all()[1].name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MockPaginatorView(PaginatorMixin, View):
 | 
					class MockPaginatorView(PaginatorMixin, View):
 | 
				
			||||||
    total = 60
 | 
					    total = 60
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, request):
 | 
					    def get(self, request):
 | 
				
			||||||
        return range(0, self.total)
 | 
					        return Response(range(0, self.total))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def post(self, request):
 | 
					    def post(self, request):
 | 
				
			||||||
        return Response(status.HTTP_201_CREATED, {'status': 'OK'})
 | 
					        return Response({'status': 'OK'}, status=status.HTTP_201_CREATED)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestPagination(TestCase):
 | 
					class TestPagination(TestCase):
 | 
				
			||||||
| 
						 | 
					@ -168,8 +168,7 @@ class TestPagination(TestCase):
 | 
				
			||||||
        """ Tests if pagination works without overwriting the limit """
 | 
					        """ Tests if pagination works without overwriting the limit """
 | 
				
			||||||
        request = self.req.get('/paginator')
 | 
					        request = self.req.get('/paginator')
 | 
				
			||||||
        response = MockPaginatorView.as_view()(request)
 | 
					        response = MockPaginatorView.as_view()(request)
 | 
				
			||||||
 | 
					        content = response.raw_content
 | 
				
			||||||
        content = json.loads(response.content)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
					        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
				
			||||||
        self.assertEqual(MockPaginatorView.total, content['total'])
 | 
					        self.assertEqual(MockPaginatorView.total, content['total'])
 | 
				
			||||||
| 
						 | 
					@ -183,8 +182,7 @@ class TestPagination(TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        request = self.req.get('/paginator')
 | 
					        request = self.req.get('/paginator')
 | 
				
			||||||
        response = MockPaginatorView.as_view(limit=limit)(request)
 | 
					        response = MockPaginatorView.as_view(limit=limit)(request)
 | 
				
			||||||
 | 
					        content = response.raw_content
 | 
				
			||||||
        content = json.loads(response.content)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
					        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
				
			||||||
        self.assertEqual(content['per_page'], limit)
 | 
					        self.assertEqual(content['per_page'], limit)
 | 
				
			||||||
| 
						 | 
					@ -200,8 +198,7 @@ class TestPagination(TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        request = self.req.get('/paginator/?limit=%d' % limit)
 | 
					        request = self.req.get('/paginator/?limit=%d' % limit)
 | 
				
			||||||
        response = MockPaginatorView.as_view()(request)
 | 
					        response = MockPaginatorView.as_view()(request)
 | 
				
			||||||
 | 
					        content = response.raw_content
 | 
				
			||||||
        content = json.loads(response.content)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
					        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
				
			||||||
        self.assertEqual(MockPaginatorView.total, content['total'])
 | 
					        self.assertEqual(MockPaginatorView.total, content['total'])
 | 
				
			||||||
| 
						 | 
					@ -217,8 +214,7 @@ class TestPagination(TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        request = self.req.get('/paginator/?limit=%d' % limit)
 | 
					        request = self.req.get('/paginator/?limit=%d' % limit)
 | 
				
			||||||
        response = MockPaginatorView.as_view()(request)
 | 
					        response = MockPaginatorView.as_view()(request)
 | 
				
			||||||
 | 
					        content = response.raw_content
 | 
				
			||||||
        content = json.loads(response.content)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
					        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
				
			||||||
        self.assertEqual(MockPaginatorView.total, content['total'])
 | 
					        self.assertEqual(MockPaginatorView.total, content['total'])
 | 
				
			||||||
| 
						 | 
					@ -230,8 +226,7 @@ class TestPagination(TestCase):
 | 
				
			||||||
        """ Pagination should only work for GET requests """
 | 
					        """ Pagination should only work for GET requests """
 | 
				
			||||||
        request = self.req.post('/paginator', data={'content': 'spam'})
 | 
					        request = self.req.post('/paginator', data={'content': 'spam'})
 | 
				
			||||||
        response = MockPaginatorView.as_view()(request)
 | 
					        response = MockPaginatorView.as_view()(request)
 | 
				
			||||||
 | 
					        content = response.raw_content
 | 
				
			||||||
        content = json.loads(response.content)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
 | 
					        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
 | 
				
			||||||
        self.assertEqual(None, content.get('per_page'))
 | 
					        self.assertEqual(None, content.get('per_page'))
 | 
				
			||||||
| 
						 | 
					@ -248,12 +243,12 @@ class TestPagination(TestCase):
 | 
				
			||||||
        """ Tests that the page range is handle correctly """
 | 
					        """ Tests that the page range is handle correctly """
 | 
				
			||||||
        request = self.req.get('/paginator/?page=0')
 | 
					        request = self.req.get('/paginator/?page=0')
 | 
				
			||||||
        response = MockPaginatorView.as_view()(request)
 | 
					        response = MockPaginatorView.as_view()(request)
 | 
				
			||||||
        content = json.loads(response.content)
 | 
					        content = response.raw_content
 | 
				
			||||||
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
 | 
					        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        request = self.req.get('/paginator/')
 | 
					        request = self.req.get('/paginator/')
 | 
				
			||||||
        response = MockPaginatorView.as_view()(request)
 | 
					        response = MockPaginatorView.as_view()(request)
 | 
				
			||||||
        content = json.loads(response.content)
 | 
					        content = response.raw_content
 | 
				
			||||||
        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
					        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
				
			||||||
        self.assertEqual(range(0, MockPaginatorView.limit), content['results'])
 | 
					        self.assertEqual(range(0, MockPaginatorView.limit), content['results'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -261,13 +256,13 @@ class TestPagination(TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        request = self.req.get('/paginator/?page=%d' % num_pages)
 | 
					        request = self.req.get('/paginator/?page=%d' % num_pages)
 | 
				
			||||||
        response = MockPaginatorView.as_view()(request)
 | 
					        response = MockPaginatorView.as_view()(request)
 | 
				
			||||||
        content = json.loads(response.content)
 | 
					        content = response.raw_content
 | 
				
			||||||
        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
					        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
				
			||||||
        self.assertEqual(range(MockPaginatorView.limit*(num_pages-1), MockPaginatorView.total), content['results'])
 | 
					        self.assertEqual(range(MockPaginatorView.limit*(num_pages-1), MockPaginatorView.total), content['results'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        request = self.req.get('/paginator/?page=%d' % (num_pages + 1,))
 | 
					        request = self.req.get('/paginator/?page=%d' % (num_pages + 1,))
 | 
				
			||||||
        response = MockPaginatorView.as_view()(request)
 | 
					        response = MockPaginatorView.as_view()(request)
 | 
				
			||||||
        content = json.loads(response.content)
 | 
					        content = response.raw_content
 | 
				
			||||||
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
 | 
					        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_existing_query_parameters_are_preserved(self):
 | 
					    def test_existing_query_parameters_are_preserved(self):
 | 
				
			||||||
| 
						 | 
					@ -275,7 +270,7 @@ class TestPagination(TestCase):
 | 
				
			||||||
        generating next/previous page links """
 | 
					        generating next/previous page links """
 | 
				
			||||||
        request = self.req.get('/paginator/?foo=bar&another=something')
 | 
					        request = self.req.get('/paginator/?foo=bar&another=something')
 | 
				
			||||||
        response = MockPaginatorView.as_view()(request)
 | 
					        response = MockPaginatorView.as_view()(request)
 | 
				
			||||||
        content = json.loads(response.content)
 | 
					        content = response.raw_content
 | 
				
			||||||
        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
					        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
				
			||||||
        self.assertTrue('foo=bar' in content['next'])
 | 
					        self.assertTrue('foo=bar' in content['next'])
 | 
				
			||||||
        self.assertTrue('another=something' in content['next'])
 | 
					        self.assertTrue('another=something' in content['next'])
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,177 +1,20 @@
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.test import TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 import status
 | 
					from djangorestframework.response import Response
 | 
				
			||||||
from djangorestframework.views import View
 | 
					from djangorestframework.views import View
 | 
				
			||||||
from djangorestframework.compat import View as DjangoView
 | 
					 | 
				
			||||||
from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
 | 
					from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
 | 
				
			||||||
    XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer
 | 
					    XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer
 | 
				
			||||||
from djangorestframework.parsers import JSONParser, YAMLParser, XMLParser
 | 
					from djangorestframework.parsers import JSONParser, YAMLParser, XMLParser
 | 
				
			||||||
from djangorestframework.mixins import ResponseMixin
 | 
					 | 
				
			||||||
from djangorestframework.response import Response
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from StringIO import StringIO
 | 
					from StringIO import StringIO
 | 
				
			||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
from decimal import Decimal
 | 
					from decimal import Decimal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DUMMYSTATUS = status.HTTP_200_OK
 | 
					 | 
				
			||||||
DUMMYCONTENT = 'dummycontent'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
 | 
					 | 
				
			||||||
RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RendererA(BaseRenderer):
 | 
					 | 
				
			||||||
    media_type = 'mock/renderera'
 | 
					 | 
				
			||||||
    format = "formata"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def render(self, obj=None, media_type=None):
 | 
					 | 
				
			||||||
        return RENDERER_A_SERIALIZER(obj)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RendererB(BaseRenderer):
 | 
					 | 
				
			||||||
    media_type = 'mock/rendererb'
 | 
					 | 
				
			||||||
    format = "formatb"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def render(self, obj=None, media_type=None):
 | 
					 | 
				
			||||||
        return RENDERER_B_SERIALIZER(obj)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MockView(ResponseMixin, DjangoView):
 | 
					 | 
				
			||||||
    renderers = (RendererA, RendererB)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, request, **kwargs):
 | 
					 | 
				
			||||||
        response = Response(DUMMYSTATUS, DUMMYCONTENT)
 | 
					 | 
				
			||||||
        return self.render(response)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class MockGETView(View):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, request, **kwargs):
 | 
					 | 
				
			||||||
        return {'foo': ['bar', 'baz']}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class HTMLView(View):
 | 
					 | 
				
			||||||
    renderers = (DocumentingHTMLRenderer, )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, request, **kwargs):
 | 
					 | 
				
			||||||
        return 'text' 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class HTMLView1(View):
 | 
					 | 
				
			||||||
    renderers = (DocumentingHTMLRenderer, JSONRenderer)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, request, **kwargs):
 | 
					 | 
				
			||||||
        return 'text' 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
urlpatterns = patterns('',
 | 
					 | 
				
			||||||
    url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
 | 
					 | 
				
			||||||
    url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])),
 | 
					 | 
				
			||||||
    url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])),
 | 
					 | 
				
			||||||
    url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])),
 | 
					 | 
				
			||||||
    url(r'^html$', HTMLView.as_view()),
 | 
					 | 
				
			||||||
    url(r'^html1$', HTMLView1.as_view()),
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class RendererIntegrationTests(TestCase):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    End-to-end testing of renderers using an RendererMixin on a generic view.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    urls = 'djangorestframework.tests.renderers'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_default_renderer_serializes_content(self):
 | 
					 | 
				
			||||||
        """If the Accept header is not set the default renderer should serialize the response."""
 | 
					 | 
				
			||||||
        resp = self.client.get('/')
 | 
					 | 
				
			||||||
        self.assertEquals(resp['Content-Type'], RendererA.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_head_method_serializes_no_content(self):
 | 
					 | 
				
			||||||
        """No response must be included in HEAD requests."""
 | 
					 | 
				
			||||||
        resp = self.client.head('/')
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
					 | 
				
			||||||
        self.assertEquals(resp['Content-Type'], RendererA.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp.content, '')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_default_renderer_serializes_content_on_accept_any(self):
 | 
					 | 
				
			||||||
        """If the Accept header is set to */* the default renderer should serialize the response."""
 | 
					 | 
				
			||||||
        resp = self.client.get('/', HTTP_ACCEPT='*/*')
 | 
					 | 
				
			||||||
        self.assertEquals(resp['Content-Type'], RendererA.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_specified_renderer_serializes_content_default_case(self):
 | 
					 | 
				
			||||||
        """If the Accept header is set the specified renderer should serialize the response.
 | 
					 | 
				
			||||||
        (In this case we check that works for the default renderer)"""
 | 
					 | 
				
			||||||
        resp = self.client.get('/', HTTP_ACCEPT=RendererA.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp['Content-Type'], RendererA.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_specified_renderer_serializes_content_non_default_case(self):
 | 
					 | 
				
			||||||
        """If the Accept header is set the specified renderer should serialize the response.
 | 
					 | 
				
			||||||
        (In this case we check that works for a non-default renderer)"""
 | 
					 | 
				
			||||||
        resp = self.client.get('/', HTTP_ACCEPT=RendererB.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_specified_renderer_serializes_content_on_accept_query(self):
 | 
					 | 
				
			||||||
        """The '_accept' query string should behave in the same way as the Accept header."""
 | 
					 | 
				
			||||||
        resp = self.client.get('/?_accept=%s' % RendererB.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_unsatisfiable_accept_header_on_request_returns_406_status(self):
 | 
					 | 
				
			||||||
        """If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
 | 
					 | 
				
			||||||
        resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, status.HTTP_406_NOT_ACCEPTABLE)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_specified_renderer_serializes_content_on_format_query(self):
 | 
					 | 
				
			||||||
        """If a 'format' query is specified, the renderer with the matching
 | 
					 | 
				
			||||||
        format attribute should serialize the response."""
 | 
					 | 
				
			||||||
        resp = self.client.get('/?format=%s' % RendererB.format)
 | 
					 | 
				
			||||||
        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_specified_renderer_serializes_content_on_format_kwargs(self):
 | 
					 | 
				
			||||||
        """If a 'format' keyword arg is specified, the renderer with the matching
 | 
					 | 
				
			||||||
        format attribute should serialize the response."""
 | 
					 | 
				
			||||||
        resp = self.client.get('/something.formatb')
 | 
					 | 
				
			||||||
        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_specified_renderer_is_used_on_format_query_with_matching_accept(self):
 | 
					 | 
				
			||||||
        """If both a 'format' query and a matching Accept header specified,
 | 
					 | 
				
			||||||
        the renderer with the matching format attribute should serialize the response."""
 | 
					 | 
				
			||||||
        resp = self.client.get('/?format=%s' % RendererB.format,
 | 
					 | 
				
			||||||
                               HTTP_ACCEPT=RendererB.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_conflicting_format_query_and_accept_ignores_accept(self):
 | 
					 | 
				
			||||||
        """If a 'format' query is specified that does not match the Accept
 | 
					 | 
				
			||||||
        header, we should only honor the 'format' query string."""
 | 
					 | 
				
			||||||
        resp = self.client.get('/?format=%s' % RendererB.format,
 | 
					 | 
				
			||||||
                               HTTP_ACCEPT='dummy')
 | 
					 | 
				
			||||||
        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_bla(self):
 | 
					 | 
				
			||||||
        resp = self.client.get('/?format=formatb',
 | 
					 | 
				
			||||||
            HTTP_ACCEPT='text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')
 | 
					 | 
				
			||||||
        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
					 | 
				
			||||||
        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
					 | 
				
			||||||
        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
_flat_repr = '{"foo": ["bar", "baz"]}'
 | 
					_flat_repr = '{"foo": ["bar", "baz"]}'
 | 
				
			||||||
_indented_repr = '{\n  "foo": [\n    "bar",\n    "baz"\n  ]\n}'
 | 
					_indented_repr = '{\n  "foo": [\n    "bar",\n    "baz"\n  ]\n}'
 | 
				
			||||||
| 
						 | 
					@ -223,6 +66,18 @@ class JSONRendererTests(TestCase):
 | 
				
			||||||
        self.assertEquals(obj, data)
 | 
					        self.assertEquals(obj, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MockGETView(View):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, request, **kwargs):
 | 
				
			||||||
 | 
					        return Response({'foo': ['bar', 'baz']})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					urlpatterns = patterns('',
 | 
				
			||||||
 | 
					    url(r'^jsonp/jsonrenderer$', MockGETView.as_view(renderers=[JSONRenderer, JSONPRenderer])),
 | 
				
			||||||
 | 
					    url(r'^jsonp/nojsonrenderer$', MockGETView.as_view(renderers=[JSONPRenderer])),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class JSONPRendererTests(TestCase):
 | 
					class JSONPRendererTests(TestCase):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Tests specific to the JSONP Renderer
 | 
					    Tests specific to the JSONP Renderer
 | 
				
			||||||
| 
						 | 
					@ -391,21 +246,3 @@ class XMLRendererTestCase(TestCase):
 | 
				
			||||||
        self.assertTrue(xml.endswith('</root>'))
 | 
					        self.assertTrue(xml.endswith('</root>'))
 | 
				
			||||||
        self.assertTrue(string in xml, '%r not in %r' % (string, xml))
 | 
					        self.assertTrue(string in xml, '%r not in %r' % (string, xml))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
class Issue122Tests(TestCase):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Tests that covers #122.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    urls = 'djangorestframework.tests.renderers'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def test_only_html_renderer(self):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Test if no infinite recursion occurs.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        resp = self.client.get('/html')
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    def test_html_renderer_is_first(self):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Test if no infinite recursion occurs.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        resp = self.client.get('/html1')
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ from django.contrib.auth.models import User
 | 
				
			||||||
from django.test import TestCase, Client
 | 
					from django.test import TestCase, Client
 | 
				
			||||||
from djangorestframework import status
 | 
					from djangorestframework import status
 | 
				
			||||||
from djangorestframework.authentication import UserLoggedInAuthentication
 | 
					from djangorestframework.authentication import UserLoggedInAuthentication
 | 
				
			||||||
from djangorestframework.compat import RequestFactory, unittest
 | 
					from djangorestframework.compat import RequestFactory
 | 
				
			||||||
from djangorestframework.mixins import RequestMixin
 | 
					from djangorestframework.mixins import RequestMixin
 | 
				
			||||||
from djangorestframework.parsers import FormParser, MultiPartParser, \
 | 
					from djangorestframework.parsers import FormParser, MultiPartParser, \
 | 
				
			||||||
    PlainTextParser, JSONParser
 | 
					    PlainTextParser, JSONParser
 | 
				
			||||||
| 
						 | 
					@ -19,9 +19,9 @@ class MockView(View):
 | 
				
			||||||
    authentication = (UserLoggedInAuthentication,)
 | 
					    authentication = (UserLoggedInAuthentication,)
 | 
				
			||||||
    def post(self, request):
 | 
					    def post(self, request):
 | 
				
			||||||
        if request.POST.get('example') is not None:
 | 
					        if request.POST.get('example') is not None:
 | 
				
			||||||
            return Response(status.HTTP_200_OK)
 | 
					            return Response(status=status.HTTP_200_OK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Response(status.INTERNAL_SERVER_ERROR)
 | 
					        return Response(status=status.INTERNAL_SERVER_ERROR)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
urlpatterns = patterns('',
 | 
					urlpatterns = patterns('',
 | 
				
			||||||
    (r'^$', MockView.as_view()),
 | 
					    (r'^$', MockView.as_view()),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,19 +1,264 @@
 | 
				
			||||||
# Right now we expect this test to fail - I'm just going to leave it commented out.
 | 
					import json
 | 
				
			||||||
# Looking forward to actually being able to raise ExpectedFailure sometime!
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#from django.test import TestCase
 | 
					 | 
				
			||||||
#from djangorestframework.response import Response
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#class TestResponse(TestCase):
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#    # Interface tests
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#    # This is mainly to remind myself that the Response interface needs to change slightly
 | 
					 | 
				
			||||||
#    def test_response_interface(self):
 | 
					 | 
				
			||||||
#        """Ensure the Response interface is as expected."""
 | 
					 | 
				
			||||||
#        response = Response()
 | 
					 | 
				
			||||||
#        getattr(response, 'status')
 | 
					 | 
				
			||||||
#        getattr(response, 'content')
 | 
					 | 
				
			||||||
#        getattr(response, 'headers')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.conf.urls.defaults import patterns, url
 | 
				
			||||||
 | 
					from django.test import TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from djangorestframework.response import Response, ErrorResponse
 | 
				
			||||||
 | 
					from djangorestframework.mixins import ResponseMixin
 | 
				
			||||||
 | 
					from djangorestframework.views import View
 | 
				
			||||||
 | 
					from djangorestframework.compat import View as DjangoView
 | 
				
			||||||
 | 
					from djangorestframework.renderers import BaseRenderer, DEFAULT_RENDERERS
 | 
				
			||||||
 | 
					from djangorestframework.compat import RequestFactory
 | 
				
			||||||
 | 
					from djangorestframework import status
 | 
				
			||||||
 | 
					from djangorestframework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
 | 
				
			||||||
 | 
					    XMLRenderer, JSONPRenderer, DocumentingHTMLRenderer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestResponseDetermineRenderer(TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_response(self, url='', accept_list=[], renderers=[]):
 | 
				
			||||||
 | 
					        request = RequestFactory().get(url, HTTP_ACCEPT=','.join(accept_list))
 | 
				
			||||||
 | 
					        return Response(request=request, renderers=renderers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_renderer_mock(self, media_type):
 | 
				
			||||||
 | 
					        return type('RendererMock', (BaseRenderer,), {
 | 
				
			||||||
 | 
					            'media_type': media_type,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_determine_accept_list_accept_header(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Test that determine_accept_list takes the Accept header.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        accept_list = ['application/pickle', 'application/json']
 | 
				
			||||||
 | 
					        response = self.get_response(accept_list=accept_list)
 | 
				
			||||||
 | 
					        self.assertEqual(response._determine_accept_list(), accept_list)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def test_determine_accept_list_overriden_header(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Test Accept header overriding.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        accept_list = ['application/pickle', 'application/json']
 | 
				
			||||||
 | 
					        response = self.get_response(url='?_accept=application/x-www-form-urlencoded',
 | 
				
			||||||
 | 
					            accept_list=accept_list)
 | 
				
			||||||
 | 
					        self.assertEqual(response._determine_accept_list(), ['application/x-www-form-urlencoded'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_determine_renderer(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Test that right renderer is chosen, in the order of Accept list.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        accept_list = ['application/pickle', 'application/json']
 | 
				
			||||||
 | 
					        PRenderer = self.get_renderer_mock('application/pickle')
 | 
				
			||||||
 | 
					        JRenderer = self.get_renderer_mock('application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        renderers = (PRenderer, JRenderer)
 | 
				
			||||||
 | 
					        response = self.get_response(accept_list=accept_list, renderers=renderers)
 | 
				
			||||||
 | 
					        renderer, media_type = response._determine_renderer()
 | 
				
			||||||
 | 
					        self.assertEqual(media_type, 'application/pickle')
 | 
				
			||||||
 | 
					        self.assertTrue(isinstance(renderer, PRenderer))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        renderers = (JRenderer,)
 | 
				
			||||||
 | 
					        response = self.get_response(accept_list=accept_list, renderers=renderers)
 | 
				
			||||||
 | 
					        renderer, media_type = response._determine_renderer()
 | 
				
			||||||
 | 
					        self.assertEqual(media_type, 'application/json')
 | 
				
			||||||
 | 
					        self.assertTrue(isinstance(renderer, JRenderer))
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def test_determine_renderer_no_renderer(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Test determine renderer when no renderer can satisfy the Accept list.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        accept_list = ['application/json']
 | 
				
			||||||
 | 
					        PRenderer = self.get_renderer_mock('application/pickle')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        renderers = (PRenderer,)
 | 
				
			||||||
 | 
					        response = self.get_response(accept_list=accept_list, renderers=renderers)
 | 
				
			||||||
 | 
					        self.assertRaises(ErrorResponse, response._determine_renderer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestResponseRenderContent(TestCase):
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def get_response(self, url='', accept_list=[], content=None):
 | 
				
			||||||
 | 
					        request = RequestFactory().get(url, HTTP_ACCEPT=','.join(accept_list))
 | 
				
			||||||
 | 
					        return Response(request=request, content=content, renderers=DEFAULT_RENDERERS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_render(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Test rendering simple data to json.  
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        content = {'a': 1, 'b': [1, 2, 3]}
 | 
				
			||||||
 | 
					        content_type = 'application/json'
 | 
				
			||||||
 | 
					        response = self.get_response(accept_list=[content_type], content=content)
 | 
				
			||||||
 | 
					        response.render()
 | 
				
			||||||
 | 
					        self.assertEqual(json.loads(response.content), content)
 | 
				
			||||||
 | 
					        self.assertEqual(response['Content-Type'], content_type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DUMMYSTATUS = status.HTTP_200_OK
 | 
				
			||||||
 | 
					DUMMYCONTENT = 'dummycontent'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RENDERER_A_SERIALIZER = lambda x: 'Renderer A: %s' % x
 | 
				
			||||||
 | 
					RENDERER_B_SERIALIZER = lambda x: 'Renderer B: %s' % x
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RendererA(BaseRenderer):
 | 
				
			||||||
 | 
					    media_type = 'mock/renderera'
 | 
				
			||||||
 | 
					    format = "formata"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def render(self, obj=None, media_type=None):
 | 
				
			||||||
 | 
					        return RENDERER_A_SERIALIZER(obj)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RendererB(BaseRenderer):
 | 
				
			||||||
 | 
					    media_type = 'mock/rendererb'
 | 
				
			||||||
 | 
					    format = "formatb"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def render(self, obj=None, media_type=None):
 | 
				
			||||||
 | 
					        return RENDERER_B_SERIALIZER(obj)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MockView(ResponseMixin, DjangoView):
 | 
				
			||||||
 | 
					    renderers = (RendererA, RendererB)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, request, **kwargs):
 | 
				
			||||||
 | 
					        response = Response(DUMMYCONTENT, status=DUMMYSTATUS)
 | 
				
			||||||
 | 
					        return self.prepare_response(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HTMLView(View):
 | 
				
			||||||
 | 
					    renderers = (DocumentingHTMLRenderer, )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, request, **kwargs):
 | 
				
			||||||
 | 
					        return Response('text')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HTMLView1(View):
 | 
				
			||||||
 | 
					    renderers = (DocumentingHTMLRenderer, JSONRenderer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, request, **kwargs):
 | 
				
			||||||
 | 
					        return Response('text') 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					urlpatterns = patterns('',
 | 
				
			||||||
 | 
					    url(r'^.*\.(?P<format>.+)$', MockView.as_view(renderers=[RendererA, RendererB])),
 | 
				
			||||||
 | 
					    url(r'^$', MockView.as_view(renderers=[RendererA, RendererB])),
 | 
				
			||||||
 | 
					    url(r'^html$', HTMLView.as_view()),
 | 
				
			||||||
 | 
					    url(r'^html1$', HTMLView1.as_view()),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TODO: Clean tests bellow - remove duplicates with above, better unit testing, ...
 | 
				
			||||||
 | 
					class RendererIntegrationTests(TestCase):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    End-to-end testing of renderers using an ResponseMixin on a generic view.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    urls = 'djangorestframework.tests.response'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_default_renderer_serializes_content(self):
 | 
				
			||||||
 | 
					        """If the Accept header is not set the default renderer should serialize the response."""
 | 
				
			||||||
 | 
					        resp = self.client.get('/')
 | 
				
			||||||
 | 
					        self.assertEquals(resp['Content-Type'], RendererA.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
 | 
				
			||||||
 | 
					        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_head_method_serializes_no_content(self):
 | 
				
			||||||
 | 
					        """No response must be included in HEAD requests."""
 | 
				
			||||||
 | 
					        resp = self.client.head('/')
 | 
				
			||||||
 | 
					        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
				
			||||||
 | 
					        self.assertEquals(resp['Content-Type'], RendererA.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp.content, '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_default_renderer_serializes_content_on_accept_any(self):
 | 
				
			||||||
 | 
					        """If the Accept header is set to */* the default renderer should serialize the response."""
 | 
				
			||||||
 | 
					        resp = self.client.get('/', HTTP_ACCEPT='*/*')
 | 
				
			||||||
 | 
					        self.assertEquals(resp['Content-Type'], RendererA.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
 | 
				
			||||||
 | 
					        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_specified_renderer_serializes_content_default_case(self):
 | 
				
			||||||
 | 
					        """If the Accept header is set the specified renderer should serialize the response.
 | 
				
			||||||
 | 
					        (In this case we check that works for the default renderer)"""
 | 
				
			||||||
 | 
					        resp = self.client.get('/', HTTP_ACCEPT=RendererA.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp['Content-Type'], RendererA.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
 | 
				
			||||||
 | 
					        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_specified_renderer_serializes_content_non_default_case(self):
 | 
				
			||||||
 | 
					        """If the Accept header is set the specified renderer should serialize the response.
 | 
				
			||||||
 | 
					        (In this case we check that works for a non-default renderer)"""
 | 
				
			||||||
 | 
					        resp = self.client.get('/', HTTP_ACCEPT=RendererB.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
				
			||||||
 | 
					        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_specified_renderer_serializes_content_on_accept_query(self):
 | 
				
			||||||
 | 
					        """The '_accept' query string should behave in the same way as the Accept header."""
 | 
				
			||||||
 | 
					        resp = self.client.get('/?_accept=%s' % RendererB.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
				
			||||||
 | 
					        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# TODO: can't pass because view is a simple Django view and response is an ErrorResponse
 | 
				
			||||||
 | 
					#    def test_unsatisfiable_accept_header_on_request_returns_406_status(self):
 | 
				
			||||||
 | 
					#        """If the Accept header is unsatisfiable we should return a 406 Not Acceptable response."""
 | 
				
			||||||
 | 
					#        resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
 | 
				
			||||||
 | 
					#        self.assertEquals(resp.status_code, status.HTTP_406_NOT_ACCEPTABLE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_specified_renderer_serializes_content_on_format_query(self):
 | 
				
			||||||
 | 
					        """If a 'format' query is specified, the renderer with the matching
 | 
				
			||||||
 | 
					        format attribute should serialize the response."""
 | 
				
			||||||
 | 
					        resp = self.client.get('/?format=%s' % RendererB.format)
 | 
				
			||||||
 | 
					        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
				
			||||||
 | 
					        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_specified_renderer_serializes_content_on_format_kwargs(self):
 | 
				
			||||||
 | 
					        """If a 'format' keyword arg is specified, the renderer with the matching
 | 
				
			||||||
 | 
					        format attribute should serialize the response."""
 | 
				
			||||||
 | 
					        resp = self.client.get('/something.formatb')
 | 
				
			||||||
 | 
					        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
				
			||||||
 | 
					        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_specified_renderer_is_used_on_format_query_with_matching_accept(self):
 | 
				
			||||||
 | 
					        """If both a 'format' query and a matching Accept header specified,
 | 
				
			||||||
 | 
					        the renderer with the matching format attribute should serialize the response."""
 | 
				
			||||||
 | 
					        resp = self.client.get('/?format=%s' % RendererB.format,
 | 
				
			||||||
 | 
					                               HTTP_ACCEPT=RendererB.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
				
			||||||
 | 
					        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_conflicting_format_query_and_accept_ignores_accept(self):
 | 
				
			||||||
 | 
					        """If a 'format' query is specified that does not match the Accept
 | 
				
			||||||
 | 
					        header, we should only honor the 'format' query string."""
 | 
				
			||||||
 | 
					        resp = self.client.get('/?format=%s' % RendererB.format,
 | 
				
			||||||
 | 
					                               HTTP_ACCEPT='dummy')
 | 
				
			||||||
 | 
					        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
				
			||||||
 | 
					        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_bla(self):
 | 
				
			||||||
 | 
					        resp = self.client.get('/?format=formatb',
 | 
				
			||||||
 | 
					            HTTP_ACCEPT='text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')
 | 
				
			||||||
 | 
					        self.assertEquals(resp['Content-Type'], RendererB.media_type)
 | 
				
			||||||
 | 
					        self.assertEquals(resp.content, RENDERER_B_SERIALIZER(DUMMYCONTENT))
 | 
				
			||||||
 | 
					        self.assertEquals(resp.status_code, DUMMYSTATUS)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Issue122Tests(TestCase):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Tests that covers #122.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    urls = 'djangorestframework.tests.response'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_only_html_renderer(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Test if no infinite recursion occurs.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        resp = self.client.get('/html')
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def test_html_renderer_is_first(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Test if no infinite recursion occurs.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        resp = self.client.get('/html1')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ from django.test import TestCase
 | 
				
			||||||
from django.utils import simplejson as json
 | 
					from django.utils import simplejson as json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from djangorestframework.views import View
 | 
					from djangorestframework.views import View
 | 
				
			||||||
 | 
					from djangorestframework.response import Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MockView(View):
 | 
					class MockView(View):
 | 
				
			||||||
| 
						 | 
					@ -11,7 +12,7 @@ class MockView(View):
 | 
				
			||||||
    permissions = ()
 | 
					    permissions = ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, request):
 | 
					    def get(self, request):
 | 
				
			||||||
        return reverse('another')
 | 
					        return Response(reverse('another'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
urlpatterns = patterns('',
 | 
					urlpatterns = patterns('',
 | 
				
			||||||
    url(r'^$', MockView.as_view()),
 | 
					    url(r'^$', MockView.as_view()),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,13 +10,14 @@ from djangorestframework.compat import RequestFactory
 | 
				
			||||||
from djangorestframework.views import View
 | 
					from djangorestframework.views import View
 | 
				
			||||||
from djangorestframework.permissions import PerUserThrottling, PerViewThrottling, PerResourceThrottling
 | 
					from djangorestframework.permissions import PerUserThrottling, PerViewThrottling, PerResourceThrottling
 | 
				
			||||||
from djangorestframework.resources import FormResource
 | 
					from djangorestframework.resources import FormResource
 | 
				
			||||||
 | 
					from djangorestframework.response import Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MockView(View):
 | 
					class MockView(View):
 | 
				
			||||||
    permissions = ( PerUserThrottling, )
 | 
					    permissions = ( PerUserThrottling, )
 | 
				
			||||||
    throttle = '3/sec'
 | 
					    throttle = '3/sec'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get(self, request):
 | 
					    def get(self, request):
 | 
				
			||||||
        return 'foo'
 | 
					        return Response('foo')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MockView_PerViewThrottling(MockView):
 | 
					class MockView_PerViewThrottling(MockView):
 | 
				
			||||||
    permissions = ( PerViewThrottling, )
 | 
					    permissions = ( PerViewThrottling, )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,8 +81,8 @@ class TestNonFieldErrors(TestCase):
 | 
				
			||||||
        content = {'field1': 'example1', 'field2': 'example2'}
 | 
					        content = {'field1': 'example1', 'field2': 'example2'}
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            MockResource(view).validate_request(content, None)
 | 
					            MockResource(view).validate_request(content, None)
 | 
				
			||||||
        except ErrorResponse, exc:
 | 
					        except ErrorResponse, response:
 | 
				
			||||||
            self.assertEqual(exc.response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
 | 
					            self.assertEqual(response.raw_content, {'errors': [MockForm.ERROR_TEXT]})
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.fail('ErrorResponse was not raised')
 | 
					            self.fail('ErrorResponse was not raised')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -154,8 +154,8 @@ class TestFormValidation(TestCase):
 | 
				
			||||||
        content = {}
 | 
					        content = {}
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            validator.validate_request(content, None)
 | 
					            validator.validate_request(content, None)
 | 
				
			||||||
        except ErrorResponse, exc:
 | 
					        except ErrorResponse, response:
 | 
				
			||||||
            self.assertEqual(exc.response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
 | 
					            self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.fail('ResourceException was not raised')
 | 
					            self.fail('ResourceException was not raised')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -164,8 +164,8 @@ class TestFormValidation(TestCase):
 | 
				
			||||||
        content = {'qwerty': ''}
 | 
					        content = {'qwerty': ''}
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            validator.validate_request(content, None)
 | 
					            validator.validate_request(content, None)
 | 
				
			||||||
        except ErrorResponse, exc:
 | 
					        except ErrorResponse, response:
 | 
				
			||||||
            self.assertEqual(exc.response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
 | 
					            self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.']}})
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.fail('ResourceException was not raised')
 | 
					            self.fail('ResourceException was not raised')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -174,8 +174,8 @@ class TestFormValidation(TestCase):
 | 
				
			||||||
        content = {'qwerty': 'uiop', 'extra': 'extra'}
 | 
					        content = {'qwerty': 'uiop', 'extra': 'extra'}
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            validator.validate_request(content, None)
 | 
					            validator.validate_request(content, None)
 | 
				
			||||||
        except ErrorResponse, exc:
 | 
					        except ErrorResponse, response:
 | 
				
			||||||
            self.assertEqual(exc.response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}})
 | 
					            self.assertEqual(response.raw_content, {'field_errors': {'extra': ['This field does not exist.']}})
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.fail('ResourceException was not raised')
 | 
					            self.fail('ResourceException was not raised')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -184,8 +184,8 @@ class TestFormValidation(TestCase):
 | 
				
			||||||
        content = {'qwerty': '', 'extra': 'extra'}
 | 
					        content = {'qwerty': '', 'extra': 'extra'}
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            validator.validate_request(content, None)
 | 
					            validator.validate_request(content, None)
 | 
				
			||||||
        except ErrorResponse, exc:
 | 
					        except ErrorResponse, response:
 | 
				
			||||||
            self.assertEqual(exc.response.raw_content, {'field_errors': {'qwerty': ['This field is required.'],
 | 
					            self.assertEqual(response.raw_content, {'field_errors': {'qwerty': ['This field is required.'],
 | 
				
			||||||
                                                                         'extra': ['This field does not exist.']}})
 | 
					                                                                         'extra': ['This field does not exist.']}})
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            self.fail('ResourceException was not raised')
 | 
					            self.fail('ResourceException was not raised')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,6 +48,13 @@ def url_resolves(url):
 | 
				
			||||||
    return True
 | 
					    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def allowed_methods(view):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Return the list of uppercased allowed HTTP methods on `view`.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return [method.upper() for method in view.http_method_names if hasattr(view, method)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# From http://www.koders.com/python/fidB6E125C586A6F49EAC38992CF3AFDAAE35651975.aspx?s=mdef:xml
 | 
					# From http://www.koders.com/python/fidB6E125C586A6F49EAC38992CF3AFDAAE35651975.aspx?s=mdef:xml
 | 
				
			||||||
#class object_dict(dict):
 | 
					#class object_dict(dict):
 | 
				
			||||||
#    """object view of dict, you can
 | 
					#    """object view of dict, you can
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -118,7 +118,7 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Return the list of allowed HTTP methods, uppercased.
 | 
					        Return the list of allowed HTTP methods, uppercased.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return [method.upper() for method in self.http_method_names if hasattr(self, method)]
 | 
					        return allowed_methods(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_name(self):
 | 
					    def get_name(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					@ -172,12 +172,14 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Return an HTTP 405 error if an operation is called which does not have a handler method.
 | 
					        Return an HTTP 405 error if an operation is called which does not have a handler method.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        raise ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED,
 | 
					        raise ErrorResponse(content=
 | 
				
			||||||
                            {'detail': 'Method \'%s\' not allowed on this resource.' % request.method})
 | 
					                {'detail': 'Method \'%s\' not allowed on this resource.' % request.method},
 | 
				
			||||||
 | 
					            status=status.HTTP_405_METHOD_NOT_ALLOWED)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def initial(self, request, *args, **kargs):
 | 
					    def initial(self, request, *args, **kargs):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Hook for any code that needs to run prior to anything else.
 | 
					        Returns an `HttpRequest`. This method is a hook for any code that needs to run
 | 
				
			||||||
 | 
					        prior to anything else.
 | 
				
			||||||
        Required if you want to do things like set `request.upload_handlers` before
 | 
					        Required if you want to do things like set `request.upload_handlers` before
 | 
				
			||||||
        the authentication and dispatch handling is run.
 | 
					        the authentication and dispatch handling is run.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					@ -187,28 +189,16 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
 | 
				
			||||||
        if not (self.orig_prefix.startswith('http:') or self.orig_prefix.startswith('https:')):
 | 
					        if not (self.orig_prefix.startswith('http:') or self.orig_prefix.startswith('https:')):
 | 
				
			||||||
            prefix = '%s://%s' % (request.is_secure() and 'https' or 'http', request.get_host())
 | 
					            prefix = '%s://%s' % (request.is_secure() and 'https' or 'http', request.get_host())
 | 
				
			||||||
            set_script_prefix(prefix + self.orig_prefix)
 | 
					            set_script_prefix(prefix + self.orig_prefix)
 | 
				
			||||||
 | 
					        return request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def final(self, request, response, *args, **kargs):
 | 
					    def final(self, request, response, *args, **kargs):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Hook for any code that needs to run after everything else in the view.
 | 
					        Returns an `HttpResponse`. This method is a hook for any code that needs to run
 | 
				
			||||||
 | 
					        after everything else in the view.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        # Restore script_prefix.
 | 
					        # Restore script_prefix.
 | 
				
			||||||
        set_script_prefix(self.orig_prefix)
 | 
					        set_script_prefix(self.orig_prefix)
 | 
				
			||||||
 | 
					        return response
 | 
				
			||||||
        # Always add these headers.
 | 
					 | 
				
			||||||
        response.headers['Allow'] = ', '.join(self.allowed_methods)
 | 
					 | 
				
			||||||
        # sample to allow caching using Vary http header
 | 
					 | 
				
			||||||
        response.headers['Vary'] = 'Authenticate, Accept'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # merge with headers possibly set at some point in the view
 | 
					 | 
				
			||||||
        response.headers.update(self.headers)
 | 
					 | 
				
			||||||
        return self.render(response)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def add_header(self, field, value):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Add *field* and *value* to the :attr:`headers` attribute of the :class:`View` class.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.headers[field] = value
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Note: session based authentication is explicitly CSRF validated,
 | 
					    # Note: session based authentication is explicitly CSRF validated,
 | 
				
			||||||
    # all other authentication is CSRF exempt.
 | 
					    # all other authentication is CSRF exempt.
 | 
				
			||||||
| 
						 | 
					@ -217,13 +207,14 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
 | 
				
			||||||
        self.request = request
 | 
					        self.request = request
 | 
				
			||||||
        self.args = args
 | 
					        self.args = args
 | 
				
			||||||
        self.kwargs = kwargs
 | 
					        self.kwargs = kwargs
 | 
				
			||||||
        self.headers = {}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            # Get a custom request, built form the original request instance
 | 
					            # Get a custom request, built form the original request instance
 | 
				
			||||||
            self.request = request = self.get_request()
 | 
					            self.request = request = self.get_request()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.initial(request, *args, **kwargs)
 | 
					            # `initial` is the opportunity to temper with the request, 
 | 
				
			||||||
 | 
					            # even completely replace it.
 | 
				
			||||||
 | 
					            self.request = request = self.initial(request, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Authenticate and check request has the relevant permissions
 | 
					            # Authenticate and check request has the relevant permissions
 | 
				
			||||||
            self._check_permissions()
 | 
					            self._check_permissions()
 | 
				
			||||||
| 
						 | 
					@ -234,28 +225,29 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                handler = self.http_method_not_allowed
 | 
					                handler = self.http_method_not_allowed
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
            response_obj = handler(request, *args, **kwargs)
 | 
					            # TODO: should we enforce HttpResponse, like Django does ?
 | 
				
			||||||
 | 
					            response = handler(request, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Allow return value to be either HttpResponse, Response, or an object, or None
 | 
					            # Prepare response for the response cycle.
 | 
				
			||||||
            if isinstance(response_obj, HttpResponse):
 | 
					            self.prepare_response(response)
 | 
				
			||||||
                return response_obj
 | 
					 | 
				
			||||||
            elif 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)
 | 
					            # Pre-serialize filtering (eg filter complex objects into natively serializable types)
 | 
				
			||||||
            response.cleaned_content = self.filter_response(response.raw_content)
 | 
					            # TODO: ugly
 | 
				
			||||||
 | 
					            if hasattr(response, 'raw_content'):
 | 
				
			||||||
 | 
					                response.raw_content = self.filter_response(response.raw_content)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                response.content = self.filter_response(response.content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        except ErrorResponse, exc:
 | 
					        except ErrorResponse, response:
 | 
				
			||||||
            response = exc.response
 | 
					            # Prepare response for the response cycle.
 | 
				
			||||||
 | 
					            self.prepare_response(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # `final` is the last opportunity to temper with the response, or even
 | 
				
			||||||
 | 
					        # completely replace it.
 | 
				
			||||||
        return self.final(request, response, *args, **kwargs)
 | 
					        return self.final(request, response, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def options(self, request, *args, **kwargs):
 | 
					    def options(self, request, *args, **kwargs):
 | 
				
			||||||
        response_obj = {
 | 
					        content = {
 | 
				
			||||||
            'name': self.get_name(),
 | 
					            'name': self.get_name(),
 | 
				
			||||||
            'description': self.get_description(),
 | 
					            'description': self.get_description(),
 | 
				
			||||||
            'renders': self._rendered_media_types,
 | 
					            'renders': self._rendered_media_types,
 | 
				
			||||||
| 
						 | 
					@ -266,11 +258,11 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
 | 
				
			||||||
            field_name_types = {}
 | 
					            field_name_types = {}
 | 
				
			||||||
            for name, field in form.fields.iteritems():
 | 
					            for name, field in form.fields.iteritems():
 | 
				
			||||||
                field_name_types[name] = field.__class__.__name__
 | 
					                field_name_types[name] = field.__class__.__name__
 | 
				
			||||||
            response_obj['fields'] = field_name_types
 | 
					            content['fields'] = field_name_types
 | 
				
			||||||
        # Note 'ErrorResponse' is misleading, it's just any response
 | 
					        # Note 'ErrorResponse' is misleading, it's just any response
 | 
				
			||||||
        # that should be rendered and returned immediately, without any
 | 
					        # that should be rendered and returned immediately, without any
 | 
				
			||||||
        # response filtering.
 | 
					        # response filtering.
 | 
				
			||||||
        raise ErrorResponse(status.HTTP_200_OK, response_obj)
 | 
					        raise ErrorResponse(content=content, status=status.HTTP_200_OK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ModelView(View):
 | 
					class ModelView(View):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user