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

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


어떤 장고 버전을 사용하고 있습니까?
Willem Van Onsem

저는 버전 2.2.5를 사용하고 있습니다
Pittfall


1
이것은 독창적 인 공동 상황이 아니며, 독특하지만 그것이 의미가있는 경우 2 개 이상의 필드입니다.
Pittfall

답변:


10

여기에 몇 가지 옵션이 설명되어 있습니다. 어쩌면 그중 하나 또는 조합이 유용 할 수 있습니다.

재정의 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")

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


하나는 내가 사용하고있는 것입니다 (그러나 데이터베이스 제약 조건을 원합니다). 접근법 2는 제 시나리오에서 팀이 게임을 한 경우 다시는 게임을 할 수 없다는 점에서 약간 다릅니다. 그룹화에 저장하려는 데이터가 더 많았 기 때문에 접근법 3을 사용하지 않았습니다. 답변 해주셔서 감사합니다.
Pittfall

팀이 게임을 한 경우 다시 게임을 할 수 없습니다. 왜냐하면 나는 match_id팀이 무제한 경기를 할 수 있도록 무제한 제약에 포함했기 때문 입니다. 재생을 다시 제한하려면이 필드를 제거하십시오.
dani herrera

어 그래! 고맙습니다. 다른 모델은 일대일 필드 일 수 있습니다.
Pittfall

1
옵션 번호 2가 가장 좋다고 생각합니다. 내가 가진 유일한 문제는 관리자가 FE로 사용되는 세계에서 "평균"사용자에 대한 사용자 정의 양식이 필요하다는 것입니다. 불행히도, 나는 그 세상에 살고 있습니다. 그러나 이것이 받아 들일만한 대답이어야한다고 생각합니다. 감사!
Pittfall 19

두 번째 옵션은 갈 길입니다. 이것은 좋은 대답입니다. 관리자에 관한 @Pitfall 추가 답변을 추가했습니다. 관리 양식은 큰 문제를 해결해서는 안됩니다.
cezar

1

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

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

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


그렇습니다. 수동으로 호출해야한다는 것이 정확하므로 접근 방식이 마음에 들지 않습니다. 언급 한 것처럼 관리자가 원하는 대로만 작동합니다.
Pittfall

0

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에게 전달됩니다. 이 답변이 독자들에게 부가 가치를 제공하기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.