내 모델은 다음과 같습니다.
class GroupedModels(models.Model):
other_model_one = models.ForeignKey('app.other_model')
other_model_two = models.ForeignKey('app.other_model')
본질적으로, 내가 원하는 것은 other_model
이 테이블에서 고유 한 것입니다. 어디에 기록이 있으면 것을 의미 other_model_one
ID는 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에게 전달됩니다. 이 답변이 독자들에게 부가 가치를 제공하기를 바랍니다.