선택 사항이지만 필수 외래 키가 하나 인 모델 작성


9

내 문제는 두 가지 외래 키 중 하나를 사용하여 어떤 종류의 모델인지 말할 수있는 모델이 있다는 것입니다. 적어도 하나는 가져야하지만 둘다는 원하지 않습니다. 이 모델을 여전히 하나의 모델로 만들거나 두 가지 유형으로 나눌 수 있습니까? 코드는 다음과 같습니다.

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)

문제는 Inspection모델입니다. 이것은 그룹이나 사이트에 연결되어야하지만 둘 다 연결되어서는 안됩니다. 현재이 설정에서는 둘 다 필요합니다.

차라리 두 개의 거의 동일한 모델로이 분할 할 필요가 없습니다 것입니다 GroupInspectionSiteInspection하나 개의 모델로 유지 모든 솔루션이 이상적 일 것이다, 그래서.


아마도 서브 클래 싱을 사용하는 것이 더 좋습니다. 당신은 할 수 Inspection클래스를, 다음으로 서브 클래스 SiteInspectionGroupInspection에 대한 -common 부품.
빌렘 반 온셈

아마도 관련이 없지만 unique=TrueFK 필드 의 일부는 Inspection주어진 하나 GroupID또는 SiteID인스턴스에 대해 하나의 인스턴스 만 존재할 수 있음을 의미합니다. IOW는 일대 다 관계가 아니라 일대 다 관계입니다. 이것이 정말로 당신이 원하는 것입니까?
bruno desthuilliers

"현재이 설정에는 둘 다 필요합니다." => 기술적으로는 데이터베이스 수준에서 위의주의 사항을 사용하여 해당 키 중 하나 또는 둘 다를 설정할 수 있습니다. ModelForm (직접 또는 django 관리자를 통해)을 사용하는 경우에만 해당 필드가 필수로 표시되며 'blank = True'인수를 전달하지 않았기 때문입니다.
bruno desthuilliers

@brunodesthuilliers 그렇습니다. 아이디어는 or 와 Inspectiona 사이를 연결 하는 것이며 , 그 관계 에 대해 여러 개의 "검사"를 가질 수 있습니다 . 이것은 하나와 관련된 모든 레코드를 더 쉽게 정렬 할 수 있도록 수행되었습니다.GroupSiteInspectionIDInspectionReportDateGroupSite. 말이되는 희망
CalMac

@ Cm0295 그룹 / 사이트 FK를 InspectionReport에 직접 넣으면 동일한 서비스 AFAICT가 생성됩니다. 적절한 키로 InspectionReports를 필터링하거나 사이트의 역 설명자를 따르십시오. 날짜)를 기준으로 정렬하면 완료됩니다.
bruno desthuilliers

답변:


5

나는 당신이 장고 방식으로 그런 유효성 검사를 할 것을 제안합니다

clean장고 모델 의 방법을 재정의함으로써

class Inspection(models.Model):
    ...

    def clean(self):
        if <<<your condition>>>:
            raise ValidationError({
                    '<<<field_name>>>': _('Reason for validation error...etc'),
                })
        ...
    ...

그러나 Model.full_clean ()과 같이 모델의 save () 메소드를 호출 할 때 모델의 clean () 메소드가 호출되지 않습니다. 모델의 데이터를 검증하기 위해 수동으로 호출 하거나 Model클래스의 save 메소드를 트리거하기 전에 항상 clean () 메소드를 호출하도록 모델의 save 메소드를 대체 할 수 있습니다


도움이 될 수있는 또 다른 솔루션하나 이상의 테이블과 관련된 다형성 필드를 제공하기 위해 GenericRelations를 이지만 시스템 디자인에서 이러한 테이블 / 객체를 처음부터 상호 교환 적으로 사용할 수있는 경우가 있습니다.


2

의견에서 언급했듯이 "이 설정으로 둘 다 필요하다"는 이유는 blank=TrueFK 필드에 를 추가하는 것을 잊었 기 때문입니다.ModelForm 입니다. . db 스키마 레벨에서, FK 중 하나 또는 모두를 채울 수 있습니다 null=True. 인수를 사용하여 해당 db 필드를 널 입력 가능하게 만들었으므로 괜찮습니다 .

또한 (내 다른 의견 참조), 당신은 당신이 정말로 FK를 독창적으로 만들고 싶어하는지 확인할 수 있습니다. 이는 기술적으로 일대 다 관계를 일대일 관계로 전환합니다. 주어진 GroupID 또는 SiteId에 대해 하나의 '검사'레코드 만 허용됩니다 (하나의 GroupId 또는 SiteId에 대해 둘 이상의 '검사'를 가질 수 없음) . 이것이 정말로 원하는 경우 대신 명시 적 OneToOneField를 사용하는 것이 좋습니다 (db 스키마는 동일하지만 모델은 더 명확하고 관련 설명자는이 사용 사례에 훨씬 더 유용합니다).

참고로 Django 모델에서 ForeignKey 필드는 원시 ID가 아닌 관련 모델 인스턴스로 구체화됩니다. IOW는 다음과 같습니다.

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)

그런 다음 bar.foo로 해결 foo합니다 foo.id. 따라서 필드 InspectionIDSiteID필드의 이름 을 적절 inspection하고site . BTW, Python에서 명명 규칙은 클래스 이름 및 의사 상수 이외의 다른 것에 대해서는 'all_lower_with_underscores'입니다.

핵심 질문 : 데이터베이스 수준에서 "하나 또는 다른"제약 조건을 적용하는 특정 표준 SQL 방법이 없으므로 일반적으로 CHECK 제약 조건을 사용하여 수행 됩니다. CHECK 제약 조건 은 모델의 메타 "제약"을 사용 하여 Django 모델에서 수행됩니다. 옵션 .

즉, DB 수준에서 제약 조건이 실제로 지원되고 적용되는 방식은 DB 공급 업체에 따라 다르며 (예 : MySQL <8.0.16 그냥 무시 하십시오) 여기에서 필요한 제약 종류 는 양식에 적용되지 않습니다 모델 수준의 검증 , 당신은, 그래서 모델을 저장하려고하는 경우에만 또한 상기 중 유효성 검사를 추가 할 모델 수준 (RESP.) 모델 또는 양식의 두 경우에 (바람직) 또는 양식 수준의 검증 clean()방법.

긴 이야기를 짧게하려면 :

  • unique=True먼저이 제약 조건 이 정말로 필요한지 다시 확인하고, 그렇다면 FK 필드를 OneToOneField로 바꾸십시오.

  • blank=TrueFK (또는 OneToOne) 필드에 인수를 추가하십시오.

  • 모델의 메타에 적절한 검사 제약 조건 을 추가하십시오 . 문서는 간결하지만 ORM을 사용하여 복잡한 쿼리를 수행하는 것으로 알고 있다면 충분히 명시 적입니다.
  • clean()하나 또는 다른 필드가 있는지 확인하고 유효성 검사 오류가 발생 하는 방법을 모델에 추가하십시오.

RDBMS가 검사 제한 조건을 존중한다고 가정하면 괜찮을 것입니다.

이 디자인을 사용하면 Inspection모델이 완전히 쓸모가 없지만 (비용이 많이 드는!) 간접적입니다. FK (및 제약 조건, 유효성 검사 등)를로 직접 이동하여 더 적은 비용으로 동일한 기능을 정확하게 얻을 수 있습니다 InspectionReport.

이제 또 다른 해결책이있을 수 있습니다. 검사 모델을 유지하되 FK를 관계의 다른 쪽 끝 (사이트 및 그룹)에 OneToOneField로 지정하십시오.

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)

그런 다음로 특정 사이트 또는 그룹에 대한 모든 보고서를 얻을 수 있습니다 yoursite.inspection.inspectionreport_set.all().

이를 통해 특정 제약 조건이나 유효성 검사를 추가하지 않아도되지만 추가 간접 수준 (SQL join절 등)이 필요합니다.

어떤 솔루션이 "최상의"솔루션인지는 상황에 따라 달라 지므로 두 가지의 의미를 이해하고 일반적으로 모델을 사용하여 자신의 요구에 더 적합한 방법을 찾는 방법을 확인해야합니다. 내가 우려하고 더 많은 맥락 (또는 의심스런)이없는 한, 간접 수준은 적지 않지만 YMMV와 함께 솔루션을 사용하고 싶습니다.

일반적인 관계에 관한 NB : 실제로 가능한 많은 관련 모델이 있거나 어떤 모델이 자신과 관련이 있는지 미리 알지 못할 때 편리합니다. 이것은 재사용 가능한 앱 ( "코멘트"또는 "태그"등의 기능을 생각 함) 또는 확장 가능한 앱 (콘텐츠 관리 프레임 워크 등)에 특히 유용합니다. 단점은 쿼리가 훨씬 무겁다는 것입니다 (DB에서 수동 쿼리를 수행하려는 경우 오히려 비실용적입니다). 경험을 통해 PITA 봇 wrt / 코드 및 성능이 빠르게 향상 될 수 있으므로 더 나은 솔루션이없는 경우 (및 / 또는 유지 관리 및 런타임 오버 헤드가 문제가되지 않는 경우) 유지하는 것이 좋습니다.

내 2 센트


2

Django에는 DB 제약 조건을 만드는 새로운 (2.2 이후) 인터페이스가 있습니다. https://docs.djangoproject.com/en/3.0/ref/models/constraints/

a CheckConstraint를 사용하여 one-and-one-one이 널이 아님을 강제 할 수 있습니다 . 명확성을 위해 두 가지를 사용합니다.

class Inspection(models.Model):
    InspectionID = models.AutoField(primary_key=True, unique=True)
    GroupID = models.OneToOneField('PartGroup', on_delete=models.CASCADE, blank=True, null=True)
    SiteID = models.OneToOneField('Site', on_delete=models.CASCADE, blank=True, null=True)

    class Meta:
        constraints = [
            models.CheckConstraint(
                check=~Q(SiteID=None) | ~Q(GroupId=None),
                name='at_least_1_non_null'),
            ),
            models.CheckConstraint(
                check=Q(SiteID=None) | Q(GroupId=None),
                name='at_least_1_null'),
            ),
        ]

DB 수준에서만 제약 조건이 적용됩니다. 양식 또는 직렬 변환기의 입력을 수동으로 확인해야합니다.

참고로 아마도 OneToOneField대신 대신 사용해야 합니다 ForeignKey(unique=True). 당신은 또한 원할 것 blank=True입니다.


0

나는 당신이 일반적인 관계 , docs 에 대해 이야기하고 있다고 생각합니다 . 당신의 대답은 비슷합니다 이것 .

언젠가 나는 일반 관계를 사용해야했지만 책과 다른 곳에서 사용법을 피해야한다는 것을 읽었습니다. 장고의 두 국자라고 생각합니다.

나는 다음과 같은 모델을 만들었습니다.

class GroupInspection(models.Model):
    InspectionID = models.ForeignKey..
    GroupID = models.ForeignKey..

class SiteInspection(models.Model):
    InspectionID = models.ForeignKey..
    SiteID = models.ForeignKey..

나는 그것이 좋은 해결책인지 확실하지 않으며 당신이 언급 한 것처럼 그것을 사용하지 않을 것이지만 이것은 내 경우에 효과가 있습니다.


"나는 책과 다른 곳에서 읽었다"는 무언가를하는 (또는하지 않는) 최악의 이유에 관한 것입니다.
bruno desthuilliers

@brunodesthuilliers 장고의 두 특종이 좋은 책이라고 생각했습니다.
Luis 실바

말할 수 없다, 나는 그것을 읽지 않았다. 그러나 그것은 관련이 없습니다. 제 요점은 책이 왜 그렇게 말하는지 이해하지 못한다면 지식이나 경험이 아니라 종교적 믿음이라는 것입니다. 종교에 관해서는 종교적 신념을 신경 쓰지 않지만 CS에는 자리가 없습니다. 어떤 기능의 장단점이 무엇인지 이해 한 후 주어진 상황 에서 적절한 지 판단 할 수 있습니다 . 일반적인 관계에 대한 유효한 사용 사례가 있습니다. 요점은 전혀 피하는 것이 아니라 피할 때를 아는 것입니다.
bruno desthuilliers

NB 나는 CS에 대해 모든 것을 알 수 없다는 것을 완벽하게 이해합니다. 어떤 책을 신뢰하는 것 외에 다른 옵션이없는 도메인이 있습니다. 그러나 나는 아마 그 주제에 관한 질문에 대답하지 않을 것입니다 ;-)
bruno desthuilliers

0

귀하의 질문에 대한 답변이 늦었을 수도 있지만 내 솔루션이 다른 사람의 경우에 적합하다고 생각했습니다.

새 모델을 만들고 호출하고 Dependency해당 모델의 논리를 적용 하겠습니다 .

class Dependency(models.Model):
    Group = models.ForeignKey('PartGroup', on_delete=models.CASCADE, null=True, unique=True)
    Site = models.ForeignKey('Site', on_delete=models.CASCADE, null=True, unique=True)

그런 다음 논리를 매우 명시 적으로 적용 할 수 있습니다.

class Dependency(models.Model):
    group = models.ForeignKey('PartGroup', on_delete=models.CASCADE, null=True, unique=True)
    site = models.ForeignKey('Site', on_delete=models.CASCADE, null=True, unique=True)

    _is_from_custom_logic = False

    @classmethod
    def create_dependency_object(cls, group=None, site=None):
        # you can apply any conditions here and prioritize the provided args
        cls._is_from_custom_logic = True
        if group:
            _new = cls.objects.create(group=group)
        elif site:
            _new = cls.objects.create(site=site)
        else:
            raise ValueError('')
        return _new

    def save(self, *args, **kwargs):
        if not self._is_from_custom_logic:
            raise Exception('')
        return super().save(*args, **kwargs)

지금 당신은 단지 하나를 만들 필요가 ForeignKey당신에 Inspection모델.

당신의에서 view기능, 당신은 만들 필요가 Dependency개체를 한 다음에 할당 Inspection기록. 함수 create_dependency_object에서 사용해야 view합니다.

이것은 코드를 명시적이고 버그 증명합니다. 시행을 너무 쉽게 우회 할 수 있습니다. 그러나 요점은이 정확한 한계를 우회하기 위해서는 사전 지식이 필요하다는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.