扩展auth.User模型、代理字段与Django管理后台

1 投票
3 回答
2236 浏览
提问于 2025-04-16 13:05

(编辑:我知道Django有一个叫“代理模型”的完全不同的功能。但这个功能对我没有帮助,因为我需要在UserProfile中添加字段。)

我正在开始一个新的Django应用,创建一个UserProfile模型,它是django.contrib.auth.models.User的扩展,并且在User中请求属性失败,具体如下:

from django.db import models
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.OneToOneField(User, related_name='profile')

    def __getattr__(self, name, *args):
        if name == 'user' or name == '_user_cache':
            raise AttributeError(name)

        try:
            return getattr(self.user, name, *args)
        except AttributeError, e:
            raise AttributeError(name)

这个方法总体上是可行的,但当我尝试在UserProfileAdmin.list_display中使用User字段时就出问题了。问题出在这里的管理验证代码:

def validate(cls, model):
    """
    Does basic ModelAdmin option validation. Calls custom validation
    classmethod in the end if it is provided in cls. The signature of the
    custom validation classmethod should be: def validate(cls, model).
    """
    # Before we can introspect models, they need to be fully loaded so that
    # inter-relations are set up correctly. We force that here.
    models.get_apps()

    opts = model._meta
    validate_base(cls, model)

    # list_display
    if hasattr(cls, 'list_display'):
        check_isseq(cls, 'list_display', cls.list_display)
        for idx, field in enumerate(cls.list_display):
            if not callable(field):
                if not hasattr(cls, field):
                    if not hasattr(model, field):
                        try:
                            opts.get_field(field)
                        except models.FieldDoesNotExist:
                            raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r."
                                % (cls.__name__, idx, field, cls.__name__, model._meta.object_name))

问题在于,虽然UserProfile的实例会有代理字段,比如电子邮件,但UserProfile类本身并没有这些字段。在Django的命令行工具中演示:

>>> hasattr(UserProfile, 'email')
False
>>> hasattr(UserProfile.objects.all()[0], 'email')
True

经过一些深入研究,我发现我想要重写django.db.models.options.Options.get_field,以便为UserProfile._meta提供支持。但似乎没有一种不算“黑科技”的方法来做到这一点(我现在有一个非常“黑科技”的解决方案,涉及到对UserProfile._meta.[get_field, get_field_by_name]进行修改)……有什么建议吗?谢谢。

3 个回答

0

这不是一个代理类,而是一种关系。想了解更多,可以查看代理模型,它是原始模型的一个子类,并且有一个设置叫做 Meta.proxy = True

0

如果你只是想在你的 UserProfileAdmin 中显示 User 的某个字段,可以试试下面的代码:

class UserProfileAdmin(admin.ModelAdmin):
    list_display = ('user__email',)

如果你想把这个字段放到表单里,就把它作为一个额外的字段添加到你的 UserProfileForm 中,并在表单中进行验证。

2

保持简单。这里有一个我们使用的库里的用户资料模型示例:

class UserProfile(models.Model):
    user = models.OneToOneField(User)
    accountcode = models.PositiveIntegerField(null=True, blank=True)

就这样。别去管那个 __getattr__ 的重写。相反,定制一下管理界面:

from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User

class UserProfileInline(admin.StackedInline):
    model = UserProfile

class StaffAdmin(UserAdmin):
    inlines = [UserProfileInline]
    # provide further customisations here

admin.site.register(User, StaffAdmin)

这样你就可以对 User 对象进行增删改查,同时可以把用户资料作为一个内联显示。现在你不需要再从用户资料去查找用户模型的属性了。如果你想从一个 User u 的实例中访问 UserProfile,可以用 u.get_profile()

撰写回答