【问题标题】:Django Multi-Table Inheritance VS Specifying Explicit OneToOne Relationship in ModelsDjango 多表继承 VS 在模型中指定显式 OneToOne 关系
【发布时间】:2009-11-25 12:18:20
【问题描述】:

希望这一切都有意义:) 如有必要,我将通过 cmets 进行澄清。另外,我正在尝试在这个问题中使用粗体文本,如果我(或你)发现它分散注意力,我会将其编辑掉。就这样吧……

使用 django.contrib.auth 为我们提供了用户和组,以及其他我不能没有的有用的东西(比如基本消息传递)。

在我的应用中,我有几种不同类型的用户。一个用户只能是一种类型。这很容易由小组处理,但需要格外小心。 但是,这些不同的用户在层次结构/关系中相互关联。

让我们来看看这些用户:-

委托人 - “顶级”用户

管理员 - 每个管理员向一个校长报告

协调员 - 每个协调员向管理员报告

除了这些还有其他不直接相关的用户类型,但以后可能会相关。例如,“公司”是另一种类型的用户,可以有各种“产品”,产品可能由“协调者”监管。 “买家”是另一种可能购买产品的用户。

现在,所有这些用户都有各种其他属性,其中一些对所有类型的用户都是通用的,而另一些则只对一种用户类型不同。例如,所有类型的用户都必须有一个地址。另一方面,只有 Principal 用户属于“BranchOffice”。

上面提到的另一点是,用户只能是一种类型

该应用程序还需要跟踪谁创建和/或修改了委托人、管理员、协调员、公司、产品等。 (这是指向 User 模型的另外两个链接。)

在这种情况下,使用Django的多表继承是不是一个好主意:-

from django.contrib.auth.models import User
class Principal(User):
    #
    #
    #    
    branchoffice = models.ForeignKey(BranchOffice)
    landline = models.CharField(blank=True, max_length=20)    
    mobile = models.CharField(blank=True, max_length=20)
    created_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalcreator")    
    modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalmodifier")
    #
    #
    #

或者我应该这样做: -

class Principal(models.Model):
    #
    #
    #
    user = models.OneToOneField(User, blank=True)
    branchoffice = models.ForeignKey(BranchOffice)
    landline = models.CharField(blank=True, max_length=20)    
    mobile = models.CharField(blank=True, max_length=20)
    created_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalcreator")    
    modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="principalmodifier")
    #
    #
    #

请记住,还有其他通过外键关联的用户类型,例如:-

class Administrator(models.Model):
    #
    #
    #
    principal = models.ForeignKey(Principal, help_text="The supervising principal for this Administrator")
    user = models.OneToOneField(User, blank=True)
    province = models.ForeignKey(         Province)
    landline = models.CharField(blank=True, max_length=20)    
    mobile = models.CharField(blank=True, max_length=20)
    created_by = models.ForeignKey(User, editable=False, blank=True, related_name="administratorcreator")    
    modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="administratormodifier")

我知道 Django 确实在幕后使用一对一的关系来进行多表继承。我只是没有足够的资格来决定哪种方法更合理。

【问题讨论】:

    标签: django inheritance multi-table


    【解决方案1】:

    我想通过@thornomad 扩展解决方案。

    直接扩展 Django 的 User 类会导致内部 django.auth 机制出现各种问题。我在类似情况下所做的正是@thornomad 所建议的——我制作了自己的 UserProfile 模型与 Django User 模型一对一链接,在该模型中我保存了额外的用户数据,并从中继承了不同类型的模型用户数。

    符合您描述的内容:

    class UserProfile(models.Model):
        user = models.OneToOneField(User, blank=True, related_name='profile')
        class Meta:
            abstract = True
    
    
    class PositionHolderUserProfile(UserProfile):
        first_name = models.CharField(max_length=30)
        last_name = models.CharField(max_length=30)
        landline = models.CharField(blank=True, max_length=20)    
        mobile = models.CharField(blank=True, max_length=20)
        created_by = models.ForeignKey(PositionHolderUserProfile, editable=False, blank=True, related_name="created_users")    
        modified_by = models.ForeignKey(PositionHolderUserProfile, editable=False, blank=True, related_name="modified_users")
    
    class Principal(PositionHolderUserProfile):
        branchoffice = models.ForeignKey(BranchOffice)
    
    class Administrator(PositionHolderUserProfile):
        superior = models.ForeignKey(Principal, related_name="subordinates")
        province = models.ForeignKey(Province)
    
    class Coordinator(PositionHolderUserProfile):
        superior = models.ForeignKey(Administrator, related_name="subordinates")
    
    
    class Company(UserProfile):
        name = models.CharField(max_length=50)
    
    class Product(models.Model):
        name = models.CharField(max_length=50)
        produced_by = models.ForeignKey(Company)
    
    class Buyer(UserProfile):
        first_name = models.CharField(max_length=30)
        last_name = models.CharField(max_length=30)
        products_bought = models.ManyToManyField(Product)
    

    【讨论】:

    • 这对我来说似乎是一个很好的实现。但是为什么同时有 UserProfile 类和 PositionHolderUserProfile 类呢?去掉后者,把里面的所有东西都推到前者的模型上不是更简单吗?
    • 我认为最好封装与所有持仓者相关的数据,例如负责人、管理员和协调员,但与买方和公司等其他用户无关。此类数据可能包括他们在哪个部门工作、他们工作了多长时间等。
    • 这似乎行不通。CommandError: One or more models did not validate: app1.positionholderuserprofile: Accessor for field 'user' clashes with related field 'User.profile'. Add a related_name argument to the definition for 'user'.
    • 自从我发布这个答案以来的 4.5 年里,Django 的用户模型发生了很大变化!
    【解决方案2】:

    我最近转而使用继承自 contrib.auto.models.User 的模型。我的一般观察是,理论上它们很棒,但有时它们并没有像他们应该的那样被自动处理。

    我认为您关于继承与 OneToOne 的决定归结为:

    • 我是否想让 Django 在 95% 的时间里自动做正确的事情,并且需要调试另外 5% 的时间

    -或-

    • 我是否想 100% 自己手动做某事

    如果您还没有看到,Scott Barham 博客有一篇关于继承 User 以及构建自定义后端以确保返回您的自定义对象的精彩帖子 -- Extending the Django User

    另外值得关注的是django-annoying 提供的 AutoOneToOne 字段。它是这两种方法的混合体——没有发生继承,但如果匹配的 OneToOneField 不存在,Django 会负责创建它。

    另外,thornomad 确实很好地说明了模型中的冗余。您可以轻松实现一个抽象类来清理它(假设您正在手动执行 OneToOne):

    class BaseExtendedUser(models.Model):
        user = models.OneToOneField(User, blank=True, related_name='profile')
        landline = models.CharField(blank=True, max_length=20)    
        mobile = models.CharField(blank=True, max_length=20)
        created_by = models.ForeignKey(User, editable=False, blank=True, related_name="created_users")    
        modified_by = models.ForeignKey(User, editable=False, blank=True, related_name="modified_users")
    
        class Meta:
            abstract = True
    
    class Administrator(BaseExtendedUser):
        province = models.ForeignKey(Province)
    
    class Principal(BaseExtendedUser):
        branchoffice = models.ForeignKey(BranchOffice)
    

    【讨论】:

    • >但有时它们不会像应有的那样被自动处理。您能否对此进行扩展,让我们知道他们的行为与预期不符?
    • 很难获得资格,但我相信大多数问题都出在 contrib.admin 上。有时你会遇到奇怪的问题,比如如果你覆盖了我发现的 AdminModel.model_save()(经过一个多小时的试验),你必须手动指定 force_insert 或 force_update 否则它不会创建匹配的 OneToOne。有时,实际的管理界面会假装出来,并说当您刚刚更新某些值时,已经存在具有该 ID 的记录。
    • 如果是这种情况,我将不得不调查一下,但无论如何我都没有使用 contrib.admin。仍在尝试找出多表继承 VS 显式 OneToOne 关系的优缺点
    【解决方案3】:

    我不认为我会继承 User 模型,而是使用自定义 UserProfile - 单独留下 contrib.auth 模型。使用自定义 UserProfile 模型,您可以设置一个 base 用户配置文件模型,该模型可以成为所有不同用户类型的一部分。

    也只是快速查看一下,我会仔细查看任何重复所有相同字段的模型(例如您的最后两个 PrincipleAdministrator 模型)。将内置的组功能与用户配置文件理念相结合可能会满足您的需求。

    【讨论】:

    • 我真的很想将其他类型的用户分开。当我查看我的模型时,我希望看到 Principal、Administrator、Coordinator 是相关的,但 Company 和 Buyer 与这些都没有直接关系。
    • 澄清一下,我确实打算在有益的地方使用抽象模型。另外,我打算为不同类型的用户提供不同的模型。
    【解决方案4】:

    请考虑当协调员被提升为委托人时数据模型中会发生什么。在这种情况下,我根本不会使用继承。请重新考虑之前发帖者的建议“将内置组功能与用户个人资料理念相结合可能会达到您的要求。”

    【讨论】:

    • 没有促销活动。 “将内置组功能与用户配置文件理念相结合可能会满足您的需求。”是一种很好的做法,我遵循它,但是我想知道在这种特定情况下继承与显式 onetoone 的优缺点是什么......
    【解决方案5】:

    您是否需要用户类的对象在任何地方都像 auth.User 一样工作?这将是在 OneToOne 上使用继承的最明显原因。 OneToOne 方法的一个优点是您可以轻松切换到另一个用户模型,如果这是一个问题。

    我在上面看到的真正问题(通过任何一种方法)是似乎没有任何东西阻止您让 Principal 对象和 Administrator 对象共享同一个用户。 OneToOneField 只能保证任意两个关系之间的一对一映射。

    【讨论】:

    • 我现在选择 OneToOne,因为我确切地知道那里发生了什么,而不必猜测继承的所有影响(例如 UserManager)。拥有更大的控制权似乎是一个有吸引力的提议。
    猜你喜欢
    • 2015-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    • 2011-01-16
    • 1970-01-01
    • 2020-12-07
    相关资源
    最近更新 更多