我可以在隐式的多对多关系中定义字段吗?

时间:2016-01-02 17:29:24

标签: django django-models many-to-many

这是我的models.py文件的一个版本。我出于安全原因删除了不相关的字段并编制了模型名称:

class FilmStudio(models.Model):
    name = models.CharField(max_length=200, unique=True)


class ProductionCompany(models.Model):
    name = models.CharField(max_length=200)
    film_studio = models.ForeignKey(FilmStudio)

    class Meta:
        # Ensure that a given combination of ProductionCompany name and FilmStudio object is unique
        unique_together = ('name', 'film_studio')


class Film(models.Model):
    title = models.CharField(max_length=200)
    production_company = models.ForeignKey(ProductionCompany)


class Actor(models.Model):
    name = models.CharField(max_length=200)
    films = models.ManyToManyField(Film, blank=True)

虽然没有明确定义,但ActorFilmStudio之间存在多对多关系。以下对Python API的调用证明了这一点:

FilmStudio.objects.filter(productioncompany__film__actor__name='Samuel L. Jackson').distinct()

这将返回Samuel L. Jackson所关联的所有FilmStudio个对象,每个对象只返回一次。我想要的是在ActorFilmStudio之间的关系上定义额外的字段(在这个例子中它没有用,我知道,但它有意义对于我的情况)。

按照Extra fields on many-to-many relationships中的描述,我可以使用中间模型来定义FilmActor之间关系的额外字段。

但这似乎并没有帮助我解决问题。我不想明确定义ActorFilmStudio关系,因为它是基于其他关系的现有关系。

是否可以在我描述的关系中定义字段?

2 个答案:

答案 0 :(得分:1)

据我所知,你无法做到这一点。

原因在于无法存储该关系的额外字段。如果我理解正确,这些"额外字段"并不是隐含在演员电影或制作电影的关系中,所以即使你说它们是隐含的,额外的字段本身也是明确的。

您可以尝试通过在需要时创建显式直接关系来模拟它。然后你可以使用模型作为抽象来模拟额外的字段,但我不确定这是否是你想要的。如果您选择这种解决方案,您可以使用默认值(在您的抽象中)填写尚未拥有实例的关系。

这种解释对你有意义吗?

编辑: (我没有仔细检查代码的工作原理,所以要有所不同) 好的,所以你有原始模型:

class FilmStudio(models.Model):
    name = models.CharField(max_length=200, unique=True)


class ProductionCompany(models.Model):
    name = models.CharField(max_length=200)
    film_studio = models.ForeignKey(FilmStudio)

    class Meta:
        # Ensure that a given combination of ProductionCompany name and FilmStudio object is unique
        unique_together = ('name', 'film_studio')


class Film(models.Model):
    title = models.CharField(max_length=200)
    production_company = models.ForeignKey(ProductionCompany)


class Actor(models.Model):
    name = models.CharField(max_length=200)
    films = models.ManyToManyField(Film, blank=True)

# The "solution" would be:

class ActorProductionComapny(models.Model):
    production_company = models.ForeignKey(ProductionCompany, related_name='actors')
    actor = models.ForeignKey(Actor, related_name='companies')
    # your extra fields here
    someproperty = models.CharField(max_length=200)

    class Meta:
        # let's say one per actor
        unique_together = ('production_company', 'actor')

这会很快变得混乱

我们使用这样的F object

FilmStudio.objects.filter(productioncompany__film__actor__name='Samuel L. Jackson', 
                          productioncompany__film__actor=F('actors__actor'),
                          actors__someproperty="Plays poker with CEO").distinct()

棘手的部分是处理默认值(即没有值时)这必须使用custom Manager来实现,但是我不在我的深度。

我会尽力解释,但它会变得棘手。

如果您想对关系进行过滤,则可能需要执行以下操作:

def filter_prod(pq_query, someproperty, actor_name):
if someproperty == "Default":
    # Great, this means we can ignore the parallel relationship:
    return pq_query.filter(productioncompany__film__actor__name=actor_name)
else:
    # Here comes the hard part
    FilmStudio.objects.filter(productioncompany__film__actor__name=actor_name, 
                              productioncompany__film__actor=F('actors__actor'),
                              actors__someproperty=someproperty).distinct()

我试图在这里说明的是,有两种actor-productioncompany关系,那些具有自定义字段值(非默认值)和没有自定义字段值的那些。

现在,您可以创建一个看起来像这样的自定义getter:

class ProductionCompany(models.Model):
    name = models.CharField(max_length=200)
    film_studio = models.ForeignKey(FilmStudio)

    def get_actors(self):
        # This one is not lazy, so be aware
        actors = list(self.actors)
        # Get a list of actor IDs
        actor_ids = [a.actor_id for a in actors]
        for actor in Actor.objects.filter(films__production_company_id=self.id):
            if actor.id not in actor_ids:
                actors.append(ActorProductionComapny(actor=actor, production_company=self)
                actor_ids.append(actor.id)
        return actors

    class Meta:
        # Ensure that a given combination of ProductionCompany name and FilmStudio object is unique
        unique_together = ('name', 'film_studio')

在您在实例上调用.save()之前,不应该保存与数据库的关系。您还可以添加一个自定义保存方法,忽略/ aports .save()调用,其中所有值都是默认值。只需记住检查它是否是新实例,因为您不希望它取消"设置回默认值"呼叫。您也可以在"设置回默认值"上删除它,但检查您是否被允许在.save()内执行此操作。

对于更复杂的查询(默认和非默认混合),您有Q对象(在F对象的页面上更下方)

答案 1 :(得分:1)

简而言之,您需要创建一个额外的模型,以便在ActorFilmStudio之间存储这些额外的关系数据。

class Actor(models.Model):
     name = models.CharField(max_length=200)
     films = models.ManyToManyField(Film, blank=True)
     film_studios = models.ManyToMany(FilmStudio, through='ActorFilmStudio')

class ActorFilmStudio(models.Model):
    actor = models.ForeignKey(Actor)
    film_studio = models.ForeignKey(FilmStudio)

    # define extra data fields here
    data1 = models.TextField()
    data2 = models.IntegerField()

考虑这一点的一种方法:您尝试存储的数据属于Actor-FilmStudio关系,并且与FilmProductionCompany无关。

您现有的为给定Actors检索一组FilmStudio的能力(反之亦然)并不一定意味着您可以使用以下定义的模型存储属于这两个模型的关系数据你的榜样。

请记住,您在示例中定义的每个模型都由数据库中的表支持。在Actor.films字段的情况下,Django创建一个额外的表来存储多对多关系数据。

由于您希望在Actor和FilmStudio之间存储关系数据,因此需要考虑数据在数据库中的存储位置。您可以将数据存储在Film模型中吗?还是ProductionCompany模型?