How to Extend Django User Model 如何擴展Django用戶模型

The Django’s built-in authentication system is great. For the most part we can use it out-of-the-box, saving a lot of development and testing effort. It fits most of the use cases and is very safe. But sometimes we need to do some fine adjustment so to fit our Web application.

Commonly we want to store a few more data related to our User. If your Web application have an social appeal, you might want to store a short bio, the location of the user, and other things like that.

In this tutorial I will present the strategies you can use to simply extend the default Django User Model, so you don’t need to implement everything from scratch.

Django的內置身份驗證系統非常好。 在大多數情況下,我們可以開箱即用,節省了大量的開發和測試工作。 它適合大多數用例,非常安全。 但有時我們需要做一些微調,以適應我們的Web應用程序。
通常我們想要存儲與我們的用戶相關的更多數據。 如果您的Web應用程序具有社會吸引力,您可能需要存儲一個簡短的生物,用戶的位置以及其他類似的東西。
在本教程中,我將介紹您可以使用的策略來簡單地擴展默認的Django用戶模型,因此您不需要從頭開始實現所有內容。

Ways to Extend the Existing User Model

擴展現有用戶模型的方法

Generally speaking, there are four different ways to extend the existing User model. Read below why and when to use them.一般來說,擴展現有用戶模型有四種不同的方式。 閱讀下面爲什麼和什麼時候使用它們。

Option 1: Using a Proxy Model使用代理模型

What is a Proxy Model?
It is a model inheritance without creating a new table in the database. It is used to change the behaviour of an existing model (e.g. default ordering, add new methods, etc.) without affecting the existing database schema.

什麼是代理模型?
它是一個模型繼承而不在數據庫中創建一個新表。 它用於改變現有模型的行爲(例如默認排序,添加新方法等),而不影響現有的數據庫模式。

When should I use a Proxy Model?
You should use a Proxy Model to extend the existing User model when you don’t need to store extra information in the database, but simply add extra methods or change the model’s query Manager.
什麼時候應該使用代理模型?
當您不需要在數據庫中存儲額外的信息時,您應該使用代理模型來擴展現有的用戶模型,而只需添加額外的方法或更改模型的查詢管理器。


What is a One-To-One Link?
It is a regular Django model that’s gonna have it’s own database table and will hold a One-To-One relationship with the existing User Model through a OneToOneField.

什麼是一對一鏈接?
它是一個常規的Django模型,它將擁有自己的數據庫表,並通過OneToOneField與現有的用戶模型保持一對一的關係。

When should I use a One-To-One Link?
You should use a One-To-One Link when you need to store extra information about the existing User Model that’s not related to the authentication process. We usually call it a User Profile.

什麼時候應該使用一對一鏈接?
當您需要存儲與認證過程無關的現有用戶模型的額外信息時,應使用一對一鏈接。 我們通常稱之爲用戶個人資料。


Option 3: Creating a Custom User Model Extending AbstractBaseUser    創建自定義用戶模型擴展AbstractBaseUser

What is a Custom User Model Extending AbstractBaseUser?
It is an entirely new User model that inherit from AbstractBaseUser. It requires a special care and to update some references through the settings.py. Ideally it should be done in the begining of the project, since it will dramatically impact the database schema. Extra care while implementing it.

什麼是自定義用戶模型擴展AbstractBaseUser?
它是一個全新的User Model,繼承自AbstractBaseUser。 它需要特別小心,並通過settings.py更新一些引用。 理想情況下,它應該在項目開始時完成,因爲它會極大地影響數據庫模式。 執行時要特別小心。

When should I use a Custom User Model Extending AbstractBaseUser?
You should use a Custom User Model when your application have specific requirements in relation to the authentication process. For example, in some cases it makes more sense to use an email address as your identification token instead of a username.


何時應該使用自定義用戶模型擴展AbstractBaseUser?
當您的應用程序具有與身份驗證過程相關的特定要求時,應使用自定義用戶模型。 例如,在某些情況下,使用電子郵件地址作爲識別令牌而不是用戶名更有意義。


Option 4: Creating a Custom User Model Extending AbstractUser 創建自定義用戶模型擴展AbstractUser

What is a Custom User Model Extending AbstractUser?
It is a new User model that inherit from AbstractUser. It requires a special care and to update some references through the settings.py. Ideally it should be done in the begining of the project, since it will dramatically impact the database schema. Extra care while implementing it.

什麼是自定義用戶模型擴展AbstractUser?
它是一個繼承自AbstractUser的新用戶模型。 它需要特別小心,並通過settings.py更新一些引用。 理想情況下,它應該在項目開始時完成,因爲它會極大地影響數據庫模式。 執行時要特別小心。

When should I use a Custom User Model Extending AbstractUser?
You should use it when you are perfectly happy with how Django handles the authentication process and you wouldn’t change anything on it. Yet, you want to add some extra information directly in the User model, without having to create an extra class (like in the Option 2).

何時應該使用自定義用戶模型擴展AbstractUser?
當Django處理身份驗證過程非常滿意時,您應該使用它,並且不會對其進行任何更改。 但是,您可以直接在用戶模型中添加一些額外的信息,而無需創建額外的類(如選項2)。

Extending User Model Using a Proxy Model  使用代理模型擴展用戶模型

This is the less intrusive way to extend the existing User model. You won’t have any drawbacks with that strategy. But it is very limited in many ways. 這是擴展現有用戶模型的侵入性較小的方式。 你不會有這個策略的任何缺點。 但是它在許多方面是非常有限的。

Here is how you do it:這是你怎麼做的:

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

In the example above we have defined a Proxy Model named Person. We tell Django this is a Proxy Model by adding the following property inside the Meta class: proxy = True.

In this case I’ve redefined the default ordering, assigned a custom Manager to the model, and also defined a new method do_something.

It is worth noting that User.objects.all() and Person.objects.all() will query the same database table. The only difference is in the behavior we define for the Proxy Model.

If that’s all you need, go for it. Keep it simple.


There is a good chance that this is what you want. Personally that is the method I use for the most part. We will be creating a new Django Model to store the extra information that relates to the User Model.

Bear in mind that using this strategy results in additional queries or joins to retrieve the related data. Basically all the time you access an related data, Django will fire an additional query. But this can be avoided for the most cases. I will get back to that later on.

有一個很好的機會,這是你想要的。 就個人而言,這是我大部分使用的方法。 我們將創建一個新的Django模型來存儲與用戶模型相關的額外信息。


請記住,使用此策略會產生其他查詢或聯接來檢索相關數據。 基本上所有的時間您訪問相關數據,Django將觸發一個額外的查詢。 但是大多數情況下可以避免這種情況。 我稍後再回來。


我通常將Django模型命名爲Profile:

I usually name the Django Model as Profile:

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)

Now this is where the magic happens: we will now define signals so our Profile model will be automatically created/updated when we create/update User instances.

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver

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)

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
    instance.profile.save()

Basically we are hooking the create_user_profile and save_user_profile methods to the User model, whenever asave event occurs. This kind of signal is called post_save.

Great stuff. Now, tell me how can I use it.

Piece of cake. Check this example in a Django Template:

<h2>{{ user.get_full_name }}</h2>
<ul>
  <li>Username: {{ user.username }}</li>
  <li>Location: {{ user.profile.location }}</li>
  <li>Birth Date: {{ user.profile.birth_date }}</li>
</ul>

How about inside a view method?

def update_profile(request, user_id):
    user = User.objects.get(pk=user_id)
    user.profile.bio = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit...'
    user.save()

Generally speaking, you will never have to call the Profile’s save method. Everything is done through the User model.

What if I’m using Django Forms?

Did you know that you can process more than one form at once? Check out this snippet:

forms.py

class UserForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ('first_name', 'last_name', 'email')

class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ('url', 'location', 'company')

views.py

@login_required
@transaction.atomic
def update_profile(request):
    if request.method == 'POST':
        user_form = UserForm(request.POST, instance=request.user)
        profile_form = ProfileForm(request.POST, instance=request.user.profile)
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, _('Your profile was successfully updated!'))
            return redirect('settings:profile')
        else:
            messages.error(request, _('Please correct the error below.'))
    else:
        user_form = UserForm(instance=request.user)
        profile_form = ProfileForm(instance=request.user.profile)
    return render(request, 'profiles/profile.html', {
        'user_form': user_form,
        'profile_form': profile_form
    })

profile.html

<form method="post">
  {% csrf_token %}
  {{ user_form.as_p }}
  {{ profile_form.as_p }}
  <button type="submit">Save changes</button>
</form>

And the extra database queries you were talking about?

Oh, right. I’ve addressed this issue in another post named “Optimize Database Queries”. You can read it clicking here.

But, long story short: Django relationships are lazy. Meaning Django will only query the database if you access one of the related properties. Sometimes it causes some undesired effects, like firing hundreds or thousands of queries. This problem can be mitigated using the select_related method.

Knowing beforehand you will need to access a related data, you can prefetch it in a single database query:

users = User.objects.all().select_related('profile')

Extending User Model Using a Custom Model Extending AbstractBaseUser    

 使用自定義模型擴展用戶模型擴展AbstractBaseUser

The hairy one. Well, honestly I try to avoid it at all costs. But sometimes you can’t run from it. And it is perfectly fine. There is hardly such a thing as best or worst solution. For the most part there is a more or less appropriate solution. If this is the most appropriate solution for you case, go ahead.

I had to do it once. Honestly I don’t know if this is the cleaner way to do it, but, here goes nothing:

I needed to use email address as auth token and in the scenario the username was completly useless for me. Also there was no need for the is_staff flag, as I wasn’t using the Django Admin.

毛茸茸的。 嗯,老實說,我儘量避免,不惜一切代價。 但是有時你不能從它跑出來。 這是非常好的。 幾乎沒有最好的或最差的解決方案。 在大多數情況下,有一個或多或少的適當的解決方案。 如果這是您最合適的解決方案,請繼續。


我不得不這樣做一次。 老實說,我不知道這是否是更乾淨的做法,但是什麼也沒有:


我需要使用電子郵件地址作爲身份驗證令牌,在這種情況下,用戶名對我來說完全沒用。 也沒有必要的is_staff標誌,因爲我沒有使用Django管理員。


這是我如何定義我自己的用戶模型:

Here is how I defined my own user model:

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)

I wanted to keep it as close as possible to the existing User model. Since we are inheriting from theAbstractBaseUser we have to follow some rules:

  • USERNAME_FIELD: A string describing the name of the field on the User model that is used as the unique identifier. The field must be unique (i.e., have unique=True set in its definition);
  • REQUIRED_FIELDS: A list of the field names that will be prompted for when creating a user via thecreatesuperuser management command;
  • is_active: A boolean attribute that indicates whether the user is considered “active”;
  • get_full_name(): A longer formal identifier for the user. A common interpretation would be the full name of the user, but it can be any string that identifies the user.
  • get_short_name(): A short, informal identifier for the user. A common interpretation would be the first name of the user.

Okay, let’s move forward. I had also to define my own UserManager. That’s because the existing manager define thecreate_user and create_superuser methods.

So, here is what my UserManager looks like:

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)

Basically I’ve done a clean up of the existing UserManager, removing the username and the is_staff property.

Now the final move. We have to update our settings.py. More specifically the AUTH_USER_MODEL property.

AUTH_USER_MODEL = 'core.User'

This way we are telling Django to use our custom model instead the default one. In the example above, I’ve created the custom model inside an app named core.

How should I reference this model?

Well, there are two ways. Consider a model named Course:

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)

This is perfectly okay. But if you are creating a reusable app, that you want to make available for the public, it is strongly advised that you use the following strategy:

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)

Extending User Model Using a Custom Model Extending AbstractUser  

使用自定義模型擴展用戶模型擴展AbstractUser

This is pretty straighforward since the class django.contrib.auth.models.AbstractUser provides the full implementation of the default User as an abstract model.

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)

Then we have to update our settings.py defining the AUTH_USER_MODEL property.

AUTH_USER_MODEL = 'core.User'

In a similar way as the previous method, this should be done ideally in the begining of a project and with an extra care. It will change the whole database schema. Also, prefer to create foreign keys to the User model importing the settings from django.conf import settings and referring to the settings.AUTH_USER_MODEL instead of referring directly to the custom User model.


Conclusions

Alright! We’ve gone through four different ways to extend the existing User Model. I tried to give you as much details as possible. As I said before, there is no best solution. It will really depend on what you need to achieve. Keep it simple and choose wisely.  好的! 我們已經通過四種不同的方式來擴展現有的用戶模型。 我試圖給你儘可能多的細節。 正如我之前所說,沒有最好的解決辦法。 這將取決於你需要實現什麼。 保持簡單,明智地選擇。

  • Proxy Model: You are happy with everything Django User provide and don’t need to store extra information.
  • User Profile: You are happy with the way Django handles the auth and need to add some non-auth related attributes to the User.
  • Custom User Model from AbstractBaseUser: The way Django handles auth doesn’t fit your project.
  • Custom User Model from AbstractUser: The way Django handles auth is a perfect fit for your project but still you want to add extra attributes without having to create a separate Model.
  • 代理模型:您對Django用戶提供的所有內容感到滿意,並且不需要存儲額外的信息。
  • 用戶個人資料:您對Django處理驗證的方式感到滿意,需要向用戶添加一些非驗證相關的屬性。
  • 來自AbstractBaseUser的自定義用戶模型:Django處理auth的方式不符合您的項目。
  • 來自AbstractUser的自定義用戶模型:Django處理auth的方式非常適合您的項目,但仍然需要添加額外的屬性,而無需創建單獨的模型。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章