django 擴展Django自帶User模型,實現用戶註冊與登錄


總體開發思路


我們要利用Django 2.0開發一個叫users的app,來實現以下6項功能。我們一共將分3篇文章來介紹。本文只介紹用戶的註冊登錄部分。


  • 用戶註冊: 註冊完成後轉到登錄頁面 

  • 用戶登錄: 登錄完成後轉到用戶資料頁面

  • 用戶資料頁面: 查看用戶註冊信息,並提供編輯資料按鈕

  • 用戶資料編輯:編輯完成後轉到用戶資料查看頁面

  • 用戶密碼重置

  • 用戶退出登陸


由於Django Auth自帶的User模型字段有限,我們還需要自定義模型UserProfile對其擴展。

Django Auth模塊自帶User模型所包含字段

  • username:用戶名

  • email: 電子郵件

  • password:密碼

  • first_name:名

  • last_name:姓

  • is_active: 是否爲活躍用戶。默認是True

  • is_staff: 是否爲員工。默認是False

  • is_superuser: 是否爲管理員。默認是False

  • date_joined: 加入日期。系統自動生成。

自定義的UserProfile模型

  • user: 與User是1對1關係

  • org:用戶名

  • telephone: 電話

  • mod_date: 最後修改日期。系統自動生成


第一步: 創建名叫users的app並修改設置setting.py


我們假設你已經利用Django創建了一個叫mysite的項目,你可以在終端cmd窗口cd進入這個目錄,並輸入以下命令創建一個叫users的app。

python manage.py startapp users 

然後找到mysite/settings.py裏將'users' 加到INSTALLED_APPS裏,如下圖所示。


INSTALLED_APPS = [
    'reg.apps.RegConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users',
]


第二步: 建立名叫UserProfile的模型(Model)


我們並沒有改變Django Auth自帶的User模型,也沒有建立新的User模型。UserProfile只是對User模型的擴展。找到users/models.py, 並創建如下UserProfile模型。由於我們引用了Django Auth自帶的User模型,所以我們必需開始先把它import進來。


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

class UserProfile(models.Model):

    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')

    org = models.CharField(
        'Organization', max_length=128, blank=True)

    telephone = models.CharField(
        'Telephone', max_length=50, blank=True)

    mod_date = models.DateTimeField('Last modified', auto_now=True)

    class Meta:
        verbose_name = 'User Profile'

    def __str__(self):
        return self.user.__str__()

然後你可以在終端輸入以下命令,就可以創建UserProfile的數據表。

python manage.py makemigrations 
python manage.py migrate 


第三步:配置URL


小編我是自上而下思考的,所以我習慣上先編寫URL,再寫視圖view。從URL配置上你應該可以直接理解我們想實現的6個功能。下面是users/urls.py裏的全部代碼。你應該注意到,我們給動態鏈接/register/取了個名字’register', 這樣我們就可以在html模板裏可以通過{% url 'users:register' %}調用這個鏈接了。


from django.urls import re_path
from . import views

app_name = 'users'
urlpatterns = [
    re_path(r'^register/$', views.register, name='register'),
    re_path(r'^login/$', views.login, name='login'),
    re_path(r'^user/(?P<pk>\d+)/profile/$', views.profile, name='profile'),
    re_path(r'^user/(?P<pk>\d+)/profile/update/$', views.profile_update, name='profile_update'),
    re_path(r'^user/(?P<pk>\d+)/pwdchange/$', views.pwd_change, name='pwd_change'),
    re_path(r'^logout/$', views.logout, name='logout'),
]


另外找到mysite/urls.py, 把我們這個app的URLs也加進去,如下圖所示。這樣當用戶訪問/accounts/register/時,瀏覽器會調用views.py裏的register函數。


urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^accounts/', include('users.urls')),

] 


第四步: 編寫試圖(view)


我們需要編寫register和login兩個視圖, 讓用戶通過表單向我們提交數據,並處理這些數據。因爲這兩個視圖都需要用表單,所以我們先在users目錄下新建forms.py, 然後創建兩個form,一個RegistrationForm,一個LoginForm代碼如下:

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


def email_check(email):
    pattern = re.compile(r"\"?([-a-zA-Z0-9.`?{}]+@\w+\.\w+)\"?")
    return re.match(pattern, email)


class RegistrationForm(forms.Form):

    username = forms.CharField(label='Username', max_length=50)
    email = forms.EmailField(label='Email',)
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password Confirmation', widget=forms.PasswordInput)

    # Use clean methods to define custom validation rules

    def clean_username(self):
        username = self.cleaned_data.get('username')

        if len(username) < 6:
            raise forms.ValidationError("Your username must be at least 6 characters long.")
        elif len(username) > 50:
            raise forms.ValidationError("Your username is too long.")
        else:
            filter_result = User.objects.filter(username__exact=username)
            if len(filter_result) > 0:
                raise forms.ValidationError("Your username already exists.")

        return username

    def clean_email(self):
        email = self.cleaned_data.get('email')

        if email_check(email):
            filter_result = User.objects.filter(email__exact=email)
            if len(filter_result) > 0:
                raise forms.ValidationError("Your email already exists.")
        else:
            raise forms.ValidationError("Please enter a valid email.")

        return email

    def clean_password1(self):
        password1 = self.cleaned_data.get('password1')

if len(password1) < 6:
            raise forms.ValidationError("Your password is too short.")
        elif len(password1) > 20:
            raise forms.ValidationError("Your password is too long.")

        return password1

    def clean_password2(self):
        password1 = self.cleaned_data.get('password1')
        password2 = self.cleaned_data.get('password2')

        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Password mismatch. Please enter again.")

        return password2


class LoginForm(forms.Form):

    username = forms.CharField(label='Username', max_length=50)
    password = forms.CharField(label='Password', widget=forms.PasswordInput)

    # Use clean methods to define custom validation rules

    def clean_username(self):
        username = self.cleaned_data.get('username')

        if email_check(username):
            filter_result = User.objects.filter(email__exact=username)
            if not filter_result:
                raise forms.ValidationError("This email does not exist.")
        else:
            filter_result = User.objects.filter(username__exact=username)
            if not filter_result:
                        raise forms.ValidationError("This username does not exist. Please register first.")

        return username


千萬不要上面的代碼嚇到。之所以代碼這麼長是因爲我們用clean方法加入了很多表單驗證項,比如檢查用戶名是否過短,用戶名是否已經存在。如果你把表單驗證拿掉,其實代碼非常少。我之所以加上這些驗證規則,是讓你瞭解最真實的網站開發。


當然你也可以不用新建forms.py而直接在html模板裏寫表單,但我並不建議這麼做。用forms.py的好處顯而易見: 

  • 所有的表單在一個文件裏,非常便於後期維護,比如增添或修訂字段。

  • forms.py可通過clean方法自定義表單驗證,非常便捷。不用在views.py裏再進行表單驗證(比如檢查用戶是否已存在),邏輯上更清晰。


我們的視圖users/views.py是這樣子的。

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.models import User
from .models import UserProfile
from django.contrib import auth
from .forms import RegistrationForm, LoginForm, ProfileForm, PwdChangeForm
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth.decorators import login_required


def register(request):
    if request.method == 'POST':

        form = RegistrationForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            email = form.cleaned_data['email']
            password = form.cleaned_data['password2']

            # 使用內置User自帶create_user方法創建用戶,不需要使用save()
              user = User.objects.create_user(username=username, password=password, email=email)

            # 如果直接使用objects.create()方法後不需要使用save()
              user_profile = UserProfile(user=user)
            user_profile.save()

            return HttpResponseRedirect("/accounts/login/")

    else:
        form = RegistrationForm()

    return render(request, 'users/registration.html', {'form': form})


def login(request):
    if request.method == 'POST':
        form = LoginForm(request.POST)
        if form.is_valid():
           username = form.cleaned_data['username']
            password = form.cleaned_data['password']

            user = auth.authenticate(username=username, password=password)

            if user is not None and user.is_active:
               auth.login(request, user)
               return HttpResponseRedirect(reverse('users:profile', args=[user.id]))

            else:
                # 登陸失敗
                 return render(request, 'users/login.html', {'form': form,
                               'message': 'Wrong password. Please try again.'})
    else:
        form = LoginForm()

    return render(request, 'users/login.html', {'form': form})


我們先看下views.register函數是怎麼工作的:

  • 當用戶通過POST方法提交表單,我們先驗證表單RegistrationForm的數據是否有效。如果有效,我們先用Django User模型自帶的create_user方法創建user對象,再創建user_profile。用戶通過一張表單提交數據,我們實際上分別存儲在兩張表裏。

  • 如果用戶註冊成功,我們通過HttpResponseRedirect方法轉到登陸頁面

  • 如果用戶沒有提交表單或不是通過POST方法提交表單,我們轉到註冊頁面,生成一張空的RegistrationForm


我們再看下views.login函數是怎麼工作的:


  • 當用戶通過POST方法提交表單,我們先驗證表單LoginForm的數據是否有效。如果有效,我們調用Django自帶的auth.authenticate() 來驗證用戶名和密碼是否正確。如果正確且用戶是活躍的,我們調用auth.login()來進行登錄。

  • 如果用戶登錄失敗,會重新轉到登錄頁面,並返回錯誤信息。

  • 如果用戶登錄成功,我們通過HttpResponseRedirect方法轉到用戶個人信息頁面

  • 如果用戶沒有提交表單或不是通過POST方法提交表單,我們轉到登錄頁面,生成一張空的LoginForm


第五步: 編寫HTML模板(Template)


在users目錄下創建/templates/users/文件夾,編寫html模板registration.htmllogin.html。其目錄結構應該如下圖所示:

下面是模板registration.html的代碼:

{% block content %}
<div class="form-wrapper">
   <form method="post" action="" enctype="multipart/form-data">
      {% csrf_token %}
      {% for field in form %}
           <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
        {% if field.help_text %}
             <p class="help">{{ field.help_text|safe }}</p>
        {% endif %}
           </div>
        {% endfor %}
      <div class="button-wrapper submit">
         <input type="submit" value="Submit" />
      </div>
   </form>
</div>
{% endblock %}


下面是模板login.html的代碼:

{% block content %}
<h2>Login</h2>
{% if message %}
 {{ message }}
{% endif %}
<div class="form-wrapper">
   <form method="post" action="" enctype="multipart/form-data">
      {% csrf_token %}
      {% for field in form %}
           <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }} {{ field }}
        {% if field.help_text %}
             <p class="help">{{ field.help_text|safe }}</p>
        {% endif %}
           </div>
        {% endfor %}
      <div class="button-wrapper submit">
         <input type="submit" value="Login" />
      </div>
   </form>
    <a href="/accounts/register">Register</a>
</div>
{% endblock %}


第六步:實戰效果


現在你可以在終端輸入以下命令,看實戰效果了。

python manage.py runserver


打開瀏覽器,訪問http://127.0.0.1:8000/accounts/register/和http://127.0.0.1:8000/accounts/login/,你就應該看到如下效果了。

當你註冊時兩次密碼不一致,或用戶名太短,或用戶名已存在,表單會告訴你錯誤在哪裏,如下圖所示:

當你成功登錄時,你會被轉到個人資料頁面。你將被允許修改自己的信息,如下圖所示:


結語


本文利用Django 2.0實現了用戶註冊與登錄的兩個功能,擴展了Django自帶的User模型,並分享了代碼。接下來我會分享如何實現查看用戶資料,允許用戶修改自己資料,修改密碼和退出登錄的其它4個功能。另外,我會專門介紹如何調用靜態文件css和js來美化本項目中的表單。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章