├── abstract-user-example ├── users │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── urls.py │ ├── apps.py │ ├── views.py │ ├── forms.py │ ├── models.py │ ├── admin.py │ ├── managers.py │ └── tests.py ├── hello_django │ ├── __init__.py │ ├── urls.py │ ├── asgi.py │ ├── wsgi.py │ └── settings.py ├── templates │ ├── _base.html │ ├── signup.html │ ├── registration │ │ └── login.html │ └── home.html └── manage.py ├── abstract-base-user-example ├── users │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── urls.py │ ├── apps.py │ ├── views.py │ ├── forms.py │ ├── models.py │ ├── admin.py │ ├── managers.py │ └── tests.py ├── hello_django │ ├── __init__.py │ ├── urls.py │ ├── asgi.py │ ├── wsgi.py │ └── settings.py ├── templates │ ├── _base.html │ ├── signup.html │ ├── registration │ │ └── login.html │ └── home.html └── manage.py ├── .gitignore ├── README.md └── LICENSE /abstract-user-example/users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /abstract-base-user-example/users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /abstract-user-example/hello_django/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /abstract-base-user-example/hello_django/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /abstract-user-example/users/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /abstract-base-user-example/users/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | env 3 | .DS_Store 4 | .idea 5 | *.sqlite3 6 | -------------------------------------------------------------------------------- /abstract-base-user-example/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | 5 | urlpatterns = [path("signup/", views.SignUp.as_view(), name="signup"), ] 6 | -------------------------------------------------------------------------------- /abstract-user-example/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | 6 | urlpatterns = [path("signup/", views.SignUp.as_view(), name="signup"), ] 7 | -------------------------------------------------------------------------------- /abstract-user-example/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "users" 7 | -------------------------------------------------------------------------------- /abstract-base-user-example/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "users" 7 | -------------------------------------------------------------------------------- /abstract-user-example/templates/_base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Custom User Model 6 | 7 | 8 | {% block content %} 9 | {% endblock %} 10 | 11 | 12 | -------------------------------------------------------------------------------- /abstract-base-user-example/templates/_base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Custom User Model 6 | 7 | 8 | {% block content %} 9 | {% endblock %} 10 | 11 | 12 | -------------------------------------------------------------------------------- /abstract-user-example/templates/signup.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | {% block title %}Sign Up{% endblock %} 4 | 5 | {% block content %} 6 |

Sign Up

7 |
8 | {% csrf_token %} 9 | {{ form.as_p }} 10 | 11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /abstract-base-user-example/templates/signup.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | {% block title %}Sign Up{% endblock %} 4 | 5 | {% block content %} 6 |

Sign Up

7 |
8 | {% csrf_token %} 9 | {{ form.as_p }} 10 | 11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /abstract-user-example/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | {% block title %}Log In{% endblock %} 4 | 5 | {% block content %} 6 |

Log In

7 |
8 | {% csrf_token %} 9 | {{ form.as_p }} 10 | 11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /abstract-base-user-example/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | {% block title %}Log In{% endblock %} 4 | 5 | {% block content %} 6 |

Log In

7 |
8 | {% csrf_token %} 9 | {{ form.as_p }} 10 | 11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /abstract-user-example/users/views.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse_lazy 2 | from django.views import generic 3 | 4 | from .forms import CustomUserCreationForm 5 | 6 | 7 | class SignUp(generic.CreateView): 8 | form_class = CustomUserCreationForm 9 | success_url = reverse_lazy("login") 10 | template_name = "signup.html" 11 | -------------------------------------------------------------------------------- /abstract-base-user-example/users/views.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse_lazy 2 | from django.views import generic 3 | 4 | from .forms import CustomUserCreationForm 5 | 6 | 7 | class SignUp(generic.CreateView): 8 | form_class = CustomUserCreationForm 9 | success_url = reverse_lazy("login") 10 | template_name = "signup.html" 11 | -------------------------------------------------------------------------------- /abstract-user-example/hello_django/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from django.views.generic.base import TemplateView 4 | 5 | urlpatterns = [ 6 | path("", TemplateView.as_view(template_name="home.html"), name="home"), 7 | path("admin/", admin.site.urls), 8 | path("users/", include("users.urls")), 9 | path("users/", include("django.contrib.auth.urls")), 10 | ] 11 | -------------------------------------------------------------------------------- /abstract-base-user-example/hello_django/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from django.views.generic.base import TemplateView 4 | 5 | urlpatterns = [ 6 | path("", TemplateView.as_view(template_name="home.html"), name="home"), 7 | path("admin/", admin.site.urls), 8 | path("users/", include("users.urls")), 9 | path("users/", include("django.contrib.auth.urls")), 10 | ] 11 | -------------------------------------------------------------------------------- /abstract-user-example/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | {% block title %}Home{% endblock %} 4 | 5 | {% block content %} 6 | {% if user.is_authenticated %} 7 | Welcome, {{ user.email }}! 8 |

Log Out

9 | {% else %} 10 |

You are not logged in

11 | Log In | 12 | Sign Up 13 | {% endif %} 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /abstract-base-user-example/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends '_base.html' %} 2 | 3 | {% block title %}Home{% endblock %} 4 | 5 | {% block content %} 6 | {% if user.is_authenticated %} 7 | Welcome, {{ user.email }}! 8 |

Log Out

9 | {% else %} 10 |

You are not logged in

11 | Log In | 12 | Sign Up 13 | {% endif %} 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /abstract-user-example/users/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.forms import UserCreationForm, UserChangeForm 2 | 3 | from .models import CustomUser 4 | 5 | 6 | class CustomUserCreationForm(UserCreationForm): 7 | 8 | class Meta: 9 | model = CustomUser 10 | fields = ("email",) 11 | 12 | 13 | class CustomUserChangeForm(UserChangeForm): 14 | 15 | class Meta: 16 | model = CustomUser 17 | fields = ("email",) 18 | -------------------------------------------------------------------------------- /abstract-base-user-example/users/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.forms import UserCreationForm, UserChangeForm 2 | 3 | from .models import CustomUser 4 | 5 | 6 | class CustomUserCreationForm(UserCreationForm): 7 | 8 | class Meta: 9 | model = CustomUser 10 | fields = ("email",) 11 | 12 | 13 | class CustomUserChangeForm(UserChangeForm): 14 | 15 | class Meta: 16 | model = CustomUser 17 | fields = ("email",) 18 | -------------------------------------------------------------------------------- /abstract-user-example/hello_django/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for hello_django project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hello_django.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /abstract-user-example/hello_django/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for hello_django project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hello_django.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /abstract-base-user-example/hello_django/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for hello_django project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hello_django.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /abstract-base-user-example/hello_django/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for hello_django project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hello_django.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /abstract-user-example/users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | from django.utils.translation import gettext_lazy as _ 4 | 5 | from .managers import CustomUserManager 6 | 7 | 8 | class CustomUser(AbstractUser): 9 | username = None 10 | email = models.EmailField(_("email address"), unique=True) 11 | 12 | USERNAME_FIELD = "email" 13 | REQUIRED_FIELDS = [] 14 | 15 | objects = CustomUserManager() 16 | 17 | def __str__(self): 18 | return self.email 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Creating a Custom User Model in Django 2 | 3 | 1. *How do I fully replace the username field with an email field for Django authentication?* 4 | 1. `AbstractUser` vs `AbstractBaseUser` 5 | 6 | ## Want to learn how to build this? 7 | 8 | Check out the [post](https://testdriven.io/blog/django-custom-user-model/). 9 | 10 | ## Want to use this project? 11 | 12 | 1. Fork/Clone 13 | 14 | 1. Pick one: `AbstractUser` or `AbstractBaseUser` 15 | 16 | 1. Run: 17 | 18 | ```sh 19 | $ python3 -m venv env && source env/bin/activate 20 | (env)$ pip install Django 21 | (env)$ python manage.py makemigrations 22 | (env)$ python manage.py migrate 23 | (env)$ python manage.py runserver 24 | ``` 25 | -------------------------------------------------------------------------------- /abstract-base-user-example/users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin 2 | from django.db import models 3 | from django.utils import timezone 4 | from django.utils.translation import gettext_lazy as _ 5 | 6 | from .managers import CustomUserManager 7 | 8 | 9 | class CustomUser(AbstractBaseUser, PermissionsMixin): 10 | email = models.EmailField(_("email address"), unique=True) 11 | is_staff = models.BooleanField(default=False) 12 | is_active = models.BooleanField(default=True) 13 | date_joined = models.DateTimeField(default=timezone.now) 14 | 15 | USERNAME_FIELD = "email" 16 | REQUIRED_FIELDS = [] 17 | 18 | objects = CustomUserManager() 19 | 20 | def __str__(self): 21 | return self.email 22 | -------------------------------------------------------------------------------- /abstract-user-example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hello_django.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /abstract-base-user-example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hello_django.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /abstract-user-example/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | 4 | from .forms import CustomUserCreationForm, CustomUserChangeForm 5 | from .models import CustomUser 6 | 7 | 8 | class CustomUserAdmin(UserAdmin): 9 | add_form = CustomUserCreationForm 10 | form = CustomUserChangeForm 11 | model = CustomUser 12 | list_display = ("email", "is_staff", "is_active",) 13 | list_filter = ("email", "is_staff", "is_active",) 14 | fieldsets = ( 15 | (None, {"fields": ("email", "password")}), 16 | ("Permissions", {"fields": ("is_staff", "is_active", "groups", "user_permissions")}), 17 | ) 18 | add_fieldsets = ( 19 | (None, { 20 | "classes": ("wide",), 21 | "fields": ( 22 | "email", "password1", "password2", "is_staff", 23 | "is_active", "groups", "user_permissions" 24 | )} 25 | ), 26 | ) 27 | search_fields = ("email",) 28 | ordering = ("email",) 29 | 30 | 31 | admin.site.register(CustomUser, CustomUserAdmin) 32 | -------------------------------------------------------------------------------- /abstract-base-user-example/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | 4 | from .forms import CustomUserCreationForm, CustomUserChangeForm 5 | from .models import CustomUser 6 | 7 | 8 | class CustomUserAdmin(UserAdmin): 9 | add_form = CustomUserCreationForm 10 | form = CustomUserChangeForm 11 | model = CustomUser 12 | list_display = ("email", "is_staff", "is_active",) 13 | list_filter = ("email", "is_staff", "is_active",) 14 | fieldsets = ( 15 | (None, {"fields": ("email", "password")}), 16 | ("Permissions", {"fields": ("is_staff", "is_active", "groups", "user_permissions")}), 17 | ) 18 | add_fieldsets = ( 19 | (None, { 20 | "classes": ("wide",), 21 | "fields": ( 22 | "email", "password1", "password2", "is_staff", 23 | "is_active", "groups", "user_permissions" 24 | )} 25 | ), 26 | ) 27 | search_fields = ("email",) 28 | ordering = ("email",) 29 | 30 | 31 | admin.site.register(CustomUser, CustomUserAdmin) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 TestDriven.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /abstract-user-example/users/managers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.base_user import BaseUserManager 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class CustomUserManager(BaseUserManager): 6 | """ 7 | Custom user model manager where email is the unique identifiers 8 | for authentication instead of usernames. 9 | """ 10 | def create_user(self, email, password, **extra_fields): 11 | """ 12 | Create and save a user with the given email and password. 13 | """ 14 | if not email: 15 | raise ValueError(_("The Email must be set")) 16 | email = self.normalize_email(email) 17 | user = self.model(email=email, **extra_fields) 18 | user.set_password(password) 19 | user.save() 20 | return user 21 | 22 | def create_superuser(self, email, password, **extra_fields): 23 | """ 24 | Create and save a SuperUser with the given email and password. 25 | """ 26 | extra_fields.setdefault("is_staff", True) 27 | extra_fields.setdefault("is_superuser", True) 28 | extra_fields.setdefault("is_active", True) 29 | 30 | if extra_fields.get("is_staff") is not True: 31 | raise ValueError(_("Superuser must have is_staff=True.")) 32 | if extra_fields.get("is_superuser") is not True: 33 | raise ValueError(_("Superuser must have is_superuser=True.")) 34 | return self.create_user(email, password, **extra_fields) 35 | -------------------------------------------------------------------------------- /abstract-base-user-example/users/managers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.base_user import BaseUserManager 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class CustomUserManager(BaseUserManager): 6 | """ 7 | Custom user model manager where email is the unique identifiers 8 | for authentication instead of usernames. 9 | """ 10 | def create_user(self, email, password, **extra_fields): 11 | """ 12 | Create and save a user with the given email and password. 13 | """ 14 | if not email: 15 | raise ValueError(_("The Email must be set")) 16 | email = self.normalize_email(email) 17 | user = self.model(email=email, **extra_fields) 18 | user.set_password(password) 19 | user.save() 20 | return user 21 | 22 | def create_superuser(self, email, password, **extra_fields): 23 | """ 24 | Create and save a SuperUser with the given email and password. 25 | """ 26 | extra_fields.setdefault("is_staff", True) 27 | extra_fields.setdefault("is_superuser", True) 28 | extra_fields.setdefault("is_active", True) 29 | 30 | if extra_fields.get("is_staff") is not True: 31 | raise ValueError(_("Superuser must have is_staff=True.")) 32 | if extra_fields.get("is_superuser") is not True: 33 | raise ValueError(_("Superuser must have is_superuser=True.")) 34 | return self.create_user(email, password, **extra_fields) 35 | -------------------------------------------------------------------------------- /abstract-user-example/users/tests.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.test import TestCase 3 | 4 | 5 | class UsersManagersTests(TestCase): 6 | 7 | def test_create_user(self): 8 | User = get_user_model() 9 | user = User.objects.create_user(email="normal@user.com", password="foo") 10 | self.assertEqual(user.email, "normal@user.com") 11 | self.assertTrue(user.is_active) 12 | self.assertFalse(user.is_staff) 13 | self.assertFalse(user.is_superuser) 14 | try: 15 | # username is None for the AbstractUser option 16 | # username does not exist for the AbstractBaseUser option 17 | self.assertIsNone(user.username) 18 | except AttributeError: 19 | pass 20 | with self.assertRaises(TypeError): 21 | User.objects.create_user() 22 | with self.assertRaises(TypeError): 23 | User.objects.create_user(email="") 24 | with self.assertRaises(ValueError): 25 | User.objects.create_user(email="", password="foo") 26 | 27 | def test_create_superuser(self): 28 | User = get_user_model() 29 | admin_user = User.objects.create_superuser(email="super@user.com", password="foo") 30 | self.assertEqual(admin_user.email, "super@user.com") 31 | self.assertTrue(admin_user.is_active) 32 | self.assertTrue(admin_user.is_staff) 33 | self.assertTrue(admin_user.is_superuser) 34 | try: 35 | # username is None for the AbstractUser option 36 | # username does not exist for the AbstractBaseUser option 37 | self.assertIsNone(admin_user.username) 38 | except AttributeError: 39 | pass 40 | with self.assertRaises(ValueError): 41 | User.objects.create_superuser( 42 | email="super@user.com", password="foo", is_superuser=False) 43 | -------------------------------------------------------------------------------- /abstract-base-user-example/users/tests.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.test import TestCase 3 | 4 | 5 | class UsersManagersTests(TestCase): 6 | 7 | def test_create_user(self): 8 | User = get_user_model() 9 | user = User.objects.create_user(email="normal@user.com", password="foo") 10 | self.assertEqual(user.email, "normal@user.com") 11 | self.assertTrue(user.is_active) 12 | self.assertFalse(user.is_staff) 13 | self.assertFalse(user.is_superuser) 14 | try: 15 | # username is None for the AbstractUser option 16 | # username does not exist for the AbstractBaseUser option 17 | self.assertIsNone(user.username) 18 | except AttributeError: 19 | pass 20 | with self.assertRaises(TypeError): 21 | User.objects.create_user() 22 | with self.assertRaises(TypeError): 23 | User.objects.create_user(email="") 24 | with self.assertRaises(ValueError): 25 | User.objects.create_user(email="", password="foo") 26 | 27 | def test_create_superuser(self): 28 | User = get_user_model() 29 | admin_user = User.objects.create_superuser(email="super@user.com", password="foo") 30 | self.assertEqual(admin_user.email, "super@user.com") 31 | self.assertTrue(admin_user.is_active) 32 | self.assertTrue(admin_user.is_staff) 33 | self.assertTrue(admin_user.is_superuser) 34 | try: 35 | # username is None for the AbstractUser option 36 | # username does not exist for the AbstractBaseUser option 37 | self.assertIsNone(admin_user.username) 38 | except AttributeError: 39 | pass 40 | with self.assertRaises(ValueError): 41 | User.objects.create_superuser( 42 | email="super@user.com", password="foo", is_superuser=False) 43 | -------------------------------------------------------------------------------- /abstract-base-user-example/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.5 on 2023-01-21 21:10 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ("auth", "0012_alter_user_first_name_max_length"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="CustomUser", 18 | fields=[ 19 | ( 20 | "id", 21 | models.BigAutoField( 22 | auto_created=True, 23 | primary_key=True, 24 | serialize=False, 25 | verbose_name="ID", 26 | ), 27 | ), 28 | ("password", models.CharField(max_length=128, verbose_name="password")), 29 | ( 30 | "last_login", 31 | models.DateTimeField( 32 | blank=True, null=True, verbose_name="last login" 33 | ), 34 | ), 35 | ( 36 | "is_superuser", 37 | models.BooleanField( 38 | default=False, 39 | help_text="Designates that this user has all permissions without explicitly assigning them.", 40 | verbose_name="superuser status", 41 | ), 42 | ), 43 | ( 44 | "email", 45 | models.EmailField( 46 | max_length=254, unique=True, verbose_name="email address" 47 | ), 48 | ), 49 | ("is_staff", models.BooleanField(default=False)), 50 | ("is_active", models.BooleanField(default=True)), 51 | ( 52 | "date_joined", 53 | models.DateTimeField(default=django.utils.timezone.now), 54 | ), 55 | ( 56 | "groups", 57 | models.ManyToManyField( 58 | blank=True, 59 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", 60 | related_name="user_set", 61 | related_query_name="user", 62 | to="auth.group", 63 | verbose_name="groups", 64 | ), 65 | ), 66 | ( 67 | "user_permissions", 68 | models.ManyToManyField( 69 | blank=True, 70 | help_text="Specific permissions for this user.", 71 | related_name="user_set", 72 | related_query_name="user", 73 | to="auth.permission", 74 | verbose_name="user permissions", 75 | ), 76 | ), 77 | ], 78 | options={ 79 | "abstract": False, 80 | }, 81 | ), 82 | ] 83 | -------------------------------------------------------------------------------- /abstract-user-example/hello_django/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for hello_django project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.1.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "django-insecure-epsx)bwj9$%c+fdwjfr!bw&4!6#tu+9@-lov*dh1xj@!i)t$z^" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | 41 | "users", 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | "django.middleware.security.SecurityMiddleware", 46 | "django.contrib.sessions.middleware.SessionMiddleware", 47 | "django.middleware.common.CommonMiddleware", 48 | "django.middleware.csrf.CsrfViewMiddleware", 49 | "django.contrib.auth.middleware.AuthenticationMiddleware", 50 | "django.contrib.messages.middleware.MessageMiddleware", 51 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 52 | ] 53 | 54 | ROOT_URLCONF = "hello_django.urls" 55 | 56 | TEMPLATES = [ 57 | { 58 | "BACKEND": "django.template.backends.django.DjangoTemplates", 59 | "DIRS": [ 60 | BASE_DIR / "templates", 61 | ], 62 | "APP_DIRS": True, 63 | "OPTIONS": { 64 | "context_processors": [ 65 | "django.template.context_processors.debug", 66 | "django.template.context_processors.request", 67 | "django.contrib.auth.context_processors.auth", 68 | "django.contrib.messages.context_processors.messages", 69 | ], 70 | }, 71 | }, 72 | ] 73 | 74 | WSGI_APPLICATION = "hello_django.wsgi.application" 75 | 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 79 | 80 | DATABASES = { 81 | "default": { 82 | "ENGINE": "django.db.backends.sqlite3", 83 | "NAME": BASE_DIR / "db.sqlite3", 84 | } 85 | } 86 | 87 | 88 | # Password validation 89 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 90 | 91 | AUTH_PASSWORD_VALIDATORS = [ 92 | { 93 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 94 | }, 95 | { 96 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 97 | }, 98 | { 99 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 100 | }, 101 | { 102 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 103 | }, 104 | ] 105 | 106 | 107 | # Internationalization 108 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 109 | 110 | LANGUAGE_CODE = "en-us" 111 | 112 | TIME_ZONE = "UTC" 113 | 114 | USE_I18N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/4.1/howto/static-files/ 121 | 122 | STATIC_URL = "static/" 123 | 124 | # Default primary key field type 125 | # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field 126 | 127 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 128 | 129 | AUTH_USER_MODEL = "users.CustomUser" 130 | 131 | LOGIN_REDIRECT_URL = "home" 132 | LOGOUT_REDIRECT_URL = "home" 133 | -------------------------------------------------------------------------------- /abstract-base-user-example/hello_django/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for hello_django project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.1.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "django-insecure-#h8#5yy)jsc@sa+r(1t@f^$)(fs(36&q@8l^+&6=c^0r)jk&)4" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | 41 | "users", # new 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | "django.middleware.security.SecurityMiddleware", 46 | "django.contrib.sessions.middleware.SessionMiddleware", 47 | "django.middleware.common.CommonMiddleware", 48 | "django.middleware.csrf.CsrfViewMiddleware", 49 | "django.contrib.auth.middleware.AuthenticationMiddleware", 50 | "django.contrib.messages.middleware.MessageMiddleware", 51 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 52 | ] 53 | 54 | ROOT_URLCONF = "hello_django.urls" 55 | 56 | TEMPLATES = [ 57 | { 58 | "BACKEND": "django.template.backends.django.DjangoTemplates", 59 | "DIRS": [ 60 | BASE_DIR / "templates", 61 | ], 62 | "APP_DIRS": True, 63 | "OPTIONS": { 64 | "context_processors": [ 65 | "django.template.context_processors.debug", 66 | "django.template.context_processors.request", 67 | "django.contrib.auth.context_processors.auth", 68 | "django.contrib.messages.context_processors.messages", 69 | ], 70 | }, 71 | }, 72 | ] 73 | 74 | WSGI_APPLICATION = "hello_django.wsgi.application" 75 | 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 79 | 80 | DATABASES = { 81 | "default": { 82 | "ENGINE": "django.db.backends.sqlite3", 83 | "NAME": BASE_DIR / "db.sqlite3", 84 | } 85 | } 86 | 87 | 88 | # Password validation 89 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 90 | 91 | AUTH_PASSWORD_VALIDATORS = [ 92 | { 93 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 94 | }, 95 | { 96 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 97 | }, 98 | { 99 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 100 | }, 101 | { 102 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 103 | }, 104 | ] 105 | 106 | 107 | # Internationalization 108 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 109 | 110 | LANGUAGE_CODE = "en-us" 111 | 112 | TIME_ZONE = "UTC" 113 | 114 | USE_I18N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/4.1/howto/static-files/ 121 | 122 | STATIC_URL = "static/" 123 | 124 | # Default primary key field type 125 | # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field 126 | 127 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 128 | 129 | AUTH_USER_MODEL = "users.CustomUser" 130 | 131 | LOGIN_REDIRECT_URL = "home" 132 | LOGOUT_REDIRECT_URL = "home" 133 | -------------------------------------------------------------------------------- /abstract-user-example/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1.5 on 2023-01-21 20:38 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ("auth", "0012_alter_user_first_name_max_length"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="CustomUser", 18 | fields=[ 19 | ( 20 | "id", 21 | models.BigAutoField( 22 | auto_created=True, 23 | primary_key=True, 24 | serialize=False, 25 | verbose_name="ID", 26 | ), 27 | ), 28 | ("password", models.CharField(max_length=128, verbose_name="password")), 29 | ( 30 | "last_login", 31 | models.DateTimeField( 32 | blank=True, null=True, verbose_name="last login" 33 | ), 34 | ), 35 | ( 36 | "is_superuser", 37 | models.BooleanField( 38 | default=False, 39 | help_text="Designates that this user has all permissions without explicitly assigning them.", 40 | verbose_name="superuser status", 41 | ), 42 | ), 43 | ( 44 | "first_name", 45 | models.CharField( 46 | blank=True, max_length=150, verbose_name="first name" 47 | ), 48 | ), 49 | ( 50 | "last_name", 51 | models.CharField( 52 | blank=True, max_length=150, verbose_name="last name" 53 | ), 54 | ), 55 | ( 56 | "is_staff", 57 | models.BooleanField( 58 | default=False, 59 | help_text="Designates whether the user can log into this admin site.", 60 | verbose_name="staff status", 61 | ), 62 | ), 63 | ( 64 | "is_active", 65 | models.BooleanField( 66 | default=True, 67 | help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", 68 | verbose_name="active", 69 | ), 70 | ), 71 | ( 72 | "date_joined", 73 | models.DateTimeField( 74 | default=django.utils.timezone.now, verbose_name="date joined" 75 | ), 76 | ), 77 | ( 78 | "email", 79 | models.EmailField( 80 | max_length=254, unique=True, verbose_name="email address" 81 | ), 82 | ), 83 | ( 84 | "groups", 85 | models.ManyToManyField( 86 | blank=True, 87 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", 88 | related_name="user_set", 89 | related_query_name="user", 90 | to="auth.group", 91 | verbose_name="groups", 92 | ), 93 | ), 94 | ( 95 | "user_permissions", 96 | models.ManyToManyField( 97 | blank=True, 98 | help_text="Specific permissions for this user.", 99 | related_name="user_set", 100 | related_query_name="user", 101 | to="auth.permission", 102 | verbose_name="user permissions", 103 | ), 104 | ), 105 | ], 106 | options={ 107 | "verbose_name": "user", 108 | "verbose_name_plural": "users", 109 | "abstract": False, 110 | }, 111 | ), 112 | ] 113 | --------------------------------------------------------------------------------