对自定义用户模型使用 Django auth UserAdmin

来自 姜戈,贡献,鉴定文件:

扩展 Django 的默认用户 如果您对 Django 的 User 模型非常满意,并且只想添加一些额外的配置文件信息,那么您可以简单地子类化 django.contrib.auth.models.AbstractUser并添加自定义配置文件字段。此类以抽象模型的形式提供默认 User 的完整实现。

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

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

这在管理中显示出来,几乎与 Django 的标准 User一样。但是,管理中最重要的区别是不存在 password-(re) set 字段,而是显示一个普通的 CharField。我真的必须覆盖管理配置中的内容才能让它工作吗?如果是这样,我怎么才能以某种干燥的方式做到这一点(即不从 Django 源代码复制内容... ... eww... ...) ?

66416 次浏览

After digging around the Django source code for a while, I found a working soultion. I am not totally happy with this solution, but it seems to work. Feel free to suggest better solutions!


Django uses UserAdmin to render the nice admin look for User model. By just using this in our admin.py-file, we can get the same look for our model.

from django.contrib.auth.admin import UserAdmin
admin.site.register(MyUser, UserAdmin)

However, this alone is probably not a good solution, since Django Admin will not display any of your special fields. There are two reasons for this:

  • UserAdmin uses UserChangeForm as the form to be used when modifying the object, which in its turn uses User as its model.
  • UserAdmin defines a formsets-property, later used by UserChangeForm, which does not include your special fields.

So, I created a special change-form which overloads the Meta inner-class so that the change form uses the correct model. I also had to overload UserAdmin to add my special fields to the fieldset, which is the part of this solution I dislike a bit, since it looks a bit ugly. Feel free to suggest improvements!

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)

nico's answer has been extremely helpful but I found Django still references the User model when creating a new user.

Ticket #19353 references this problem.

In order to fix it i had to make a few more additions to 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)

A simpler solution, 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 will correctly reference MyUser model for creation and modification. I'm using Django 1.6.2.

cesc's answer wasn't working for me when I attempted to add a custom field to the creation form. Perhaps it's changed since 1.6.2? Either way, I found adding the field to both fieldsets and add_fieldsets did the trick.

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)

Another similar solution (Took from here):

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)

If you want to hook into the default sections instead of copying and pasting Django core code, you can extend the UserAdmin class and inject your fields into the fieldsets attribute as wished.

In Django v4.0.x, the fieldsets tuple you are modifying looks like this:

    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")}),
)

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

Now, for example, to add a custom role field:

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)