ساخت مدل User سفارشی در Django
یکشنبه 2 خرداد 1400این مقاله به صورت گام به گام توضیح میدهد چطور یک User model سفارشی در جنگو بسازیم تا از آدرس ایمیل به عنوان شناسه اصلی کاربر به جای نام کاربری برای احراز هویت استفاده شود.
در پایان این مقاله شما قادر خواهید بود:
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 را بررسی کردیم تا از آدرس ایمیل به عنوان شناسه اصلی کاربر به جای نام کاربری برای احراز هویت استفاده شود.
- Python
- 4k بازدید
- 2 تشکر