Django 從 0 到 1 打造完整電商平臺:登錄與登出功能實現(xiàn)


作者: IT策士?

10余年一線大廠經(jīng)驗,專注 IT 思維、架構(gòu)、職場進階。持續(xù)發(fā)布最新文章,助你少走彎路。


前言

上一篇我們完成了用戶注冊功能,數(shù)據(jù)庫里已經(jīng)積累了一批注冊用戶。但一個完整的身份認證體系,光有注冊是不夠的——用戶還需要能夠登錄登出。

今天這篇文章,我們將基于 Django 內(nèi)置的認證系統(tǒng)(django.contrib.auth),通過自定義認證后端實現(xiàn)多方式登錄(手機號/郵箱/用戶名),同時集成"記住我"、登錄后跳轉(zhuǎn)、導航欄狀態(tài)切換等實用功能。

跟著我的步驟,半小時內(nèi)讓你的項目真正"認人"。


一、需求分析

我們要實現(xiàn)的登錄功能清單:

功能點 說明
多方式登錄 支持手機號、郵箱、用戶名任一方式登錄
錯誤提示 密碼錯誤或用戶不存在時給出明確提示
記住我 支持延長 session 有效期
導航欄切換 登錄后顯示"歡迎,XXX"及下拉菜單
智能跳轉(zhuǎn) 登錄后返回之前想訪問的頁面,否則回首頁
安全登出 清除 session,回到首頁

二、自定義認證后端

2.1 為什么要自定義?

Django 默認的認證后端只能通過 usernamepassword 登錄。但我們的注冊邏輯允許用戶用手機號或郵箱注冊,username 字段存儲的可能是手機號或郵箱前綴,這會導致用戶記不住自己的用戶名。

解決方案: 自定義認證后端,讓系統(tǒng)同時匹配手機號、郵箱和用戶名。

2.2 實現(xiàn)代碼

apps/users/ 目錄下創(chuàng)建 backends.py

from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from .models import User


class MultiFieldAuthBackend(ModelBackend):
    """
    支持手機號 / 郵箱 / 用戶名 登錄的自定義認證后端
    """
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            # 使用 Q 對象進行 OR 查詢,同時匹配三個字段
            user = User.objects.get(
                Q(username=username) |
                Q(email=username) |
                Q(phone=username)
            )
        except User.DoesNotExist:
            return None

        # 校驗密碼(Django 自動處理密碼哈希比對)
        if user.check_password(password):
            return user
        return None

2.3 工作原理

Django 調(diào)用 authenticate() 時會遍歷所有配置的認證后端。我們的自定義后端優(yōu)先嘗試匹配手機號、郵箱或用戶名,只要用戶輸入的是三者之一,都能找到對應賬戶。

2.4 注冊后端

django_ecommerce/settings.py 中聲明:

AUTHENTICATION_BACKENDS = [
    'users.backends.MultiFieldAuthBackend',      # 自定義后端(優(yōu)先)
    'django.contrib.auth.backends.ModelBackend', # 保留默認后端
]

三、登錄表單

apps/users/forms.py 中追加登錄表單:

from django import forms


class LoginForm(forms.Form):
    username = forms.CharField(
        label='賬號',
        widget=forms.TextInput(attrs={
            'class': 'form-control',
            'placeholder': '手機號 / 郵箱 / 用戶名'
        })
    )
    password = forms.CharField(
        label='密碼',
        widget=forms.PasswordInput(attrs={
            'class': 'form-control',
            'placeholder': '請輸入密碼'
        })
    )
    remember = forms.BooleanField(
        label='記住我',
        required=False,
        widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
    )

設(shè)計思路: 表單只負責收集和基礎(chǔ)校驗,真正的認證邏輯交給視圖處理,保持關(guān)注點分離。


四、登錄視圖

編輯 apps/users/views.py,添加登錄邏輯:

from django.contrib.auth import authenticate, login, logout
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import LoginForm


def user_login(request):
    # 已登錄用戶直接重定向到首頁
    if request.user.is_authenticated:
        return redirect('home')

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

            # 調(diào)用自定義后端進行認證
            user = authenticate(request, username=username, password=password)

            if user is not None:
                if user.is_active:
                    # 執(zhí)行登錄,將用戶寫入 session
                    login(request, user)

                    # 處理"記住我"邏輯
                    if remember:
                        # Session 有效期 30 天
                        request.session.set_expiry(60 * 60 * 24 * 30)
                    else:
                        # 瀏覽器關(guān)閉即失效
                        request.session.set_expiry(0)

                    # 跳轉(zhuǎn)到 next 參數(shù)指定的頁面,否則回首頁
                    next_url = request.GET.get('next', 'home')
                    messages.success(request, f'歡迎回來,{user.username}!')
                    return redirect(next_url)
                else:
                    messages.error(request, '該賬號已被禁用,請聯(lián)系管理員。')
            else:
                messages.error(request, '賬號或密碼錯誤,請重試。')
    else:
        form = LoginForm()

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

核心要點解析

要點 說明
request.user.is_authenticated 檢查用戶是否已登錄,避免重復登錄
authenticate() 自動調(diào)用我們自定義的 MultiFieldAuthBackend
login(request, user) 將認證信息寫入 session,完成登錄
set_expiry() 控制 session 有效期,實現(xiàn)"記住我"
next 參數(shù) Django 內(nèi)置跳轉(zhuǎn)機制,登錄后自動返回原頁面

五、登出視圖

繼續(xù)在 views.py 末尾添加,邏輯非常簡單:

def user_logout(request):
    logout(request)  # 清除當前 session 中的所有認證數(shù)據(jù)
    messages.success(request, '您已成功退出登錄。')
    return redirect('home')

六、配置 URL 路由

編輯 apps/users/urls.py,追加登錄和登出路由:

from django.urls import path
from . import views

urlpatterns = [
    path('register/', views.register, name='register'),
    path('send_sms/', views.send_sms_code, name='send_sms'),
    path('activate/<int:user_id>/<str:token>/', views.activate_email, name='activate_email'),
    path('login/', views.user_login, name='login'),
    path('logout/', views.user_logout, name='logout'),
]

七、登錄頁面模板

創(chuàng)建 apps/users/templates/users/login.html

{% extends 'base.html' %}
{% block title %}用戶登錄{% endblock %}

{% block content %}
<div class="row justify-content-center">
    <div class="col-md-6 col-lg-5">
        <div class="card shadow-sm">
            <div class="card-body p-4">
                <h3 class="text-center mb-4">?? 用戶登錄</h3>

                <form method="post" novalidate>
                    {% csrf_token %}

                    <!-- 全局錯誤提示 -->
                    {% if form.non_field_errors %}
                        <div class="alert alert-danger">
                            {{ form.non_field_errors.0 }}
                        </div>
                    {% endif %}

                    <!-- 賬號輸入 -->
                    <div class="mb-3">
                        <label class="form-label">{{ form.username.label }}</label>
                        {{ form.username }}
                        {% if form.username.errors %}
                            <div class="text-danger small">{{ form.username.errors.0 }}</div>
                        {% endif %}
                    </div>

                    <!-- 密碼輸入 -->
                    <div class="mb-3">
                        <label class="form-label">{{ form.password.label }}</label>
                        {{ form.password }}
                        {% if form.password.errors %}
                            <div class="text-danger small">{{ form.password.errors.0 }}</div>
                        {% endif %}
                    </div>

                    <!-- 記住我 -->
                    <div class="mb-3 form-check">
                        {{ form.remember }}
                        <label class="form-check-label" for="{{ form.remember.id_for_label }}">
                            {{ form.remember.label }}
                        </label>
                    </div>

                    <button type="submit" class="btn btn-primary w-100">登錄</button>
                </form>

                <p class="text-center mt-3">
                    還沒有賬號?<a href="{% url 'users:register' %}">立即注冊</a>
                </p>
            </div>
        </div>
    </div>
</div>
{% endblock %}

八、更新導航欄(動態(tài)切換)

打開 templates/base.html,替換導航欄的用戶相關(guān)部分:

<ul class="navbar-nav ms-auto">
    <li class="nav-item">
        <a class="nav-link" href="{% url 'home' %}">首頁</a>
    </li>
    <li class="nav-item">
        <a class="nav-link" href="#">商品</a>
    </li>
    <li class="nav-item">
        <a class="nav-link" href="#">購物車</a>
    </li>
    
    {% if user.is_authenticated %}
        <!-- 已登錄:顯示用戶下拉菜單 -->
        <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
                ?? {{ user.username }}
            </a>
            <ul class="dropdown-menu dropdown-menu-end">
                <li><a class="dropdown-item" href="#">個人中心</a></li>
                <li><a class="dropdown-item" href="#">我的訂單</a></li>
                <li><hr class="dropdown-divider"></li>
                <li><a class="dropdown-item" href="{% url 'admin:index' %}">后臺管理</a></li>
                <li><a class="dropdown-item" href="{% url 'users:logout' %}">退出登錄</a></li>
            </ul>
        </li>
    {% else %}
        <!-- 未登錄:顯示登錄/注冊入口 -->
        <li class="nav-item">
            <a class="nav-link" href="{% url 'users:login' %}">登錄</a>
        </li>
        <li class="nav-item">
            <a class="nav-link" href="{% url 'users:register' %}">注冊</a>
        </li>
    {% endif %}
</ul>

效果: 未登錄時顯示"登錄"和"注冊",登錄后顯示用戶名下拉菜單,包含個人中心、訂單、后臺管理、退出登錄等入口。


九、完整流程測試

啟動開發(fā)服務(wù)器:

python manage.py runserver

9.1 手機號登錄測試

  1. 訪問 http://127.0.0.1:8000/users/login/

  2. 輸入手機號 13800138000(上一篇注冊的手機號)和密碼

  3. 勾選"記住我",點擊登錄

預期結(jié)果:

  • 終端輸出 302 重定向狀態(tài)碼

  • 瀏覽器跳轉(zhuǎn)到首頁

  • 導航欄顯示 ?? 13800138000 下拉菜單

  • 頁面頂部顯示綠色消息:"歡迎回來,13800138000!"

9.2 郵箱登錄測試

  1. 點擊導航欄"退出登錄",確認看到成功提示

  2. 重新進入登錄頁,輸入郵箱 test@example.com 和密碼

  3. 點擊登錄

預期結(jié)果: 同樣登錄成功,驗證自定義后端對郵箱的匹配支持。

9.3 錯誤情況測試

輸入錯誤的密碼,頁面應顯示紅色提示:

賬號或密碼錯誤,請重試。

終端返回 200 狀態(tài)碼,表示停留在登錄頁并展示錯誤信息。

9.4 登出流程測試

點擊"退出登錄":

  • 終端輸出 302 重定向

  • 回到首頁,導航欄恢復為"登錄"和"注冊"

  • 頁面顯示綠色消息:"您已成功退出登錄。"


十、未登錄訪問保護(預告)

某些頁面(如個人中心、購物車)必須登錄才能訪問。Django 提供了 login_required 裝飾器,后續(xù)我們會這樣使用:

from django.contrib.auth.decorators import login_required

@login_required(login_url='users:login')
def personal_center(request):
    ...

當未登錄用戶訪問被保護頁面時,系統(tǒng)會自動跳轉(zhuǎn)到登錄頁,并帶上 ?next=/原路徑 參數(shù)。這正是我們在登錄視圖中已經(jīng)支持的 next 跳轉(zhuǎn)機制,登錄后自動返回原頁面。


十一、總結(jié)與下集預告

本篇完成清單

  • ? 自定義認證后端:支持手機號/郵箱/用戶名三種方式登錄

  • ? 登錄視圖:集成"記住我"功能和 next 智能跳轉(zhuǎn)

  • ? 登出功能:一行代碼清除 session

  • ? 導航欄動態(tài)切換:根據(jù)登錄狀態(tài)顯示不同菜單

  • ? 完整測試:覆蓋登錄、登出、錯誤提示等全部流程

身份認證的基礎(chǔ)已經(jīng)打牢。從下一篇開始,我們將進入用戶體系的深化部分。

下集預告

第 8 篇:個人中心頁面

  • 用戶信息展示

  • 修改密碼

  • 更換手機號/郵箱

讓用戶真正擁有自己的"小窩"。


?? 想了解更多? 搜索公眾號、今日頭條「IT策士」,一起升級 IT 思維!


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容