فصل ۱: شروع درست — معماری، ساختار پروژه و Custom User

Custom User از روز اول: ورود با شماره‌ی موبایل

مهم‌ترین تصمیم قبل از اولین 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ها و شروع دوباره است؛ مهاجرت درجا ممکن است اما چند مرحله‌ی دستی و پرخطر دارد.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.