ساخت مدل User سفارشی در Django

یکشنبه 2 خرداد 1400

این مقاله به صورت گام به گام توضیح می‌دهد چطور یک User model سفارشی در جنگو بسازیم تا از آدرس ایمیل به عنوان شناسه اصلی کاربر به جای نام کاربری برای احراز هویت استفاده شود.

ساخت مدل User سفارشی در Django

در پایان این مقاله شما قادر خواهید بود:

1. تفاوت بین AbstractUser و AbstractBaseUser را توصیف کنید.

2. توضیح دهید که چرا هنگام شروع پروژه جنگو جدید باید یک مدل سفارشی User را تنظیم کنیم.

3. یک پروژه جدید جنگو را با یک مدل User سفارشی شروع کنید.

4. به جای نام کاربری برای احراز هویت، از آدرس ایمیل به عنوان شناسه اصلی کاربر استفاده کنید.

5. هنگام پیاده سازی یک مدل User سفارشی، توسعه test-first را تمرین کنید.

مقایسه AbstractUser و AbstractUser

مدیل User پیش فرض در جنگو از نام کاربری (username) برای شناسایی منحصر به فرد بودن کاربر در هنگام احراز هویت استفاده می‌کند. اگر ترجیح می دهید از آدرس ایمیل استفاده کنید، باید یک مدل User سفارشی با ساب کلاس کردن AbstractUser یا AbstractBaseUser بسازید.

گزینه ها:

AbstractUser: اگر از فیلدهای موجود در مدل User راضی هستید و فقط می‌خواهید فیلد username را حذف کنید، از این مورد استفاده کنید.

AbstractBaseUser: اگر می‌خواهید با ایجاد یک مدل User کاملا جدید خود، از ابتدا شروع کنید از این گزینه استفاده کنید.

ما در این مقاله به هر دو مورد خواهیم پرداخت.

این مراحل برای هر دو یکسان هستند:

1. یک مدل User سفارشی و Manager ایجاد کنید.

2. settings.py را آپدیت کنید.

3. فرم های UserCreationForm و UserChangeForm را سفارشی کنید.

4. ادمین را آپدیت کنید.

بسیار توصیه می‌کنیم تا هنگام شروع پروژه جدید جنگو، یک مدل User سفارشی بسازید. بدون آن، اگر می‌خواهید فیلدهای جدید به User model اضافه کنید، باید مدل دیگری بسازید (مثل UserProfile) و آن را با OneToOneField (فیلد یک به یک) به مدل User جنگو لینک دهید.

راه اندازی پروژه

با ایجاد یک پروژه Django جدید همراه با users app شروع کنید:

$ mkdir django-custom-user-model && cd django-custom-user-model
$ python3 -m venv env
$ source env/bin/activate

(env)$ pip install Django==3.2.2
(env)$ django-admin startproject hello_django .
(env)$ python manage.py startapp users

مایگریشن ها را اعمال نکنید. به خاطر داشته باشید قبل از اعمال اولین مایگریشن، باید مدل User سفارشی را بسازید.

برنامه جدید را به لیست INSTALLED_APPS در settings.py اضافه کنید:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'users',
]

تست ها

بیاید یک رویکرد test-first را انجام دهیم:

from django.contrib.auth import get_user_model
from django.test import TestCase


class UsersManagersTests(TestCase):

    def test_create_user(self):
        User = get_user_model()
        user = User.objects.create_user(email='normal@user.com', password='foo')
        self.assertEqual(user.email, 'normal@user.com')
        self.assertTrue(user.is_active)
        self.assertFalse(user.is_staff)
        self.assertFalse(user.is_superuser)
        try:
            # username is None for the AbstractUser option
            # username does not exist for the AbstractBaseUser option
            self.assertIsNone(user.username)
        except AttributeError:
            pass
        with self.assertRaises(TypeError):
            User.objects.create_user()
        with self.assertRaises(TypeError):
            User.objects.create_user(email='')
        with self.assertRaises(ValueError):
            User.objects.create_user(email='', password="foo")

    def test_create_superuser(self):
        User = get_user_model()
        admin_user = User.objects.create_superuser(email='super@user.com', password='foo')
        self.assertEqual(admin_user.email, 'super@user.com')
        self.assertTrue(admin_user.is_active)
        self.assertTrue(admin_user.is_staff)
        self.assertTrue(admin_user.is_superuser)
        try:
            # username is None for the AbstractUser option
            # username does not exist for the AbstractBaseUser option
            self.assertIsNone(admin_user.username)
        except AttributeError:
            pass
        with self.assertRaises(ValueError):
            User.objects.create_superuser(
                email='super@user.com', password='foo', is_superuser=False)

مشخصات را به users/tests.py اضافه کنید، و سپس مطمئن شوید که تست ها شکست خورده‌اند.

Model Manager

ابتدا باید با subclass کردن BaseUserManager یک Manager سفارشی اضافه کنیم که به جای username از ایمیل به عنوان شناسه منحصر به فرد استفاده کند.

یک فایل managers.py در پوشه " users" بسازید:

from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import ugettext_lazy as _


class CustomUserManager(BaseUserManager):
    """
    Custom user model manager where email is the unique identifiers
    for authentication instead of usernames.
    """
    def create_user(self, email, password, **extra_fields):
        """
        Create and save a User with the given email and password.
        """
        if not email:
            raise ValueError(_('The Email must be set'))
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        """
        Create and save a SuperUser with the given email and password.
        """
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError(_('Superuser must have is_staff=True.'))
        if extra_fields.get('is_superuser') is not True:
            raise ValueError(_('Superuser must have is_superuser=True.'))
        return self.create_user(email, password, **extra_fields)

User Model

تصمیم بگیرید از کدام گزینه می‌خواهید استفاده کنید: subclass کردن AbstractUser یا AbstractBaseUser.

AbstractUser

users/models.py را آپدیت کنید:

from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import ugettext_lazy as _

from .managers import CustomUserManager


class CustomUser(AbstractUser):
    username = None
    email = models.EmailField(_('email address'), unique=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.email

در اینجا ما:

یک کلاس جدید به نام CustomUser ساختیم که AbstractUser را subclass می‌کند.

فیلد username را حذف کردیم.

فیلد ایمیل را required و یونیک ساختیم.

USERNAME_FIELD را، که شناسه منحصر به فرد را برای مدل User تعریف می‌کند، با email ست کردیم.

مشخص شده که همه آبجکت ‌های کلاس از CustomUserManager آمده‌اند.

AbstractBaseUser

users/models.py را آپدیت کنید:

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from .managers import CustomUserManager


class CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_('email address'), unique=True)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    date_joined = models.DateTimeField(default=timezone.now)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.email

در اینجا ما:

یک کلاس جدید به نام CustomUser ساختیم که AbstractBaseUser را ساب کلاس می‌کند.

فیلدها را برای email، is_staff، is_active و date_joined اضافه کرده ایم.

USERNAME_FIELD را، که شناسه منحصر به فرد کاربر را تعریف می‌کند، با email ست کرده ایم.

مشخص شده است که همه آبجکت های کلاس از CustomUserManager آمده اند.

تنظیمات

خط زیر را به فایل settings.py اضافه کنید تا جنگو بداند از کلاس User جدید اضافه کند:

AUTH_USER_MODEL = 'users.CustomUser'

حالا می‌توانید مایگریشن‌ها را ساخته و اعمال کنید، که یک دیتابیس جدید را می‌سازد که از User model سفارشی استفاده می‌کند. قبل از انجام این کار، بیاید بدون ایجاد فایل مایگریشن، با --dry-run flag، ببینیم که مایگریشن در واقع چگونه خواهد بود:

(env)$ python manage.py makemigrations --dry-run --verbosity 3

باید چیزی شبیه به این را ببینید:

# Generated by Django 3.2.2 on 2021-05-12 20:43

from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

    initial = True

    dependencies = [
        ('auth', '0012_alter_user_first_name_max_length'),
    ]

    operations = [
        migrations.CreateModel(
            name='CustomUser',
            fields=[
                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('password', models.CharField(max_length=128, verbose_name='password')),
                ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
                ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
                ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
                ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')),
                ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
                ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
            ],
            options={
                'verbose_name': 'user',
                'verbose_name_plural': 'users',
                'abstract': False,
            },
        ),
    ]

مطمئن شوید که مایگریشن شامل فیلد username نباشد، سپس مایگریشن را ایجاد و اعمال کنید:

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate

نمایی از schema:

$ sqlite3 db.sqlite3

SQLite version 3.28.0 2019-04-15 14:49:49
Enter ".help" for usage hints.

sqlite> .tables

auth_group                         django_migrations
auth_group_permissions             django_session
auth_permission                    users_customuser
django_admin_log                   users_customuser_groups
django_content_type                users_customuser_user_permissions

sqlite> .schema users_customuser

CREATE TABLE IF NOT EXISTS "users_customuser" (
  "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
  "password" varchar(128) NOT NULL,
  "last_login" datetime NULL,
  "is_superuser" bool NOT NULL,
  "first_name" varchar(150) NOT NULL,
  "last_name" varchar(150) NOT NULL,
  "is_staff" bool NOT NULL,
  "is_active" bool NOT NULL,
  "date_joined" datetime NOT NULL,
  "email" varchar(254) NOT NULL UNIQUE
);

اکنون می‌توانید با get_user_model() یا settings.AUTH_USER_MODEL به مدل User مراجعه کنید. برای اطلاعات بیشتر می‌توانید به مستندات رسمی User model مراجعه کنید.

همچنین هنگام ایجاد یک سوپر یوزر، از شما خواسته می‌شود به جای نام کاربری ایمیل را وارد کنید:

(env)$ python manage.py createsuperuser

Email address: test@test.com
Password:
Password (again):
Superuser created successfully.

مطمئن شوید که تست ها پاس شده‌اند:

(env)$ python manage.py test

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.282s

OK
Destroying test database for alias 'default'...

Forms

در مرحله بعدی، بیاید فرم های UserCreationForm و UserChangeForm را ساب کلاس کنیم تا آن‌ها از مدل جدید CustomUser استفاده کنند.

یک فایل جدید در " users" به نام forms.py بسازید:

from django.contrib.auth.forms import UserCreationForm, UserChangeForm

from .models import CustomUser


class CustomUserCreationForm(UserCreationForm):

    class Meta:
        model = CustomUser
        fields = ('email',)


class CustomUserChangeForm(UserChangeForm):

    class Meta:
        model = CustomUser
        fields = ('email',)

Admin

به ادمین بگویید تا با ساب کلاس کردن UserAdmin در users/admin.py از این فرم ها استفاده کند:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser


class CustomUserAdmin(UserAdmin):
    add_form = CustomUserCreationForm
    form = CustomUserChangeForm
    model = CustomUser
    list_display = ('email', 'is_staff', 'is_active',)
    list_filter = ('email', 'is_staff', 'is_active',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Permissions', {'fields': ('is_staff', 'is_active')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active')}
        ),
    )
    search_fields = ('email',)
    ordering = ('email',)


admin.site.register(CustomUser, CustomUserAdmin)

به این صورت. سرور را اجرا کرده و وارد سایت ادمین شوید. باید بتوانید مانند حالت عادی کاربران را اضافه کرده و تغییر دهید.

جمع بندی

در این مقاله ما نحوه ایجاد یک مدل سفارش User را بررسی کردیم تا از آدرس ایمیل به عنوان شناسه اصلی کاربر به جای نام کاربری برای احراز هویت استفاده شود.

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان
  • Python
  • 4k بازدید
  • 2 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید