歡迎回來,大家!在上一部分中,我們為 Django 部落格應用程式建立了安全的用戶註冊流程。然而,註冊成功後,我們被重定向到主頁。一旦我們實現用戶身份驗證,這種行為就會被修改。使用者身份驗證可確保只有授權使用者才能存取某些功能並保護敏感資訊。
在本系列中,我們將在以下實體關係圖 (ERD) 的指導下建立一個完整的部落格應用程式。這次,我們的重點將是建立安全的使用者身份驗證流程。如果您覺得此內容有幫助,請按讚、評論和訂閱,以便在下一部分發佈時保持更新。
這是我們實現登入功能後登入頁面外觀的預覽。如果您還沒有閱讀本系列的前面部分,我建議您這樣做,因為本教學是前面步驟的延續。
好的,我們開始吧! !
Django 附帶了一個名為 contrib.auth 的內建應用程序,它簡化了我們處理用戶身份驗證的過程。你可以檢查 blog_env/settings.py 文件,在 INSTALLED_APPS 下,你會看到 auth 已經列出了。
# django_project/settings.py INSTALLED_APPS = [ # "django.contrib.admin", "django.contrib.auth", # <p>auth應用程式為我們提供了多種身份驗證視圖,用於處理登入、登出、密碼變更、密碼重設等。這意味著基本的身份驗證功能,例如使用者登入、註冊和權限,無需使用即可使用從頭開始建立一切。 </p> <p>在本教程中,我們將僅關注登入和登出視圖,並在本系列的後續部分中介紹其餘視圖。 </p> <h2> 1. 建立登入表單 </h2> <p>依照我們的 TDD 方法,我們先為登入表單建立測試。由於我們還沒有建立登入表單,因此導航到 users/forms.py 檔案並建立一個繼承自 AuthenticationForm 的新類別。 <br> </p> <pre class="brush:php;toolbar:false"># users/forms.py from django.contrib.auth import AuthenticationForm class LoginForm(AuthenticationForm):
定義表單後,我們可以在 users/tests/test_forms.py 中新增測試案例來驗證其功能。
# users/tests/test_forms.py # --- other code class LoginFormTest(TestCase): def setUp(self): self.user = User.objects.create_user( full_name= 'Tester User', email= 'tester@gmail.com', bio= 'new bio for tester', password= 'password12345' ) def test_valid_credentials(self): """ With valid credentials, the form should be valid """ credentials = { 'email': 'tester@gmail.com', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertTrue(form.is_valid()) def test_wrong_credentials(self): """ With wrong credentials, the form should raise Invalid email or password error """ credentials = { 'email': 'tester@gmail.com', 'password': 'wrongpassword', 'remember_me': False } form = LoginForm(data = credentials) self.assertIn('Invalid email or password', str(form.errors['__all__'])) def test_credentials_with_empty_email(self): """ Should raise an error when the email field is empty """ credentials = { 'email': '', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['email'])) def test_credentials_with_empty_password(self): """ Should raise error when the password field is empty """ credentials = { 'email': 'tester@gmail.com', 'password': '', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['password']))
這些測試涵蓋了使用有效憑證成功登入、使用無效憑證登入失敗以及正確處理錯誤訊息等場景。
AuthenticationForm 類別預設提供一些基本的驗證。但是,透過我們的 LoginForm,我們可以自訂其行為並添加任何必要的驗證規則來滿足我們的特定要求。
# django_project/settings.py INSTALLED_APPS = [ # "django.contrib.admin", "django.contrib.auth", # <p>我們建立了一個自訂登入表單,其中包含以下欄位:<strong>電子郵件</strong>、<strong>密碼</strong>和<strong>remember_me</strong>。 Remember_me 複選框可讓使用者跨瀏覽器會話保持登入工作階段。 </p> <p>由於我們的表單擴充了 AuthenticationForm,因此我們覆寫了一些預設行為:</p>
- ** __init__ 方法**:我們已從表單中刪除了預設的使用者名字段,以與我們基於電子郵件的身份驗證保持一致。
- clean() 方法:此方法驗證電子郵件和密碼欄位。如果憑證有效,我們將使用 Django 的內建身份驗證機制對使用者進行身份驗證。
- confirm_login_allowed() 方法:此內建方法提供了登入前額外驗證的機會。如果需要,您可以重寫此方法來實施自訂檢查。 現在我們的測試應該通過:
# users/forms.py from django.contrib.auth import AuthenticationForm class LoginForm(AuthenticationForm):
2. 建立我們的登入視圖
2.1 為登入視圖建立測試
由於我們還沒有登入視圖,所以讓我們導航到 users/views.py 檔案並建立一個繼承自驗證應用程式的 LoginView 的新類別
# users/tests/test_forms.py # --- other code class LoginFormTest(TestCase): def setUp(self): self.user = User.objects.create_user( full_name= 'Tester User', email= 'tester@gmail.com', bio= 'new bio for tester', password= 'password12345' ) def test_valid_credentials(self): """ With valid credentials, the form should be valid """ credentials = { 'email': 'tester@gmail.com', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertTrue(form.is_valid()) def test_wrong_credentials(self): """ With wrong credentials, the form should raise Invalid email or password error """ credentials = { 'email': 'tester@gmail.com', 'password': 'wrongpassword', 'remember_me': False } form = LoginForm(data = credentials) self.assertIn('Invalid email or password', str(form.errors['__all__'])) def test_credentials_with_empty_email(self): """ Should raise an error when the email field is empty """ credentials = { 'email': '', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['email'])) def test_credentials_with_empty_password(self): """ Should raise error when the password field is empty """ credentials = { 'email': 'tester@gmail.com', 'password': '', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['password']))
在 users/tests/test_views.py 檔案的底部加入這些測試案例
# users/forms.py # -- other code from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm # new line from django.contrib.auth import get_user_model, authenticate # new line # --- other code class LoginForm(AuthenticationForm): email = forms.EmailField( required=True, widget=forms.EmailInput(attrs={'placeholder': 'Email','class': 'form-control',}) ) password = forms.CharField( required=True, widget=forms.PasswordInput(attrs={ 'placeholder': 'Password', 'class': 'form-control', 'data-toggle': 'password', 'id': 'password', 'name': 'password', }) ) remember_me = forms.BooleanField(required=False) def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) # Remove username field if 'username' in self.fields: del self.fields['username'] def clean(self): email = self.cleaned_data.get('email') password = self.cleaned_data.get('password') # Authenticate using email and password if email and password: self.user_cache = authenticate(self.request, email=email, password=password) if self.user_cache is None: raise forms.ValidationError("Invalid email or password") else: self.confirm_login_allowed(self.user_cache) return self.cleaned_data class Meta: model = User fields = ('email', 'password', 'remember_me')
我們需要確保這些測試在這個階段失敗。
2.2 建立登入視圖
在文件底部的users/views.py檔案中加入以下程式碼:
(.venv)$ python3 manage.py test users.tests.test_forms Found 9 test(s). Creating test database for alias 'default'... System check identified no issues (0 silenced). ......... ---------------------------------------------------------------------- Ran 9 tests in 3.334s OK Destroying test database for alias 'default'...
在上面的程式碼中,我們完成了以下任務:
- 設定form_class 屬性:我們將自訂的LoginForm 指定為form_class 屬性,因為我們不再使用預設的AuthenticationForm。
- 重寫 form_valid 方法:我們重寫 form_valid 方法,該方法在發布有效的表單資料時呼叫。這允許我們在用戶成功登入後實現自訂行為。
- 處理會話過期:如果使用者沒有選取 Remember_me 框,則會話將在瀏覽器關閉時自動過期。但是,如果選取 Remember_me 框,會話將持續 settings.py 中定義的持續時間。預設會話長度為兩週,但我們可以使用 settings.py 中的 SESSION_COOKIE_AGE 變數修改它。例如,要將 cookie 期限設定為 7 天,我們可以將以下行新增到我們的設定中:
# -- other code from .forms import CustomUserCreationForm, LoginForm from django.contrib.auth import get_user_model, views # -- other code class CustomLoginView(views.LoginForm):
為了連接您的自訂登入功能並允許使用者存取登入頁面,我們將在 users/urls.py 檔案中定義 URL 模式。該文件會將特定的 URL(本例中為 /log_in/)對應到對應的視圖 (CustomLoginView)。此外,我們將使用 Django 的內建 LogoutView 新增註銷功能的路徑。
# django_project/settings.py INSTALLED_APPS = [ # "django.contrib.admin", "django.contrib.auth", # <p>一切似乎都井然有序,但我們應該指定在成功登入和登出後將使用者重定向到何處。為此,我們將使用 LOGIN_REDIRECT_URL 和 LOGOUT_REDIRECT_URL 設定。在 blog_app/settings.py 檔案的底部,新增以下行以將使用者重新導向至主頁:<br> </p> <pre class="brush:php;toolbar:false"># users/forms.py from django.contrib.auth import AuthenticationForm class LoginForm(AuthenticationForm):
現在我們有了登入 URL,讓我們更新 users/views.py 檔案中的 SignUpView,以便在註冊成功時重定向到登入頁面。
# users/tests/test_forms.py # --- other code class LoginFormTest(TestCase): def setUp(self): self.user = User.objects.create_user( full_name= 'Tester User', email= 'tester@gmail.com', bio= 'new bio for tester', password= 'password12345' ) def test_valid_credentials(self): """ With valid credentials, the form should be valid """ credentials = { 'email': 'tester@gmail.com', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertTrue(form.is_valid()) def test_wrong_credentials(self): """ With wrong credentials, the form should raise Invalid email or password error """ credentials = { 'email': 'tester@gmail.com', 'password': 'wrongpassword', 'remember_me': False } form = LoginForm(data = credentials) self.assertIn('Invalid email or password', str(form.errors['__all__'])) def test_credentials_with_empty_email(self): """ Should raise an error when the email field is empty """ credentials = { 'email': '', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['email'])) def test_credentials_with_empty_password(self): """ Should raise error when the password field is empty """ credentials = { 'email': 'tester@gmail.com', 'password': '', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['password']))
我們還將更新我們的 SignUpTexts,特別是 test_signup_ Correct_data(self),以反映新行為並確保我們的更改得到正確測試。
# users/forms.py # -- other code from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm # new line from django.contrib.auth import get_user_model, authenticate # new line # --- other code class LoginForm(AuthenticationForm): email = forms.EmailField( required=True, widget=forms.EmailInput(attrs={'placeholder': 'Email','class': 'form-control',}) ) password = forms.CharField( required=True, widget=forms.PasswordInput(attrs={ 'placeholder': 'Password', 'class': 'form-control', 'data-toggle': 'password', 'id': 'password', 'name': 'password', }) ) remember_me = forms.BooleanField(required=False) def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) # Remove username field if 'username' in self.fields: del self.fields['username'] def clean(self): email = self.cleaned_data.get('email') password = self.cleaned_data.get('password') # Authenticate using email and password if email and password: self.user_cache = authenticate(self.request, email=email, password=password) if self.user_cache is None: raise forms.ValidationError("Invalid email or password") else: self.confirm_login_allowed(self.user_cache) return self.cleaned_data class Meta: model = User fields = ('email', 'password', 'remember_me')
2.3 建立登入模板
然後使用文字編輯器建立一個 users/templates/registration/login.html 檔案並包含以下程式碼:
(.venv)$ python3 manage.py test users.tests.test_forms Found 9 test(s). Creating test database for alias 'default'... System check identified no issues (0 silenced). ......... ---------------------------------------------------------------------- Ran 9 tests in 3.334s OK Destroying test database for alias 'default'...
我們將在本系列後面添加忘記密碼功能,但現在它只是一個死連結。
現在,讓我們更新 layout.html 範本以包含登入、註冊和登出連結。
# -- other code from .forms import CustomUserCreationForm, LoginForm from django.contrib.auth import get_user_model, views # -- other code class CustomLoginView(views.LoginForm):
在我們的模板中,我們檢查使用者是否經過身份驗證。如果用戶已登錄,我們將顯示登出連結和用戶的全名。否則,我們會顯示登入和註冊連結。
現在讓我們執行所有測試
# users/tests/test_views.py # -- other code class LoginTests(TestCase): def setUp(self): User.objects.create_user( full_name= 'Tester User', email= 'tester@gmail.com', bio= 'new bio for tester', password= 'password12345' ) self.valid_credentials = { 'email': 'tester@gmail.com', 'password': 'password12345', 'remember_me': False } def test_login_url(self): """User can navigate to the login page""" response = self.client.get(reverse('users:login')) self.assertEqual(response.status_code, 200) def test_login_template(self): """Login page render the correct template""" response = self.client.get(reverse('users:login')) self.assertTemplateUsed(response, template_name='registration/login.html') self.assertContains(response, '<a class="btn btn-outline-dark text-white" href="/users/sign_up/">Sign Up</a>') def test_login_with_valid_credentials(self): """User should be log in when enter valid credentials""" response = self.client.post(reverse('users:login'), self.valid_credentials, follow=True) self.assertEqual(response.status_code, 200) self.assertRedirects(response, reverse('home')) self.assertTrue(response.context['user'].is_authenticated) self.assertContains(response, '<button type="submit" class="btn btn-danger"><i class="bi bi-door-open-fill"></i> Log out</button>') def test_login_with_wrong_credentials(self): """Get error message when enter wrong credentials""" credentials = { 'email': 'tester@gmail.com', 'password': 'wrongpassword', 'remember_me': False } response = self.client.post(reverse('users:login'), credentials, follow=True) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Invalid email or password') self.assertFalse(response.context['user'].is_authenticated)
3. 測試瀏覽器內的一切是否正常
現在我們已經配置了登入和登出功能,是時候測試網頁瀏覽器中的所有內容了。讓我們啟動開發伺服器
# django_project/settings.py INSTALLED_APPS = [ # "django.contrib.admin", "django.contrib.auth", # <p>導覽至註冊頁面並輸入有效的憑證。成功註冊後,您應該會被重新導向到登入頁面。在登入表單中輸入使用者訊息,登入後點選登出按鈕。然後您應該登出並重定向到主頁。最後,驗證您是否不再登錄,並且註冊和登入連結是否再次顯示。 <br> 一切都很完美,但我注意到,當用戶登入並訪問註冊頁面(http://127.0.0.1:8000/users/sign_up/)時,他們仍然可以存取註冊表單。理想情況下,用戶登入後,他們不應該能夠訪問註冊頁面。 <br> <img src="/static/imghwm/default1.png" data-src="https://img.php.cn/upload/article/000/000/000/172924669044575.jpg?x-oss-process=image/resize,p_40" class="lazy" alt="Guide to Building a Complete Blog App with Django using TDD Methodology and PostgreSQL (Part Secure User Authentication"><br> 這種行為可能會為我們的專案帶來一些安全漏洞。為了解決這個問題,我們需要更新 SignUpView 以將任何登入的使用者重新導向到主頁。 <br> 但首先,讓我們更新 LoginTest 以新增涵蓋該場景的新測試。因此,在 users/tests/test_views.py 中加入此程式碼。 <br> </p> <pre class="brush:php;toolbar:false"># users/forms.py from django.contrib.auth import AuthenticationForm class LoginForm(AuthenticationForm):
現在,我們可以更新我們的 SignUpView
# users/tests/test_forms.py # --- other code class LoginFormTest(TestCase): def setUp(self): self.user = User.objects.create_user( full_name= 'Tester User', email= 'tester@gmail.com', bio= 'new bio for tester', password= 'password12345' ) def test_valid_credentials(self): """ With valid credentials, the form should be valid """ credentials = { 'email': 'tester@gmail.com', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertTrue(form.is_valid()) def test_wrong_credentials(self): """ With wrong credentials, the form should raise Invalid email or password error """ credentials = { 'email': 'tester@gmail.com', 'password': 'wrongpassword', 'remember_me': False } form = LoginForm(data = credentials) self.assertIn('Invalid email or password', str(form.errors['__all__'])) def test_credentials_with_empty_email(self): """ Should raise an error when the email field is empty """ credentials = { 'email': '', 'password': 'password12345', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['email'])) def test_credentials_with_empty_password(self): """ Should raise error when the password field is empty """ credentials = { 'email': 'tester@gmail.com', 'password': '', 'remember_me': False } form = LoginForm(data = credentials) self.assertFalse(form.is_valid()) self.assertIn('This field is required', str(form.errors['password']))
在上面的程式碼中,我們重寫 SignUpView 的dispatch() 方法來重定向任何已登入並嘗試存取註冊頁面的使用者。此重定向將使用我們的settings.py 檔案中設定的 LOGIN_REDIRECT_URL,在本例中指向主頁。
好的!再次執行所有測試以確認我們的更新按預期工作
# users/forms.py # -- other code from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm # new line from django.contrib.auth import get_user_model, authenticate # new line # --- other code class LoginForm(AuthenticationForm): email = forms.EmailField( required=True, widget=forms.EmailInput(attrs={'placeholder': 'Email','class': 'form-control',}) ) password = forms.CharField( required=True, widget=forms.PasswordInput(attrs={ 'placeholder': 'Password', 'class': 'form-control', 'data-toggle': 'password', 'id': 'password', 'name': 'password', }) ) remember_me = forms.BooleanField(required=False) def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) # Remove username field if 'username' in self.fields: del self.fields['username'] def clean(self): email = self.cleaned_data.get('email') password = self.cleaned_data.get('password') # Authenticate using email and password if email and password: self.user_cache = authenticate(self.request, email=email, password=password) if self.user_cache is None: raise forms.ValidationError("Invalid email or password") else: self.confirm_login_allowed(self.user_cache) return self.cleaned_data class Meta: model = User fields = ('email', 'password', 'remember_me')
我知道還有很多事情需要完成,但讓我們花點時間欣賞一下我們迄今為止所取得的成就。我們一起設定了專案環境,連接了 PostgreSQL 資料庫,並為 Django 部落格應用程式實現了安全的使用者註冊和登入系統。在下一部分中,我們將深入建立使用者個人資料頁面,使用戶能夠編輯其資訊並重設密碼!隨著我們繼續我們的 Django 部落格應用程式之旅,請繼續關注更多令人興奮的開發!
我們隨時重視您的回饋。請在下面的評論中分享您的想法、問題或建議。不要忘記按讚、發表評論並訂閱以了解最新動態!
以上是使用 Django 使用 TDD 方法和 PostgreSQL 建立完整部落格應用程式的指南(部分安全用戶身份驗證)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

Python更易學且易用,C 則更強大但複雜。 1.Python語法簡潔,適合初學者,動態類型和自動內存管理使其易用,但可能導致運行時錯誤。 2.C 提供低級控制和高級特性,適合高性能應用,但學習門檻高,需手動管理內存和類型安全。

Python和C 在内存管理和控制方面的差异显著。1.Python使用自动内存管理,基于引用计数和垃圾回收,简化了程序员的工作。2.C 则要求手动管理内存,提供更多控制权但增加了复杂性和出错风险。选择哪种语言应基于项目需求和团队技术栈。

Python在科學計算中的應用包括數據分析、機器學習、數值模擬和可視化。 1.Numpy提供高效的多維數組和數學函數。 2.SciPy擴展Numpy功能,提供優化和線性代數工具。 3.Pandas用於數據處理和分析。 4.Matplotlib用於生成各種圖表和可視化結果。

選擇Python還是C 取決於項目需求:1)Python適合快速開發、數據科學和腳本編寫,因其簡潔語法和豐富庫;2)C 適用於需要高性能和底層控制的場景,如係統編程和遊戲開發,因其編譯型和手動內存管理。

Python在數據科學和機器學習中的應用廣泛,主要依賴於其簡潔性和強大的庫生態系統。 1)Pandas用於數據處理和分析,2)Numpy提供高效的數值計算,3)Scikit-learn用於機器學習模型構建和優化,這些庫讓Python成為數據科學和機器學習的理想工具。

每天學習Python兩個小時是否足夠?這取決於你的目標和學習方法。 1)制定清晰的學習計劃,2)選擇合適的學習資源和方法,3)動手實踐和復習鞏固,可以在這段時間內逐步掌握Python的基本知識和高級功能。

Python在Web開發中的關鍵應用包括使用Django和Flask框架、API開發、數據分析與可視化、機器學習與AI、以及性能優化。 1.Django和Flask框架:Django適合快速開發複雜應用,Flask適用於小型或高度自定義項目。 2.API開發:使用Flask或DjangoRESTFramework構建RESTfulAPI。 3.數據分析與可視化:利用Python處理數據並通過Web界面展示。 4.機器學習與AI:Python用於構建智能Web應用。 5.性能優化:通過異步編程、緩存和代碼優

Python在開發效率上優於C ,但C 在執行性能上更高。 1.Python的簡潔語法和豐富庫提高開發效率。 2.C 的編譯型特性和硬件控制提升執行性能。選擇時需根據項目需求權衡開發速度與執行效率。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SublimeText3漢化版
中文版,非常好用

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

Dreamweaver CS6
視覺化網頁開發工具

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

禪工作室 13.0.1
強大的PHP整合開發環境