दो वैकल्पिक, लेकिन एक अनिवार्य विदेशी कुंजी के साथ एक मॉडल बनाना


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के लिए गैर -common भागों।
विलेम वैन ओन्सेम

संभवतः असंबंधित, लेकिन unique=Trueआपके FK फ़ील्ड में भाग का मतलब है कि Inspectionकिसी दिए गए GroupIDया SiteIDउदाहरण के लिए केवल एक ही उदाहरण मौजूद हो सकता है - IOW, यह एक से एक संबंध है, एक से कई के लिए नहीं। क्या यह वास्तव में आप चाहते हैं?
ब्रूनो डेस्टहिलियर्स

"वर्तमान में इस सेट के साथ इसे दोनों की आवश्यकता है।" => तकनीकी रूप से, यह नहीं है - डेटाबेस स्तर पर, आप या तो उन दोनों की या दोनों में से कोई भी कुंजी सेट कर सकते हैं (ऊपर उल्लिखित कैविटी के साथ)। यह केवल जब एक ModelForm (सीधे या django व्यवस्थापक के माध्यम से) का उपयोग कर रहा है कि उन क्षेत्रों को आवश्यक के रूप में चिह्नित किया जाएगा, और ऐसा इसलिए है क्योंकि आपने 'रिक्त = सत्य' तर्क पारित नहीं किया था।
ब्रूनो डेथिलियर्स

@brunodesthuilliers हाँ विचार है करने के लिए है Inspectionके बीच एक कड़ी हो Groupया Siteऔर एक InspectionID, तो मैं के रूप में कई "निरीक्षण" हो सकता है InspectionReportकि एक रिश्ते के लिए। ऐसा इसलिए किया गया ताकि मैं Dateएक Groupया उससे संबंधित सभी रिकॉर्ड के लिए अधिक आसानी से छांट सकूं Site। आशा है कि समझ में आता है
कैलमैक

@ Cm0295 मुझे डर है कि इस अप्रत्यक्ष स्तर के बिंदु को न देखें - समूह / साइट FKs को सीधे निरीक्षणालय में डालने से सटीक एक ही सेवा मिलती है AFAICT - उपयुक्त निरीक्षण द्वारा अपने निरीक्षण फ़िल्टर को फ़िल्टर करें (या बस साइट से रिवर्स डिस्क्रिप्टर का पालन करें) समूह), उन्हें तिथि के अनुसार क्रमबद्ध करें और आप कर रहे हैं।
ब्रूनो डेथिलियर्स

जवाबों:


5

मेरा सुझाव है कि आप इस तरह के सत्यापन Django तरह से करते हैं

cleanDjango मॉडल की विधि को ओवरराइड करके

class Inspection(models.Model):
    ...

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

ध्यान दें, हालांकि, जैसे कि Model.full_clean (), मॉडल का स्वच्छ () पद्धति को लागू नहीं किया जाता है जब आप अपने मॉडल की सेव () विधि कहते हैं। मॉडल के डेटा को मान्य करने के लिए इसे मैन्युअल रूप से कॉल करने की आवश्यकता है, या आप Modelक्लास को बचाने के तरीके को ट्रिगर करने से पहले इसे हमेशा साफ () विधि से कॉल करने के लिए मॉडल की सेव विधि को ओवरराइड कर सकते हैं।


एक और समाधान जो जेनेरिकलेरेशन का उपयोग करने में मदद कर सकता है , एक बहुरूपिक क्षेत्र प्रदान करने के लिए जो एक से अधिक तालिका से संबंधित है, लेकिन यह तब हो सकता है जब इन तालिकाओं / वस्तुओं को पहली जगह से सिस्टम डिजाइन में परस्पर उपयोग किया जा सकता है।


2

जैसा कि टिप्पणियों में उल्लेख किया गया है, इसका कारण यह है कि "इस सेट अप के साथ दोनों की आवश्यकता है" बस इतना है कि आप blank=Trueअपने एफके फ़ील्ड में जोड़ना भूल गए हैं , इसलिए आपका ModelForm(या तो कस्टम एक या व्यवस्थापक द्वारा उत्पन्न डिफ़ॉल्ट) फॉर्म फ़ील्ड को आवश्यक बना देगा । डीबी स्कीमा स्तर पर, आप दोनों में से किसी एक या एफके को नहीं भर सकते हैं, यह ठीक होगा क्योंकि आपने उन डीबी क्षेत्रों को अशक्त ( null=Trueतर्क के साथ ) बना दिया है।

इसके अलावा, (cf my other comments), आप यह जांचना चाहते हैं कि आपका वास्तव में FK अद्वितीय होना चाहता है। यह तकनीकी रूप से आपके एक रिश्ते को एक से एक रिश्ते में बदल देता है - आपको दिए गए GroupID या SiteId के लिए केवल एक एकल 'निरीक्षण' रिकॉर्ड की अनुमति है (आप एक GroupId या SiteId के लिए दो या अधिक 'निरीक्षण' नहीं कर सकते) । यदि यह वास्तव में है कि आप क्या चाहते हैं, तो आप इसके बजाय एक स्पष्ट OneToOneField का उपयोग करना चाह सकते हैं (db स्कीमा समान होगी लेकिन मॉडल अधिक स्पष्ट होगा और संबंधित डिस्क्रिप्टर इस उपयोग के मामले के लिए बहुत अधिक उपयोगी होगा)।

एक साइड नोट के रूप में: एक Django मॉडल में, एक विदेशीके फील्ड एक संबंधित मॉडल उदाहरण के रूप में, एक कच्ची आईडी के रूप में भौतिकवाद करता है। 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। बीटीडब्ल्यू, पायथन में, नामकरण सम्मेलन 'all_lower_with_underscores' वर्ग के नाम और छद्म-स्थिरांक के अलावा और कुछ के लिए है।

अब आपके मूल प्रश्न के लिए: डेटाबेस स्तर पर "एक या दूसरे" बाधा को लागू करने का कोई विशिष्ट मानक SQL तरीका नहीं है, इसलिए यह आमतौर पर एक CHECK बाधा का उपयोग करके किया जाता है , जो कि मॉडल के मेटा "बाधाओं" के साथ एक Django मॉडल में किया जाता है विकल्प

यह कहा जा रहा है, कि db स्तर पर कैसे बाधाओं का वास्तव में समर्थन और लागू किया जाता है, यह आपके DB विक्रेता (MySQL <8.0.16 केवल सादा उदाहरण के लिए उन्हें अनदेखा करता है) पर निर्भर करता है , और आपको जिस तरह की बाधा की आवश्यकता होगी, वह फ़ॉर्म पर लागू नहीं होगी या मॉडल स्तर की मान्यता , केवल जब मॉडल को बचाने की कोशिश कर रहा है, तो आप भी मॉडल के स्तर (अधिमानतः) या प्रपत्र स्तर सत्यापन में दोनों (या) मॉडल या प्रपत्र की clean()विधि में सत्यापन जोड़ना चाहते हैं ।

तो एक लंबी कहानी बनाने के लिए:

  • पहले unique=Trueदोहराएं कि आप वास्तव में यह बाधा चाहते हैं , और यदि हाँ तो अपने FK फ़ील्ड को OneToOneField से बदलें।

  • blank=Trueअपने FK (या OneToOne) दोनों फ़ील्ड में एक arg जोड़ें

  • अपने मॉडल के मेटा में उचित चेक बाधा जोड़ें - डॉक्टर सक्सेस है, लेकिन फिर भी पर्याप्त स्पष्ट है यदि आप ORM के साथ जटिल प्रश्न करना जानते हैं (और यदि आपके पास सीखने का समय नहीं है;;)
  • clean()अपने मॉडल के लिए एक विधि जोड़ें जो आपके या तो एक या दूसरे क्षेत्र की जाँच करता है और एक सत्यापन त्रुटि उठाता है

और आपको ठीक होना चाहिए, अपने RDBMS का सम्मान करते हुए निश्चित रूप से चेक बाधाओं को दूर करना चाहिए।

बस ध्यान दें, इस डिजाइन के साथ, आपका Inspectionमॉडल पूरी तरह से बेकार है (अभी तक महंगा है!) अप्रत्यक्ष - आपको एफकेएस (और बाधाओं, सत्यापन आदि) को सीधे स्थानांतरित करके कम कीमत पर सटीक समान सुविधाएं मिलेंगी InspectionReport

अब एक और समाधान हो सकता है - निरीक्षण मॉडल रखें, लेकिन रिश्ते के दूसरे छोर पर (साइट और समूह में) एफके को वनटॉइनफिल्ड के रूप में रखें:

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()

यह किसी भी विशिष्ट बाधा या मान्यता को जोड़ने से बचता है, लेकिन एक अतिरिक्त अप्रत्यक्ष स्तर (एसक्यूएल joinक्लॉज आदि) की कीमत पर।

उन समाधानों में से कौन सा "सबसे अच्छा" वास्तव में संदर्भ-निर्भर है, इसलिए आपको दोनों के निहितार्थों को समझना होगा और जांचना होगा कि आप आमतौर पर अपने मॉडल का उपयोग कैसे करते हैं यह जानने के लिए कि आपकी अपनी आवश्यकताओं के लिए अधिक उपयुक्त है। जहां तक ​​मेरा संबंध है और अधिक संदर्भ के बिना (या संदेह में) मैं कम अप्रत्यक्ष स्तरों के साथ समाधान का उपयोग करूंगा, लेकिन YMMV।

जेनेरिक संबंधों के बारे में एनबी: वे काम कर सकते हैं जब आपके पास वास्तव में बहुत सारे संबंधित मॉडल होते हैं और / या पहले से नहीं जानते हैं कि कौन से मॉडल आपके खुद से संबंधित होना चाहते हैं। यह पुन: प्रयोज्य एप्लिकेशन ("टिप्पणी" या "टैग" आदि सुविधाओं) या एक्स्टेंसिबल लोगों (सामग्री प्रबंधन रूपरेखा आदि) के लिए विशेष रूप से उपयोगी है। नकारात्मक पक्ष यह है कि यह क्वेरी को अधिक भारी बनाता है (और जब आप अपने db पर मैन्युअल क्वेरी करना चाहते हैं तो अव्यवहारिक)। अनुभव से, वे जल्दी से एक PITA बॉट wrt / code और perfs बन सकते हैं, इसलिए जब कोई बेहतर समाधान (और / या जब रखरखाव और रनवे ओवरहेड एक मुद्दा नहीं है) के लिए उन्हें रखना बेहतर होगा।

मेरे 2 सेंट।


2

Django में DB बाधाओं को बनाने के लिए एक नया (2.2 के बाद से) इंटरफ़ेस है: https://docs.djangoproject.com/en/3.0/ref/models/constraints/

आप एक 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'),
            ),
        ]

यह केवल डीबी स्तर पर बाधा को लागू करेगा। आपको अपने फॉर्म या धारावाहिकों में इनपुट को मैन्युअल रूप से मान्य करना होगा।

साइड नोट के रूप में, आपको संभवतः OneToOneFieldइसके बजाय उपयोग करना चाहिए ForeignKey(unique=True)। आप भी चाहेंगे blank=True


0

मुझे लगता है कि आप सामान्य संबंधों , डॉक्स के बारे में बात कर रहे हैं । आपका उत्तर इस के समान है ।

कुछ समय पहले मुझे जेनेरिक संबंधों का उपयोग करने की आवश्यकता थी, लेकिन मैंने एक किताब में पढ़ा था और कहीं और उपयोग से बचा जाना चाहिए, मुझे लगता है कि यह दो स्कूप ऑफ Django था।

मैंने इस तरह एक मॉडल तैयार किया:

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

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

मुझे यकीन नहीं है कि यह एक अच्छा समाधान है और जैसा कि आपने उल्लेख किया है कि आप इसका उपयोग नहीं करेंगे, लेकिन यह मेरे मामले में काम किया है।


"मैं एक किताब और कहीं और पढ़ता हूं" कुछ करने (या करने से बचने) के सबसे खराब संभावित कारण के बारे में है।
ब्रूनो डेस्टहिलियर्स

@brunodesthuilliers मुझे लगा कि Django के दो स्कूप एक अच्छी किताब थी।
लुइस सिल्वा

नहीं बता सकता, मैंने इसे नहीं पढ़ा है। लेकिन यह असंबंधित है: मेरी बात यह है कि यदि आप यह नहीं समझते हैं कि पुस्तक ऐसा क्यों कहती है, तो यह ज्ञान नहीं है और न ही अनुभव, यह धार्मिक विश्वास है। जब यह धर्म की बात आती है, तो मुझे धार्मिक विश्वास नहीं होता, लेकिन सीएस में इनका कोई स्थान नहीं है। या तो आप समझते हैं कि कुछ सुविधा के पेशेवरों और विपक्ष क्या हैं और फिर आप यह निर्धारित कर सकते हैं कि क्या यह किसी दिए गए संदर्भ में उचित है , या आप नहीं करते हैं और फिर आपको जो भी पढ़ा है उसे नासमझ तोता नहीं करना चाहिए। जेनेरिक संबंधों के लिए बहुत ही वैध उपयोग के मामले हैं, बिंदु यह नहीं है कि वे उनसे बचें, बल्कि यह जानें कि उन्हें कब बचना है।
ब्रूनो डेस्टहिलियर्स

नायब मैं पूरी तरह से समझता हूं कि कोई व्यक्ति सीएस के बारे में सब कुछ नहीं जान सकता है - ऐसे डोमेन हैं जहां मेरे पास कुछ किताबों पर भरोसा करने के अलावा और कोई विकल्प नहीं है। लेकिन तब मैं शायद उस विषय पर प्रश्नों का उत्तर नहीं दूंगा ;-)
ब्रूनो डेथिलियर्स

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कार्यों में उपयोग करते हैं।

यह बहुत ज्यादा आपके कोड को स्पष्ट और बग प्रूफ बनाता है। प्रवर्तन को बहुत आसानी से बायपास किया जा सकता है। लेकिन मुद्दा यह है कि इसे ठीक करने के लिए इस सटीक सीमा को पूर्व ज्ञान की आवश्यकता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.