İki isteğe bağlı, ancak bir zorunlu yabancı anahtarla model oluşturma


9

Benim sorunum, ne tür bir model olduğunu söylemek için iki yabancı anahtardan birini alabilen bir modelim olması. En az bir tane almasını istiyorum ama ikisini birden almamasını istiyorum. Bu hala bir model olabilir mi yoksa iki türe ayırmalı mıyım? İşte kod:

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)

Sorun Inspectionmodeldir. Bu, bir gruba veya bir siteye bağlanmalıdır, ancak her ikisine birden bağlanmamalıdır. Şu anda bu kurulumla her ikisine de ihtiyacı var.

Bunu neredeyse iki özdeş modele bölmek istemem GroupInspectionve SiteInspectionbu yüzden onu tek bir model olarak tutan herhangi bir çözüm ideal olacaktır.


Belki de alt sınıf kullanımı burada daha iyidir. Bir yapabilir Inspectionsınıfını ve sonra içine alt sınıf SiteInspectionve GroupInspectioniçin sigara -ortak parçaları.
Willem Van Onsem

Muhtemelen ilgisiz, ancak unique=TrueFK alanlarınızdaki bölüm, Inspectionbelirli bir örnek GroupIDveya SiteIDörnek için yalnızca bir örneğin var olabileceği anlamına gelir - IOW, bu bire bir ilişki, bire çok değil. Gerçekten istediğin bu mu?
bruno desthuilliers

"Şu anda bu kurulumda her ikisine de ihtiyacı var." => teknik olarak, veritabanı düzeyinde, bu anahtarların her ikisini de ya da hiçbirini ayarlayabilirsiniz (yukarıda belirtilen uyarı ile). Yalnızca bir ModelForm (doğrudan veya django admin aracılığıyla) kullanıldığında bu alanlar gerektiği gibi işaretlenir ve bunun nedeni 'blank = True' bağımsız değişkenini geçmemenizdir.
bruno desthuilliers

@brunodesthuilliers Evet fikir, ya da ve Inspectionarasında bir bağlantı olmaktır , o zaman bu bir ilişki için birden fazla "denetime" sahip olabilirim . Bu yapıldığını ben daha kolayca sıralama tarafından can böylece biriyle ilgili tüm kayıtlar için veya . Umut bu mantıklıGroupSiteInspectionIDInspectionReportDateGroupSite
CalMac

@ Cm0295 Korkarım bu dolaylılık seviyesinin noktasını görmüyorum - grup / site FK'lerini doğrudan InspectionReport'a koymak aynı AFAICT hizmetini verir - InspectionReports'larınızı uygun anahtarla filtreleyin (veya yalnızca Site'deki ters tanımlayıcıyı izleyin veya Grup), tarihe göre sıralayın ve işiniz bitti.
bruno desthuilliers

Yanıtlar:


5

Django yolunda böyle bir doğrulama yapmanızı öneririm

cleanDjango Modeli yöntemini geçersiz kılarak

class Inspection(models.Model):
    ...

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

Ancak, Model.full_clean () gibi, modelinizin save () yöntemini çağırdığınızda bir modelin clean () yönteminin çağrılmadığını unutmayın. modelin verilerini doğrulamak için manuel olarak çağrılması gerekir veya Modelsınıf kaydetme yöntemini tetiklemeden önce her zaman clean () yöntemini çağırmak için modelin kaydetme yöntemini geçersiz kılabilirsiniz.


Yardımcı olabilecek başka bir çözüm , birden fazla tabloyla ilişkili bir polimorfik alan sağlamak için GenericRelations kullanmaktır , ancak bu tabloların / nesnelerin ilk etapta sistem tasarımında birbirinin yerine kullanılabilmesi söz konusu olabilir.


2

Yorumlarda belirtildiği gibi, "bu kurulumla her ikisine de ihtiyaç duyması" nın sebebi sadece blank=TrueFK alanlarınızı eklemeyi unutmanızdır , böylece ModelForm(özel alan veya yönetici tarafından oluşturulan varsayılan) form alanını zorunlu hale getirecektir . Db şema düzeyinde, bu FK'lardan birini veya hiçbirini doldurabilirsiniz, çünkü bu db alanlarını nulllable ( null=Trueargüman ile) yaptığınızdan, tamam olur .

Ayrıca, (diğer yorumlarım varsa), FK'larınızın gerçekten benzersiz olmasını istediğinizi kontrol etmek isteyebilirsiniz. Bu, teknik olarak bir çok ilişkinizi bire bir ilişkiye dönüştürür - belirli bir GroupID veya SiteId için yalnızca bir tek 'inceleme' kaydına izin verilir (bir GroupId veya SiteId için iki veya daha fazla 'denetiminiz olamaz). . GERÇEKTEN istediğiniz bu ise, bunun yerine açık bir OneToOneField kullanmak isteyebilirsiniz (db şeması aynı olacaktır ancak model daha açık ve ilgili tanımlayıcı bu kullanım durumu için çok daha kullanışlı olacaktır).

Bir yan not olarak: Django Modelinde, bir ForeignKey alanı ham kimlik olarak değil ilgili bir model örneği olarak gerçekleşir. IOW, bu verilen:

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)

sonra bar.fooçözülecek foo, değil foo.id. Bu yüzden kesinlikle InspectionIDve SiteIDalanlarınızı uygun inspectionve olarak yeniden adlandırmak istiyorsunuz site. BTW, Python'da, adlandırma kuralı, sınıf adları ve sözde sabitler dışında herhangi bir şey için 'all_lower_with_underscores' şeklindedir.

Şimdi temel sorunuz için: veritabanı düzeyinde "biri veya diğeri" kısıtlamasını zorlamanın standart bir SQL yolu yoktur, bu nedenle genellikle Django modelinde modelin meta "kısıtlamaları" ile yapılan bir CHECK kısıtlaması kullanılarak yapılır. seçeneğini seçin .

Bununla birlikte, db düzeyinde kısıtlamaların gerçekte nasıl desteklendiği ve uygulandığı DB satıcınıza bağlıdır (MySQL <8.0.16 sadece bunları görmezden gelin ) ve burada ihtiyacınız olacak kısıtlama türü formda uygulanmayacaktır. veya model seviyesi doğrulama , çok, modeli kurtarmaya çalışırken yalnızca ayrıca en ya doğrulama eklemek istediğiniz modeli düzeyinde (sırasıyla.) modeli veya formun her iki durumda da (tercihen) veya form düzeyi doğrulama clean()yöntemiyle.

Uzun bir hikaye kısaca anlatmak gerekirse:

  • ilk önce bu unique=Truekısıtlamayı gerçekten istediğinizden emin olun ve evet ise FK alanınızı bir OneToOneField ile değiştirin.

  • Bir eklemek blank=Trueiçin FK (veya OneToOne) hem arg alanları

  • modelinizin meta'sına uygun kontrol kısıtlamasını ekleyin - doküman, kısa, ancak ORM ile karmaşık sorgular yapmayı biliyorsanız hala yeterince açıktır (ve eğer yapmadıysanız öğrenirsiniz ;-))
  • clean()modelinize bir veya diğer alanınızı kontrol eden ve başka bir doğrulama hatası veren bir yöntem ekleyin

ve RDBMS'nizin elbette kontrol kısıtlamalarına saygı duyduğunu varsayarak iyi olmalısınız.

Bu tasarımla, Inspectionmodelinizin tamamen işe yaramaz (ancak maliyetli!) Bir dolaylı olduğunu unutmayın - FK'leri (ve kısıtlamaları, doğrulama vb.) Doğrudan içine taşıyarak aynı özellikleri daha düşük bir maliyetle elde edersiniz InspectionReport.

Şimdi başka bir çözüm olabilir - Denetim modelini koruyun, ancak FK'yi ilişkinin diğer ucuna (Site ve Grup'ta) OneToOneField olarak koyun:

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)

Ardından, belirli bir Site veya Grup için tüm raporları ile alabilirsiniz yoursite.inspection.inspectionreport_set.all().

Bu, herhangi bir özel kısıtlama veya doğrulama eklemek zorunda kalmamakta, ancak ek bir dolaylama düzeyi (SQL joinyantümcesi vb.) Pahasına olmaktadır.

Bu çözümlerden hangisinin "en iyi" olacağı gerçekten bağlama bağlıdır, bu nedenle her ikisinin de sonuçlarını anlamanız ve hangilerinin kendi ihtiyaçlarınıza daha uygun olduğunu bulmak için modellerinizi nasıl kullandığınızı kontrol etmeniz gerekir. Endişe duyduğum kadarıyla ve daha fazla bağlam (veya şüphesiz) olmadan, çözümü daha az dolaylı seviyelerle, ancak YMMV ile kullanmayı tercih ederim.

Genel ilişkilerle ilgili not: olası pek çok ilgili modeliniz olduğunda ve / veya kendinizle hangi modelleri ilişkilendirmek isteyeceğinizi bilmiyorsanız bunlar kullanışlı olabilir. Bu, yeniden kullanılabilir uygulamalar ("yorumlar" veya "etiketler" vb özelliklerini düşünün) veya genişletilebilir uygulamalar (içerik yönetimi çerçeveleri vb.) İçin özellikle yararlıdır. Dezavantajı, sorgulamayı çok daha ağır hale getirir (ve db'nizde manuel sorgular yapmak istediğinizde pratik değildir). Deneyimden, hızlı bir şekilde bir PITA bot wrt / kodu ve perfs olabilirler, bu yüzden daha iyi bir çözüm olmadığında (ve / veya bakım ve çalışma zamanı yükü bir sorun olmadığında) tutmak daha iyidir.

Benim 2 sentim.


2

Django, DB kısıtlamaları oluşturmak için yeni (2.2'den beri) bir arayüze sahiptir: https://docs.djangoproject.com/en/3.0/ref/models/constraints/

Birini CheckConstraintve yalnızca birini boş bırakmak için bir kullanabilirsiniz . Açıklık için iki tane kullanıyorum:

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'),
            ),
        ]

Bu, kısıtlamayı yalnızca DB düzeyinde uygular. Formlarınızdaki veya serileştiricilerinizdeki girdileri manuel olarak doğrulamanız gerekir.

Bir yan not olarak, muhtemelen OneToOneFieldyerine kullanmalısınız ForeignKey(unique=True). Siz de isteyeceksiniz blank=True.


0

Bence Genel ilişkilerden bahsediyorsun , dokümanlar . Yanıtınız benzeyen bu bir .

Bir süre önce Jenerik ilişkileri kullanmam gerekiyordu ama bir kitapta ve başka bir yerde kullanımdan kaçınılması gerektiğini okudum, bence bu Django'nun İki Kepçesi idi.

Sonunda böyle bir model oluşturdum:

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

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

İyi bir çözüm olup olmadığından emin değilim ve belirttiğiniz gibi kullanmak istemezsiniz, ancak bu benim durumumda işe yaradı.


"Bir kitapta ve başka bir yerde okudum" bir şey yapmak (veya yapmaktan kaçınmak) için en kötü sebeple ilgilidir.
bruno desthuilliers

@brunodesthuilliers Django'dan İki Kepçe'nin iyi bir kitap olduğunu düşündüm.
Luis Silva

Söyleyemem, okumadım. Ama bu ilgisiz: Demek istediğim, kitabın neden böyle söylediğini anlamıyorsanız, o zaman bilgi ya da deneyim değil, dini inançtır. Dine gelince dini inanca aldırmıyorum, fakat CS'de yeri yok. Ya bazı özelliklerin artılarını ve eksilerini anlarsınız ve daha sonra belirli bir bağlamda uygun olup olmadığını yargılayabilir veya yapmazsınız ve sonra okuduklarınızı dikkatsizce papağan yapmamalısınız. Genel ilişkiler için çok geçerli bir kullanım durumu vardır, asıl mesele onlardan kaçınmak değil, onlardan ne zaman kaçınacağını bilmek.
bruno desthuilliers

Not: CS ile ilgili her şeyi bilemeyeceğimi çok iyi anlıyorum - bazı kitaplara güvenmekten başka seçeneğim olmayan alanlar var. Ama sonra muhtemelen bu konudaki soruları cevaplamayacağım ;-)
bruno desthuilliers

0

Sorunuzu cevaplamak için geç olabilir, ancak çözümümün başka bir kişinin durumuna uygun olabileceğini düşündüm.

Yeni bir model oluşturacağım, diyelim Dependencyve mantığı bu modele uygulayacağım.

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)

O zaman çok açık bir şekilde uygulanacak bir mantık yazardım.

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)

Şimdi sadece tek bir oluşturmak için gereken ForeignKeysizin için Inspectionbir model.

Senin içinde viewfonksiyonları, bir oluşturmak için gereken Dependencynesne ve ardından atamak Inspectionrekor. Emin kullandığınız olun create_dependency_objectsizin de viewfonksiyonları.

Bu, kodunuzu açıkça ve hataya dayanıklı hale getirir. Uygulama çok kolay atlanabilir. Ancak asıl nokta, bu kesin sınırlamanın atlanması için önceden bilgiye ihtiyaç duymasıdır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.