การสร้างแบบจำลองที่มีทางเลือกสองทาง แต่ต้องเป็นรหัสต่างประเทศที่จำเป็น


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รูปแบบ สิ่งนี้ควรเชื่อมโยงกับกลุ่มหรือไซต์ แต่ไม่ใช่ทั้งสองอย่าง ขณะนี้มีการตั้งค่านี้มันต้องการทั้ง

ฉันอยากจะได้ไม่ต้องแยกนี้ขึ้นเป็นสองรุ่นเหมือนกันเกือบGroupInspectionและSiteInspectionเพื่อแก้ปัญหาใด ๆ ที่ทำให้มันเป็นหนึ่งในรูปแบบที่จะเหมาะ


บางทีการใช้คลาสย่อยนั้นดีกว่าที่นี่ คุณสามารถสร้างInspectionคลาสและจากนั้นซับคลาสลงในSiteInspectionและGroupInspectionสำหรับส่วนที่ไม่ใช่คอมมอน
Willem Van Onsem

อาจไม่เกี่ยวข้องกัน แต่unique=Trueส่วนหนึ่งในเขตข้อมูล FK ของคุณหมายความว่ามีเพียงหนึ่งInspectionอินสแตนซ์เท่านั้นที่สามารถมีอยู่ได้สำหรับอินสแตนซ์ที่กำหนดGroupIDหรือSiteIDอินสแตนซ์ - IOW มันเป็นความสัมพันธ์แบบหนึ่งต่อหนึ่ง นี่เป็นสิ่งที่คุณต้องการจริงๆเหรอ?
bruno desthuilliers

"ขณะนี้การตั้งค่านี้ต้องการทั้งสองอย่าง" => ในทางเทคนิคแล้วมันไม่ได้ - ในระดับฐานข้อมูลคุณสามารถตั้งค่าได้ทั้งสองอย่างหรือไม่ใช้คีย์เหล่านั้น (ด้วยข้อสังเกตที่กล่าวถึงข้างต้น) เฉพาะเมื่อใช้ ModelForm (โดยตรงหรือผ่านผู้ดูแลระบบ django) ว่าฟิลด์เหล่านั้นจะถูกทำเครื่องหมายตามต้องการและนั่นเป็นเพราะคุณไม่ได้ผ่านอาร์กิวเมนต์ 'blank = True'
bruno desthuilliers

@brunodesthuilliers ใช่ความคิดคือการมีInspectionการเชื่อมโยงระหว่างGroupหรือSiteและและInspectionIDจากนั้นฉันสามารถมี "การตรวจสอบ" หลายอย่างในรูปแบบของInspectionReportความสัมพันธ์นั้น นี้ทำเพื่อที่ฉันจะได้ง่ายขึ้นสามารถจัดเรียงDateไว้เป็นหลักฐานทั้งหมดที่เกี่ยวข้องกับหนึ่งหรือGroup Siteหวังว่าจะทำให้ความรู้สึก
CalMac

@ Cm0295 ฉันเกรงว่าจะไม่เห็นระดับของจุดทางอ้อมนี้ - ทำให้กลุ่ม / ไซต์ FKs เข้าสู่ InspectionReport โดยตรงให้ผลการบริการที่เหมือนกัน AFAICT - กรอง InspectionReports ของคุณด้วยคีย์ที่เหมาะสม (หรือเพียงแค่ทำตามตัวบอกสถานะย้อนกลับจากไซต์หรือ กลุ่ม) จัดเรียงตามวันที่และคุณทำเสร็จแล้ว
bruno desthuilliers

คำตอบ:


5

ฉันขอแนะนำให้คุณทำการตรวจสอบดังกล่าวด้วยวิธี Django

โดยเอาชนะcleanวิธีการของ Django Model

class Inspection(models.Model):
    ...

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

อย่างไรก็ตามโปรดทราบว่าเช่นเดียวกับ Model.full_clean () วิธีการ clean () ของโมเดลจะไม่ถูกเรียกใช้เมื่อคุณเรียกใช้เมธอด save () ของแบบจำลอง จำเป็นต้องถูกเรียกด้วยตนเองเพื่อตรวจสอบความถูกต้องของข้อมูลของโมเดลหรือคุณสามารถแทนที่วิธีการบันทึกของโมเดลเพื่อให้เรียกเมธอด clean () เสมอก่อนที่จะทริกเกอร์Modelเมธอด save class


อีกวิธีที่อาจช่วยได้คือการใช้GenericRelationsเพื่อจัดเตรียมฟิลด์ polymorphic ที่เกี่ยวข้องกับมากกว่าหนึ่งตาราง แต่อาจเป็นกรณีที่สามารถใช้ตาราง / วัตถุเหล่านี้แทนกันในการออกแบบระบบจากที่แรก


2

ดังที่ได้กล่าวไว้ในความคิดเห็นเหตุผลที่ "พร้อมกับการตั้งค่านี้จำเป็นต้องใช้ทั้งคู่" ก็คือคุณลืมที่จะเพิ่มลงในblank=Trueเขตข้อมูล FK ของคุณดังนั้นModelForm(อย่างใดอย่างหนึ่งที่กำหนดเองหรือค่าเริ่มต้นที่สร้างโดยผู้ดูแลระบบ) จะทำให้ฟิลด์ฟอร์ม . ที่ระดับ db schema คุณสามารถเติมทั้งสองอย่างใดอย่างหนึ่งหรือทั้งหมดของ FKs เหล่านั้นมันจะโอเคตั้งแต่คุณทำให้เขตข้อมูล db เหล่านั้นเป็นโมฆะ (ด้วยnull=Trueอาร์กิวเมนต์)

นอกจากนี้ (ถ้ามีความคิดเห็นอื่นของฉัน) คุณอาจต้องการตรวจสอบว่า FK ของคุณต้องการที่จะไม่ซ้ำกันจริงๆ เทคนิคนี้จะเปลี่ยนความสัมพันธ์ของคุณเป็นความสัมพันธ์แบบหนึ่งต่อหนึ่ง - คุณอนุญาตให้มีเพียงหนึ่งระเบียน 'การตรวจสอบ' สำหรับ GroupID หรือ SiteId ที่ระบุ (คุณไม่สามารถมี 'การตรวจสอบ' สองรายการขึ้นไปสำหรับ GroupId หรือ SiteId) . หากนั่นคือสิ่งที่คุณต้องการจริงๆคุณอาจต้องการใช้ OneToOneField แทนอย่างชัดเจน (db schema จะเหมือนกัน แต่ตัวแบบจะชัดเจนมากขึ้นและตัวอธิบายที่เกี่ยวข้องสามารถใช้กับกรณีการใช้งานนี้ได้มากกว่า)

หมายเหตุด้านข้าง: ในโมเดล 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ดังนั้นคุณต้องการเปลี่ยนชื่อInspectionIDและSiteIDเขตข้อมูลของคุณ ให้เหมาะสมinspectionและsiteแน่นอน BTW ใน Python อนุสัญญาการตั้งชื่อคือ 'all_lower_with_underscores' สำหรับสิ่งอื่นนอกเหนือจากชื่อคลาสและค่าคงที่หลอก

ตอนนี้สำหรับคำถามหลักของคุณ: ไม่มีวิธี SQL มาตรฐานเฉพาะในการบังคับใช้ข้อ จำกัด "หนึ่งหรืออื่น ๆ " ในระดับฐานข้อมูลดังนั้นจึงมักจะใช้ข้อ จำกัด การตรวจสอบซึ่งทำในรูปแบบ Django กับข้อ จำกัด เมตาของรุ่น ตัวเลือก

สิ่งนี้ถูกกล่าวว่าการสนับสนุนและบังคับใช้ข้อ จำกัด ที่ระดับ db นั้นขึ้นอยู่กับผู้ขาย DB ของคุณ (MySQL <8.0.16 เพียงแค่ไม่สนใจตัวอย่าง) และข้อ จำกัด ที่คุณต้องการที่นี่จะไม่ถูกบังคับใช้ที่ฟอร์ม หรือการตรวจสอบระดับรูปแบบเฉพาะเมื่อพยายามที่จะบันทึกรูปแบบเพื่อให้คุณยังต้องการที่จะเพิ่มการตรวจสอบทั้งในระดับรุ่น (กว่า) หรือการตรวจสอบระดับรูปแบบในทั้งสองกรณีใน (resp.) รูปแบบหรือรูปแบบของclean()วิธีการ

ดังนั้นเพื่อให้เรื่องสั้นสั้น:

  • ตรวจสอบอีกครั้งก่อนว่าคุณต้องการunique=Trueข้อ จำกัดนี้จริง ๆและถ้าใช่ให้แทนที่ฟิลด์ FK ของคุณด้วย OneToOneField

  • เพิ่มblank=Truearg ให้กับทั้งฟิลด์ FK (หรือ OneToOne) ของคุณ

  • เพิ่มข้อ จำกัด การตรวจสอบที่ถูกต้องในเมตาของโมเดลของคุณ - เอกสารนั้นสำเร็จ แต่ก็ยังชัดเจนพอถ้าคุณรู้ว่าต้องทำเคียวรีที่ซับซ้อนด้วย ORM (และถ้าคุณไม่มีเวลาก็เรียนรู้ ;-))
  • เพิ่มclean()วิธีการในแบบจำลองของคุณที่ตรวจสอบว่าคุณมีอย่างใดอย่างหนึ่งหรือเขตข้อมูลอื่นและทำให้เกิดข้อผิดพลาดการตรวจสอบอื่น

และคุณควรจะโอเคสมมติว่า RDBMS ของคุณเคารพการตรวจสอบข้อ จำกัด ของหลักสูตร

ทราบเพียงว่ามีการออกแบบนี้ของคุณInspectionรูปแบบเป็นไร้ประโยชน์โดยสิ้นเชิงร้าย (ยังมีค่าใช้จ่าย!) - คุณจะได้รับคุณสมบัติเดียวกันแน่นอนด้วยต้นทุนที่ต่ำโดยการย้าย FKs (และข้อ จำกัด ของการตรวจสอบ ฯลฯ ) 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 joinclause เป็นต้น)

โซลูชันใดที่จะ "ดีที่สุด" นั้นขึ้นอยู่กับบริบทดังนั้นคุณต้องเข้าใจความหมายของทั้งสองและตรวจสอบว่าคุณใช้แบบจำลองของคุณเพื่อหาว่าสิ่งใดเหมาะสมกว่าสำหรับความต้องการของคุณ เท่าที่ฉันกังวลและไม่มีบริบทเพิ่มเติม (หรือมีข้อสงสัย) ฉันควรใช้วิธีแก้ปัญหาที่มีระดับการอ้อมน้อยกว่า แต่ YMMV

หมายเหตุเกี่ยวกับความสัมพันธ์ทั่วไป: สิ่งเหล่านี้มีประโยชน์เมื่อคุณมีแบบจำลองที่เกี่ยวข้องและ / หรือไม่ทราบล่วงหน้าว่าแบบใดที่คุณต้องการเกี่ยวข้องกับตัวคุณเอง สิ่งนี้มีประโยชน์เป็นพิเศษสำหรับแอพที่ใช้ซ้ำได้ (คิดว่า "ความคิดเห็น" หรือคุณสมบัติ "แท็ก" ฯลฯ ) หรือสิ่งที่ขยายได้ (กรอบการจัดการเนื้อหาและอื่น ๆ ) ข้อเสียคือมันทำให้การสืบค้นหนักกว่ามาก (และค่อนข้างทำไม่ได้เมื่อคุณต้องการที่จะทำแบบสอบถามด้วยตนเองในฐานข้อมูลของคุณ) จากประสบการณ์พวกเขาสามารถกลายเป็น PITA bot wrt / code และ perfs ได้อย่างรวดเร็วดังนั้นควรเก็บไว้เมื่อไม่มีวิธีแก้ปัญหาที่ดีกว่า (และ / หรือเมื่อการบำรุงรักษาและค่าใช้จ่ายรันไทม์ไม่เป็นปัญหา)

2 เซนต์ของฉัน


2

Django มีอินเทอร์เฟซใหม่ (ตั้งแต่ 2.2) สำหรับการสร้างข้อ จำกัด ของฐานข้อมูล: https://docs.djangoproject.com/en/3.0/ref/models/constraints/

คุณสามารถใช้ a CheckConstraintเพื่อบังคับใช้แบบหนึ่งต่อหนึ่งเท่านั้นไม่ใช่ค่าว่าง ฉันใช้สองเพื่อความชัดเจน:

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

ฉันคิดว่าคุณกำลังพูดถึง ทั่วไปความสัมพันธ์ , เอกสาร คำตอบของคุณมีลักษณะคล้ายกับคนนี้

บางครั้งที่ผ่านมาฉันต้องการใช้ความสัมพันธ์ทั่วไป แต่ฉันอ่านในหนังสือและที่อื่นที่ควรหลีกเลี่ยงการใช้งานฉันคิดว่ามันเป็น Two Scoops ของ Django

ฉันลงเอยด้วยการสร้างแบบจำลองเช่นนี้:

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

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

ฉันไม่แน่ใจว่ามันเป็นทางออกที่ดีและอย่างที่คุณพูดไปแล้วว่าคุณไม่ควรใช้มัน แต่มันใช้ได้ในกรณีของฉัน


"ฉันอ่านในหนังสือและที่อื่น" เป็นเรื่องเกี่ยวกับเหตุผลที่เลวร้ายที่สุดที่จะทำ (หรือหลีกเลี่ยง) บางสิ่งบางอย่าง
bruno desthuilliers

@brunodesthuilliers ฉันคิดว่าสอง Scoops ของ Django เป็นหนังสือที่ดี
Luis Silva

บอกไม่ได้ฉันยังไม่ได้อ่าน แต่นั่นไม่เกี่ยวข้อง: ประเด็นของฉันคือถ้าคุณไม่เข้าใจว่าทำไมหนังสือถึงพูดเช่นนั้นไม่ใช่ความรู้หรือประสบการณ์มันเป็นความเชื่อทางศาสนา ฉันไม่สนใจความเชื่อทางศาสนาเมื่อพูดถึงเรื่องศาสนา แต่พวกเขาไม่มีที่ใน 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.