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 |
