aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md1
-rw-r--r--docs/api-guide/parsers.md11
-rw-r--r--docs/index.md12
-rw-r--r--docs/topics/release-notes.md1
-rw-r--r--rest_framework/relations.py20
-rw-r--r--rest_framework/tests/relations_hyperlink.py9
-rw-r--r--rest_framework/tests/relations_pk.py7
-rw-r--r--rest_framework/tests/relations_slug.py162
8 files changed, 197 insertions, 26 deletions
diff --git a/README.md b/README.md
index a7020166..d7cc1c6d 100644
--- a/README.md
+++ b/README.md
@@ -295,4 +295,3 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[markdown]: http://pypi.python.org/pypi/Markdown/
[pyyaml]: http://pypi.python.org/pypi/PyYAML
[django-filter]: http://pypi.python.org/pypi/django-filter
-
diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md
index de968557..0cd01639 100644
--- a/docs/api-guide/parsers.md
+++ b/docs/api-guide/parsers.md
@@ -14,6 +14,16 @@ REST framework includes a number of built in Parser classes, that allow you to a
The set of valid parsers for a view is always defined as a list of classes. When either `request.DATA` or `request.FILES` is accessed, REST framework will examine the `Content-Type` header on the incoming request, and determine which parser to use to parse the request content.
+---
+
+**Note**: When developing client applications always remember to make sure you're setting the `Content-Type` header when sending data in an HTTP request.
+
+If you don't set the content type, most clients will default to using `'application/x-www-form-urlencoded'`, which may not be what you wanted.
+
+As an example, if you are sending `json` encoded data using jQuery with the [.ajax() method][jquery-ajax], you should make sure to include the `contentType: 'application/json'` setting.
+
+---
+
## Setting the parsers
The default set of parsers may be set globally, using the `DEFAULT_PARSER_CLASSES` setting. For example, the following settings would allow requests with `YAML` content.
@@ -169,6 +179,7 @@ The following third party packages are also available.
[MessagePack][messagepack] is a fast, efficient binary serialization format. [Juan Riaza][juanriaza] maintains the [djangorestframework-msgpack][djangorestframework-msgpack] package which provides MessagePack renderer and parser support for REST framework.
+[jquery-ajax]: http://api.jquery.com/jQuery.ajax/
[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
diff --git a/docs/index.md b/docs/index.md
index 497f1900..3ed11c02 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -132,9 +132,9 @@ Run the tests:
## Support
-For support please see the [REST framework discussion group][group], or try the `#restframework` channel on `irc.freenode.net`.
+For support please see the [REST framework discussion group][group], try the `#restframework` channel on `irc.freenode.net`, or raise a question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag.
-Paid support is also available from [DabApps], and can include work on REST framework core, or support with building your REST framework API. Please contact [Tom Christie][email] if you'd like to discuss commercial support options.
+[Paid support is available][paid-support] from [DabApps][dabapps], and can include work on REST framework core, or support with building your REST framework API. Please [contact DabApps][contact-dabapps] if you'd like to discuss commercial support options.
## License
@@ -209,5 +209,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[credits]: topics/credits.md
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
-[DabApps]: http://dabapps.com
-[email]: mailto:tom@tomchristie.com
+[stack-overflow]: http://stackoverflow.com/
+[django-rest-framework-tag]: http://stackoverflow.com/questions/tagged/django-rest-framework
+[django-tag]: http://stackoverflow.com/questions/tagged/django
+[paid-support]: http://dabapps.com/services/build/api-development/
+[dabapps]: http://dabapps.com
+[contact-dabapps]: http://dabapps.com/contact/
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index e00a5e93..bbe11fac 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -19,6 +19,7 @@ Major version numbers (x.0.0) are reserved for project milestones. No major poi
### Master
* Support json encoding of timedelta objects.
+* Bugfix: Return proper validation errors when incorrect types supplied for relational fields.
* Bugfix: Support nullable FKs with `SlugRelatedField`.
### 2.1.16
diff --git a/rest_framework/relations.py b/rest_framework/relations.py
index 7ded3891..af63ceaa 100644
--- a/rest_framework/relations.py
+++ b/rest_framework/relations.py
@@ -177,7 +177,7 @@ class PrimaryKeyRelatedField(RelatedField):
default_error_messages = {
'does_not_exist': _("Invalid pk '%s' - object does not exist."),
- 'invalid': _('Invalid value.'),
+ 'incorrect_type': _('Incorrect type. Expected pk value, received %s.'),
}
# TODO: Remove these field hacks...
@@ -208,7 +208,8 @@ class PrimaryKeyRelatedField(RelatedField):
msg = self.error_messages['does_not_exist'] % smart_unicode(data)
raise ValidationError(msg)
except (TypeError, ValueError):
- msg = self.error_messages['invalid']
+ received = type(data).__name__
+ msg = self.error_messages['incorrect_type'] % received
raise ValidationError(msg)
def field_to_native(self, obj, field_name):
@@ -235,7 +236,7 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField):
default_error_messages = {
'does_not_exist': _("Invalid pk '%s' - object does not exist."),
- 'invalid': _('Invalid value.'),
+ 'incorrect_type': _('Incorrect type. Expected pk value, received %s.'),
}
def prepare_value(self, obj):
@@ -275,7 +276,8 @@ class ManyPrimaryKeyRelatedField(ManyRelatedField):
msg = self.error_messages['does_not_exist'] % smart_unicode(data)
raise ValidationError(msg)
except (TypeError, ValueError):
- msg = self.error_messages['invalid']
+ received = type(data).__name__
+ msg = self.error_messages['incorrect_type'] % received
raise ValidationError(msg)
### Slug relationships
@@ -333,7 +335,7 @@ class HyperlinkedRelatedField(RelatedField):
'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.'),
+ 'incorrect_type': _('Incorrect type. Expected url string, received %s.'),
}
def __init__(self, *args, **kwargs):
@@ -397,8 +399,8 @@ class HyperlinkedRelatedField(RelatedField):
try:
http_prefix = value.startswith('http:') or value.startswith('https:')
except AttributeError:
- msg = self.error_messages['invalid']
- raise ValidationError(msg)
+ msg = self.error_messages['incorrect_type']
+ raise ValidationError(msg % type(value).__name__)
if http_prefix:
# If needed convert absolute URLs to relative path
@@ -434,8 +436,8 @@ class HyperlinkedRelatedField(RelatedField):
except ObjectDoesNotExist:
raise ValidationError(self.error_messages['does_not_exist'])
except (TypeError, ValueError):
- msg = self.error_messages['invalid']
- raise ValidationError(msg)
+ msg = self.error_messages['incorrect_type']
+ raise ValidationError(msg % type(value).__name__)
return obj
diff --git a/rest_framework/tests/relations_hyperlink.py b/rest_framework/tests/relations_hyperlink.py
index 7d65eae7..6d137f68 100644
--- a/rest_framework/tests/relations_hyperlink.py
+++ b/rest_framework/tests/relations_hyperlink.py
@@ -215,6 +215,13 @@ class HyperlinkedForeignKeyTests(TestCase):
]
self.assertEquals(serializer.data, expected)
+ def test_foreign_key_update_incorrect_type(self):
+ data = {'url': '/foreignkeysource/1/', 'name': u'source-1', 'target': 2}
+ instance = ForeignKeySource.objects.get(pk=1)
+ serializer = ForeignKeySourceSerializer(instance, data=data)
+ self.assertFalse(serializer.is_valid())
+ self.assertEquals(serializer.errors, {'target': [u'Incorrect type. Expected url string, received int.']})
+
def test_reverse_foreign_key_update(self):
data = {'url': '/foreignkeytarget/2/', 'name': u'target-2', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/3/']}
instance = ForeignKeyTarget.objects.get(pk=2)
@@ -227,7 +234,7 @@ class HyperlinkedForeignKeyTests(TestCase):
expected = [
{'url': '/foreignkeytarget/1/', 'name': u'target-1', 'sources': ['/foreignkeysource/1/', '/foreignkeysource/2/', '/foreignkeysource/3/']},
{'url': '/foreignkeytarget/2/', 'name': u'target-2', 'sources': []},
- ]
+ ]
self.assertEquals(new_serializer.data, expected)
serializer.save()
diff --git a/rest_framework/tests/relations_pk.py b/rest_framework/tests/relations_pk.py
index dd1e86b5..3391e60a 100644
--- a/rest_framework/tests/relations_pk.py
+++ b/rest_framework/tests/relations_pk.py
@@ -194,6 +194,13 @@ class PKForeignKeyTests(TestCase):
]
self.assertEquals(serializer.data, expected)
+ def test_foreign_key_update_incorrect_type(self):
+ data = {'id': 1, 'name': u'source-1', 'target': 'foo'}
+ instance = ForeignKeySource.objects.get(pk=1)
+ serializer = ForeignKeySourceSerializer(instance, data=data)
+ self.assertFalse(serializer.is_valid())
+ self.assertEquals(serializer.errors, {'target': [u'Incorrect type. Expected pk value, received str.']})
+
def test_reverse_foreign_key_update(self):
data = {'id': 2, 'name': u'target-2', 'sources': [1, 3]}
instance = ForeignKeyTarget.objects.get(pk=2)
diff --git a/rest_framework/tests/relations_slug.py b/rest_framework/tests/relations_slug.py
index 503b61e8..37ccc75e 100644
--- a/rest_framework/tests/relations_slug.py
+++ b/rest_framework/tests/relations_slug.py
@@ -1,9 +1,23 @@
from django.test import TestCase
from rest_framework import serializers
-from rest_framework.tests.models import NullableForeignKeySource, ForeignKeyTarget
+from rest_framework.tests.models import NullableForeignKeySource, ForeignKeySource, ForeignKeyTarget
-class NullableSlugSourceSerializer(serializers.ModelSerializer):
+class ForeignKeyTargetSerializer(serializers.ModelSerializer):
+ sources = serializers.ManySlugRelatedField(slug_field='name')
+
+ class Meta:
+ model = ForeignKeyTarget
+
+
+class ForeignKeySourceSerializer(serializers.ModelSerializer):
+ target = serializers.SlugRelatedField(slug_field='name')
+
+ class Meta:
+ model = ForeignKeySource
+
+
+class NullableForeignKeySourceSerializer(serializers.ModelSerializer):
target = serializers.SlugRelatedField(slug_field='name', null=True)
class Meta:
@@ -11,6 +25,132 @@ class NullableSlugSourceSerializer(serializers.ModelSerializer):
# TODO: M2M Tests, FKTests (Non-nulable), One2One
+class PKForeignKeyTests(TestCase):
+ def setUp(self):
+ target = ForeignKeyTarget(name='target-1')
+ target.save()
+ new_target = ForeignKeyTarget(name='target-2')
+ new_target.save()
+ for idx in range(1, 4):
+ source = ForeignKeySource(name='source-%d' % idx, target=target)
+ source.save()
+
+ def test_foreign_key_retrieve(self):
+ queryset = ForeignKeySource.objects.all()
+ serializer = ForeignKeySourceSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': u'source-1', 'target': 'target-1'},
+ {'id': 2, 'name': u'source-2', 'target': 'target-1'},
+ {'id': 3, 'name': u'source-3', 'target': 'target-1'}
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_reverse_foreign_key_retrieve(self):
+ queryset = ForeignKeyTarget.objects.all()
+ serializer = ForeignKeyTargetSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': u'target-1', 'sources': ['source-1', 'source-2', 'source-3']},
+ {'id': 2, 'name': u'target-2', 'sources': []},
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_foreign_key_update(self):
+ data = {'id': 1, 'name': u'source-1', 'target': 'target-2'}
+ instance = ForeignKeySource.objects.get(pk=1)
+ serializer = ForeignKeySourceSerializer(instance, data=data)
+ self.assertTrue(serializer.is_valid())
+ self.assertEquals(serializer.data, data)
+ serializer.save()
+
+ # Ensure source 1 is updated, and everything else is as expected
+ queryset = ForeignKeySource.objects.all()
+ serializer = ForeignKeySourceSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': u'source-1', 'target': 'target-2'},
+ {'id': 2, 'name': u'source-2', 'target': 'target-1'},
+ {'id': 3, 'name': u'source-3', 'target': 'target-1'}
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_foreign_key_update_incorrect_type(self):
+ data = {'id': 1, 'name': u'source-1', 'target': 123}
+ instance = ForeignKeySource.objects.get(pk=1)
+ serializer = ForeignKeySourceSerializer(instance, data=data)
+ self.assertFalse(serializer.is_valid())
+ self.assertEquals(serializer.errors, {'target': [u'Object with name=123 does not exist.']})
+
+ def test_reverse_foreign_key_update(self):
+ data = {'id': 2, 'name': u'target-2', 'sources': ['source-1', 'source-3']}
+ instance = ForeignKeyTarget.objects.get(pk=2)
+ serializer = ForeignKeyTargetSerializer(instance, data=data)
+ self.assertTrue(serializer.is_valid())
+ # We shouldn't have saved anything to the db yet since save
+ # hasn't been called.
+ queryset = ForeignKeyTarget.objects.all()
+ new_serializer = ForeignKeyTargetSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': u'target-1', 'sources': ['source-1', 'source-2', 'source-3']},
+ {'id': 2, 'name': u'target-2', 'sources': []},
+ ]
+ self.assertEquals(new_serializer.data, expected)
+
+ serializer.save()
+ self.assertEquals(serializer.data, data)
+
+ # Ensure target 2 is update, and everything else is as expected
+ queryset = ForeignKeyTarget.objects.all()
+ serializer = ForeignKeyTargetSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': u'target-1', 'sources': ['source-2']},
+ {'id': 2, 'name': u'target-2', 'sources': ['source-1', 'source-3']},
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_foreign_key_create(self):
+ data = {'id': 4, 'name': u'source-4', 'target': 'target-2'}
+ serializer = ForeignKeySourceSerializer(data=data)
+ serializer.is_valid()
+ self.assertTrue(serializer.is_valid())
+ obj = serializer.save()
+ self.assertEquals(serializer.data, data)
+ self.assertEqual(obj.name, u'source-4')
+
+ # Ensure source 4 is added, and everything else is as expected
+ queryset = ForeignKeySource.objects.all()
+ serializer = ForeignKeySourceSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': u'source-1', 'target': 'target-1'},
+ {'id': 2, 'name': u'source-2', 'target': 'target-1'},
+ {'id': 3, 'name': u'source-3', 'target': 'target-1'},
+ {'id': 4, 'name': u'source-4', 'target': 'target-2'},
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_reverse_foreign_key_create(self):
+ data = {'id': 3, 'name': u'target-3', 'sources': ['source-1', 'source-3']}
+ serializer = ForeignKeyTargetSerializer(data=data)
+ self.assertTrue(serializer.is_valid())
+ obj = serializer.save()
+ self.assertEquals(serializer.data, data)
+ self.assertEqual(obj.name, u'target-3')
+
+ # Ensure target 3 is added, and everything else is as expected
+ queryset = ForeignKeyTarget.objects.all()
+ serializer = ForeignKeyTargetSerializer(queryset)
+ expected = [
+ {'id': 1, 'name': u'target-1', 'sources': ['source-2']},
+ {'id': 2, 'name': u'target-2', 'sources': []},
+ {'id': 3, 'name': u'target-3', 'sources': ['source-1', 'source-3']},
+ ]
+ self.assertEquals(serializer.data, expected)
+
+ def test_foreign_key_update_with_invalid_null(self):
+ data = {'id': 1, 'name': u'source-1', 'target': None}
+ instance = ForeignKeySource.objects.get(pk=1)
+ serializer = ForeignKeySourceSerializer(instance, data=data)
+ self.assertFalse(serializer.is_valid())
+ self.assertEquals(serializer.errors, {'target': [u'Value may not be null']})
+
class SlugNullableForeignKeyTests(TestCase):
def setUp(self):
@@ -24,7 +164,7 @@ class SlugNullableForeignKeyTests(TestCase):
def test_foreign_key_retrieve_with_null(self):
queryset = NullableForeignKeySource.objects.all()
- serializer = NullableSlugSourceSerializer(queryset)
+ serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
{'id': 1, 'name': u'source-1', 'target': 'target-1'},
{'id': 2, 'name': u'source-2', 'target': 'target-1'},
@@ -34,7 +174,7 @@ class SlugNullableForeignKeyTests(TestCase):
def test_foreign_key_create_with_valid_null(self):
data = {'id': 4, 'name': u'source-4', 'target': None}
- serializer = NullableSlugSourceSerializer(data=data)
+ serializer = NullableForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, data)
@@ -42,7 +182,7 @@ class SlugNullableForeignKeyTests(TestCase):
# Ensure source 4 is created, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
- serializer = NullableSlugSourceSerializer(queryset)
+ serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
{'id': 1, 'name': u'source-1', 'target': 'target-1'},
{'id': 2, 'name': u'source-2', 'target': 'target-1'},
@@ -58,7 +198,7 @@ class SlugNullableForeignKeyTests(TestCase):
"""
data = {'id': 4, 'name': u'source-4', 'target': ''}
expected_data = {'id': 4, 'name': u'source-4', 'target': None}
- serializer = NullableSlugSourceSerializer(data=data)
+ serializer = NullableForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
obj = serializer.save()
self.assertEquals(serializer.data, expected_data)
@@ -66,7 +206,7 @@ class SlugNullableForeignKeyTests(TestCase):
# Ensure source 4 is created, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
- serializer = NullableSlugSourceSerializer(queryset)
+ serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
{'id': 1, 'name': u'source-1', 'target': 'target-1'},
{'id': 2, 'name': u'source-2', 'target': 'target-1'},
@@ -78,14 +218,14 @@ class SlugNullableForeignKeyTests(TestCase):
def test_foreign_key_update_with_valid_null(self):
data = {'id': 1, 'name': u'source-1', 'target': None}
instance = NullableForeignKeySource.objects.get(pk=1)
- serializer = NullableSlugSourceSerializer(instance, data=data)
+ serializer = NullableForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
self.assertEquals(serializer.data, data)
serializer.save()
# Ensure source 1 is updated, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
- serializer = NullableSlugSourceSerializer(queryset)
+ serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
{'id': 1, 'name': u'source-1', 'target': None},
{'id': 2, 'name': u'source-2', 'target': 'target-1'},
@@ -101,14 +241,14 @@ class SlugNullableForeignKeyTests(TestCase):
data = {'id': 1, 'name': u'source-1', 'target': ''}
expected_data = {'id': 1, 'name': u'source-1', 'target': None}
instance = NullableForeignKeySource.objects.get(pk=1)
- serializer = NullableSlugSourceSerializer(instance, data=data)
+ serializer = NullableForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
self.assertEquals(serializer.data, expected_data)
serializer.save()
# Ensure source 1 is updated, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
- serializer = NullableSlugSourceSerializer(queryset)
+ serializer = NullableForeignKeySourceSerializer(queryset)
expected = [
{'id': 1, 'name': u'source-1', 'target': None},
{'id': 2, 'name': u'source-2', 'target': 'target-1'},