From 41f9be4c7e3189e6744c5cca120a8116262ea6e8 Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Thu, 11 Jun 2026 21:20:59 +0330 Subject: [PATCH] fix(users): require mobile for superuser creation --- apps/users/admin.py | 14 +++++++---- .../migrations/0008_alter_user_managers.py | 18 ++++++++++++++ apps/users/models.py | 24 +++++++++++++++++-- 3 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 apps/users/migrations/0008_alter_user_managers.py diff --git a/apps/users/admin.py b/apps/users/admin.py index 5357240..a245054 100644 --- a/apps/users/admin.py +++ b/apps/users/admin.py @@ -14,6 +14,7 @@ from core.admin import SoftDeleteListFilter, BaseModelAdmin class UserAdminForm(forms.ModelForm): + mobile = forms.CharField(required=True) bio = forms.CharField(widget=SimpleMDEEditor(), required=False) student_id = forms.CharField(required=False) @@ -25,13 +26,13 @@ class UserAdminForm(forms.ModelForm): class UserAdmin(BaseUserAdmin, BaseModelAdmin, ImportExportModelAdmin): form = UserAdminForm resource_class = UserResource - list_display = ('email', 'username', 'university', 'is_email_verified', 'date_joined') - list_filter = ('is_email_verified', 'is_staff', 'year_of_study', SoftDeleteListFilter) - search_fields = ('email', 'username', 'student_id', 'first_name', 'last_name') + list_display = ('email', 'mobile', 'username', 'university', 'is_email_verified', 'is_mobile_verified', 'date_joined') + list_filter = ('is_email_verified', 'is_mobile_verified', 'is_staff', 'year_of_study', SoftDeleteListFilter) + search_fields = ('email', 'mobile', 'username', 'student_id', 'first_name', 'last_name') ordering = ('-date_joined',) fieldsets = ( - ('Auth Credentials', {'fields': ('username', 'email', 'password')}), + ('Auth Credentials', {'fields': ('username', 'email', 'mobile', 'password')}), ('Personal info', { 'fields': ('first_name', 'last_name', 'student_id', 'university', 'year_of_study', 'major', 'bio', 'profile_picture') }), @@ -43,6 +44,9 @@ class UserAdmin(BaseUserAdmin, BaseModelAdmin, ImportExportModelAdmin): ('Email Verification', { 'fields': ('is_email_verified', 'email_verification_token', 'email_verification_sent_at') }), + ('Mobile Verification', { + 'fields': ('is_mobile_verified',) + }), ('Password Reset', { 'fields': ('password_reset_token', 'password_reset_token_expires_at'), 'classes': ('collapse',) @@ -57,7 +61,7 @@ class UserAdmin(BaseUserAdmin, BaseModelAdmin, ImportExportModelAdmin): 'Step 1', { 'classes': ('wide',), - 'fields': ('email', 'student_id', 'password1', 'password2', 'usable_password'), + 'fields': ('email', 'mobile', 'student_id', 'password1', 'password2', 'usable_password'), }, ), ) diff --git a/apps/users/migrations/0008_alter_user_managers.py b/apps/users/migrations/0008_alter_user_managers.py new file mode 100644 index 0000000..35c2aae --- /dev/null +++ b/apps/users/migrations/0008_alter_user_managers.py @@ -0,0 +1,18 @@ +import apps.users.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("users", "0007_user_is_mobile_verified_user_mobile_alter_user_email_and_more"), + ] + + operations = [ + migrations.AlterModelManagers( + name="user", + managers=[ + ("objects", apps.users.models.UserManager()), + ], + ), + ] diff --git a/apps/users/models.py b/apps/users/models.py index fd6eb53..7315df8 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -1,4 +1,4 @@ -from django.contrib.auth.models import AbstractUser +from django.contrib.auth.models import AbstractUser, UserManager as DjangoUserManager from django.utils import timezone from django.db import models @@ -14,6 +14,24 @@ from core.models import BaseModel from apps.users.email_identity import normalize_email_identity, normalize_mobile_number +class UserManager(DjangoUserManager): + def _normalize_required_mobile(self, mobile): + normalized = normalize_mobile_number(mobile) + if not normalized: + raise ValueError("The mobile number must be set") + return normalized + + def create_user(self, username, email=None, password=None, **extra_fields): + extra_fields["mobile"] = self._normalize_required_mobile(extra_fields.get("mobile")) + return super().create_user(username, email=email, password=password, **extra_fields) + + def create_superuser(self, username, email=None, password=None, **extra_fields): + extra_fields["mobile"] = self._normalize_required_mobile(extra_fields.get("mobile")) + extra_fields.setdefault("is_active", True) + extra_fields.setdefault("is_mobile_verified", True) + return super().create_superuser(username, email=email, password=password, **extra_fields) + + class University(BaseModel): code = models.CharField(max_length=64, unique=True) name = models.CharField(max_length=255) @@ -69,7 +87,9 @@ class User(AbstractUser, BaseModel): password_reset_token_expires_at = models.DateTimeField(null=True, blank=True) USERNAME_FIELD = 'username' - REQUIRED_FIELDS = [] + REQUIRED_FIELDS = ['mobile'] + + objects = UserManager() class Meta: db_table = 'users'