From 90c9968a70e0a3d14cf4433cd356bcbdd30fce1b Mon Sep 17 00:00:00 2001 From: Michael Marvick Date: Sun, 25 Jan 2015 23:45:56 -0800 Subject: [PATCH 01/28] tutorial #1 incorrectly showed string of json instead of ReturnDict type from 'serializer.data', and also has a third item in the second usage --- docs/tutorial/1-serialization.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 80e869ea6..458161d07 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -151,7 +151,7 @@ We've now got a few snippet instances to play with. Let's take a look at serial serializer = SnippetSerializer(snippet) serializer.data - # {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'} + # ReturnDict([('pk', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]) At this point we've translated the model instance into Python native datatypes. To finalize the serialization process we render the data into `json`. @@ -182,7 +182,8 @@ We can also serialize querysets instead of model instances. To do so we simply serializer = SnippetSerializer(Snippet.objects.all(), many=True) serializer.data - # [{'pk': 1, 'title': u'', 'code': u'foo = "bar"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}, {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}] + # [OrderedDict([('pk', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('pk', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('pk', 3), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])] + ## Using ModelSerializers From 2a6937f381fe514e6cc9165c0aee200bf145788f Mon Sep 17 00:00:00 2001 From: Michael Marvick Date: Sun, 25 Jan 2015 23:46:27 -0800 Subject: [PATCH 02/28] tutorial #2 incorrectly showed /item.json instead of /item/.json for format suffixes --- docs/tutorial/2-requests-and-responses.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index c04269695..9315a6644 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -96,7 +96,7 @@ Notice that we're no longer explicitly tying our requests or responses to a give ## Adding optional format suffixes to our URLs -To take advantage of the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as [http://example.com/api/items/4.json][json-url]. +To take advantage of the fact that our responses are no longer hardwired to a single content type let's add support for format suffixes to our API endpoints. Using format suffixes gives us URLs that explicitly refer to a given format, and means our API will be able to handle URLs such as [http://example.com/api/items/4/.json][json-url]. Start by adding a `format` keyword argument to both of the views, like so. From 73bd0d539f24d45695615c25a072175c58a4cf98 Mon Sep 17 00:00:00 2001 From: Michael Marvick Date: Sun, 25 Jan 2015 23:47:01 -0800 Subject: [PATCH 03/28] tutorial #5 incorrectly referenced 'settings.py' instead of 'tutorial/settings.py' --- docs/tutorial/5-relationships-and-hyperlinked-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index 2841f03e9..740a4ce21 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -138,7 +138,7 @@ After adding all those names into our URLconf, our final `snippets/urls.py` file The list views for users and code snippets could end up returning quite a lot of instances, so really we'd like to make sure we paginate the results, and allow the API client to step through each of the individual pages. -We can change the default list style to use pagination, by modifying our `settings.py` file slightly. Add the following setting: +We can change the default list style to use pagination, by modifying our `tutorial/settings.py` file slightly. Add the following setting: REST_FRAMEWORK = { 'PAGINATE_BY': 10 From ee2f2d6baa786e711b8b9707fc5218711c8ddc33 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 30 Jan 2015 15:58:33 +0000 Subject: [PATCH 04/28] Added 1.8-alpha to supported list. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74bcaeefa..53140e556 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements * Python (2.6.5+, 2.7, 3.2, 3.3, 3.4) -* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7) +* Django (1.4.11+, 1.5.6+, 1.6.3+, 1.7, 1.8-alpha) # Installation From 53b29f0902a52f7020c95ab7488a61208b8ee8a2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 31 Jan 2015 08:27:17 +0000 Subject: [PATCH 05/28] _closable_objects as an empty list, not deleted --- rest_framework/response.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/response.py b/rest_framework/response.py index 7f90bae10..c21c60a2e 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -86,8 +86,9 @@ class Response(SimpleTemplateResponse): state = super(Response, self).__getstate__() for key in ( 'accepted_renderer', 'renderer_context', 'resolver_match', - 'client', 'request', 'wsgi_request', '_closable_objects' + 'client', 'request', 'wsgi_request' ): if key in state: del state[key] + state['_closable_objects'] = [] return state From aaa1fcd5d1137a8a32d4923a331032ffd9877975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Padilla?= Date: Sun, 1 Feb 2015 16:18:02 -0400 Subject: [PATCH 06/28] Fixes #2493 --- docs/tutorial/1-serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 80e869ea6..ceb23a020 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -97,7 +97,7 @@ The first thing we need to get started on our Web API is to provide a way of ser class SnippetSerializer(serializers.Serializer): pk = serializers.IntegerField(read_only=True) title = serializers.CharField(required=False, allow_blank=True, max_length=100) - code = serializers.CharField(style={'type': 'textarea'}) + code = serializers.CharField(style={'base_template': 'textarea.html'}) linenos = serializers.BooleanField(required=False) language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python') style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly') From 9437d9b8eef9ef2b0f5f69257c8515416527c5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Padilla?= Date: Sun, 1 Feb 2015 16:18:40 -0400 Subject: [PATCH 07/28] Fix base_template name in example --- docs/topics/3.0-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 24e69c2de..59fe779ca 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -826,7 +826,7 @@ The `style` keyword argument can be used to pass through additional information For example, to use a `textarea` control instead of the default `input` control, you would use the following… additional_notes = serializers.CharField( - style={'base_template': 'text_area.html'} + style={'base_template': 'textarea.html'} ) Similarly, to use a radio button control instead of the default `select` control, you would use the following… From 2111a99b320c546045f4660dec05f17b931935ca Mon Sep 17 00:00:00 2001 From: Dustin Farris Date: Sun, 1 Feb 2015 16:00:24 -0500 Subject: [PATCH 08/28] Update link for ember-django-adapter --- docs/topics/third-party-resources.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/third-party-resources.md b/docs/topics/third-party-resources.md index 6f4df2886..e26e3a2fa 100644 --- a/docs/topics/third-party-resources.md +++ b/docs/topics/third-party-resources.md @@ -237,7 +237,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [django-rest-framework-proxy][django-rest-framework-proxy] - Proxy to redirect incoming request to another API server. * [gaiarestframework][gaiarestframework] - Utils for django-rest-framewok * [drf-extensions][drf-extensions] - A collection of custom extensions -* [ember-data-django-rest-adapter][ember-data-django-rest-adapter] - An ember-data adapter +* [ember-django-adapter][ember-django-adapter] - An adapter for working with Ember.js ## Other Resources @@ -309,7 +309,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [django-rest-framework-proxy]: https://github.com/eofs/django-rest-framework-proxy [gaiarestframework]: https://github.com/AppsFuel/gaiarestframework [drf-extensions]: https://github.com/chibisov/drf-extensions -[ember-data-django-rest-adapter]: https://github.com/toranb/ember-data-django-rest-adapter +[ember-django-adapter]: https://github.com/dustinfarris/ember-django-adapter [beginners-guide-to-the-django-rest-framework]: http://code.tutsplus.com/tutorials/beginners-guide-to-the-django-rest-framework--cms-19786 [getting-started-with-django-rest-framework-and-angularjs]: http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html [end-to-end-web-app-with-django-rest-framework-angularjs]: http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework From 8f1d42e7d5146e19842d2837259284f8730b451d Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Mon, 2 Feb 2015 10:50:54 +0200 Subject: [PATCH 09/28] Fixed typos in docstrings. --- rest_framework/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 42d1e3700..2fd907ecd 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -633,11 +633,11 @@ def raise_errors_on_nested_writes(method_name, serializer, validated_data): If we don't do this explicitly they'd get a less helpful error when calling `.save()` on the serializer. - We don't *automatically* support these sorts of nested writes brecause + We don't *automatically* support these sorts of nested writes because there are too many ambiguities to define a default behavior. Eg. Suppose we have a `UserSerializer` with a nested profile. How should - we handle the case of an update, where the `profile` realtionship does + we handle the case of an update, where the `profile` relationship does not exist? Any of the following might be valid: * Raise an application error. From 4b65e9e42be068ad3e742692262451f8836f09d3 Mon Sep 17 00:00:00 2001 From: Jason Yan Date: Mon, 2 Feb 2015 16:14:34 -0800 Subject: [PATCH 10/28] Fixed missing whitespace in error string. --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 2fd907ecd..d76658b03 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -177,7 +177,7 @@ class BaseSerializer(Field): ) assert hasattr(self, 'initial_data'), ( - 'Cannot call `.is_valid()` as no `data=` keyword argument was' + 'Cannot call `.is_valid()` as no `data=` keyword argument was ' 'passed when instantiating the serializer instance.' ) From f6765696610a0de3cf7d9986a2dfab40ca37e88b Mon Sep 17 00:00:00 2001 From: James Cooke Date: Tue, 3 Feb 2015 13:43:03 +0000 Subject: [PATCH 11/28] Small documentation fixes * Remove "you you" from viewsets API-guide * Fix link from routers API-guide to viewsets API-guide --- docs/api-guide/routers.md | 2 +- docs/api-guide/viewsets.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 592f7d66f..222b6cd25 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -304,7 +304,7 @@ The [wq.db package][wq.db] provides an advanced [Router][wq.db-router] class (an The [`DRF-extensions` package][drf-extensions] provides [routers][drf-extensions-routers] for creating [nested viewsets][drf-extensions-nested-viewsets], [collection level controllers][drf-extensions-collection-level-controllers] with [customizable endpoint names][drf-extensions-customizable-endpoint-names]. [cite]: http://guides.rubyonrails.org/routing.html -[route-decorators]: viewsets.html#marking-extra-actions-for-routing +[route-decorators]: viewsets.md#marking-extra-actions-for-routing [drf-nested-routers]: https://github.com/alanjds/drf-nested-routers [wq.db]: http://wq.io/wq.db [wq.db-router]: http://wq.io/docs/app.py diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index b09dfc9e9..bbf92c6ce 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -201,7 +201,7 @@ Note that you can use any of the standard attributes or method overrides provide def get_queryset(self): return self.request.user.accounts.all() -Note however that upon removal of the `queryset` property from your `ViewSet`, any associated [router][routers] will be unable to derive the base_name of your Model automatically, and so you you will have to specify the `base_name` kwarg as part of your [router registration][routers]. +Note however that upon removal of the `queryset` property from your `ViewSet`, any associated [router][routers] will be unable to derive the base_name of your Model automatically, and so you will have to specify the `base_name` kwarg as part of your [router registration][routers]. Also note that although this class provides the complete set of create/list/retrieve/update/destroy actions by default, you can restrict the available operations by using the standard permission classes. From 76efbdddb69d0e7279c1b9de066e829f34019609 Mon Sep 17 00:00:00 2001 From: Warren Jin Date: Tue, 3 Feb 2015 17:18:54 -0500 Subject: [PATCH 12/28] docs --- docs/api-guide/fields.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index b3d274ddb..f379ac720 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -567,6 +567,10 @@ The [drf-compound-fields][drf-compound-fields] package provides "compound" seria The [drf-extra-fields][drf-extra-fields] package provides extra serializer fields for REST framework, including `Base64ImageField` and `PointField` classes. +## djangrestframework-recursive + +the [djangorestframework-recursive][djangorestframework-recursive] package provides a `RecursiveField` for serializing and deserializing recursive structures + ## django-rest-framework-gis The [django-rest-framework-gis][django-rest-framework-gis] package provides geographic addons for django rest framework like a `GeometryField` field and a GeoJSON serializer. @@ -583,6 +587,7 @@ The [django-rest-framework-hstore][django-rest-framework-hstore] package provide [iso8601]: http://www.w3.org/TR/NOTE-datetime [drf-compound-fields]: http://drf-compound-fields.readthedocs.org [drf-extra-fields]: https://github.com/Hipo/drf-extra-fields +[djangorestframework-recursive]: https://github.com/heywbj/django-rest-framework-recursive [django-rest-framework-gis]: https://github.com/djangonauts/django-rest-framework-gis [django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore [django-hstore]: https://github.com/djangonauts/django-hstore From 7bb5fd270da98d8957efb4bf0e4bd4679ddbcf5f Mon Sep 17 00:00:00 2001 From: Greg Kempe Date: Wed, 4 Feb 2015 16:03:03 +0200 Subject: [PATCH 13/28] FIX: Don't default to list in method args Fixes @list_route and @detail_route so that they don't initialize their `methods` parameter as a list. In some cases the list gets cleared, and the result is that default parameter is now empty, and may get reused unexpectedly. --- rest_framework/decorators.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 325435b3f..a68227c14 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -109,10 +109,12 @@ def permission_classes(permission_classes): return decorator -def detail_route(methods=['get'], **kwargs): +def detail_route(methods=None, **kwargs): """ Used to mark a method on a ViewSet that should be routed for detail requests. """ + if methods is None: + methods = ['get'] def decorator(func): func.bind_to_methods = methods func.detail = True @@ -121,10 +123,12 @@ def detail_route(methods=['get'], **kwargs): return decorator -def list_route(methods=['get'], **kwargs): +def list_route(methods=None, **kwargs): """ Used to mark a method on a ViewSet that should be routed for list requests. """ + if methods is None: + methods = ['get'] def decorator(func): func.bind_to_methods = methods func.detail = False From 58e7bbc8ecad8016cc18f7dbd31b235cb515b785 Mon Sep 17 00:00:00 2001 From: Ofir Ovadia Date: Wed, 4 Feb 2015 16:08:41 +0200 Subject: [PATCH 14/28] Prefetching the user object when getting the token in TokenAuthentication. Since the user object is fetched 4 lines after getting Token from the database, this removes a DB query for each token-authenticated request. --- rest_framework/authentication.py | 2 +- tests/test_authentication.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 4832ad33b..f7601fb12 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -167,7 +167,7 @@ class TokenAuthentication(BaseAuthentication): def authenticate_credentials(self, key): try: - token = self.model.objects.get(key=key) + token = self.model.objects.select_related('user').get(key=key) except self.model.DoesNotExist: raise exceptions.AuthenticationFailed('Invalid token') diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 44837c4ef..caabcc214 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -202,6 +202,12 @@ class TokenAuthTests(TestCase): response = self.csrf_client.post('/token/', {'example': 'example'}, format='json', HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_post_json_makes_one_db_query(self): + """Ensure that authenticating a user using a token performs only one DB query""" + auth = "Token " + self.key + func_to_test = lambda: self.csrf_client.post('/token/', {'example': 'example'}, format='json', HTTP_AUTHORIZATION=auth) + self.assertNumQueries(1, func_to_test) + def test_post_form_failing_token_auth(self): """Ensure POSTing form over token auth without correct credentials fails""" response = self.csrf_client.post('/token/', {'example': 'example'}) From d920683237bd2eb17d110a80fc09708a67340f01 Mon Sep 17 00:00:00 2001 From: Greg Kempe Date: Wed, 4 Feb 2015 16:13:30 +0200 Subject: [PATCH 15/28] Use inline if --- rest_framework/decorators.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index a68227c14..7604eae13 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -18,8 +18,7 @@ def api_view(http_method_names=None): Decorator that converts a function-based view into an APIView subclass. Takes a list of allowed methods for the view as an argument. """ - if http_method_names is None: - http_method_names = ['GET'] + http_method_names = ['GET'] if http_method_names is None else http_method_names def decorator(func): @@ -113,8 +112,8 @@ def detail_route(methods=None, **kwargs): """ Used to mark a method on a ViewSet that should be routed for detail requests. """ - if methods is None: - methods = ['get'] + methods = ['get'] if methods is None else methods + def decorator(func): func.bind_to_methods = methods func.detail = True @@ -127,8 +126,8 @@ def list_route(methods=None, **kwargs): """ Used to mark a method on a ViewSet that should be routed for list requests. """ - if methods is None: - methods = ['get'] + methods = ['get'] if methods is None else methods + def decorator(func): func.bind_to_methods = methods func.detail = False From e13d2af1374c8a2b2146e1126d9406bfb4bbd9ec Mon Sep 17 00:00:00 2001 From: Greg Kempe Date: Wed, 4 Feb 2015 16:26:23 +0200 Subject: [PATCH 16/28] Parens around if clause --- rest_framework/decorators.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 7604eae13..21de1acf4 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -18,7 +18,7 @@ def api_view(http_method_names=None): Decorator that converts a function-based view into an APIView subclass. Takes a list of allowed methods for the view as an argument. """ - http_method_names = ['GET'] if http_method_names is None else http_method_names + http_method_names = ['GET'] if (http_method_names is None) else http_method_names def decorator(func): @@ -112,7 +112,7 @@ def detail_route(methods=None, **kwargs): """ Used to mark a method on a ViewSet that should be routed for detail requests. """ - methods = ['get'] if methods is None else methods + methods = ['get'] if (methods is None) else methods def decorator(func): func.bind_to_methods = methods @@ -126,7 +126,7 @@ def list_route(methods=None, **kwargs): """ Used to mark a method on a ViewSet that should be routed for list requests. """ - methods = ['get'] if methods is None else methods + methods = ['get'] if (methods is None) else methods def decorator(func): func.bind_to_methods = methods From fffde8a63be7660e716672c500f0f2bd66c7d345 Mon Sep 17 00:00:00 2001 From: Kaptian Date: Thu, 5 Feb 2015 13:27:26 -0800 Subject: [PATCH 17/28] Update throttling.py Use pk pseudo attribute for identifying the user (in case the user model is not the default and has a different column name for the unique id) --- rest_framework/throttling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 0f10136d6..261fc2463 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -191,7 +191,7 @@ class UserRateThrottle(SimpleRateThrottle): def get_cache_key(self, request, view): if request.user.is_authenticated(): - ident = request.user.id + ident = request.user.pk else: ident = self.get_ident(request) @@ -239,7 +239,7 @@ class ScopedRateThrottle(SimpleRateThrottle): with the '.throttle_scope` property of the view. """ if request.user.is_authenticated(): - ident = request.user.id + ident = request.user.pk else: ident = self.get_ident(request) From 5bf803b6ed260d9afde47400b7d5e8912a16ecf6 Mon Sep 17 00:00:00 2001 From: Michael Marvick Date: Thu, 5 Feb 2015 19:42:36 -0800 Subject: [PATCH 18/28] Revert some of the changes made in 1-serialization.md --- docs/tutorial/1-serialization.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 458161d07..80e869ea6 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -151,7 +151,7 @@ We've now got a few snippet instances to play with. Let's take a look at serial serializer = SnippetSerializer(snippet) serializer.data - # ReturnDict([('pk', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]) + # {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'} At this point we've translated the model instance into Python native datatypes. To finalize the serialization process we render the data into `json`. @@ -182,8 +182,7 @@ We can also serialize querysets instead of model instances. To do so we simply serializer = SnippetSerializer(Snippet.objects.all(), many=True) serializer.data - # [OrderedDict([('pk', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('pk', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('pk', 3), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])] - + # [{'pk': 1, 'title': u'', 'code': u'foo = "bar"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}, {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}] ## Using ModelSerializers From 75ff754517c30df043de906b0a6fb0e1777570b7 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 6 Feb 2015 10:12:57 +0100 Subject: [PATCH 19/28] Use twine to upload to pypi. --- setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index efe39d8d4..391987bc9 100755 --- a/setup.py +++ b/setup.py @@ -48,8 +48,11 @@ if sys.argv[-1] == 'publish': if os.system("pip freeze | grep wheel"): print("wheel not installed.\nUse `pip install wheel`.\nExiting.") sys.exit() - os.system("python setup.py sdist upload") - os.system("python setup.py bdist_wheel upload") + if os.system("pip freeze | grep twine") + print("twine not installed.\nUse `pip install twine`.\nExiting.") + sys.exit() + os.system("python setup.py sdist bdist_wheel") + os.system("twine upload dist/*") print("You probably want to also tag the version now:") print(" git tag -a %s -m 'version %s'" % (version, version)) print(" git push --tags") From 9dd97a0ee515089a1f818007f23460cf83159e71 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 6 Feb 2015 10:23:58 +0100 Subject: [PATCH 20/28] Fixed a typo. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 391987bc9..4cdcfa86e 100755 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ if sys.argv[-1] == 'publish': if os.system("pip freeze | grep wheel"): print("wheel not installed.\nUse `pip install wheel`.\nExiting.") sys.exit() - if os.system("pip freeze | grep twine") + if os.system("pip freeze | grep twine"): print("twine not installed.\nUse `pip install twine`.\nExiting.") sys.exit() os.system("python setup.py sdist bdist_wheel") From 238a3b507baa4543e3bae82a6fc9d88a0aadc5ea Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 6 Feb 2015 13:50:40 +0100 Subject: [PATCH 21/28] Add Twine to the requirements. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 00d973cdf..e5f555f5e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ django-oauth2-provider>=0.2.4 # wheel for PyPI installs wheel==0.24.0 +twine==1.4.0 # MkDocs for documentation previews/deploys mkdocs==0.11.1 From 750d0c9f2b994af2ba92d2d470bbe079f9d9847c Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 6 Feb 2015 13:57:08 +0100 Subject: [PATCH 22/28] Add Twine to the requirements. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index e5f555f5e..32938ab23 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ django-oauth2-provider>=0.2.4 # wheel for PyPI installs wheel==0.24.0 +# twine for secured PyPI uploads twine==1.4.0 # MkDocs for documentation previews/deploys From d13c807616030b285589cec2fddf4e34a8e22b4a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 9 Feb 2015 17:02:54 +0000 Subject: [PATCH 23/28] Fix misleading AttributeErrors --- rest_framework/request.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/rest_framework/request.py b/rest_framework/request.py index cfbbdeccd..38fcf9c0a 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -18,6 +18,7 @@ from django.utils.six import BytesIO from rest_framework import HTTP_HEADER_ENCODING from rest_framework import exceptions from rest_framework.settings import api_settings +import sys import warnings @@ -485,8 +486,16 @@ class Request(object): else: self.auth = None - def __getattr__(self, attr): + def __getattribute__(self, attr): """ - Proxy other attributes to the underlying HttpRequest object. + If an attribute does not exist on this instance, then we also attempt + to proxy it to the underlying HttpRequest object. """ - return getattr(self._request, attr) + try: + return super(Request, self).__getattribute__(attr) + except AttributeError: + info = sys.exc_info() + try: + return getattr(self._request, attr) + except AttributeError: + raise info[0], info[1], info[2].tb_next From 54d82f59ed8a5d2ad4c679680dc52b8a94831d50 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 9 Feb 2015 17:19:22 +0000 Subject: [PATCH 24/28] Py3 compat fix --- rest_framework/request.py | 8 ++++---- tests/test_request.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/rest_framework/request.py b/rest_framework/request.py index 38fcf9c0a..c4de9424a 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -12,9 +12,9 @@ from __future__ import unicode_literals from django.conf import settings from django.http import QueryDict from django.http.multipartparser import parse_header +from django.utils import six from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MergeDict as DjangoMergeDict -from django.utils.six import BytesIO from rest_framework import HTTP_HEADER_ENCODING from rest_framework import exceptions from rest_framework.settings import api_settings @@ -363,7 +363,7 @@ class Request(object): elif hasattr(self._request, 'read'): self._stream = self._request else: - self._stream = BytesIO(self.raw_post_data) + self._stream = six.BytesIO(self.raw_post_data) def _perform_form_overloading(self): """ @@ -405,7 +405,7 @@ class Request(object): self._CONTENTTYPE_PARAM in self._data ): self._content_type = self._data[self._CONTENTTYPE_PARAM] - self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context['encoding'])) + self._stream = six.BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context['encoding'])) self._data, self._files, self._full_data = (Empty, Empty, Empty) def _parse(self): @@ -498,4 +498,4 @@ class Request(object): try: return getattr(self._request, attr) except AttributeError: - raise info[0], info[1], info[2].tb_next + six.reraise(info[0], info[1], info[2].tb_next) diff --git a/tests/test_request.py b/tests/test_request.py index 02a9b1e27..06ad8e937 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -249,6 +249,26 @@ class TestUserSetter(TestCase): login(self.request, self.user) self.assertEqual(self.wrapped_request.user, self.user) + def test_calling_user_fails_when_attribute_error_is_raised(self): + """ + This proves that when an AttributeError is raised inside of the request.user + property, that we can handle this and report the true, underlying error. + """ + class AuthRaisesAttributeError(object): + def authenticate(self, request): + import rest_framework + rest_framework.MISSPELLED_NAME_THAT_DOESNT_EXIST + + self.request = Request(factory.get('/'), authenticators=(AuthRaisesAttributeError(),)) + SessionMiddleware().process_request(self.request) + + login(self.request, self.user) + try: + self.request.user + except AttributeError as error: + self.assertEqual(str(error), "'module' object has no attribute 'MISSPELLED_NAME_THAT_DOESNT_EXIST'") + else: + assert False, 'AttributeError not raised' class TestAuthSetter(TestCase): From 0669f507b3a63114cf429f0662b1781f0e1fa5f8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 9 Feb 2015 17:22:13 +0000 Subject: [PATCH 25/28] pep8 fix --- tests/test_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_request.py b/tests/test_request.py index 06ad8e937..c274ab69d 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -270,8 +270,8 @@ class TestUserSetter(TestCase): else: assert False, 'AttributeError not raised' -class TestAuthSetter(TestCase): +class TestAuthSetter(TestCase): def test_auth_can_be_set(self): request = Request(factory.get('/')) request.auth = 'DUMMY' From b2939c157d32e604e10099be891e382d8c54bbec Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 9 Feb 2015 17:43:20 +0000 Subject: [PATCH 26/28] Fixes for latest version of pep8 --- env/pip-selfcheck.json | 1 + rest_framework/templatetags/rest_framework.py | 4 +++- tests/test_authentication.py | 5 ++++- tests/test_relations_hyperlink.py | 4 +++- tests/test_renderers.py | 9 +++++++-- tests/test_response.py | 9 +++++++-- tests/test_throttling.py | 8 ++++++-- 7 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 env/pip-selfcheck.json diff --git a/env/pip-selfcheck.json b/env/pip-selfcheck.json new file mode 100644 index 000000000..db1087af9 --- /dev/null +++ b/env/pip-selfcheck.json @@ -0,0 +1 @@ +{"last_check":"2015-02-09T17:34:33Z","pypi_version":"6.0.8"} \ No newline at end of file diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 69e03af40..d66ffb330 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -154,7 +154,9 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru If autoescape is True, the link text and URLs will get autoescaped. """ - trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x + def trim_url(x, limit=trim_url_limit): + return limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x + safe_input = isinstance(text, SafeData) words = word_split_re.split(force_text(text)) for i, word in enumerate(words): diff --git a/tests/test_authentication.py b/tests/test_authentication.py index caabcc214..19fe6043f 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -205,7 +205,10 @@ class TokenAuthTests(TestCase): def test_post_json_makes_one_db_query(self): """Ensure that authenticating a user using a token performs only one DB query""" auth = "Token " + self.key - func_to_test = lambda: self.csrf_client.post('/token/', {'example': 'example'}, format='json', HTTP_AUTHORIZATION=auth) + + def func_to_test(): + return self.csrf_client.post('/token/', {'example': 'example'}, format='json', HTTP_AUTHORIZATION=auth) + self.assertNumQueries(1, func_to_test) def test_post_form_failing_token_auth(self): diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index f1b882edf..2230c275c 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -12,7 +12,9 @@ factory = APIRequestFactory() request = factory.get('/') # Just to ensure we have a request in the serializer context -dummy_view = lambda request, pk: None +def dummy_view(request, pk): + pass + urlpatterns = patterns( '', diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 54eea8ceb..4f41144e5 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -28,8 +28,13 @@ import re DUMMYSTATUS = status.HTTP_200_OK DUMMYCONTENT = 'dummycontent' -RENDERER_A_SERIALIZER = lambda x: ('Renderer A: %s' % x).encode('ascii') -RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii') + +def RENDERER_A_SERIALIZER(x): + return ('Renderer A: %s' % x).encode('ascii') + + +def RENDERER_B_SERIALIZER(x): + return ('Renderer B: %s' % x).encode('ascii') expected_results = [ diff --git a/tests/test_response.py b/tests/test_response.py index f233ae332..4a9deaa29 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -38,8 +38,13 @@ class MockTextMediaRenderer(BaseRenderer): DUMMYSTATUS = status.HTTP_200_OK DUMMYCONTENT = 'dummycontent' -RENDERER_A_SERIALIZER = lambda x: ('Renderer A: %s' % x).encode('ascii') -RENDERER_B_SERIALIZER = lambda x: ('Renderer B: %s' % x).encode('ascii') + +def RENDERER_A_SERIALIZER(x): + return ('Renderer A: %s' % x).encode('ascii') + + +def RENDERER_B_SERIALIZER(x): + return ('Renderer B: %s' % x).encode('ascii') class RendererA(BaseRenderer): diff --git a/tests/test_throttling.py b/tests/test_throttling.py index cc36a004c..50a53b3eb 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -188,7 +188,9 @@ class ScopedRateThrottleTests(TestCase): class XYScopedRateThrottle(ScopedRateThrottle): TIMER_SECONDS = 0 THROTTLE_RATES = {'x': '3/min', 'y': '1/min'} - timer = lambda self: self.TIMER_SECONDS + + def timer(self): + return self.TIMER_SECONDS class XView(APIView): throttle_classes = (XYScopedRateThrottle,) @@ -290,7 +292,9 @@ class XffTestingBase(TestCase): class Throttle(ScopedRateThrottle): THROTTLE_RATES = {'test_limit': '1/day'} TIMER_SECONDS = 0 - timer = lambda self: self.TIMER_SECONDS + + def timer(self): + return self.TIMER_SECONDS class View(APIView): throttle_classes = (Throttle,) From 1a087c8c5bac6f157979ef9ff540c0eb23848fb4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 9 Feb 2015 17:47:59 +0000 Subject: [PATCH 27/28] Fix .gitignore --- .gitignore | 18 +++++++----------- env/pip-selfcheck.json | 1 - 2 files changed, 7 insertions(+), 12 deletions(-) delete mode 100644 env/pip-selfcheck.json diff --git a/.gitignore b/.gitignore index 2bdf8f7eb..3d5f1043d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,18 +3,14 @@ *~ .* -site/ -htmlcov/ -coverage/ -build/ -dist/ -*.egg-info/ +/site/ +/htmlcov/ +/coverage/ +/build/ +/dist/ +/*.egg-info/ +/env/ MANIFEST -bin/ -include/ -lib/ -local/ - !.gitignore !.travis.yml diff --git a/env/pip-selfcheck.json b/env/pip-selfcheck.json deleted file mode 100644 index db1087af9..000000000 --- a/env/pip-selfcheck.json +++ /dev/null @@ -1 +0,0 @@ -{"last_check":"2015-02-09T17:34:33Z","pypi_version":"6.0.8"} \ No newline at end of file From 7b639c0cd0676172cc8502e833f5b708f39f9a83 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 9 Feb 2015 17:57:08 +0000 Subject: [PATCH 28/28] Drop django master from the build matrix. Currently always going to be a know failure case. We can add it back when we start to consdier Django 1.9 support. --- .travis.yml | 12 ------------ tox.ini | 3 +-- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 28ebfc00f..4f9297853 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,18 +25,6 @@ env: - TOX_ENV=py33-django18alpha - TOX_ENV=py32-django18alpha - TOX_ENV=py27-django18alpha - - TOX_ENV=py34-djangomaster - - TOX_ENV=py33-djangomaster - - TOX_ENV=py32-djangomaster - - TOX_ENV=py27-djangomaster - -matrix: - fast_finish: true - allow_failures: - - env: TOX_ENV=py34-djangomaster - - env: TOX_ENV=py33-djangomaster - - env: TOX_ENV=py32-djangomaster - - env: TOX_ENV=py27-djangomaster install: - pip install tox diff --git a/tox.ini b/tox.ini index 8e0369643..eda92c19b 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = py27-{flake8,docs}, {py26,py27}-django14, {py26,py27,py32,py33,py34}-django{15,16}, - {py27,py32,py33,py34}-django{17,18alpha,master} + {py27,py32,py33,py34}-django{17,18alpha} [testenv] commands = ./runtests.py --fast @@ -15,7 +15,6 @@ deps = django16: Django==1.6.3 # Should track minimum supported django17: Django==1.7.2 # Should track maximum supported django18alpha: https://www.djangoproject.com/download/1.8a1/tarball/ - djangomaster: https://github.com/django/django/zipball/master {py26,py27}-django{14,15,16,17}: django-guardian==1.2.3 {py26,py27}-django{14,15,16}: oauth2==1.5.211 {py26,py27}-django{14,15,16}: django-oauth-plus==2.2.1