Meu problema é que tenho um modelo que pode usar uma das duas chaves estrangeiras para dizer que tipo de modelo é. Quero que leve pelo menos um, mas não ambos. Ainda posso ter um modelo ou devo dividi-lo em dois tipos. Aqui está o código:
class Inspection(models.Model):
InspectionID = models.AutoField(primary_key=True, unique=True)
GroupID = models.ForeignKey('PartGroup', on_delete=models.CASCADE, null=True, unique=True)
SiteID = models.ForeignKey('Site', on_delete=models.CASCADE, null=True, unique=True)
@classmethod
def create(cls, groupid, siteid):
inspection = cls(GroupID = groupid, SiteID = siteid)
return inspection
def __str__(self):
return str(self.InspectionID)
class InspectionReport(models.Model):
ReportID = models.AutoField(primary_key=True, unique=True)
InspectionID = models.ForeignKey('Inspection', on_delete=models.CASCADE, null=True)
Date = models.DateField(auto_now=False, auto_now_add=False, null=True)
Comment = models.CharField(max_length=255, blank=True)
Signature = models.CharField(max_length=255, blank=True)
O problema é o Inspection
modelo. Isso deve estar vinculado a um grupo ou site, mas não a ambos. Atualmente, com essa configuração, ele precisa de ambos.
Prefiro não ter que dividir isso em dois modelos quase idênticos GroupInspection
e SiteInspection
, portanto, qualquer solução que o mantenha como um modelo seria o ideal.
django
django-models
CalMac
fonte
fonte
Inspection
classe e depois subclassar paraSiteInspection
eGroupInspection
para as partes não comuns.unique=True
parte em seus campos FK significa que apenas umaInspection
instância pode existir para uma determinadaGroupID
ouSiteID
instância - IOW, é um relacionamento individual, não um para muitos. É mesmo isto que queres ?Inspection
um elo entre oGroup
orSite
e umInspectionID
, para que eu possa ter várias "inspeções" na forma deInspectionReport
um único relacionamento. Isso foi feito para que eu possa classificar mais facilmenteDate
todos os registros relacionados a umGroup
ouSite
. Espero que faça sentidoRespostas:
Eu sugiro que você faça essa validação da maneira do Django
substituindo o
clean
método do Django Modelfonte
Como mencionado nos comentários, a razão pela qual "com essa configuração precisa de ambos" é apenas porque você esqueceu de adicionar os
blank=True
campos aos seus FK; portanto, seuModelForm
(personalizado ou o padrão gerado pelo administrador) fará com que o campo de formulário seja obrigatório . No nível do esquema db, você pode preencher ambos, um ou nenhum desses FKs, tudo bem, pois você tornou esses campos db anuláveis (com onull=True
argumento).Além disso, (veja meus outros comentários), você pode querer verificar se realmente deseja que os FKs sejam únicos. Tecnicamente, isso transforma seu relacionamento de um para muitos em um para um - você só pode ter um único registro de 'inspeção' para um determinado GroupID ou SiteId (você não pode ter duas ou mais 'inspeção' para um GroupId ou SiteId) . Se isso é REALMENTE o que você deseja, convém usar um OneToOneField explícito (o esquema db será o mesmo, mas o modelo será mais explícito e o descritor relacionado muito mais utilizável para esse caso de uso).
Como uma observação lateral: em um Django Model, um campo ForeignKey se materializa como uma instância de modelo relacionada, não como um ID bruto. IOW, dado o seguinte:
então
bar.foo
resolveráfoo
, nãofoo.id
. Então, você certamente deseja renomear seus camposInspectionID
eSiteID
para apropriadoinspection
esite
. BTW, em Python, a convenção de nomenclatura é 'all_lower_with_underscores' para qualquer outra coisa além de nomes de classe e pseudo-constantes.Agora, a sua pergunta principal: não existe uma maneira padrão específica do SQL de impor uma restrição "uma ou outra" no nível do banco de dados; portanto, isso geralmente é feito com a restrição CHECK , feita em um modelo Django com as meta "restrições" do modelo. opção .
Dito isto, como as restrições são realmente suportadas e aplicadas no nível db depende do seu fornecedor de banco de dados (o MySQL <8.0.16 simplesmente as ignora por exemplo), e o tipo de restrição que você precisará aqui não será aplicado no formato ou validação no nível do modelo , apenas ao tentar salvar o modelo, portanto, você também deseja adicionar a validação no nível do modelo (preferencialmente) ou no nível do formulário, nos dois casos no modelo (resp.) ou no
clean()
método do formulário .Então, para resumir uma longa história:
primeiro verifique se você realmente deseja essa
unique=True
restrição e, se sim, substitua seu campo FK por um OneToOneField.adicione um
blank=True
argumento aos campos FK (ou OneToOne)clean()
método ao seu modelo que verifique se você tem um ou outro campo e gera um erro de validaçãoe você deve estar bem, supondo que seu RDBMS respeite as restrições de verificação, é claro.
Observe que, com esse design, seu
Inspection
modelo é um indireto totalmente inútil (mas caro!) - você obteria exatamente os mesmos recursos a um custo menor movendo os FKs (e restrições, validação etc.) diretamente paraInspectionReport
.Agora pode haver outra solução - mantenha o modelo de Inspeção, mas coloque o FK como OneToOneField na outra extremidade do relacionamento (no Site e no Grupo):
E então você pode obter todos os relatórios de um determinado site ou grupo com
yoursite.inspection.inspectionreport_set.all()
.Isso evita a necessidade de adicionar qualquer restrição ou validação específica, mas ao custo de um nível de indireção adicional (
join
cláusula SQL , etc.).Qual dessas soluções seria "a melhor" é realmente dependente do contexto; portanto, você precisa entender as implicações de ambas e verificar como costuma usar seus modelos para descobrir qual é mais apropriada para suas próprias necessidades. No que me diz respeito e sem mais contexto (ou dúvida), prefiro usar a solução com os níveis menos indiretos, mas YMMV.
Nota: em relação às relações genéricas: essas podem ser úteis quando você realmente possui muitos modelos possíveis e / ou não sabe de antemão quais modelos você deseja relacionar aos seus. Isso é especialmente útil para aplicativos reutilizáveis (pense em "comentários" ou "tags" etc.) ou extensíveis (estruturas de gerenciamento de conteúdo etc.). A desvantagem é que torna a consulta muito mais pesada (e pouco prática quando você deseja fazer consultas manuais no seu banco de dados). Por experiência, eles podem se tornar rapidamente um código / perf e código de bot da PITA, então é melhor mantê-los quando não houver uma solução melhor (e / ou quando a sobrecarga de manutenção e tempo de execução não for um problema).
Meus 2 centavos.
fonte
O Django tem uma nova interface (desde a 2.2) para criar restrições de banco de dados: https://docs.djangoproject.com/en/3.0/ref/models/constraints/
Você pode usar a
CheckConstraint
para impor um e apenas um não é nulo. Eu uso dois para maior clareza:Isso apenas aplicará a restrição no nível do banco de dados. Você precisará validar entradas em seus formulários ou serializadores manualmente.
Como uma nota lateral, você provavelmente deve usar em
OneToOneField
vez deForeignKey(unique=True)
. Você também vai quererblank=True
.fonte
Eu acho que você está falando sobre relações genéricas , documentos . Sua resposta é semelhante a esta .
Algum tempo atrás, eu precisava usar relações genéricas, mas li em um livro e em algum outro lugar que o uso deveria ser evitado, acho que era o Two Scoops of Django.
Acabei criando um modelo como este:
Não tenho certeza se é uma boa solução e, como você mencionou, prefere não usá-la, mas isso funciona no meu caso.
fonte
Pode ser que seja tarde para responder à sua pergunta, mas achei que minha solução poderia se encaixar no caso de outra pessoa.
Eu criaria um novo modelo, vamos chamá-lo
Dependency
e aplicaria a lógica nesse modelo.Então eu escreveria a lógica para ser aplicável de maneira muito explícita.
Agora você só precisa criar um único
ForeignKey
para o seuInspection
modelo.Em suas
view
funções, você precisa criar umDependency
objeto e atribuí-lo ao seuInspection
registro. Certifique-se de usarcreate_dependency_object
suasview
funções.Isso praticamente torna seu código explícito e à prova de erros. A imposição pode ser ignorada com muita facilidade. Mas o ponto é que ele precisa de conhecimento prévio dessa limitação exata para ser contornado.
fonte