diff options
Diffstat (limited to 'rest_framework')
| -rw-r--r-- | rest_framework/__init__.py | 2 | ||||
| -rw-r--r-- | rest_framework/authentication.py | 2 | ||||
| -rw-r--r-- | rest_framework/fields.py | 7 | ||||
| -rw-r--r-- | rest_framework/filters.py | 1 | ||||
| -rw-r--r-- | rest_framework/pagination.py | 8 | ||||
| -rw-r--r-- | rest_framework/parsers.py | 20 | ||||
| -rw-r--r-- | rest_framework/routers.py | 4 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 28 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/base.html | 30 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/login_base.html | 35 | ||||
| -rw-r--r-- | rest_framework/templatetags/rest_framework.py | 21 | ||||
| -rw-r--r-- | rest_framework/test.py | 2 | ||||
| -rw-r--r-- | rest_framework/urls.py | 2 | ||||
| -rw-r--r-- | rest_framework/utils/formatting.py | 6 | ||||
| -rw-r--r-- | rest_framework/views.py | 10 | 
15 files changed, 124 insertions, 54 deletions
diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index f95bdc22..8d82a4b9 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ ______ _____ _____ _____    __  """  __title__ = 'Django REST framework' -__version__ = '2.4.0' +__version__ = '2.4.2'  __author__ = 'Tom Christie'  __license__ = 'BSD 2-Clause'  __copyright__ = 'Copyright 2011-2014 Tom Christie' diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 5721a869..f3fec05e 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -344,7 +344,7 @@ class OAuth2Authentication(BaseAuthentication):          user = token.user          if not user.is_active: -            msg = 'User inactive or deleted: %s' % user.username +            msg = 'User inactive or deleted: %s' % user.get_username()              raise exceptions.AuthenticationFailed(msg)          return (user, token) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 3ec28908..7496a629 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -51,8 +51,11 @@ def get_attribute(instance, attrs):      for attr in attrs:          try:              instance = getattr(instance, attr) -        except AttributeError: -            return instance[attr] +        except AttributeError as exc: +            try: +                return instance[attr] +            except (KeyError, TypeError): +                raise exc      return instance diff --git a/rest_framework/filters.py b/rest_framework/filters.py index e2080013..c580f935 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -56,7 +56,6 @@ class DjangoFilterBackend(BaseFilterBackend):                  class Meta:                      model = queryset.model                      fields = filter_fields -                    order_by = True              return AutoFilterSet          return None diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 478d32b4..9cf31629 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -43,8 +43,9 @@ class DefaultObjectSerializer(serializers.Field):      as the default.      """ -    def __init__(self, source=None, context=None): -        # Note: Swallow context kwarg - only required for eg. ModelSerializer. +    def __init__(self, source=None, many=None, context=None): +        # Note: Swallow context and many kwargs - only required for +        # eg. ModelSerializer.          super(DefaultObjectSerializer, self).__init__(source=source) @@ -61,6 +62,7 @@ class BasePaginationSerializer(serializers.Serializer):          """          super(BasePaginationSerializer, self).__init__(*args, **kwargs)          results_field = self.results_field +          try:              object_serializer = self.Meta.object_serializer_class          except AttributeError: @@ -70,7 +72,7 @@ class BasePaginationSerializer(serializers.Serializer):              child=object_serializer(),              source='object_list'          ) -        self.fields[results_field].bind(results_field, self, self)  # TODO: Support automatic binding +        self.fields[results_field].bind(results_field, self, self)  class PaginationSerializer(BasePaginationSerializer): diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index aa4fd3f1..c287908d 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -11,7 +11,7 @@ from django.http import QueryDict  from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser  from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter  from django.utils import six -from rest_framework.compat import etree, yaml, force_text +from rest_framework.compat import etree, yaml, force_text, urlparse  from rest_framework.exceptions import ParseError  from rest_framework import renderers  import json @@ -290,6 +290,22 @@ class FileUploadParser(BaseParser):          try:              meta = parser_context['request'].META              disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) -            return force_text(disposition[1]['filename']) +            filename_parm = disposition[1] +            if 'filename*' in filename_parm: +                return self.get_encoded_filename(filename_parm) +            return force_text(filename_parm['filename'])          except (AttributeError, KeyError):              pass + +    def get_encoded_filename(self, filename_parm): +        """ +        Handle encoded filenames per RFC6266. See also: +        http://tools.ietf.org/html/rfc2231#section-4 +        """ +        encoded_filename = force_text(filename_parm['filename*']) +        try: +            charset, lang, filename = encoded_filename.split('\'', 2) +            filename = urlparse.unquote(filename) +        except (ValueError, LookupError): +            filename = force_text(filename_parm['filename']) +        return filename diff --git a/rest_framework/routers.py b/rest_framework/routers.py index ae56673d..8f1ab6fa 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -284,10 +284,10 @@ class DefaultRouter(SimpleRouter):          class APIRoot(views.APIView):              _ignore_model_permissions = True -            def get(self, request, format=None): +            def get(self, request, *args, **kwargs):                  ret = {}                  for key, url_name in api_root_dict.items(): -                    ret[key] = reverse(url_name, request=request, format=format) +                    ret[key] = reverse(url_name, request=request, format=kwargs.get('format', None))                  return Response(ret)          return APIRoot.as_view() diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 13e57939..8fe999ae 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -317,6 +317,19 @@ class ModelSerializerOptions(object):          self.depth = getattr(meta, 'depth', 0) +def lookup_class(mapping, obj): +    """ +    Takes a dictionary with classes as keys, and an object. +    Traverses the object's inheritance hierarchy in method +    resolution order, and returns the first matching value +    from the dictionary or None. +    """ +    return next( +        (mapping[cls] for cls in inspect.getmro(obj.__class__) if cls in mapping), +        None +    ) + +  class ModelSerializer(Serializer):      field_mapping = {          models.AutoField: IntegerField, @@ -580,13 +593,20 @@ class ModelSerializer(Serializer):          if decimal_places is not None:              kwargs['decimal_places'] = decimal_places +        if isinstance(model_field, models.BooleanField): +            # models.BooleanField has `blank=True`, but *is* actually +            # required *unless* a default is provided. +            # Also note that <1.6 `default=False`, >=1.6 `default=None`. +            kwargs.pop('required', None) +          if validator_kwarg:              kwargs['validators'] = validator_kwarg -        try: -            return self.field_mapping[model_field.__class__](**kwargs) -        except KeyError: -            return ModelField(model_field=model_field, **kwargs) +        cls = lookup_class(self.field_mapping, model_field) +        if cls is None: +            cls = ModelField +            kwargs['model_field'] = model_field +        return cls(**kwargs)  class HyperlinkedModelSerializerOptions(ModelSerializerOptions): diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index cee9724d..a84ccf26 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -5,14 +5,14 @@  <html>      <head>          {% block head %} -     +              {% block meta %}                  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>                  <meta name="robots" content="NONE,NOARCHIVE" />              {% endblock %} -     +              <title>{% block title %}Django REST framework{% endblock %}</title> -     +              {% block style %}                  {% block bootstrap_theme %}                      <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/bootstrap.min.css" %}"/> @@ -21,10 +21,11 @@                  <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/prettify.css" %}"/>                  <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/default.css" %}"/>              {% endblock %} -             +          {% endblock %}      </head> +    {% block body %}      <body class="{% block bodyclass %}{% endblock %} container">          <div class="wrapper"> @@ -43,17 +44,9 @@                              <ul class="nav pull-right">                                  {% block userlinks %}                                      {% if user.is_authenticated %} -                                    <li class="dropdown"> -                                        <a href="#" class="dropdown-toggle" data-toggle="dropdown"> -                                            {{ user }} -                                            <b class="caret"></b> -                                        </a> -                                        <ul class="dropdown-menu"> -                                            <li>{% optional_logout request %}</li> -                                        </ul> -                                    </li> +                                        {% optional_logout request user %}                                      {% else %} -                                        <li>{% optional_login request %}</li> +                                        {% optional_login request %}                                      {% endif %}                                  {% endblock %}                              </ul> @@ -84,7 +77,7 @@                              <div class="btn-group format-selection">                                  <a class="btn btn-primary js-tooltip" href='{{ request.get_full_path }}'                                       rel="nofollow" title="Make a GET request on the {{ name }} resource">GET</a> -         +                                  <button class="btn btn-primary dropdown-toggle js-tooltip" data-toggle="dropdown"                                          title="Specify a format for the GET request">                                      <span class="caret"></span> @@ -143,7 +136,7 @@                  </div>                  {% if display_edit_forms %} -     +                      {% if post_form or raw_data_post_form %}                          <div {% if post_form %}class="tabbable"{% endif %}>                              {% if post_form %} @@ -189,7 +182,7 @@                              </div>                          </div>                      {% endif %} -     +                      {% if put_form or raw_data_put_form or raw_data_patch_form %}                          <div {% if put_form %}class="tabbable"{% endif %}>                              {% if put_form %} @@ -245,7 +238,7 @@                  {% endif %}              </div>              <!-- END Content --> -             +              <footer>                  {% block footer %}                      <p>Sponsored by <a href="http://dabapps.com/">DabApps</a>.</p> @@ -261,4 +254,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 43860e53..8ab682ac 100644 --- a/rest_framework/templates/rest_framework/login_base.html +++ b/rest_framework/templates/rest_framework/login_base.html @@ -17,21 +17,44 @@                      <div class="row-fluid">                          <div> -                            <form action="{% url 'rest_framework:login' %}" class=" form-inline" method="post"> +                            <form action="{% url 'rest_framework:login' %}" role="form" method="post">                                  {% csrf_token %} -                                <div id="div_id_username" class="clearfix control-group"> +                                <div id="div_id_username" +                                    class="clearfix control-group {% if form.username.errors %}error{% endif %}">                                      <div class="controls">                                          <Label class="span4">Username:</label> -                                        <input style="height: 25px" type="text" name="username" maxlength="100" autocapitalize="off" autocorrect="off" class="textinput textInput" id="id_username"> +                                        <input style="height: 25px" type="text" name="username" maxlength="100" +                                            autocapitalize="off" +                                            autocorrect="off" class="span12 textinput textInput" +                                            id="id_username" required +                                            {% if form.username.value %}value="{{ form.username.value }}"{% endif %}> +                                        {% if form.username.errors %} +                                            <p class="text-error"> +                                                {{ form.username.errors|striptags }} +                                            </p> +                                        {% endif %}                                      </div>                                  </div> -                                <div id="div_id_password" class="clearfix control-group"> -                                    <div class="controls"> +                                    <div id="div_id_password" +                                        class="clearfix control-group {% if form.password.errors %}error{% endif %}"> +                                        <div class="controls">                                          <Label class="span4">Password:</label> -                                        <input style="height: 25px" type="password" name="password" maxlength="100" autocapitalize="off" autocorrect="off" class="textinput textInput" id="id_password"> +                                        <input style="height: 25px" type="password" name="password" maxlength="100" +                                            autocapitalize="off" autocorrect="off" class="span12 textinput textInput" +                                            id="id_password" required> +                                        {% if form.password.errors %} +                                            <p class="text-error"> +                                                {{ form.password.errors|striptags }} +                                            </p> +                                        {% endif %}                                      </div>                                  </div>                                  <input type="hidden" name="next" value="{{ next }}" /> +                                {% if form.non_field_errors %} +                                    {% for error in form.non_field_errors %} +                                        <div class="well well-small text-error" style="border: none">{{ error }}</div> +                                    {% endfor %} +                                {% endif %}                                  <div class="form-actions-no-box">                                      <input type="submit" name="submit" value="Log in" class="btn btn-primary" id="submit-id-submit">                                  </div> diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index b80a7d77..864d64dd 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -41,22 +41,31 @@ def optional_login(request):      except NoReverseMatch:          return '' -    snippet = "<a href='%s?next=%s'>Log in</a>" % (login_url, escape(request.path)) +    snippet = "<li><a href='{href}?next={next}'>Log in</a></li>".format(href=login_url, next=escape(request.path))      return snippet  @register.simple_tag -def optional_logout(request): +def optional_logout(request, user):      """      Include a logout snippet if REST framework's logout view is in the URLconf.      """      try:          logout_url = reverse('rest_framework:logout')      except NoReverseMatch: -        return '' - -    snippet = "<a href='%s?next=%s'>Log out</a>" % (logout_url, escape(request.path)) -    return snippet +        return '<li class="navbar-text">{user}</li>'.format(user=user) + +    snippet = """<li class="dropdown"> +        <a href="#" class="dropdown-toggle" data-toggle="dropdown"> +            {user} +            <b class="caret"></b> +        </a> +        <ul class="dropdown-menu"> +            <li><a href='{href}?next={next}'>Log out</a></li> +        </ul> +    </li>""" + +    return snippet.format(user=user, href=logout_url, next=escape(request.path))  @register.simple_tag diff --git a/rest_framework/test.py b/rest_framework/test.py index f89a6dcd..9b40353a 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -36,7 +36,7 @@ class APIRequestFactory(DjangoRequestFactory):          Encode the data returning a two tuple of (bytes, content_type)          """ -        if not data: +        if data is None:              return ('', content_type)          assert format is None or content_type is None, ( diff --git a/rest_framework/urls.py b/rest_framework/urls.py index 8fa3073e..cfcee534 100644 --- a/rest_framework/urls.py +++ b/rest_framework/urls.py @@ -6,7 +6,7 @@ your API requires authentication:      urlpatterns = patterns('',          ... -        url(r'^auth', include('rest_framework.urls', namespace='rest_framework')) +        url(r'^auth/', include('rest_framework.urls', namespace='rest_framework'))      )  The urls must be namespaced as 'rest_framework', and you should make sure diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index 6d53aed1..470af51b 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -2,11 +2,12 @@  Utility functions to return a formatted name and description for a given view.  """  from __future__ import unicode_literals +import re  from django.utils.html import escape  from django.utils.safestring import mark_safe -from rest_framework.compat import apply_markdown -import re + +from rest_framework.compat import apply_markdown, force_text  def remove_trailing_string(content, trailing): @@ -28,6 +29,7 @@ def dedent(content):      as it fails to dedent multiline docstrings that include      unindented text on the initial line.      """ +    content = force_text(content)      whitespace_counts = [len(line) - len(line.lstrip(' '))                           for line in content.splitlines()[1:] if line.lstrip()] diff --git a/rest_framework/views.py b/rest_framework/views.py index 079e9285..3b7b1c16 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -108,7 +108,9 @@ class APIView(View):          """          view = super(APIView, cls).as_view(**initkwargs)          view.cls = cls -        return view +        # Note: session based authentication is explicitly CSRF validated, +        # all other authentication is CSRF exempt. +        return csrf_exempt(view)      @property      def allowed_methods(self): @@ -376,9 +378,9 @@ class APIView(View):          response.exception = True          return response -    # Note: session based authentication is explicitly CSRF validated, -    # all other authentication is CSRF exempt. -    @csrf_exempt +    # Note: Views are made CSRF exempt from within `as_view` as to prevent +    # accidental removal of this exemption in cases where `dispatch` needs to +    # be overridden.      def dispatch(self, request, *args, **kwargs):          """          `.dispatch()` is pretty much the same as Django's regular dispatch,  | 
