La base de datos de mi aplicación se completa y se mantiene sincronizada con fuentes de datos externas. Tengo un modelo abstracto del que derivan todos los modelos de mi aplicación Django 2.2, definidos de la siguiente manera:
class CommonModel(models.Model):
# Auto-generated by Django, but included in this example for clarity.
# id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
ORIGIN_SOURCEA = '1'
ORIGIN_SOURCEB = '2'
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, 'Source A'),
(ORIGIN_SOURCEB, 'Source B'),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
class A(CommonModel):
some_stuff = models.CharField()
class B(CommonModel):
other_stuff = models.IntegerField()
to_a_fk = models.ForeignKey("myapp.A", on_delete=models.CASCADE)
class C(CommonModel):
more_stuff = models.CharField()
b_m2m = models.ManyToManyField("myapp.B")
El object_id
campo no se puede establecer como único ya que cada fuente de datos que uso en mi aplicación puede tener un objeto con un object_id = 1
. De ahí la necesidad de rastrear el origen del objeto, por el campo object_origin
.
Desafortunadamente, el ORM de Django no admite claves externas de más de una columna.
Problema
Mientras id
mantengo la clave primaria autogenerada en la base de datos ( ), me gustaría hacer que mi clave externa y las relaciones de muchos a muchos sucedan en ambos campos object_id
y en object_origin
lugar de la clave primaria id
.
Lo que he intentado
Pensé en hacer algo como esto:
class CommonModel(models.Model):
# Auto-generated by Django, but included in this example for clarity.
# id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
ORIGIN_SOURCEA = '1'
ORIGIN_SOURCEB = '2'
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, 'Source A'),
(ORIGIN_SOURCEB, 'Source B'),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
def _get_composed_object_origin_id(self):
return f"{self.object_origin}:{self.object_id}"
composed_object_origin_id = property(_get_composed_object_origin_id)
class A(CommonModel):
some_stuff = models.CharField()
class B(CommonModel):
other_stuff = models.IntegerField()
to_a_fk = models.ForeignKey("myapp.A", to_field="composed_object_origin_id", on_delete=models.CASCADE)
Pero Django se queja al respecto:
myapp.B.to_a_fk: (fields.E312) The to_field 'composed_object_origin_id' doesn't exist on the related model 'myapp.A'.
Y suena legítimo, Django exceptuó el archivo dado to_field
para ser un campo de base de datos. Pero no hay necesidad de agregar un nuevo campo a mi CommonModel
ya que composed_object_type_id
está construido con dos campos no anulables ...
fuente
Respuestas:
Usted mencionó en su comentario en la otra respuesta que object_id no es único pero es único en combinación con object_type, entonces, ¿podría usar un
unique_together
en la metaclase? es decirfuente
¿Tiene / puede establecer el
unique
atributo en elobject_id
campo?Si esto no funciona, cambiaría el tipo de campo a un
uuid
campo:fuente
object_id
no se puede establecer como único porque hay casos en los que no es único. En realidad, en la fuente de datos externa que me proporciona los datos que uso en mi aplicación, la clave principal se compone de dos campos:object_type
yobject_id
.object_id
no es único, no debe crear una clave externa. Esto podría causar errores en la base de datos y no quiere eso. Si no desea utilizar el pk en su lugar, también puede administrar la relación usted mismo en lasmodels.Model
funciones integradas.object_type
yobject_id
juntos se garantiza que serán únicos. Peroobject_id
solo no lo es.Usted es mencionado en su pregunta como " Desafortunadamente, el ORM de Django no admite claves externas de más de una columna ".
Sí, Django no proporciona ese tipo de soporte porque Django es más confiable de lo que pensamos :)
Entonces, Django proporciona una meta opción para superar este tipo de problema y esa opción es
unique_together
.Puede proporcionar conjuntos de nombres de campo que, en conjunto, deben ser únicos, en su caso ...
Puede proporcionar una lista de la lista, conjuntos de conjuntos o lista simple, conjunto simple a
unique_together
opción declass meta:
.Sí, pero Django dijo que ...
Puede agregar en
UniqueConstraint
lugar deunique_together
en el mismoclass meta:
en su caso, puede escribir de la siguiente manera ...Por lo tanto, la mejor práctica es usar la
constraints
opción en lugarunique_together
declass meta:
.fuente
Puede hacer que el ID de origen del objeto compuesto sea un campo (
composed_object_origin_id
) que se actualicesave
y se use comoto_field
.fuente