diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index eb6fca890..5bb381d32 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -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]. diff --git a/rest_framework/genericrelations.py b/rest_framework/genericrelations.py index 963871799..e9c649384 100644 --- a/rest_framework/genericrelations.py +++ b/rest_framework/genericrelations.py @@ -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 \ No newline at end of file + try: + serializer.from_native(value) + # Returns the first serializer that can handle the value without errors. + return serializer + except Exception: + pass + return None diff --git a/rest_framework/tests/relations_generic.py b/rest_framework/tests/relations_generic.py index 9f991648f..481e6f84f 100644 --- a/rest_framework/tests/relations_generic.py +++ b/rest_framework/tests/relations_generic.py @@ -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({