03 Jan 2019, 02:00

Django: Changing User Model in Mid-Project

Every now and then I see some questions pop up in stackoverflow with title:

ValueError: Related model ‘app.User’ cannot be resolved

This is a very common issue and pops up when the user tries to run the migration. So, lets talk more about what is this problem and how can we solve it.

The Problem

This error occurs when you have been using Django’s default User model, and in mid project, you need some changes in auth.User model and decided to use CustomUser.

Why It Occurs

As per documentation:

Changing AUTH_USER_MODEL after you’ve created database tables is significantly more difficult since it affects foreign keys and many-to-many relationship.

As auth.User is core part of Django, and when you change the tables, it can’t work properly. This problem is hard to identify even for myself, because of the error it throws due to migration.

How to Resolve It

Easy Way

The easiest(and cleanest) way is to delete all your migration files from all apps, drop the database. Then create a new database and connect your project to that. Then run python manage.py makemigrations and python manage.py migrate to make changes in DB. Means to make a clean start for the project, data wise.

Hard Way

Dropping the Database is not always possible for us. So there is an another approach to resolve this issue(based on ticket #25313):

  1. Create a custom user model identical to auth.User, call it User (so many-to-many tables keep the same name) and set db_table='auth_user' (so it uses the same table). Like this:

    from django.contrib.auth.models import AbstractUser
    
    class User(AbstractUser):
        class Meta:
            db_table='auth_user'
  2. Throw away all your migrations from all the apps(except for __init__.py file inside the migrations folder).

  3. Now update value of AUTH_USER_MODEL in your settings.py with myapp.User(Custom User Model) like AUTH_USER_MODEL = 'myapp.User'.

  4. Recreate a fresh set of migrations using python manage.py makemigrations.

  5. Make a backup of your database.

  6. Delete all entries from django_migrations table.

  7. Fake-apply the new set of migrations using python manage.py migrate --fake.

  8. Optional: Set db_table="your_custom_table" or remove it altogether.

  9. Make other changes to the custom model, generate migrations, apply them.

Alternative Way

You can take a different approach rather than overriding you auth.User model. You can define a new model(lets say Profile), and put your relevant fields there. Then, make a OneToOne relation with auth.User like this:

class Profile(models.Model):
    user = models.OneToOneField(User)
    some_field = models.CharField(max_length=255, null=True, blank=True, default=None)

You can use post_save signals to create a profile automatically every time a User is created.

from django.dispatch import receiver
from django.core.signals import post_save
from django.contrib.auth.models import User

@receiver(post_save, sender=User)
def create_user_profile(sender, instance=None, created=None, **kwargs):
    if created:
        Profile.objects.create(user=instance)

You can use this easily in python:

user = User.objects.first()
user.profile.some_field  # Thus you get value of some field from Profile
# OR
profile = Profile.objects.first()
user = profile.user  # Thus you get auth.User

Or in template:

{{ user.profile.some_field }}

FYI: this process was mentioned in the documentation as well.

For Future Projects

For future projects, you should use your CustomUser model from the beginning. It will reduce a lot of hassle when it comes to customizing User.

Conclusion

To Be Honest, this an old problem and there is no good way to resolve it(at least not in Django 2.1). Hopefully in future releases, it will be fixed.

Related Posts:

---

Something wrong? Suggest an improvement or add a comment (see article history)