【问题标题】:Django generic relations modellingDjango 通用关系建模
【发布时间】:2017-02-28 17:12:16
【问题描述】:

我正在尝试在 Django 中建模某些东西,但它似乎不太正确。一个Book 和一个Movie 可以包含一个或多个Persons,每个Role(如“作者”或“导演”)在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')

这似乎可行,但是当得到所有Books 和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() 感觉不对 - 就像一个角色可以拥有多本书。我想我希望在制作特定书籍/电影的人之间建立更直接的关系。

有没有更好的建模方法?

【问题讨论】:

  • 您是否考虑过通过模型使用多对多关系?例如。你的书/电影模特会通过这个角色建立多对多的关系吗?见:docs.djangoproject.com/en/1.10/topics/db/models/…
  • 我认为问题在于将角色用于多个模型——书籍、音乐和其他东西——而不仅仅是一个。这就是我选择通用关系的原因。还是我误会了?
  • 啊...我想我可以做print(role.role_name, role.content_object.title)...content_object 是书或电影...

标签: django django-models


【解决方案1】:

tl;博士

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

为什么不是泛型关系?

Django 使用关系数据库。关系数据库的大部分功能、效率和可靠性都源于表之间的关系由设计者(或开发人员,或其他)固定的事实:只需查看模型定义,你知道这些表是如何相关的。 用户不能影响这一点。

另一方面,GenericForeignKey打破了这个基本概念,允许用户任何其他人创建新的关系数据库中的表。这些关系只存在于 data 中,而不存在于 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 有一个ForeignKey 直接到Media,可以使用Media 对象、Book 对象、Movie 对象或您决定使用的任何其他子类来设置添加。子类可以具有附加属性。无论Media 的类型如何,我们都可以通过person.roles.all()(基本many-to-many relation)轻松获得Person 扮演的所有角色的列表。

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

from django.contrib import admin
from mediaroles.models import Person, Role, Book, Movie, Media

class RoleInline(admin.TabularInline):
    model = Role
    extra = 0

class PersonAdmin(admin.ModelAdmin):
    inlines = (RoleInline,)

admin.site.register(Person, PersonAdmin)
admin.site.register([Role, Book, Movie])

【讨论】:

  • 感谢您对此的详细解释。有更多选择真是太好了。
【解决方案2】:

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

正如您在上面的 cmets 中提到的,问题来了,因为您有几个模型(书籍和电影)需要这种关系。对此的解决方案是使用 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() 调用是必要的,因为一个人可能在一本书/电影中拥有多个角色(例如,他们可能是制片人和导演),因此如果没有它,过滤器将返回book 为该人在电影/书中所扮演的每个角色。

更新

针对您的评论,也许更好的解决方案是使用 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 没有意义,所以我删除了这些字段。

【讨论】:

  • 谢谢!这种方式在很多方面都比较简单。我认为唯一的缺点是,如果不手动组合多个 QuerySet,您将无法获得该人所做的所有事情的单一列表(person.role_set.all() 使用 GenericRelations)。顺便说一句,这样做在BaseRole 中有content_typeobject_id 有什么意义吗?
  • @PhilGyford,我已经更新了答案以解决您的问题。希望这会有所帮助?
  • 再次感谢!我现在有很多选择——只需要决定我真正需要做什么:)
猜你喜欢
  • 2016-09-22
  • 1970-01-01
  • 1970-01-01
  • 2015-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多