Archit Verma
Archit Verma

Reputation: 1917

Custom user model in django

I want to create a custom user model using django.contrib.auth.models.AbstractUser as stated in the djangodocs:

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.

So I inherited the AbstractUser class in my Users class and added a field. But when I run the python manage.py syncdb I get the following error:

CommandError: One or more models did not validate:
admin.logentry: 'user' has a relation with model login.users, which has either 
not been installed or is abstract.

I went through other questions on stackoverflow but couldn't resolve the error. Here is my code:

models.py

from django.conf import settings
from django.db import models
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import AbstractUser
from django.contrib import admin

class Users(AbstractUser):
    college = models.CharField(max_length=40)

admin.site.register(Users, UserAdmin)

admin.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from login.models import Users
from django import forms

class UsersChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = Users

class UsersAdmin(UserAdmin):
    form = UsersChangeForm

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


admin.site.register(Users, UsersAdmin)

settings.py

INSTALLED_APPS = (
    'forms',
    'login',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
)

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

AUTH_USER_MODEL = 'login.users'

EDIT:

I want to store the user information in the same table as auth_user and not in a new table.

Upvotes: 14

Views: 12141

Answers (4)

user24834406
user24834406

Reputation: 11

I found an easy and perfect sln. Feel free to check, here are my files For reference check on git (https://github.com/bytechtechnologies/Create-CustomUser-in-Django) or YouTube (https://youtu.be/W11uZ8cbWuE)

models.py file

from django.contrib.auth.base_user import BaseUserManager, AbstractBaseUser
from django.db import models


class MyUserManager(BaseUserManager):
    def create_user(self, username, phone, password=None):
        user = self.model(
            username=username,
            phone=phone
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, username, phone, password=None):
        user = self.create_user(
            username=username,
            phone=phone,
            password=password
        )

        user.is_admin = True
        user.save(using=self._db)
        return user


class CustomUser(AbstractBaseUser):
    username = models.CharField(
        max_length=150,
        unique=True
    )
    phone = models.IntegerField()
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    objects = MyUserManager()

    USERNAME_FIELD = "username"
    REQUIRED_FIELDS = ["phone"]

    def __str__(self):
        return self.username

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):
        return True

    @property
    def is_staff(self):
        return self.is_admin

admin.py file

from django import forms
from django.contrib import admin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.contrib.auth.models import Group
from django.core.exceptions import ValidationError
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin

from .models import CustomUser


class UserCreationForm(forms.ModelForm):
    password1 = forms.CharField(label="password", widget=forms.PasswordInput)
    password2 = forms.CharField(label="password confirmation", widget=forms.PasswordInput)

    class Meta:
        model = CustomUser
        fields = ["username", "phone"]

    def clean_password2(self):
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")

        if password1 and password2 and password1 != password2:
            raise ValidationError("passwords do not match")

        return password2

    def save(self, commit=True):
        user = super().save(
            commit=False
        )

        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()

        return user


class UserChangeForm(forms.ModelForm):
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = CustomUser
        fields = ["username", "phone", "password", "is_active", "is_admin"]


class UserAdmin(BaseUserAdmin):
    form = UserChangeForm
    add_form = UserCreationForm

    list_display = ["username", "phone", "is_admin"]
    list_filter = ["username"]
    fieldsets = [
        (None, {"fields": ["username", "password"]}),
        ("Personal info", {"fields": ["phone"]}),
        ("Permissions", {"fields": ["is_admin"]},
         ),
    ]

    add_fieldsets = [(
        None, {
            "classes": ["wide"],
            "fields": ["username", "phone", "password1", "password2"],
        },
    ),
    ]

    search_fields = ["username"]
    filter_horizontal = []
    ordering = ["username"]


admin.site.register(CustomUser, UserAdmin)

admin.site.unregister(Group)

settings.py file

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'project1app'   # the name of your start app for example I named mine 'project1app'
]

AUTH_USER_MODEL = "project1app.CustomUser"  # name of your start app and name of user class for example my app name is 'project1app' and user class is CustomUser

Upvotes: 0

harishkb
harishkb

Reputation: 406

It is a good practice to keep your code generic by using get_user_model() (to get the User model that is active in your project. Defined in django.contrib.auth) and AUTH_USER_MODEL (to reference the User model) rather than directly referring to the User model.

from django.conf import settings

class MyUser(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL)

    # Or a ForeingKey to the College table?
    college = models.CharField(max_length=40)

    other_data = ...

Upvotes: 3

Victor Castillo Torres
Victor Castillo Torres

Reputation: 10811

try removing this line in models.py:

admin.site.register(Users, UserAdmin)

I figured out your error, You have the INSTALLED_APPS in the wrong order they should be the next:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    #'django.contrib.sites',
    #'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
    'login',
    'forms',
)

AUTH_USER_MODEL = "login.User"

Why?

looking at the source code admin uses the User model so maybe you can think:

But my user model is into login app and this one is before admin app, therefore login is installed before admin, why it does not work?. That's because django.contrib.auth is swappable (the model can be replaced for another one in your case has been swappable by login.User) so as you created a new User Model therefore django has to change its user Model to the new one and then you have to install the rest.

How must be at the moment of run syncdb:


1.- django.contrib.auth is installed, then this see if has been swapped, if yes it adds the new field or override completely the User Model (in your case only you add a field).

2.- admin, contentypes.... your apps, are installed and all works.

How you had it:

1.- login is installed (where the new User Model was, you put the AUTH_USER_MODEL in settings)

2.- Then forms.

3.- Then django tries to install the admin app but it can't because auth_user table hasn't been added that's because django.contrib.auth was after admin, therefore there is no User Model, this one isnt's swapped and this throws the error  **admin.logentry: 'user' has a relation with model login.users, which has either 
not been installed or is abstract.**

So accordingly first you always have to install the django.contrib.auth app and then all the apps that depends from.

Upvotes: 1

Nil
Nil

Reputation: 2390

I did this in one of my project. I was surprised to see that you extended User because the doc says something else :) You can extend Django User model, but if you only want to add new fields (not change its behavior), you should use a OneToOneField.

If you wish to store information related to User, you can use a one-to-one
relationship to a model containing the fields for additional information.

So, as you can see in the link, your code should look like:

from django.contrib.auth.models import User

class MyUser(models.Model):
    user = models.OneToOneField(User)

    # Or a ForeingKey to the College table?
    college = models.CharField(max_length=40)

    other_data = ...

Upvotes: 9

Related Questions