diff options
Diffstat (limited to 'rest_framework')
22 files changed, 468 insertions, 334 deletions
| diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index f30012b9..7f724c18 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ ______ _____ _____ _____    __  """  __title__ = 'Django REST framework' -__version__ = '2.3.14' +__version__ = '2.4.3'  __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/authtoken/migrations/0001_initial.py b/rest_framework/authtoken/migrations/0001_initial.py index 2e5d6b47..769f6202 100644 --- a/rest_framework/authtoken/migrations/0001_initial.py +++ b/rest_framework/authtoken/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# encoding: utf8 +# -*- coding: utf-8 -*-  from __future__ import unicode_literals  from django.db import models, migrations @@ -15,12 +15,11 @@ class Migration(migrations.Migration):          migrations.CreateModel(              name='Token',              fields=[ -                ('key', models.CharField(max_length=40, serialize=False, primary_key=True)), -                ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, to_field='id')), +                ('key', models.CharField(primary_key=True, serialize=False, max_length=40)),                  ('created', models.DateTimeField(auto_now_add=True)), +                ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='auth_token')),              ],              options={ -                'abstract': False,              },              bases=(models.Model,),          ), diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py index 99e99ae3..472e59ee 100644 --- a/rest_framework/authtoken/serializers.py +++ b/rest_framework/authtoken/serializers.py @@ -22,7 +22,7 @@ class AuthTokenSerializer(serializers.Serializer):                  attrs['user'] = user                  return attrs              else: -                msg = _('Unable to login with provided credentials.') +                msg = _('Unable to log in with provided credentials.')                  raise serializers.ValidationError(msg)          else:              msg = _('Must include "username" and "password"') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 9d707c9b..c0253f86 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -475,8 +475,12 @@ class CharField(WritableField):          if isinstance(value, six.string_types):              return value -        if value is None and not self.allow_none: -            return '' +        if value is None: +            if not self.allow_none: +                return '' +            else: +                # Return None explicitly because smart_text(None) == 'None'. See #1834 for details +                return None          return smart_text(value) @@ -559,7 +563,7 @@ class ChoiceField(WritableField):              if isinstance(v, (list, tuple)):                  # This is an optgroup, so look inside the group for options                  for k2, v2 in v: -                    if value == smart_text(k2): +                    if value == smart_text(k2) or value == k2:                          return True              else:                  if value == smart_text(k) or value == k: 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/generics.py b/rest_framework/generics.py index 77deb8e4..a62da00b 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -252,6 +252,12 @@ class GenericAPIView(views.APIView):          if serializer_class is not None:              return serializer_class +        warnings.warn( +            'The `.model` attribute on view classes is now deprecated in favor ' +            'of the more explicit `serializer_class` and `queryset` attributes.', +            DeprecationWarning, stacklevel=2 +        ) +          assert self.model is not None, \              "'%s' should either include a 'serializer_class' attribute, " \              "or use the 'model' attribute as a shortcut for " \ @@ -282,6 +288,11 @@ class GenericAPIView(views.APIView):              return self.queryset._clone()          if self.model is not None: +            warnings.warn( +                'The `.model` attribute on view classes is now deprecated in favor ' +                'of the more explicit `serializer_class` and `queryset` attributes.', +                DeprecationWarning, stacklevel=2 +            )              return self.model._default_manager.all()          error_format = "'%s' must define 'queryset' or 'model'" @@ -387,10 +398,11 @@ class GenericAPIView(views.APIView):              if method not in self.allowed_methods:                  continue -            cloned_request = clone_request(request, method) +            original_request = self.request +            self.request = clone_request(request, method)              try:                  # Test global permissions -                self.check_permissions(cloned_request) +                self.check_permissions(self.request)                  # Test object permissions                  if method == 'PUT':                      try: @@ -408,6 +420,8 @@ class GenericAPIView(views.APIView):                  # appropriate metadata about the fields that should be supplied.                  serializer = self.get_serializer()                  actions[method] = serializer.metadata() +            finally: +                self.request = original_request          if actions:              ret['actions'] = actions diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index d51ea929..1f5749f1 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) @@ -82,7 +83,9 @@ class BasePaginationSerializer(serializers.Serializer):          else:              context_kwarg = {} -        self.fields[results_field] = object_serializer(source='object_list', **context_kwarg) +        self.fields[results_field] = object_serializer(source='object_list', +                                                       many=True, +                                                       **context_kwarg)  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/permissions.py b/rest_framework/permissions.py index 6a1a0077..29f60d6d 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -108,6 +108,9 @@ class DjangoModelPermissions(BasePermission):          return [perm % kwargs for perm in self.perms_map[method]]      def has_permission(self, request, view): +        # Note that `.model` attribute on views is deprecated, although we +        # enforce the deprecation on the view `get_serializer_class()` and +        # `get_queryset()` methods, rather than here.          model_cls = getattr(view, 'model', None)          queryset = getattr(view, 'queryset', None) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 406ebcf7..169e6e8b 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -19,6 +19,8 @@ import itertools  from collections import namedtuple  from django.conf.urls import patterns, url  from django.core.exceptions import ImproperlyConfigured +from django.core.urlresolvers import NoReverseMatch +from django.utils.datastructures import SortedDict  from rest_framework import views  from rest_framework.response import Response  from rest_framework.reverse import reverse @@ -128,6 +130,9 @@ class SimpleRouter(BaseRouter):          If `base_name` is not specified, attempt to automatically determine          it from the viewset.          """ +        # Note that `.model` attribute on views is deprecated, although we +        # enforce the deprecation on the view `get_serializer_class()` and +        # `get_queryset()` methods, rather than here.          model_cls = getattr(viewset, 'model', None)          queryset = getattr(viewset, 'queryset', None)          if model_cls is None and queryset is not None: @@ -135,7 +140,7 @@ class SimpleRouter(BaseRouter):          assert model_cls, '`base_name` argument not specified, and could ' \              'not automatically determine the name from the viewset, as ' \ -            'it does not have a `.model` or `.queryset` attribute.' +            'it does not have a `.queryset` attribute.'          return model_cls._meta.object_name.lower() @@ -273,7 +278,7 @@ class DefaultRouter(SimpleRouter):          """          Return a view to use as the API root.          """ -        api_root_dict = {} +        api_root_dict = SortedDict()          list_name = self.routes[0].name          for prefix, viewset, basename in self.registry:              api_root_dict[prefix] = list_name.format(basename=basename) @@ -281,10 +286,19 @@ class DefaultRouter(SimpleRouter):          class APIRoot(views.APIView):              _ignore_model_permissions = True -            def get(self, request, format=None): -                ret = {} +            def get(self, request, *args, **kwargs): +                ret = SortedDict()                  for key, url_name in api_root_dict.items(): -                    ret[key] = reverse(url_name, request=request, format=format) +                    try: +                        ret[key] = reverse( +                            url_name, +                            request=request, +                            format=kwargs.get('format', None) +                        ) +                    except NoReverseMatch: +                        # Don't bail out if eg. no list routes exist, only detail routes. +                        continue +                  return Response(ret)          return APIRoot.as_view() diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 27af7ef3..d24cb99d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -625,6 +625,20 @@ class ModelSerializerOptions(SerializerOptions):          self.write_only_fields = getattr(meta, 'write_only_fields', ()) +def _get_class_mapping(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):      """      A serializer that deals with model instances and querysets. @@ -899,15 +913,17 @@ class ModelSerializer(Serializer):              models.URLField: ['max_length'],          } -        if model_field.__class__ in attribute_dict: -            attributes = attribute_dict[model_field.__class__] +        attributes = _get_class_mapping(attribute_dict, model_field) +        if attributes:              for attribute in attributes:                  kwargs.update({attribute: getattr(model_field, attribute)}) -        try: -            return self.field_mapping[model_field.__class__](**kwargs) -        except KeyError: -            return ModelField(model_field=model_field, **kwargs) +        serializer_field_class = _get_class_mapping( +            self.field_mapping, model_field) + +        if serializer_field_class: +            return serializer_field_class(**kwargs) +        return ModelField(model_field=model_field, **kwargs)      def get_validation_exclusions(self, instance=None):          """ diff --git a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css index 6bfb778c..6fa1e6cb 100644 --- a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css +++ b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css @@ -6,30 +6,30 @@ a single block in the template.  */ -  .form-actions { -    background: transparent; -    border-top-color: transparent; -    padding-top: 0; +  background: transparent; +  border-top-color: transparent; +  padding-top: 0;  }  .navbar-inverse .brand a { -    color: #999; +  color: #999999;  }  .navbar-inverse .brand:hover a { -    color: white; -    text-decoration: none; +  color: white; +  text-decoration: none;  }  /* custom navigation styles */ -.wrapper .navbar{ +.navbar {    width: 100%; -  position: absolute; +  position: fixed;    left: 0;    top: 0; +  z-index: 3;  } -.navbar .navbar-inner{ +.navbar .navbar-inner {    background: #2C2C2C;    color: white;    border: none; @@ -37,25 +37,26 @@ a single block in the template.    border-radius: 0px;  } -.navbar .navbar-inner .nav li, .navbar .navbar-inner .nav li a, .navbar .navbar-inner .brand:hover{ +.navbar .navbar-inner .nav li, .navbar .navbar-inner .nav li a, .navbar .navbar-inner .brand:hover {    color: white;  }  .nav-list > .active > a, .nav-list > .active > a:hover { -  background: #2c2c2c; +  background: #2C2C2C;  } -.navbar .navbar-inner .dropdown-menu li a, .navbar .navbar-inner .dropdown-menu li{ - color: #A30000; +.navbar .navbar-inner .dropdown-menu li a, .navbar .navbar-inner .dropdown-menu li { +  color: #A30000;  } -.navbar .navbar-inner .dropdown-menu li a:hover{ -  background: #eeeeee; -  color: #c20000; + +.navbar .navbar-inner .dropdown-menu li a:hover { +  background: #EEEEEE; +  color: #C20000;  }  /*=== dabapps bootstrap styles ====*/ -html{ +html {    width:100%;    background: none;  } @@ -65,121 +66,127 @@ body, .navbar .navbar-inner .container-fluid {    margin: 0 auto;  } -body{ +body {    background: url("../img/grid.png") repeat-x;    background-attachment: fixed;  } -#content{ -    margin: 0; +#content { +  margin: 0; +  padding-bottom: 60px;  }  /* sticky footer and footer */  html, body {    height: 100%;  } +  .wrapper { +  position: relative; +  top: 0; +  left: 0; +  padding-top: 60px; +  margin: -60px 0;    min-height: 100%; -  height: auto !important; -  height: 100%; -  margin: 0 auto -60px;  }  .form-switcher { -    margin-bottom: 0; +  margin-bottom: 0;  }  .well { -    -webkit-box-shadow: none; -       -moz-box-shadow: none; -            box-shadow: none; +  -webkit-box-shadow: none; +     -moz-box-shadow: none; +          box-shadow: none;  }  .well .form-actions { -    padding-bottom: 0; -    margin-bottom: 0; +  padding-bottom: 0; +  margin-bottom: 0;  }  .well form { -    margin-bottom: 0; +  margin-bottom: 0;  }  .well form .help-block { -    color: #999; +  color: #999999;  }  .nav-tabs { -    border: 0; +  border: 0;  }  .nav-tabs > li { -    float: right; +  float: right;  }  .nav-tabs li a { -    margin-right: 0; +  margin-right: 0;  }  .nav-tabs > .active > a { -    background: #f5f5f5; +  background: #F5F5F5;  }  .nav-tabs > .active > a:hover { -    background: #f5f5f5; -} - -.tabbable.first-tab-active .tab-content -{ -    border-top-right-radius: 0; +  background: #F5F5F5;  } -#footer, #push { -  height: 60px; /* .push must be the same height as .footer */ +.tabbable.first-tab-active .tab-content { +  border-top-right-radius: 0;  } -#footer{ -    text-align: right; +footer { +  position: absolute; +  bottom: 0; +  left: 0; +  clear: both; +  z-index: 10; +  height: 60px; +  width: 95%; +  margin: 0 2.5%;  } -#footer p { +footer p {    text-align: center;    color: gray; -  border-top: 1px solid #DDD; +  border-top: 1px solid #DDDDDD;    padding-top: 10px;  } -#footer a { -  color: gray; +footer a { +  color: gray !important;    font-weight: bold;  } -#footer a:hover { +footer a:hover {    color: gray;  }  .page-header { -    border-bottom: none; -    padding-bottom: 0px; -    margin-bottom: 20px; +  border-bottom: none; +  padding-bottom: 0px; +  margin-bottom: 20px;  }  /* custom general page styles */ -.hero-unit h2, .hero-unit h1{ +.hero-unit h1, .hero-unit h2 {    color: #A30000;  } -body a, body a{ +body a {    color: #A30000;  } -body a:hover{ +body a:hover {    color: #c20000;  } -#content a span{ -    text-decoration: underline; +#content a span { +  text-decoration: underline;   }  .request-info { -    clear:both; +  clear:both;  } diff --git a/rest_framework/static/rest_framework/css/default.css b/rest_framework/static/rest_framework/css/default.css index 0261a303..461cdfe5 100644 --- a/rest_framework/static/rest_framework/css/default.css +++ b/rest_framework/static/rest_framework/css/default.css @@ -3,20 +3,20 @@  content running up underneath it. */  h1 { -    font-weight: 500; +  font-weight: 500;  }  h2, h3 { -    font-weight: 300; +  font-weight: 300;  }  .resource-description, .response-info { -    margin-bottom: 2em; +  margin-bottom: 2em;  }  .version:before { -    content: "v"; -    opacity: 0.6; -    padding-right: 0.25em; +  content: "v"; +  opacity: 0.6; +  padding-right: 0.25em;  }  .version { @@ -24,16 +24,16 @@ h2, h3 {  }  .format-option { -    font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace; +  font-family: Menlo, Consolas, "Andale Mono", "Lucida Console", monospace;  }  .button-form { -    float: right; -    margin-right: 1em; +  float: right; +  margin-right: 1em;  }  ul.breadcrumb { -  margin: 58px 0 0 0; +  margin: 80px 0 0 0;  }  form select, form input, form textarea { @@ -43,17 +43,18 @@ form select, form input, form textarea {  form select[multiple] {    height: 150px;  } +  /* To allow tooltips to work on disabled elements */  .disabled-tooltip-shield { -    position: absolute; -    top: 0; -    right: 0; -    bottom: 0; -    left: 0; +  position: absolute; +  top: 0; +  right: 0; +  bottom: 0; +  left: 0;  }  .errorlist { -    margin-top: 0.5em; +  margin-top: 0.5em;  }  pre { @@ -64,8 +65,7 @@ pre {  }  .page-header { -    border-bottom: none; -    padding-bottom: 0px; -    margin-bottom: 20px; +  border-bottom: none; +  padding-bottom: 0px; +  margin-bottom: 20px;  } - diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index b6e9ca5c..3628daa0 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -4,233 +4,255 @@  <!DOCTYPE html>  <html>      <head> -    {% block head %} +        {% block head %} -        {% block meta %} -        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> -        <meta name="robots" content="NONE,NOARCHIVE" /> -        {% endblock %} +            {% 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> +            <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" %}"/> -            <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/prettify.css" %}"/> -        <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/default.css" %}"/> -        {% endblock %} +            {% 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/prettify.css" %}"/> +                <link rel="stylesheet" type="text/css" href="{% static "rest_framework/css/default.css" %}"/> +            {% endblock %} -    {% endblock %} +        {% endblock %}      </head> -  {% block body %} -  <body class="{% block bodyclass %}{% endblock %} container"> - -    <div class="wrapper"> - -    {% block navbar %} -    <div class="navbar {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}"> -        <div class="navbar-inner"> -            <div class="container-fluid"> -                <span href="/"> -                    {% block branding %}<a class='brand' rel="nofollow" href='http://www.django-rest-framework.org'>Django REST framework <span class="version">{{ version }}</span></a>{% endblock %} -                </span> -                <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> -                        {% else %} -                            <li>{% optional_login request %}</li> -                        {% endif %} -                    {% endblock %} -                </ul> -            </div> -        </div> -    </div> -    {% endblock %} +    {% block body %} +    <body class="{% block bodyclass %}{% endblock %} container"> -    {% block breadcrumbs %} -    <ul class="breadcrumb"> -        {% for breadcrumb_name, breadcrumb_url in breadcrumblist %} -            <li> -                <a href="{{ breadcrumb_url }}" {% if forloop.last %}class="active"{% endif %}>{{ breadcrumb_name }}</a> {% if not forloop.last %}<span class="divider">›</span>{% endif %} -            </li> -        {% endfor %} -    </ul> -    {% endblock %} +        <div class="wrapper"> -    <!-- Content --> -    <div id="content"> - -        {% if 'GET' in allowed_methods %} -            <form id="get-form" class="pull-right"> -                <fieldset> -                    <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> -                        </button> -                        <ul class="dropdown-menu"> -                            {% for format in available_formats %} -                                <li> -                                    <a class="js-tooltip format-option" href='{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}' rel="nofollow" title="Make a GET request on the {{ name }} resource with the format set to `{{ format }}`">{{ format }}</a> -                                </li> -                            {% endfor %} -                        </ul> +            {% block navbar %} +                <div class="navbar {% block bootstrap_navbar_variant %}navbar-inverse{% endblock %}"> +                    <div class="navbar-inner"> +                        <div class="container-fluid"> +                            <span> +                                {% block branding %} +                                    <a class='brand' rel="nofollow" href='http://www.django-rest-framework.org'> +                                        Django REST framework <span class="version">{{ version }}</span> +                                    </a> +                                {% endblock %} +                            </span> +                            <ul class="nav pull-right"> +                                {% block userlinks %} +                                    {% if user.is_authenticated %} +                                        {% optional_logout request user %} +                                    {% else %} +                                        {% optional_login request %} +                                    {% endif %} +                                {% endblock %} +                            </ul> +                        </div>                      </div> +                </div> +            {% endblock %} -                </fieldset> -            </form> -        {% endif %} - -        {% if options_form %} -            <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> -            </form> -        {% endif %} - -        {% if delete_form %} -            <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> -            </form> -        {% endif %} - -        <div class="content-main"> -            <div class="page-header"><h1>{{ name }}</h1></div> -            {% block description %} -            {{ description }} +            {% block breadcrumbs %} +                <ul class="breadcrumb"> +                    {% for breadcrumb_name, breadcrumb_url in breadcrumblist %} +                        <li> +                            <a href="{{ breadcrumb_url }}" {% if forloop.last %}class="active"{% endif %}> +                                {{ breadcrumb_name }} +                            </a> +                            {% if not forloop.last %}<span class="divider">›</span>{% endif %} +                        </li> +                    {% endfor %} +                </ul>              {% endblock %} -            <div class="request-info" style="clear: both" > -                <pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre> -            </div> -            <div class="response-info"> -                <pre class="prettyprint"><div class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %} -{% for key, val in response_headers.items %}<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span> -{% endfor %} -</div>{{ content|urlize_quoted_links }}</pre>{% endautoescape %} -            </div> -        </div> -            {% if display_edit_forms %} +            <!-- Content --> +            <div id="content"> -                {% if post_form or raw_data_post_form %} -                <div {% if post_form %}class="tabbable"{% endif %}> -                    {% if post_form %} -                    <ul class="nav nav-tabs form-switcher"> -                        <li><a name='html-tab' href="#object-form" data-toggle="tab">HTML form</a></li> -                        <li><a name='raw-tab' href="#generic-content-form" data-toggle="tab">Raw data</a></li> -                    </ul> -                    {% endif %} -                    <div class="well tab-content"> -                        {% if post_form %} -                        <div class="tab-pane" id="object-form"> -                            {% with form=post_form %} -                            <form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal"> -                                <fieldset> -                                    {{ post_form }} -                                    <div class="form-actions"> -                                        <button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button> -                                    </div> -                                </fieldset> -                            </form> -                            {% endwith %} -                        </div> -                        {% endif %} -                        <div {% if post_form %}class="tab-pane"{% endif %} id="generic-content-form"> -                            {% with form=raw_data_post_form %} -                            <form action="{{ request.get_full_path }}" method="POST" class="form-horizontal"> -                                <fieldset> -                                    {% include "rest_framework/raw_data_form.html" %} -                                    <div class="form-actions"> -                                        <button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button> -                                    </div> -                                </fieldset> -                            </form> -                            {% endwith %} -                        </div> +                {% if 'GET' in allowed_methods %} +                    <form id="get-form" class="pull-right"> +                        <fieldset> +                            <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> +                                </button> +                                <ul class="dropdown-menu"> +                                    {% for format in available_formats %} +                                        <li> +                                            <a class="js-tooltip format-option" +                                                href='{% add_query_param request api_settings.URL_FORMAT_OVERRIDE format %}' +                                                 rel="nofollow" +                                               title="Make a GET request on the {{ name }} resource with the format set to `{{ format }}`"> +                                                {{ format }} +                                            </a> +                                        </li> +                                    {% endfor %} +                                </ul> +                            </div> +                        </fieldset> +                    </form> +                {% endif %} + +                {% if options_form %} +                    <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> +                    </form> +                {% endif %} + +                {% if delete_form %} +                    <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> +                    </form> +                {% endif %} + +                <div class="content-main"> +                    <div class="page-header"> +                        <h1>{{ name }}</h1> +                    </div> +                    {% block description %} +                        {{ description }} +                    {% endblock %} +                    <div class="request-info" style="clear: both" > +                        <pre class="prettyprint"><b>{{ request.method }}</b> {{ request.get_full_path }}</pre> +                    </div> +                    <div class="response-info"> +                        <pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %} +{% for key, val in response_headers.items %}<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span> +{% endfor %} +</span>{{ content|urlize_quoted_links }}</pre>{% endautoescape %}                      </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 %} -                    <ul class="nav nav-tabs form-switcher"> -                        <li><a name='html-tab' href="#object-form" data-toggle="tab">HTML form</a></li> -                        <li><a  name='raw-tab' href="#generic-content-form" data-toggle="tab">Raw data</a></li> -                    </ul> -                    {% endif %} -                    <div class="well tab-content"> -                        {% if put_form %} -                        <div class="tab-pane" id="object-form"> -                            <form action="{{ request.get_full_path }}" method="POST" enctype="multipart/form-data" class="form-horizontal"> -                                <fieldset> -                                    {{ put_form }} -                                    <div class="form-actions"> -                                        <button class="btn btn-primary js-tooltip" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PUT" title="Make a PUT request on the {{ name }} resource">PUT</button> +                {% if display_edit_forms %} + +                    {% if post_form or raw_data_post_form %} +                        <div {% if post_form %}class="tabbable"{% endif %}> +                            {% if post_form %} +                                <ul class="nav nav-tabs form-switcher"> +                                    <li> +                                        <a name='html-tab' href="#post-object-form" data-toggle="tab">HTML form</a> +                                    </li> +                                    <li> +                                        <a name='raw-tab' href="#post-generic-content-form" data-toggle="tab">Raw data</a> +                                    </li> +                                </ul> +                            {% endif %} +                            <div class="well tab-content"> +                                {% if post_form %} +                                    <div class="tab-pane" id="post-object-form"> +                                        {% with form=post_form %} +                                            <form action="{{ request.get_full_path }}" +                                                  method="POST" enctype="multipart/form-data" class="form-horizontal"> +                                                <fieldset> +                                                    {{ post_form }} +                                                    <div class="form-actions"> +                                                        <button class="btn btn-primary" +                                                                title="Make a POST request on the {{ name }} resource">POST</button> +                                                    </div> +                                                </fieldset> +                                            </form> +                                        {% endwith %}                                      </div> -                                </fieldset> -                            </form> +                                {% endif %} +                                <div {% if post_form %}class="tab-pane"{% endif %} id="post-generic-content-form"> +                                    {% with form=raw_data_post_form %} +                                        <form action="{{ request.get_full_path }}" method="POST" class="form-horizontal"> +                                            <fieldset> +                                                {% include "rest_framework/raw_data_form.html" %} +                                                <div class="form-actions"> +                                                    <button class="btn btn-primary" +                                                            title="Make a POST request on the {{ name }} resource">POST</button> +                                                </div> +                                            </fieldset> +                                        </form> +                                    {% endwith %} +                                </div> +                            </div>                          </div> -                        {% endif %} -                        <div {% if put_form %}class="tab-pane"{% endif %} id="generic-content-form"> -                            {% with form=raw_data_put_or_patch_form %} -                            <form action="{{ request.get_full_path }}" method="POST" class="form-horizontal"> -                                <fieldset> -                                    {% include "rest_framework/raw_data_form.html" %} -                                    <div class="form-actions"> -                                        {% if raw_data_put_form %} -                                        <button class="btn btn-primary js-tooltip" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PUT" title="Make a PUT request on the {{ name }} resource">PUT</button> -                                        {% endif %} -                                        {% if raw_data_patch_form %} -                                        <button class="btn btn-primary js-tooltip" name="{{ api_settings.FORM_METHOD_OVERRIDE }}" value="PATCH" title="Make a PATCH request on the {{ name }} resource">PATCH</button> -                                        {% endif %} +                    {% endif %} + +                    {% if put_form or raw_data_put_form or raw_data_patch_form %} +                        <div {% if put_form %}class="tabbable"{% endif %}> +                            {% if put_form %} +                                <ul class="nav nav-tabs form-switcher"> +                                    <li> +                                        <a name='html-tab' href="#put-object-form" data-toggle="tab">HTML form</a> +                                    </li> +                                    <li> +                                        <a  name='raw-tab' href="#put-generic-content-form" data-toggle="tab">Raw data</a> +                                    </li> +                                </ul> +                            {% endif %} +                            <div class="well tab-content"> +                                {% if put_form %} +                                    <div class="tab-pane" id="put-object-form"> +                                        <form action="{{ request.get_full_path }}" +                                              method="POST" enctype="multipart/form-data" class="form-horizontal"> +                                            <fieldset> +                                                {{ put_form }} +                                                <div class="form-actions"> +                                                    <button class="btn btn-primary js-tooltip" +                                                             name="{{ api_settings.FORM_METHOD_OVERRIDE }}" +                                                            value="PUT" title="Make a PUT request on the {{ name }} resource">PUT</button> +                                                </div> +                                            </fieldset> +                                        </form>                                      </div> -                                </fieldset> -                            </form> -                            {% endwith %} +                                {% endif %} +                                <div {% if put_form %}class="tab-pane"{% endif %} id="put-generic-content-form"> +                                    {% with form=raw_data_put_or_patch_form %} +                                        <form action="{{ request.get_full_path }}" method="POST" class="form-horizontal"> +                                            <fieldset> +                                                {% include "rest_framework/raw_data_form.html" %} +                                                <div class="form-actions"> +                                                    {% if raw_data_put_form %} +                                                        <button class="btn btn-primary js-tooltip" +                                                                 name="{{ api_settings.FORM_METHOD_OVERRIDE }}" +                                                                value="PUT" title="Make a PUT request on the {{ name }} resource">PUT</button> +                                                    {% endif %} +                                                    {% if raw_data_patch_form %} +                                                        <button class="btn btn-primary js-tooltip" +                                                                 name="{{ api_settings.FORM_METHOD_OVERRIDE }}" +                                                                value="PATCH" title="Make a PATCH request on the {{ name }} resource">PATCH</button> +                                                    {% endif %} +                                                </div> +                                            </fieldset> +                                        </form> +                                    {% endwith %} +                                </div> +                            </div>                          </div> -                    </div> -                </div> +                    {% endif %}                  {% endif %} -            {% endif %} - -        </div> -        <!-- END content-main --> - -    </div> -    <!-- END Content --> - -        <div id="push"></div> - -    </div> +            </div> +            <!-- END Content --> -    </div><!-- ./wrapper --> +            <footer> +                {% block footer %} +                    <p>Sponsored by <a href="http://dabapps.com/">DabApps</a>.</p> +                {% endblock %} +            </footer> -    {% block footer %} -    {% endblock %} +        </div><!-- ./wrapper --> -    {% block script %} -    <script src="{% static "rest_framework/js/jquery-1.8.1-min.js" %}"></script> -    <script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script> -    <script src="{% static "rest_framework/js/prettify-min.js" %}"></script> -    <script src="{% static "rest_framework/js/default.js" %}"></script> +        {% block script %} +            <script src="{% static "rest_framework/js/jquery-1.8.1-min.js" %}"></script> +            <script src="{% static "rest_framework/js/bootstrap.min.js" %}"></script> +            <script src="{% static "rest_framework/js/prettify-min.js" %}"></script> +            <script src="{% static "rest_framework/js/default.js" %}"></script> +        {% endblock %} +    </body>      {% 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 23df3443..38346ab7 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -103,7 +103,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): @@ -371,9 +373,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, diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index bb5b304e..84b4bd8d 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -20,6 +20,7 @@ from __future__ import unicode_literals  from functools import update_wrapper  from django.utils.decorators import classonlymethod +from django.views.decorators.csrf import csrf_exempt  from rest_framework import views, generics, mixins @@ -89,7 +90,7 @@ class ViewSetMixin(object):          # resolved URL.          view.cls = cls          view.suffix = initkwargs.get('suffix', None) -        return view +        return csrf_exempt(view)      def initialize_request(self, request, *args, **kargs):          """ | 
