2つのフィールドに一意のIDを作成する方法はありますか?


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として、ID 123。オーバーライドはできるcleanと思いますが、djangoに何かが組み込まれているのではないかと思っていました。

PSQLでバージョン2.2.5を使用しています。

編集:これは、一緒に不平等な状況ではありません。私はレコードを追加した場合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を使用しています
ピットフォール


1
これはユニークな一緒の状況ではありません。これはユニークですが、それが理にかなっている場合、2つのフィールドを超えています。
ピットフォール

回答:


10

ここでいくつかのオプションについて説明します。おそらくそのうちの1つまたは組み合わせが役立つでしょう。

オーバーライド 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との対戦を避けたいとします。また、チーム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

これは対称的な問題のように見えます、djangoはそれを処理できます。GroupedModelsモデルを作成する代わりに、それ自体をonにしたManyToManyFieldフィールドを作成しますOtherModel

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

これは、djangoがこれらのシナリオに組み込まれているものです。


アプローチ1は、私が使用していたアプローチです(ただし、データベースの制約を期待しています)。アプローチ2は少し異なります。私のシナリオでは、チームがゲームをプレイした場合、彼らは再びゲームをプレイすることはできません。グルーピングに保存したいデータが多かったため、アプローチ3は使用しませんでした。答えてくれてありがとう。
ピットフォール、

チームがゲームをプレイした場合、彼らは再びゲームをプレイすることはできません。これはmatch_id、チームが無制限の試合をプレイできるようにするために、私がunike制約に含めたためです。再生を制限するには、このフィールドを削除してください。
ダニ・エレーラ

ああそう!私はそれを逃したことに感謝し、私の他のモデルは1対1のフィールドになる可能性があります。
Pittfall

1
私はオプション番号2が一番好きだと思います。私が持っている唯一の問題は、管理者がFEとして使用されている世界では、間違いなく「平均的な」ユーザー用のカスタムフォームが必要なことです。残念ながら、私はその世界に住んでいます。しかし、私はこれが受け入れられる答えであるべきだと思います。ありがとう!
ピットフォール

2番目のオプションは、進むべき道です。これは素晴らしい答えです。管理者に関するピットフォール私はさらに答えを追加しました。管理者フォームは解決するべき大きな問題ではないはずです。
セザール

1

これはあまり満足のいく答えではありませんが、残念なことに、単純な組み込み機能で説明していることを行う方法はありません。

あなたが説明したものcleanは機能しますが、ModelFormを使用するときに自動的に呼び出されるだけだと思う​​ので、手動で呼び出すように注意する必要があります。複雑なデータベース制約作成できる可能性がありますが、それはDjangoの外部に存在し、データベースの例外を処理する必要があります(トランザクションの途中でDjangoでこれを行うのは難しい場合があります)。

たぶん、データを構造化するより良い方法がありますか?


はい、あなたはそれが手動で呼び出されなければならないことは正しいです、それが私がアプローチを好きでなかった理由です。あなたが言ったように、それは管理者で私が望むようにのみ機能します。
ピットフォール

0

ダニ・エレーラからはすでに素晴らしい答えがありますが、さらに詳しく説明したいと思います。

2番目のオプションで説明したように、OPで必要なソリューションは、デザインを変更し、2つの一意の制約をペアで実装することです。バスケットボールの試合との類似は、非常に実用的な方法で問題を示しています。

バスケットボールの試合の代わりに、フットボール(またはサッカー)ゲームの例を使用します。フットボールの試合(私はそれと呼んでいますEvent)は2つのチーム(私のモデルではチームはCompetitor)によって行われます。これは多対多の関係(m:n)でありn、この特定のケースでは2つに制限されているため、原則は無制限の数に適しています。

モデルは次のようになります。

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ラウンド、
  • 会場:アンフィールド
  • 時間:2019年10月30日、19:30 GMT
  • 参加者:
    • 名前:リバプール、都市:リバプール
    • 名前:アーセナル、都市:ロンドン

次に、問題から問題を解決する必要があります。Djangoは多対多の関係を持つモデル間に中間テーブルを自動的に作成しますが、カスタムモデルを使用してフィールドを追加することもできます。私はそのモデルを呼び出しますParticipant

クラスParticipant(models.Model):
    ロール=(
        (「H」、「ホーム」)、
        (「V」、「訪問者」)、
    )
    event = models.ForeignKey(Event、on_delete = models.CASCADE)
    competitor = models.ForeignKey(Competitor、on_delete = models.CASCADE)
    role = models.CharField(max_length = 1、choices = ROLES)

    クラスMeta:
        unique_together =(
            ( 'イベント'、 'ロール')、
            (「イベント」、「競合他社」)、
        )

    def __str __(self):
        '{}-{}'を返します。format(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

一意の制約により、イベントごとの競合者の数は自動的に2つに制限されます(ホーム訪問者の 2つの役割しかないため)。

特定のイベント(サッカーゲーム)では、ホームチームとビジターチームはそれぞれ1つだけです。クラブ(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つまでです。

これは、さまざまなユースケースに合わせてリファクタリングできます。私たちのイベントは、水泳競技であり、代わりに自宅と訪問者の、私達は私達はちょうどリファクタリング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、50m背泳ぎ決勝、

    • 会場:南部大学市民水泳センター
    • 時間:28 7月2019、20:02 UTC + 9
    • 参加者:

      • 名前:マイケルアンドリュー、都市:米国、エディーナ、役割:レーン1
      • 名前:Zane Waddell、都市:ブルームフォンテーン、南アフリカ、役割:レーン2
      • 名前:Evgeny Rylov、都市:Novotroitsk、ロシア、役割:レーン3
      • 名前:Kliment Kolesnikov、都市:モスクワ、ロシア、役割:レーン4

      //レーン5からレーン8まで(ソース:ウィキペディア)

水泳選手はヒートで1回しか出現できず、レーンはヒートで1回しか占有できません。

コードをGitHub:https : //github.com/cezar77/competitionに配置します

繰り返しになりますが、クレジットはすべてdani herreraに送られます。この回答が読者にいくつかの付加価値を提供することを願っています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.