有没有办法在两个字段上创建一个唯一的id?

2024-10-03 04:33:40 发布

您现在位置:Python中文网/ 问答频道 /正文

这是我的模型:

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_twoid作为123创建另一个记录。我可以重写clean我想,但我想知道django是否有内置的功能。在

我使用的是PSQL版本2.2.5。在

编辑:这不是一个不齐聚的情况。如果我添加一个带有other_model_one_id=1和其他other_model_two_id=2的记录,我将不能添加另一个带有other_model_one_id=2和其他{}的记录


Tags: 模型idappmodelmodels记录oneclass
3条回答

已经有一个来自dani herrera的伟大的answer,但是我想进一步详细说明它。在

如第二个选项所述,OP所需的解决方案是更改设计并成对实现两个唯一约束。与篮球比赛的类比很实际地说明了这个问题。在

我用足球(或足球)比赛作为例子,而不是篮球比赛。足球比赛(我称之为Event)由两个团队进行(在我的模型中,一个团队是Competitor)。这是一个多对多关系(m: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

事件可以是:

  • 标题:卡拉宝杯,第四轮
  • 地点:安菲尔德
  • 时间:30。2019年10月19:30 GMT
  • 参加人员:
    • 名称:利物浦,城市:利物浦
    • 名称:阿森纳,城市:伦敦

现在我们要从问题上解决问题。Django自动在具有多对多关系的模型之间创建一个中间表,但是我们可以使用自定义模型并添加更多字段。我把这个模型称为Participant

^{pr2}$

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时,我们可以选择主团队和访问者团队。因此,{cd2>每一个事件可以添加更多的条目。在

这可以针对不同的用例进行重构。假设我们的项目是游泳比赛,而不是主场和游客,我们有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())

通过此修改,我们可以进行以下活动:

  • 标题:国际泳联2019,50米仰泳男子决赛

    • 地点:南部大学水上运动中心
    • 时间:28。2019年7月,20:02 UTC+9
    • 参加人员:

      • 姓名:Michael Andrew,城市:美国爱丁堡,角色:lane 1
      • 姓名:Zane Waddell,城市:南非布隆方丹,角色:lane 2
      • 姓名:叶夫根尼·里洛夫,城市:俄罗斯诺沃特洛伊茨克,角色:3号车道
      • 姓名:Kliment Kolesnikov,城市:俄罗斯莫斯科,角色:车道4

      //以此类推,从第5道到第8道(来源:Wikipedia

一个游泳者在炎热的天气里只能出现一次,而一条泳道在炎热的天气里只能被占用一次。在

我把代码放到GitHub:https://github.com/cezar77/competition。在

再说一遍,所有的学分都归丹尼·赫雷拉所有。我希望这个答案能为读者提供一些附加值。在

我在这里解释几个选项,也许其中一个或一个组合对你有用。在

重写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)

变更设计

我放了一个简单易懂的样品。假设这个场景:

^{pr2}$

现在,你想避免一个队和自己比赛,而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为这些场景内置的。在

这不是一个非常令人满意的答案,但不幸的是,没有办法用一个简单的内置功能来实现您所描述的功能。在

您用clean描述的内容可以工作,但是您必须小心地手动调用它,因为我认为只有在使用ModelForm时才会自动调用它。您可能能够create a complex database constraint,但这将存在于Django之外,并且您必须处理数据库异常(这在Django中很难处理)。在

也许有更好的方法来组织数据?在

相关问题 更多 >