Como se mencionó en los comentarios, la razón por la que "con esta configuración necesita ambos" es que olvidó agregar los blank=True
campos FK, por lo que su ModelForm
(ya sea uno personalizado o el predeterminado generado por el administrador) hará que el campo del formulario sea obligatorio . En el nivel de esquema de base de datos, puede completar ambos, ya sea uno o ninguno de esos FK, estaría bien ya que hizo que los campos de base de datos fueran nulos (con el null=True
argumento).
Además, (vea mis otros comentarios), es posible que desee comprobar que realmente desea que los FK sean únicos. Esto técnicamente convierte su relación uno a muchos en una relación uno a uno: solo se le permite un único registro de 'inspección' para un GroupID o SiteId determinado (no puede tener dos o más 'inspección' para un GroupId o SiteId) . Si eso es REALMENTE lo que desea, es posible que desee utilizar un OneToOneField explícito (el esquema db será el mismo pero el modelo será más explícito y el descriptor relacionado será mucho más útil para este caso de uso).
Como nota al margen: en un modelo Django, un campo ForeignKey se materializa como una instancia de modelo relacionada, no como una identificación sin formato. IOW, dado esto:
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)
entonces bar.foo
resolverá a foo
, no a foo.id
. Por lo tanto, sin duda desea cambiar el nombre de sus campos InspectionID
y SiteID
a apropiado inspection
y site
. Por cierto, en Python, la convención de nomenclatura es 'all_lower_with_underscores' para cualquier otra cosa que no sean nombres de clase y pseudo-constantes.
Ahora para su pregunta central: no hay una forma SQL estándar específica de imponer una restricción "uno u otro" en el nivel de la base de datos, por lo que generalmente se hace usando una restricción CHECK , que se realiza en un modelo Django con las meta "restricciones" del modelo opción .
Dicho esto, la forma en que las restricciones son realmente compatibles y aplicadas en el nivel de base de datos depende de su proveedor de base de datos (MySQL <8.0.16 simplemente ignórelas , por ejemplo), y el tipo de restricción que necesitará aquí no se aplicará en el formulario o validación a nivel de modelo , solo cuando se intenta guardar el modelo, por lo que también desea agregar validación a nivel de modelo (preferiblemente) o validación a nivel de formulario, en ambos casos en el modelo (resp.) o clean()
método de formulario .
Para resumir una larga historia:
primero verifique que realmente desea esta unique=True
restricción, y en caso afirmativo, reemplace su campo FK con OneToOneField.
agregue un blank=True
argumento a sus dos campos FK (o OneToOne)
- agregue la restricción de verificación adecuada en el meta de su modelo: el documento es breve pero aún lo suficientemente explícito si sabe hacer consultas complejas con el ORM (y si no lo hace, es hora de aprender ;-))
- agregue un
clean()
método a su modelo que verifique que tenga uno u otro campo y genere un error de validación
y debería estar bien, suponiendo que su RDBMS respete las restricciones de verificación, por supuesto.
Solo tenga en cuenta que, con este diseño, su Inspection
modelo es una indirección totalmente inútil (¡pero costosa!): Obtendrá exactamente las mismas características a un costo menor al mover los FK (y las restricciones, la validación, etc.) directamente InspectionReport
.
Ahora puede haber otra solución: mantener el modelo de inspección, pero colocar el FK como OneToOneField en el otro extremo de la relación (en el sitio y el 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)
Y luego puede obtener todos los informes de un sitio o grupo determinado con yoursite.inspection.inspectionreport_set.all()
.
Esto evita tener que agregar ninguna restricción o validación específica, pero a costa de un nivel de indirección adicional ( join
cláusula SQL, etc.).
Cuál de esas soluciones sería "la mejor" depende realmente del contexto, por lo que debe comprender las implicaciones de ambas y verificar cómo usa típicamente sus modelos para descubrir cuál es más apropiado para sus propias necesidades. En lo que a mí respecta y sin más contexto (o dudas) prefiero usar la solución con los niveles de indirección menos, pero YMMV.
NB con respecto a las relaciones genéricas: pueden ser útiles cuando realmente tiene muchos posibles modelos relacionados y / o no sabe de antemano qué modelos querrá relacionar con los suyos. Esto es especialmente útil para aplicaciones reutilizables (piense en "comentarios" o "etiquetas", etc.) o extensibles (marcos de administración de contenido, etc.). La desventaja es que hace que las consultas sean mucho más pesadas (y bastante poco prácticas cuando quieres hacer consultas manuales en tu base de datos). Por experiencia, pueden convertirse rápidamente en un código / error y rendimiento de bots de PITA, por lo que es mejor mantenerlos para cuando no haya una mejor solución (y / o cuando la sobrecarga de mantenimiento y tiempo de ejecución no sea un problema).
Mis 2 centavos
Inspection
clase y luego subclase enSiteInspection
yGroupInspection
para las partes no comunes.