mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-05 21:10:13 +03:00
Merge 0705671731
into 8bd5e7e612
This commit is contained in:
commit
93b549730b
|
@ -308,7 +308,7 @@ See the Django documentation on [reverse relationships][reverse-relationships] f
|
||||||
|
|
||||||
## Generic relationships
|
## Generic relationships
|
||||||
|
|
||||||
If you want to serialize a generic foreign key, you need to define a custom field, to determine explicitly how you want serialize the targets of the relationship.
|
If you want to serialize a generic foreign key, you need to define a `GenericRelatedField` with a configuration dictionary as first argument, that describes the representation of each model you possibly want to connect to the generic foreign key.
|
||||||
|
|
||||||
For example, given the following model for a tag, which has a generic relationship with other arbitrary models:
|
For example, given the following model for a tag, which has a generic relationship with other arbitrary models:
|
||||||
|
|
||||||
|
@ -343,40 +343,112 @@ And the following two models, which may be have associated tags:
|
||||||
text = models.CharField(max_length=1000)
|
text = models.CharField(max_length=1000)
|
||||||
tags = GenericRelation(TaggedItem)
|
tags = GenericRelation(TaggedItem)
|
||||||
|
|
||||||
We could define a custom field that could be used to serialize tagged instances, using the type of each instance to determine how it should be serialized.
|
Now we define serializers for each model that may get associated with tags.
|
||||||
|
|
||||||
class TaggedObjectRelatedField(serializers.RelatedField):
|
class BookmarkSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
A custom field to use for the `tagged_object` generic relationship.
|
A simple `ModelSerializer` subclass for serializing `Bookmark` objects.
|
||||||
"""
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Bookmark
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
def to_native(self, value):
|
|
||||||
|
class NoteSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
A simple `ModelSerializer` subclass for serializing `Note` objects.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Note
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
|
The model serializer for the `Tag` model could look like this:
|
||||||
|
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
A `Tag` serializer with a `GenericRelatedField` mapping all possible
|
||||||
|
models to their respective serializers.
|
||||||
|
"""
|
||||||
|
tagged_object = serializers.GenericRelatedField({
|
||||||
|
Bookmark: BookmarkSerializer(),
|
||||||
|
Note: NoteSerializer()
|
||||||
|
}, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
|
The JSON representation of a `Tag` object with `name='django'` and its generic foreign key pointing at a `Bookmark` object with `url='https://www.djangoproject.com/'` would look like this:
|
||||||
|
|
||||||
|
{
|
||||||
|
'tagged_object': {
|
||||||
|
'url': 'https://www.djangoproject.com/'
|
||||||
|
},
|
||||||
|
'tag_name': 'django'
|
||||||
|
}
|
||||||
|
|
||||||
|
If you want to have your generic foreign key represented as hyperlink, simply use `HyperlinkedRelatedField` objects:
|
||||||
|
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
A `Tag` serializer with a `GenericRelatedField` mapping all possible
|
||||||
|
models to properly set up `HyperlinkedRelatedField`s.
|
||||||
|
"""
|
||||||
|
tagged_object = serializers.GenericRelatedField({
|
||||||
|
Bookmark: serializers.HyperlinkedRelatedField(view_name='bookmark-detail'),
|
||||||
|
Note: serializers.HyperlinkedRelatedField(view_name='note-detail'),
|
||||||
|
}, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
|
The JSON representation of the same `Tag` example object could now look something like this:
|
||||||
|
|
||||||
|
{
|
||||||
|
'tagged_object': '/bookmark/1/',
|
||||||
|
'tag_name': 'django'
|
||||||
|
}
|
||||||
|
|
||||||
|
These examples cover the default behavior of generic foreign key representation. However, you may also want to write to generic foreign key fields through your API.
|
||||||
|
|
||||||
|
By default, a `GenericRelatedField` iterates over its nested serializers and returns the value of the first serializer, that is actually able to perform `from_native`` on the input value without any errors.
|
||||||
|
Note, that (at the moment) only `HyperlinkedRelatedField` is able to serialize model objects out of the box.
|
||||||
|
|
||||||
|
This `Tag` serializer is able to write to it's generic foreign key field:
|
||||||
|
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
Serialize tagged objects to a simple textual representation.
|
A `Tag` serializer with a `GenericRelatedField` mapping all possible
|
||||||
"""
|
models to properly set up `HyperlinkedRelatedField`s.
|
||||||
if isinstance(value, Bookmark):
|
|
||||||
return 'Bookmark: ' + value.url
|
|
||||||
elif isinstance(value, Note):
|
|
||||||
return 'Note: ' + value.text
|
|
||||||
raise Exception('Unexpected type of tagged object')
|
|
||||||
|
|
||||||
If you need the target of the relationship to have a nested representation, you can use the required serializers inside the `.to_native()` method:
|
|
||||||
|
|
||||||
def to_native(self, value):
|
|
||||||
"""
|
"""
|
||||||
Serialize bookmark instances using a bookmark serializer,
|
tagged_object = serializers.GenericRelatedField({
|
||||||
and note instances using a note serializer.
|
Bookmark: serializers.HyperlinkedRelatedField(view_name='bookmark-detail'),
|
||||||
"""
|
Note: serializers.HyperlinkedRelatedField(view_name='note-detail'),
|
||||||
if isinstance(value, Bookmark):
|
}, read_only=False)
|
||||||
serializer = BookmarkSerializer(value)
|
|
||||||
elif isinstance(value, Note):
|
|
||||||
serializer = NoteSerializer(value)
|
|
||||||
else:
|
|
||||||
raise Exception('Unexpected type of tagged object')
|
|
||||||
|
|
||||||
return serializer.data
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
Note that reverse generic keys, expressed using the `GenericRelation` field, can be serialized using the regular relational field types, since the type of the target in the relationship is always known.
|
The following operations would create a `Tag` object with it's `tagged_object` property pointing at the `Bookmark` object found at the given detail end point.
|
||||||
|
|
||||||
|
tag_serializer = TagSerializer(data={
|
||||||
|
'tag_name': 'python'
|
||||||
|
'tagged_object': '/bookmark/1/'
|
||||||
|
})
|
||||||
|
|
||||||
|
tag_serializer.is_valid()
|
||||||
|
tag_serializer.save()
|
||||||
|
|
||||||
|
If you feel that this default behavior doesn't suit your needs, you can subclass `GenericRelatedField` and override its `determine_deserializer_for_data` or `determine_serializer_for_data` respectively to implement your own way of decision-making.
|
||||||
|
|
||||||
|
A few things you should note:
|
||||||
|
|
||||||
|
* Although `GenericForeignKey` fields can be set to any model object, the `GenericRelatedField` only handles models explicitly defined in its configuration dictionary.
|
||||||
|
* Reverse generic keys, expressed using the `GenericRelation` field, can be serialized using the regular relational field types, since the type of the target in the relationship is always known.
|
||||||
|
* Please take into account that the order in which you register serializers matters as far as write operations are concerned.
|
||||||
|
* Unless you provide custom serializer determination, only `HyperlinkedRelatedFields` provide write access to generic model relations.
|
||||||
|
|
||||||
For more information see [the Django documentation on generic relations][generic-relations].
|
For more information see [the Django documentation on generic relations][generic-relations].
|
||||||
|
|
||||||
|
|
87
rest_framework/genericrelations.py
Normal file
87
rest_framework/genericrelations.py
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from rest_framework import six
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.exceptions import ConfigurationError
|
||||||
|
|
||||||
|
|
||||||
|
class GenericRelatedField(serializers.WritableField):
|
||||||
|
"""
|
||||||
|
Represents a generic relation foreign key.
|
||||||
|
It's actually more of a wrapper, that delegates the logic to registered serializers based on the `Model` class.
|
||||||
|
"""
|
||||||
|
default_error_messages = {
|
||||||
|
'no_model_match': _('Invalid model - model not available.'),
|
||||||
|
'no_url_match': _('Invalid hyperlink - No URL match'),
|
||||||
|
'incorrect_url_match': _('Invalid hyperlink - view name not available'),
|
||||||
|
}
|
||||||
|
|
||||||
|
form_field_class = forms.URLField
|
||||||
|
|
||||||
|
def __init__(self, serializers, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Needs an extra parameter `serializers` which has to be a dict key: value being `Model`: serializer.
|
||||||
|
"""
|
||||||
|
super(GenericRelatedField, self).__init__(*args, **kwargs)
|
||||||
|
self.serializers = serializers
|
||||||
|
for model, serializer in six.iteritems(self.serializers):
|
||||||
|
# We have to do it, because the serializer can't access a explicit manager through the
|
||||||
|
# GenericForeignKey field on the model.
|
||||||
|
if hasattr(serializer, 'queryset') and serializer.queryset is None:
|
||||||
|
serializer.queryset = model._default_manager.all()
|
||||||
|
|
||||||
|
def field_to_native(self, obj, field_name):
|
||||||
|
"""
|
||||||
|
Delegates to the `to_native` method of the serializer registered under obj.__class__
|
||||||
|
"""
|
||||||
|
value = super(GenericRelatedField, self).field_to_native(obj, field_name)
|
||||||
|
serializer = self.determine_deserializer_for_data(value)
|
||||||
|
|
||||||
|
# Necessary because of context, field resolving etc.
|
||||||
|
serializer.initialize(self.parent, field_name)
|
||||||
|
return serializer.to_native(value)
|
||||||
|
|
||||||
|
def to_native(self, value):
|
||||||
|
# Override to prevent the simplifying process of value as present in `WritableField.to_native`.
|
||||||
|
return value
|
||||||
|
|
||||||
|
def from_native(self, value):
|
||||||
|
# Get the serializer responsible for input resolving
|
||||||
|
try:
|
||||||
|
serializer = self.determine_serializer_for_data(value)
|
||||||
|
except ConfigurationError as e:
|
||||||
|
raise ValidationError(e)
|
||||||
|
serializer.initialize(self.parent, self.source)
|
||||||
|
return serializer.from_native(value)
|
||||||
|
|
||||||
|
def determine_deserializer_for_data(self, value):
|
||||||
|
try:
|
||||||
|
model = value.__class__
|
||||||
|
serializer = self.serializers[model]
|
||||||
|
except KeyError:
|
||||||
|
raise ValidationError(self.error_messages['no_model_match'])
|
||||||
|
return serializer
|
||||||
|
|
||||||
|
def determine_serializer_for_data(self, value):
|
||||||
|
# While one could easily execute the "try" block within from_native and reduce operations, I consider the
|
||||||
|
# concept of serializing is already very naive and vague, that's why I'd go for stringency with the deserialization
|
||||||
|
# process here.
|
||||||
|
serializers = []
|
||||||
|
for serializer in six.itervalues(self.serializers):
|
||||||
|
try:
|
||||||
|
serializer.from_native(value)
|
||||||
|
# Collects all serializers that can handle the input data.
|
||||||
|
serializers.append(serializer)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
# If no serializer found, raise error.
|
||||||
|
l = len(serializers)
|
||||||
|
if l < 1:
|
||||||
|
raise ConfigurationError('Could not determine a valid serializer for value %r.' % value)
|
||||||
|
elif l > 1:
|
||||||
|
raise ConfigurationError('There were multiple serializers found for value %r.' % value)
|
||||||
|
return serializers[0]
|
311
rest_framework/tests/relations_generic.py
Normal file
311
rest_framework/tests/relations_generic.py
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.contrib.contenttypes.generic import GenericRelation, GenericForeignKey
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import models
|
||||||
|
from django.test import TestCase, RequestFactory
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_framework.compat import patterns, url
|
||||||
|
from rest_framework.exceptions import ConfigurationError
|
||||||
|
from rest_framework.genericrelations import GenericRelatedField
|
||||||
|
from rest_framework.reverse import reverse
|
||||||
|
|
||||||
|
factory = RequestFactory()
|
||||||
|
request = factory.get('/') # Just to ensure we have a request in the serializer context
|
||||||
|
|
||||||
|
def dummy_view(request, pk):
|
||||||
|
pass
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^bookmark/(?P<pk>[0-9]+)/$', dummy_view, name='bookmark-detail'),
|
||||||
|
url(r'^note/(?P<pk>[0-9]+)/$', dummy_view, name='note-detail'),
|
||||||
|
url(r'^tag/(?P<pk>[0-9]+)/$', dummy_view, name='tag-detail'),
|
||||||
|
url(r'^contact/(?P<my_own_slug>[-\w]+)/$', dummy_view, name='contact-detail'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Tag(models.Model):
|
||||||
|
"""
|
||||||
|
Tags have a descriptive slug, and are attached to an arbitrary object.
|
||||||
|
"""
|
||||||
|
tag = models.SlugField()
|
||||||
|
content_type = models.ForeignKey(ContentType)
|
||||||
|
object_id = models.PositiveIntegerField()
|
||||||
|
tagged_item = GenericForeignKey('content_type', 'object_id')
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.tag
|
||||||
|
|
||||||
|
|
||||||
|
class Bookmark(models.Model):
|
||||||
|
"""
|
||||||
|
A URL bookmark that may have multiple tags attached.
|
||||||
|
"""
|
||||||
|
url = models.URLField()
|
||||||
|
tags = GenericRelation(Tag)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return 'Bookmark: %s' % self.url
|
||||||
|
|
||||||
|
|
||||||
|
class Note(models.Model):
|
||||||
|
"""
|
||||||
|
A textual note that may have multiple tags attached.
|
||||||
|
"""
|
||||||
|
text = models.TextField()
|
||||||
|
tags = GenericRelation(Tag)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return 'Note: %s' % self.text
|
||||||
|
|
||||||
|
|
||||||
|
class BookmarkSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Bookmark
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
|
|
||||||
|
class NoteSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Note
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
|
|
||||||
|
class TestGenericRelatedFieldDeserialization(TestCase):
|
||||||
|
|
||||||
|
urls = 'rest_framework.tests.relations_generic'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.bookmark = Bookmark.objects.create(url='https://www.djangoproject.com/')
|
||||||
|
Tag.objects.create(tagged_item=self.bookmark, tag='django')
|
||||||
|
Tag.objects.create(tagged_item=self.bookmark, tag='python')
|
||||||
|
self.note = Note.objects.create(text='Remember the milk')
|
||||||
|
Tag.objects.create(tagged_item=self.note, tag='reminder')
|
||||||
|
|
||||||
|
def test_relations_as_hyperlinks(self):
|
||||||
|
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
tagged_item = GenericRelatedField({
|
||||||
|
Bookmark: serializers.HyperlinkedRelatedField(view_name='bookmark-detail'),
|
||||||
|
Note: serializers.HyperlinkedRelatedField(view_name='note-detail'),
|
||||||
|
}, source='tagged_item', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', 'content_type', 'object_id', )
|
||||||
|
|
||||||
|
serializer = TagSerializer(Tag.objects.all(), many=True)
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
'tagged_item': '/bookmark/1/',
|
||||||
|
'tag': 'django',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'tagged_item': '/bookmark/1/',
|
||||||
|
'tag': 'python',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'tagged_item': '/note/1/',
|
||||||
|
'tag': 'reminder'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.assertEqual(serializer.data, expected)
|
||||||
|
|
||||||
|
def test_relations_as_nested(self):
|
||||||
|
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
tagged_item = GenericRelatedField({
|
||||||
|
Bookmark: BookmarkSerializer(),
|
||||||
|
Note: NoteSerializer(),
|
||||||
|
}, source='tagged_item', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', 'content_type', 'object_id', )
|
||||||
|
|
||||||
|
serializer = TagSerializer(Tag.objects.all(), many=True)
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
'tagged_item': {
|
||||||
|
'url': 'https://www.djangoproject.com/'
|
||||||
|
},
|
||||||
|
'tag': 'django'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'tagged_item': {
|
||||||
|
'url': 'https://www.djangoproject.com/'
|
||||||
|
},
|
||||||
|
'tag': 'python'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'tagged_item': {
|
||||||
|
'text': 'Remember the milk',
|
||||||
|
},
|
||||||
|
'tag': 'reminder'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.assertEqual(serializer.data, expected)
|
||||||
|
|
||||||
|
def test_mixed_serializers(self):
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
tagged_item = GenericRelatedField({
|
||||||
|
Bookmark: BookmarkSerializer(),
|
||||||
|
Note: serializers.HyperlinkedRelatedField(view_name='note-detail'),
|
||||||
|
}, source='tagged_item', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', 'content_type', 'object_id', )
|
||||||
|
|
||||||
|
serializer = TagSerializer(Tag.objects.all(), many=True)
|
||||||
|
expected = [
|
||||||
|
{
|
||||||
|
'tagged_item': {
|
||||||
|
'url': 'https://www.djangoproject.com/'
|
||||||
|
},
|
||||||
|
'tag': 'django'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'tagged_item': {
|
||||||
|
'url': 'https://www.djangoproject.com/'
|
||||||
|
},
|
||||||
|
'tag': 'python'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'tagged_item': '/note/1/',
|
||||||
|
'tag': 'reminder'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.assertEqual(serializer.data, expected)
|
||||||
|
|
||||||
|
def test_invalid_model(self):
|
||||||
|
# Leaving out the Note model should result in a ValidationError
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
tagged_item = GenericRelatedField({
|
||||||
|
Bookmark: BookmarkSerializer(),
|
||||||
|
}, source='tagged_item', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', 'content_type', 'object_id', )
|
||||||
|
serializer = TagSerializer(Tag.objects.all(), many=True)
|
||||||
|
|
||||||
|
def call_data():
|
||||||
|
return serializer.data
|
||||||
|
self.assertRaises(ValidationError, call_data)
|
||||||
|
|
||||||
|
|
||||||
|
class TestGenericRelatedFieldSerialization(TestCase):
|
||||||
|
|
||||||
|
urls = 'rest_framework.tests.relations_generic'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.bookmark = Bookmark.objects.create(url='https://www.djangoproject.com/')
|
||||||
|
Tag.objects.create(tagged_item=self.bookmark, tag='django')
|
||||||
|
Tag.objects.create(tagged_item=self.bookmark, tag='python')
|
||||||
|
self.note = Note.objects.create(text='Remember the milk')
|
||||||
|
|
||||||
|
def test_hyperlink_serialization(self):
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
tagged_item = GenericRelatedField({
|
||||||
|
Bookmark: serializers.HyperlinkedRelatedField(view_name='bookmark-detail'),
|
||||||
|
Note: serializers.HyperlinkedRelatedField(view_name='note-detail'),
|
||||||
|
}, source='tagged_item', read_only=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', 'content_type', 'object_id', )
|
||||||
|
|
||||||
|
serializer = TagSerializer(data={
|
||||||
|
'tag': 'reminder',
|
||||||
|
'tagged_item': reverse('note-detail', kwargs={'pk': self.note.pk})
|
||||||
|
})
|
||||||
|
serializer.is_valid()
|
||||||
|
expected = {
|
||||||
|
'tagged_item': '/note/1/',
|
||||||
|
'tag': 'reminder'
|
||||||
|
}
|
||||||
|
self.assertEqual(serializer.data, expected)
|
||||||
|
|
||||||
|
def test_configuration_error(self):
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
tagged_item = GenericRelatedField({
|
||||||
|
Bookmark: BookmarkSerializer(),
|
||||||
|
Note: serializers.HyperlinkedRelatedField(view_name='note-detail'),
|
||||||
|
}, source='tagged_item', read_only=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', 'content_type', 'object_id', )
|
||||||
|
|
||||||
|
serializer = TagSerializer(data={
|
||||||
|
'tag': 'reminder',
|
||||||
|
'tagged_item': 'just a string'
|
||||||
|
})
|
||||||
|
|
||||||
|
with self.assertRaises(ConfigurationError):
|
||||||
|
serializer.fields['tagged_item'].determine_serializer_for_data('just a string')
|
||||||
|
|
||||||
|
def test_not_registered_view_name(self):
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
tagged_item = GenericRelatedField({
|
||||||
|
Bookmark: serializers.HyperlinkedRelatedField(view_name='bookmark-detail'),
|
||||||
|
}, source='tagged_item', read_only=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', 'content_type', 'object_id', )
|
||||||
|
|
||||||
|
serializer = TagSerializer(data={
|
||||||
|
'tag': 'reminder',
|
||||||
|
'tagged_item': reverse('note-detail', kwargs={'pk': self.note.pk})
|
||||||
|
})
|
||||||
|
self.assertFalse(serializer.is_valid())
|
||||||
|
|
||||||
|
def test_invalid_url(self):
|
||||||
|
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
tagged_item = GenericRelatedField({
|
||||||
|
Bookmark: serializers.HyperlinkedRelatedField(view_name='bookmark-detail'),
|
||||||
|
}, source='tagged_item', read_only=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', 'content_type', 'object_id', )
|
||||||
|
|
||||||
|
serializer = TagSerializer(data={
|
||||||
|
'tag': 'reminder',
|
||||||
|
'tagged_item': 'foo-bar'
|
||||||
|
})
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'tagged_item': ['Could not determine a valid serializer for value %r.' % 'foo-bar']
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertFalse(serializer.is_valid())
|
||||||
|
self.assertEqual(expected, serializer.errors)
|
||||||
|
|
||||||
|
def test_serializer_save(self):
|
||||||
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
|
tagged_item = GenericRelatedField({
|
||||||
|
Bookmark: serializers.HyperlinkedRelatedField(view_name='bookmark-detail'),
|
||||||
|
Note: serializers.HyperlinkedRelatedField(view_name='note-detail'),
|
||||||
|
}, source='tagged_item', read_only=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
exclude = ('id', 'content_type', 'object_id', )
|
||||||
|
|
||||||
|
serializer = TagSerializer(data={
|
||||||
|
'tag': 'reminder',
|
||||||
|
'tagged_item': reverse('note-detail', kwargs={'pk': self.note.pk})
|
||||||
|
})
|
||||||
|
serializer.is_valid()
|
||||||
|
expected = {
|
||||||
|
'tagged_item': '/note/1/',
|
||||||
|
'tag': 'reminder'
|
||||||
|
}
|
||||||
|
serializer.save()
|
||||||
|
tag = Tag.objects.get(pk=3)
|
||||||
|
self.assertEqual(tag.tagged_item, self.note)
|
Loading…
Reference in New Issue
Block a user