مهمترین تصمیم قبل از اولین migrate
مستندات رسمی جنگو صریحاً توصیه میکند در هر پروژهی تازه، حتی اگر فعلاً به فیلد اضافهای نیاز ندارید، یک مدل کاربر اختصاصی بسازید. دلیلش ساده است: وقتی چند جدول با ForeignKey به auth.User اشاره کردند و دادهی واقعی وارد شد، عوض کردن مدل کاربر یکی از دردناکترین مهاجرتهاست. در ایران هم تقریباً همهی سامانهها ورود با موبایل میخواهند؛ پس از همان روز اول mobile را شناسهی کاربر میکنیم.
AbstractUser یا AbstractBaseUser؟
| پایه | چه میدهد | کِی |
|---|---|---|
| AbstractUser | همهی فیلدهای User استاندارد (نام، ایمیل، is_staff، گروهها) | بیشتر پروژهها؛ فقط شناسه را عوض میکنیم |
| AbstractBaseUser + PermissionsMixin | فقط رمز و last_login؛ بقیه را خودتان تعریف میکنید | وقتی ساختار کاملاً متفاوت میخواهید |
پیادهسازی با AbstractUser
# apps/accounts/models.py
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.core.validators import RegexValidator
from django.db import models
mobile_validator = RegexValidator(r"^09\d{9}$", "شمارهی موبایل باید ۱۱ رقم و با 09 شروع شود.")
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, mobile, password, **extra):
if not mobile:
raise ValueError("شمارهی موبایل الزامی است.")
user = self.model(mobile=mobile, **extra)
user.set_password(password) # password=None یعنی رمز غیرقابلاستفاده (ورود فقط با OTP)
user.save(using=self._db)
return user
def create_user(self, mobile, password=None, **extra):
extra.setdefault("is_staff", False)
extra.setdefault("is_superuser", False)
return self._create_user(mobile, password, **extra)
def create_superuser(self, mobile, password, **extra):
extra.update(is_staff=True, is_superuser=True)
return self._create_user(mobile, password, **extra)
class User(AbstractUser):
username = None # فیلد username حذف میشود
mobile = models.CharField("موبایل", max_length=11, unique=True, validators=[mobile_validator])
USERNAME_FIELD = "mobile"
REQUIRED_FIELDS = [] # createsuperuser جز موبایل و رمز چیزی نمیپرسد
objects = UserManager()
def __str__(self):
return self.get_full_name() or self.mobile
در settings: AUTH_USER_MODEL = "accounts.User"، سپس makemigrations accounts و بعد migrate. ترتیب مهم است: این باید قبل از اولین migrate پروژه انجام شود.
ارجاع درست به مدل کاربر
# در models.py: رشته از settings
from django.conf import settings
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
# در view، فرم و سرویس: خود کلاس
from django.contrib.auth import get_user_model
User = get_user_model()
برای ادمین هم باید از UserAdmin ارث ببرید و ordering، fieldsets و add_fieldsets را بازنویسی کنید، چون نسخهی پیشفرض آنها به username اشاره میکند و ادمین با خطای «unknown field» بالا نمیآید.
نکتههایی که کمتر کسی میداند
username = Noneدر زیرکلاس AbstractUser فیلد را واقعاً از مدل حذف میکند؛ ترفندی کوتاه که نیاز به نوشتن کل مدل از صفر را برطرف میکند.set_password(None)رمز «غیرقابلاستفاده» ثبت میکند وhas_usable_password()برایش False است؛ مناسب کاربرانی که فقط با پیامک وارد میشوند.- موبایل را قبل از ذخیره نرمال کنید (ارقام فارسی، +98، فاصله)؛ وگرنه «۰۹۱۲…» و «0912…» دو کاربر جدا میشوند و unique هم جلویش را نمیگیرد.
- در فایل models هرگز
get_user_model()را در سطح ماژول صدا نزنید؛ ممکن است قبل از بارگذاری اپها اجرا شود. در ForeignKey همیشهsettings.AUTH_USER_MODEL. - اگر پروژه قبلاً با auth.User migrate شده و هنوز دادهی مهمی ندارد، سادهترین راه پاک کردن پایگاه داده و migrationها و شروع دوباره است؛ مهاجرت درجا ممکن است اما چند مرحلهی دستی و پرخطر دارد.