From b74c5235c509738c7afea0be0dd8283bb8339ebe Mon Sep 17 00:00:00 2001 From: Colin Huang Date: Sun, 15 Sep 2013 21:56:43 -0700 Subject: [Add]: CustomValidationTests.test_partial_update This test is to make sure that validate_ is not called when partial=True and is not found in .data. --- rest_framework/tests/test_serializer.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index c2497660..9792685e 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -496,6 +496,33 @@ class CustomValidationTests(TestCase): self.assertFalse(serializer.is_valid()) self.assertEqual(serializer.errors, {'email': ['Enter a valid email address.']}) + def test_partial_update(self): + """ + Make sure that validate_email isn't called when partial=True and email + isn't found in data. + """ + initial_data = { + 'email': 'tom@example.com', + 'content': 'A test comment', + 'created': datetime.datetime(2012, 1, 1) + } + + serializer = self.CommentSerializerWithFieldValidator(data=initial_data) + self.assertEqual(serializer.is_valid(), True) + instance = serializer.object + + new_content = 'An *updated* test comment' + partial_data = { + 'content': new_content + } + + serializer = self.CommentSerializerWithFieldValidator(instance=instance, + data=partial_data, + partial=True) + self.assertEqual(serializer.is_valid(), True) + instance = serializer.object + self.assertEqual(instance.content, new_content) + class PositiveIntegerAsChoiceTests(TestCase): def test_positive_integer_in_json_is_correctly_parsed(self): -- cgit v1.2.3 From 7c3769f04b5ec2cd14dcbd7e3601d59092255906 Mon Sep 17 00:00:00 2001 From: Craig de Stigter Date: Fri, 11 Oct 2013 15:31:55 +1300 Subject: fix writing into foreign key with non-null source --- rest_framework/serializers.py | 2 +- rest_framework/tests/test_serializer_nested.py | 67 ++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 33db82ee..fa5ac143 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -403,7 +403,7 @@ class BaseSerializer(WritableField): return # Set the serializer object if it exists - obj = getattr(self.parent.object, field_name) if self.parent.object else None + obj = get_component(self.parent.object, self.source or field_name) if self.parent.object else None obj = obj.all() if is_simple_callable(getattr(obj, 'all', None)) else obj if self.source == '*': diff --git a/rest_framework/tests/test_serializer_nested.py b/rest_framework/tests/test_serializer_nested.py index 71d0e24b..e454235a 100644 --- a/rest_framework/tests/test_serializer_nested.py +++ b/rest_framework/tests/test_serializer_nested.py @@ -244,3 +244,70 @@ class WritableNestedSerializerObjectTests(TestCase): serializer = self.AlbumSerializer(data=data, many=True) self.assertEqual(serializer.is_valid(), True) self.assertEqual(serializer.object, expected_object) + + +class ForeignKeyNestedSerializerUpdateTests(TestCase): + def setUp(self): + class Artist(object): + def __init__(self, name): + self.name = name + + def __eq__(self, other): + return self.name == other.name + + class Album(object): + def __init__(self, name, artist): + self.name, self.artist = name, artist + + def __eq__(self, other): + return self.name == other.name and self.artist == other.artist + + class ArtistSerializer(serializers.Serializer): + name = serializers.CharField() + + def restore_object(self, attrs, instance=None): + if instance: + instance.name = attrs['name'] + else: + instance = Artist(attrs['name']) + return instance + + class AlbumSerializer(serializers.Serializer): + name = serializers.CharField() + by = ArtistSerializer(source='artist') + + def restore_object(self, attrs, instance=None): + if instance: + instance.name = attrs['name'] + instance.artist = attrs['artist'] + else: + instance = Album(attrs['name'], attrs['artist']) + return instance + + self.Artist = Artist + self.Album = Album + self.AlbumSerializer = AlbumSerializer + + def test_create_via_foreign_key_with_source(self): + """ + Check that we can both *create* and *update* into objects across + ForeignKeys that have a `source` specified. + Regression test for # + """ + data = { + 'name': 'Discovery', + 'by': {'name': 'Daft Punk'}, + } + + expected = self.Album(artist=self.Artist('Daft Punk'), name='Discovery') + + # create + serializer = self.AlbumSerializer(data=data) + self.assertEqual(serializer.is_valid(), True) + self.assertEqual(serializer.object, expected) + + # update + original = self.Album(artist=self.Artist('The Bats'), name='Free All the Monsters') + serializer = self.AlbumSerializer(instance=original, data=data) + self.assertEqual(serializer.is_valid(), True) + self.assertEqual(serializer.object, expected) -- cgit v1.2.3 From 86ea969e1154de20a53fc5b853e8340508648e98 Mon Sep 17 00:00:00 2001 From: Craig de Stigter Date: Fri, 11 Oct 2013 15:50:07 +1300 Subject: fix ticket link in test docstring --- rest_framework/tests/test_serializer_nested.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_serializer_nested.py b/rest_framework/tests/test_serializer_nested.py index e454235a..029f8bff 100644 --- a/rest_framework/tests/test_serializer_nested.py +++ b/rest_framework/tests/test_serializer_nested.py @@ -292,7 +292,7 @@ class ForeignKeyNestedSerializerUpdateTests(TestCase): """ Check that we can both *create* and *update* into objects across ForeignKeys that have a `source` specified. - Regression test for # + Regression test for #1170 """ data = { 'name': 'Discovery', -- cgit v1.2.3 From c6be12f02b5e07e412c8c91b368566a85364b907 Mon Sep 17 00:00:00 2001 From: Colin Huang Date: Sun, 15 Sep 2013 18:03:52 -0700 Subject: [Fix]: Error with partial=True and validate_ The error occurs when serializer is set with partial=True and a field-level validation is defined on a field, for which there's no corresponding update value in .data --- rest_framework/serializers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index a63c7f6c..0b5ae042 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -255,10 +255,13 @@ class BaseSerializer(WritableField): for field_name, field in self.fields.items(): if field_name in self._errors: continue + + source = field.source or field_name + if self.partial and source not in attrs: + continue try: validate_method = getattr(self, 'validate_%s' % field_name, None) if validate_method: - source = field.source or field_name attrs = validate_method(attrs, source) except ValidationError as err: self._errors[field_name] = self._errors.get(field_name, []) + list(err.messages) -- cgit v1.2.3 From e83bc003234418fc6b21b841de216319491bd38d Mon Sep 17 00:00:00 2001 From: Rikki Date: Wed, 16 Oct 2013 03:03:51 +0100 Subject: Added name of file to edit So reader doesn't have to remember, or check through all the files to find where this code fragment was, mention the file name when it is relevant.--- docs/tutorial/2-requests-and-responses.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index 6ff97f37..ba9eb723 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -35,7 +35,7 @@ The wrappers also provide behaviour such as returning `405 Method Not Allowed` r Okay, let's go ahead and start using these new components to write a few views. -We don't need our `JSONResponse` class anymore, so go ahead and delete that. Once that's done we can start refactoring our views slightly. +We don't need our `JSONResponse` class in `views.py` anymore, so go ahead and delete that. Once that's done we can start refactoring our views slightly. from rest_framework import status from rest_framework.decorators import api_view @@ -64,7 +64,7 @@ We don't need our `JSONResponse` class anymore, so go ahead and delete that. On Our instance view is an improvement over the previous example. It's a little more concise, and the code now feels very similar to if we were working with the Forms API. We're also using named status codes, which makes the response meanings more obvious. -Here is the view for an individual snippet. +Here is the view for an individual snippet (still in `views.py`). @api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): -- cgit v1.2.3 From cb123e896ed2dca230088296db9663af5a53252d Mon Sep 17 00:00:00 2001 From: Rikki Date: Wed, 16 Oct 2013 03:08:43 +0100 Subject: Mention name of file to edit To reduce unnecessary cognitive load of the learner, name the file they are putting this code in.--- docs/tutorial/3-class-based-views.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index 9fc424fe..67a75d9f 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -4,7 +4,7 @@ We can also write our API views using class based views, rather than function ba ## Rewriting our API using class based views -We'll start by rewriting the root view as a class based view. All this involves is a little bit of refactoring. +We'll start by rewriting the root view as a class based view. All this involves is a little bit of refactoring of `views.py`. from snippets.models import Snippet from snippets.serializers import SnippetSerializer @@ -30,7 +30,7 @@ We'll start by rewriting the root view as a class based view. All this involves return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) -So far, so good. It looks pretty similar to the previous case, but we've got better separation between the different HTTP methods. We'll also need to update the instance view. +So far, so good. It looks pretty similar to the previous case, but we've got better separation between the different HTTP methods. We'll also need to update the instance view in `views.py`. class SnippetDetail(APIView): """ @@ -62,7 +62,7 @@ So far, so good. It looks pretty similar to the previous case, but we've got be That's looking good. Again, it's still pretty similar to the function based view right now. -We'll also need to refactor our URLconf slightly now we're using class based views. +We'll also need to refactor our `urls.py` slightly now we're using class based views. from django.conf.urls import patterns, url from rest_framework.urlpatterns import format_suffix_patterns @@ -83,7 +83,7 @@ One of the big wins of using class based views is that it allows us to easily co The create/retrieve/update/delete operations that we've been using so far are going to be pretty similar for any model-backed API views we create. Those bits of common behaviour are implemented in REST framework's mixin classes. -Let's take a look at how we can compose our views by using the mixin classes. +Let's take a look at how we can compose our `views.py` by using the mixin classes. from snippets.models import Snippet from snippets.serializers import SnippetSerializer @@ -126,7 +126,7 @@ Pretty similar. Again we're using the `GenericAPIView` class to provide the cor ## Using generic class based views -Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use. +Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use to trim down `views.py` even more. from snippets.models import Snippet from snippets.serializers import SnippetSerializer -- cgit v1.2.3 From bf6084895263f827a80191fd6ed4eb437b555f9a Mon Sep 17 00:00:00 2001 From: Rikki Date: Wed, 16 Oct 2013 03:21:43 +0100 Subject: Using the filenames where relevant Sometimes it's hard to tell which file the code is intended to go in. Now it spells it out.--- docs/tutorial/4-authentication-and-permissions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 510aa243..ecf92a7b 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -12,7 +12,7 @@ Currently our API doesn't have any restrictions on who can edit or delete code s We're going to make a couple of changes to our `Snippet` model class. First, let's add a couple of fields. One of those fields will be used to represent the user who created the code snippet. The other field will be used to store the highlighted HTML representation of the code. -Add the following two fields to the model. +Add the following two fields to the `Snippet` model in `models.py`. owner = models.ForeignKey('auth.User', related_name='snippets') highlighted = models.TextField() @@ -52,7 +52,7 @@ You might also want to create a few different users, to use for testing the API. ## Adding endpoints for our User models -Now that we've got some users to work with, we'd better add representations of those users to our API. Creating a new serializer is easy: +Now that we've got some users to work with, we'd better add representations of those users to our API. Creating a new serializer is easy. In `serializers.py` add: from django.contrib.auth.models import User @@ -65,7 +65,7 @@ Now that we've got some users to work with, we'd better add representations of t Because `'snippets'` is a *reverse* relationship on the User model, it will not be included by default when using the `ModelSerializer` class, so we needed to add an explicit field for it. -We'll also add a couple of views. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class based views. +We'll also add a couple of views to `views.py`. We'd like to just use read-only views for the user representations, so we'll use the `ListAPIView` and `RetrieveAPIView` generic class based views. class UserList(generics.ListAPIView): queryset = User.objects.all() @@ -80,7 +80,7 @@ Make sure to also import the `UserSerializer` class from snippets.serializers import UserSerializer -Finally we need to add those views into the API, by referencing them from the URL conf. +Finally we need to add those views into the API, by referencing them from the URL conf. Add the following to the patterns in `urls.py`. url(r'^users/$', views.UserList.as_view()), url(r'^users/(?P[0-9]+)/$', views.UserDetail.as_view()), @@ -98,7 +98,7 @@ On **both** the `SnippetList` and `SnippetDetail` view classes, add the followin ## Updating our serializer -Now that snippets are associated with the user that created them, let's update our `SnippetSerializer` to reflect that. Add the following field to the serializer definition: +Now that snippets are associated with the user that created them, let's update our `SnippetSerializer` to reflect that. Add the following field to the serializer definition in `serializers.py`: owner = serializers.Field(source='owner.username') -- cgit v1.2.3 From d31fd33f4bbd52fa60949b15c2614528991e2c7a Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Tue, 8 Oct 2013 16:15:15 +0200 Subject: Allow to customize description so that markup can be accepted if needed. --- rest_framework/templates/rest_framework/base.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 33be36db..7ab17dff 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -110,7 +110,9 @@
+ {% block description %} {{ description }} + {% endblock %}
{{ request.method }} {{ request.get_full_path }}
-- cgit v1.2.3 From 8a5fea06f01ed4c5114ec0743516b6e6179c88b4 Mon Sep 17 00:00:00 2001 From: badaud_t Date: Thu, 17 Oct 2013 01:07:50 +0200 Subject: Fix typo YAMLRendererTests --- rest_framework/tests/test_renderers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py index df6f4aa6..78a7dac8 100644 --- a/rest_framework/tests/test_renderers.py +++ b/rest_framework/tests/test_renderers.py @@ -328,7 +328,7 @@ if yaml: class YAMLRendererTests(TestCase): """ - Tests specific to the JSON Renderer + Tests specific to the YAML Renderer """ def test_render(self): -- cgit v1.2.3 From b730aec0f46e2b849b3c597bcf1a1bcdc158e415 Mon Sep 17 00:00:00 2001 From: badaud_t Date: Thu, 17 Oct 2013 01:08:24 +0200 Subject: Fix decimal support with YAMLRenderer --- rest_framework/tests/test_renderers.py | 11 +++++++++++ rest_framework/utils/encoders.py | 3 +++ 2 files changed, 14 insertions(+) diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py index 78a7dac8..76299a89 100644 --- a/rest_framework/tests/test_renderers.py +++ b/rest_framework/tests/test_renderers.py @@ -354,6 +354,17 @@ if yaml: data = parser.parse(StringIO(content)) self.assertEqual(obj, data) + def test_render_decimal(self): + """ + Test YAML decimal rendering. + """ + renderer = YAMLRenderer() + content = renderer.render({'field': Decimal('111.2')}, 'application/yaml') + self.assertYAMLContains(content, "field: '111.2'") + + def assertYAMLContains(self, content, string): + self.assertTrue(string in content, '%r not in %r' % (string, content)) + class XMLRendererTestCase(TestCase): """ diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 7efd5417..35ad206b 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -89,6 +89,9 @@ else: node.flow_style = best_style return node + SafeDumper.add_representer(decimal.Decimal, + SafeDumper.represent_decimal) + SafeDumper.add_representer(SortedDict, yaml.representer.SafeRepresenter.represent_dict) SafeDumper.add_representer(DictWithMetadata, -- cgit v1.2.3 From 545ee013e332db0a81e5bd82b76f30b7d2d08b12 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 17 Oct 2013 09:40:06 +0100 Subject: Added @badale, for work on #1179. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 586bb0f0..9a20028c 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -171,6 +171,7 @@ The following people have helped make REST framework great. * Tai Lee - [mrmachine] * Markus Kaiserswerth - [mkai] * Henry Clifford - [hcliff] +* Thomas Badaud - [badale] Many thanks to everyone who's contributed to the project. @@ -378,3 +379,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [mrmachine]: https://github.com/mrmachine [mkai]: https://github.com/mkai [hcliff]: https://github.com/hcliff +[badale]: https://github.com/badale -- cgit v1.2.3 From cc3c16eaa09c7dc63592ae8bf4ee30f1af263be1 Mon Sep 17 00:00:00 2001 From: Bruno Renié Date: Mon, 14 Oct 2013 16:28:32 +0200 Subject: Fix a docstring to reflect what the method does --- 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 33db82ee..6801e24d 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -912,7 +912,7 @@ class ModelSerializer(Serializer): def save_object(self, obj, **kwargs): """ - Save the deserialized object and return it. + Save the deserialized object. """ if getattr(obj, '_nested_forward_relations', None): # Nested relationships need to be saved before we can save the -- cgit v1.2.3 From daf927ef68ce992055e8b7bc1a07cf03ee67b742 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 17 Oct 2013 12:28:58 -0700 Subject: add @tamakisquare for work on #1111 --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 9a20028c..e9c45965 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -172,6 +172,7 @@ The following people have helped make REST framework great. * Markus Kaiserswerth - [mkai] * Henry Clifford - [hcliff] * Thomas Badaud - [badale] +* Colin Huang - [tamakisquare] Many thanks to everyone who's contributed to the project. @@ -380,3 +381,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [mkai]: https://github.com/mkai [hcliff]: https://github.com/hcliff [badale]: https://github.com/badale +[tamakisquare]: https://github.com/tamakisquare -- cgit v1.2.3 From 78c8e6de40f89580b9a4cefb6595d52bc1a6afbc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 18 Oct 2013 09:10:54 +0100 Subject: Update 2-requests-and-responses.md --- 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 ba9eb723..7fa4f3e4 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -64,7 +64,7 @@ We don't need our `JSONResponse` class in `views.py` anymore, so go ahead and de Our instance view is an improvement over the previous example. It's a little more concise, and the code now feels very similar to if we were working with the Forms API. We're also using named status codes, which makes the response meanings more obvious. -Here is the view for an individual snippet (still in `views.py`). +Here is the view for an individual snippet, in the `views.py` module. @api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): -- cgit v1.2.3 From c3aeb16557f2cbb1c1218b5af7bab646e4958234 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 18 Oct 2013 09:32:04 +0100 Subject: Update 3-class-based-views.md --- docs/tutorial/3-class-based-views.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index 67a75d9f..b37bc31b 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -83,7 +83,7 @@ One of the big wins of using class based views is that it allows us to easily co The create/retrieve/update/delete operations that we've been using so far are going to be pretty similar for any model-backed API views we create. Those bits of common behaviour are implemented in REST framework's mixin classes. -Let's take a look at how we can compose our `views.py` by using the mixin classes. +Let's take a look at how we can compose the views by using the mixin classes. Here's our `views.py` module again. from snippets.models import Snippet from snippets.serializers import SnippetSerializer @@ -126,7 +126,7 @@ Pretty similar. Again we're using the `GenericAPIView` class to provide the cor ## Using generic class based views -Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use to trim down `views.py` even more. +Using the mixin classes we've rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use to trim down our `views.py` module even more. from snippets.models import Snippet from snippets.serializers import SnippetSerializer -- cgit v1.2.3 From 63e6a3b4925bf54e80ae63502a0353136e846b31 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 19 Oct 2013 20:43:23 -0700 Subject: paginator should validate page and provide default - use the standard paginator.validate_number method rather strict_postive_int. - support optional paginator method, default_page_number, to get the default page number rather than hard-coding it to 1 - this allows supporting non-integer based pagination which can be an important performance tweak on extermely large datasets or high request loads - relatively thorough unit tests of the changes --- rest_framework/generics.py | 14 ++++-- rest_framework/tests/test_pagination.py | 88 +++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 4f134bce..6b42a1d5 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -145,10 +145,18 @@ class GenericAPIView(views.APIView): allow_empty_first_page=self.allow_empty) page_kwarg = self.kwargs.get(self.page_kwarg) page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg) - page = page_kwarg or page_query_param or 1 + page = page_kwarg or page_query_param + if not page: + # we didn't recieve a page + if hasattr(paginator, 'default_page_number'): + # our paginator has a method that will provide a default + page = paginator.default_page_number() + else: + # fall back on the base default value + page = 1 try: - page_number = strict_positive_int(page) - except ValueError: + page_number = paginator.validate_number(page) + except InvalidPage: if page == 'last': page_number = paginator.num_pages else: diff --git a/rest_framework/tests/test_pagination.py b/rest_framework/tests/test_pagination.py index d6bc7895..a1118f1e 100644 --- a/rest_framework/tests/test_pagination.py +++ b/rest_framework/tests/test_pagination.py @@ -430,3 +430,91 @@ class TestCustomPaginationSerializer(TestCase): 'objects': ['john', 'paul'] } self.assertEqual(serializer.data, expected) + + +class NonIntegerPage(object): + + def __init__(self, paginator, object_list, prev_token, token, next_token): + self.paginator = paginator + self.object_list = object_list + self.prev_token = prev_token + self.token = token + self.next_token = next_token + + def has_next(self): + return not not self.next_token + + def next_page_number(self): + return self.next_token + + def has_previous(self): + return not not self.prev_token + + def previous_page_number(self): + return self.prev_token + + +class NonIntegerPaginator(object): + + def __init__(self, object_list, per_page): + self.object_list = object_list + self.per_page = per_page + + def count(self): + # pretend like we don't know how many pages we have + return None + + def default_page_token(self): + return None + + def page(self, token=None): + if token: + try: + first = self.object_list.index(token) + except ValueError: + first = 0 + else: + first = 0 + n = len(self.object_list) + last = min(first + self.per_page, n) + prev_token = self.object_list[last - (2 * self.per_page)] if first else None + next_token = self.object_list[last] if last < n else None + return NonIntegerPage(self, self.object_list[first:last], prev_token, token, next_token) + + +class TestNonIntegerPagination(TestCase): + + + def test_custom_pagination_serializer(self): + objects = ['john', 'paul', 'george', 'ringo'] + paginator = NonIntegerPaginator(objects, 2) + + request = APIRequestFactory().get('/foobar') + serializer = CustomPaginationSerializer( + instance=paginator.page(), + context={'request': request} + ) + expected = { + 'links': { + 'next': 'http://testserver/foobar?page={0}'.format(objects[2]), + 'prev': None + }, + 'total_results': None, + 'objects': objects[:2] + } + self.assertEqual(serializer.data, expected) + + request = APIRequestFactory().get('/foobar') + serializer = CustomPaginationSerializer( + instance=paginator.page('george'), + context={'request': request} + ) + expected = { + 'links': { + 'next': None, + 'prev': 'http://testserver/foobar?page={0}'.format(objects[0]), + }, + 'total_results': None, + 'objects': objects[2:] + } + self.assertEqual(serializer.data, expected) -- cgit v1.2.3 From ed9c3258a6f9df6fabb569a65f3eb3363affa523 Mon Sep 17 00:00:00 2001 From: Jesús Espino Date: Mon, 21 Oct 2013 10:24:06 +0200 Subject: Remove the detail=None from APIException signature The documentation not match with the implementation. The APIException doesn't have detail parameter in the constructor class, actually doesn't have constructor method at all.--- docs/api-guide/exceptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index 0c48783a..c46d415e 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -82,7 +82,7 @@ Note that the exception handler will only be called for responses generated by r ## APIException -**Signature:** `APIException(detail=None)` +**Signature:** `APIException()` The **base class** for all exceptions raised inside REST framework. -- cgit v1.2.3 From 70b0798118c8c02903421bca03e0406fe65d737f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 21 Oct 2013 09:30:01 +0100 Subject: Add invite signup --- docs/template.html | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/template.html b/docs/template.html index a20c8111..749d0afe 100644 --- a/docs/template.html +++ b/docs/template.html @@ -167,7 +167,32 @@
+ + +
-- cgit v1.2.3 From 76672787cdba6a4ab8173b51fa099c910556889b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 21 Oct 2013 09:47:07 +0100 Subject: Added . Closes #1188. --- docs/api-guide/generic-views.md | 3 ++- rest_framework/generics.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index dc0076df..24fc0bc7 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -65,7 +65,8 @@ The following attributes control the basic view behavior. * `queryset` - The queryset that should be used for returning objects from this view. Typically, you must either set this attribute, or override the `get_queryset()` method. * `serializer_class` - The serializer class that should be used for validating and deserializing input, and for serializing output. Typically, you must either set this attribute, or override the `get_serializer_class()` method. -* `lookup_field` - The field that should be used to lookup individual model instances. Defaults to `'pk'`. The URL conf should include a keyword argument corresponding to this value. More complex lookup styles can be supported by overriding the `get_object()` method. Note that when using hyperlinked APIs you'll need to ensure that *both* the API views *and* the serializer classes use lookup fields that correctly correspond with the URL conf. +* `lookup_field` - The model field that should be used to for performing object lookup of individual model instances. Defaults to `'pk'`. Note that when using hyperlinked APIs you'll need to ensure that *both* the API views *and* the serializer classes set the lookup fields if you need to use a custom value. +* `lookup_url_kwarg` - The URL keyword argument that should be used for object lookup. The URL conf should include a keyword argument corresponding to this value. If unset this defaults to using the same value as `lookup_field`. **Shortcuts**: diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 4f134bce..f46dea76 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -54,6 +54,7 @@ class GenericAPIView(views.APIView): # If you want to use object lookups other than pk, set this attribute. # For more complex lookup requirements override `get_object()`. lookup_field = 'pk' + lookup_url_kwarg = None # Pagination settings paginate_by = api_settings.PAGINATE_BY @@ -278,9 +279,11 @@ class GenericAPIView(views.APIView): pass # Deprecation warning # Perform the lookup filtering. + # Note that `pk` and `slug` are deprecated styles of lookup filtering. + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field + lookup = self.kwargs.get(lookup_url_kwarg, None) pk = self.kwargs.get(self.pk_url_kwarg, None) slug = self.kwargs.get(self.slug_url_kwarg, None) - lookup = self.kwargs.get(self.lookup_field, None) if lookup is not None: filter_kwargs = {self.lookup_field: lookup} -- cgit v1.2.3 From 216ac8a5c1ba39bf24e4e91b6fac7e0ac1dee7e4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 21 Oct 2013 17:19:28 +0100 Subject: Use lookup_url_kwarg in presave if required --- rest_framework/mixins.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index 426865ff..4606c78b 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -158,7 +158,8 @@ class UpdateModelMixin(object): Set any attributes on the object that are implicit in the request. """ # pk and/or slug attributes are implicit in the URL. - lookup = self.kwargs.get(self.lookup_field, None) + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field + lookup = self.kwargs.get(lookup_url_kwarg, None) pk = self.kwargs.get(self.pk_url_kwarg, None) slug = self.kwargs.get(self.slug_url_kwarg, None) slug_field = slug and self.slug_field or None -- cgit v1.2.3 From f0a129dcda3d671b88b5049d9ddaec53a4b32faf Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Mon, 21 Oct 2013 14:23:06 -0700 Subject: retract the default page stuff. better way comming in a seperate pr --- rest_framework/generics.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 6b42a1d5..4015ab20 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -145,15 +145,7 @@ class GenericAPIView(views.APIView): allow_empty_first_page=self.allow_empty) page_kwarg = self.kwargs.get(self.page_kwarg) page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg) - page = page_kwarg or page_query_param - if not page: - # we didn't recieve a page - if hasattr(paginator, 'default_page_number'): - # our paginator has a method that will provide a default - page = paginator.default_page_number() - else: - # fall back on the base default value - page = 1 + page = page_kwarg or page_query_param or 1 try: page_number = paginator.validate_number(page) except InvalidPage: -- cgit v1.2.3 From c36122a7ba2cdc69f94f5732f26428329be54200 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Mon, 21 Oct 2013 14:26:21 -0700 Subject: remove stray func from test --- rest_framework/tests/test_pagination.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/rest_framework/tests/test_pagination.py b/rest_framework/tests/test_pagination.py index a1118f1e..cadb515f 100644 --- a/rest_framework/tests/test_pagination.py +++ b/rest_framework/tests/test_pagination.py @@ -464,9 +464,6 @@ class NonIntegerPaginator(object): # pretend like we don't know how many pages we have return None - def default_page_token(self): - return None - def page(self, token=None): if token: try: -- cgit v1.2.3 From fa87fac61b87858e80788fc233591fa11dbc18e7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 22 Oct 2013 10:21:06 +0100 Subject: Added @ross for work on #1187. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index e9c45965..cd3b3710 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -173,6 +173,7 @@ The following people have helped make REST framework great. * Henry Clifford - [hcliff] * Thomas Badaud - [badale] * Colin Huang - [tamakisquare] +* Ross McFarland - [ross] Many thanks to everyone who's contributed to the project. @@ -382,3 +383,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [hcliff]: https://github.com/hcliff [badale]: https://github.com/badale [tamakisquare]: https://github.com/tamakisquare +[ross]: https://github.com/ross -- cgit v1.2.3 From 25c9d552c05527f4b8b257d59cd7be39005f3668 Mon Sep 17 00:00:00 2001 From: Jacek Bzdak Date: Tue, 22 Oct 2013 13:11:14 +0200 Subject: Explained a bit more about django-filter implementation. Well, I spent some time trying to gues how djang-filter works, and if this changes would be introduced, I would have saved this time. --- docs/api-guide/filtering.md | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index 784aa585..bcb0bb41 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -165,8 +165,8 @@ For more advanced filtering requirements you can specify a `FilterSet` class tha from rest_framework import generics class ProductFilter(django_filters.FilterSet): - min_price = django_filters.NumberFilter(lookup_type='gte') - max_price = django_filters.NumberFilter(lookup_type='lte') + min_price = django_filters.NumberFilter(name="price", lookup_type='gte') + max_price = django_filters.NumberFilter(name="price", lookup_type='lte') class Meta: model = Product fields = ['category', 'in_stock', 'min_price', 'max_price'] @@ -176,12 +176,51 @@ For more advanced filtering requirements you can specify a `FilterSet` class tha serializer_class = ProductSerializer filter_class = ProductFilter + Which will allow you to make requests such as: http://example.com/api/products?category=clothing&max_price=10.00 For more details on using filter sets see the [django-filter documentation][django-filter-docs]. +You can also span relationships using `django-filter`, let's assume that each +product has foreign key to `Manufacturer` model, so we create filter that +filters using `Manufacturer` name. For example: + + import django_filters + from myapp.models import Product + from myapp.serializers import ProductSerializer + from rest_framework import generics + + class ProductFilter(django_filters.FilterSet): + class Meta: + model = Product + fields = ['category', 'in_stock', 'manufacturer__name`] + +This enables us to make queries like: + + http://example.com/api/products?manufacturer__name=foo + +This is nice, but it shows underlying model structure in REST API, which may +be undesired, but you can use: + + import django_filters + from myapp.models import Product + from myapp.serializers import ProductSerializer + from rest_framework import generics + + class ProductFilter(django_filters.FilterSet): + + manufacturer = django_filters.CharFilter(name="manufacturer__name") + + class Meta: + model = Product + fields = ['category', 'in_stock', 'manufacturer`] + +And now you can execute: + + http://example.com/api/products?manufacturer=foo + --- **Hints & Tips** -- cgit v1.2.3 From cc9c7cd8a479b7fa76a66b8669e4a62fd78be867 Mon Sep 17 00:00:00 2001 From: Jacek Bzdak Date: Tue, 22 Oct 2013 13:15:48 +0200 Subject: Small documentation fix --- docs/api-guide/filtering.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index bcb0bb41..a0132ffc 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -181,8 +181,6 @@ Which will allow you to make requests such as: http://example.com/api/products?category=clothing&max_price=10.00 -For more details on using filter sets see the [django-filter documentation][django-filter-docs]. - You can also span relationships using `django-filter`, let's assume that each product has foreign key to `Manufacturer` model, so we create filter that filters using `Manufacturer` name. For example: @@ -220,6 +218,8 @@ be undesired, but you can use: And now you can execute: http://example.com/api/products?manufacturer=foo + +For more details on using filter sets see the [django-filter documentation][django-filter-docs]. --- -- cgit v1.2.3 From f92d8bd9721d788e3017c16fb285189c88112a46 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 22 Oct 2013 12:21:26 +0100 Subject: Added @jbzdak, for the nice docs improvements in #1191. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index cd3b3710..bcf77b03 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -174,6 +174,7 @@ The following people have helped make REST framework great. * Thomas Badaud - [badale] * Colin Huang - [tamakisquare] * Ross McFarland - [ross] +* Jacek Bzdak - [jbzdak] Many thanks to everyone who's contributed to the project. @@ -384,3 +385,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [badale]: https://github.com/badale [tamakisquare]: https://github.com/tamakisquare [ross]: https://github.com/ross +[jbzdak]: https://github.com/jbzdak -- cgit v1.2.3 From 6b3500b684a43fb67c42231859fa27cf5193298a Mon Sep 17 00:00:00 2001 From: alexanderlukanin13 Date: Thu, 24 Oct 2013 17:52:52 +0600 Subject: Fixed UnicodeEncodeError when POST JSON via web interface; added test --- rest_framework/request.py | 2 +- rest_framework/tests/test_request.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/rest_framework/request.py b/rest_framework/request.py index 977d4d96..b883d0d4 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -334,7 +334,7 @@ class Request(object): self._CONTENT_PARAM in self._data and self._CONTENTTYPE_PARAM in self._data): self._content_type = self._data[self._CONTENTTYPE_PARAM] - self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(HTTP_HEADER_ENCODING)) + self._stream = BytesIO(self._data[self._CONTENT_PARAM].encode(self.parser_context['encoding'])) self._data, self._files = (Empty, Empty) def _parse(self): diff --git a/rest_framework/tests/test_request.py b/rest_framework/tests/test_request.py index 969d8024..a60e7615 100644 --- a/rest_framework/tests/test_request.py +++ b/rest_framework/tests/test_request.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals from django.contrib.auth.models import User from django.contrib.auth import authenticate, login, logout from django.contrib.sessions.middleware import SessionMiddleware +from django.core.handlers.wsgi import WSGIRequest from django.test import TestCase from rest_framework import status from rest_framework.authentication import SessionAuthentication @@ -15,12 +16,13 @@ from rest_framework.parsers import ( MultiPartParser, JSONParser ) -from rest_framework.request import Request +from rest_framework.request import Request, Empty from rest_framework.response import Response from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory, APIClient from rest_framework.views import APIView from rest_framework.compat import six +from io import BytesIO import json @@ -146,6 +148,34 @@ class TestContentParsing(TestCase): request.parsers = (JSONParser(), ) self.assertEqual(request.DATA, json_data) + def test_form_POST_unicode(self): + """ + JSON POST via default web interface with unicode data + """ + # Note: environ and other variables here have simplified content compared to real Request + CONTENT = b'_content_type=application%2Fjson&_content=%7B%22request%22%3A+4%2C+%22firm%22%3A+1%2C+%22text%22%3A+%22%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%21%22%7D' + environ = { + 'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'application/x-www-form-urlencoded', + 'CONTENT_LENGTH': len(CONTENT), + 'wsgi.input': BytesIO(CONTENT), + } + wsgi_request = WSGIRequest(environ=environ) + wsgi_request._load_post_and_files() + parsers = (JSONParser(), FormParser(), MultiPartParser()) + parser_context = { + 'encoding': 'utf-8', + 'kwargs': {}, + 'args': (), + } + request = Request(wsgi_request, parsers=parsers, parser_context=parser_context) + method = request.method + self.assertEqual(method, 'POST') + self.assertEqual(request._content_type, 'application/json') + self.assertEqual(request._stream.getvalue(), b'{"request": 4, "firm": 1, "text": "\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82!"}') + self.assertEqual(request._data, Empty) + self.assertEqual(request._files, Empty) + # def test_accessing_post_after_data_form(self): # """ # Ensures request.POST can be accessed after request.DATA in -- cgit v1.2.3 From 63023078856e78fa043df96378137fd7acc2c1de Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 24 Oct 2013 13:45:16 +0100 Subject: Update comment in `get_parser_context`. --- rest_framework/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/views.py b/rest_framework/views.py index 853e6461..e863af6d 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -154,8 +154,8 @@ class APIView(View): Returns a dict that is passed through to Parser.parse(), as the `parser_context` keyword argument. """ - # Note: Additionally `request` will also be added to the context - # by the Request object. + # Note: Additionally `request` and `encoding` will also be added + # to the context by the Request object. return { 'view': self, 'args': getattr(self, 'args', ()), -- cgit v1.2.3 From 4d894fd39ec5670e72756c26908468fd743354c6 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 24 Oct 2013 13:50:05 +0100 Subject: Added @alexanderlukanin13 for fix #1198. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index bcf77b03..028dfffc 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -175,6 +175,7 @@ The following people have helped make REST framework great. * Colin Huang - [tamakisquare] * Ross McFarland - [ross] * Jacek Bzdak - [jbzdak] +* Alexander Lukanin - [alexanderlukanin13] Many thanks to everyone who's contributed to the project. @@ -386,3 +387,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [tamakisquare]: https://github.com/tamakisquare [ross]: https://github.com/ross [jbzdak]: https://github.com/jbzdak +[alexanderlukanin13]: https://github.com/alexanderlukanin13 -- cgit v1.2.3 From c92af2b1dd25acebe440f667ede3bad4906b9b28 Mon Sep 17 00:00:00 2001 From: Yamila Date: Thu, 24 Oct 2013 15:56:53 +0200 Subject: Typo on generic-views.md --- docs/api-guide/generic-views.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 24fc0bc7..9681c8c7 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -125,7 +125,7 @@ Note that if your API doesn't include any object level permissions, you may opti Returns the class that should be used for the serializer. Defaults to returning the `serializer_class` attribute, or dynamically generating a serializer class if the `model` shortcut is being used. -May be override to provide dynamic behavior such as using different serializers for read and write operations, or providing different serializers to different types of uesr. +May be override to provide dynamic behavior such as using different serializers for read and write operations, or providing different serializers to different types of users. For example: -- cgit v1.2.3 From 82e9ddcf7a5cb5fda81e84326bb6f8181ccdffab Mon Sep 17 00:00:00 2001 From: Yamila Moreno Date: Thu, 24 Oct 2013 15:39:02 +0200 Subject: Added get_filter_backends method --- docs/api-guide/generic-views.md | 20 ++++++++++++++++++-- rest_framework/generics.py | 12 +++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 24fc0bc7..8fedcdaa 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -121,6 +121,22 @@ For example: Note that if your API doesn't include any object level permissions, you may optionally exclude the ``self.check_object_permissions, and simply return the object from the `get_object_or_404` lookup. +#### `get_filter_backends(self)` + +Returns the classes that should be used to filter the queryset. Defaults to returning the `filter_backends` attribute. + +May be override to provide more complex behavior with filters, as using different (or even exlusive) lists of filter_backends depending on different criteria. + +For example: + + def get_filter_backends(self): + if "geo_route" in self.request.QUERY_PARAMS: + return (GeoRouteFilter, CategoryFilter) + elif "geo_point" in self.request.QUERY_PARAMS: + return (GeoPointFilter, CategoryFilter) + + return (CategoryFilter,) + #### `get_serializer_class(self)` Returns the class that should be used for the serializer. Defaults to returning the `serializer_class` attribute, or dynamically generating a serializer class if the `model` shortcut is being used. @@ -328,7 +344,7 @@ You can then simply apply this mixin to a view or viewset anytime you need to ap serializer_class = UserSerializer lookup_fields = ('account', 'username') -Using custom mixins is a good option if you have custom behavior that needs to be used +Using custom mixins is a good option if you have custom behavior that needs to be used ## Creating custom base classes @@ -337,7 +353,7 @@ If you are using a mixin across multiple views, you can take this a step further class BaseRetrieveView(MultipleFieldLookupMixin, generics.RetrieveAPIView): pass - + class BaseRetrieveUpdateDestroyView(MultipleFieldLookupMixin, generics.RetrieveUpdateDestroyAPIView): pass diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 6d204cf5..7cb80a84 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -175,6 +175,14 @@ class GenericAPIView(views.APIView): method if you want to apply the configured filtering backend to the default queryset. """ + for backend in self.get_filter_backends(): + queryset = backend().filter_queryset(self.request, queryset, self) + return queryset + + def get_filter_backends(self): + """ + Returns the list of filter backends that this view requires. + """ filter_backends = self.filter_backends or [] if not filter_backends and self.filter_backend: warnings.warn( @@ -185,10 +193,8 @@ class GenericAPIView(views.APIView): PendingDeprecationWarning, stacklevel=2 ) filter_backends = [self.filter_backend] + return filter_backends - for backend in filter_backends: - queryset = backend().filter_queryset(self.request, queryset, self) - return queryset ######################## ### The following methods provide default implementations -- cgit v1.2.3 From 2ddf7869e3ce57d056c5b0546154c7bbe524cc09 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 24 Oct 2013 15:29:40 +0100 Subject: Added @yamila-moreno for work on #1199. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 028dfffc..e6de1dbe 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -176,6 +176,7 @@ The following people have helped make REST framework great. * Ross McFarland - [ross] * Jacek Bzdak - [jbzdak] * Alexander Lukanin - [alexanderlukanin13] +* Yamila Moreno - [yamila-moreno] Many thanks to everyone who's contributed to the project. @@ -388,3 +389,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [ross]: https://github.com/ross [jbzdak]: https://github.com/jbzdak [alexanderlukanin13]: https://github.com/alexanderlukanin13 +[yamila-moreno]: https://github.com/yamila-moreno -- cgit v1.2.3 From be55a3c5c7f0c573129903e29b7c9dfc02dd5958 Mon Sep 17 00:00:00 2001 From: Jakub Roztočil Date: Thu, 24 Oct 2013 17:53:02 +0200 Subject: Removed commented-out credits from footer to make django-debug-toolbar work. The comment, although valid, caused that the Django debug toolbar's injected HTML was partially commented-out and thus the toolbar didn't work as expected.--- rest_framework/templates/rest_framework/base.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 7ab17dff..495163b6 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -221,9 +221,6 @@ {% block footer %} - {% endblock %} {% block script %} -- cgit v1.2.3 From 7d5499bcac379a506f78fc0065ebe31c8d01240f Mon Sep 17 00:00:00 2001 From: Kit Randel Date: Fri, 25 Oct 2013 11:45:33 +1300 Subject: In the API test client example 'data' was not defined. There's also no need to define 'expected' as we can just test against the dict. --- docs/api-guide/testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/testing.md b/docs/api-guide/testing.md index 35c1f766..4a8a9168 100644 --- a/docs/api-guide/testing.md +++ b/docs/api-guide/testing.md @@ -205,10 +205,10 @@ You can use any of REST framework's test case classes as you would for the regul Ensure we can create a new account object. """ url = reverse('account-list') - expected = {'name': 'DabApps'} + data = {'name': 'DabApps'} response = self.client.post(url, data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(response.data, expected) + self.assertEqual(response.data, data) --- -- cgit v1.2.3 From 458af921f36cec48ff6c27f4824d69f1aafcd18e Mon Sep 17 00:00:00 2001 From: S. Andrew Sheppard Date: Tue, 29 Oct 2013 15:10:06 -0500 Subject: minor typo --- rest_framework/viewsets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index d91323f2..7eb29f99 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -9,7 +9,7 @@ Actions are only bound to methods at the point of instantiating the views. user_detail = UserViewSet.as_view({'get': 'retrieve'}) Typically, rather than instantiate views from viewsets directly, you'll -regsiter the viewset with a router and let the URL conf be determined +register the viewset with a router and let the URL conf be determined automatically. router = DefaultRouter() -- cgit v1.2.3 From f72488d60915f2f77234bc75ccfd604cc6a4143f Mon Sep 17 00:00:00 2001 From: erkarl Date: Thu, 31 Oct 2013 03:47:23 +0200 Subject: Updated OAuth2 authentication docs. --- docs/api-guide/authentication.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 7caeac1e..1a1c68b8 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -265,6 +265,12 @@ This authentication class depends on the optional [django-oauth2-provider][djang 'provider.oauth2', ) +Then add `OAuth2Authentication` to your global `DEFAULT_AUTHENTICATION` setting: + + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework.authentication.OAuth2Authentication', + ), + You must also include the following in your root `urls.py` module: url(r'^oauth2/', include('provider.oauth2.urls', namespace='oauth2')), -- cgit v1.2.3 From e33435d0da0dba13fae39070b3d87ad8af47862f Mon Sep 17 00:00:00 2001 From: Rob Hudson Date: Thu, 31 Oct 2013 15:03:50 -0700 Subject: Fixed exception handling with YAML and XML parsers. --- rest_framework/parsers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 98fc0341..f1b3e38d 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -83,7 +83,7 @@ class YAMLParser(BaseParser): data = stream.read().decode(encoding) return yaml.safe_load(data) except (ValueError, yaml.parser.ParserError) as exc: - raise ParseError('YAML parse error - %s' % six.u(exc)) + raise ParseError('YAML parse error - %s' % six.text_type(exc)) class FormParser(BaseParser): @@ -153,7 +153,7 @@ class XMLParser(BaseParser): try: tree = etree.parse(stream, parser=parser, forbid_dtd=True) except (etree.ParseError, ValueError) as exc: - raise ParseError('XML parse error - %s' % six.u(exc)) + raise ParseError('XML parse error - %s' % six.text_type(exc)) data = self._xml_convert(tree.getroot()) return data -- cgit v1.2.3 From 5e0538fcda75eed82ed183c7c4dcfcda5ad35d5f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Sat, 2 Nov 2013 09:15:35 +0000 Subject: Added @robhudson for work on #1211. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index e6de1dbe..9751850b 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -177,6 +177,7 @@ The following people have helped make REST framework great. * Jacek Bzdak - [jbzdak] * Alexander Lukanin - [alexanderlukanin13] * Yamila Moreno - [yamila-moreno] +* Rob Hudson - [robhudson] Many thanks to everyone who's contributed to the project. @@ -390,3 +391,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [jbzdak]: https://github.com/jbzdak [alexanderlukanin13]: https://github.com/alexanderlukanin13 [yamila-moreno]: https://github.com/yamila-moreno +[robhudson]: https://github.com/robhudson -- cgit v1.2.3 From 53258908210b1eabd0ee204653a589d6579ac772 Mon Sep 17 00:00:00 2001 From: Mathieu Pillard Date: Tue, 5 Nov 2013 17:21:18 +0100 Subject: Improve handling of 'empty' values for ChoiceField The empty value defaults back to '' (for backwards-compatibility) but is changed automatically to None for ModelSerializers if the `null` property is set on the db field. --- rest_framework/fields.py | 8 ++-- rest_framework/serializers.py | 2 + rest_framework/tests/test_fields.py | 74 +++++++++++++++++++++++++++++-------- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index e23fc001..6c07dbb3 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -497,6 +497,7 @@ class ChoiceField(WritableField): } def __init__(self, choices=(), *args, **kwargs): + self.empty = kwargs.pop('empty', '') super(ChoiceField, self).__init__(*args, **kwargs) self.choices = choices if not self.required: @@ -537,9 +538,10 @@ class ChoiceField(WritableField): return False def from_native(self, value): - if value in validators.EMPTY_VALUES: - return None - return super(ChoiceField, self).from_native(value) + value = super(ChoiceField, self).from_native(value) + if value == self.empty or value in validators.EMPTY_VALUES: + return self.empty + return value class EmailField(CharField): diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 4210d058..5240dbf6 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -794,6 +794,8 @@ class ModelSerializer(Serializer): # TODO: TypedChoiceField? if model_field.flatchoices: # This ModelField contains choices kwargs['choices'] = model_field.flatchoices + if model_field.null: + kwargs['empty'] = None return ChoiceField(**kwargs) # put this below the ChoiceField because min_value isn't a valid initializer diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py index 34fbab9c..333476ba 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -42,6 +42,31 @@ class TimeFieldModelSerializer(serializers.ModelSerializer): model = TimeFieldModel +SAMPLE_CHOICES = [ + ('red', 'Red'), + ('green', 'Green'), + ('blue', 'Blue'), +] + + +class ChoiceFieldModel(models.Model): + choice = models.CharField(choices=SAMPLE_CHOICES, blank=True, max_length=255) + + +class ChoiceFieldModelSerializer(serializers.ModelSerializer): + class Meta: + model = ChoiceFieldModel + + +class ChoiceFieldModelWithNull(models.Model): + choice = models.CharField(choices=SAMPLE_CHOICES, blank=True, null=True, max_length=255) + + +class ChoiceFieldModelWithNullSerializer(serializers.ModelSerializer): + class Meta: + model = ChoiceFieldModelWithNull + + class BasicFieldTests(TestCase): def test_auto_now_fields_read_only(self): """ @@ -667,34 +692,53 @@ class ChoiceFieldTests(TestCase): """ Tests for the ChoiceField options generator """ - - SAMPLE_CHOICES = [ - ('red', 'Red'), - ('green', 'Green'), - ('blue', 'Blue'), - ] - def test_choices_required(self): """ Make sure proper choices are rendered if field is required """ - f = serializers.ChoiceField(required=True, choices=self.SAMPLE_CHOICES) - self.assertEqual(f.choices, self.SAMPLE_CHOICES) + f = serializers.ChoiceField(required=True, choices=SAMPLE_CHOICES) + self.assertEqual(f.choices, SAMPLE_CHOICES) def test_choices_not_required(self): """ Make sure proper choices (plus blank) are rendered if the field isn't required """ - f = serializers.ChoiceField(required=False, choices=self.SAMPLE_CHOICES) - self.assertEqual(f.choices, models.fields.BLANK_CHOICE_DASH + self.SAMPLE_CHOICES) + f = serializers.ChoiceField(required=False, choices=SAMPLE_CHOICES) + self.assertEqual(f.choices, models.fields.BLANK_CHOICE_DASH + SAMPLE_CHOICES) + + def test_invalid_choice_model(self): + s = ChoiceFieldModelSerializer(data={'choice' : 'wrong_value'}) + self.assertFalse(s.is_valid()) + self.assertEqual(s.errors, {'choice': [u'Select a valid choice. wrong_value is not one of the available choices.']}) + self.assertEqual(s.data['choice'], '') + + def test_empty_choice_model(self): + """ + Test that the 'empty' value is correctly passed and used depending on the 'null' property on the model field. + """ + s = ChoiceFieldModelSerializer(data={'choice' : ''}) + self.assertTrue(s.is_valid()) + self.assertEqual(s.data['choice'], '') + + s = ChoiceFieldModelWithNullSerializer(data={'choice' : ''}) + self.assertTrue(s.is_valid()) + self.assertEqual(s.data['choice'], None) def test_from_native_empty(self): """ - Make sure from_native() returns None on empty param. + Make sure from_native() returns an empty string on empty param by default. """ - f = serializers.ChoiceField(choices=self.SAMPLE_CHOICES) - result = f.from_native('') - self.assertEqual(result, None) + f = serializers.ChoiceField(choices=SAMPLE_CHOICES) + self.assertEqual(f.from_native(''), '') + self.assertEqual(f.from_native(None), '') + + def test_from_native_empty_override(self): + """ + Make sure you can override from_native() behavior regarding empty values. + """ + f = serializers.ChoiceField(choices=SAMPLE_CHOICES, empty=None) + self.assertEqual(f.from_native(''), None) + self.assertEqual(f.from_native(None), None) class EmailFieldTests(TestCase): -- cgit v1.2.3 From 5829eb7a5b0d45fe668d7ce1ad394a7b5966c70d Mon Sep 17 00:00:00 2001 From: Mathieu Pillard Date: Wed, 6 Nov 2013 12:51:40 +0100 Subject: Drop u'' prefix for python 3.x compatibility --- rest_framework/tests/test_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_fields.py b/rest_framework/tests/test_fields.py index 333476ba..ab2cceac 100644 --- a/rest_framework/tests/test_fields.py +++ b/rest_framework/tests/test_fields.py @@ -709,7 +709,7 @@ class ChoiceFieldTests(TestCase): def test_invalid_choice_model(self): s = ChoiceFieldModelSerializer(data={'choice' : 'wrong_value'}) self.assertFalse(s.is_valid()) - self.assertEqual(s.errors, {'choice': [u'Select a valid choice. wrong_value is not one of the available choices.']}) + self.assertEqual(s.errors, {'choice': ['Select a valid choice. wrong_value is not one of the available choices.']}) self.assertEqual(s.data['choice'], '') def test_empty_choice_model(self): -- cgit v1.2.3 From 1296753b5c55de19e057eb6fde73da2b41f7032b Mon Sep 17 00:00:00 2001 From: Bruno Renié Date: Fri, 8 Nov 2013 11:58:14 +0100 Subject: Updated versions in tox and travis config --- .travis.yml | 28 ++++++++++++++-------------- tox.ini | 20 ++++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index d12479e9..386c1d64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,20 +7,20 @@ python: - "3.3" env: - - DJANGO="https://www.djangoproject.com/download/1.6a1/tarball/" - - DJANGO="django==1.5.1 --use-mirrors" - - DJANGO="django==1.4.5 --use-mirrors" - - DJANGO="django==1.3.7 --use-mirrors" + - DJANGO="django==1.6" + - DJANGO="django==1.5.5" + - DJANGO="django==1.4.10" + - DJANGO="django==1.3.7" install: - pip install $DJANGO - pip install defusedxml==0.3 - - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install oauth2==1.5.211 --use-mirrors; fi" - - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth-plus==2.0 --use-mirrors; fi" - - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth2-provider==0.2.4 --use-mirrors; fi" - - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-guardian==1.1.1 --use-mirrors; fi" - - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install django-filter==0.5.4 --use-mirrors; fi" - - "if [[ ${DJANGO::11} != 'django==1.3' ]]; then pip install django-filter==0.6 --use-mirrors; fi" + - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install oauth2==1.5.211; fi" + - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth-plus==2.0; fi" + - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-oauth2-provider==0.2.4; fi" + - "if [[ ${TRAVIS_PYTHON_VERSION::1} != '3' ]]; then pip install django-guardian==1.1.1; fi" + - "if [[ ${DJANGO::11} == 'django==1.3' ]]; then pip install django-filter==0.5.4; fi" + - "if [[ ${DJANGO::11} != 'django==1.3' ]]; then pip install django-filter==0.6; fi" - export PYTHONPATH=. script: @@ -29,11 +29,11 @@ script: matrix: exclude: - python: "3.2" - env: DJANGO="django==1.4.5 --use-mirrors" + env: DJANGO="django==1.4.5" - python: "3.2" - env: DJANGO="django==1.3.7 --use-mirrors" + env: DJANGO="django==1.3.7" - python: "3.3" - env: DJANGO="django==1.4.5 --use-mirrors" + env: DJANGO="django==1.4.5" - python: "3.3" - env: DJANGO="django==1.3.7 --use-mirrors" + env: DJANGO="django==1.3.7" diff --git a/tox.ini b/tox.ini index 6e3b8e0a..1fa0a958 100644 --- a/tox.ini +++ b/tox.ini @@ -7,19 +7,19 @@ commands = {envpython} rest_framework/runtests/runtests.py [testenv:py3.3-django1.6] basepython = python3.3 -deps = https://www.djangoproject.com/download/1.6a1/tarball/ +deps = Django==1.6 django-filter==0.6a1 defusedxml==0.3 [testenv:py3.2-django1.6] basepython = python3.2 -deps = https://www.djangoproject.com/download/1.6a1/tarball/ +deps = Django==1.6 django-filter==0.6a1 defusedxml==0.3 [testenv:py2.7-django1.6] basepython = python2.7 -deps = https://www.djangoproject.com/download/1.6a1/tarball/ +deps = Django==1.6 django-filter==0.6a1 defusedxml==0.3 django-oauth-plus==2.0 @@ -29,7 +29,7 @@ deps = https://www.djangoproject.com/download/1.6a1/tarball/ [testenv:py2.6-django1.6] basepython = python2.6 -deps = https://www.djangoproject.com/download/1.6a1/tarball/ +deps = Django==1.6 django-filter==0.6a1 defusedxml==0.3 django-oauth-plus==2.0 @@ -39,19 +39,19 @@ deps = https://www.djangoproject.com/download/1.6a1/tarball/ [testenv:py3.3-django1.5] basepython = python3.3 -deps = django==1.5 +deps = django==1.5.5 django-filter==0.6a1 defusedxml==0.3 [testenv:py3.2-django1.5] basepython = python3.2 -deps = django==1.5 +deps = django==1.5.5 django-filter==0.6a1 defusedxml==0.3 [testenv:py2.7-django1.5] basepython = python2.7 -deps = django==1.5 +deps = django==1.5.5 django-filter==0.6a1 defusedxml==0.3 django-oauth-plus==2.0 @@ -61,7 +61,7 @@ deps = django==1.5 [testenv:py2.6-django1.5] basepython = python2.6 -deps = django==1.5 +deps = django==1.5.5 django-filter==0.6a1 defusedxml==0.3 django-oauth-plus==2.0 @@ -71,7 +71,7 @@ deps = django==1.5 [testenv:py2.7-django1.4] basepython = python2.7 -deps = django==1.4.3 +deps = django==1.4.10 django-filter==0.6a1 defusedxml==0.3 django-oauth-plus==2.0 @@ -81,7 +81,7 @@ deps = django==1.4.3 [testenv:py2.6-django1.4] basepython = python2.6 -deps = django==1.4.3 +deps = django==1.4.10 django-filter==0.6a1 defusedxml==0.3 django-oauth-plus==2.0 -- cgit v1.2.3 From d4a50429b098656e7a0855c6acf12f0aa4bc434f Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Fri, 8 Nov 2013 13:12:40 +0100 Subject: Fixed a regression with ValidationError under Django 1.6 --- rest_framework/serializers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 5240dbf6..7cdb55c8 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -42,6 +42,7 @@ def pretty_name(name): class RelationsList(list): _deleted = [] + class NestedValidationError(ValidationError): """ The default ValidationError behavior is to stringify each item in the list @@ -56,9 +57,13 @@ class NestedValidationError(ValidationError): def __init__(self, message): if isinstance(message, dict): - self.messages = [message] + self._messages = [message] else: - self.messages = message + self._messages = message + + @property + def messages(self): + return self._messages class DictWithMetadata(dict): -- cgit v1.2.3 From b7b57adee2cc5a785a5df492424969c8ba311aa8 Mon Sep 17 00:00:00 2001 From: Ben Pietravalle Date: Fri, 8 Nov 2013 13:19:40 +0000 Subject: Fix object creation with reverse M2M when related_name unspecified It seems that field.related_query_name() does not return the related_name for reverse M2M relations when related_name is not explicitly set in the M2M field definition. So, change to use obj.get_accessor_name(), where obj is an instance of RelatedObject, as are returned by a model's _meta.get_all_related_many_to_many_objects(), or as in the tuples returned by _meta.get_all_m2m_objects_with_model(). --- 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 5240dbf6..244fbe63 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -873,7 +873,7 @@ class ModelSerializer(Serializer): # Reverse m2m relations for (obj, model) in meta.get_all_related_m2m_objects_with_model(): - field_name = obj.field.related_query_name() + field_name = obj.get_accessor_name() if field_name in attrs: m2m_data[field_name] = attrs.pop(field_name) -- cgit v1.2.3 From f2ea5780d2d6e783019bceb73c79bb9445e99e0b Mon Sep 17 00:00:00 2001 From: Bruno Renié Date: Fri, 8 Nov 2013 14:58:36 +0100 Subject: Exclude 1.4 on python 3 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 386c1d64..bcf1bae0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,11 +29,11 @@ script: matrix: exclude: - python: "3.2" - env: DJANGO="django==1.4.5" + env: DJANGO="django==1.4.10" - python: "3.2" env: DJANGO="django==1.3.7" - python: "3.3" - env: DJANGO="django==1.4.5" + env: DJANGO="django==1.4.10" - python: "3.3" env: DJANGO="django==1.3.7" -- cgit v1.2.3 From fd2c291c4d9243937a31e0e6f523016067824b83 Mon Sep 17 00:00:00 2001 From: Doğan Çeçen Date: Mon, 11 Nov 2013 11:54:30 +0200 Subject: Typo on api-guide/fields.md and serializers.py --- docs/api-guide/fields.md | 4 ++-- rest_framework/serializers.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 962c49e2..4272c9a7 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -299,9 +299,9 @@ Django's regular [FILE_UPLOAD_HANDLERS] are used for handling uploaded files. # Custom fields -If you want to create a custom field, you'll probably want to override either one or both of the `.to_native()` and `.from_native()` methods. These two methods are used to convert between the initial datatype, and a primative, serializable datatype. Primative datatypes may be any of a number, string, date/time/datetime or None. They may also be any list or dictionary like object that only contains other primative objects. +If you want to create a custom field, you'll probably want to override either one or both of the `.to_native()` and `.from_native()` methods. These two methods are used to convert between the initial datatype, and a primitive, serializable datatype. Primitive datatypes may be any of a number, string, date/time/datetime or None. They may also be any list or dictionary like object that only contains other primitive objects. -The `.to_native()` method is called to convert the initial datatype into a primative, serializable datatype. The `from_native()` method is called to restore a primative datatype into it's initial representation. +The `.to_native()` method is called to convert the initial datatype into a primitive, serializable datatype. The `from_native()` method is called to restore a primitive datatype into it's initial representation. ## Examples diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 16095452..163abf4f 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -6,8 +6,8 @@ form encoded input. Serialization in REST framework is a two-phase process: 1. Serializers marshal between complex types like model instances, and -python primatives. -2. The process of marshalling between python primatives and request and +python primitives. +2. The process of marshalling between python primitives and request and response content is handled by parsers and renderers. """ from __future__ import unicode_literals -- cgit v1.2.3 From 52ac2199a8b332f7a485d5c22b1a53633b4be9dd Mon Sep 17 00:00:00 2001 From: Jacob Haslehurst Date: Mon, 11 Nov 2013 22:24:37 +1100 Subject: Added drf-ujson-renderer to renderers docs drf-ujson-renderer is a third party renderer that implements JSON renderering using UltraJSON--- docs/api-guide/renderers.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index 657377d9..1f286ef1 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -409,6 +409,10 @@ The following third party packages are also available. Comma-separated values are a plain-text tabular data format, that can be easily imported into spreadsheet applications. [Mjumbe Poe][mjumbewu] maintains the [djangorestframework-csv][djangorestframework-csv] package which provides CSV renderer support for REST framework. +## UltraJSON + +[UltraJSON][ultrajson] is a blazing-fast C JSON encoder which can give 2-10x performance increases on typical workloads. [Jacob Haslehurst][hzy] maintains the [drf-ujson-renderer][drf-ujson-renderer] package which implements JSON rendering using the UJSON package. + [cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process [conneg]: content-negotiation.md [browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers @@ -426,3 +430,6 @@ Comma-separated values are a plain-text tabular data format, that can be easily [mjumbewu]: https://github.com/mjumbewu [djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack [djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv +[ultrajson]: https://github.com/esnme/ultrajson +[hzy]: https://github.com/hzy +[drf-ujson-renderer]: https://github.com/gizmag/drf-ujson-renderer -- cgit v1.2.3