Django 通用工厂

Django Generic Factory

我有两个问题困扰着我。我面临一个实现,其中一些文档与不同级别的地理数据相关,并希望工厂生成它们。让我们看一个我认为它可能如何工作的例子:

from django.contrib.gis.db import models


class Country(models.Model):
    name = models.CharField(max_length=60)


class Region(models.Model):
    country = models.ForeignKey(Country, on_delete=models.PROTECT)
    name = models.CharField(max_length=60)


class Law(models.Model):
    text = models.CharField(max_length=60)

    class Meta:
        abstract = True


class CountryLaw(Law):
    country = models.ForeignKey(Country, on_delete=models.CASCADE)


class RegionLaw(Law):
    region = models.ForeignKey(Region, on_delete=models.CASCADE)

# Not sure this work but the idea is here
class LawManager(models.Manager):
    def create_law(text,geodata):
        if isinstance(geodata, Country):
            return CountryLaw(text=text, country=geodata)
        elif isinstance(geodata, Region)
            return RegionLaw(text=text, region=geodata)
        else:
            raise TypeError("Inapropriate geodata type")

我想要一些工厂方法,因为我有一些工作来填充所有法律通用的 "Law" 的字段,但是这个例子没有显示它。 我的问题如下:

我在 google 和 Whosebug 上搜索答案,但不知道使用什么关键字,也没有找到任何可以帮助我的东西..

感谢您的帮助!

这里有几个选项。以下 "list" 并不详尽,尽管它可能提供了一些想法,并且可以根据这些想法构建变体。

让模型保持原样

在这种情况下,我们将其建模为:

+---------+ 1      N +------------+
| Country |----------| CountryLaw |
+---------+          +------------+
    | 1
    |
    | N
+---------+ 1      N +-----------+
| Region  |----------| RegionLaw |
+---------+          +-----------+

这里我们构造了两个Law。虽然我们当然可以超类化这两个 Law,但这意味着每个都有自己的类型。

的优点是如果两者有特定的语义,例如CountryLawRegionLaw的处理方式应该完全不同,那么更容易实现。此外,如果 CountryLaw 具有特定字段,例如对 RegionLaw 不重要(反之亦然),那么我们将避免在 NULL 值(或其他占位符)上浪费磁盘空间。

缺点是,例如,如果我们想查询属于 'Germany' 的法律,我们必须分两步进行:查询德国的 CountryLaw,然后查询对于 Germany 的所有区域的 RegionLaws。如果您还有 SubRegions、Citys 等

,这也很容易失控

使用 代理 地区

这里我们认为所有 Law 都是为特定的 Region 制作的,但诀窍在于我们构建了一个像整个国家一样运作的 Region。因此,除了 'Saxony''Bavaria',我们还使用一个 虚拟 区域 'Germany' 来代表整个国家。

然后我们可以引入一个 Law 模型,它附加到 Region。如果我们经常需要区分区域和国家,我们可以添加一个字段 is_country ,例如指定这是 "country proxy" 还是真实区域:

+---------+
| Country |
+---------+
    | 1
    |
    | N
+-------------------+ 1      N +-----+
|       Region      |----------| Law |
+-------------------+          +-----+
| is_country : bool |
+-------------------+

优点是我们只有一个Law对象,设计起来比较容易。此外,很容易查询映射到一个国家(包括或不包括地区)的法律。

不利的是,如果国家 Laws 和地区 Laws 明显不同,那么这将导致大量的检查(每次查看附加区域是否真的是一个地区或国家),而且它会导致很多 unused 字段。此外,如果我们启用越来越多的层,我们需要引入越来越多的代理对象。例如,如果我们将使用三层(CountryRegionSubRegion),那么我们需要为每个国家构建一个 "proxy" 区域(其中包含一个 proxy 子区域),对于每个区域,一个 proxy 子区域。所以如果有 n 个国家和 m 个地区,这将导致 2×n + m 代理objects,这也会造成data duplication(我们多次重复country/region的名称,如果以后对某个国家或地区进行重命名,会导致一些痛苦更新所有这些代理)。

使用 类结构

如果级别数可以很大,或者动态(在某种意义上,一些"areas"*有子区域,而另一些则没有区域) ,或者我们想以 uniform 的方式处理所有这些级别,我们可以决定使用类似 tree 的结构。

这里我们定义一个模型,比如Area,一个Area可以有一个parent,也就是一个Area,我们可以构造一个tree-结构呀。例如:

class <b>Area</b>(models.Model):
    parent = ForeignKey('app.Area', on_delete=models.SET_NULL, related_name='children')

然后我们可以为每个 Area 附加零、一个或多个定律。所以模型看起来像:

  ----
 N|  |1
+------+ 1      N +-----+
| Area |----------| Law |
+------+          +-----+

优点是这里我们有一个模型用于CountryRegionSubregion等。此外,我们可以实现一个层次结构正是我们想要的方式,例如一些(小)国家没有 Regions(例如“城市国家”,如 梵蒂冈城新加坡等)。此外,Law 对象将 link 变成类似 "area" 的对象。也很容易获得某个区域的附属法则

然而一个问题是很难获得所有一个国家的Law,它的区域,它的次区域等等。但是这可以处理,例如通过设计一个多对多 table 来编码这个树结构的 传递闭包 :这个多对多关系将包含 links 是一个拥有 所有 区域、次区域等的国家,但这仍然不是很优雅。这也意味着所有这些 Area 实例都是统一表示的。因此,如果我们想在 Area 中添加一个包含官方语言的列表,所有地区都有 "official languages",而大多数次区域可能只是 "inherit" 他们国家的官方语言。

Law 对象中使用 GenericForeignKey(Django 功能)

Django 还有一种特殊的关系,称为 GenericForeignKey [doc]。对于此类问题,这可能看起来 很棒 功能,但我真的建议尽可能避免这种关系。

默认情况下,Django 为每个模型添加一个隐式主键:如果开发人员没有 指定带有primary_key=True 的字段。 Django 会自动添加一个 IntegerField 来指定一个标识符作为主键。因此可以合理地假设 大多数 模型有一个 IntegerField 作为主键(事实上它是 un-Django 来指定另一个首要的关键)。我们还可以生成一个列表,将每个模型映射到一个整数:例如,我们可以说 0 映射到 User1 映射到 Group,等等。

这意味着大多数模型实例可以用两个整数来标识:一个整数指定模型,一个指定对应模型的主键。例如 (0, 14) 是带有主键 14User(假设我们使用上面段落中定义的 "lookup table")。这是一个强大的概念:因此我们可以使用两个数据库列来存储这些整数,并且每次都让 Django 获取对象。这就是 GenericForeignKey 背后的想法。因此我们可以定义一个 Law 像:

class Law(models.Model):
    <b>content_type</b> = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    <b>object_id</b> = models.PositiveIntegerField()
    <b>area = GenericForeignKey('content_type', 'object_id')</b>

所以这意味着现在我们的 Law 存储了两个真实字段:一个 content_type 和一个 object_id,如果我们查询 some_law.area,Django 将获取一个与这两个整数对应的对象。

因此我们可以用它来指代 CountryRegionSubregion,这在我们可以指代各种模型时很有用。但是也有很多缺点。主要问题是,如果我们想在查询中使用它们,这种关系通常非常麻烦。确实:假设我们想将 Law 模型与 area 字段结合起来。那我们应该加入哪个table呢? CountryRegionSubRegion?如果一个 Law 指的是 Country 而另一个指的是 Region 怎么办?所以通常我们不能 JOIN.

此外,模型 本身 保证 GenericForeignKey 将始终引用 "area"-像对象。它可以引用另一个 LawUserCriminal 等。因此,您有责任编写 sane 逻辑将始终确保关系有意义。虽然这看起来很容易,但要维持良好的 理智 关系却很难。大多数数据库无法检查外键是否引用 有效 对象,因为没有 FOREIGN KEY 约束,因为 "target table" 是未知的。

虽然有很多缺点,但在某些情况下GenericForeignKey 可以成为某些问题的优雅解决方案,但必须小心。

尽管据我所知,没有普遍接受的方式在图表中指定此类关系,但它可能看起来像这样:

+---------+
| Country |
+---------+.
    | 1      .
    |          .
    | N          .
+---------+        . +-----+
| Region  |. . . . . | Law |
+---------+ 1      N +-----+