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=Truecampos aos seus FK; portanto, seu ModelForm(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 o null=Trueargumento).
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:
class Foo(models.Model):
name = models.TextField()
class Bar(models.Model):
foo = models.ForeignKey(Foo)
foo = Foo.objects.create(name="foo")
bar = Bar.objects.create(foo=foo)
então bar.fooresolverá foo, não foo.id. Então, você certamente deseja renomear seus campos InspectionIDe SiteIDpara apropriado inspectione site. 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=Truerestrição e, se sim, substitua seu campo FK por um OneToOneField.
adicione um blank=Trueargumento aos campos FK (ou OneToOne)
- adicione a restrição de verificação adequada na meta do seu modelo - o documento é sucinto, mas ainda suficientemente explícito se você souber fazer consultas complexas com o ORM (e se não o fizer, é hora de aprender ;-))
- adicione um
clean()método ao seu modelo que verifique se você tem um ou outro campo e gera um erro de validação
e você deve estar bem, supondo que seu RDBMS respeite as restrições de verificação, é claro.
Observe que, com esse design, seu Inspectionmodelo é 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 para InspectionReport.
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):
class Inspection(models.Model):
id = models.AutoField(primary_key=True) # a pk is always unique !
class InspectionReport(models.Model):
# you actually don't need to manually specify a PK field,
# Django will provide one for you if you don't
# id = models.AutoField(primary_key=True)
inspection = ForeignKey(Inspection, ...)
date = models.DateField(null=True) # you should have a default then
comment = models.CharField(max_length=255, blank=True default="")
signature = models.CharField(max_length=255, blank=True, default="")
class Group(models.Model):
inspection = models.OneToOneField(Inspection, null=True, blank=True)
class Site(models.Model):
inspection = models.OneToOneField(Inspection, null=True, blank=True)
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 ( joinclá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.
Inspectionclasse e depois subclassar paraSiteInspectioneGroupInspectionpara as partes não comuns.