使用两个模型而不是一个模型有什么好处?

What are the benefits of having two models instead of one?

我有一个 Django 模型

class Person(models.Model):
    name = models.CharField(max_length=50)
    team = models.ForeignKey(Team)

还有团队模型

class Team(models.Model):
    name = models.CharField(max_length=50)

然后,我想添加一个'coach' 属性,这是一对一的关系。如果我没记错的话,我有两种方法。

第一种方法是将字段添加到团队:

class Team(models.Model):
    name = models.CharField(max_length=50)
    coach = models.OneToOneField(Person, related_name='master')

第二个是创建一个新模型:

class TeamCoach(models.Model):
    team = models.OneToOneField(Team)
    coach = models.OneToOneField(Person)

这样对吗?实际用途有很大区别吗?每种方法的优缺点是什么?

我会说 NEITHER,因为每个 Person 都有一个 Team,如果每个 团队有一个教练,这是相当多余的循环,有点不必要。

最好直接在 Person 中添加一个名为 type 的字段,这样更干净直接,例如:

class Person(models.Model):
    # use _ if you care about i18n
    TYPES = ('member', 'member',
             'coach', 'coach',)

    name = models.CharField(max_length=50)
    team = models.ForeignKey(Team)
    type = models.CharField(max_length=20, choices=TYPES)

尽管我会认真考虑重构 Person 以使其更通用,并让 Team 拥有 ManyToManyPerson... 在这种情况下,您可以在其他领域重复使用 Person,例如 Cheerleaders。

class Person(models.Model):
    # use _ if you care about i18n
    TYPES = ('member', 'member',
             'coach', 'coach',)

    name = models.CharField(max_length=50)
    type = models.CharField(max_length=20, choices=TYPES)

class Team(models.Model):
    name = models.CharField(max_length=50)
    member = models.ManyToManyField(Person, related_name='master')

让你的模型更加通用和 DRY,它们应该易于管理并且不与某些领域紧密耦合(除非绝对必要),然后模型更适合未来并且不会那么容易陷入迁移噩梦。

希望对您有所帮助。

我不能轻易同意 @Anzel,而且问题的名称是

What are the benefits of having two models instead of one?

我会尽力付出我的两分钱。但在开始之前,我想引用 docs.

中的一些内容

It doesn’t matter which model has the ManyToManyField, but you should only put it in one of the models – not both.

Generally, ManyToManyField instances should go in the object that’s going to be edited on a form. In the above example, toppings is in Pizza (rather than Topping having a pizzas ManyToManyField ) because it’s more natural to think about a pizza having toppings than a topping being on multiple pizzas. The way it’s set up above, the Pizza form would let users select the toppings.

基本上,这是您在创建 M2M 关系时首先要考虑的事情 (您的 TeamCoach 模型就是这样,稍后会详细介绍) 持有关系的对象是哪一个。哪个更适合您的问题——在创建团队时为团队选择教练,还是在创建团队时为个人选择团队?如果你问我,我更喜欢第二种变体并将 teams 保留在 Person class 内。

现在让我们转到文档的下一部分

多对多关系的额外字段

When you’re only dealing with simple many-to-many relationships such as mixing and matching pizzas and toppings, a standard ManyToManyField is all you need. However, sometimes you may need to associate data with the relationship between two models.

For example, consider the case of an application tracking the musical groups which musicians belong to. There is a many-to-many relationship between a person and the groups of which they are a member, so you could use a ManyToManyField to represent this relationship. However, there is a lot of detail about the membership that you might want to collect, such as the date at which the person joined the group.

For these situations, Django allows you to specify the model that will be used to govern the many-to-many relationship. You can then put extra fields on the intermediate model. The intermediate model is associated with the ManyToManyField using the through argument to point to the model that will act as an intermediary.

这实际上就是您问题的答案,拥有中间模型使您能够存储有关集合的额外数据。考虑下赛季教练转会到另一支球队的情况,如果你只是更新 M2M 关系,你将丢失他过去执教过的球队的轨迹。或者你永远无法回答 XXX 年那支球队的教练是谁的问题。因此,如果您需要更多数据,请使用中间模型。这也是@Anzel 出错的原因,type 字段是该中间模型的附加数据,它的位置必须在其中。

现在我可能会这样创建关系:

class Person(models.Model):
    name = models.CharField(max_length=50)
    teams = models.ManyToManyField('Team', through='TeamRole')


class Team(models.Model):
    name = models.CharField(max_length=50)


class TeamRole(models.Model):
    COACH = 1
    PLAYER = 2
    CHEERLEADER = 3
    ROLES = (
        (COACH, 'Coach'),
        (PLAYER, 'Player'),
        (CHEERLEADER, 'Cheerleader'),
    )
    team = models.ForeignKey(Team)
    person = models.ForeignKey(Person)
    role = models.IntegerField(choices=ROLES)
    date_joined = models.DateField()
    date_left = models.DateField(blank=True, null=True, default=None)

我将如何查询?嗯,我可以用role得到我要找什么类型的persons,我也可以用date_left字段得到当前persons参与那个团队现在。以下是一些示例方法:

class Person(models.Model):
    #...

    def get_current_team(self):
        return self.teams.filter(teamrole__date_left__isnull=True).get()


class Team(models.Model):
    #...

    def _get_persons_by_role(self, role, only_active):
        persons = self.person_set.filter(teamrole__role=role)
        if only_active:
            return persons.filter(teamrole__date_left__isnull=True)
        return persons

    def get_coaches(self, only_active=True):
        return self._get_persons_by_role(TeamRole.COACH, only_active)

    def get_players(self, only_active=True):
        return self._get_persons_by_role(TeamRole.PLAYER, only_active)