在Django中建模复杂关系

3 投票
3 回答
3478 浏览
提问于 2025-04-15 12:31

我正在用Django开发一个网络服务,需要处理一种非常复杂的关系,但我一直找不到合适的解决办法。

想象一下有三个基本模型,我们叫它们网站(Site)、分类(Category)和项目(Item)。每个网站可以包含一个或多个分类,但它们之间的关系有两种可能的方式:一种是“公共”分类,这种分类是多对多的关系,也就是说它们是预先定义好的,每个网站可以关联零个或多个分类,反之亦然。另一种分类是为每个网站单独定义的,这种分类只属于那个网站,不属于其他网站;也就是说,它们是多对一的关系,因为每个网站可以有多个这样的分类。

在内部,这两种分类完全相同,唯一的区别在于它们与网站的关系。不过,我可以把它们分成两个不同的模型(可能有一个共同的父模型),但这只能解决我问题的一半:项目模型与分类是多对一的关系,也就是说每个项目只属于一个分类,理想情况下,它不应该关心它与网站的关系。

另一种解决方案是让这两种不同类型的网站-分类关系共存(也就是说,在同一个分类模型上同时有外键和多对多字段),但这个方案感觉像是打开了一个新的麻烦盒子。

有没有人知道有没有第三种更好的解决办法来应对这个死胡同?

3 个回答

0

注意:我了解对象关系映射、Rails和Python,但对Django不太熟悉。

我看到还有两个额外的选项:

  1. 从数据库的角度来看,我可以让处理多对多关系的表多加一个字段,这个字段用来表示是“通用”关系还是“站点”关系,并且添加一些限制来限制“站点”关系的类型。我觉得在Django中可以做到这一点,具体可以参考“多对多关系中的额外字段”这部分。

如果你使用的是早期版本的Django,你仍然可以通过将多对多表做成一个明确的模型来实现这一点。

  1. 从对象的角度来看,我可以考虑把类别分成三个类:

    BaseCategory(基础类别)

    CommonCategory(通用类别,继承自BaseCategory)

    SiteCategory(站点类别,继承自BaseCategory)

然后可以使用Django的继承模型之一。

1

作为一种替代方案,你可以使用Django的内容类型(通用关系)来连接这些项目。使用这种方法的一个好处是,它可以让你根据将来的数据需求,以不同的方式使用分类模型。

你可以通过编写模型方法来简化使用网站分类的过程,这样就能更方便地提取和排序分类。Django的管理后台也支持通用关系的嵌套使用。

你的模型可以这样设计:

Site(models.Model):
  label = models.CharField(max_length=255)

Category(models.Model):
  site = models.ManyToManyField(Site)
  label = models.CharField(max_length=255)

SiteCategory(models.Model):
  site = models.ForeignKey(Site)
  label = models.CharField(max_length=255)

Item(models.Model):
  label = models.CharField(max_length=255)
  content_type = models.ForeignKey(ContentType)
  object_id = models.PositiveIntegerField()
  content_object = generic.GenericForeignKey('content_type', 'object_id')

如果想更深入了解内容类型以及如何查询通用关系,可以查看这里:http://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/

4

为什么不把两种类型的分类放在一个模型里,这样你只需要三个模型就行了呢?

Site

Category
  Sites = models.ManyToManyField(Site)
  IsCommon =   models.BooleanField()

Item
  Category = models.ForeignKey(Category)

你说“这两种分类在内部是完全相同的”。听起来这样是可行的。请注意,一个多对多字段(ManyToManyField)只包含一个值也是完全可以的,所以你不需要在同一个分类模型里同时使用“外键(ForeignKey)和多对多字段”,这听起来会很麻烦。只需在多对多字段里放一个值就可以了。

撰写回答