diff options
| -rwxr-xr-x | docs/api-guide/authentication.md | 3 | ||||
| -rw-r--r-- | docs/topics/contributing.md | 8 | ||||
| -rw-r--r-- | rest_framework/routers.py | 5 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 6 | ||||
| -rw-r--r-- | rest_framework/test.py | 46 | ||||
| -rw-r--r-- | tests/test_testing.py | 47 | ||||
| -rw-r--r-- | tox.ini | 2 | 
7 files changed, 110 insertions, 7 deletions
| diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index b355012e..3a5156fd 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -168,12 +168,13 @@ The `curl` command line tool may be useful for testing token authenticated APIs.  If you want every user to have an automatically generated Token, you can simply catch the User's `post_save` signal. +    from django.conf import settings      from django.contrib.auth import get_user_model      from django.db.models.signals import post_save      from django.dispatch import receiver      from rest_framework.authtoken.models import Token -    @receiver(post_save, sender=get_user_model()) +    @receiver(post_save, sender=settings.AUTH_USER_MODEL)      def create_auth_token(sender, instance=None, created=False, **kwargs):          if created:              Token.objects.create(user=instance) diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md index 3400bc8f..4fafb1b1 100644 --- a/docs/topics/contributing.md +++ b/docs/topics/contributing.md @@ -210,7 +210,9 @@ We recommend the [`django-reusable-app`][django-reusable-app] template as a good  ## Linking to your package -Once your package is decently documented and available on PyPI open a pull request or issue, and we'll add a link to it from the main REST framework documentation. +Once your package is decently documented and available on PyPI open a pull request or issue, and we'll add a link to it from the main REST framework documentation. You can add your package under **Third party packages** of the API Guide section that best applies, like [Authentication][authentication] or [Permissions][permissions]. You can also link your package under the [Third Party Resources][third-party-resources] section. + +We also suggest adding it to the [REST Framework][rest-framework-grid] grid on Django Packages.  [cite]: http://www.w3.org/People/Berners-Lee/FAQ.html  [code-of-conduct]: https://www.djangoproject.com/conduct/ @@ -225,3 +227,7 @@ Once your package is decently documented and available on PyPI open a pull reque  [docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs  [mou]: http://mouapp.com/  [django-reusable-app]: https://github.com/dabapps/django-reusable-app +[authentication]: ../api-guide/authentication.md +[permissions]: ../api-guide/permissions.md +[third-party-resources]: third-party-resources.md +[rest-framework-grid]: https://www.djangopackages.com/grids/g/django-rest-framework/ diff --git a/rest_framework/routers.py b/rest_framework/routers.py index f2d06211..169e6e8b 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -20,6 +20,7 @@ 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 @@ -277,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) @@ -286,7 +287,7 @@ class DefaultRouter(SimpleRouter):              _ignore_model_permissions = True              def get(self, request, *args, **kwargs): -                ret = {} +                ret = SortedDict()                  for key, url_name in api_root_dict.items():                      try:                          ret[key] = reverse( diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 340c6be0..7d85894f 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -849,7 +849,7 @@ class ModelSerializer(Serializer):          }          if model_field: -            kwargs['required'] = not(model_field.null or model_field.blank) +            kwargs['required'] = not(model_field.null or model_field.blank) and model_field.editable              if model_field.help_text is not None:                  kwargs['help_text'] = model_field.help_text              if model_field.verbose_name is not None: @@ -872,7 +872,7 @@ class ModelSerializer(Serializer):          """          kwargs = {} -        if model_field.null or model_field.blank: +        if model_field.null or model_field.blank and model_field.editable:              kwargs['required'] = False          if isinstance(model_field, models.AutoField) or not model_field.editable: @@ -1130,7 +1130,7 @@ class HyperlinkedModelSerializer(ModelSerializer):          }          if model_field: -            kwargs['required'] = not(model_field.null or model_field.blank) +            kwargs['required'] = not(model_field.null or model_field.blank) and model_field.editable              if model_field.help_text is not None:                  kwargs['help_text'] = model_field.help_text              if model_field.verbose_name is not None: diff --git a/rest_framework/test.py b/rest_framework/test.py index 9b40353a..74d2c868 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -156,6 +156,52 @@ class APIClient(APIRequestFactory, DjangoClient):          kwargs.update(self._credentials)          return super(APIClient, self).request(**kwargs) +    def get(self, path, data=None, follow=False, **extra): +        response = super(APIClient, self).get(path, data=data, **extra) +        if follow: +            response = self._handle_redirects(response, **extra) +        return response + +    def post(self, path, data=None, format=None, content_type=None, +             follow=False, **extra): +        response = super(APIClient, self).post( +            path, data=data, format=format, content_type=content_type, **extra) +        if follow: +            response = self._handle_redirects(response, **extra) +        return response + +    def put(self, path, data=None, format=None, content_type=None, +            follow=False, **extra): +        response = super(APIClient, self).put( +            path, data=data, format=format, content_type=content_type, **extra) +        if follow: +            response = self._handle_redirects(response, **extra) +        return response + +    def patch(self, path, data=None, format=None, content_type=None, +              follow=False, **extra): +        response = super(APIClient, self).patch( +            path, data=data, format=format, content_type=content_type, **extra) +        if follow: +            response = self._handle_redirects(response, **extra) +        return response + +    def delete(self, path, data=None, format=None, content_type=None, +               follow=False, **extra): +        response = super(APIClient, self).delete( +            path, data=data, format=format, content_type=content_type, **extra) +        if follow: +            response = self._handle_redirects(response, **extra) +        return response + +    def options(self, path, data=None, format=None, content_type=None, +                follow=False, **extra): +        response = super(APIClient, self).options( +            path, data=data, format=format, content_type=content_type, **extra) +        if follow: +            response = self._handle_redirects(response, **extra) +        return response +      def logout(self):          self._credentials = {}          return super(APIClient, self).logout() diff --git a/tests/test_testing.py b/tests/test_testing.py index 9c472026..9fd5966e 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -5,6 +5,7 @@ from django.conf.urls import patterns, url  from io import BytesIO  from django.contrib.auth.models import User +from django.shortcuts import redirect  from django.test import TestCase  from rest_framework.decorators import api_view  from rest_framework.response import Response @@ -28,10 +29,16 @@ def session_view(request):      }) +@api_view(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']) +def redirect_view(request): +    return redirect('/view/') + +  urlpatterns = patterns(      '',      url(r'^view/$', view),      url(r'^session-view/$', session_view), +    url(r'^redirect-view/$', redirect_view),  ) @@ -111,6 +118,46 @@ class TestAPITestClient(TestCase):          response = self.client.get('/view/')          self.assertEqual(response.data['auth'], b'') +    def test_follow_redirect(self): +        """ +        Follow redirect by setting follow argument. +        """ +        response = self.client.get('/redirect-view/') +        self.assertEqual(response.status_code, 302) +        response = self.client.get('/redirect-view/', follow=True) +        self.assertIsNotNone(response.redirect_chain) +        self.assertEqual(response.status_code, 200) + +        response = self.client.post('/redirect-view/') +        self.assertEqual(response.status_code, 302) +        response = self.client.post('/redirect-view/', follow=True) +        self.assertIsNotNone(response.redirect_chain) +        self.assertEqual(response.status_code, 200) + +        response = self.client.put('/redirect-view/') +        self.assertEqual(response.status_code, 302) +        response = self.client.put('/redirect-view/', follow=True) +        self.assertIsNotNone(response.redirect_chain) +        self.assertEqual(response.status_code, 200) + +        response = self.client.patch('/redirect-view/') +        self.assertEqual(response.status_code, 302) +        response = self.client.patch('/redirect-view/', follow=True) +        self.assertIsNotNone(response.redirect_chain) +        self.assertEqual(response.status_code, 200) + +        response = self.client.delete('/redirect-view/') +        self.assertEqual(response.status_code, 302) +        response = self.client.delete('/redirect-view/', follow=True) +        self.assertIsNotNone(response.redirect_chain) +        self.assertEqual(response.status_code, 200) + +        response = self.client.options('/redirect-view/') +        self.assertEqual(response.status_code, 302) +        response = self.client.options('/redirect-view/', follow=True) +        self.assertIsNotNone(response.redirect_chain) +        self.assertEqual(response.status_code, 200) +  class TestAPIRequestFactory(TestCase):      def test_csrf_exempt_by_default(self): @@ -9,6 +9,8 @@ envlist =  [testenv]  commands = ./runtests.py --fast +setenv = +       PYTHONDONTWRITEBYTECODE=1  [testenv:flake8]  basepython = python2.7 | 
