这是我的模特:
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
,但是我想知道django是否内置了某些东西。
我将2.2.5版与PSQL一起使用。
编辑:这不是在一起的情况。如果我使用other_model_one_id=1
和其他other_model_two_id=2
添加一条记录,则我应该不能使用other_model_one_id=2
和其他other_model_two_id=1
答案 0 :(得分: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团队只能与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
这看起来像一个symetrical问题,django可以为您解决。无需创建GroupedModels
模型,只需在OtherModel
上创建一个ManyToManyField字段即可:
from django.db import models
class OtherModel(models.Model):
...
grouped_models = models.ManyToManyField("self")
这是django在这些情况下内置的内容。
答案 1 :(得分:1)
这不是一个很令人满意的答案,但是不幸的是,事实是没有办法通过简单的内置功能来完成您要描述的事情。
您用clean
描述的内容可以使用,但是您必须小心手动调用它,因为我认为只有在使用ModelForm时才会自动调用它。您也许可以create a complex database constraint,但是它将生活在Django之外,并且您必须处理数据库异常(在事务中间,这在Django中可能很困难)。
也许有更好的数据结构方法?
答案 2 :(得分:0)
answer 已经有不错的dani herrera,但我希望对此做进一步的阐述。
如第二个选项所述,OP所需的解决方案是更改设计并成对实现两个唯一约束。篮球比赛的类比非常实用地说明了这个问题。
我用篮球(或足球)游戏代替篮球比赛。足球比赛(我称为Event
)由两支球队进行(在我的模型中,一支球队为Competitor
)。这是一个多对多关系(m:n
),在此特定情况下,n
限于两个,该原理适用于无限数。
这是我们的模型的外观:
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
一个事件可能是:
现在我们必须从问题中解决问题。 Django自动在具有多对多关系的模型之间创建中间表,但是我们可以使用自定义模型并添加更多字段。我称该模型为Participant
:
class Participant(models.Model): ROLES = ( ('H', 'Home'), ('V', 'Visitor'), ) 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())
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
现在,唯一性约束将自动将每个事件的参赛者数量限制为两个(因为只有两个角色:首页和访问者)。
在特定事件中(足球比赛),只能有一个主队和一个访客队。俱乐部(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,5,000万仰泳男子决赛,
参与者:
//,以此类推,从第5泳道到第8泳道(来源:Wikipedia
游泳者只能在一次加热中出现一次,泳道只能在一次加热中被占用一次。
我将代码放到GitHub:https://github.com/cezar77/competition。
同样,所有学分归于dani herrera。我希望这个答案可以为读者带来更多的价值。