Mi problema es que tengo un modelo que puede tomar una de las dos claves externas para decir qué tipo de modelo es. Quiero que tome al menos uno pero no los dos. ¿Puedo hacer que esto siga siendo un modelo o debería dividirlo en dos tipos? Aquí está el 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)
El problema es el Inspection
modelo. Esto debe estar vinculado a un grupo o un sitio, pero no a ambos. Actualmente con esta configuración necesita ambos.
Yo preferiría no tener que dividir esto en dos modelos casi idénticos GroupInspection
y SiteInspection
, por lo que cualquier solución que lo mantiene como uno modelo sería ideal.
django
django-models
CalMac
fuente
fuente
Inspection
clase y luego subclase enSiteInspection
yGroupInspection
para las partes no comunes.unique=True
parte en sus campos de FK significa que soloInspection
puede existir una instancia para una determinadaGroupID
oSiteID
instancia: AHORA, es una relación uno a uno, no una para muchos. Es esto realmente lo que quieres ?Inspection
un vínculo entreGroup
oSite
y anInspectionID
, entonces puedo tener múltiples "inspecciones" en forma deInspectionReport
esa relación. Esto se hizo para poder ordenar más fácilmenteDate
todos los registros relacionados con unoGroup
oSite
. Espero que tenga sentidoRespuestas:
Te sugiero que hagas tal validación a la manera de Django
anulando el
clean
método del modelo Djangofuente
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 suModelForm
(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 elnull=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:
entonces
bar.foo
resolverá afoo
, no afoo.id
. Por lo tanto, sin duda desea cambiar el nombre de sus camposInspectionID
ySiteID
a apropiadoinspection
ysite
. 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)clean()
método a su modelo que verifique que tenga uno u otro campo y genere un error de validacióny 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.) directamenteInspectionReport
.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):
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
fuente
Django tiene una nueva interfaz (desde 2.2) para crear restricciones de DB: https://docs.djangoproject.com/en/3.0/ref/models/constraints/
Puede usar a
CheckConstraint
para forzar one-and-only-one no es nulo. Yo uso dos para mayor claridad:Esto solo impondrá la restricción en el nivel de base de datos. Deberá validar las entradas en sus formularios o serializadores manualmente.
Como nota al margen, probablemente debería usar en
OneToOneField
lugar deForeignKey(unique=True)
. También querráblank=True
.fuente
Creo que estás hablando de relaciones genéricas , documentos . Tu respuesta es similar a esta .
Hace algún tiempo necesitaba usar relaciones genéricas, pero leí en un libro y en otro lugar que debería evitarse el uso, creo que fueron dos cucharadas de Django.
Terminé creando un modelo como este:
No estoy seguro de si es una buena solución y, como mencionaste, preferirías no usarla, pero esto funciona en mi caso.
fuente
Puede que sea tarde para responder a su pregunta, pero pensé que mi solución podría ajustarse al caso de otra persona.
Crearía un nuevo modelo, llamémoslo
Dependency
, y aplicaría la lógica en ese modelo.Entonces escribiría la lógica para que sea aplicable de manera muy explícita.
Ahora solo necesita crear un single
ForeignKey
para suInspection
modelo.En sus
view
funciones, debe crear unDependency
objeto y luego asignarlo a suInspection
registro. Asegúrese de usarlocreate_dependency_object
en susview
funciones.Esto hace que su código sea explícito y a prueba de errores. La aplicación de la ley puede pasarse por alto con demasiada facilidad. Pero el punto es que necesita conocimiento previo a esta limitación exacta para ser ignorado.
fuente