Python 项目:Django 构建Web应用程序添加用户账户(二)

简介: Python 项目:Django 构建Web应用程序添加用户账户

注册页面

下面就需要创建一个让新用户能够注册的页面。接下来将使用 Django 提供的表单 UserCreationForm ,并且需要编写自己的视图函数和模版。


  1. 注册页面的 URL 模式

在 users/urls.py 中添加定义了注册页面的 URL 模式,如下:

'''为应用 users 定义 URL 模式'''
from django.conf.urls import url
from django.urls import path
from django.contrib.auth import login
from django.contrib.auth.views import LoginView
from . import views 
urlpatterns = [
    #登陆页面
    url(r'^login/$',LoginView.as_view(template_name='users/login.html'),name='login'),
    #注销页面
    url(r'^logout/$', views.logout_view, name='logout'),
    #注册页面
    url(r'^register/$', views.register, name='register'),
]


这个添加的模式与 URL http://localhost:8000/users/register/ 匹配。


  1. 视图函数 register()
在 users/views.py 中添加对应的视图函数 register(),如下:
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.contrib.auth import logout, login, authenticate
from django.contrib.auth.forms import UserCreationForm
# Create your views here.
def logout_view(request):
    '''注销用户'''
    logout(request)
    return HttpResponseRedirect(reverse('learning_logs:index'))
def register(request):
    '''注册新用户'''
    if request.method != 'POST':
        '''显示空的注册表'''
        form = UserCreationForm()
    else:
        # 处理填写好的表单
        form = UserCreationForm(data=request.POST)
        if form.is_valid():
            new_user = form.save()
            # 让用户自动登录
            authenticated_user = authenticated(username=new_user.username, password=request.POST['password'])
            login(request, authenticated_user)
            return HttpResponseRedirect(reverse('learning_logs:index'))
    context = {'form':form}
    return render(request, 'users/register.html', context)


  1. 注册模版

对应就需要在 login.html 的同级目录下新建 register.html 模版:

{% extends "learning_logs/base.html" %}
{% block content %}
  <form method="post" action="{% url 'users:register' %}">
    {% csrf_token %}
  {{ form.as_p }}
  <button name="submit">register</button>
  <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
  </form>
{% endblock content %}


  1. 链接到注册页面

接下来就需要在用户没有登陆时提供新用户注册的链接,所以在 base.html 中添加注册页面的链接:

<p>
  <a href="{% url 'learning_logs:index' %}">Learning Log</a>
  <a href="{% url 'learning_logs:topics' %}">Topics</a>
  {% if user.is_authenticated %}
    Hello, {{ user.username }}.
  <a href="{% url 'users:logout' %}">log out</a>
  {% else %}
  <a href="{% url 'users:register' %}">register</a>
    <a href="{% url 'users:login' %}">log in</a>
  {% endif %}
</p>
{% block content %}{% endblock content %}


  1. 运行

20201203073926886.png20201203080058412.png20201203213113236.png

让用户拥有自己的数据

用户应该能够输入其专用的数据,所以需要创建一个系统,确定各项数据所属的用户,然后限制对页面的访问,让用户只能使用自己的数据。在这里,将修改模型 Topic ,使得每个主题都归属于特定用户。这也将影响条目,所以每个条目都属于特定的主题,因此,需要先限制一些页面的访问。


使用 @login_required 限制访问

Django 提供了装饰器 @login_required ,能够轻松实现这样的目的:对于某些页面,只允许已登录的用户访问它们。

装饰器(decorator)是放在函数定义前面的指令,Python 在函数运行钱,根据它来修改函数代码的行为。


  1. 限制对 topics 页面的访问

每个主题都归特定用户所有,因此应只允许已登录的用户请求 topics 页面。为此,在 learning_logs/views.py 中添加如下代码:

from django.contrib.auth.decorators import login_required
...
@login_required    
def topics(request):
    '''显示所有的主题'''
    topics = Topic.objects.order_by("date_added")
    context = {'topics':topics}
    return render(request, 'learning_logs/topics.html', context)
...


首先导入函数 login_required() 。将该函数作为装饰器用于视图函数 topics() 中。在 login_required 前面加上 @ 符号后放置到函数声明前面,会使得 Python 在运行 topics() 的代码前先运行 login_required() 的代码。而 login_required() 的代码是用于检查用户是否处于登录状态的。

login_required() 将检查用户是否已登录,仅当用户已登录时,Django 才运行 topics() 的代码。如果用户未登录,则会重定向到登录页面。


为了实现这种重定向,我们需要修改 settings.py ,让 Django 知道到哪里去查找登录页面。所以可以在 settings.py 末尾添加如下:


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = '/static/'
LOGIN_URL = '/users/login/'


  1. 全面限制对项目 “学习笔记” 的访问

Django 使得能够轻松地限制对页面的访问,但是必须针对要保护哪些页面做出决定。最好先确定项目的哪些页面不需要保护,再限制对其他所有页面的访问。这样可以轻松修改过于严格的访问限制,其风险比不限制对敏感页面的访问更低。

在项目“学习笔记”中,不需要限制访问的页面有:主页、注册页面、注销页面、

所以在下面的 learning_logs/views.py 中,对除了 index() 外的每个视图都应用了装饰器 @login_required.



@login_required
def topics(request):
  ...
@login_required    
def topic(request, topic_id):
  ...
@login_required
def new_topic(request):
  ...
@login_required
def new_entry(request, topic_id):
  ...
@login_required
def edit_entry(request, entry_id):
  ...


将数据关联到用户

现在,需要将数据关联到提交它们的用户。我们只需要将最高层的数据关联到用户,这样更底层的数据将自动关联到用户。所以,在项目“学习笔记”中,应用程序的最高层数据是主题,而所有条目都与特定主题相关联。只要每个主题都归属于特定用户,我们能够确定数据库中每个条目的所有者。

下面来修改模型 Topic ,在其中添加一个关联到用户的外键。这样修改后,我们需要修改数据库并进行迁移,并且还需要使用视图进行修改,使其只显示与当前登录的用户相关联的数据。


  1. 修改模型 Topic

对 models.py 的修改如下:

from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Topic(models.Model):
    '''用户学习的主题'''
    text = models.CharField(max_length=200)
    date_added = models.DateTimeField(auto_now_add=True)
    # 添加归属用户
    owner = models.ForeignKey(User,on_delete=models.CASCADE,)
    def __str__(self):
        '''返回模型的字符串表示'''
        return self.text
class Entry(models.Model):
    '''学到的有关主题的具体知识'''
    topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
    text = models.TextField()
    date_added = models.DateTimeField(auto_now_add=True)
    class Meta:
        verbose_name_plural = 'entries'
    def __str__(self):
        '''返回模型的字符串表示'''
        return self.text[:50] + "..."


首先,导入 django.contrib.auth.models 中的模型 User ,然后在 Topic 中添加字段 owner ,建立到模型 User 的外键关系。


  1. 确定当前有哪些用户

需要迁移数据库,Django 将对数据库进行修改,使得其能够存储主题和用户之间的关联。为执行迁移,Django 需要知道该将各个既有主题关联到哪个用户。最简单的办法就是将既有主题都关联到同一用户(如:超级用户)。为此,需要知道该用户的 ID。

查看已创建的所有用户 ID,启动 Django shell ,如下:

<...>py.exe .\manage.py shell
Python 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> User.objects.all()
<QuerySet [<User: 11_admin>, <User: new_user>, <User: tt_user>, <User: zy_user>]>
>>> for user in User.objects.all():
...     print(user.username, user.id)
...
11_admin 1
new_user 2
tt_user 3
zy_user 4
>>>


这里,我们遍历打印了 User模型中的用户名和ID,其中得到4个用户,其中 11_admin 1 是超级用户。


  1. 迁移数据库

当知道用户 ID 后,就可以执行操作迁移数据库了:

<.../Scripts> py.exe .\manage.py makemigrations learning_logs
System check identified some issues:
WARNINGS:
?: (2_0.W001) Your URL pattern '^edit_entry/(?P<entry_id>\d+)/$' [name='edit_entry'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^new_entry/(?P<topic_id>\d+)/$' [name='new_entry'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^new_topic/$' [name='new_topic'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^topics/$' [name='topics'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^topics/(?P<topic_id>\d+)/$' [name='topic'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
You are trying to add a non-nullable field 'owner' to topic without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> 1
Migrations for 'learning_logs':
  learning_logs\migrations\0003_topic_owner.py
    - Add field owner to topic


现在可以执行迁移,如下:


<...\Scripts> py.exe .\manage.py migrate
System check identified some issues:
WARNINGS:
?: (2_0.W001) Your URL pattern '^edit_entry/(?P<entry_id>\d+)/$' [name='edit_entry'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^new_entry/(?P<topic_id>\d+)/$' [name='new_entry'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^new_topic/$' [name='new_topic'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^topics/$' [name='topics'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^topics/(?P<topic_id>\d+)/$' [name='topic'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, learning_logs, sessions
Running migrations:
  Applying learning_logs.0003_topic_owner... OK


Django 应用新的迁移,结果一切顺利。为了验证迁移符合预期,可以使用 Django shell 打印如下输入:


<...\Scripts> py.exe .\manage.py shell
Python 3.8.3 (tags/v3.8.3:6f8c832, May 13 2020, 22:37:02) [MSC v.1924 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from learning_logs.models import Topic
>>> for topic in Topic.objects.all():
...     print(topic, topic.owner)
...
Chess 11_admin
Rock Climbing 11_admin
Sports 11_admin
Sports 11_admin


从 learning_logs.models 中导入 Topic ,再遍历所有的既有主题,并打印每个主题及其所属的用户。所以之前的所有主题现在都属于了 11_admin 这个用户了。


只允许用户访问自己的主题

当前,不管你以哪个用户的身份登陆,都能够看到所有的主题。现在就来改变这样的情况,只允许用户访问并显示属于自己的主题。


在 views.py 中,对函数 topics() 做如下修改:


@login_required
def topics(request):
    '''显示所有的主题'''
    #topics = Topic.objects.order_by("date_added")
    topics = Topic.objects.filter(owner=request.user).order_by('date_added')
    context = {'topics':topics}
    return render(request, 'learning_logs/topics.html', context)


保护用户的主题

我们还没有限制对显示单个主题的页面的访问,因此,目前所有的已登录的用户都可以输入类似于 http://localhost:8000/topics/1/ 的 URL ,来访问显示相应主题的页面。

为了修复这个问题,在视图函数 topic() 获取请求的条目前执行检查:


from django.http import HttpResponseRedirect, Http404
@login_required    
def topic(request, topic_id):
    '''显示单个主题的所有内容'''
    topic = Topic.objects.get(id=topic_id)
    #确认请求的主题属于当前用户
    if topic.owner != request.user:
        raise Http404
    entries = topic.entry_set.order_by("-date_added")
    context = {'topic' : topic, 'entries' : entries}
    return render(request, 'learning_logs/topic.html', context)


服务器上没用请求的资源时,标准的做法就是返回 404 响应。所以,在这里,如果当前用户不是这个主题的所属用户,则返回404页面。


保护页面 edit_entry

页面 edit_entry 的 URL 为 http://localhost:8000/edit_entry/entry_id/,其中 entry_id 是一个数字,下面需要来保护这个页面,禁止用户通过输入类似前面的特定 URL 访问其他用户的条目:


 

@login_required
def edit_entry(request, entry_id):
    '''编辑已有条目'''
    entry = Entry.objects.get(id=entry_id)
    topic = entry.topic
    #检查当前用户
    if topic.owner != request.user:
        raise Http404
    if request.method != 'POST':
        '''初次请求,使用当前条目填充表单'''
        form = EntryForm(instance=entry)
    else:
        '''POST 提交的数据,对数据进行处理'''
        form = EntryForm(instance=entry, data=request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic.id]))
    context = {'entry':entry, 'topic':topic, 'form':form}
    return render(request, 'learning_logs/edit_entry.html', context)


将新主题关联到当前用户

当前,用于添加新主题的页面是存在问题的,因为新创建的新主题是无法关联到自己的。所以需要修改 new_topic() 视图函数:



@login_required
def new_topic(request):
    '''添加新主题'''
    if request.method != 'POST':
        '''未提交数据:创建空表单'''
        form = TopicForm()
    else:
        '''POST 提交数据,对数据进行处理'''
        form = TopicForm(request.POST)
        if form.is_valid():
            new_topic = form.save(commit=False)
            new_topic.owner = request.user
            new_topic.save()
            #form.save()
            return HttpResponseRedirect(reverse('learning_logs:topics'))
    context = {'form':form}
    return render(request, 'learning_logs/new_topic.html', context)

我们首先调用 form.save() ,并且传递实参 commit=False ,这里因为先修改了新主题,再将其保存到数据库中。接下来,将新主题的 owner 属性设置为当前用户,最后,对刚刚定义的主题实例调用 save() 。修改后,这个问题就得到了解决。


最终运行效果

<...\Scripts> py.exe .\manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified some issues:
WARNINGS:
?: (2_0.W001) Your URL pattern '^edit_entry/(?P<entry_id>\d+)/$' [name='edit_entry'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^new_entry/(?P<topic_id>\d+)/$' [name='new_entry'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^new_topic/$' [name='new_topic'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^topics/$' [name='topics'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
?: (2_0.W001) Your URL pattern '^topics/(?P<topic_id>\d+)/$' [name='topic'] has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path().
System check identified 5 issues (0 silenced).
December 03, 2020 - 22:48:58
Django version 3.1.3, using settings 'learning_log.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

2020120322522220.png20201203225242420.png20201203225252862.png20201203225339132.png20201203225351685.png20201203225402906.png2020120322543662.png20201203225448391.png


小结

通过本篇内容的记录和学习,现在总结一下本篇的主要内容:


  • 使用 Django 管理用户账户
  • 简历简单用户身份验证和账户注册系统
  • Django URL 模式创建自己的Web应用程序
相关文章
|
13天前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
96 44
|
2天前
|
存储 数据库连接 API
Python环境变量在开发和运行Python应用程序时起着重要的作用
Python环境变量在开发和运行Python应用程序时起着重要的作用
28 15
|
1天前
|
关系型数据库 数据库 数据安全/隐私保护
Python Web开发
Python Web开发
14 6
|
6天前
|
开发框架 前端开发 JavaScript
利用Python和Flask构建轻量级Web应用的实战指南
利用Python和Flask构建轻量级Web应用的实战指南
18 2
|
11天前
|
JavaScript 前端开发 开发工具
web项目规范配置(husky、eslint、lint-staged、commit)
通过上述配置,可以确保在Web项目开发过程中自动进行代码质量检查和规范化提交。Husky、ESLint、lint-staged和Commitlint共同作用,使得每次提交代码之前都会自动检查代码风格和语法问题,防止不符合规范的代码进入代码库。这不仅提高了代码质量,还保证了团队协作中的一致性。希望这些配置指南能帮助你建立高效的开发流程。
25 5
|
9天前
|
前端开发 API 开发者
Python Web开发者必看!AJAX、Fetch API实战技巧,让前后端交互如丝般顺滑!
在Web开发中,前后端的高效交互是提升用户体验的关键。本文通过一个基于Flask框架的博客系统实战案例,详细介绍了如何使用AJAX和Fetch API实现不刷新页面查看评论的功能。从后端路由设置到前端请求处理,全面展示了这两种技术的应用技巧,帮助Python Web开发者提升项目质量和开发效率。
22 1
|
14天前
|
安全 数据库 开发者
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第26天】本文详细介绍了如何在Django框架下进行全栈开发,包括环境安装与配置、创建项目和应用、定义模型类、运行数据库迁移、创建视图和URL映射、编写模板以及启动开发服务器等步骤,并通过示例代码展示了具体实现过程。
28 2
|
15天前
|
JSON API 数据格式
如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架
本文介绍了如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架,适合小型项目和微服务。文章从环境准备、创建基本Flask应用、定义资源和路由、请求和响应处理、错误处理等方面进行了详细说明,并提供了示例代码。通过这些步骤,读者可以快速上手构建自己的RESTful API。
24 2
|
17天前
|
JavaScript 前端开发 数据安全/隐私保护
Web开发者必看:手把手教你如何轻松播放m3u8流地址,解锁视频播放新技能,让你的项目更上一层楼!
【10月更文挑战第23天】随着互联网技术的发展,m3u8格式因良好的兼容性和高压缩率被广泛用于网络流媒体传输。本文介绍如何在Web端播放m3u8流地址,包括引入视频播放器(如Video.js)、创建播放器容器、初始化播放器及播放m3u8流的具体步骤。此外,还涉及处理加密m3u8流的示例。
58 1
|
17天前
|
Kubernetes 网络协议 Python
Python网络编程:从Socket到Web应用
在信息时代,网络编程是软件开发的重要组成部分。Python作为多用途编程语言,提供了从Socket编程到Web应用开发的强大支持。本文将从基础的Socket编程入手,逐步深入到复杂的Web应用开发,涵盖Flask、Django等框架的应用,以及异步Web编程和微服务架构。通过本文,读者将全面了解Python在网络编程领域的应用。
17 1

热门文章

最新文章