From 466d4584ae64e73713a56144b6c2128e76e0f5b6 Mon Sep 17 00:00:00 2001 From: Mark Shirley Date: Thu, 3 Jan 2013 23:18:58 +0100 Subject: [PATCH 01/22] Remove duplicate release notes line --- docs/topics/release-notes.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 3b13f86fb..c073c03be 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -22,7 +22,6 @@ Major version numbers (x.0.0) are reserved for project milestones. No major poi * Added `PATCH` support. * Added `RetrieveUpdateAPIView`. -* Relation changes are now persisted in `save` instead of in `.restore_object`. * Remove unused internal `save_m2m` flag on `ModelSerializer.save()`. * Tweak behavior of hyperlinked fields with an explicit format suffix. * Relation changes are now persisted in `.save()` instead of in `.restore_object()`. From 6e9865cb71ff45e90020d3d0dc7c40f20c760d2e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 3 Jan 2013 23:17:31 +0000 Subject: [PATCH 02/22] Fix for #446. Note: Also needs applying to other relational types. --- rest_framework/relations.py | 21 +++++++++++++++++++-- rest_framework/tests/fields.py | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 rest_framework/tests/fields.py diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 686dcf04e..ae0d3de89 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -4,6 +4,7 @@ from django import forms from django.forms import widgets from django.forms.models import ModelChoiceIterator from django.utils.encoding import smart_unicode +from django.utils.translation import ugettext_lazy as _ from rest_framework.fields import Field, WritableField from rest_framework.reverse import reverse from urlparse import urlparse @@ -168,6 +169,11 @@ class PrimaryKeyRelatedField(RelatedField): default_read_only = False form_field_class = forms.ChoiceField + default_error_messages = { + 'does_not_exist': _("Invalid pk '%s' - object does not exist."), + 'invalid': _('Invalid value.'), + } + # TODO: Remove these field hacks... def prepare_value(self, obj): return self.to_native(obj.pk) @@ -193,7 +199,10 @@ class PrimaryKeyRelatedField(RelatedField): try: return self.queryset.get(pk=data) except ObjectDoesNotExist: - msg = "Invalid pk '%s' - object does not exist." % smart_unicode(data) + msg = self.error_messages['does_not_exist'] % smart_unicode(data) + raise ValidationError(msg) + except (TypeError, ValueError): + msg = self.error_messages['invalid'] raise ValidationError(msg) def field_to_native(self, obj, field_name): @@ -215,6 +224,11 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): default_read_only = False form_field_class = forms.MultipleChoiceField + default_error_messages = { + 'does_not_exist': _("Invalid pk '%s' - object does not exist."), + 'invalid': _('Invalid value.'), + } + def prepare_value(self, obj): return self.to_native(obj.pk) @@ -249,7 +263,10 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField): try: return self.queryset.get(pk=data) except ObjectDoesNotExist: - msg = "Invalid pk '%s' - object does not exist." % smart_unicode(data) + msg = self.error_messages['does_not_exist'] % smart_unicode(data) + raise ValidationError(msg) + except (TypeError, ValueError): + msg = self.error_messages['invalid'] raise ValidationError(msg) ### Slug relationships diff --git a/rest_framework/tests/fields.py b/rest_framework/tests/fields.py new file mode 100644 index 000000000..147a9229b --- /dev/null +++ b/rest_framework/tests/fields.py @@ -0,0 +1,18 @@ +from django.db import models +from django.test import TestCase +from rest_framework import serializers + + +class NullModel(models.Model): + pass + + +class FieldTests(TestCase): + def test_pk_related_field_with_empty_string(self): + """ + Regression test for #446 + + https://github.com/tomchristie/django-rest-framework/issues/446 + """ + field = serializers.PrimaryKeyRelatedField(queryset=NullModel.objects.all()) + self.assertRaises(serializers.ValidationError, field.from_native, ('',)) From 4c86fd46d772e1fd3789d9ed2a76b9b92cce0872 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 4 Jan 2013 13:05:31 +0000 Subject: [PATCH 03/22] Rename module for basic relational field tests --- rest_framework/tests/{fields.py => relations.py} | 4 ++++ 1 file changed, 4 insertions(+) rename rest_framework/tests/{fields.py => relations.py} (91%) diff --git a/rest_framework/tests/fields.py b/rest_framework/tests/relations.py similarity index 91% rename from rest_framework/tests/fields.py rename to rest_framework/tests/relations.py index 147a9229b..108ec473e 100644 --- a/rest_framework/tests/fields.py +++ b/rest_framework/tests/relations.py @@ -1,3 +1,7 @@ +""" +General tests for relational fields. +""" + from django.db import models from django.test import TestCase from rest_framework import serializers From eb14278a3b08247c0aff5b2338a98203b51728c3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 4 Jan 2013 13:50:40 +0000 Subject: [PATCH 04/22] Add proper validation for updating relational fields with incorrect types. Fixes #446. --- docs/topics/release-notes.md | 8 ++++-- rest_framework/relations.py | 48 ++++++++++++++++++++++++------- rest_framework/tests/relations.py | 13 ++++++++- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index c073c03be..40b65761e 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -16,6 +16,10 @@ Major version numbers (x.0.0) are reserved for project milestones. No major poi ## 2.1.x series +### Master + +* Bugfix: Validation errors instead of exceptions when related fields receive incorrect types. + ### 2.1.15 **Date**: 3rd Jan 2013 @@ -36,9 +40,9 @@ Major version numbers (x.0.0) are reserved for project milestones. No major poi * Bugfix: Model fields with `blank=True` are now `required=False` by default. * Bugfix: Nested serializers now support nullable relationships. -**Note**: From 2.1.14 onwards, relational fields move out of the `fields.py` module and into the new `relations.py` module, in order to seperate them from regular data type fields, such as `CharField` and `IntegerField`. +**Note**: From 2.1.14 onwards, relational fields move out of the `fields.py` module and into the new `relations.py` module, in order to separate them from regular data type fields, such as `CharField` and `IntegerField`. -This change will not affect user code, so long as it's following the recommended import style of `from rest_framework import serializers` and refering to fields using the style `serializers.PrimaryKeyRelatedField`. +This change will not affect user code, so long as it's following the recommended import style of `from rest_framework import serializers` and referring to fields using the style `serializers.PrimaryKeyRelatedField`. ### 2.1.13 diff --git a/rest_framework/relations.py b/rest_framework/relations.py index ae0d3de89..fcef42dd7 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -276,6 +276,11 @@ class SlugRelatedField(RelatedField): default_read_only = False form_field_class = forms.ChoiceField + default_error_messages = { + 'does_not_exist': _("Object with %s=%s does not exist."), + 'invalid': _('Invalid value.'), + } + def __init__(self, *args, **kwargs): self.slug_field = kwargs.pop('slug_field', None) assert self.slug_field, 'slug_field is required' @@ -291,8 +296,11 @@ class SlugRelatedField(RelatedField): try: return self.queryset.get(**{self.slug_field: data}) except ObjectDoesNotExist: - raise ValidationError('Object with %s=%s does not exist.' % + raise ValidationError(self.error_messages['does_not_exist'] % (self.slug_field, unicode(data))) + except (TypeError, ValueError): + msg = self.error_messages['invalid'] + raise ValidationError(msg) class ManySlugRelatedField(ManyRelatedMixin, SlugRelatedField): @@ -311,6 +319,14 @@ class HyperlinkedRelatedField(RelatedField): default_read_only = False form_field_class = forms.ChoiceField + default_error_messages = { + 'no_match': _('Invalid hyperlink - No URL match'), + 'incorrect_match': _('Invalid hyperlink - Incorrect URL match'), + 'configuration_error': _('Invalid hyperlink due to configuration error'), + 'does_not_exist': _("Invalid hyperlink - object does not exist."), + 'invalid': _('Invalid value.'), + } + def __init__(self, *args, **kwargs): try: self.view_name = kwargs.pop('view_name') @@ -347,7 +363,7 @@ class HyperlinkedRelatedField(RelatedField): slug = getattr(obj, self.slug_field, None) if not slug: - raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name) + raise Exception('Could not resolve URL for field using view name "%s"' % view_name) kwargs = {self.slug_url_kwarg: slug} try: @@ -361,7 +377,7 @@ class HyperlinkedRelatedField(RelatedField): except: pass - raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name) + raise Exception('Could not resolve URL for field using view name "%s"' % view_name) def from_native(self, value): # Convert URL -> model instance pk @@ -369,7 +385,13 @@ class HyperlinkedRelatedField(RelatedField): if self.queryset is None: raise Exception('Writable related fields must include a `queryset` argument') - if value.startswith('http:') or value.startswith('https:'): + try: + http_prefix = value.startswith('http:') or value.startswith('https:') + except AttributeError: + msg = self.error_messages['invalid'] + raise ValidationError(msg) + + if http_prefix: # If needed convert absolute URLs to relative path value = urlparse(value).path prefix = get_script_prefix() @@ -379,10 +401,10 @@ class HyperlinkedRelatedField(RelatedField): try: match = resolve(value) except: - raise ValidationError('Invalid hyperlink - No URL match') + raise ValidationError(self.error_messages['no_match']) if match.url_name != self.view_name: - raise ValidationError('Invalid hyperlink - Incorrect URL match') + raise ValidationError(self.error_messages['incorrect_match']) pk = match.kwargs.get(self.pk_url_kwarg, None) slug = match.kwargs.get(self.slug_url_kwarg, None) @@ -394,14 +416,18 @@ class HyperlinkedRelatedField(RelatedField): elif slug is not None: slug_field = self.get_slug_field() queryset = self.queryset.filter(**{slug_field: slug}) - # If none of those are defined, it's an error. + # If none of those are defined, it's probably a configuation error. else: - raise ValidationError('Invalid hyperlink') + raise ValidationError(self.error_messages['configuration_error']) try: obj = queryset.get() except ObjectDoesNotExist: - raise ValidationError('Invalid hyperlink - object does not exist.') + raise ValidationError(self.error_messages['does_not_exist']) + except (TypeError, ValueError): + msg = self.error_messages['invalid'] + raise ValidationError(msg) + return obj @@ -460,7 +486,7 @@ class HyperlinkedIdentityField(Field): slug = getattr(obj, self.slug_field, None) if not slug: - raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name) + raise Exception('Could not resolve URL for field using view name "%s"' % view_name) kwargs = {self.slug_url_kwarg: slug} try: @@ -474,4 +500,4 @@ class HyperlinkedIdentityField(Field): except: pass - raise ValidationError('Could not resolve URL for field using view name "%s"' % view_name) + raise Exception('Could not resolve URL for field using view name "%s"' % view_name) diff --git a/rest_framework/tests/relations.py b/rest_framework/tests/relations.py index 108ec473e..91daea8a6 100644 --- a/rest_framework/tests/relations.py +++ b/rest_framework/tests/relations.py @@ -19,4 +19,15 @@ class FieldTests(TestCase): https://github.com/tomchristie/django-rest-framework/issues/446 """ field = serializers.PrimaryKeyRelatedField(queryset=NullModel.objects.all()) - self.assertRaises(serializers.ValidationError, field.from_native, ('',)) + self.assertRaises(serializers.ValidationError, field.from_native, '') + self.assertRaises(serializers.ValidationError, field.from_native, []) + + def test_hyperlinked_related_field_with_empty_string(self): + field = serializers.HyperlinkedRelatedField(queryset=NullModel.objects.all(), view_name='') + self.assertRaises(serializers.ValidationError, field.from_native, '') + self.assertRaises(serializers.ValidationError, field.from_native, []) + + def test_slug_related_field_with_empty_string(self): + field = serializers.SlugRelatedField(queryset=NullModel.objects.all(), slug_field='pk') + self.assertRaises(serializers.ValidationError, field.from_native, '') + self.assertRaises(serializers.ValidationError, field.from_native, []) From 26f9acb45ac0dcd1363399f518834c56d3f9984d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 4 Jan 2013 14:11:05 +0000 Subject: [PATCH 05/22] Validation errors instead of exceptions when serializers receive incorrect types. Fixes #402. --- docs/topics/release-notes.md | 1 + rest_framework/serializers.py | 7 ++++++- rest_framework/tests/serializer.py | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 40b65761e..eff7314b8 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -18,6 +18,7 @@ Major version numbers (x.0.0) are reserved for project milestones. No major poi ### Master +* Bugfix: Validation errors instead of exceptions when serializers receive incorrect types. * Bugfix: Validation errors instead of exceptions when related fields receive incorrect types. ### 2.1.15 diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index bd54db4ca..fa92838b6 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -208,6 +208,11 @@ class BaseSerializer(Field): Converts a dictionary of data into a dictionary of deserialized fields. """ reverted_data = {} + + if data is not None and not isinstance(data, dict): + self._errors['non_field_errors'] = [u'Invalid data'] + return None + for field_name, field in self.fields.items(): field.initialize(parent=self, field_name=field_name) try: @@ -276,7 +281,7 @@ class BaseSerializer(Field): """ if hasattr(data, '__iter__') and not isinstance(data, dict): # TODO: error data when deserializing lists - return (self.from_native(item) for item in data) + return [self.from_native(item, None) for item in data] self._errors = {} if data is not None or files is not None: diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 8767385eb..bd96ba23e 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -69,6 +69,7 @@ class AlbumsSerializer(serializers.ModelSerializer): model = Album fields = ['title'] # lists are also valid options + class PositiveIntegerAsChoiceSerializer(serializers.ModelSerializer): class Meta: model = HasPositiveIntegerAsChoice @@ -240,6 +241,25 @@ class ValidationTests(TestCase): self.assertFalse(serializer.is_valid()) self.assertEquals(serializer.errors, {'content': [u'Test not in value']}) + def test_bad_type_data_is_false(self): + """ + Data of the wrong type is not valid. + """ + data = ['i am', 'a', 'list'] + serializer = CommentSerializer(self.comment, data=data) + self.assertEquals(serializer.is_valid(), False) + self.assertEquals(serializer.errors, {'non_field_errors': [u'Invalid data']}) + + data = 'and i am a string' + serializer = CommentSerializer(self.comment, data=data) + self.assertEquals(serializer.is_valid(), False) + self.assertEquals(serializer.errors, {'non_field_errors': [u'Invalid data']}) + + data = 42 + serializer = CommentSerializer(self.comment, data=data) + self.assertEquals(serializer.is_valid(), False) + self.assertEquals(serializer.errors, {'non_field_errors': [u'Invalid data']}) + def test_cross_field_validation(self): class CommentSerializerWithCrossFieldValidator(CommentSerializer): From 8a4e2c172570933fb6da86dc9eae69027ffb6f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Gro=C3=9F?= Date: Sat, 5 Jan 2013 11:20:36 +0100 Subject: [PATCH 06/22] removed duplicate entry in 2.1.15 --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index cb91d018f..8a9149cf1 100644 --- a/README.md +++ b/README.md @@ -87,10 +87,9 @@ To run the tests. * Added `PATCH` support. * Added `RetrieveUpdateAPIView`. -* Relation changes are now persisted in `save` instead of in `.restore_object`. +* Relation changes are now persisted in `.save` instead of in `.restore_object`. * Remove unused internal `save_m2m` flag on `ModelSerializer.save()`. * Tweak behavior of hyperlinked fields with an explicit format suffix. -* Relation changes are now persisted in `.save()` instead of in `.restore_object()`. * Bugfix: Fix issue with FileField raising exception instead of validation error when files=None. * Bugfix: Partial updates should not set default values if field is not included. From a061e3d9e20c4c481c2ac2eee5b17bb1430cace6 Mon Sep 17 00:00:00 2001 From: Juan Riaza Date: Sat, 5 Jan 2013 13:40:02 +0100 Subject: [PATCH 07/22] deprecate simplejson --- rest_framework/parsers.py | 2 +- rest_framework/renderers.py | 2 +- rest_framework/tests/authentication.py | 2 +- rest_framework/tests/generics.py | 2 +- rest_framework/tests/hyperlinkedserializers.py | 2 +- rest_framework/tests/request.py | 2 +- rest_framework/utils/encoders.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 4841676c9..149d64311 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -8,11 +8,11 @@ on the request, such as form content or json encoded data. from django.http import QueryDict from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser from django.http.multipartparser import MultiPartParserError -from django.utils import simplejson as json from rest_framework.compat import yaml, ETParseError from rest_framework.exceptions import ParseError from xml.etree import ElementTree as ET from xml.parsers.expat import ExpatError +import json import datetime import decimal diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index a4ae717db..0a34abaa0 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -8,10 +8,10 @@ REST framework also provides an HTML renderer the renders the browsable API. """ import copy import string +import json from django import forms from django.http.multipartparser import parse_header from django.template import RequestContext, loader, Template -from django.utils import simplejson as json from rest_framework.compat import yaml from rest_framework.exceptions import ConfigurationError from rest_framework.settings import api_settings diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index 838e081bb..e86041bc4 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -1,7 +1,6 @@ from django.contrib.auth.models import User from django.http import HttpResponse from django.test import Client, TestCase -from django.utils import simplejson as json from rest_framework import permissions from rest_framework.authtoken.models import Token @@ -9,6 +8,7 @@ from rest_framework.authentication import TokenAuthentication from rest_framework.compat import patterns from rest_framework.views import APIView +import json import base64 diff --git a/rest_framework/tests/generics.py b/rest_framework/tests/generics.py index 843017ebf..4799a04b5 100644 --- a/rest_framework/tests/generics.py +++ b/rest_framework/tests/generics.py @@ -1,6 +1,6 @@ +import json from django.db import models from django.test import TestCase -from django.utils import simplejson as json from rest_framework import generics, serializers, status from rest_framework.tests.utils import RequestFactory from rest_framework.tests.models import BasicModel, Comment, SlugBasedModel diff --git a/rest_framework/tests/hyperlinkedserializers.py b/rest_framework/tests/hyperlinkedserializers.py index ee4d8e577..c6a8224b1 100644 --- a/rest_framework/tests/hyperlinkedserializers.py +++ b/rest_framework/tests/hyperlinkedserializers.py @@ -1,6 +1,6 @@ +import json from django.test import TestCase from django.test.client import RequestFactory -from django.utils import simplejson as json from rest_framework import generics, status, serializers from rest_framework.compat import patterns, url from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel diff --git a/rest_framework/tests/request.py b/rest_framework/tests/request.py index 1f05ff8ff..4b0324056 100644 --- a/rest_framework/tests/request.py +++ b/rest_framework/tests/request.py @@ -1,12 +1,12 @@ """ Tests for content parsing, and form-overloaded content parsing. """ +import json from django.contrib.auth.models import User from django.contrib.auth import authenticate, login, logout from django.contrib.sessions.middleware import SessionMiddleware from django.test import TestCase, Client from django.test.client import RequestFactory -from django.utils import simplejson as json from rest_framework import status from rest_framework.authentication import SessionAuthentication from rest_framework.compat import patterns diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 2d1fb353e..c70b24dda 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -4,7 +4,7 @@ Helper classes for parsers. import datetime import decimal import types -from django.utils import simplejson as json +import json from django.utils.datastructures import SortedDict from rest_framework.compat import timezone from rest_framework.serializers import DictWithMetadata, SortedDictWithMetadata From d7f3c86ad1edec7e7d42fe75238100832f5e7f1b Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Sat, 5 Jan 2013 17:45:55 +0200 Subject: [PATCH 08/22] Updated release notes --- docs/topics/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index eff7314b8..edd948ac8 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -18,6 +18,7 @@ Major version numbers (x.0.0) are reserved for project milestones. No major poi ### Master +* Deprecate django.utils.simplejson in favor of Python 2.6's built-in json module. * Bugfix: Validation errors instead of exceptions when serializers receive incorrect types. * Bugfix: Validation errors instead of exceptions when related fields receive incorrect types. From 12bb25b372de43ac3a944a8eff518b1c6f41ddfc Mon Sep 17 00:00:00 2001 From: Yuri Prezument Date: Sat, 5 Jan 2013 17:51:33 +0200 Subject: [PATCH 09/22] Added @juanriaza. Thanks --- docs/topics/credits.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 008e4a1bd..7da6308db 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -2,7 +2,7 @@ The following people have helped make REST framework great. -* Tom Christie - [tomchristie] +* Tom Christie - [tomchristie] * Marko Tibold - [markotibold] * Paul Bagwell - [pbgwl] * Sébastien Piquemal - [sebpiq] @@ -85,6 +85,7 @@ The following people have helped make REST framework great. * Toran Billups - [toranb] * Sébastien Béal - [sebastibe] * Andrew Hankinson - [ahankinson] +* Juan Riaza - [juanriaza] Many thanks to everyone who's contributed to the project. @@ -96,7 +97,7 @@ Project hosting is with [GitHub]. Continuous integration testing is managed with [Travis CI][travis-ci]. -The [live sandbox][sandbox] is hosted on [Heroku]. +The [live sandbox][sandbox] is hosted on [Heroku]. Various inspiration taken from the [Piston], [Tastypie] and [Dagny] projects. @@ -107,7 +108,7 @@ Development of REST framework 2.0 was sponsored by [DabApps]. For usage questions please see the [REST framework discussion group][group]. You can also contact [@_tomchristie][twitter] directly on twitter. - + [email]: mailto:tom@tomchristie.com [twitter]: http://twitter.com/_tomchristie [bootstrap]: http://twitter.github.com/bootstrap/ @@ -205,3 +206,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [toranb]: https://github.com/toranb [sebastibe]: https://github.com/sebastibe [ahankinson]: https://github.com/ahankinson +[juanriaza]: https://github.com/juanriaza From 9b67a33b922e2797adab261640ee6f0acb093985 Mon Sep 17 00:00:00 2001 From: Michael Mior Date: Sun, 6 Jan 2013 15:49:12 -0500 Subject: [PATCH 10/22] Use the correct static template tag in Django 1.5 --- 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 09c658bcd..82fcdfe74 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -23,7 +23,7 @@ register = template.Library() # conflicts with this rest_framework template tag module. try: # Django 1.5+ - from django.contrib.staticfiles.templatetags import StaticFilesNode + from django.contrib.staticfiles.templatetags.staticfiles import StaticFilesNode @register.tag('static') def do_static(parser, token): From 152e6d5c0af9c55aa9c276b8270aad700c43eae3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 7 Jan 2013 08:57:43 +0000 Subject: [PATCH 11/22] Added @michaelmior. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 7da6308db..252e41def 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -86,6 +86,7 @@ The following people have helped make REST framework great. * Sébastien Béal - [sebastibe] * Andrew Hankinson - [ahankinson] * Juan Riaza - [juanriaza] +* Michael Mior - [michaelmior] Many thanks to everyone who's contributed to the project. @@ -207,3 +208,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [sebastibe]: https://github.com/sebastibe [ahankinson]: https://github.com/ahankinson [juanriaza]: https://github.com/juanriaza +[michaelmior]: https://github.com/michaelmior From c736b80290dfd650bee0fa3c530da65792dcde32 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Mon, 7 Jan 2013 12:52:20 +0000 Subject: [PATCH 12/22] Be more informative when reporting import errors. --- rest_framework/settings.py | 4 ++-- rest_framework/tests/extras/__init__.py | 0 rest_framework/tests/extras/bad_import.py | 1 + rest_framework/tests/settings.py | 21 +++++++++++++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 rest_framework/tests/extras/__init__.py create mode 100644 rest_framework/tests/extras/bad_import.py create mode 100644 rest_framework/tests/settings.py diff --git a/rest_framework/settings.py b/rest_framework/settings.py index ee24a4ad9..5c77c55cd 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -115,8 +115,8 @@ def import_from_string(val, setting_name): module_path, class_name = '.'.join(parts[:-1]), parts[-1] module = importlib.import_module(module_path) return getattr(module, class_name) - except: - msg = "Could not import '%s' for API setting '%s'" % (val, setting_name) + except ImportError as e: + msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e) raise ImportError(msg) diff --git a/rest_framework/tests/extras/__init__.py b/rest_framework/tests/extras/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rest_framework/tests/extras/bad_import.py b/rest_framework/tests/extras/bad_import.py new file mode 100644 index 000000000..68263d947 --- /dev/null +++ b/rest_framework/tests/extras/bad_import.py @@ -0,0 +1 @@ +raise ValueError diff --git a/rest_framework/tests/settings.py b/rest_framework/tests/settings.py new file mode 100644 index 000000000..0293fdc3e --- /dev/null +++ b/rest_framework/tests/settings.py @@ -0,0 +1,21 @@ +"""Tests for the settings module""" +from django.test import TestCase + +from rest_framework.settings import APISettings, DEFAULTS, IMPORT_STRINGS + + +class TestSettings(TestCase): + """Tests relating to the api settings""" + + def test_non_import_errors(self): + """Make sure other errors aren't suppressed.""" + settings = APISettings({'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.tests.extras.bad_import.ModelSerializer'}, DEFAULTS, IMPORT_STRINGS) + with self.assertRaises(ValueError): + settings.DEFAULT_MODEL_SERIALIZER_CLASS + + def test_import_error_message_maintained(self): + """Make sure real import errors are captured and raised sensibly.""" + settings = APISettings({'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.tests.extras.not_here.ModelSerializer'}, DEFAULTS, IMPORT_STRINGS) + with self.assertRaises(ImportError) as cm: + settings.DEFAULT_MODEL_SERIALIZER_CLASS + self.assertTrue('ImportError' in str(cm.exception)) From 5bded1ecf03936621806991bf7f68434dd44c4ac Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Mon, 7 Jan 2013 14:34:45 +0000 Subject: [PATCH 13/22] Use ResolveMatch.view_name so namespaces work. --- rest_framework/relations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index fcef42dd7..0d93f4480 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -403,7 +403,7 @@ class HyperlinkedRelatedField(RelatedField): except: raise ValidationError(self.error_messages['no_match']) - if match.url_name != self.view_name: + if match.view_name != self.view_name: raise ValidationError(self.error_messages['incorrect_match']) pk = match.kwargs.get(self.pk_url_kwarg, None) From de00d3720ea6d4aca06c596eaee8d0d528f19f82 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 7 Jan 2013 14:55:55 +0000 Subject: [PATCH 14/22] Added @mjtamlyn. Thanks! --- docs/topics/credits.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/topics/credits.md b/docs/topics/credits.md index 252e41def..83272766e 100644 --- a/docs/topics/credits.md +++ b/docs/topics/credits.md @@ -87,6 +87,7 @@ The following people have helped make REST framework great. * Andrew Hankinson - [ahankinson] * Juan Riaza - [juanriaza] * Michael Mior - [michaelmior] +* Marc Tamlyn - [mjtamlyn] Many thanks to everyone who's contributed to the project. @@ -209,3 +210,4 @@ You can also contact [@_tomchristie][twitter] directly on twitter. [ahankinson]: https://github.com/ahankinson [juanriaza]: https://github.com/juanriaza [michaelmior]: https://github.com/michaelmior +[mjtamlyn]: https://github.com/mjtamlyn From d9df15f32174f9ddac7135ee33c74771a3173350 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 7 Jan 2013 14:56:26 +0000 Subject: [PATCH 15/22] Added @juanriaza's `djangorestframework-msgpack` package to the docs. --- docs/api-guide/parsers.md | 13 +++++++++++++ docs/api-guide/renderers.md | 14 +++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 185b616cb..9356b4205 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -159,4 +159,17 @@ For example: files = {name: uploaded} return DataAndFiles(data, files) +--- + +# Third party packages + +The following third party packages are also available. + +## MessagePack + +[MessagePack][messagepack] is a fast, efficient binary serialization format. [Juan Riaza][juanriaza] maintains the `djangorestframework-msgpack` package which provides MessagePack renderer and parser support for REST framework. Documentation is [available here][djangorestframework-msgpack]. + [cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion +[messagepack]: https://github.com/juanriaza/django-rest-framework-msgpack +[juanriaza]: https://github.com/juanriaza +[djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack \ No newline at end of file diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index 374ff0ab2..389dec1f6 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -271,6 +271,15 @@ Exceptions raised and handled by an HTML renderer will attempt to render using o Templates will render with a `RequestContext` which includes the `status_code` and `details` keys. +--- + +# Third party packages + +The following third party packages are also available. + +## MessagePack + +[MessagePack][messagepack] is a fast, efficient binary serialization format. [Juan Riaza][juanriaza] maintains the `djangorestframework-msgpack` package which provides MessagePack renderer and parser support for REST framework. Documentation is [available here][djangorestframework-msgpack]. [cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process [conneg]: content-negotiation.md @@ -280,4 +289,7 @@ Templates will render with a `RequestContext` which includes the `status_code` a [quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven [application/vnd.github+json]: http://developer.github.com/v3/media/ [application/vnd.collection+json]: http://www.amundsen.com/media-types/collection/ -[django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views \ No newline at end of file +[django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views +[messagepack]: https://github.com/juanriaza/django-rest-framework-msgpack +[juanriaza]: https://github.com/juanriaza +[djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack \ No newline at end of file From e429f702e00ed807d68e90cd6a6af2749eb0b73e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 7 Jan 2013 20:17:52 +0000 Subject: [PATCH 16/22] Fix PAGINATE_BY_PARAM docs. Refs #551 --- docs/api-guide/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 7884d096b..8c87f2ca5 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -106,7 +106,7 @@ The default page size to use for pagination. If set to `None`, pagination is di Default: `None` -## PAGINATE_BY_KWARG +## PAGINATE_BY_PARAM The name of a query parameter, which can be used by the client to overide the default page size to use for pagination. If set to `None`, clients may not override the default page size. From 4bb504732d045fb7db0fc1ddc1fc926197dd2c91 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 7 Jan 2013 21:08:55 +0000 Subject: [PATCH 17/22] Respect blank=True on relational fields. Fixes #537 --- 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 fa92838b6..19a955b85 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -430,7 +430,7 @@ class ModelSerializer(Serializer): # TODO: filter queryset using: # .using(db).complex_filter(self.rel.limit_choices_to) kwargs = { - 'null': model_field.null, + 'null': model_field.null or model_field.blank, 'queryset': model_field.rel.to._default_manager } From 31b585f26a8fc72e5b527b7672c7691e374dc494 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 7 Jan 2013 21:13:10 +0000 Subject: [PATCH 18/22] Note paginate_by=None usage. Fixes #555. --- docs/api-guide/pagination.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index ab335e6e2..71253afb0 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -97,6 +97,8 @@ You can also set the pagination style on a per-view basis, using the `ListAPIVie paginate_by = 10 paginate_by_param = 'page_size' +Note that using a `paginate_by` value of `None` will 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 4e8f55887d6ce86a2293f8b8cbb255bc58995336 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 7 Jan 2013 21:37:44 +0000 Subject: [PATCH 19/22] Clean up test slightly. Refs #552 --- rest_framework/tests/pagination.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index 81d297a1f..3b5508779 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -181,10 +181,10 @@ class UnitTestPagination(TestCase): """ Ensure context gets passed through to the object serializer. """ - serializer = PassOnContextPaginationSerializer(self.first_page) + serializer = PassOnContextPaginationSerializer(self.first_page, context={'foo': 'bar'}) serializer.data results = serializer.fields[serializer.results_field] - self.assertTrue(serializer.context is results.context) + self.assertEquals(serializer.context, results.context) class TestUnpaginated(TestCase): From 4df1172665f6df3d4c4df53b4836e2c6ed462da5 Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Tue, 8 Jan 2013 11:45:55 +0000 Subject: [PATCH 20/22] Fix reference to BasicAuthentication in settings. --- docs/api-guide/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 8c87f2ca5..a422e5f61 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -65,7 +65,7 @@ Default: ( 'rest_framework.authentication.SessionAuthentication', - 'rest_framework.authentication.UserBasicAuthentication' + 'rest_framework.authentication.BasicAuthentication' ) ## DEFAULT_PERMISSION_CLASSES From 49cd5e59a8679004a067e2db72f8ba1d9ac5b69c Mon Sep 17 00:00:00 2001 From: Marc Tamlyn Date: Tue, 8 Jan 2013 12:20:01 +0000 Subject: [PATCH 21/22] ObtainAuthToken pluggable Serializer. It should have serializer_class in the same way as any other API view. --- rest_framework/authtoken/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/authtoken/views.py b/rest_framework/authtoken/views.py index d318c7233..7c03cb766 100644 --- a/rest_framework/authtoken/views.py +++ b/rest_framework/authtoken/views.py @@ -12,10 +12,11 @@ class ObtainAuthToken(APIView): permission_classes = () parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,) renderer_classes = (renderers.JSONRenderer,) + serializer_class = AuthTokenSerializer model = Token def post(self, request): - serializer = AuthTokenSerializer(data=request.DATA) + serializer = self.serializer_class(data=request.DATA) if serializer.is_valid(): token, created = Token.objects.get_or_create(user=serializer.object['user']) return Response({'token': token.key}) From c1f194b0a592c88c7de512958f62c43695df018f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 8 Jan 2013 15:03:14 +0000 Subject: [PATCH 22/22] Fix inconsistent view_name logic. Fixes #567. --- rest_framework/relations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 0d93f4480..adc478000 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -367,13 +367,13 @@ class HyperlinkedRelatedField(RelatedField): kwargs = {self.slug_url_kwarg: slug} try: - return reverse(self.view_name, kwargs=kwargs, request=request, format=format) + return reverse(view_name, kwargs=kwargs, request=request, format=format) except: pass kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug} try: - return reverse(self.view_name, kwargs=kwargs, request=request, format=format) + return reverse(view_name, kwargs=kwargs, request=request, format=format) except: pass @@ -490,13 +490,13 @@ class HyperlinkedIdentityField(Field): kwargs = {self.slug_url_kwarg: slug} try: - return reverse(self.view_name, kwargs=kwargs, request=request, format=format) + return reverse(view_name, kwargs=kwargs, request=request, format=format) except: pass kwargs = {self.pk_url_kwarg: obj.pk, self.slug_url_kwarg: slug} try: - return reverse(self.view_name, kwargs=kwargs, request=request, format=format) + return reverse(view_name, kwargs=kwargs, request=request, format=format) except: pass