個人網站搭建 (第十八天) 自定義用戶模型

    在Django自帶的User類中,只有用戶名、郵箱、密碼等等一些基礎信息。若是此時有添加用戶電話,暱稱,qq號等其餘信息的需求時,自帶User類的弊端就出現了。那麼若是出現上述需求時,就須要自定義用戶模型。html

    在Django的文檔中對於自定義用戶模型,有下面這麼兩段話。python

    有兩種方法能夠擴展默認User模型而無需替換本身的模型。若是您須要的更改純粹是行爲上的,而且不須要對數據庫中存儲的內容進行任何更改,則能夠基於建立代理模型User。這容許代理模型提供的任何功能,包括默認排序,自定義管理器或自定義模型方法。git

    若是您但願存儲與之相關的信息User,可使用 OneToOneField包含這些字段的模型來獲取其餘信息。這種一對一模型一般稱爲配置文件模型,由於它可能存儲有關站點用戶的非身份驗證相關信息。數據庫

自定義模型繼承AbstractUserdjango

1.AbstractUser 類的部分代碼架構

class AbstractUser(AbstractBaseUser, PermissionsMixin):
    """
    An abstract base class implementing a fully featured User model with
    admin-compliant permissions.

    Username and password are required. Other fields are optional.
    """
    username_validator = UnicodeUsernameValidator() if six.PY3 else ASCIIUsernameValidator()

    username = models.CharField(
        _('username'),
        max_length=150,
        unique=True,
        help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
        validators=[username_validator],
        error_messages={
            'unique': _("A user with that username already exists."),
        },
    )
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    email = models.EmailField(_('email address'), blank=True)
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_('Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )

2.平時常用的User類app

class User(AbstractUser):
    """
    Users within the Django authentication system are represented by this
    model.

    Username, password and email are required. Other fields are optional.
    """
    class Meta(AbstractUser.Meta):
        swappable = 'AUTH_USER_MODEL'

    能夠看出一樣的方法,我也能夠試着使用一個新的 User 來繼承 AbstractUser 類,並添加上須要的字段。我一開始在本地開發使用的是這種辦法,後來出了不少的一系列問題,好比數據庫遷移失敗,修改表結構,並且數據庫遷移失敗是因爲migrations文件衝突致使的,必須刪除該遷移文件,並從新生成遷移文件。考慮到這樣下去,會影響到我生產環境的數據庫,並且須要修改的代碼也比較多,因此最後不得不棄用此方法。工具

    若是要具體使用這種方法,能夠查看Django文檔的擴展示有User模型是如何使用的。post

3.優缺點
    1) 優勢:
        ①自定義強。
        ②沒有沒必要要的字段(須要繼承AbstractBaseUser)。
    2) 缺點:
        ①須要刪除庫來或者要項目一開始就使用。
        ②配置admin麻煩 。網站

新的模型拓展關聯User    

    除了將新模型繼承 AbstractUser ,還可創建新模型使其關聯User。這種方法很是適合項目已經基本成型,但在後來開發中又須要修改用戶表結構的操做,最終我選用了拓展關聯User的方法。

1.先看看官方的一個例子
    例如建立一個Employee模型:

from django.contrib.auth.models import User

class Employee(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    department = models.CharField(max_length=100)

    假設現有員工Fred Smith同時擁有User和Employee模型,你可使用Django的標準相關模型約定訪問相關信息:

>>> u = User.objects.get(username='fsmith')
>>> freds_department = u.employee.department

    要將配置文件模型的字段添加到管理員的用戶頁面,須要在應用程序中定義一個 InlineModelAdmin(對於此示例,咱們將使用a StackedInline)admin.py並將其添加到在UserAdmin類中註冊的 User類:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User

from my_user_profile_app.models import Employee

# Define an inline admin descriptor for Employee model
# which acts a bit like a singleton
class EmployeeInline(admin.StackedInline):
    model = Employee
    can_delete = False
    verbose_name_plural = 'employee'
    
# Define a new User admin
class UserAdmin(BaseUserAdmin):
    inlines = (EmployeeInline,)
    
# Re-register UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)

    能夠看到,在註冊應用時,用到了admin下的StackedInline,這裏與xadmin的註冊有所區分,後面會提到。

2.優缺點
    1) 優勢:
        ①使用方便。
        ②不用刪除庫重來影響總體架構。
    2) 缺點:
        ①對比繼承方法,查詢速度稍稍慢一丁點。

3.需求分析
    在一開始我就用的是Django自帶的用戶管理系統,因此個人用戶屬性只有用戶名、郵箱、密碼。光靠這些不是很容易記住用戶的身份,應該還須要相似於暱稱的屬性,這樣可讓用戶以用戶用戶名進行登陸,登陸以後能夠顯示他們本身設置的暱稱。

4.新建Profile模型 
    我定義了一個Profile類,經過models的OneToOneField將其一對一地關聯到User類,這裏我只新增了nickname(暱稱),也能夠添加其餘的。

from django.db import models
from django.contrib.auth.models import User


class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name='用戶名')
    nickname = models.CharField(max_length=20, verbose_name='暱稱')

    def __str__(self):
        return self.nickname

    class Meta:
        verbose_name = '暱稱'
        verbose_name_plural = '暱稱'

5.註冊Profile模型     
    若是你的後臺用的是admin,那麼直接根據官方的例子就能正常將新的User模型註冊到後臺。這裏我用的是xadmin後臺,它與admin同樣,都屬於Djnago的後臺管理,只不過xadmin的功能要略顯強大一些,具體在Django中配置xadmin能夠查看網站搭建 (第十四天) xadmin後臺強化,這裏就不贅述了。由於xadmin在註冊應用時,繼承的是object,而admin繼承的是admin.ModelAdmin,因此在實現註冊模型上有必定的區別。
    以前,我在xadmin模塊中怎樣都找不到StackedInline,內心想着完了,又要從新使用admin了。試弄了一會,發現其實InlineModelAdmin仍是照樣繼承object,而UserAdmin在xadmin/plugins/auth 目錄下。最後實現以下:

import xadmin
from xadmin.plugins.auth import UserAdmin as BaseAdmin
from django.contrib.auth.models import User
from .models import Profile


class ProfileInline(object):
    model = Profile
    extra = 0


class UserAdmin(BaseAdmin):
    inlines = [ProfileInline]
    list_display = ['username', 'nickname', 'email', 'is_staff', 'is_active', 'is_superuser']

    def nickname(self, obj):
        return obj.profile.nickname
    nickname.short_description = '暱稱'


xadmin.site.unregister(User)
xadmin.site.register(User, UserAdmin)

    上面是將Profile模型的nickname字段註冊到User中,還能夠將Profile模型再註冊一個應用。

class ProfileAdmin(object):
    """
    做用:自定義文章管理工具
    admin.ModelAdmin:繼承admin.ModelAdmin類
    """
    list_display = ['user', 'nickname']


xadmin.site.register(Profile, ProfileAdmin)

    作好這一步,就能夠去xadmin後臺爲用戶添加暱稱了。

6.添加User模型的方法
    實現好模型註冊之後,爲了能將用戶的暱稱顯示在頁面中,還須要爲User類綁定一些方法。

def get_nickname_or_username(self):
    if Profile.objects.filter(user=self).exists():
        profile = Profile.objects.get(user=self)
        return profile.nickname
    else:
        return self.username


def has_nickname(self):
    return Profile.objects.filter(user=self).exists()
    
# 綁定方法不須要添加括號
User.get_nickname_or_username = get_nickname_or_username
User.has_nickname = has_nickname

    而後就能夠在以前顯示username的地方修改成nickname的顯示,如導航欄的歡迎用戶。

<a href="#" class="dropdown-toggle hidden-sm" data-toggle="dropdown" role="button">
    <span class="glyphicon glyphicon-user item"></span>&nbsp;您好:&nbsp;{{ user.get_nickname_or_username }}<span class="caret"></span>
</a>

7.定義修改暱稱表單
    以前仍是須要管理員本身到後臺對用戶進行添加暱稱操做,要是可讓用戶本身修改暱稱豈不是更好。實現起來也不難,只要再創建一個form表單,將post的數據清洗一下,再將合法的暱稱綁定在user上。關於Django的form表單使用,能夠參考以前寫的用戶註冊或登陸表單。
    一樣,在user/forms.py下新建ChangeNameForm表單。

from django.contrib import auth
from django.contrib.auth.models import User

class ChangeNameForm(forms.Form):
    new_nickname = forms.CharField(
        label='新暱稱',
        max_length=20,
        min_length=3,
        widget=forms.TextInput(
            attrs={'placeholder': '請輸入新的暱稱'}
        )
    )

    def __init__(self, *args,  **kwargs):
        if 'user' in kwargs:
            self.user = kwargs.pop('user')
        super(ChangeNameForm, self).__init__(*args, **kwargs)

    def clean(self):
        # 驗證用戶是否處在登陸狀態
        if self.user.is_authenticated:
            self.cleaned_data['user'] = self.user
        else:
            raise forms.ValidationError('用戶還沒有登陸')
        return self.cleaned_data

    def clean_new_nickname(self):
        new_nickname = self.cleaned_data.get('new_nickname', '').strip()
        if new_nickname == '':
            raise forms.ValidationError("新的暱稱不能爲空")
        return new_nickname

8.定義修改暱稱的邏輯處理

from .models import Profile
from .forms import *
from django.shortcuts import redirect, render

def change_name(request):
    """修改暱稱"""
    if request.method == 'POST':
        name_form = ChangeNameForm(request.POST, user=request.user)
        if name_form.is_valid():
            new_nickname = name_form.cleaned_data['new_nickname']
            profile, created = Profile.objects.get_or_create(user=request.user)
            profile.nickname = new_nickname
            profile.save()
            return redirect(request.GET.get('from', reverse('blog:home')))

    else:
        name_form = ChangeNameForm()

    context = {'name_form': name_form}
    return render(request, 'user/change_name.html', context)

9.創建修改暱稱視圖

{% extends 'base.html' %}
{% load staticfiles %}

{% block title %}
    修改暱稱
{% endblock %}

{% block nav_login_active %}active{% endblock %}
{% block lunbobox %}{% endblock %}

{% block content %}
    <div class="container" style="margin-top: 70px;">

        <div class="head-login">
            <h2 class="text-info">修改暱稱</h2>
            <span>與我取得聯繫,共同成長吧</span>
        </div>

        <div class="change_name">
             <form action="" method="POST">
             {% csrf_token %}
             {% for field in name_form %}
                 <label for="{{ field.id_for_label }}">{{ field.label }}:</label>
                 {{ field }}
                 <p class="text-danger">{{ field.errors.as_text }}</p>
             {% endfor %}
                {# 錯誤信息標紅 #}
                <span class="text-danger">{{ login_form.non_field_errors }}</span>
                {# <span>用戶名:</span> #}
                {# <input type="text" name="username"> #}
                {# <span>密碼:</span> #}
                {# <input type="password" name="password"> #}
                 <button class="btn btn-primary pull-right" type="submit">修改</button>
            </form>

        </div>

    </div>
{% endblock %}

原文出處:https://www.jzfblog.com/detail/119,文章的更新編輯以此連接爲準。歡迎關注源站文章!

相關文章
相關標籤/搜索