Late switch to Custom User Model always hurts?

The official Django documentation “highly recommends” implementing a custom user model and William S. Vincent says in “Django for Professionals”:

If you have not started with a custom user model from the very first
migrate command you run, then you’re in for a world of hurt because
User is tightly interwoven with the rest of Django internally. It is
challenging to switch over to a custom user model mid-project.

Unfortunately, I saw these recommendations too late and now I wonder, if this pain is always the case or only if you already make use of users somehow. In my Django project, there is only one single user and none of my models are related to users in any kind (which I am planning to change soon).

Is there any hope that it wouldn’t get that terrible? This extensive guide, for example, has the following assumptions:

  1. You have an existing project without a custom user model.
  2. You’re using Django’s migrations, and all migrations are up-to-date (and
    have been applied to the production database).
  3. You have an existing set of users that you need to keep, and any number of models that point to Django’s built-in User model.

Actually, I do not need the migrations as I am the only developer and no one else has my code yet. But there is already content in the database which I want to keep.

Under these circumstances, can I simply follow the easy steps as if I would switch to a custom user model from the very beginning?

P. S.: I recently posted the very same question on Stack Overflow and got no answer. So I hope it is OK to try it here again.

Hi @ralfzosel.

It sounds as if you’re at early days, so you could dumpdata for that that you want to keep. Start with a fresh custom user model, and then import you data again, making the few adjustments you might need to you exported data. From what you’ve described, I imagine that would be easier than trying migrate to a custom user model, which is a bit of a pain. (If you’ve not got to migrate a live DB, I’m not sure it’s that hard—follow the steps—but…)

I have to say though, do you really need a custom user model? I never use one. (I have a profile model with a one-to-one field to the default user model, and I annotate the username field as extra info when fetching, which is the only one I need from the base model in 90% of cases.)

I know the docs say it’s recommended, but I don’t really know why. It’s fine to have a custom user model, but I’ve lost count of the number of times I’ve seen this core auth model, which should be small and targeted, become a dumping ground for every Oh I just need one more field as the project evolves. For me it’s a complexity you’re better off without.

I know not everyone agrees with that, in fact these days it’s probably the minority view, but before you take on a bunch of work, consider that you can just live with the default model, and you will in fact be fine.
You could spend all the time you’d be working on your custom model on other bits of your app instead.

I hope that helps, or gives you another view to think about at least.

Kind Regards,

Carlton

2 Likes

From what was described about the app’s scenario, it sounds like this is about as well positioned to switching to a custom user model as I’d ever hear.

Considering that django.contrib.auth.models.User is a subclass of AbstractUser, you might be able to get away with setting a custom user class with the db table name of the existing User table (using Meta.db_table).

There may be some gotchas in there that I’m not aware of. I could see registered content types and auto generated permissions for the user model as being likely stumbling blocks.

I’ve not tried this strategy myself so I’d exercise caution.

Yes! I agree with Matt there — before finally deploying, and letting others loose on the code is the time if you can (and you want to).

Hi @ralfzosel,

I am in the exact same situation right now. I read the documentation to late. At the same time I will change from MySQL to PostgreSQL. Unfortunatly there are dependancies between the Django user model and at least one of my apps. My solution is to make a new project. Create the user model/authentication logic with a PostgreSQL_new database. Then I will make a new PostgreSQL_old database alongside the MySQL_old database. I will then dump all tables/models from the MySQL_old that are not related to Users, load the data in the PostgreSQL_old, and dump it from there. Then I will evaluate the PostgreSQL_old so it has no User model traces left, and lad the data into the PostgreSQL_new database. Maybe the trick with the PostgreSQL_old is too much, but I will not risk having som left over model data in the new, hopefully clean PostgreSQL_new database.

Wish you all the best.

Thanks a lot for your insights into your considerations. The only example of the necessity of a custom user model seems to be when someone wants to log in with the email address instead of the username. I am not sure if I ever want to implement this, but who knows?

As this is my first Django project and I am eager to learn as much as I can, I will try this.

So first I will make a dump of my database and then I will make sure that I am able to restore it, just in case. As soon as this works, I will try to change the user model and I will let you know how it worked.

Meanwhile I followed @wsvincent’s step by step guide for switching to custom user model, which worked perfectly. :+1:

I did this from scratch, meaning with a new, empty database. @mblayman, your idea sounds interesting, but your caveats discouraged me to try this.

Now, I am obviously at the point that you, @carltongibson, mentioned earlier, and I have to do “the few adjustments to my exported data” now. Well, actually I already did some adjustments with the export itself and excluded – on top to the usual stuff – also “auth.user” like that:

python manage.py dumpdata --exclude auth.permission --exclude contenttypes --exclude auth.user > db.json

Now python manage.py loaddata db.json still leads to some errors, but I obviously simply confused the database somehow. I will sort that out and come back soon.

OK, I still get this error:

django.db.utils.IntegrityError: Problem installing fixtures: The row in table 'django_admin_log' with primary key '398' has an invalid foreign key: django_admin_log.content_type_id contains a value '24' that does not have a corresponding value in django_content_type.id.

I think the problem might be this:

When I started from scratch now, I got a “clean” django_content_type table (with 23 entries) while the original database contains 31 entries including some which aren’t in use anymore. There are some with app_label “dummy” for instance as a remnant from earlier tests with a dummy app which I have removed meanwhile.

Probably the ‘django_admin_log’ still refers to these and I assume, I actually simply could remove this old log without too big a loss. If I am right – how could I exclude this log from the import? Or do you have other ideas?

Couple different ways to do this.

For a quick and immediate fix (if this is a one-time shot), you can use the exclude option on your loaddata command.

For a more permanent solution, your db.json file is an editable text file. You could find all the entries that are loading data into the log file and remove them.

You could also redo your export to exclude that model to prevent it from being written.

Or, you could empty that table in the database before doing your export.

I chose the option you mentioned last and emptied the database:

postgres=# TRUNCATE django_admin_log;

Another export … and finally success:

% python manage.py loaddata db.json
Installed 22190 object(s) from 1 fixture(s)

:champagne:

Thank you all for your help! I would have been lost without you. In the end, I would say: yes, late switch to Custom User Model is a pain, but I have learned a lot from that.

2 Likes

Happy for you :slight_smile: I did the same routine as you, I think. I started a new clean django implementation with a new User Model as advised by the community. Then I copied my models from the old project to the new, and ran migrations. I am in the process of dumping/loading data which came to a stop when I ran into to a couple of string conversion type errors.

First the problem of varchar fields allowed to be NULL, which DJANGO loaddata did not exept.
The fix was:

UPDATE <table> SET columns = "" WHERE column IS NULL;

Then another sting/text problem with a mySQL mediumtext field. Django: “A string literal cannot contain NUL (0x00) characters.”

UPDATE SET column = replace(column, chr(0), “”)

Now I will try again to lad the data… :slight_smile: