2 alan üzerinde benzersiz bir kimlik oluşturmanın bir yolu var mı?


14

İşte benim modelim:

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

Temel olarak, istediğim şey other_modelbu tabloda benzersiz olmak. Nerede bir kayıt varsa o vasıtası Yani other_model_oneid 123, ben izin vermemelidir bir rekor ile oluşturulacak other_model_twoolarak id 123. cleanSanırım geçersiz kılabilirim ama django'nun yerleşik bir şey olup olmadığını merak ediyordum.

PSQL ile 2.2.5 sürümünü kullanıyorum.

Düzenleme: Bu birlikte bir durum değildir. Birlikte bir kayıt eklerseniz other_model_one_id=1ve diğer other_model_two_id=2, ben başka bir kayıt eklemek mümkün olmamalı other_model_one_id=2ve diğerother_model_two_id=1


Hangi Django sürümünü kullanıyorsunuz?
Willem Van Onsem

Sürüm 2.2.5 kullanıyorum
Pittfall


1
Bu benzersiz bir birliktelik değil, bu eşsiz bir durum ama eğer mantıklıysa 2'den fazla alan.
Pittfall

Yanıtlar:


10

Burada birkaç seçeneği açıklarım, belki bunlardan biri veya bir kombinasyon sizin için yararlı olabilir.

ağır basan save

Kısıtlamanız bir iş kuralıdır, saveverileri tutarlı tutmak için yöntemi geçersiz kılabilirsiniz :


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)

Tasarımı değiştirin

Anlaması kolay bir örnek koydum. Bu senaryoyu varsayalım:

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

Şimdi, bir takımın kendisiyle bir maç oynamasını önlemek istiyorsunuz, A takımı da sadece bir kez B takımıyla oynayabilir (neredeyse kurallarınız). Modellerinizi şu şekilde yeniden tasarlayabilirsiniz:

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

Bu simetrik bir konu gibi görünüyor , django bunu sizin için halledebilir. GroupedModelsModel oluşturmak yerine, kendisiyle birlikte bir ManyToManyField alanı oluşturun OtherModel:

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

Django'nun bu senaryolar için oluşturduğu şey budur.


Bir yaklaşım kullandığım (ama bir veritabanı kısıtlaması umuyordum). Yaklaşım 2, senaryomda biraz farklıdır, eğer bir takım bir oyun oynadıysa, bir daha asla oyun oynayamazlar. Grup 3'te depolamak istediğim daha fazla veri olduğu için 3. yaklaşımı kullanmadım. Cevap için teşekkürler.
Pittfall

eğer bir takım oyun oynamışsa, bir daha asla oyun oynayamazlar. çünkü bu match_id, takımların sınırsız maç oynamasına izin vermek için unike kısıtlamasına dahil ettim . Oynatmayı tekrar kısıtlamak için bu alanı kaldırmanız yeterlidir.
dani herrera

Ah evet! teşekkür ederim bunu özledim ve diğer modelim bire bir alan olabilir.
Pittfall

1
En iyi seçenek 2'yi sevdiğimi düşünüyorum. Bununla ilgili tek sorun, yönetici FE olarak kullanıldığı bir dünyada tartışmasız "ortalama" kullanıcı için özel bir forma ihtiyaç duymasıdır. Maalesef o dünyada yaşıyorum. Ama bence bu kabul edilen cevap olmalı. Teşekkürler!
Pittfall

İkinci seçenek gidilecek yoldur. Bu harika bir cevap. @Pitfall yönetici ile ilgili başka bir cevap ekledim. Yönetici formunun çözülmesi büyük bir sorun olmamalıdır.
cezar

1

Çok tatmin edici bir cevap değil, ama ne yazık ki gerçek, basit bir yerleşik özellik ile tanımladığınız şeyi yapmanın bir yolu yok.

Ne ile tarif cleanişe yarayacak, ama sadece ModelForm kullanırken otomatik olarak çağrıldığını düşünüyorum gibi manuel olarak aramak için dikkatli olmak zorunda. Sen mümkün olabilir karmaşık veritabanı kısıtlama oluşturmak ama bu Django dışında yaşayacaktı ve (ne zaman bir işlemin ortasında Django zor olabilir) veritabanı istisnalar işlemek gerekir.

Belki de verileri yapılandırmanın daha iyi bir yolu var mı?


Evet, manuel olarak çağrılması gerektiği konusunda haklısınız, bu yüzden yaklaşımı beğenmedim. Bahsettiğiniz gibi sadece yönetici istediğim gibi çalışır.
Pittfall

0

Büyük zaten var cevabı gelen dani herrera ancak ben daha bunun üzerinde durmak istiyoruz.

İkinci seçenekte açıklandığı gibi, OP'nin gerektirdiği çözüm tasarımı değiştirmek ve iki benzersiz kısıtlamayı çift olarak uygulamaktır. Basketbol maçlarıyla analoji, sorunu çok pratik bir şekilde göstermektedir.

Basketbol maçı yerine futbol (veya futbol) oyunlarında örnek kullanıyorum. Bir futbol oyunu (buna ben diyorum Event) iki takım tarafından oynanır (modellerimde bir takım Competitor). Bu, çoktan çoğa bir ilişkidir ( m:n), nbu özel durumda iki ile sınırlıdır, ilke sınırsız sayıda için uygundur.

Modellerimiz şöyle görünüyor:

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

Bir olay şunlar olabilir:

  • Başlık: Carabao Kupası, 4. tur,
  • mekan: Anfield
  • zaman: 30. Ekim 2019, 19:30 GMT
  • katılımcılar:
    • isim: Liverpool, şehir: Liverpool
    • isim: Arsenal, şehir: Londra

Şimdi sorunu sorudan çözmek zorundayız. Django, çoktan çoğa ilişkisi olan modeller arasında otomatik olarak bir ara tablo oluşturur, ancak özel bir model kullanabilir ve daha fazla alan ekleyebiliriz. Ben buna model diyorum Participant:

sınıf Katılımcısı (modeller. Model):
    ROLLER = (
        ('H', 'Ev'),
        ('V', 'Ziyaretçi'),
    )
    event = models.ForeignKey (Etkinlik, on_delete = models.CASCADE)
    competitor = models.ForeignKey (Rakip, on_delete = models.CASCADE)
    role = models.CharField (max_length = 1, seçenekler = ROLES)

    Meta sınıfı:
        unique_together = (
            ('etkinlik', 'rol'),
            ('etkinlik', 'rakip'),
        )

    def __str __ (öz):
        dönüş '{} - {}'. biçimi (self.event, self.get_role_display ())

Bu ManyToManyField, throughara modeli belirlememize izin veren bir seçeneğe sahiptir . Modelde bunu değiştirelim 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

Benzersiz kısıtlamalar artık etkinlik başına yarışmacı sayısını otomatik olarak ikiyle sınırlayacaktır (çünkü yalnızca iki rol vardır: Ev ve Ziyaretçi ).

Belirli bir etkinlikte (futbol oyunu) sadece bir ev sahibi takım ve sadece bir ziyaretçi ekibi olabilir. Bir kulüp ( Competitor) ev sahibi takım veya ziyaretçi takımı olarak görünebilir.

Tüm bunları şimdi yöneticide nasıl yönetiriz? Bunun gibi:

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)

ParticipantOlarak satır içi olarak ekledik EventAdmin. Yeni Eventyarattığımızda ev sahibi takımı ve ziyaretçi ekibini seçebiliriz. Seçenek max_num, giriş sayısını 2 ile sınırlar, bu nedenle etkinlik başına 2'den fazla ekip eklenemez.

Bu, farklı kullanım durumları için yeniden düzenlenebilir. En Bizim olaylar yüzme yarışmaları ve yerine ev ve ziyaretçinin, biz Biz sadece planı ayrı 1'den 8'e şerit var diyelim 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())

Bu değişiklikle şu etkinliğe sahip olabiliriz:

  • Başlık: FINA 2019, 50m sırtüstü erkek finali,

    • mekan: Nambu Üniversitesi Belediye Su Sporları Merkezi
    • zaman: 28. Temmuz 2019, 20:02 UTC + 9
    • katılımcılar:

      • isim: Michael Andrew, şehir: Edina, ABD, rol: şerit 1
      • isim: Zane Waddell, şehir: Bloemfontein, Güney Afrika, rol: şerit 2
      • isim: Evgeny Rylov, şehir: Novotroitsk, Rusya, rol: şerit 3
      • isim: Kliment Kolesnikov, şehir: Moskova, Rusya, rol: şerit 4

      // ve böylece şerit 5'ten şerit 8'e (kaynak: Wikipedia

Bir yüzücü ısıda sadece bir kez görünebilir ve bir şerit ısıda sadece bir kez işgal edilebilir.

Kodu GitHub'a koydum: https://github.com/cezar77/competition .

Yine, tüm krediler dani herrera'ya gidiyor. Umarım bu cevap okuyuculara bir miktar katma değer sağlar.

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.