Django通用关系建模

我正在尝试在Django中建模,但这似乎并不合适.书和电影可以包含一个或多个人,每个人在该书或电影上都有一个角色(如“作者”或“导演”).

我正在用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')

这似乎有用,但在获得一个人工作的所有书籍时似乎很奇怪:

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)

role.books.all()中的for book感觉不对 – 就像角色可以拥有多本书一样.我想我希望在一个特定的书/电影上工作的人之间有更直接的关系.

有没有更好的方法来模拟这个?

最佳答案 如果是我,我会通过角色模型尝试使用多对多关系的方法. (见
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()调用是必要的,因为一个人可以在一本书/电影上拥有多个角色(例如,他们可以是制片人和导演),所以没有它,过滤器会为每个人返回一本书的实例.该人在电影/书中的角色.

UPDATE

在回答您的评论时,或许更好的解决方案是使用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是没有意义的,所以我删除了这些字段.

点赞