有没有一种方法可以在2个字段上创建唯一的ID?

时间:2019-10-23 13:50:49

标签: python django django-models

这是我的模特:

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

添加另一条记录

3 个答案:

答案 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

一个事件可能是:

  • 标题:Carabao杯,第四轮,
  • 地点:安菲尔德
  • 时间:2019年10月30日,格林尼治标准时间19:30
  • 参加者:
    • 名称:利物浦,城市:利物浦
    • 名称:阿森纳,城市:伦敦

现在我们必须从问题中解决问题。 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万仰泳男子决赛,

    • 地点:南部大学市政水上运动中心
    • 时间:2019年7月28日,20:02 UTC + 9
    • 参与者:

      • 姓名:Michael Andrew,城市:美国埃迪纳,角色:1号车道
      • 姓名:Zane Waddell,城市:南非布隆方丹,角色:2号车道
      • 名称:Evgeny Rylov,城市:俄罗斯Novotroitsk,作用:3号车道
      • 名称:Kliment Kolesnikov,城市:俄罗斯莫斯科,角色:4车道

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

游泳者只能在一次加热中出现一次,泳道只能在一次加热中被占用一次。

我将代码放到GitHub:https://github.com/cezar77/competition

同样,所有学分归于dani herrera。我希望这个答案可以为读者带来更多的价值。