diff options
| -rw-r--r-- | .travis.yml | 16 | ||||
| -rwxr-xr-x | docs/api-guide/generic-views.md | 2 | ||||
| -rw-r--r-- | docs/api-guide/pagination.md | 3 | ||||
| -rw-r--r-- | docs/api-guide/permissions.md | 2 | ||||
| -rw-r--r-- | docs/topics/release-notes.md | 19 | ||||
| -rw-r--r-- | rest_framework/authtoken/models.py | 2 | ||||
| -rw-r--r-- | rest_framework/fields.py | 2 | ||||
| -rw-r--r-- | rest_framework/serializers.py | 8 | ||||
| -rw-r--r-- | tests/models.py | 3 | ||||
| -rw-r--r-- | tests/test_authentication.py | 8 | ||||
| -rw-r--r-- | tests/test_serializer.py | 26 | ||||
| -rw-r--r-- | tox.ini | 26 | 
12 files changed, 88 insertions, 29 deletions
| diff --git a/.travis.yml b/.travis.yml index 4f4d0c30..d2f20e2b 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: @@ -24,7 +24,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: @@ -33,13 +33,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/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index fb927ea8..7d06f246 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**: diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index efc4ae7f..e57aed1a 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -103,6 +103,7 @@ You can also set the pagination style on a per-view basis, using the `ListAPIVie          max_paginate_by = 100  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. @@ -157,4 +158,4 @@ The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin`  [cite]: https://docs.djangoproject.com/en/dev/topics/pagination/  [drf-extensions]: http://chibisov.github.io/drf-extensions/docs/ -[paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin
\ No newline at end of file +[paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 6a0f48f4..50f669a2 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): diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 2bc8b2d6..335497ee 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 diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 8eac2cc4..167fa531 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/fields.py b/rest_framework/fields.py index 946a5954..8cdc5551 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() diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index ea9509bf..9cb548a5 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/tests/models.py b/tests/models.py index 6c8f2342..0256697a 100644 --- a/tests/models.py +++ b/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/tests/test_authentication.py b/tests/test_authentication.py index 1d90493e..d0290eac 100644 --- a/tests/test_authentication.py +++ b/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 @@ -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, six.string_types)) +      def test_token_login_json(self):          """Ensure token login view using JSON POST works."""          client = APIClient(enforce_csrf_checks=True) diff --git a/tests/test_serializer.py b/tests/test_serializer.py index f8966886..73eb5c79 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -9,7 +9,8 @@ from django.utils.translation import ugettext_lazy as _  from rest_framework import serializers, fields, relations  from 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 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): @@ -7,7 +7,7 @@ commands = py.test -q  [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 @@ -15,7 +15,7 @@ deps = https://www.djangoproject.com/download/1.7b1/tarball/  [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 @@ -23,7 +23,7 @@ deps = https://www.djangoproject.com/download/1.7b1/tarball/  [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 @@ -35,7 +35,7 @@ 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 @@ -43,7 +43,7 @@ deps = Django==1.6  [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 @@ -51,7 +51,7 @@ deps = Django==1.6  [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 @@ -63,7 +63,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 @@ -75,7 +75,7 @@ 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 @@ -83,7 +83,7 @@ deps = django==1.5.5  [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 @@ -91,7 +91,7 @@ deps = django==1.5.5  [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 @@ -103,7 +103,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 @@ -115,7 +115,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 @@ -127,7 +127,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 | 
