分享在球館預約系統中使用 Django的LoginRequiredMixin 控制訪問權限的經驗與解決方案。從繼承順序錯誤、登入頁面設定問題到權限精細控制,文章提供了程式碼示例和實用技巧,幫助開發者有效實現用戶身份驗證功能。
這個球館預約系統包含以下主要功能:
- 公告 (無需登入)
- 瀏覽可用場地(需要登入)
- 預約場地(需要登入)
- 查詢個人預約記錄(需要登入)
- 修改或取消預約(需要登入)
- 聯絡我們 (無需登入)
- 管理者後臺管理 (需登入且驗證帳號權限)
為了實現這些功能,我選擇使用 Django 的基於類的視圖(Class-Based Views)並結合 LoginRequiredMixin 來控制訪問權限。
LoginRequiredMixin 是 Django 提供的一個混入類,用於限制未登入用戶訪問特定視圖。它相當於函數視圖中的 @login_required 裝飾器,但專門用於基於類的視圖。
首先,為預約功能創建了基本的視圖:
# views.py
from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView
from .models import Booking
class BookingListView(ListView):
model = Booking
template_name = 'bookings/booking_list.html'
context_object_name = 'bookings'
class BookingCreateView(CreateView):
model = Booking
template_name = 'bookings/booking_create.html'
fields = ['court', 'date', 'start_time', 'end_time']
success_url = '/bookings/'
class BookingDetailView(DetailView):
model = Booking
template_name = 'bookings/booking_detail.html'
接下來,我需要確保只有登入用戶才能訪問預約功能,所以我嘗試使用 LoginRequiredMixin:
# views.py
from django.views.generic import ListView, CreateView, DetailView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Booking
class BookingListView(LoginRequiredMixin, ListView):
model = Booking
template_name = 'bookings/booking_list.html'
context_object_name = 'bookings'
def get_queryset(self):
return Booking.objects.filter(user=self.request.user)
class BookingCreateView(LoginRequiredMixin, CreateView):
model = Booking
template_name = 'bookings/booking_create.html'
fields = ['court', 'date', 'start_time', 'end_time']
success_url = '/bookings/'
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
class BookingDetailView(LoginRequiredMixin, DetailView):
model = Booking
template_name = 'bookings/booking_detail.html'
我第一次實作時,不小心寫成了這樣:
# 錯誤的寫法
class BookingCreateView(CreateView, LoginRequiredMixin): # 錯誤的順序!
# ...
結果遇到了奇怪的錯誤:視圖不會重定向到登入頁面,而是直接顯示 HTTP 500 錯誤。
解決方案:
在 Python 的多重繼承中,方法解析順序(Method Resolution Order,MRO)是從左到右的。LoginRequiredMixin 必須放在左側,確保其 dispatch() 方法先於視圖的 dispatch() 方法被調用:
# 正確的寫法
class BookingCreateView(LoginRequiredMixin, CreateView): # 正確的順序!
# ...
當我修正了繼承順序後,系統開始正確地攔截未登入用戶,但出現了新錯誤:
NoReverseMatch at /bookings/create/
Reverse for 'login' not found. 'login' is not a valid view function or pattern name.
或者在某些情況下會看到:
Page not found (404)
Request URL: http://localhost:8000/accounts/login/?next=/bookings/create/
這是因為 LoginRequiredMixin 默認會將未登入用戶重定向到 settings.LOGIN_URL,而 Django 的默認值是 'accounts/login/'。但我的項目中沒有這個 URL!
解決方案:
settings.py 中指定登入頁面的 URL:# settings.py
LOGIN_URL = 'login' # 使用 URL 名稱
# 或者
LOGIN_URL = '/users/login/' # 使用路徑
urls.py 中定義了相應的 URL:# urls.py
from django.urls import path
from django.contrib.auth import views as auth_views
urlpatterns = [
# ...
path('users/login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'),
# ...
]
users/login.html:<!-- users/login.html -->
{% extends 'base.html' %}
{% block content %}
<div class="login-form">
<h2>登入</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">登入</button>
</form>
<p>還沒有帳號?<a href="{% url 'register' %}">註冊</a></p>
</div>
{% endblock %}
解決了登入頁面問題後,我發現當用戶登入成功,系統不會自動跳轉回他們原本想訪問的頁面。
解決方案:
確保在登入視圖中處理 next 參數:
# views.py
from django.contrib.auth.views import LoginView
from django.urls import reverse_lazy
class CustomLoginView(LoginView):
template_name = 'users/login.html'
redirect_authenticated_user = True
def get_success_url(self):
next_url = self.request.GET.get('next')
if next_url:
return next_url
return reverse_lazy('home') # 默認重定向到首頁
然後在 urls.py 中使用這個自定義視圖:
# urls.py
from .views import CustomLoginView
urlpatterns = [
# ...
path('users/login/', CustomLoginView.as_view(), name='login'),
# ...
]
有時候我們需要在不同視圖中指定不同的登入重定向 URL:
class BookingCreateView(LoginRequiredMixin, CreateView):
login_url = '/custom-login/' # 自定義登入頁面
redirect_field_name = 'redirect_to' # 自定義重定向參數名(默認是 'next')
# ...
除了基本的登入限制外,有時我需要更精細的權限控制,例如確保用戶只能查看和修改自己的預約。這時可以結合使用 UserPassesTestMixin:
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class BookingUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Booking
template_name = 'bookings/booking_update.html'
fields = ['date', 'start_time', 'end_time']
def test_func(self):
booking = self.get_object()
return booking.user == self.request.user # 只允許預約的創建者修改
def handle_no_permission(self):
# 自定義權限拒絕時的行為
return HttpResponseForbidden("您沒有權限修改這個預約")
Django 的 LoginRequiredMixin 是實現用戶身份驗證的強大工具,但正確使用它需要注意幾個關鍵點:
LoginRequiredMixin 必須放在最左側LOGIN_URL 並創建了相應的登入視圖和模板UserPassesTestMixin通過這些步驟,我成功實現了球館預約系統的用戶身份驗證功能,確保只有登入用戶才能預約場地和管理自己的預約記錄。
參考資料:
- Django 官方文檔:LoginRequiredMixin
- Django 官方文檔:Authentication in Web requests
- Django 官方文檔:Class-based views