TL DR . There is currently no sensible way to do this without creating a custom Serializer
/ Deserializer
.
The problem with models that have common relationships is that Django does not see target
as a field at all, only target_content_type
and target_object_id
, and it tries to serialize and deserialize them separately.
The classes responsible for serializing and deserializing Django models are in the django.core.serializers.base
and django.core.serializers.python
. All the rest ( xml
, json
and yaml
) apply to any of them (and python
continues to base
). Field serialization is performed as follows (irrelevant lines):
for obj in queryset: for field in concrete_model._meta.local_fields: if field.rel is None: self.handle_field(obj, field) else: self.handle_fk_field(obj, field)
Here's the first complication: the ContentType
foreign key ContentType
processed normally, with natural keys, as we expected. But PositiveIntegerField
handled by handle_field
, which is executed as follows:
def handle_field(self, obj, field): value = field._get_val_from_obj(obj)
i.e. the only setting option here (subclassing PositiveIntegerField
and defining custom value_to_string
) will have no effect since the serializer will not call This. Changing the target_object_id
data target_object_id
to something larger than an integer is likely to break many other things, so this is not an option.
We could define our custom handle_field
to emit natural keys in this case, but then a second complication arises: deserialization is done as follows:
for (field_name, field_value) in six.iteritems(d["fields"]): field = Model._meta.get_field(field_name) ... data[field.name] = field.to_python(field_value)
Even if we set up the to_python
method, it only field_value
, outside the context of the object. This is not a problem when using integers, since it will be interpreted as the primary key of the model , regardless of which model . But for deserializing a natural key, we first need to know which model this key belongs to and that the information is not available if we did not receive a reference to the object (and the target_content_type
field target_content_type
already been deserialized).
As you can see, this is not an impossible task - supporting natural keys in a generic relationship, but to achieve this, a lot needs to be changed in the serialization and deserialization code. Necessary steps, then (if someone copes with the task):
- Create a custom
Field
extending PositiveIntegerField
using methods for encoding / decoding an object - calling the reference models natural_key
and get_by_natural_key
; - Cancel the
handle_field
serializer to invoke the encoder, if any; - Introduce your own deserializer, which: 1) imposes some order in the fields, ensuring that the content type is deserialized in front of the natural key; 2) calls the decoder, passing not only
field_value
, but also a link to the decoded ContentType
.