O banco de dados do meu aplicativo é preenchido e mantido sincronizado com fontes de dados externas. Eu tenho um modelo abstrato a partir do qual todos os modelos do meu aplicativo Django 2.2 derivam, definidos da seguinte maneira:
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")
O object_id
campo não pode ser definido como exclusivo, pois cada fonte de dados que eu uso no meu aplicativo pode ter um objeto com um object_id = 1
. Daí a necessidade de rastrear a origem do objeto, pelo campo object_origin
.
Infelizmente, o ORM do Django não suporta chaves estrangeiras de mais de uma coluna.
Problema
Enquanto mantém a chave primária gerada automaticamente no banco de dados ( id
), gostaria que minha chave estrangeira e as relações muitos-para-muitos acontecessem nos campos object_id
e object_origin
em vez da chave primária id
.
O que eu tentei
Pensei em fazer algo assim:
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)
Mas o Django reclama disso:
myapp.B.to_a_fk: (fields.E312) The to_field 'composed_object_origin_id' doesn't exist on the related model 'myapp.A'.
E parece legítimo, o Django exceto o arquivado dado to_field
como um campo de banco de dados. Mas não há necessidade de adicionar um novo campo ao meu, CommonModel
pois composed_object_type_id
é construído usando dois campos não anuláveis ...
fonte
Respostas:
Você mencionou em seu comentário na outra resposta que object_id não é exclusivo, mas é único em combinação com object_type, então você poderia usar um
unique_together
na metaclasse? iefonte
Você tem / pode definir o
unique
atributo noobject_id
campo?Se isso não funcionar, eu mudaria o tipo de campo para um
uuid
campo:fonte
object_id
não pode ser definido como único, porque há casos em que não é exclusivo. Na verdade, na fonte de dados externa que me fornece os dados que uso no meu aplicativo, a chave primária é composta por dois campos:object_type
eobject_id
.object_id
não for exclusivo, você não deve criar uma chave estrangeira. Isso pode causar erros no banco de dados e você não deseja isso. Se você não quiser usar o pk, também poderá gerenciar o racionamento em si mesmo nasmodels.Model
funções internas.object_type
eobject_id
juntos são garantidos para ser único. Masobject_id
sozinho não é.Você é mencionado em sua pergunta como " Infelizmente, o ORM do Django não suporta chaves estrangeiras de mais de uma coluna ".
Sim, o Django não fornece esse tipo de suporte porque o Django é mais confiável do que pensamos :)
Então, o Django fornece uma meta opção para superar esse tipo de problema e essa opção é
unique_together
.Você pode fornecer conjuntos de nomes de campos que, juntos, devem ser exclusivos, no seu caso ...
Você pode fornecer uma lista de lista, conjuntos de conjuntos ou lista simples, conjunto simples à
unique_together
opção declass meta:
.Sim, mas o Django disse que ...
Você pode adicionar, em
UniqueConstraint
vez deunique_together
no mesmo,class meta:
no seu caso, você pode escrever como abaixo ...Portanto, a melhor prática é usar a
constraints
opção em vezunique_together
declass meta:
.fonte
Você pode transformar o ID de origem do objeto composto em um campo (
composed_object_origin_id
) atualizadosave
e usado como oto_field
.fonte