TL; DR - En la actualidad no hay manera sensata de hacerlo, sin llegar a la creación de una costumbre Serializer
/Deserializer
par.
El problema con los modelos que tienen las relaciones genéricas es que Django no ver target
como un campo en absoluto, solamente target_content_type
y target_object_id
, y se intenta serializar y deserializar ellos individualmente.
Las clases responsables de serializar y deserializar modelos de Django se encuentran en los módulos django.core.serializers.base
y django.core.serializers.python
. Todos los demás (xml
, json
y yaml
) amplían cualquiera de ellos (y python
se extiende base
). La serialización de campo se hace de la siguiente (líneas irrelevantes oitirán):
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)
Aquí está la primera complicación: la clave externa a ContentType
se maneja bien, con teclas naturales como esperábamos. Pero el PositiveIntegerField
es manejado por handle_field
, que se implementa como esto:
def handle_field(self, obj, field):
value = field._get_val_from_obj(obj)
# Protected types (i.e., primitives like None, numbers, dates,
# and Decimals) are passed through as is. All other values are
# converted to string first.
if is_protected_type(value):
self._current[field.name] = value
else:
self._current[field.name] = field.value_to_string(obj)
es decir, la única posibilidad de personalización aquí (subclasificación de PositiveIntegerField
y la definición de un custom value_to_string
) no tendrá ningún efecto, ya que el serializador no llamarlo. Cambiar el tipo de datos de target_object_id
a algo más que un entero probablemente romperá muchas otras cosas, por lo que no es una opción.
Nos podría definir nuestra costumbre handle_field
para emitir claves naturales, en este caso, pero luego viene la segunda complicación: la deserialización se hace así:
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)
Incluso si personalizamos el método to_python
, se actúa solo en el field_value
, fuera del contexto del objeto. No es un problema cuando se usan números enteros, ya que se interpretará como la clave principal del modelo , independientemente del modelo que sea. Pero para deserializar una clave natural, primero necesitamos saber a qué modelo pertenece esa clave, y esa información no está disponible a menos que obtengamos una referencia al objeto (y el campo target_content_type
ya se ha deserializado).
Como puede ver, no es una tarea imposible, ya que admite claves naturales en relaciones genéricas, pero para lograr eso, habría que cambiar muchas cosas en el código de serialización y deserialización.Las medidas necesarias, entonces (si alguien se siente a la altura) son:
- Crea una alfombrilla de
Field
extendiéndose PositiveIntegerField
, con métodos para codificar/decodificar un objeto - pidiendo que los modelos de referencia natural_key
y get_by_natural_key
;
- Ignorar el serializador
handle_field
para llamar al codificador si está presente;
- Implemente un deserializador personalizado que: 1) impone cierto orden en los campos, asegurando que el tipo de contenido se deserialice antes que la clave natural; 2) llama al decodificador, pasando no solo el
field_value
sino también una referencia al decodificado ContentType
.
Tengo curiosidad, ¿se encuentra alguna solución para esto? Hice una búsqueda pero nada útil ha surgido. – shanyu
aún no, pero lo actualizaré con una solución si encuentro uno – Riz
¿Podría elaborar su pregunta? Algún ejemplo. – Rohan