มีวิธีการสร้างรหัสที่ไม่ซ้ำกันมากกว่า 2 ช่องหรือไม่?


14

นี่คือแบบจำลองของฉัน:

class GroupedModels(models.Model):
    other_model_one = models.ForeignKey('app.other_model')
    other_model_two = models.ForeignKey('app.other_model')

โดยพื้นฐานแล้วสิ่งที่ฉันต้องการคือother_modelการมีเอกลักษณ์ในตารางนี้ ซึ่งหมายความว่าหากมีการบันทึกที่other_model_oneID เป็น123ผมไม่ควรอนุญาตให้บันทึกอีกครั้งเพื่อให้ได้รับการสร้างขึ้นด้วยรหัสเป็นother_model_two 123ฉันสามารถแทนที่cleanฉันเดาได้ แต่ฉันสงสัยว่า django มีบางอย่างในตัวหรือไม่

ฉันใช้เวอร์ชั่น 2.2.5 กับ PSQL

แก้ไข: นี่ไม่ใช่สถานการณ์ที่ไม่เข้าด้วยกัน หากฉันเพิ่มระเบียนด้วยother_model_one_id=1และอื่น ๆother_model_two_id=2ฉันไม่ควรเพิ่มระเบียนอื่นด้วยother_model_one_id=2และอื่น ๆother_model_two_id=1


คุณใช้ Django เวอร์ชันใดอยู่?
Willem Van Onsem

ฉันใช้เวอร์ชั่น 2.2.5
Pittfall


1
นี่ไม่ใช่สถานการณ์ที่ไม่ซ้ำกันด้วยกันนี่เป็นสิ่งที่ไม่เหมือนใคร แต่มีมากกว่า 2 สาขาถ้ามันสมเหตุสมผล
Pittfall

คำตอบ:


10

ฉันอธิบายตัวเลือกหลายอย่างที่นี่บางทีหนึ่งในนั้นหรือชุดค่าผสมอาจมีประโยชน์สำหรับคุณ

ที่เอาชนะ save

ข้อ จำกัด ของคุณคือกฎทางธุรกิจคุณสามารถแทนที่saveวิธีการเพื่อให้ข้อมูลสอดคล้องกัน:


class GroupedModels(models.Model): 
    # ...
    def clean(self):
        if (self.other_model_one.pk == self.other_model_two.pk):
            raise ValidationError({'other_model_one':'Some message'}) 
        if (self.other_model_one.pk < self.other_model_two.pk):
            #switching models
            self.other_model_one, self.other_model_two = self.other_model_two, self.other_model_one
    # ...
    def save(self, *args, **kwargs):
        self.clean()
        super(GroupedModels, self).save(*args, **kwargs)

เปลี่ยนการออกแบบ

ฉันใส่ตัวอย่างที่เข้าใจง่าย สมมติว่าสถานการณ์นี้:

class BasketballMatch(models.Model):
    local = models.ForeignKey('app.team')
    visitor = models.ForeignKey('app.team')

ตอนนี้คุณต้องการหลีกเลี่ยงทีมที่เล่นด้วยตัวเองและทีม A สามารถเล่นกับทีม B ได้เพียงครั้งเดียว (เกือบกฎของคุณ) คุณสามารถออกแบบโมเดลของคุณใหม่ได้ดังนี้:

class BasketballMatch(models.Model):
    HOME = 'H'
    GUEST = 'G'
    ROLES = [
        (HOME, 'Home'),
        (GUEST, 'Guest'),
    ]
    match_id = models.IntegerField()
    role = models.CharField(max_length=1, choices=ROLES)
    player = models.ForeignKey('app.other_model')

    class Meta:
      unique_together = [ ( 'match_id', 'role', ) ,
                          ( 'match_id', 'player',) , ]

ManyToManyField.symmetrical

ดูเหมือนว่าปัญหาsymetrical จะทำให้ django สามารถจัดการคุณได้ แทนที่จะสร้างGroupedModelsแบบจำลองให้สร้างฟิลด์ ManyToManyField ด้วยตัวเองบนOtherModel:

from django.db import models
class OtherModel(models.Model):
    ...
    grouped_models = models.ManyToManyField("self")

นี่คือสิ่งที่ django สร้างขึ้นสำหรับสถานการณ์เหล่านี้


แนวทางที่หนึ่งคือสิ่งที่ฉันใช้ (แต่หวังว่าจะมีข้อ จำกัด ของฐานข้อมูล) วิธีที่ 2 แตกต่างกันเล็กน้อยในสถานการณ์ของฉันถ้าทีมเล่นเกมพวกเขาไม่สามารถเล่นเกมได้อีก ฉันไม่ได้ใช้วิธีที่ 3 เพราะมีข้อมูลมากกว่าที่ฉันต้องการจัดเก็บในการจัดกลุ่ม ขอบคุณสำหรับคำตอบ.
Pittfall

ถ้าทีมเล่นเกมพวกเขาไม่สามารถเล่นเกมได้อีก เพราะสิ่งนี้ฉันรวมอยู่match_idในข้อ จำกัด ที่ไม่เหมือนกันเพื่ออนุญาตให้ทีมเล่นคู่ที่ไม่ จำกัด เพียงลบฟิลด์นี้เพื่อ จำกัด การเล่นอีกครั้ง
dani herrera

อ่าใช่! ขอบคุณที่ฉันพลาดและโมเดลอื่น ๆ ของฉันอาจเป็นแบบหนึ่งต่อหนึ่ง
Pittfall

1
ฉันคิดว่าฉันชอบตัวเลือกหมายเลข 2 ที่ดีที่สุด ปัญหาเดียวที่ฉันมีคือมันต้องการเนื้อหาที่กำหนดเองสำหรับผู้ใช้ "เฉลี่ย" ในโลกที่ผู้ดูแลระบบใช้เป็น FE น่าเสียดายที่ฉันอยู่ในโลกนี้ แต่ฉันคิดว่านี่ควรเป็นคำตอบที่ยอมรับได้ ขอบคุณ!
Pittfall

ตัวเลือกที่สองเป็นวิธีที่จะไป นี่คือคำตอบที่ดี @Pitfall เกี่ยวกับผู้ดูแลระบบฉันได้เพิ่มคำตอบเพิ่มเติม แบบฟอร์มผู้ดูแลไม่ควรเป็นปัญหาใหญ่ในการแก้ไข
cezar

1

มันไม่ได้เป็นคำตอบที่น่าพอใจมาก แต่น่าเสียดายที่ความจริงก็คือไม่มีวิธีที่จะทำสิ่งที่คุณกำลังอธิบายด้วยคุณสมบัติในตัวที่เรียบง่าย

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

อาจมีวิธีที่ดีกว่าในการจัดโครงสร้างข้อมูลหรือไม่


ใช่คุณถูกต้องว่าต้องถูกเรียกด้วยตนเองซึ่งเป็นสาเหตุที่ฉันไม่ชอบวิธีการ ใช้งานได้ตามที่ฉันต้องการในผู้ดูแลระบบตามที่คุณกล่าวถึง
Pittfall

0

มีคำตอบที่ดีจากdani herrera อยู่แล้ว แต่ฉันต้องการที่จะอธิบายรายละเอียดเพิ่มเติม

ตามที่อธิบายไว้ในตัวเลือกที่สองวิธีแก้ปัญหาตามที่ OP ต้องการคือเปลี่ยนการออกแบบและใช้ข้อ จำกัด ที่ไม่ซ้ำกันสองข้อ การเปรียบเทียบกับการแข่งขันบาสเก็ตบอลแสดงให้เห็นถึงปัญหาในทางปฏิบัติอย่างมาก

แทนที่จะเป็นการแข่งขันบาสเก็ตบอลฉันใช้ตัวอย่างกับเกมฟุตบอล (หรือฟุตบอล) เกมฟุตบอล (ที่ฉันเรียกว่าEvent) เล่นโดยสองทีม (ในแบบจำลองของฉันคือทีมCompetitor) นี่คือความสัมพันธ์แบบหลายต่อหลายคน ( m:n) ซึ่งnจำกัด เพียงสองในกรณีนี้หลักการนี้เหมาะสำหรับจำนวนไม่ จำกัด

นี่คือรูปแบบของเรา:

class Competitor(models.Model):
    name = models.CharField(max_length=100)
    city = models.CharField(max_length=100)

    def __str__(self):
        return self.name


class Event(models.Model):
    title = models.CharField(max_length=200)
    venue = models.CharField(max_length=100)
    time = models.DateTimeField()
    participants = models.ManyToManyField(Competitor)

    def __str__(self):
        return self.title

เหตุการณ์อาจเป็น:

  • หัวข้อ: คาราบาวคัพรอบที่ 4
  • สถานที่: แอนฟิลด์
  • เวลา: 30. ตุลาคม 2562, 19:30 น. GMT
  • เข้าร่วม:
    • ชื่อ: ลิเวอร์พูล, เมือง: ลิเวอร์พูล
    • ชื่อ: Arsenal, city: London

ตอนนี้เราต้องแก้ปัญหาจากคำถาม Django สร้างตารางกลางระหว่างโมเดลที่มีความสัมพันธ์แบบหลายต่อหลายโดยอัตโนมัติ แต่เราสามารถใช้โมเดลที่กำหนดเองและเพิ่มฟิลด์เพิ่มเติม ฉันเรียกรูปแบบนั้นว่าParticipant:

คลาสผู้เข้าร่วม (โมเดลโมเดล):
    บทบาท = (
        ('H', 'Home'),
        ('V', 'ผู้เยี่ยมชม'),
    )
    event = models.ForeignKey (เหตุการณ์, on_delete = models.CASCADE)
    คู่แข่ง = รุ่นต่างประเทศ Keyey (คู่แข่ง, on_delete = รุ่น .CASCADE)
    role = models.CharField (max_length = 1, ตัวเลือก = บทบาท)

    คลาส Meta:
        unique_together = (
            ('เหตุการณ์', 'บทบาท'),
            ('เหตุการณ์', 'คู่แข่ง'),
        )

    def __str __ (ตัวเอง):
        ส่งคืน '{} - {}' รูปแบบ (self.event, self.get_role_display ())

ManyToManyFieldมีตัวเลือกthroughที่ช่วยให้เราสามารถระบุรุ่นกลาง ลองเปลี่ยนมันในโมเดลEvent:

class Event(models.Model):
    title = models.CharField(max_length=200)
    venue = models.CharField(max_length=100)
    time = models.DateTimeField()
    participants = models.ManyToManyField(
        Competitor,
        related_name='events', # if we want to retrieve events for a competitor
        through='Participant'
    )

    def __str__(self):
        return self.title

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

ในเหตุการณ์เฉพาะ (เกมฟุตบอล) อาจมีทีมเหย้าเพียงทีมเดียวและทีมผู้เยี่ยมชมเพียงทีมเดียวเท่านั้น สโมสร ( Competitor) สามารถปรากฏได้ทั้งในฐานะทีมเหย้าหรือทีมเยือน

เราจะจัดการสิ่งเหล่านี้ทั้งหมดในผู้ดูแลระบบได้อย่างไร แบบนี้:

from django.contrib import admin

from .models import Competitor, Event, Participant


class ParticipantInline(admin.StackedInline): # or admin.TabularInline
    model = Participant
    max_num = 2


class CompetitorAdmin(admin.ModelAdmin):
    fields = ('name', 'city',)


class EventAdmin(admin.ModelAdmin):
    fields = ('title', 'venue', 'time',)
    inlines = [ParticipantInline]


admin.site.register(Competitor, CompetitorAdmin)
admin.site.register(Event, EventAdmin)

เราได้เพิ่มเป็นแบบอินไลน์ในParticipant EventAdminเมื่อเราสร้างใหม่Eventเราสามารถเลือกทีมบ้านและทีมผู้เยี่ยมชม ตัวเลือกmax_numจะ จำกัด จำนวนของรายการที่ 2 ดังนั้นจึงไม่สามารถเพิ่มได้อีก 2 ทีมต่อเหตุการณ์

สิ่งนี้สามารถ refactored สำหรับกรณีการใช้งานที่แตกต่างกัน สมมติว่ากิจกรรมของเราคือการแข่งขันว่ายน้ำและแทนที่จะเป็นบ้านและผู้มาเยือนเรามีเลน 1-8 เราแค่ปรับโครงสร้างParticipant:

class Participant(models.Model):
    ROLES = (
        ('L1', 'lane 1'),
        ('L2', 'lane 2'),
        # ... L3 to L8
    )
    event = models.ForeignKey(Event, on_delete=models.CASCADE)
    competitor = models.ForeignKey(Competitor, on_delete=models.CASCADE)
    role = models.CharField(max_length=1, choices=ROLES)

    class Meta:
        unique_together = (
            ('event', 'role'),
            ('event', 'competitor'),
        )

    def __str__(self):
        return '{} - {}'.format(self.event, self.get_role_display())

ด้วยการปรับเปลี่ยนนี้เราสามารถมีกิจกรรมนี้:

  • ชื่อเรื่อง: FINA 2019, นักกรรเชียง 50 ม. คนสุดท้าย,

    • สถานที่: ศูนย์กีฬาทางน้ำแห่งชาติมหาวิทยาลัย Nambu
    • เวลา: 28. กรกฎาคม 2019, 20:02 UTC + 9
    • เข้าร่วม:

      • ชื่อ: Michael Andrew, เมือง: Edina, USA, บทบาท: lane 1
      • ชื่อ: Zane Waddell, เมือง: Bloemfontein, แอฟริกาใต้, บทบาท: lane 2
      • ชื่อ: Evgeny Rylov, เมือง: Novotroitsk, รัสเซีย, บทบาท: lane 3
      • ชื่อ: Kliment Kolesnikov, เมือง: Moscow, Russia, บทบาท: lane 4

      // จากเลน 5 ถึงเลน 8 (ที่มา: Wikipedia

นักว่ายน้ำสามารถปรากฏได้เพียงครั้งเดียวในความร้อนและสามารถครอบครองเลนได้เพียงครั้งเดียวในความร้อน

ฉันใส่รหัสเพื่อ GitHub: https://github.com/cezar77/competition

เครดิตทั้งหมดไปที่ dani herrera ฉันหวังว่าคำตอบนี้จะช่วยเพิ่มมูลค่าให้กับผู้อ่าน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.