mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 09:57:55 +03:00 
			
		
		
		
	More tests, getting new serialization into resource
This commit is contained in:
		
							parent
							
								
									a2575c1191
								
							
						
					
					
						commit
						4d12679675
					
				| 
						 | 
					@ -3,7 +3,7 @@ The ``authentication`` module provides a set of pluggable authentication classes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Authentication behavior is provided by adding the ``AuthMixin`` class to a ``View`` .
 | 
					Authentication behavior is provided by adding the ``AuthMixin`` class to a ``View`` .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The set of authentication methods which are used is then specified by setting
 | 
					The set of authentication methods which are used is then specified by setting the
 | 
				
			||||||
``authentication`` attribute on the ``View`` class, and listing a set of authentication classes.
 | 
					``authentication`` attribute on the ``View`` class, and listing a set of authentication classes.
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,7 +81,7 @@ class UserLoggedInAuthenticaton(BaseAuthenticaton):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def authenticate(self, request):
 | 
					    def authenticate(self, request):
 | 
				
			||||||
        # TODO: Switch this back to request.POST, and let MultiPartParser deal with the consequences.
 | 
					        # TODO: Switch this back to request.POST, and let FormParser/MultiPartParser deal with the consequences.
 | 
				
			||||||
        if getattr(request, 'user', None) and request.user.is_active:
 | 
					        if getattr(request, 'user', None) and request.user.is_active:
 | 
				
			||||||
            # If this is a POST request we enforce CSRF validation.
 | 
					            # If this is a POST request we enforce CSRF validation.
 | 
				
			||||||
            if request.method.upper() == 'POST':
 | 
					            if request.method.upper() == 'POST':
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,7 @@ __all__ = (
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RequestMixin(object):
 | 
					class RequestMixin(object):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Mixin class to provide request parsing behaviour.
 | 
					    Mixin class to provide request parsing behavior.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    USE_FORM_OVERLOADING = True
 | 
					    USE_FORM_OVERLOADING = True
 | 
				
			||||||
| 
						 | 
					@ -93,6 +93,13 @@ class RequestMixin(object):
 | 
				
			||||||
            if content_length == 0:
 | 
					            if content_length == 0:
 | 
				
			||||||
                return None
 | 
					                return None
 | 
				
			||||||
            elif hasattr(request, 'read'):
 | 
					            elif hasattr(request, 'read'):
 | 
				
			||||||
 | 
					                # UPDATE BASED ON COMMENT BELOW:
 | 
				
			||||||
 | 
					                #
 | 
				
			||||||
 | 
					                # Yup, this was a bug in Django - fixed and waiting check in - see ticket 15785.
 | 
				
			||||||
 | 
					                # http://code.djangoproject.com/ticket/15785
 | 
				
			||||||
 | 
					                #
 | 
				
			||||||
 | 
					                # COMMENT:
 | 
				
			||||||
 | 
					                #
 | 
				
			||||||
                # It's not at all clear if this needs to be byte limited or not.
 | 
					                # It's not at all clear if this needs to be byte limited or not.
 | 
				
			||||||
                # Maybe I'm just being dumb but it looks to me like there's some issues
 | 
					                # Maybe I'm just being dumb but it looks to me like there's some issues
 | 
				
			||||||
                # with that in Django.
 | 
					                # with that in Django.
 | 
				
			||||||
| 
						 | 
					@ -117,8 +124,6 @@ class RequestMixin(object):
 | 
				
			||||||
                #except (ValueError, TypeError):
 | 
					                #except (ValueError, TypeError):
 | 
				
			||||||
                #    content_length = 0
 | 
					                #    content_length = 0
 | 
				
			||||||
                # self._stream = LimitedStream(request, content_length)
 | 
					                # self._stream = LimitedStream(request, content_length)
 | 
				
			||||||
                #
 | 
					 | 
				
			||||||
                # UPDATE: http://code.djangoproject.com/ticket/15785
 | 
					 | 
				
			||||||
                self._stream = request
 | 
					                self._stream = request
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self._stream = StringIO(request.raw_post_data)
 | 
					                self._stream = StringIO(request.raw_post_data)
 | 
				
			||||||
| 
						 | 
					@ -290,6 +295,10 @@ class ResponseMixin(object):
 | 
				
			||||||
        return resp
 | 
					        return resp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # TODO: This should be simpler now.
 | 
				
			||||||
 | 
					    #       Add a handles_response() to the renderer, then iterate through the
 | 
				
			||||||
 | 
					    #       acceptable media types, ordered by how specific they are,
 | 
				
			||||||
 | 
					    #       calling handles_response on each renderer.
 | 
				
			||||||
    def _determine_renderer(self, request):
 | 
					    def _determine_renderer(self, request):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Return the appropriate renderer for the output, given the client's 'Accept' header,
 | 
					        Return the appropriate renderer for the output, given the client's 'Accept' header,
 | 
				
			||||||
| 
						 | 
					@ -321,7 +330,7 @@ class ResponseMixin(object):
 | 
				
			||||||
            qvalue = Decimal('1.0')
 | 
					            qvalue = Decimal('1.0')
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            if len(components) > 1:
 | 
					            if len(components) > 1:
 | 
				
			||||||
                # Parse items that have a qvalue eg text/html;q=0.9
 | 
					                # Parse items that have a qvalue eg 'text/html; q=0.9'
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    (q, num) = components[-1].split('=')
 | 
					                    (q, num) = components[-1].split('=')
 | 
				
			||||||
                    if q == 'q':
 | 
					                    if q == 'q':
 | 
				
			||||||
| 
						 | 
					@ -356,10 +365,10 @@ class ResponseMixin(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        raise ErrorResponse(status.HTTP_406_NOT_ACCEPTABLE,
 | 
					        raise ErrorResponse(status.HTTP_406_NOT_ACCEPTABLE,
 | 
				
			||||||
                                {'detail': 'Could not satisfy the client\'s Accept header',
 | 
					                                {'detail': 'Could not satisfy the client\'s Accept header',
 | 
				
			||||||
                                 'available_types': self.renderted_media_types})
 | 
					                                 'available_types': self.rendered_media_types})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def renderted_media_types(self):
 | 
					    def rendered_media_types(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Return an list of all the media types that this resource can render.
 | 
					        Return an list of all the media types that this resource can render.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,7 @@ from urllib import quote_plus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = (
 | 
					__all__ = (
 | 
				
			||||||
    'BaseRenderer',
 | 
					    'BaseRenderer',
 | 
				
			||||||
 | 
					    'TemplateRenderer',
 | 
				
			||||||
    'JSONRenderer',
 | 
					    'JSONRenderer',
 | 
				
			||||||
    'DocumentingHTMLRenderer',
 | 
					    'DocumentingHTMLRenderer',
 | 
				
			||||||
    'DocumentingXHTMLRenderer',
 | 
					    'DocumentingXHTMLRenderer',
 | 
				
			||||||
| 
						 | 
					@ -87,10 +88,12 @@ class DocumentingTemplateRenderer(BaseRenderer):
 | 
				
			||||||
    template = None
 | 
					    template = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_content(self, view, request, obj, media_type):
 | 
					    def _get_content(self, view, request, obj, media_type):
 | 
				
			||||||
        """Get the content as if it had been rendered by a non-documenting renderer.
 | 
					        """
 | 
				
			||||||
 | 
					        Get the content as if it had been rendered by a non-documenting renderer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (Typically this will be the content as it would have been if the Resource had been
 | 
					        (Typically this will be the content as it would have been if the Resource had been
 | 
				
			||||||
        requested with an 'Accept: */*' header, although with verbose style formatting if appropriate.)"""
 | 
					        requested with an 'Accept: */*' header, although with verbose style formatting if appropriate.)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Find the first valid renderer and render the content. (Don't use another documenting renderer.)
 | 
					        # Find the first valid renderer and render the content. (Don't use another documenting renderer.)
 | 
				
			||||||
        renderers = [renderer for renderer in view.renderers if not isinstance(renderer, DocumentingTemplateRenderer)]
 | 
					        renderers = [renderer for renderer in view.renderers if not isinstance(renderer, DocumentingTemplateRenderer)]
 | 
				
			||||||
| 
						 | 
					@ -106,9 +109,11 @@ class DocumentingTemplateRenderer(BaseRenderer):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_form_instance(self, view):
 | 
					    def _get_form_instance(self, view):
 | 
				
			||||||
        """Get a form, possibly bound to either the input or output data.
 | 
					        """
 | 
				
			||||||
 | 
					        Get a form, possibly bound to either the input or output data.
 | 
				
			||||||
        In the absence on of the Resource having an associated form then
 | 
					        In the absence on of the Resource having an associated form then
 | 
				
			||||||
        provide a form that can be used to submit arbitrary content."""
 | 
					        provide a form that can be used to submit arbitrary content.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Get the form instance if we have one bound to the input
 | 
					        # Get the form instance if we have one bound to the input
 | 
				
			||||||
        form_instance = getattr(view, 'bound_form_instance', None)
 | 
					        form_instance = getattr(view, 'bound_form_instance', None)
 | 
				
			||||||
| 
						 | 
					@ -138,8 +143,10 @@ class DocumentingTemplateRenderer(BaseRenderer):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_generic_content_form(self, view):
 | 
					    def _get_generic_content_form(self, view):
 | 
				
			||||||
        """Returns a form that allows for arbitrary content types to be tunneled via standard HTML forms
 | 
					        """
 | 
				
			||||||
        (Which are typically application/x-www-form-urlencoded)"""
 | 
					        Returns a form that allows for arbitrary content types to be tunneled via standard HTML forms
 | 
				
			||||||
 | 
					        (Which are typically application/x-www-form-urlencoded)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # If we're not using content overloading there's no point in supplying a generic form,
 | 
					        # If we're not using content overloading there's no point in supplying a generic form,
 | 
				
			||||||
        # as the view won't treat the form's value as the content of the request.
 | 
					        # as the view won't treat the form's value as the content of the request.
 | 
				
			||||||
| 
						 | 
					@ -197,8 +204,8 @@ class DocumentingTemplateRenderer(BaseRenderer):
 | 
				
			||||||
        template = loader.get_template(self.template)
 | 
					        template = loader.get_template(self.template)
 | 
				
			||||||
        context = RequestContext(self.view.request, {
 | 
					        context = RequestContext(self.view.request, {
 | 
				
			||||||
            'content': content,
 | 
					            'content': content,
 | 
				
			||||||
            'resource': self.view,
 | 
					            'resource': self.view,        # TODO: rename to view
 | 
				
			||||||
            'request': self.view.request,
 | 
					            'request': self.view.request, # TODO: remove
 | 
				
			||||||
            'response': self.view.response,
 | 
					            'response': self.view.response,
 | 
				
			||||||
            'description': description,
 | 
					            'description': description,
 | 
				
			||||||
            'name': name,
 | 
					            'name': name,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,89 @@
 | 
				
			||||||
from django.db.models import Model
 | 
					from django.db import models
 | 
				
			||||||
from django.db.models.query import QuerySet
 | 
					from django.db.models.query import QuerySet
 | 
				
			||||||
from django.db.models.fields.related import RelatedField
 | 
					from django.db.models.fields.related import RelatedField
 | 
				
			||||||
 | 
					from django.utils.encoding import smart_unicode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import decimal
 | 
					import decimal
 | 
				
			||||||
import inspect
 | 
					import inspect
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _model_to_dict(instance, fields=None, exclude=None):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This is a clone of Django's ``django.forms.model_to_dict`` except that it
 | 
				
			||||||
 | 
					    doesn't coerce related objects into primary keys.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    opts = instance._meta
 | 
				
			||||||
 | 
					    data = {}
 | 
				
			||||||
 | 
					    for f in opts.fields + opts.many_to_many:
 | 
				
			||||||
 | 
					        if not f.editable:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        if fields and not f.name in fields:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        if exclude and f.name in exclude:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        if isinstance(f, models.ForeignKey):
 | 
				
			||||||
 | 
					            data[f.name] = getattr(instance, f.name)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            data[f.name] = f.value_from_object(instance)
 | 
				
			||||||
 | 
					    return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _object_to_data(obj):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Convert an object into a serializable representation.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if isinstance(obj, dict):
 | 
				
			||||||
 | 
					        # dictionaries
 | 
				
			||||||
 | 
					        return dict([ (key, _object_to_data(val)) for key, val in obj.iteritems() ])    
 | 
				
			||||||
 | 
					    if isinstance(obj, (tuple, list, set, QuerySet)):
 | 
				
			||||||
 | 
					        # basic iterables
 | 
				
			||||||
 | 
					        return [_object_to_data(item) for item in obj]
 | 
				
			||||||
 | 
					    if isinstance(obj, models.Manager):
 | 
				
			||||||
 | 
					        # Manager objects
 | 
				
			||||||
 | 
					        ret = [_object_to_data(item) for item in obj.all()]
 | 
				
			||||||
 | 
					    if isinstance(obj, models.Model):
 | 
				
			||||||
 | 
					        # Model instances
 | 
				
			||||||
 | 
					        return _object_to_data(_model_to_dict(obj))
 | 
				
			||||||
 | 
					    if isinstance(obj, decimal.Decimal):
 | 
				
			||||||
 | 
					        # Decimals (force to string representation)
 | 
				
			||||||
 | 
						    return str(obj)
 | 
				
			||||||
 | 
					    if inspect.isfunction(obj) and not inspect.getargspec(obj)[0]:
 | 
				
			||||||
 | 
					        # function with no args
 | 
				
			||||||
 | 
					        return _object_to_data(obj())
 | 
				
			||||||
 | 
					    if inspect.ismethod(obj) and len(inspect.getargspec(obj)[0]) == 1:
 | 
				
			||||||
 | 
					        # method with only a 'self' args
 | 
				
			||||||
 | 
					        return _object_to_data(obj())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # fallback
 | 
				
			||||||
 | 
					    return smart_unicode(obj, strings_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO: Replace this with new Serializer code based on Forms API.
 | 
					# TODO: Replace this with new Serializer code based on Forms API.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#class Resource(object):
 | 
				
			||||||
 | 
					#    def __init__(self, view):
 | 
				
			||||||
 | 
					#        self.view = view
 | 
				
			||||||
 | 
					#    
 | 
				
			||||||
 | 
					#    def object_to_data(self, obj):
 | 
				
			||||||
 | 
					#        pass
 | 
				
			||||||
 | 
					#    
 | 
				
			||||||
 | 
					#    def data_to_object(self, data, files):
 | 
				
			||||||
 | 
					#        pass
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#class FormResource(object):
 | 
				
			||||||
 | 
					#    pass
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#class ModelResource(object):
 | 
				
			||||||
 | 
					#    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Resource(object):
 | 
					class Resource(object):
 | 
				
			||||||
    """A Resource determines how an object maps to a serializable entity.
 | 
					    """
 | 
				
			||||||
    Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets."""
 | 
					    A Resource determines how a python object maps to some serializable data.
 | 
				
			||||||
 | 
					    Objects that a resource can act on include plain Python object instances, Django Models, and Django QuerySets.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    # The model attribute refers to the Django Model which this Resource maps to.
 | 
					    # The model attribute refers to the Django Model which this Resource maps to.
 | 
				
			||||||
    # (The Model's class, rather than an instance of the Model)
 | 
					    # (The Model's class, rather than an instance of the Model)
 | 
				
			||||||
| 
						 | 
					@ -50,7 +123,7 @@ class Resource(object):
 | 
				
			||||||
                ret = thing
 | 
					                ret = thing
 | 
				
			||||||
            elif isinstance(thing, decimal.Decimal):
 | 
					            elif isinstance(thing, decimal.Decimal):
 | 
				
			||||||
                ret = str(thing)
 | 
					                ret = str(thing)
 | 
				
			||||||
            elif isinstance(thing, Model):
 | 
					            elif isinstance(thing, models.Model):
 | 
				
			||||||
                ret = _model(thing, fields=fields)
 | 
					                ret = _model(thing, fields=fields)
 | 
				
			||||||
            #elif isinstance(thing, HttpResponse):    TRC
 | 
					            #elif isinstance(thing, HttpResponse):    TRC
 | 
				
			||||||
            #    raise HttpStatusCode(thing)
 | 
					            #    raise HttpStatusCode(thing)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,14 @@
 | 
				
			||||||
from django.core.handlers.wsgi import STATUS_CODE_TEXT
 | 
					from django.core.handlers.wsgi import STATUS_CODE_TEXT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ =['Response', 'ErrorResponse']
 | 
					__all__ = ('Response', 'ErrorResponse')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO: remove raw_content/cleaned_content and just use content?
 | 
					# TODO: remove raw_content/cleaned_content and just use content?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Response(object):
 | 
					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={}):
 | 
					    def __init__(self, status=200, content=None, headers={}):
 | 
				
			||||||
        self.status = status
 | 
					        self.status = status
 | 
				
			||||||
        self.has_content_body = content is not None
 | 
					        self.has_content_body = content is not None
 | 
				
			||||||
| 
						 | 
					@ -15,12 +18,18 @@ class Response(object):
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def status_text(self):
 | 
					    def status_text(self):
 | 
				
			||||||
        """Return reason text corresponding to our HTTP response status code.
 | 
					        """
 | 
				
			||||||
        Provided for convenience."""
 | 
					        Return reason text corresponding to our HTTP response status code.
 | 
				
			||||||
 | 
					        Provided for convenience.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return STATUS_CODE_TEXT.get(self.status, '')
 | 
					        return STATUS_CODE_TEXT.get(self.status, '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ErrorResponse(BaseException):
 | 
					class ErrorResponse(BaseException):
 | 
				
			||||||
    """An exception representing an HttpResponse 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.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, status, content=None, headers={}):
 | 
					    def __init__(self, status, content=None, headers={}):
 | 
				
			||||||
        self.response = Response(status, content=content, headers=headers)
 | 
					        self.response = Response(status, content=content, headers=headers)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,9 @@
 | 
				
			||||||
"""Descriptive HTTP status codes, for code readability.
 | 
					"""
 | 
				
			||||||
 | 
					Descriptive HTTP status codes, for code readability.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See RFC 2616 - Sec 10: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
 | 
					See RFC 2616 - Sec 10: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
 | 
				
			||||||
Also, django.core.handlers.wsgi.STATUS_CODE_TEXT"""
 | 
					Also see django.core.handlers.wsgi.STATUS_CODE_TEXT
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Verbose format
 | 
					# Verbose format
 | 
				
			||||||
HTTP_100_CONTINUE = 100
 | 
					HTTP_100_CONTINUE = 100
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,7 +48,7 @@
 | 
				
			||||||
				<h2>GET {{ name }}</h2>
 | 
									<h2>GET {{ name }}</h2>
 | 
				
			||||||
				<div class='submit-row' style='margin: 0; border: 0'>
 | 
									<div class='submit-row' style='margin: 0; border: 0'>
 | 
				
			||||||
				<a href='{{ request.path }}' rel="nofollow" style='float: left'>GET</a>
 | 
									<a href='{{ request.path }}' rel="nofollow" style='float: left'>GET</a>
 | 
				
			||||||
				{% for media_type in resource.renderted_media_types %}
 | 
									{% for media_type in resource.rendered_media_types %}
 | 
				
			||||||
				  {% with resource.ACCEPT_QUERY_PARAM|add:"="|add:media_type as param %}
 | 
									  {% with resource.ACCEPT_QUERY_PARAM|add:"="|add:media_type as param %}
 | 
				
			||||||
				    [<a href='{{ request.path|add_query_param:param }}' rel="nofollow">{{ media_type }}</a>]
 | 
									    [<a href='{{ request.path|add_query_param:param }}' rel="nofollow">{{ media_type }}</a>]
 | 
				
			||||||
				  {% endwith %}
 | 
									  {% endwith %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										31
									
								
								djangorestframework/tests/resource.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								djangorestframework/tests/resource.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					"""Tests for the resource module"""
 | 
				
			||||||
 | 
					from django.test import TestCase
 | 
				
			||||||
 | 
					from djangorestframework.resource import _object_to_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import decimal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestObjectToData(TestCase): 
 | 
				
			||||||
 | 
					    """Tests for the _object_to_data function"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_decimal(self):
 | 
				
			||||||
 | 
					        """Decimals need to be converted to a string representation."""
 | 
				
			||||||
 | 
					        self.assertEquals(_object_to_data(decimal.Decimal('1.5')), '1.5')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def test_function(self):
 | 
				
			||||||
 | 
					        """Functions with no arguments should be called."""
 | 
				
			||||||
 | 
					        def foo():
 | 
				
			||||||
 | 
					            return 1
 | 
				
			||||||
 | 
					        self.assertEquals(_object_to_data(foo), 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_method(self):
 | 
				
			||||||
 | 
					        """Methods with only a ``self`` argument should be called."""
 | 
				
			||||||
 | 
					        class Foo(object):
 | 
				
			||||||
 | 
					            def foo(self):
 | 
				
			||||||
 | 
					                return 1
 | 
				
			||||||
 | 
					        self.assertEquals(_object_to_data(Foo().foo), 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_datetime(self):
 | 
				
			||||||
 | 
					        """datetime objects are left as-is."""
 | 
				
			||||||
 | 
					        now = datetime.datetime.now()
 | 
				
			||||||
 | 
					        self.assertEquals(_object_to_data(now), now)
 | 
				
			||||||
| 
						 | 
					@ -31,20 +31,24 @@ class FormValidator(BaseValidator):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def validate(self, content):
 | 
					    def validate(self, content):
 | 
				
			||||||
        """Given some content as input return some cleaned, validated content.
 | 
					        """
 | 
				
			||||||
 | 
					        Given some content as input return some cleaned, validated content.
 | 
				
			||||||
        Raises a ErrorResponse with status code 400 (Bad Request) on failure.
 | 
					        Raises a ErrorResponse with status code 400 (Bad Request) on failure.
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        Validation is standard form validation, with an additional constraint that no extra unknown fields may be supplied.
 | 
					        Validation is standard form validation, with an additional constraint that no extra unknown fields may be supplied.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        On failure the ErrorResponse content is a dict which may contain 'errors' and 'field-errors' keys.
 | 
					        On failure the ErrorResponse content is a dict which may contain 'errors' and 'field-errors' keys.
 | 
				
			||||||
        If the 'errors' key exists it is a list of strings of non-field errors.
 | 
					        If the 'errors' key exists it is a list of strings of non-field errors.
 | 
				
			||||||
        If the 'field-errors' key exists it is a dict of {field name as string: list of errors as strings}."""
 | 
					        If the 'field-errors' key exists it is a dict of {field name as string: list of errors as strings}.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return self._validate(content)
 | 
					        return self._validate(content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _validate(self, content, allowed_extra_fields=()):
 | 
					    def _validate(self, content, allowed_extra_fields=()):
 | 
				
			||||||
        """Wrapped by validate to hide the extra_fields option that the ModelValidatorMixin uses.
 | 
					        """
 | 
				
			||||||
 | 
					        Wrapped by validate to hide the extra_fields option that the ModelValidatorMixin uses.
 | 
				
			||||||
        extra_fields is a list of fields which are not defined by the form, but which we still
 | 
					        extra_fields is a list of fields which are not defined by the form, but which we still
 | 
				
			||||||
        expect to see on the input."""
 | 
					        expect to see on the input.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        bound_form = self.get_bound_form(content)
 | 
					        bound_form = self.get_bound_form(content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if bound_form is None:
 | 
					        if bound_form is None:
 | 
				
			||||||
| 
						 | 
					@ -138,7 +142,8 @@ class ModelFormValidator(FormValidator):
 | 
				
			||||||
    # TODO: test the different validation here to allow for get get_absolute_url to be supplied on input and not bork out
 | 
					    # TODO: test the different validation here to allow for get get_absolute_url to be supplied on input and not bork out
 | 
				
			||||||
    # TODO: be really strict on fields - check they match in the handler methods. (this isn't a validator thing tho.)
 | 
					    # TODO: be really strict on fields - check they match in the handler methods. (this isn't a validator thing tho.)
 | 
				
			||||||
    def validate(self, content):
 | 
					    def validate(self, content):
 | 
				
			||||||
        """Given some content as input return some cleaned, validated content.
 | 
					        """
 | 
				
			||||||
 | 
					        Given some content as input return some cleaned, validated content.
 | 
				
			||||||
        Raises a ErrorResponse with status code 400 (Bad Request) on failure.
 | 
					        Raises a ErrorResponse with status code 400 (Bad Request) on failure.
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        Validation is standard form or model form validation,
 | 
					        Validation is standard form or model form validation,
 | 
				
			||||||
| 
						 | 
					@ -148,7 +153,8 @@ class ModelFormValidator(FormValidator):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        On failure the ErrorResponse content is a dict which may contain 'errors' and 'field-errors' keys.
 | 
					        On failure the ErrorResponse content is a dict which may contain 'errors' and 'field-errors' keys.
 | 
				
			||||||
        If the 'errors' key exists it is a list of strings of non-field errors.
 | 
					        If the 'errors' key exists it is a list of strings of non-field errors.
 | 
				
			||||||
        If the 'field-errors' key exists it is a dict of {field name as string: list of errors as strings}."""
 | 
					        If the 'field-errors' key exists it is a dict of {field name as string: list of errors as strings}.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        return self._validate(content, allowed_extra_fields=self._property_fields_set)
 | 
					        return self._validate(content, allowed_extra_fields=self._property_fields_set)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user