Using Django auth UserAdmin for a custom user model

61,738

Solution 1

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)

Solution 2

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.

Solution 3

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)

Solution 4

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)

Solution 5

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)
Share:
61,738
nip3o
Author by

nip3o

Updated on August 20, 2021

Comments

  • nip3o
    nip3o almost 3 years

    From the Django.Contrib.Auth docs:

    Extending Django’s default User If you’re entirely happy with Django’s User model and you just want to add some additional profile information, you can simply subclass django.contrib.auth.models.AbstractUser and add your custom profile fields. This class provides the full implementation of the default User as an abstract model.

    Said and done. I created a new model like below:

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

    This shows up in admin almost like Django's standard User. However, the most important difference in admin is that the password-(re)set field is not present, but a normal CharField is displayed instead. Do I really have to override stuff in the admin-config to get this to work? If so, how can I do that in somewhat DRY way (i.e. without copying stuff from the Django source... eww...)?

  • Dachmt
    Dachmt about 11 years
    Great! That's exactly what I was looking for, I'm doing the same with my custom user model, using the AbstractUser. This solution works great, some customization (hiding fields, adding your 'some_extra_data' in the 'Personal info') would be nice too but for now you made my day. Thanks @nico
  • allcaps
    allcaps about 11 years
    Overloading of de UserAdmin fieldsets ugly? It works. Thanks.
  • Thane Brimhall
    Thane Brimhall almost 11 years
    Also note you need to override the user creation form, per the answer by @kdh454.
  • Nick
    Nick almost 10 years
    Note that starting in 1.6, the call to forms.ValidationError has a second argument: code='duplicate_username': github.com/django/django/blob/1.6/django/contrib/auth/… Also, I tried this on 1.6.5 and, for some reason, I didn't need to override UserChangeForm at all and the change form worked fine with my custom field. I can't explain why. It seems like it shouldn't have worked because of the UserChangeForm class having model = User present: github.com/django/django/blob/1.6.5/django/contrib/auth/…
  • Chase Roberts
    Chase Roberts almost 10 years
    I don't know why but I kept getting a "TypeError: unsupported operand type(s) for +: 'NoneType' and 'tuple'" when trying this.
  • Rômulo Collopy
    Rômulo Collopy over 8 years
    I suggest you to inspect UserAdmin.fieldsets and see if it's None
  • stratis
    stratis about 8 years
    FYI ticket #19353 has now been fixed which essentially means that your solution may be redundant. If we just use @nip3o's solution we should be good.
  • Vladimir Marton
    Vladimir Marton about 6 years
    This is perfect, it's not messy at all. The django documentation points to this article: docs.djangoproject.com/en/2.0/topics/auth/customizing/… ... but I wasnt able to make it work with that, it was complaining about some User foreign key constraints. Might be related only to my project, nevertheless your solution works!
  • Guillaume Lebreton
    Guillaume Lebreton over 5 years
    Instead let None, you can input a string and it will be used as the title of the section in the admin form
  • nicorellius
    nicorellius about 4 years
    Agree with @brillout, found this is the best answer. To add, you might get this error, which makes sense (Python 3.8, Django 3): ERRORS: <class 'users.admin.CustomerUserAdmin'>: (admin.E005) Both 'fieldsets' and 'fields' are specified.