Django:为啥要为 UserProfile 创建 OneToOne 而不是子类化 auth.User?

Posted

技术标签:

【中文标题】Django:为啥要为 UserProfile 创建 OneToOne 而不是子类化 auth.User?【英文标题】:Django: Why create a OneToOne to UserProfile instead of subclassing auth.User?Django:为什么要为 UserProfile 创建 OneToOne 而不是子类化 auth.User? 【发布时间】:2011-07-24 01:31:17 【问题描述】:

注意:如果您想通过告诉我您不喜欢 django.contrib.auth 来“回答”这个问题,请继续。那不会有帮助。我很清楚在这个问题上意见的范围和强度。

现在,问题来了:

约定是创建一个模型,UserProfile,对用户具有 OneToOne。

在我能想到的所有方面,一种更有效和有效的方法是将 User 子类化为一个打算用于系统中每个人的类 - 一个名为 Person(User) 的类。

我还没有看到一个连贯的解释为什么前者是传统的,而后者被认为是一种黑客行为。前段时间,为了获得使用 get_profile() 的能力,我改用 OneToOne 方法,从那以后我就后悔了。我正在考虑切换回去,除非我能够理解这种方法的优势。

【问题讨论】:

【参考方案1】:

对于为什么在实践中继承 User 不如拥有 UserProfile 有用,至少从“官方”来源来看,从来没有真正好的解释。

但是,我有几个原因,这是在我自己决定子类化 User 是“要走的路”之后出现的。

您需要一个自定义身份验证后端。这不是什么大问题,但是您需要编写的代码越少越好。 其他应用程序可能会假设您的用户是django.contrib.auth.models.User。大多数情况下这都可以,除非该代码正在获取用户对象。因为我们是一个子类,所以任何只使用我们的 User 对象的代码都应该没问题。 用户一次只能“成为”一个子类。例如,如果您有学生和教师的用户子类,那么在给定时间,您的用户将只能是教师或学生。使用 UserProfiles,可以同时将教师和学生个人资料附加到同一用户。 接下来,从一个子类转换为另一个是困难的:尤其是如果您已经拥有一个子类的实例。

因此,您可能会说,“我的项目将永远只有一个 User 子类”。那正是我所想。现在我们有三个,加上普通用户,可能还有第四个。 需求发生变化,不得不更改大量代码来处理这种情况并不是很有趣。

注意:最近有很多关于 django-developers 的讨论,讨论如何更好地解决与 contrib.auth 用户模型相关的问题。

【讨论】:

如果任何其他应用使用django.contrib.auth.models.User,则应将它们重写为使用get_user_model()【参考方案2】:

继承User模型是否更高效有效?我不明白为什么,但我想阅读您的论点。 IMNSHO,模型继承一直很痛苦。

然而,这可能无法回答您的问题,但我对 Will Hardy 在this snippet 中提出的解决方案非常满意。通过利用信号,它会自动为每个新用户创建一个新的用户配置文件。

链接不太可能消失,但这是我的代码版本略有不同:

from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.utils.translation import ugettext_lazy as _

class AuthUserProfileModelBase(models.base.ModelBase):
    # _prepare is not part of the public API and may change
    def _prepare(self):
        super(AuthUserProfileModelBase, self)._prepare()
        def on_save(sender, instance, created, **kwargs):
            if created:
                self.objects.create(user=instance)
        # Automatically link profile when a new user is created
        post_save.connect(on_save, sender=User, weak=False)

# Every profile model must inherit this class
class AuthUserProfileModel(models.Model):
    class Meta:
        abstract = True
    __metaclass__ = AuthUserProfileModelBase
    user = models.OneToOneField(User, db_column='auth_user_id',
        primary_key=True, parent_link=True)

# The actual profile model
class Profile(AuthUserProfileModel):
    class Meta:
        app_label = 'some_app_label'
        db_table = 'auth_user_profile'
        managed = True
    language = models.CharField(_('language'), max_length=5, default='en')

当然,所有功劳都归功于 Will Hardy。

【讨论】:

我认为继承有利的主要原因之一,至少在理论上,是由 P.S.在 Will 的 sn-p 中:“(PS:如果像 django 的模型继承一样让生成的类代理 User 对象的属性,同时在创建 User 对象时仍然自动创建 UserProfile 对象 :-)”跨度> @Justin:我不认为这是一个真正的优势,主要是因为代理已经是一个属性了 - profile_instance.user.desired_attribute Welp,有时候我不知道我是否有一个非洲燕子或燕子对象,我希望能够以任何方式访问它的椰子。需要提前确定这一点让我觉得很奇怪——这就是类继承的全部意义所在。 (顺便说一句,这个讨论正好碰到了我的另一个问题,这个问题的答案会给你50分和我的很多爱:-)。在这里:***.com/questions/5348157/…) @Justin:我看不到连接。事实上,尖锐的问题似乎与我们在这里讨论的内容相去甚远。在您最初的问题的情况下,什么时候会出现这个问题?抱歉,我没意识到。【参考方案3】:

您确实意识到,模型子类化是通过底层的 OneToOne 关系实现的?事实上,就效率而言,我看不出这两种方法有什么区别。

在我看来,现有具体模型的子类化是一种令人讨厌的 hack,应该尽可能避免。它涉及隐藏数据库关系,以便不清楚何时执行额外的数据库访问。显式显示关系并在必要时显式访问它们会更加清晰。

现在,我喜欢的第三种选择是创建一个全新的用户模型,以及一个返回新模型实例而不是默认模型的自定义身份验证后端。创建后端只需要定义几个简单的方法,所以很容易做到。

【讨论】:

是的,我当然意识到子类化创建了一个“隐式”OneToOne。在清晰度和效率方面,person.email 比 userprofile.user.email 更清晰。你能具体说明现有模型的子类化问题是什么吗?你的第三个选择让我很感兴趣。有没有我可以阅读的关于这种技术的好文档? 另外,在您的第二段中:您不能对在具体模型上使用普通 OneToOne 隐藏数据库命中做出相同的声明吗?或者你是说因为你不总是需要父母的PK,所以有时可以省去行程?如果是这样,这是一个足够公平的观点,但它并不真正适用于这里,因为我们总是(至少,在我能想到的每一种情况下)无论如何都想要去用户。 当我对 User 进行子类化时,它工作了一段时间,但后来当我创建了多对多“通过”关系时,当通过对象尝试访问 FK 到子类化的用户模块时,它实际上会而是访问用户模型,这会导致各种问题并且如果不进入 django 内部结构,就无法真正纠正。这是 django 1.3.1。我切换到只使用指向某些用户对象的配置文件对象并解决了。 这篇文章过时了吗?我不明白为什么这是一个“讨厌的黑客”,特别是因为 Django 现在可以控制在设置中设置自定义用户模型,并且所有想要“用户”的东西都应该使用 get_user_model() 获取它(不知道什么时候虽然添加了东西) 子类化 concrete 模型过去和现在仍然是一个讨厌的 hack。子类化一个抽象模型,就像你将 AbstractUser 子类化以创建自定义 User 模型一样,非常好,尽管在我写这篇文章时它不可用。

以上是关于Django:为啥要为 UserProfile 创建 OneToOne 而不是子类化 auth.User?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Django 中创建保存 User 和 UserProfile 对象的视图

IntegrityError -userprofile.u_id 不能为NULL,django用户注册

在 Django 中扩展用户对象:用户模型继承还是使用 UserProfile?

Django UserProfile slug 字段

django查询过滤

Django - 管理员中的 UserProfile m2m 字段 - 错误