The recording for this lesson has not been published yet.
In the tutorial your Post model points at Django's built-in user:
# blog/models.py (from the tutorial)
from django.conf import settings
from django.db import models
class Post(models.Model):
author = models.ForeignKey("auth.User", on_delete=models.CASCADE)
title = models.CharField(max_length=200)
text = models.TextField()
That works, and it will keep working - until the day you need one extra field on the user.
A phone number, a "has accepted the terms" flag, a preferred language, or the very common
wish to log in with an email address instead of a username. The built-in
django.contrib.auth.models.User is a normal concrete model living in an app
you do not own, so you cannot add a field to it.
The official recommendation is therefore blunt: start every project with your own user model, even if it is empty. It costs you five minutes now and saves a painful migration later.
Changing AUTH_USER_MODEL after you have run migrate is not a
normal schema change. Every foreign key to the user, plus the permission and session
tables, points at the old table. Section 4 explains what to do when you are already
past that point.
Django gives you two starting points. They differ in how much you inherit and how much you have to write yourself.
AbstractUser | AbstractBaseUser | |
|---|---|---|
| What you get | username, first_name, last_name, email, is_staff, is_active, date_joined, groups, permissions | password, last_login and the password hashing machinery - nothing else |
| What you must write | only your extra fields | every field, USERNAME_FIELD, REQUIRED_FIELDS, a manager, and usually PermissionsMixin |
| Admin and auth forms | work out of the box | need custom forms |
| Use it when | you are happy with a username plus extras (almost always) | you truly need a different identity model, for example email-only with no username |
The user belongs in its own app. Create accounts and put a single, almost
empty model in it:
python manage.py startapp accounts
# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
bio = models.TextField(blank=True)
website = models.URLField(blank=True)
def __str__(self):
return self.get_username()
Register the app and point the setting at the model using the
app_label.ModelName form - never an import:
# mysite/settings.py
INSTALLED_APPS = [
...
"accounts.apps.AccountsConfig",
"blog.apps.BlogConfig",
]
AUTH_USER_MODEL = "accounts.User"
From now on, never import User directly in your own code. There are two
correct ways to refer to it, and which one you use depends on when the code runs.
| Where | Use | Why |
|---|---|---|
| Model fields | settings.AUTH_USER_MODEL (a string) |
Models are imported at load time; a string avoids a circular import |
| Views, forms, tests, management commands | get_user_model() |
Resolved at call time, when the app registry is ready |
# blog/models.py
from django.conf import settings
from django.db import models
class Post(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="posts",
)
...
# blog/views.py
from django.contrib.auth import get_user_model
User = get_user_model() # fine at module level in a view module
def author_list(request):
authors = User.objects.filter(posts__isnull=False).distinct()
...
In views and templates you already have the current user as
request.user. get_user_model() is for queries, not for
reading the logged-in user.
If your blog is fresh, delete db.sqlite3 and the migration files of your own
apps, then run makemigrations and migrate once. Done.
If the blog is on PythonAnywhere with posts you care about, you have three honest options.
python manage.py dumpdata blog --indent 2 > posts.json
# rebuild the database with the new user model, recreate the users, then
python manage.py loaddata posts.json
You will have to fix the author ids in the JSON by hand, because the new
user table starts numbering from 1 again.auth.User and put your
extra fields in a Profile model with a OneToOneField. That is
lesson 4.4, and it is a perfectly respectable answer for an existing project.db_table = "auth_user", then use
migrations.SeparateDatabaseAndState so Django's state changes while the
table stays. You also have to repoint django_admin_log,
auth_user_groups and auth_user_user_permissions. It is
documented in tickets and blog posts rather than in the official docs, and it is easy to
get wrong. Only attempt it on a branch, against a copy of the production database.
ValueError: The field blog.Post.author was declared with a lazy reference to
'accounts.user', but app 'accounts' isn't installed - or a migration that wants
to delete auth_user. Stop and read section 4 again instead of forcing it.
Swapping the model unregisters the nice admin page you used in the tutorial. Reuse
Django's UserAdmin and only extend the fieldsets:
# accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import User
@admin.register(User)
class UserAdmin(BaseUserAdmin):
list_display = ("username", "email", "is_staff", "date_joined")
fieldsets = BaseUserAdmin.fieldsets + (
("Blog profile", {"fields": ("bio", "website")}),
)
add_fieldsets = BaseUserAdmin.add_fieldsets + (
("Blog profile", {"fields": ("bio", "website")}),
)
If you write your own forms, subclass the auth ones so password hashing keeps working:
# accounts/forms.py
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from .models import User
class UserCreateForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = User
fields = ("username", "email")
class UserEditForm(UserChangeForm):
class Meta(UserChangeForm.Meta):
model = User
Finally, prove it works:
python manage.py makemigrations accounts
python manage.py migrate
python manage.py createsuperuser
python manage.py shell -c "from django.contrib.auth import get_user_model; print(get_user_model())"
<class 'accounts.models.User'>
accounts app with a User model based on
AbstractUser and set AUTH_USER_MODEL before your first
migrate.Post.author to use settings.AUTH_USER_MODEL and add
related_name="posts". Check the generated migration with
sqlmigrate.from django.contrib.auth.models import User and
replace every hit with get_user_model().pbkdf2_sha256$).get_user_model()._meta.label == "accounts.User",
so a future settings mistake fails loudly.