Cách mở rộng Django User Model
Django về cơ bản khá ngon, nó hỗ trợ hầu hết các tình huống xử lý của developer. Tuy nhiên trong một vài trường hợp bạn vẫn muốn thay đổi để phù hợp với ứng dụng của bạn hơn. Ví dụ như lưu trữ thêm các thông tin khác cho User model chẳng hạn.
Bài viết này sẽ cung cấp cho bạn một số phương án để bạn có thể xử lý nhanh gọn lẹ yêu cầu trên.
Cách 1: Sử dụng Proxy Model
Proxy Model là cái gì?
Proxy Model là class kế thừa một model nào đó mà không cần tạo ra một bảng mới trong cơ sở dữ liệu. Nó được sử dụng để thay đổi hành vi của một mô hình hiện có (ví dụ: thứ tự mặc định, thêm phương thức mới, v.v.) mà không ảnh hưởng đến sơ đồ quan hệ của cơ sở dữ liệu hiện tại.
Khi nào nên sử dụng Proxy Model
Bạn nên sử dụng Proxy Model để mở rộng User model khi bạn không cần lưu trữ thêm thông tin trong cơ sở dữ liệu mà chỉ cần thêm các phương thức để bổ sung hoặc thay đổi cách thức truy vấn của Manager.
Proxy Model là cách dễ nhất để mở rộng User model. Nhưng nó có hạn chế về nhiều mặt.
Sử dụng Proxy Model như thế nào?
from django.contrib.auth.models import User
from .managers import PersonManager
class Person(User):
objects = PersonManager()
class Meta:
proxy = True
ordering = ('first_name', )
def do_something(self):
...
Trong đoạn code trên, ta tạo ra class Person kế thừa class User, khai báo nó là Proxy Model bằng cách thêm cờ proxy = True
trong class Meta của Person. Lúc này User.objects.all()
và Person.objects.all()
đều cùng query đến cùng 1 bảng trong cơ sở dữ liệu.
Ngoài ra trong Django, class User chỉ chứa những gì căn bản nhất đủ để đại diện cho đối tượng người dùng. Do đó, trong trường hợp ta cần thêm phương thức để sử dụng cho các mục đích khác thì ta có thể khai báo trong class Person, ví dụ như: do_something()
.
Cách 2: Sử dụng quan hệ One-to-One
One-to-One ở đây là gì?
Về cơ bản, cách làm này có nghĩa là bạn sẽ tạo ra một bảng khác có quan hệ 1 - 1 với User Model. Bảng này sẽ có nhiệm vụ bổ sung thông tin cho bảng User.
Khi nào nên sử dụng?
Bạn nên dùng cách này khi bạn muốn lưu trữ thêm các thông tin khác không liên quan trực tiếp đến việc đăng nhập. Thường thì bảng này sẽ có tên là Profile, Profile sẽ có quan hệ 1-1 với User. Điều này có nghĩa là mỗi User trong bảng User sẽ có 1 Profile trong bảng Profile tương ứng với nó. Các thông tin sẽ được lưu thường là ảnh đại diện, mô tả, vị trí, ngày sinh...
Sử dụng như thế nào?
Trong model, ta bổ sung thêm class Profile, có quan hệ 1-1 với User.
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
# Bắt tín hiệu @post_save từ bảng User để tạo ra bản ghi mới trong bảng
# Profile một cách tự động
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
# Bắt tín hiệu @post_save từ bảng User để lưu lại profile vào bảng
# Profile một cách tự động
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
Về cơ bản, sau khi khai báo model như trên dữ liệu của bảng Profile sẽ được tự động lưu lại theo bất kỳ sự thay đổi nào diễn ra ở bảng User.
Do đó trong view, hầu hết các trường hợp bạn không cần phải can thiệp gì cả. Nếu bạn muốn cập nhật một trường cụ thể của profile thì bạn có thể cập nhật nó như ví dụ dưới đây:
def update_profile(request, user_id):
user = User.objects.get(pk=user_id)
user.profile.bio = 'Lorem ipsum dolor sit amet'
user.save()
Ngoài ra, lưu ý "Django relationships are lazy.". Với lazy-loading, bạn chỉ nhận được một data trong một lần query và phải thực hiện query tiếp khi bạn muốn lấy ra các objects liên quan đến nó. Hay nói cách khác, ta sẽ phải query N+1 lần
Do đó nếu bạn muốn lấy cả dữ liệu profile trong 1 truy vấn duy nhất đến bảng User thì bạn có thể sử dụng selected_relate.
users = User.objects.all().select_related('profile')
Cách 3: Kế thừa AbstractUser
AbstractUser là gì
class django.contrib.auth.models.AbstractUser
là class định nghĩa đầy đủ một người dùng, nó được kế thừa từ AbstractBaseUser.
Khi nào nên sử dụng?
Khi bạn không muốn sử dụng hai cách trên, nhưng cũng không yêu cầu đặc biệt gì về việc authentication, ví dụ bạn vẫn muốn dùng username để đăng nhập như mặc định của Django.
Về cơ bản thì bạn chỉ cần cẩn trọng hơn khi thực hiện cách làm này so với các cách đã nêu trước đó, vì nó sẽ làm thay đổi diagram của cơ sở dữ liệu.
Sử dụng như thế nào?
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
birth_date = models.DateField(null=True, blank=True)
Trong model, ta khai báo class User kế thừa từ AbstractUser.
Tiếp theo bạn sẽ phải cập nhật giá trị AUTH_USER_MODEL
trong settings.py.
AUTH_USER_MODEL = 'db.User'
Lưu ý, ở đây tôi dùng db
là django app để lưu trữ models.py. Bạn cần xem lại là app nào đang lưu models.py của bạn rồi thay thế tương ứng.
Sau khi bạn khai báo xong model và settings.py. Bạn có 2 cách để gọi đến model này:
Cách đầu tiên: Import trực tiếp vào để sử dụng
from django.db import models
from testapp.core.models import User
class Course(models.Model):
slug = models.SlugField(max_length=100)
name = models.CharField(max_length=100)
tutor = models.ForeignKey(User, on_delete=models.CASCADE)
Cách thứ 2: Sử dụng thông qua settings
from django.db import models
from django.conf import settings
class Course(models.Model):
slug = models.SlugField(max_length=100)
name = models.CharField(max_length=100)
tutor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
Về cơ bản cách đầu tiên là đủ để dùng. Nhưng nếu bạn muốn thay đổi AUTH_USER_MODEL
trong tương lai hoặc bạn muốn publish User model của bạn cho người khác dùng thì bạn sẽ phải dùng cách thứ 2.
Cuối cùng, bạn có thể sử dụng User như những model khác trong view và serializer.
Cách 4: Kế thừa AbstractBaseUser
AbstractBaseUser là gì?
AbstractBaseUser
là class cơ bản để định nghĩa một người dùng cụ thể là thông tin cơ bản liên quan đến authentication: đăng nhập, đăng ký.
Khi nào nên sử dụng?
Nên sử dụng khi ứng dụng của bạn cần những yêu cầu đặc biệt về authentication, ví dụ: bạn muốn đăng nhập dùng email
hoặc phone
thay vì username
.
Ngoài ra, cách làm này đòi hỏi bạn phải cẩn trọng nhiều hơn và thời điểm lý tưởng nhất để sử dụng cách làm này là khi bắt đầu dự án, vì cách làm này sẽ ảnh hưởng khá nhiều đến diagram cơ sở dữ liệu.
Sử dụng như thế nào?
Dưới đây là ví dụ về cách sử dụng:
from __future__ import unicode_literals
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from .managers import UserManager
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_('email address'), unique=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
is_active = models.BooleanField(_('active'), default=True)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def get_full_name(self):
'''
Returns the first_name plus the last_name, with a space in between.
'''
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
'''
Returns the short name for the user.
'''
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
'''
Sends an email to this User.
'''
send_mail(subject, message, from_email, [self.email], **kwargs)
Ở đây bạn định nghĩa một class User
trong model. class này kế thừa tất cả thuộc tính và phương thức của AbstractBaseUser
:
- USERNAME_FIELD: Tên trường sẽ được sử dụng làm trường username khi đăng nhập. Ở đây ta đang dùng email để đăng nhập thì
USERNAME_FIELD = 'email'
. - REQUIRED_FIELDS: Những trường bắt buộc phải có khi tạo user bằng câu lệnh
createsuperuser
trên commandline. - Các phương thức bên dưới, bạn có thể không cần quan tâm đến nó.
Bây giờ, ta sẽ phải thay đổi một chút trong Manager bằng cách thay username thành email và bỏ những trường không cần thiết, ví dụ như tôi không dùng Django Admin nên tôi bỏ is_staff
đi.
from django.contrib.auth.base_user import BaseUserManager
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, email, password, **extra_fields):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('The given email must be set')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
extra_fields.setdefault('is_superuser', 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)
Cuối cùng là cập nhật giá trị AUTH_USER_MODEL
trong file settings.py.
AUTH_USER_MODEL = 'db.User'
Lưu ý, ở đây tôi dùng db
là django app để lưu trữ models.py. Bạn cần xem lại là app nào đang lưu models.py của bạn rồi thay thế tương ứng.
Cách sử dụng User model vừa khai báo cũng giống hệt như cách thứ 3 ở trên.
