mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-02 19:40:13 +03:00
Implementation of serialization process as discussed here: https://groups.google.com/forum/?fromgroups#\!topic/django-rest-framework/BRsLJ92JRrk. Including docs and tests.
This commit is contained in:
parent
610da51d65
commit
529effdfc7
|
@ -426,7 +426,8 @@ The JSON representation of the same `Tag` example object could now look somethin
|
||||||
|
|
||||||
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.
|
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, the `GenericRelatedField` can be used with `read_only=False` only if you use `HyperlinkedRelatedField` as representation for every model you register with your `GenericRelatedField`.
|
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:
|
This `Tag` serializer is able to write to it's generic foreign key field:
|
||||||
|
|
||||||
|
@ -454,14 +455,14 @@ The following operations would create a `Tag` object with it's `tagged_object` p
|
||||||
tag_serializer.is_valid()
|
tag_serializer.is_valid()
|
||||||
tag_serializer.save()
|
tag_serializer.save()
|
||||||
|
|
||||||
If you feel that this default behavior doesn't suit your needs, you can subclass `GenericRelatedField` and override its `determine_serializer_for_data` method to implement your own way of decision-making.
|
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:
|
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.
|
* 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.
|
* 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.
|
||||||
* You can mix `ModelSerializer` and `HyperlinkedRelatedField` in one `GenericRelatedField` configuration dictionary for deserialization purposes. It is considered bad practice though.
|
* Please take into account that the order in which you register serializers matters as far as write operations are concerned.
|
||||||
* If you mix `ModelSerializer` and `HyperlinkedRelatedField` in one `GenericRelatedField` configuration dictionary, the serialization process (PUT/POST) will raise a `ConfigurationError`.
|
* 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].
|
||||||
|
|
||||||
|
|
|
@ -54,14 +54,11 @@ class GenericRelatedField(serializers.WritableField):
|
||||||
def from_native(self, value):
|
def from_native(self, value):
|
||||||
# Get the serializer responsible for input resolving
|
# Get the serializer responsible for input resolving
|
||||||
serializer = self.determine_serializer_for_data(value)
|
serializer = self.determine_serializer_for_data(value)
|
||||||
|
if serializer is None:
|
||||||
|
raise ConfigurationError('Could not determine a valid serializer for value "%r"' % value)
|
||||||
serializer.initialize(self.parent, self.source)
|
serializer.initialize(self.parent, self.source)
|
||||||
|
import pdb
|
||||||
# The following is necessary due to the inconsistency of the `from_native` argument count when a serializer
|
pdb.set_trace()
|
||||||
# accepts files.
|
|
||||||
args = [value]
|
|
||||||
import inspect
|
|
||||||
if len(inspect.getargspec(serializer.from_native).args) > 2:
|
|
||||||
args.append(None)
|
|
||||||
return serializer.from_native(value)
|
return serializer.from_native(value)
|
||||||
|
|
||||||
def determine_deserializer_for_data(self, value):
|
def determine_deserializer_for_data(self, value):
|
||||||
|
@ -73,37 +70,14 @@ class GenericRelatedField(serializers.WritableField):
|
||||||
return serializer
|
return serializer
|
||||||
|
|
||||||
def determine_serializer_for_data(self, value):
|
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.
|
||||||
for serializer in six.itervalues(self.serializers):
|
for serializer in six.itervalues(self.serializers):
|
||||||
if not isinstance(serializer, serializers.HyperlinkedRelatedField):
|
try:
|
||||||
raise ConfigurationError('Please use HyperlinkedRelatedField as serializers on GenericRelatedField \
|
serializer.from_native(value)
|
||||||
instances with read_only=False or set read_only=True.')
|
# Returns the first serializer that can handle the value without errors.
|
||||||
|
return serializer
|
||||||
# This excerpt is an exact copy of ``rest_framework.relations.HyperlinkedRelatedField``, Line 363
|
except Exception:
|
||||||
# From here until ...
|
pass
|
||||||
try:
|
return None
|
||||||
http_prefix = value.startswith('http:') or value.startswith('https:')
|
|
||||||
except AttributeError:
|
|
||||||
msg = self.error_messages['incorrect_type']
|
|
||||||
raise ValidationError(msg % type(value).__name__)
|
|
||||||
|
|
||||||
if http_prefix:
|
|
||||||
# If needed convert absolute URLs to relative path
|
|
||||||
value = urlparse.urlparse(value).path
|
|
||||||
prefix = get_script_prefix()
|
|
||||||
if value.startswith(prefix):
|
|
||||||
value = '/' + value[len(prefix):]
|
|
||||||
try:
|
|
||||||
match = resolve(value)
|
|
||||||
except Exception:
|
|
||||||
raise ValidationError(self.error_messages['no_url_match'])
|
|
||||||
|
|
||||||
# ... here
|
|
||||||
|
|
||||||
matched_serializer = None
|
|
||||||
for serializer in six.itervalues(self.serializers):
|
|
||||||
if serializer.view_name == match.url_name:
|
|
||||||
matched_serializer = serializer
|
|
||||||
|
|
||||||
if matched_serializer is None:
|
|
||||||
raise ValidationError(self.error_messages['incorrect_url_match'])
|
|
||||||
return matched_serializer
|
|
||||||
|
|
|
@ -241,10 +241,11 @@ class TestGenericRelatedFieldSerialization(TestCase):
|
||||||
|
|
||||||
serializer = TagSerializer(data={
|
serializer = TagSerializer(data={
|
||||||
'tag': 'reminder',
|
'tag': 'reminder',
|
||||||
'tagged_item': reverse('note-detail', kwargs={'pk': self.note.pk})
|
'tagged_item': 'just a string'
|
||||||
})
|
})
|
||||||
self.assertRaises(ConfigurationError, serializer.is_valid)
|
self.assertRaises(ConfigurationError, serializer.is_valid)
|
||||||
|
|
||||||
|
|
||||||
def test_not_registered_view_name(self):
|
def test_not_registered_view_name(self):
|
||||||
class TagSerializer(serializers.ModelSerializer):
|
class TagSerializer(serializers.ModelSerializer):
|
||||||
tagged_item = GenericRelatedField({
|
tagged_item = GenericRelatedField({
|
||||||
|
|
Loading…
Reference in New Issue
Block a user