[python] 2 개의 필드에 고유 한 ID를 만드는 방법이 있습니까?

내 모델은 다음과 같습니다.

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추측 할 수 있지만 장고에 무언가가 내장되어 있는지 궁금합니다.

PSQL에서 버전 2.2.5를 사용하고 있습니다.

편집 : 이것은 unqiue together 상황이 아닙니다. 내가 가진 레코드를 추가하는 경우 other_model_one_id=1와 다른 other_model_two_id=2, 내가 가진 또 다른 레코드를 추가 할 수 없어야 other_model_one_id=2및 기타other_model_two_id=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모델 을 작성하는 대신 ManyToManyField 필드를 자체적으로 작성하십시오 OtherModel.

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

이것이 장고가 이러한 시나리오에 내장 한 것입니다.


답변

매우 만족스러운 답변은 아니지만 불행히도 진실은 간단한 내장 기능으로 설명하는 것을 할 수있는 방법이 없다는 것입니다.

설명 한 내용 clean은 효과가 있지만 ModelForm을 사용할 때 자동으로 호출된다고 생각하기 때문에 수동으로 호출해야합니다. 복잡한 데이터베이스 제약 조건만들 수는 있지만 장고 외부에 있으며 데이터베이스 예외를 처리해야합니다 (트랜잭션 중간에 장고에서는 어려울 수 있음).

데이터를 구성하는 더 좋은 방법이 있습니까?


답변

dani herrera 로부터 이미 큰 답변 이 있지만 더 자세히 설명하고 싶습니다.

두 번째 옵션에서 설명했듯이 OP에 필요한 솔루션은 디자인을 변경하고 두 가지 고유 제약 조건을 쌍으로 구현하는 것입니다. 농구 경기와의 비유는 문제를 매우 실용적인 방식으로 보여줍니다.

농구 경기 대신 축구 (또는 축구) 게임에서 예를 사용합니다. 축구 게임 (내가 부르는 Event)은 두 팀 (내 모델에서 팀은 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

이벤트는 다음과 같습니다.

  • 제목 : Carabao Cup, 4 라운드,
  • 장소 : 안필드
  • 시간 : 2019 년 10 월 30 일 19:30 GMT
  • 참가자 :
    • 이름 : 리버풀, 도시 : 리버풀
    • 이름 : 아스날, 도시 : 런던

이제 우리는 문제에서 문제를 해결해야합니다. Django는 다 대다 관계로 모델간에 중간 테이블을 자동으로 생성하지만 사용자 정의 모델을 사용하고 추가 필드를 추가 할 수 있습니다. 나는 그 모델을 부른다 Participant:

참가자 (models.Model) 클래스 :
    역할 = (
        ( 'H', '홈'),
        ( 'V', '방문자'),
    )
    이벤트 = models.ForeignKey (이벤트, on_delete = models.CASCADE)
    경쟁사 = models.ForeignKey (경쟁사, on_delete = models.CASCADE)
    역할 = models.CharField (max_length = 1, choices = ROLES)

    메타 클래스 :
        unique_together = (
            ( '이벤트', '역할'),
            ( '이벤트', '경쟁사'),
        )

    데프 __str__ (자체) :
        return '{}-{}'. 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 개로 제한합니다 ( 방문자 라는 두 가지 역할 만 있기 때문 ).

특정 이벤트 (축구 경기)에는 홈 팀과 방문자 팀이 하나만있을 수 있습니다. 클럽 ( 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 배영 남자 결승,

    • 장소 : 남부 대학 시립 아쿠아틱 센터
    • 시간 : 2019 년 7 월 28 일 20:02 UTC + 9
    • 참가자 :

      • 이름 : Michael Andrew, 도시 : 미국 Edina, 역할 : 1 차선
      • 이름 : Zane Waddell, 도시 : Bloemfontein, 남아프리카, 역할 : 2 차선
      • 이름 : Evgeny Rylov, 도시 : 러시아 Novotroitsk, 역할 : 레인 3
      • 이름 : Kliment Kolesnikov, 도시 : 모스크바, 러시아, 역할 : 4 레인

      // 5 번 레인에서 8 번 레인까지 (출처 : Wikipedia

수영 선수는 더위에 한 번만 나타날 수 있으며 레인은 더위에 한 번만 차지할 수 있습니다.

코드를 GitHub ( https://github.com/cezar77/competition)에 넣었습니다 .

다시 모든 크레딧은 dani herrera에게 전달됩니다. 이 답변이 독자들에게 부가 가치를 제공하기를 바랍니다.


답변