diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/authentication.py | 14 | ||||
| -rw-r--r-- | rest_framework/compat.py | 15 | ||||
| -rw-r--r-- | rest_framework/filters.py | 4 | ||||
| -rw-r--r-- | rest_framework/generics.py | 17 | ||||
| -rw-r--r-- | rest_framework/renderers.py | 37 | ||||
| -rw-r--r-- | rest_framework/request.py | 11 | ||||
| -rw-r--r-- | rest_framework/response.py | 6 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 2 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/base.html | 6 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/login_base.html | 15 | ||||
| -rw-r--r-- | rest_framework/test.py | 4 | ||||
| -rw-r--r-- | rest_framework/urls.py | 8 | 
12 files changed, 87 insertions, 52 deletions
| diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index cbc83574..82cea70f 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -310,6 +310,13 @@ class OAuth2Authentication(BaseAuthentication):          auth = get_authorization_header(request).split() +        if len(auth) == 1: +            msg = 'Invalid bearer header. No credentials provided.' +            raise exceptions.AuthenticationFailed(msg) +        elif len(auth) > 2: +            msg = 'Invalid bearer header. Token string should not contain spaces.' +            raise exceptions.AuthenticationFailed(msg) +          if auth and auth[0].lower() == b'bearer':              access_token = auth[1]          elif 'access_token' in request.POST: @@ -319,13 +326,6 @@ class OAuth2Authentication(BaseAuthentication):          else:              return None -        if len(auth) == 1: -            msg = 'Invalid bearer header. No credentials provided.' -            raise exceptions.AuthenticationFailed(msg) -        elif len(auth) > 2: -            msg = 'Invalid bearer header. Token string should not contain spaces.' -            raise exceptions.AuthenticationFailed(msg) -          return self.authenticate_credentials(request, access_token)      def authenticate_credentials(self, request, access_token): diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 6a5cbbe4..4b16a8ca 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -44,12 +44,15 @@ except ImportError:      django_filters = None -# django-guardian is optional -try: -    import guardian -    import guardian.shortcuts  # Fixes #1624 -except ImportError: -    guardian = None +# Django-guardian is optional. Import only if guardian is in INSTALLED_APPS +# Fixes (#1712). We keep the try/except for the test suite. +guardian = None +if 'guardian' in settings.INSTALLED_APPS: +    try: +        import guardian +        import guardian.shortcuts  # Fixes #1624 +    except ImportError: +        pass  # cStringIO only if it's available, otherwise StringIO diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 96d15eb9..c3b846ae 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -116,6 +116,10 @@ class OrderingFilter(BaseFilterBackend):      def get_ordering(self, request):          """          Ordering is set by a comma delimited ?ordering=... query parameter. +         +        The `ordering` query parameter can be overridden by setting +        the `ordering_param` value on the OrderingFilter or by +        specifying an `ORDERING_PARAM` value in the API settings.          """          params = request.QUERY_PARAMS.get(self.ordering_param)          if params: diff --git a/rest_framework/generics.py b/rest_framework/generics.py index e38c52b1..cecb548f 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -43,6 +43,10 @@ class GenericAPIView(views.APIView):      # You'll need to either set these attributes,      # or override `get_queryset()`/`get_serializer_class()`. +    # If you are overriding a view method, it is important that you call +    # `get_queryset()` instead of accessing the `queryset` property directly, +    # as `queryset` will get evaluated only once, and those results are cached +    # for all subsequent requests.      queryset = None      serializer_class = None @@ -185,7 +189,13 @@ class GenericAPIView(views.APIView):          """          Returns the list of filter backends that this view requires.          """ -        filter_backends = self.filter_backends or [] +        if self.filter_backends is None: +            filter_backends = [] +        else: +            # Note that we are returning a *copy* of the class attribute, +            # so that it is safe for the view to mutate it if needed. +            filter_backends = list(self.filter_backends) +          if not filter_backends and self.filter_backend:              warnings.warn(                  'The `filter_backend` attribute and `FILTER_BACKEND` setting ' @@ -195,6 +205,7 @@ class GenericAPIView(views.APIView):                  DeprecationWarning, stacklevel=2              )              filter_backends = [self.filter_backend] +          return filter_backends @@ -258,6 +269,10 @@ class GenericAPIView(views.APIView):          This must be an iterable, and may be a queryset.          Defaults to using `self.queryset`. +        This method should always be used rather than accessing `self.queryset` +        directly, as `self.queryset` gets evaluated only once, and those results +        are cached for all subsequent requests. +          You may want to override this if you need to provide different          querysets depending on the incoming request. diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 484961ad..7048d87d 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -54,32 +54,37 @@ class JSONRenderer(BaseRenderer):      format = 'json'      encoder_class = encoders.JSONEncoder      ensure_ascii = True -    charset = None -    # JSON is a binary encoding, that can be encoded as utf-8, utf-16 or utf-32. + +    # We don't set a charset because JSON is a binary encoding, +    # that can be encoded as utf-8, utf-16 or utf-32.      # See: http://www.ietf.org/rfc/rfc4627.txt      # Also: http://lucumr.pocoo.org/2013/7/19/application-mimetypes-and-encodings/ +    charset = None + +    def get_indent(self, accepted_media_type, renderer_context): +        if accepted_media_type: +            # If the media type looks like 'application/json; indent=4', +            # then pretty print the result. +            base_media_type, params = parse_header(accepted_media_type.encode('ascii')) +            try: +                return max(min(int(params['indent']), 8), 0) +            except (KeyError, ValueError, TypeError): +                pass + +        # If 'indent' is provided in the context, then pretty print the result. +        # E.g. If we're being called by the BrowsableAPIRenderer. +        return renderer_context.get('indent', None) +      def render(self, data, accepted_media_type=None, renderer_context=None):          """ -        Render `data` into JSON. +        Render `data` into JSON, returning a bytestring.          """          if data is None:              return bytes() -        # If 'indent' is provided in the context, then pretty print the result. -        # E.g. If we're being called by the BrowsableAPIRenderer.          renderer_context = renderer_context or {} -        indent = renderer_context.get('indent', None) - -        if accepted_media_type: -            # If the media type looks like 'application/json; indent=4', -            # then pretty print the result. -            base_media_type, params = parse_header(accepted_media_type.encode('ascii')) -            indent = params.get('indent', indent) -            try: -                indent = max(min(int(indent), 8), 0) -            except (ValueError, TypeError): -                indent = None +        indent = self.get_indent(accepted_media_type, renderer_context)          ret = json.dumps(data, cls=self.encoder_class,              indent=indent, ensure_ascii=self.ensure_ascii) diff --git a/rest_framework/request.py b/rest_framework/request.py index 40467c03..d508f9b4 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -42,13 +42,20 @@ class override_method(object):          self.view = view          self.request = request          self.method = method +        self.action = getattr(view, 'action', None)      def __enter__(self):          self.view.request = clone_request(self.request, self.method) +        if self.action is not None: +            # For viewsets we also set the `.action` attribute. +            action_map = getattr(self.view, 'action_map', {}) +            self.view.action = action_map.get(self.method.lower())          return self.view.request      def __exit__(self, *args, **kwarg):          self.view.request = self.request +        if self.action is not None: +            self.view.action = self.action  class Empty(object): @@ -280,8 +287,8 @@ class Request(object):              self._method = self._request.method              # Allow X-HTTP-METHOD-OVERRIDE header -            self._method = self.META.get('HTTP_X_HTTP_METHOD_OVERRIDE', -                                         self._method) +            if 'HTTP_X_HTTP_METHOD_OVERRIDE' in self.META: +                self._method = self.META['HTTP_X_HTTP_METHOD_OVERRIDE'].upper()      def _load_stream(self):          """ diff --git a/rest_framework/response.py b/rest_framework/response.py index 1dc6abcf..25b78524 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -5,6 +5,7 @@ it is initialized with unrendered data, instead of a pre-rendered string.  The appropriate renderer is called during Django's template response rendering.  """  from __future__ import unicode_literals +import django  from django.core.handlers.wsgi import STATUS_CODE_TEXT  from django.template.response import SimpleTemplateResponse  from rest_framework.compat import six @@ -15,8 +16,11 @@ class Response(SimpleTemplateResponse):      An HttpResponse that allows its data to be rendered into      arbitrary media types.      """ +    # TODO: remove that once Django 1.3 isn't supported +    if django.VERSION >= (1, 4): +        rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_closable_objects'] -    def __init__(self, data=None, status=200, +    def __init__(self, data=None, status=None,                   template_name=None, headers=None,                   exception=False, content_type=None):          """ diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index a15b8f22..2fdc9b9d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -976,7 +976,7 @@ class ModelSerializer(Serializer):              try:                  setattr(instance, key, val)              except ValueError: -                self._errors[key] = self.error_messages['required'] +                self._errors[key] = [self.error_messages['required']]          # Any relations that cannot be set until we've          # saved the model get hidden away on these diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 210741ed..b6e9ca5c 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -25,6 +25,7 @@      {% endblock %}      </head> +  {% block body %}    <body class="{% block bodyclass %}{% endblock %} container">      <div class="wrapper"> @@ -94,7 +95,7 @@          {% endif %}          {% if options_form %} -            <form class="button-form" action="{{ request.get_full_path }}" method="POST" class="pull-right"> +            <form class="button-form" action="{{ request.get_full_path }}" method="POST">                  {% csrf_token %}                  <input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="OPTIONS" />                  <button class="btn btn-primary js-tooltip" title="Make an OPTIONS request on the {{ name }} resource">OPTIONS</button> @@ -102,7 +103,7 @@          {% endif %}          {% if delete_form %} -            <form class="button-form" action="{{ request.get_full_path }}" method="POST" class="pull-right"> +            <form class="button-form" action="{{ request.get_full_path }}" method="POST">                  {% csrf_token %}                  <input type="hidden" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="DELETE" />                  <button class="btn btn-danger js-tooltip" title="Make a DELETE request on the {{ name }} resource">DELETE</button> @@ -231,4 +232,5 @@      <script src="{% static "rest_framework/js/default.js" %}"></script>      {% endblock %}    </body> +  {% endblock %}  </html> diff --git a/rest_framework/templates/rest_framework/login_base.html b/rest_framework/templates/rest_framework/login_base.html index be83c2f5..43860e53 100644 --- a/rest_framework/templates/rest_framework/login_base.html +++ b/rest_framework/templates/rest_framework/login_base.html @@ -1,18 +1,9 @@ +{% extends "rest_framework/base.html" %}  {% load url from future %}  {% load staticfiles %}  {% load rest_framework %} -<html> - -    <head> -        {% block style %} -        {% block bootstrap_theme %} -            <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap.min.css" %}"/> -            <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap-tweaks.css" %}"/> -        {% endblock %} -        <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/default.css" %}"/> -        {% endblock %} -    </head> +    {% block body %}      <body class="container">          <div class="container-fluid" style="margin-top: 30px"> @@ -51,4 +42,4 @@              </div><!-- /.row-fluid -->          </div><!-- /.container-fluid -->      </body> -</html> +    {% endblock %} diff --git a/rest_framework/test.py b/rest_framework/test.py index 284bcee0..d4ec50a0 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -154,6 +154,10 @@ class APIClient(APIRequestFactory, DjangoClient):          kwargs.update(self._credentials)          return super(APIClient, self).request(**kwargs) +    def logout(self): +        self._credentials = {} +        return super(APIClient, self).logout() +  class APITransactionTestCase(testcases.TransactionTestCase):      client_class = APIClient diff --git a/rest_framework/urls.py b/rest_framework/urls.py index 87ec0f0a..eed4bd14 100644 --- a/rest_framework/urls.py +++ b/rest_framework/urls.py @@ -2,15 +2,15 @@  Login and logout views for the browsable API.  Add these to your root URLconf if you're using the browsable API and -your API requires authentication. - -The urls must be namespaced as 'rest_framework', and you should make sure -your authentication settings include `SessionAuthentication`. +your API requires authentication:      urlpatterns = patterns('',          ...          url(r'^auth', include('rest_framework.urls', namespace='rest_framework'))      ) +     +The urls must be namespaced as 'rest_framework', and you should make sure +your authentication settings include `SessionAuthentication`.  """  from __future__ import unicode_literals  from django.conf.urls import patterns, url | 
