使用Django认证UserAdmin来处理自定义用户模型

93

来自 Django.Contrib.Auth 文档

扩展 Django 默认的用户 如果您对 Django 的 User 模型完全满意,只想添加一些额外的个人资料信息,可以简单地继承 django.contrib.auth.models.AbstractUser 并添加自定义个人资料字段。该类作为抽象模型提供了默认 User 的完整实现。

说做就做。我创建了一个新模型如下:

class MyUser(AbstractUser):
  some_extra_data = models.CharField(max_length=100, blank=True)

这在管理员界面上几乎像 Django 的标准 “User” 一样显示。然而,在管理员中最重要的区别是密码(重新)设置字段不存在,而是显示普通的 CharField。我真的需要在管理员配置中覆盖一些东西才能使其工作吗?如果是这样,有什么干净利落的方法可以做到这一点吗(即不从 Django 源代码中复制... 呃...)?
6个回答

152

在研究了一段时间Django源代码后,我找到了一个可行的解决方案。虽然我对这个解决方案不是完全满意,但它似乎起作用了。欢迎提出更好的解决方案!


Django使用UserAdminUser模型渲染美观的管理员界面。只需在admin.py文件中使用此代码,我们就可以为我们的模型获得相同的外观。
from django.contrib.auth.admin import UserAdmin
admin.site.register(MyUser, UserAdmin)

然而,仅靠这个可能不是一个好的解决方案,因为Django Admin不会显示任何你特殊字段。有两个原因:
  • UserAdmin使用UserChangeForm作为修改对象时使用的表单,后者又使用User作为其模型。
  • UserAdmin定义了一个formsets属性,稍后由UserChangeForm使用,其中不包括你的特殊字段。
所以,我创建了一个特殊的更改表单,重载了内部类Meta,使更改表单使用正确的模型。我还不得不重载UserAdmin,将我的特殊字段添加到fieldset中,这是我不太喜欢的部分解决方案,因为它看起来有点丑陋。欢迎提出改进建议!
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm

class MyUserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = MyUser

class MyUserAdmin(UserAdmin):
    form = MyUserChangeForm

    fieldsets = UserAdmin.fieldsets + (
            (None, {'fields': ('some_extra_data',)}),
    )


admin.site.register(MyUser, MyUserAdmin)

太好了!这正是我在寻找的,我正在使用AbstractUser来创建自定义用户模型。这个解决方案非常好,一些自定义(隐藏字段,在“个人信息”中添加“some_extra_data”)也很不错,但现在你让我的一天变得美好了。谢谢@nico - Dachmt
用户管理字段集的重载看起来不太好看?但它确实有效。谢谢。 - allcaps
7
请注意,根据@kdh454的回答,您需要覆盖用户创建表格。 - Thane Brimhall
这很完美,一点也不凌乱。Django文档指向了这篇文章:https://docs.djangoproject.com/en/2.0/topics/auth/customizing/#extending-the-existing-user-model ... 但是我无法通过它使其工作,因为它抱怨某些用户外键约束。可能只与我的项目有关,但是您的解决方案有效! - Vladimir Marton

62

一个更简单的解决方案,admin.py:

from django.contrib.auth.admin import UserAdmin
from main.models import MyUser

class MyUserAdmin(UserAdmin):
    model = MyUser

    fieldsets = UserAdmin.fieldsets + (
            (None, {'fields': ('some_extra_data',)}),
    )

admin.site.register(MyUser, MyUserAdmin)

Django会正确地引用MyUser模型进行创建和修改。我正在使用Django 1.6.2。


我不知道为什么,但每次尝试时都会得到一个“TypeError:unsupported operand type(s) for +: 'NoneType' and 'tuple'”的错误。 - Chase Roberts
我建议您检查UserAdmin.fieldsets并查看它是否为None。 - Rômulo Collopy
2
不要使用None,你可以输入一个字符串作为管理员表单中该部分的标题。 - Guillaume Lebreton
同意@brillout的观点,发现这是最好的答案。此外,您可能会遇到这个错误,这是有道理的(Python 3.8,Django 3): ERRORS:<class'users.admin.CustomerUserAdmin'>:(admin.E005)同时指定了'fieldset'和'fields'。 - nicorellius

58

nico的回答非常有帮助,但是我发现在创建新用户时Django仍然会引用User模型。

Ticket #19353 提到了这个问题。

为了解决它,我不得不在admin.py中进行一些其他的添加。

admin.py:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from main.models import MyUser
from django import forms


class MyUserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = MyUser


class MyUserCreationForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        model = MyUser

    def clean_username(self):
        username = self.cleaned_data['username']
        try:
            MyUser.objects.get(username=username)
        except MyUser.DoesNotExist:
            return username
        raise forms.ValidationError(self.error_messages['duplicate_username'])


class MyUserAdmin(UserAdmin):
    form = MyUserChangeForm
    add_form = MyUserCreationForm
    fieldsets = UserAdmin.fieldsets + (
        (None, {'fields': ('extra_field1', 'extra_field2',)}),
    )

admin.site.register(MyUser, MyUserAdmin)

3
请注意,从1.6版本开始,调用forms.ValidationError需要第二个参数:code='duplicate_username'。参考链接:https://github.com/django/django/blob/1.6/django/contrib/auth/forms.py#L101-L104。此外,我在1.6.5上尝试了这个方法,出于某种原因,我根本不需要覆盖`UserChangeForm`,我的自定义字段也能正常工作。我无法解释为什么会这样,因为`UserChangeForm`类中有`model = User`存在,看起来似乎不应该生效。参考链接:https://github.com/django/django/blob/1.6.5/django/contrib/auth/forms.py#L138。 - Nick
2
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - stratis

13

当我尝试向创建表单添加自定义字段时,cesc的答案对我没有用。也许自1.6.2以来有所改变?无论如何,我发现将该字段添加到fieldsets和add_fieldsets两个地方都能起作用。

ADDITIONAL_USER_FIELDS = (
    (None, {'fields': ('some_additional_field',)}),
)

class MyUserAdmin(UserAdmin):
    model = MyUser

    add_fieldsets = UserAdmin.add_fieldsets + ADDITIONAL_USER_FIELDS
    fieldsets = UserAdmin.fieldsets + ADDITIONAL_USER_FIELDS

admin.site.register(MyUser, MyUserAdmin)

4
如果你想要连接默认的部分而不是复制粘贴Django核心代码,你可以扩展UserAdmin类并根据需要将你的字段注入到fieldsets属性中。
在Django v4.0.x中,你修改的fieldsets元组看起来像这样:
    fieldsets = (
        (None, {"fields": ("username", "password")}),
        (_("Personal info"), {"fields": ("first_name", "last_name", "email")}),
        (
            _("Permissions"),
            {
                "fields": (
                    "is_active",
                    "is_staff",
                    "is_superuser",
                    "groups",
                    "user_permissions",
                ),
            },
        ),
        (_("Important dates"), {"fields": ("last_login", "date_joined")}),
    )

来源:https://github.com/django/django/blob/stable/4.0.x/django/contrib/auth/admin.py#L47-L63

例如,现在要添加一个自定义角色(role)字段:

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


class UserAdmin(BaseUserAdmin):
    fieldsets = BaseUserAdmin.fieldsets
    fieldsets[0][1]['fields'] = fieldsets[0][1]['fields'] + (
        'role',
    )


admin.site.register(User, UserAdmin)

3

另一个类似的解决方案(取自此处):

from __future__ import unicode_literals

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import AbstractUser
from django.utils.translation import ugettext_lazy as _

from .models import User


class UserAdminWithExtraFields(UserAdmin):

    def __init__(self, *args, **kwargs):
        super(UserAdminWithExtraFields, self).__init__(*args, **kwargs)

        abstract_fields = [field.name for field in AbstractUser._meta.fields]
        user_fields = [field.name for field in self.model._meta.fields]

        self.fieldsets += (
            (_('Extra fields'), {
                'fields': [
                    f for f in user_fields if (
                        f not in abstract_fields and
                        f != self.model._meta.pk.name
                    )
                ],
            }),
        )


admin.site.register(User, UserAdminWithExtraFields)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接