From d48ba1cff76ffceb1d700e9e0c6ccf518a6382da Mon Sep 17 00:00:00 2001 From: Andrey Kaygorodov Date: Wed, 5 Feb 2014 05:47:27 +0800 Subject: [PATCH 01/23] turn of pagination --- docs/api-guide/pagination.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index 0829589f8..f86e6ce11 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -102,7 +102,7 @@ You can also set the pagination style on a per-view basis, using the `ListAPIVie paginate_by_param = 'page_size' max_paginate_by = 100 -Note that using a `paginate_by` value of `None` will turn off pagination for the view. +Note that using a `paginate_by` value of `None` will turn off pagination for the view. But if you specified `PAGINATE_BY` and `PAGINATE_BY_PARAM` in your settings file then you have to set both `paginate_by` and `paginate_by_param` to a `None` value in order to turn off pagination for the view. For more complex requirements such as serialization that differs depending on the requested media type you can override the `.get_paginate_by()` and `.get_pagination_serializer_class()` methods. From 2d20512d259f51a5a5c2b71b20f98d24e0176f16 Mon Sep 17 00:00:00 2001 From: Andrey Kaygorodov Date: Wed, 5 Feb 2014 21:10:51 +0800 Subject: [PATCH 02/23] #1390, docs, turning of pagination --- docs/api-guide/pagination.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index f86e6ce11..047a09883 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -102,7 +102,8 @@ You can also set the pagination style on a per-view basis, using the `ListAPIVie paginate_by_param = 'page_size' max_paginate_by = 100 -Note that using a `paginate_by` value of `None` will turn off pagination for the view. But if you specified `PAGINATE_BY` and `PAGINATE_BY_PARAM` in your settings file then you have to set both `paginate_by` and `paginate_by_param` to a `None` value in order to turn off pagination for the view. +Note that using a `paginate_by` value of `None` will turn off pagination for the view. +Note if you use the `PAGINATE_BY_PARAM` settings, you also have to set the `paginate_by_param` attribute in your view to `None` in order to turn off pagination for those requests that contain the `paginate_by_param` parameter. For more complex requirements such as serialization that differs depending on the requested media type you can override the `.get_paginate_by()` and `.get_pagination_serializer_class()` methods. From a23059b6f73aaff9709f611826bac892e56663dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Wed, 9 Apr 2014 23:35:41 +0200 Subject: [PATCH 03/23] Add more TRAILING_PUNCTUATION to work with YAML. Fixes #1517 --- rest_framework/templatetags/rest_framework.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index beb8c5b0e..dff176d62 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -180,7 +180,7 @@ def add_class(value, css_class): # Bunch of stuff cloned from urlize -TRAILING_PUNCTUATION = ['.', ',', ':', ';', '.)', '"', "'"] +TRAILING_PUNCTUATION = ['.', ',', ':', ';', '.)', '"', "']", "'}", "'"] WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('[', ']'), ('<', '>'), ('"', '"'), ("'", "'")] word_split_re = re.compile(r'(\s+)') From 7ae8409370635ccec7d3c160ea87281f21c9ae11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Thu, 10 Apr 2014 01:35:45 +0200 Subject: [PATCH 04/23] Allow unicode YAML dump with UnicodeYAMLRenderer Fixes #1519 --- rest_framework/renderers.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 7a7da5610..484961add 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -193,6 +193,7 @@ class YAMLRenderer(BaseRenderer): format = 'yaml' encoder = encoders.SafeDumper charset = 'utf-8' + ensure_ascii = True def render(self, data, accepted_media_type=None, renderer_context=None): """ @@ -203,7 +204,15 @@ class YAMLRenderer(BaseRenderer): if data is None: return '' - return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder) + return yaml.dump(data, stream=None, encoding=self.charset, Dumper=self.encoder, allow_unicode=not self.ensure_ascii) + + +class UnicodeYAMLRenderer(YAMLRenderer): + """ + Renderer which serializes to YAML. + Does *not* apply character escaping for non-ascii characters. + """ + ensure_ascii = False class TemplateHTMLRenderer(BaseRenderer): From f68596a7326777f971d9551ff1bfc7176389ea22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Thu, 10 Apr 2014 01:58:06 +0200 Subject: [PATCH 05/23] Document new UnicodeYAMLRenderer --- docs/api-guide/renderers.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index 7798827bc..7a3429bfd 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -138,6 +138,26 @@ Renders the request data into `YAML`. Requires the `pyyaml` package to be installed. +Note that non-ascii characters will be rendered using `\uXXXX` character escape. For example: + + unicode black star: "\u2605" + +**.media_type**: `application/yaml` + +**.format**: `'.yaml'` + +**.charset**: `utf-8` + +## UnicodeYAMLRenderer + +Renders the request data into `YAML`. + +Requires the `pyyaml` package to be installed. + +Note that non-ascii characters will not be character escaped. For example: + + unicode black star: ★ + **.media_type**: `application/yaml` **.format**: `'.yaml'` From 0a0e4f22e72badd1d8700a2b253cb27450a5319f Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Sat, 12 Apr 2014 17:51:02 +0100 Subject: [PATCH 06/23] Set GenericForeignKey fields on object before save * A model with a required GenericForeignKey can be saved if the field is set --- rest_framework/serializers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index cb7539e0b..1d6097edd 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -16,6 +16,7 @@ import datetime import inspect import types from decimal import Decimal +from django.contrib.contenttypes.generic import GenericForeignKey from django.core.paginator import Page from django.db import models from django.forms import widgets @@ -943,6 +944,8 @@ class ModelSerializer(Serializer): # Forward m2m relations for field in meta.many_to_many + meta.virtual_fields: + if isinstance(field, GenericForeignKey): + continue if field.name in attrs: m2m_data[field.name] = attrs.pop(field.name) From 853c7a16c15c7291561bc4b3dfbcad88ea262a18 Mon Sep 17 00:00:00 2001 From: Ian Foote Date: Sun, 13 Apr 2014 17:26:15 +0100 Subject: [PATCH 07/23] Use setattr for adding fields to a new instance Add test for restoring a GenericForeignKey --- rest_framework/serializers.py | 18 ++++++++---------- rest_framework/tests/test_genericrelations.py | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 1d6097edd..ea9509bf9 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -955,17 +955,15 @@ class ModelSerializer(Serializer): if isinstance(self.fields.get(field_name, None), Serializer): nested_forward_relations[field_name] = attrs[field_name] - # Update an existing instance... - if instance is not None: - for key, val in attrs.items(): - try: - setattr(instance, key, val) - except ValueError: - self._errors[key] = self.error_messages['required'] + # Create an empty instance of the model + if instance is None: + instance = self.opts.model() - # ...or create a new instance - else: - instance = self.opts.model(**attrs) + for key, val in attrs.items(): + try: + setattr(instance, key, val) + except ValueError: + self._errors[key] = self.error_messages['required'] # Any relations that cannot be set until we've # saved the model get hidden away on these diff --git a/rest_framework/tests/test_genericrelations.py b/rest_framework/tests/test_genericrelations.py index fa09c9e6c..46a2d863f 100644 --- a/rest_framework/tests/test_genericrelations.py +++ b/rest_framework/tests/test_genericrelations.py @@ -131,3 +131,21 @@ class TestGenericRelations(TestCase): } ] self.assertEqual(serializer.data, expected) + + def test_restore_object_generic_fk(self): + """ + Ensure an object with a generic foreign key can be restored. + """ + + class TagSerializer(serializers.ModelSerializer): + class Meta: + model = Tag + exclude = ('content_type', 'object_id') + + serializer = TagSerializer() + + bookmark = Bookmark(url='http://example.com') + attrs = {'tagged_item': bookmark, 'tag': 'example'} + + tag = serializer.restore_object(attrs) + self.assertEqual(tag.tagged_item, bookmark) From 4b3eb6e0b0e6412693de126ac92482a276ca9a78 Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 12:21:38 +0400 Subject: [PATCH 08/23] Fixed parse file name --- rest_framework/parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index f1b3e38d4..703cefca8 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -288,7 +288,7 @@ class FileUploadParser(BaseParser): try: meta = parser_context['request'].META - disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION']) + disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) return disposition[1]['filename'] except (AttributeError, KeyError): pass From 063addabfeb716f54c5784917e92ab6abb635ff5 Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 12:28:41 +0400 Subject: [PATCH 09/23] Removed encode from test Django does not produce such a decoding by default, this test was not honest. --- rest_framework/tests/test_parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_parsers.py b/rest_framework/tests/test_parsers.py index 7699e10c9..ffd6b360f 100644 --- a/rest_framework/tests/test_parsers.py +++ b/rest_framework/tests/test_parsers.py @@ -96,7 +96,7 @@ class TestFileUploadParser(TestCase): request = MockRequest() request.upload_handlers = (MemoryFileUploadHandler(),) request.META = { - 'HTTP_CONTENT_DISPOSITION': 'Content-Disposition: inline; filename=file.txt'.encode('utf-8'), + 'HTTP_CONTENT_DISPOSITION': 'Content-Disposition: inline; filename=file.txt', 'HTTP_CONTENT_LENGTH': 14, } self.parser_context = {'request': request, 'kwargs': {}} From d474934d365291c28d5741898257cbdd5d0aa9ec Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 13:01:24 +0400 Subject: [PATCH 10/23] Fixed return type From bytes to str --- rest_framework/parsers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 703cefca8..d49b17a4a 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -10,6 +10,7 @@ from django.core.files.uploadhandler import StopFutureHandlers from django.http import QueryDict from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter +from django.utils.encoding import force_str from rest_framework.compat import etree, six, yaml from rest_framework.exceptions import ParseError from rest_framework import renderers @@ -289,6 +290,6 @@ class FileUploadParser(BaseParser): try: meta = parser_context['request'].META disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) - return disposition[1]['filename'] + return force_str(disposition[1]['filename']) except (AttributeError, KeyError): pass From d1f4dfca2061cb552158ac7ea6f2de609989797b Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 13:04:18 +0400 Subject: [PATCH 11/23] Removed decode from test filename --- rest_framework/tests/test_parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/tests/test_parsers.py b/rest_framework/tests/test_parsers.py index ffd6b360f..8af906772 100644 --- a/rest_framework/tests/test_parsers.py +++ b/rest_framework/tests/test_parsers.py @@ -112,4 +112,4 @@ class TestFileUploadParser(TestCase): def test_get_filename(self): parser = FileUploadParser() filename = parser.get_filename(self.stream, None, self.parser_context) - self.assertEqual(filename, 'file.txt'.encode('utf-8')) + self.assertEqual(filename, 'file.txt') From 3fe038357267f947eba467f2b7714a782fa93c33 Mon Sep 17 00:00:00 2001 From: Vladislav Vlastovskiy Date: Mon, 14 Apr 2014 13:21:24 +0400 Subject: [PATCH 12/23] Fixed convert bytes to str Use compact function for convert --- rest_framework/parsers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index d49b17a4a..4990971b8 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -10,8 +10,7 @@ from django.core.files.uploadhandler import StopFutureHandlers from django.http import QueryDict from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser from django.http.multipartparser import MultiPartParserError, parse_header, ChunkIter -from django.utils.encoding import force_str -from rest_framework.compat import etree, six, yaml +from rest_framework.compat import etree, six, yaml, force_text from rest_framework.exceptions import ParseError from rest_framework import renderers import json @@ -290,6 +289,6 @@ class FileUploadParser(BaseParser): try: meta = parser_context['request'].META disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) - return force_str(disposition[1]['filename']) + return force_text(disposition[1]['filename']) except (AttributeError, KeyError): pass From 617c9825913cfc0cdeaa4405df0b885db0a9ff60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 15 Apr 2014 14:12:09 +0200 Subject: [PATCH 13/23] Add test for UnicodeYAMLRenderer --- rest_framework/tests/test_renderers.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/rest_framework/tests/test_renderers.py b/rest_framework/tests/test_renderers.py index c7bf772ef..7cb7d0f93 100644 --- a/rest_framework/tests/test_renderers.py +++ b/rest_framework/tests/test_renderers.py @@ -12,7 +12,7 @@ from rest_framework.compat import yaml, etree, patterns, url, include, six, Stri from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \ - XMLRenderer, JSONPRenderer, BrowsableAPIRenderer, UnicodeJSONRenderer + XMLRenderer, JSONPRenderer, BrowsableAPIRenderer, UnicodeJSONRenderer, UnicodeYAMLRenderer from rest_framework.parsers import YAMLParser, XMLParser from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory @@ -467,6 +467,17 @@ if yaml: self.assertTrue(string in content, '%r not in %r' % (string, content)) + class UnicodeYAMLRendererTests(TestCase): + """ + Tests specific for the Unicode YAML Renderer + """ + def test_proper_encoding(self): + obj = {'countries': ['United Kingdom', 'France', 'España']} + renderer = UnicodeYAMLRenderer() + content = renderer.render(obj, 'application/yaml') + self.assertEqual(content.strip(), 'countries: [United Kingdom, France, España]'.encode('utf-8')) + + class XMLRendererTestCase(TestCase): """ Tests specific to the XML Renderer From ef1d65282771c806f68d717d57172597184db26c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 15 Apr 2014 14:02:11 +0200 Subject: [PATCH 14/23] Introduce tests for urlize_quoted_links() function --- rest_framework/tests/test_urlizer.py | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 rest_framework/tests/test_urlizer.py diff --git a/rest_framework/tests/test_urlizer.py b/rest_framework/tests/test_urlizer.py new file mode 100644 index 000000000..3dc8e8fe5 --- /dev/null +++ b/rest_framework/tests/test_urlizer.py @@ -0,0 +1,38 @@ +from __future__ import unicode_literals +from django.test import TestCase +from rest_framework.templatetags.rest_framework import urlize_quoted_links +import sys + + +class URLizerTests(TestCase): + """ + Test if both JSON and YAML URLs are transformed into links well + """ + def _urlize_dict_check(self, data): + """ + For all items in dict test assert that the value is urlized key + """ + for original, urlized in data.items(): + assert urlize_quoted_links(original, nofollow=False) == urlized + + def test_json_with_url(self): + """ + Test if JSON URLs are transformed into links well + """ + data = {} + data['"url": "http://api/users/1/", '] = \ + '"url": "http://api/users/1/", ' + data['"foo_set": [\n "http://api/foos/1/"\n], '] = \ + '"foo_set": [\n "http://api/foos/1/"\n], ' + self._urlize_dict_check(data) + + def test_yaml_with_url(self): + """ + Test if YAML URLs are transformed into links well + """ + data = {} + data['''{users: 'http://api/users/'}'''] = \ + '''{users: 'http://api/users/'}''' + data['''foo_set: ['http://api/foos/1/']'''] = \ + '''foo_set: ['http://api/foos/1/']''' + self._urlize_dict_check(data) From a6e525cf3a22a01a4f9924e975ea7288d80ac5ef Mon Sep 17 00:00:00 2001 From: Sergey Sinitsyn Date: Thu, 24 Apr 2014 15:58:53 +0600 Subject: [PATCH 15/23] Add help_text and verbose_name attribute mapping for related field --- rest_framework/serializers.py | 8 ++++++++ rest_framework/tests/models.py | 3 ++- rest_framework/tests/test_serializer.py | 26 ++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ea9509bf9..9cb548a51 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -828,6 +828,10 @@ class ModelSerializer(Serializer): if model_field: kwargs['required'] = not(model_field.null or model_field.blank) + if model_field.help_text is not None: + kwargs['help_text'] = model_field.help_text + if model_field.verbose_name is not None: + kwargs['label'] = model_field.verbose_name return PrimaryKeyRelatedField(**kwargs) @@ -1088,6 +1092,10 @@ class HyperlinkedModelSerializer(ModelSerializer): if model_field: kwargs['required'] = not(model_field.null or model_field.blank) + if model_field.help_text is not None: + kwargs['help_text'] = model_field.help_text + if model_field.verbose_name is not None: + kwargs['label'] = model_field.verbose_name if self.opts.lookup_field: kwargs['lookup_field'] = self.opts.lookup_field diff --git a/rest_framework/tests/models.py b/rest_framework/tests/models.py index 6c8f2342b..0256697a1 100644 --- a/rest_framework/tests/models.py +++ b/rest_framework/tests/models.py @@ -143,7 +143,8 @@ class ForeignKeyTarget(RESTFrameworkModel): class ForeignKeySource(RESTFrameworkModel): name = models.CharField(max_length=100) - target = models.ForeignKey(ForeignKeyTarget, related_name='sources') + target = models.ForeignKey(ForeignKeyTarget, related_name='sources', + help_text='Target', verbose_name='Target') # Nullable ForeignKey diff --git a/rest_framework/tests/test_serializer.py b/rest_framework/tests/test_serializer.py index 3ee2b38a7..e688c8239 100644 --- a/rest_framework/tests/test_serializer.py +++ b/rest_framework/tests/test_serializer.py @@ -9,7 +9,8 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers, fields, relations from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, BlogPostComment, Book, CallableDefaultValueModel, DefaultValueModel, - ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel) + ManyToManyModel, Person, ReadOnlyManyToManyModel, Photo, RESTFrameworkModel, + ForeignKeySource, ManyToManySource) from rest_framework.tests.models import BasicModelSerializer import datetime import pickle @@ -176,6 +177,16 @@ class PositiveIntegerAsChoiceSerializer(serializers.ModelSerializer): fields = ['some_integer'] +class ForeignKeySourceSerializer(serializers.ModelSerializer): + class Meta: + model = ForeignKeySource + + +class HyperlinkedForeignKeySourceSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ForeignKeySource + + class BasicTests(TestCase): def setUp(self): self.comment = Comment( @@ -1600,6 +1611,19 @@ class ManyFieldHelpTextTest(TestCase): self.assertEqual('Some help text.', rel_field.help_text) +class AttributeMappingOnAutogeneratedRelatedFields(TestCase): + + def test_primary_key_related_field(self): + serializer = ForeignKeySourceSerializer() + self.assertEqual(serializer.fields['target'].help_text, 'Target') + self.assertEqual(serializer.fields['target'].label, 'Target') + + def test_hyperlinked_related_field(self): + serializer = HyperlinkedForeignKeySourceSerializer() + self.assertEqual(serializer.fields['target'].help_text, 'Target') + self.assertEqual(serializer.fields['target'].label, 'Target') + + @unittest.skipUnless(PIL is not None, 'PIL is not installed') class AttributeMappingOnAutogeneratedFieldsTests(TestCase): From f4a82dd5dadf95908c96c402f7f68b8e74c7de7a Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Thu, 24 Apr 2014 14:33:36 +0200 Subject: [PATCH 16/23] Updated the release notes. --- docs/topics/release-notes.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 2bc8b2d6a..335497eec 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,6 +40,25 @@ You can determine your currently installed version using `pip freeze`: ## 2.3.x series +### 2.3.x + +**Date**: April 2014 + +* Fix nested serializers linked through a backward foreign key relation +* Fix bad links for the `BrowsableAPIRenderer` with `YAMLRenderer` +* Add `UnicodeYAMLRenderer` that extends `YAMLRenderer` with unicode +* Fix `parse_header` argument convertion +* Fix mediatype detection under Python3 +* Web browseable API now offers blank option on dropdown when the field is not required +* `APIException` representation improved for logging purposes +* Allow source="*" within nested serializers +* Better support for custom oauth2 provider backends +* Fix field validation if it's optional and has no value +* Add `SEARCH_PARAM` and `ORDERING_PARAM` +* Fix `APIRequestFactory` to support arguments within the url string for GET +* Allow three transport modes for access tokens when accessing a protected resource +* Fix `Request`'s `QueryDict` encoding + ### 2.3.13 **Date**: 6th March 2014 From 82094554e5d267bcb550d3f7be26552befd7a1fe Mon Sep 17 00:00:00 2001 From: Kamil Niski Date: Sun, 27 Apr 2014 02:54:47 +0200 Subject: [PATCH 17/23] Minor typo --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 946a59545..8cdc55515 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -289,7 +289,7 @@ class WritableField(Field): self.validators = self.default_validators + validators self.default = default if default is not None else self.default - # Widgets are ony used for HTML forms. + # Widgets are only used for HTML forms. widget = widget or self.widget if isinstance(widget, type): widget = widget() From 4a1ef6d4b15c504881662a2667564394cb333b6b Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Sun, 27 Apr 2014 11:52:33 +0200 Subject: [PATCH 18/23] Updated Django's versions. --- .travis.yml | 16 ++++++++-------- tox.ini | 26 +++++++++++++------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index 60b48cbaf..bd6d2539a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,10 @@ python: - "3.3" env: - - DJANGO="https://www.djangoproject.com/download/1.7b1/tarball/" - - DJANGO="django==1.6.2" - - DJANGO="django==1.5.5" - - DJANGO="django==1.4.10" + - DJANGO="https://www.djangoproject.com/download/1.7b2/tarball/" + - DJANGO="django==1.6.3" + - DJANGO="django==1.5.6" + - DJANGO="django==1.4.11" - DJANGO="django==1.3.7" install: @@ -23,7 +23,7 @@ install: - "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.7; fi" - "if [[ ${TRAVIS_PYTHON_VERSION::1} == '3' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" - - "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7b1/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" + - "if [[ ${DJANGO} == 'https://www.djangoproject.com/download/1.7b2/tarball/' ]]; then pip install -e git+https://github.com/linovia/django-guardian.git@feature/django_1_7#egg=django-guardian-1.2.0; fi" - export PYTHONPATH=. script: @@ -32,13 +32,13 @@ script: matrix: exclude: - python: "2.6" - env: DJANGO="https://www.djangoproject.com/download/1.7b1/tarball/" + env: DJANGO="https://www.djangoproject.com/download/1.7b2/tarball/" - python: "3.2" - env: DJANGO="django==1.4.10" + env: DJANGO="django==1.4.11" - python: "3.2" env: DJANGO="django==1.3.7" - python: "3.3" - env: DJANGO="django==1.4.10" + env: DJANGO="django==1.4.11" - python: "3.3" env: DJANGO="django==1.3.7" diff --git a/tox.ini b/tox.ini index 855ab0ceb..e21210058 100644 --- a/tox.ini +++ b/tox.ini @@ -7,21 +7,21 @@ commands = {envpython} rest_framework/runtests/runtests.py [testenv:py3.3-django1.7] basepython = python3.3 -deps = https://www.djangoproject.com/download/1.7b1/tarball/ +deps = https://www.djangoproject.com/download/1.7b2/tarball/ django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py3.2-django1.7] basepython = python3.2 -deps = https://www.djangoproject.com/download/1.7b1/tarball/ +deps = https://www.djangoproject.com/download/1.7b2/tarball/ django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py2.7-django1.7] basepython = python2.7 -deps = https://www.djangoproject.com/download/1.7b1/tarball/ +deps = https://www.djangoproject.com/download/1.7b2/tarball/ django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -32,21 +32,21 @@ deps = https://www.djangoproject.com/download/1.7b1/tarball/ [testenv:py3.3-django1.6] basepython = python3.3 -deps = Django==1.6 +deps = Django==1.6.3 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py3.2-django1.6] basepython = python3.2 -deps = Django==1.6 +deps = Django==1.6.3 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py2.7-django1.6] basepython = python2.7 -deps = Django==1.6 +deps = Django==1.6.3 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -57,7 +57,7 @@ deps = Django==1.6 [testenv:py2.6-django1.6] basepython = python2.6 -deps = Django==1.6 +deps = Django==1.6.3 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -68,21 +68,21 @@ deps = Django==1.6 [testenv:py3.3-django1.5] basepython = python3.3 -deps = django==1.5.5 +deps = django==1.5.6 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py3.2-django1.5] basepython = python3.2 -deps = django==1.5.5 +deps = django==1.5.6 django-filter==0.7 defusedxml==0.3 Pillow==2.3.0 [testenv:py2.7-django1.5] basepython = python2.7 -deps = django==1.5.5 +deps = django==1.5.6 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -93,7 +93,7 @@ deps = django==1.5.5 [testenv:py2.6-django1.5] basepython = python2.6 -deps = django==1.5.5 +deps = django==1.5.6 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -104,7 +104,7 @@ deps = django==1.5.5 [testenv:py2.7-django1.4] basepython = python2.7 -deps = django==1.4.10 +deps = django==1.4.11 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 @@ -115,7 +115,7 @@ deps = django==1.4.10 [testenv:py2.6-django1.4] basepython = python2.6 -deps = django==1.4.10 +deps = django==1.4.11 django-filter==0.7 defusedxml==0.3 django-oauth-plus==2.2.1 From 1c777ffe8b67c342bc1b27fefe67d1094a2f6b07 Mon Sep 17 00:00:00 2001 From: Max Peterson Date: Mon, 28 Apr 2014 12:35:55 +0100 Subject: [PATCH 19/23] Ensure Token.generate_key returns a string. --- rest_framework/authtoken/models.py | 2 +- rest_framework/tests/test_authentication.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 8eac2cc49..167fa5314 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -34,7 +34,7 @@ class Token(models.Model): return super(Token, self).save(*args, **kwargs) def generate_key(self): - return binascii.hexlify(os.urandom(20)) + return binascii.hexlify(os.urandom(20)).decode() def __unicode__(self): return self.key diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index c37d2a512..8773f580b 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -195,6 +195,12 @@ class TokenAuthTests(TestCase): token = Token.objects.create(user=self.user) self.assertTrue(bool(token.key)) + def test_generate_key_returns_string(self): + """Ensure generate_key returns a string""" + token = Token() + key = token.generate_key() + self.assertTrue(isinstance(key, str)) + def test_token_login_json(self): """Ensure token login view using JSON POST works.""" client = APIClient(enforce_csrf_checks=True) From 170fa10ae0f2b531a8011be33cc9417b9f71e698 Mon Sep 17 00:00:00 2001 From: Max Peterson Date: Mon, 28 Apr 2014 13:10:34 +0100 Subject: [PATCH 20/23] Python < 3 compatibility. --- rest_framework/tests/test_authentication.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index 8773f580b..34ce1b7ac 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -199,7 +199,13 @@ class TokenAuthTests(TestCase): """Ensure generate_key returns a string""" token = Token() key = token.generate_key() - self.assertTrue(isinstance(key, str)) + try: + # added in Python < 3 + base = unicode + except NameError: + # added in Python >= 3 + base = str + self.assertTrue(isinstance(key, base)) def test_token_login_json(self): """Ensure token login view using JSON POST works.""" From 73597a16a2a6a388a08af923a1da8aa71d2f2848 Mon Sep 17 00:00:00 2001 From: Max Peterson Date: Mon, 28 Apr 2014 13:13:51 +0100 Subject: [PATCH 21/23] Better Python < 3 compatibility. --- rest_framework/tests/test_authentication.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/rest_framework/tests/test_authentication.py b/rest_framework/tests/test_authentication.py index 34ce1b7ac..a1c43d9ce 100644 --- a/rest_framework/tests/test_authentication.py +++ b/rest_framework/tests/test_authentication.py @@ -19,7 +19,7 @@ from rest_framework.authentication import ( OAuth2Authentication ) from rest_framework.authtoken.models import Token -from rest_framework.compat import patterns, url, include +from rest_framework.compat import patterns, url, include, six from rest_framework.compat import oauth2_provider, oauth2_provider_scope from rest_framework.compat import oauth, oauth_provider from rest_framework.test import APIRequestFactory, APIClient @@ -199,13 +199,7 @@ class TokenAuthTests(TestCase): """Ensure generate_key returns a string""" token = Token() key = token.generate_key() - try: - # added in Python < 3 - base = unicode - except NameError: - # added in Python >= 3 - base = str - self.assertTrue(isinstance(key, base)) + self.assertTrue(isinstance(key, six.string_types)) def test_token_login_json(self): """Ensure token login view using JSON POST works.""" From 5e8f05a8de410125d6df7a8e27f61e94176a8897 Mon Sep 17 00:00:00 2001 From: dpetzel Date: Mon, 28 Apr 2014 13:51:50 -0400 Subject: [PATCH 22/23] very minor typo in code example --- docs/api-guide/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 6a0f48f44..50f669a2d 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -56,7 +56,7 @@ You can also set the authentication policy on a per-view, or per-viewset basis, using the `APIView` class based views. from rest_framework.permissions import IsAuthenticated - from rest_framework.responses import Response + from rest_framework.response import Response from rest_framework.views import APIView class ExampleView(APIView): From d8cb85ef8fb0a0804d9b2c09d909ad99f69301c8 Mon Sep 17 00:00:00 2001 From: Laurent Bristiel Date: Mon, 28 Apr 2014 22:00:36 +0200 Subject: [PATCH 23/23] typo --- 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 fb927ea8b..7d06f246c 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -70,7 +70,7 @@ The following attributes control the basic view behavior. **Shortcuts**: -* `model` - This shortcut may be used instead of setting either (or both) of the `queryset`/`serializer_class` attributes, although using the explicit style is generally preferred. If used instead of `serializer_class`, then then `DEFAULT_MODEL_SERIALIZER_CLASS` setting will determine the base serializer class. Note that `model` is only ever used for generating a default queryset or serializer class - the `queryset` and `serializer_class` attributes are always preferred if provided. +* `model` - This shortcut may be used instead of setting either (or both) of the `queryset`/`serializer_class` attributes, although using the explicit style is generally preferred. If used instead of `serializer_class`, then `DEFAULT_MODEL_SERIALIZER_CLASS` setting will determine the base serializer class. Note that `model` is only ever used for generating a default queryset or serializer class - the `queryset` and `serializer_class` attributes are always preferred if provided. **Pagination**: