有没有一种方法可以在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_twoID为as的另一条记录123。我可以覆盖clean我的猜测,但是我想知道django是否内置了某些东西。

我在PSQL中使用版本2.2.5。

编辑:这不是一个不合时宜的情况。如果我用other_model_one_id=1和其他添加一条记录other_model_two_id=2,我应该不能用other_model_one_id=2和其他添加另一条记录。other_model_two_id=1


您正在使用哪个Django版本?
Willem Van Onsem,

我正在使用版本2.2.5
Pittfall '19


1
这不是唯一的在一起的情况,这是唯一的,但是如果有意义的话,可以超过2个字段。
Pittfall '19

Answers:


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

这看起来像一个对称问题,django可以为您解决。无需创建GroupedModels模型,只需在其上创建一个ManyToManyField字段即可OtherModel

from django.db import models
class OtherModel(models.Model):
    ...
    grouped_models = models.ManyToManyField("self")

这就是django在这些情况下内置的内容。


方法之一是我正在使用的方法(但希望有数据库约束)。方法2与我的情况稍有不同,如果团队玩过游戏,他们将永远无法再玩游戏。我没有使用方法3,因为我想在分组中存储更多数据。感谢您的回答。
Pittfall

如果团队玩过游戏,他们将永远无法再玩游戏。因为我加入match_id了unike约束,才允许球队进行无限次比赛。只需删除此字段即可再次限制播放。
丹妮·埃雷拉

是的!谢谢,我错过了这一点,而我的其他模型可能是一对一的领域。
Pittfall '19

1
我认为我最喜欢2号选项。我唯一遇到的问题是,在管理员用作FE的情况下,它可能需要为“普通”用户使用自定义表单。不幸的是,我生活在那个世界上。但是我认为这应该是公认的答案。谢谢!
Pittfall

第二种选择是要走的路。这是一个很好的答案。关于管理员的@陷阱我已经添加了进一步的答案。管理表单应该不是要解决的大问题。
cezar

1

这不是一个很令人满意的答案,但是不幸的是,事实是没有办法通过简单的内置功能来完成您要描述的事情。

您所描述的内容clean可以正常工作,但是您必须小心手动调用它,因为我认为只有在使用ModelForm时才会自动调用它。您可能可以创建一个复杂的数据库约束,但该约束将存在于Django之外,并且您必须处理数据库异常(在事务中间,这在Django中可能很困难)。

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


是的,您是正确的,必须手动调用它,这就是为什么我不喜欢这种方法的原因。正如您所提到的,它只能按我想要的管理方式工作。
Pittfall

0

已经有一个伟大的答案来自达尼·埃雷拉,但是我希望它来进一步阐述。

如第二个选项所述,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杯,第4轮,
  • 地点:安菲尔德
  • 时间:2019年10月30日,格林尼治标准时间19:30
  • 参加者:
    • 名称:利物浦,城市:利物浦
    • 名称:阿森纳,城市:伦敦

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

类参与者(models.Model):
    角色=(
        (“ H”,“首页”),
        (“ V”,“访客”),
    )
    event = models.ForeignKey(Event,on_delete = models.CASCADE)
    竞争对手= models.ForeignKey(竞争对手,on_delete = models.CASCADE)
    角色=模型。CharField(max_length = 1,choices = ROLES)

    类Meta:
        unique_together =(
            (“事件”,“角色”),
            (“事件”,“竞争对手”),
        )

    def __str __():
        返回'{}-{}'。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

现在,唯一的约束将自动将每个事件的参赛者数量限制为两个(因为只有两个角色:HomeVisitor)。

在特定事件(足球比赛)中,只能有一个主队和一个访客队。俱乐部(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)

我们在中添加了Participantas内联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())

通过此修改,我们可以发生以下事件:

  • 标题:国际泳联2019年,5,000万仰泳男子决赛,

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

      • 姓名:迈克尔·安德鲁(Michael Andrew),城市:美国埃迪纳(Edina),作用:1车道
      • 名称:Zane Waddell,城市:南非布隆方丹,作用:2号车道
      • 姓名:Evgeny Rylov,城市:俄罗斯Novotroitsk,作用:3号车道
      • 名字:Kliment Kolesnikov,城市:俄罗斯莫斯科,作用:4号车道

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

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

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

同样,所有学分归于dani herrera。我希望这个答案可以为读者提供一些附加值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.