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:
lukasbuenger 2013-09-16 15:37:31 +02:00
parent 610da51d65
commit 529effdfc7
3 changed files with 21 additions and 45 deletions

View File

@ -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.
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:
@ -454,14 +455,14 @@ The following operations would create a `Tag` object with it's `tagged_object` p
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_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:
* 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.
* You can mix `ModelSerializer` and `HyperlinkedRelatedField` in one `GenericRelatedField` configuration dictionary for deserialization purposes. It is considered bad practice though.
* If you mix `ModelSerializer` and `HyperlinkedRelatedField` in one `GenericRelatedField` configuration dictionary, the serialization process (PUT/POST) will raise a `ConfigurationError`.
* 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].

View File

@ -54,14 +54,11 @@ class GenericRelatedField(serializers.WritableField):
def from_native(self, value):
# Get the serializer responsible for input resolving
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)
# The following is necessary due to the inconsistency of the `from_native` argument count when a serializer
# accepts files.
args = [value]
import inspect
if len(inspect.getargspec(serializer.from_native).args) > 2:
args.append(None)
import pdb
pdb.set_trace()
return serializer.from_native(value)
def determine_deserializer_for_data(self, value):
@ -73,37 +70,14 @@ class GenericRelatedField(serializers.WritableField):
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.
for serializer in six.itervalues(self.serializers):
if not isinstance(serializer, serializers.HyperlinkedRelatedField):
raise ConfigurationError('Please use HyperlinkedRelatedField as serializers on GenericRelatedField \
instances with read_only=False or set read_only=True.')
# This excerpt is an exact copy of ``rest_framework.relations.HyperlinkedRelatedField``, Line 363
# From here until ...
try:
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
try:
serializer.from_native(value)
# Returns the first serializer that can handle the value without errors.
return serializer
except Exception:
pass
return None

View File

@ -241,10 +241,11 @@ class TestGenericRelatedFieldSerialization(TestCase):
serializer = TagSerializer(data={
'tag': 'reminder',
'tagged_item': reverse('note-detail', kwargs={'pk': self.note.pk})
'tagged_item': 'just a string'
})
self.assertRaises(ConfigurationError, serializer.is_valid)
def test_not_registered_view_name(self):
class TagSerializer(serializers.ModelSerializer):
tagged_item = GenericRelatedField({