欢迎回来,大家!在上一部分中,我们为 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 - 适用于 Windows 的极简 GNU
这个项目正在迁移到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集成开发环境