Django通用关系建模

时间:2017-02-28 17:12:17

标签: django django-models

我试图在Django中建模,但它看起来并不合适。 BookMovie可以包含一个或多个Person个,其中每个人都有Role(例如"作者"或& #34;导演")关于BookMovie

我用这样的Generic relations做这件事:

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=255)

class Role(models.Model):
    person = models.ForeignKey('Person')
    role_name = models.CharField(max_length=255)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

class Book(models.Model):
    title = models.CharField(max_length=255)
    roles = GenericRelation('Role', related_query_name='books')

class Movie(models.Model):
    title = models.CharField(max_length=255)
    roles = GenericRelation('Role', related_query_name='movies')

这似乎有效,但是当获得Book Person所有person = Person.objects.get(pk=1) for role in person.role_set.all(): print(role.role_name) for book in role.books.all(): print(book.title) 时,它似乎很奇怪:

for book in role.books.all()

vagrant up感觉不对 - 就像角色可以有多本书一样。我想我希望在一个特定书籍/电影上工作的人之间有更直接的关系。

有更好的方法对此进行建模吗?

2 个答案:

答案 0 :(得分:1)

如果是我,我会通过角色模型使用多对多的关系来尝试如下方法。 (见https://docs.djangoproject.com/en/1.10/topics/db/models/#s-extra-fields-on-many-to-many-relationships

正如你在评论中提到的那样,麻烦来了,因为你有几个需要这种关系的模型(书和电影)。解决方案是使用BaseRole模型并为电影角色和书籍角色创建单独的模型。像这样:

from django.db import models


class Person(models.Model):
    name = models.CharField(max_length=255)


class BaseRole(models.Model):
    role_name = models.CharField(max_length=255)
    person = models.ForeignKey('Person', on_delete=models.CASCADE)

    class Meta:
        abstract = True


class MovieRole(BaseRole):
    movie = models.ForeignKey('Movie', on_delete=models.CASCADE)


class BookRole(BaseRole):
    book = models.ForeignKey('Book', on_delete=models.CASCADE)


class Book(models.Model):
    title = models.CharField(max_length=255)
    roles = models.ManyToManyField(Person, through='BookRole')


class Movie(models.Model):
    title = models.CharField(max_length=255)
    roles = models.ManyToManyField(Person, through='MovieRole')

这样,当您想要过滤所有人工作的书籍时,您可以按照以下方式进行:

person = Person.objects.get(pk=1)
books = Book.objects.filter(roles=person).distinct()

distinct()调用是必要的,因为一个人可以在书籍/电影上有多个角色(例如,他们可能是制片人和导演),所以如果没有它,过滤器将返回一个实例该书中每个人在电影/书中扮演的角色。

更新

在回复您的评论时,或许更好的解决方案是使用Django Polymorphic(https://django-polymorphic.readthedocs.io/en/stable/quickstart.html)。

所以你要设置你的模型:

from django.db import models

from polymorphic.models import PolymorphicModel


class Person(models.Model):
    name = models.CharField(max_length=255)


class Role(models.Model):
    role_name = models.CharField(max_length=255)
    person = models.ForeignKey('Person', on_delete=models.CASCADE)
    media = models.ForeignKey('Media', on_delete=models.CASCADE)


class Media(PolymorphicModel):
    title = models.CharField(max_length=255)
    roles = models.ManyToManyField(Person, through='Role')


class Book(Media):
    pass

class Movie(Media):
    pass

通过这种方式,你仍然可以获得一个人所有的书籍:

person = Person.objects.get(pk=1)
books = Book.objects.filter(roles=person).distinct()

但你也可以通过以下方式获得所有媒体:

media = person.media_set.all()

是的,你是对的,拥有content_type和object_id毫无意义,所以我删除了这些字段。

答案 1 :(得分:1)

tl; dr

在这种情况下,Django的通用关系可能不是最佳选择。 相反,看起来某种形式的继承更合适。

为什么没有通用关系?

Django使用关系数据库。关系数据库的功能,效率和可靠性在很大程度上取决于以下事实:表之间的关系由 designer (或开发人员,或其他人员)固定:只需查看模型定义,您知道表格之间的关系。 用户不能影响这一点。

另一方面,GenericForeignKey通过允许用户任何建立新的关系,从而打破了这个基本概念。 >数据库中的其他表。这些关系仅存在于数据中,而不存在于 schema 中。 设计器无法知道在任何时间点将存在哪些关系,模型定义也无法告诉我们表之间的关系。为此,我们需要查看数据库中的实际数据。不用说,这个clever trick使事情变得非常复杂,并且使关系完整性很难维护,即使不是不可能的。

仍然,在某些用例中,GenericForeignKey的灵活性正是所需要的。 Django自己的TaggedItem示例很好地说明了这一点。

但是,据我所知,OP的情况不是 其中之一。

那又是什么?

answer by @tdsymonds指向正确的方向,即使用某种来自Media类的继承形式。但是,我相信使用django-polymorphic会使事情复杂化,因为有several ways使用纯Django实现此目的。

尽管可能不是最有效的查询方法,但最简单的方法可能是使用Django的multi-table inheritance(又称class-table inheritance)。

在OP的示例中,它变为:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=255)
    roles = models.ManyToManyField(to='Media', through='Role')

class Role(models.Model):
    person = models.ForeignKey('Person', on_delete=models.CASCADE)
    media = models.ForeignKey('Media', on_delete=models.CASCADE)
    role_name = models.CharField(max_length=255)

class Media(models.Model):
    title = models.CharField(max_length=255)

class Book(Media):
    pass

class Movie(Media):
    pass

请注意,Role直接与ForeignKeyMedia,可以使用Media对象,Book对象,{{ 1}}对象,或您决定添加的任何其他子类。子类可以具有其他属性。我们可以通过Movie(基本的many-to-many relation)轻松获得Person所扮演的所有角色的列表,而不管Media的类型如何。

要查看管理员中的角色,请添加内联,如docs中所述:

person.roles.all()