扩展 auth.User 模型、代理字段和 Django 管理

发布于 2024-10-20 21:21:31 字数 2378 浏览 1 评论 0原文

(编辑:我知道 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)

这通常工作正常,但当我尝试使用 User 时会中断UserProfileAdmin.list_display 中的 字段。问题出在此处的管理验证代码中:

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 shell 中演示:

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

经过一番挖掘,看起来我想覆盖 UserProfile._meta 的 django.db.models.options.Options.get_field 。但似乎没有一种非 hacky 的方法来做到这一点(我现在有一个非常 hacky 的解决方案,其中涉及猴子修补 UserProfile._meta.[get_field, get_field_by_name])...有什么建议吗?谢谢。

(Edit: I know that there's a totally separate feature in Django called "Proxy Models". That feature doesn't help me, because I need to be able to add fields to UserProfile.)

So I'm starting a new Django app and I'm creating a UserProfile model which is a extension of django.contrib.auth.models.User and failed attribute requests back to User, as follows:

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)

This works fine in general, but breaks when I try to use a User field in UserProfileAdmin.list_display. The problem is in the admin validation code here:

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))

The problem is that while an instance of UserProfile will have proxied fields, e.g. email, the UserProfile class itself doesn't. Demoed in the Django shell:

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

After some digging, it looks like I'd want to override django.db.models.options.Options.get_field for UserProfile._meta. But there doesn't seem to be a non-hacky way to do this (I have a very hacky solution right now, which involves monkey-patching UserProfile._meta.[get_field, get_field_by_name])...any suggestions? Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

风向决定发型 2024-10-27 21:21:31

保持简单。这是我们使用的库中的 UserProfile 模型的示例:

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 对象进行 CRUD,并以内联方式访问 UserProfile。现在,您不必将属性查找从 UserProfile 代理到 User 模型。要从 User u 实例访问 UserProfile,请使用 u.get_profile()

Keep it simple. Here's an example of a UserProfile model from a library we use:

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

That's it. Don't bother with the __getattr__ override. Customise the admin interface instead:

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)

This allows you to CRUD the User object, with access to the UserProfile as an Inline. Now you don't have to proxy attribute lookups from the UserProfile to the User model. To access the UserProfile from an instance of User u, use u.get_profile()

我的痛♀有谁懂 2024-10-27 21:21:31

这不是一个代理类,而是一种关系。有关代理模型的更多信息,这是一个原始模型的子类,带有 Meta.proxy = True

This is not a proxy class, it is a relationship. See more on Proxy Models, which are a subclass of the original model, with the Meta.proxy = True

灰色世界里的红玫瑰 2024-10-27 21:21:31

如果您只想将 User 中的字段显示在 UserProfileAdmin 的 list_display 中,请尝试:

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

如果您希望将其作为表单的一部分,请将其作为额外字段添加到 UserProfileForm 中,并在表单中验证它。

If you just want a field from User to be in list_display in your UserProfileAdmin, try:

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

If you want to have it as part of the form, add it into your UserProfileForm as an extra field, and validate it in the form.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文