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()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.

How to Extend Django User Model
The Django’s built-in authentication system is great. For the most part we can use it out-of-the-box, saving a lot ofdevelopment and testing effort. It fits ...

Subscribe to bachdgvn.com

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe