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

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

文章目录

让用户输入数据

添加新主题

添加新条目

编辑目录

创建用户账户

创建应用程序 `users`

登录页面

注销

注册页面

让用户拥有自己的数据

使用 `@login_required` 限制访问

将数据关联到用户

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

保护用户的主题

保护页面 `edit_entry`

将新主题关联到当前用户

最终运行效果

小结


Web 应用程序的核心是让任何用户都能够注册账户并且能够使用它,不论用户身在何方。

本篇中,将通过创建一些数据库表单,使得用户能够添加主题和条目、编辑已有条目,并且防范基于表单的网页发起的常见攻击。然后,将实现一个用户身份验证系统,创建一个注册页面,方便用户创建账户,并且让有些页面仅仅提供已登录的用户访问。(确保用户数据安全)


项目前身参考 构建简单的Django项目


让用户输入数据

建立用于创建用户账户的身份验证系统前,首先需要添加几个页面以方便用户输入数据。目前,仅有超级用户能够管理网站输入数据,但是不能让用户去使用管理网站,所以需要使用 Django 的表单创建工具来创建让用户能够输入数据的页面。


添加新主题

首先让用户能够添加新主题。创建基于表单的页面方法与之前新建网页的方式一样:定义 URL、编写视图函数、编写对应模版。(这里需要导入包含表单的模块 forms.py)


  1. 用于添加主题的表单

让用户输入并提交信息的页面都是表单(即使看起来不像)。用户输入信息时,需要进行验证,确认提供的信息是正确的数据类型,并且不是恶意的信息,如中断服务器的代码。然后,我们再对这些有效信息进行处理,并将其保存到数据库的合适地方。(这些工作都是由 Django 自动完成的)

在 Django 中,创建表单的最简单的方式就是使用 ModelForm ,它根据项目中定义的模型信息自动创建表单。创建一个名为 forms.py 文件,将其放置到 models.py 同级目录中,其中编写一个表单如下:

from django import forms
from .models import Topic
class TopicForm(forms.ModelForm):
    class Meta:
        model = Topic
        fields = {'text'}
        labels = {'text':''}


这里首先导入了模块 forms 以及使用到的模型 Topic ,定义了一个 TopicForm 的类,继承了 forms.ModelForm 。

在 TopicForm 类中仅仅包含一个内嵌的 Meta 类,告诉 Django 根据哪个模型创建表单,以及在表单中包含哪些字段。在这里的表单中只包含字段 text ,并且让 Django 不要为字段 text 生成对应的标签。


  1. URL模式 new_topic

新建的网页的 URL 应该尖端且具有描述性,所以当用户需要添加新主题时,将切换到 http://localhost:8000/new_topic/ 这个 URL 下。在 urls.py 中添加:

'''定义 learning_logs 的 URL 模式'''
from django.conf.urls import url
from django.urls import path
from . import views 
urlpatterns = [
    #主页
    #url(r'^$', views.index, name='index'),
    path('', views.index, name='index'),
    path(r'^topics/$', views.topics, name='topics'),
    path(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
    #用于添加新主题的网页
    path(r'^new_topic/$', views.new_topic, name='new_tipic'),
]


  1. 添加视图函数 new_topic()

函数 new_topic() 需要处理两种情况:

  • 刚刚进入 new_topic 网页,这时候应该单纯显示一个空表单。
  • 对提交的表单数据进行处理,并使用户重定向到网页 topics 中。

修改 views.py 中的内容


from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .models import Topic
from .forms import TopicForm
# Create your views here.
def index(request):
    '''学习笔记的主页'''
    return render(request, 'learning_logs/index.html')
def topics(request):
    '''显示所有的主题'''
    topics = Topic.objects.order_by("date_added")
    context = {'topics':topics}
    return render(request, 'learning_logs/topics.html', context)
def topic(request, topic_id):
    '''显示单个主题的所有内容'''
    topic = Topic.objects.get(id=topic_id)
    entries = topic.entry_set.order_by("-date_added")
    context = {'topic' : topic, 'entries' : entries}
    return render(request, 'learning_logs/topic.html', context)
def new_topic(request):
    '''添加新主题'''
    if request.method != 'POST':
        '''未提交数据:创建空表单'''
        form = TopicForm()
    else:
        '''POST 提交数据,对数据进行处理'''
        form = TopicForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('learning_logs:topics'))
    context = {'form':form}
    return render(request, 'learning_logs/new_topic.html', context)


这里使用 HttpResponseRedirect 类将用户重定向到网页 topics 中。


  1. GET请求和POST请求

创建 Web 应用程序时,将用到两种主要请求类型:GET 请求、POST 请求。对于只是从服务器读取数据的页面,只需要用到 GET 请求;在用户需要通过表单提交信息时,通常用到 POST 请求。处理所有表单时,都将指定 POST 方法。(此外还有其他类型的请求方式,这里先不说)

函数 new_topic() 将请求对象作为参数。用户初次请求该网页时,浏览器将发送 GET 请求,而当用户填写完信息提交表单时,浏览器会发送 POST 请求。根据请求的类型,我们可以确定用户请求的是空表单还是填写好的表单数据。


  1. 模版 new_topic

下面创建新模版文件 new_topic.html 存放到 base.html 的同级目录下。


{% extends "learning_logs/base.html" %}
{% block content %}
  <p>Add a new topic:</p>
  <form action="{% url 'new_topic' %}" method='post'>
    {% csrf_token %}
    {{ form.as_p }}
  <button name="submit">add topic</button>
  </form>
{% endblock content %}


这个模版基础了 base.html 内容。这里顶一个一个 HTML 表单。实参 action 告诉服务器将要提交的表单数据发送到哪里,这里将数据发回到视图函数 new_topic() 。实参 method 让浏览器以 POST 方式请求提交数据。

Django 使用模版标签 {% csrf_token %} 来防止攻击者利用表单来获取服务器未经授权的访问。后面,显示表单,从中能够知道 Django 使得完成显示表单等任务优化地更简单了:我们只需要包含模版变量 {{ form.as_p }} ,就可以让 Django 自动创建显示表单所需要的全部字段。修饰符 as_p 让 Django 以段落格式渲染所有表单元素,这是一种整洁地显示表单的简单方式。

Django 不会自动为表单创建提交按钮,因此后面又创建了提交按钮 <button name="submit">add topic</button> 。


  1. 链接到页面 new_topic

接下来,将新建的页面链接到页面 topics 中,修改 topics.html

{% extends "learning_logs/base.html" %}
{% block content %}
  <p>Topics</p>
  <ul>
    {% for topic in topics %}
   <a href="{% url 'topic' topic.id %}">{{ topic }}</a>
  {% empty %}
   <li>No topics have been added yet.</li>
  {% endfor %}
  </ul>
  <a href="{% url 'new_topic' %}">Add a new topic:</a>
{% endblock content %}


完成后,运行服务,访问页面:

20201130214407695.png20201130214716764.png

添加新条目

现在用户可以添加新主题了,但是如果还想要添加新条目应该怎么办呢?所以这里将再次定义 URL,编写视图函数和模版链接到添加新条目的页面。但在此之前,还需要在 forms.py 中添加一个类


  1. 用于添加新条目的表单

创建一个与模型 Entry 相关联的表单,但是这个表单的定制程度要比 TopicForm 要高级一些:

forms.py
from django import forms
from .models import Topic, Entry
class TopicForm(forms.ModelForm):
    class Meta:
        model = Topic
        fields = {'text'}
        labels = {'text':''}
class EntryForm(forms.ModelForm):
    class Meta:
        model = Entry
        fields = {'text'}
        labels = {'text':''}
        widgets = {'text': forms.Textarea(attrs={'cols':80})}


这里导入了 Entry 类,并且创建了新的 EntryForm 表单,定义了 widgets 属性,widget 是一个 HTML 表单元素(单行文本框、多行文本区域、下拉列表等)。通过设置属性 widgets ,可以覆盖 Django 选择的默认小部件。通过让 Django 使用 forms.Textarea ,定制了字段 ‘text’ 的输入小部件,将文本区域的宽度设置为 80 ,给用户提供了足够的空间用来编写有意义的条目。


  1. URL模式 new_entry

在 urls.py 中添加新的 URL new_entry ,如下:

'''定义 learning_logs 的 URL 模式'''
from django.conf.urls import url
from django.urls import path
from . import views 
urlpatterns = [
    #主页
    #url(r'^$', views.index, name='index'),
    path('', views.index, name='index'),
    path(r'^topics/$', views.topics, name='topics'),
    path(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
    #用于添加新主题的网页
    path(r'^new_topic/$', views.new_topic, name='new_topic'),
    #用于添加新的条目的网页
    path(r'^new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'),
]


  1. 视图函数 new_entry()

同之前方式类似,创建 new_entry() 视图函数在 views.py 中:

def new_entry(request, topic_id):
    '''在特定的主题中添加新条目'''
    topic = Topic.objects.get(id=topic_id)
    if request.method != 'POST':
        '''未提交数据:创建空表单'''
        form = EntryForm()
    else:
        '''POST 提交数据,对数据进行处理'''
        form = EntryForm(data=request.POST)
        if form.is_valid():
            new_entry = form.save(commit=False)
            new_entry.topic = topic
            new_entry.save()
            return HttpResponseRedirect(reverse('learning_logs:topic', args=[topic_id]))
    context = {'topic':topic, 'form':form}
    return render(request, 'learning_logs/new_entry.html', context)


  1. 创建模版 new_entry.html
{% extends "learning_logs/base.html" %}
{% block content %}
  <p><a href="{% url 'topic' topic_id %}">{{ topic }}</a></p>
  <p>Add a new entry:</p>
  <form action="{% url 'new_entry' topic_id %}" method='post'>
    {% csrf_token %}
    {{ form.as_p }}
  <button name="submit">add entry</button>
  </form>
{% endblock content %}


同时修改 topic.html :


{% extends "learning_logs/base.html" %}
{% block content %}
  <p>Topics</p>
  <p>Entries:</p>
  <p>
    <a href="{% url 'new_entry' topic_id %}">add new entry</a>
  </p>
  <ul>
    {% for entry in entries %}
   <li>
     <p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
  <p>{{ entry.text|linebreaks }}</p>
   </li>
  {% empty %}
   <li>
     There are no entries for this topic yet.
   </li>
  {% endfor %}
  </ul>
{% endblock content %}


运行:

20201130221918258.png20201130224140889.png

编辑目录

下面创建一个页面,使得用户能够编辑既有的条目。


  1. URL 模式 edit_entry

这个页面的 URL 需要传递要编辑的条目的 ID。修改后的 learning_logs/urls.py 如下:

'''定义 learning_logs 的 URL 模式'''
from django.conf.urls import url
from django.urls import path
from . import views 
urlpatterns = [
    #主页
    #url(r'^$', views.index, name='index'),
    path('', views.index, name='index'),
    path(r'^topics/$', views.topics, name='topics'),
    path(r'^topics/(?P<topic_id>\d+)/$', views.topic, name='topic'),
    #用于添加新主题的网页
    path(r'^new_topic/$', views.new_topic, name='new_topic'),
    #用于添加新的条目的网页
    path(r'^new_entry/(?P<topic_id>\d+)/$', views.new_entry, name='new_entry'),
    #用于编辑条目的页面
    path(r'^edit_entry/(?P<entry_id>\d+)/$', views.edit_entry, name='edit_entry'),
]


在 URL (http://localhost:8000/edit_entry/1/)中传递的 ID 存储在形参 entry_id 中。


  1. 视图函数 edit_entry()

在 views.py 中添加视图函数 edit_entry()


def edit_entry(request, entry_id):
    '''编辑已有条目'''
    entry = Entry.objects.get(id=entry_id)
    topic = entry.topic
    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)


模版 edit_entry.html

新建 edit_entry.html 文件:

{% extends "learning_logs/base.html" %}
{% block content %}
  <p><a href="{% url 'topic' topic.id %}">{{ topic }}</a></p>
  <p>Add a new entry:</p>
  <form action="{% url 'edit_entry' entry.id %}" method='post'>
    {% csrf_token %}
    {{ form.as_p }}
  <button name="submit">add entry</button>
  </form>
{% endblock content %}


链接到页面 edit_entry

现在,在显示特定主题的页面中,添加 edit_entry 的链接:

topic.html

{% extends "learning_logs/base.html" %}
{% block content %}
  <p>Topics</p>
  <p>Entries:</p>
  <p>
    <a href="{% url 'new_entry' topic.id %}">add new entry</a>
  </p>
  <ul>
    {% for entry in entries %}
   <li>
     <p>{{ entry.date_added|date:'M d, Y H:i' }}</p>
  <p>{{ entry.text|linebreaks }}</p>
  <p>
    <a href="{% url 'edit_entry' entry.id %}">edit entry</a>
  </p>
   </li>
  {% empty %}
   <li>
     There are no entries for this topic yet.
   </li>
  {% endfor %}
  </ul>
{% endblock content %}


运行:

20201130230955601.png20201130231051590.png2020113023243158.png

创建用户账户

接下来,就需要建立一个用户注册和身份验证系统,使得用户能够注册账户并且登录和注销。这里将针对这一功能模块创建新的应用程序,其中包含与处理用户账户相关的所有功能,同时还需要对模型 Topic 进行修改,使得每个主题归属于特定的用户。


创建应用程序 users

首先使用命令 startapp 创建一个名为 users 的应用程序:


<...\Scripts> py.exe .\manage.py startapp users


将应用程序 users 添加到 settings.py 中

在 settings.py 中,需要将这个新的应用添加进来。如下所示:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    #   自定义应用
    'learning_logs',
    # 账户管理应用
    'users',
]


  1. 包含应用程序 users 的 URL

在项目根目录中的 urls.py 中添加匹配 URL:

from django.contrib import admin
from django.urls import include, path
urlpatterns = [
    path(r'admin/', admin.site.urls),
    path(r'users/', include(('users.urls', 'users'), namespace='users')),
    path(r'', include(('learning_logs.urls', 'learning_logs'), namespace='learning_logs')),
]


登录页面

首先需要实现登录页面的功能。为此,将使用 Django 提供的默认登录视图,所以这里的 URL 模式会稍微不一样。在目录 users/ 中,新建一个名为 urls.py 的文件,其中添加如下内容:


'''为应用 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'^$', views.index, name='index'),
    #登陆页面
    #path(r'^login/$', login, {'template_name':'users/login.html'}, name='login'),
    url(r'^login/$',LoginView.as_view(template_name='users/login.html'),name='login'),
]


这里使用 login 告诉 Django 使用默认视图 login ,并且传递了一个字典告诉 Django 去哪里能够查找我们编写的模版 users/login.html 。


  1. 模版 login.html

用户请求登录页面时,Django 将使用默认视图 login ,但是我们依然需要为这个页面提供模版,所以在 users/ 应用程序目录下创建一个 templates/users/ 目录,并在其中新建 login.html 模版文件,内容添加如下:

{% extends "learning_logs/base.html" %}
{% block content %}
  {% if form.errors %}
  <p>Your username and password didn't match. Please try again.</p>
  {% endif %}
  <form method='post' action="{% url 'users:login' %}">
  {% csrf_token %}
  {{ form.as_p }}
  <button name="submit">log in</button>
  <input type="hidden" name="next" value="{% url 'learning_logs:index' %}" />
  </form>
{% endblock content %}


  1. 链接到登陆页面

下面在 base.html 中添加到登录页面的链接,让所有页面都包含它。用户已登录时,不需要显示这个链接,所以需要将它嵌套到一个 {% if %} 标签中。

<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 }}.
  {% else %}
    <a href="{% url 'users:login' %}">log in</a>
  {% endif %}
</p>
{% block content %}{% endblock content %}


在 Django 身份验证系统中,每个模版都可以使用变量 user ,这个变量有一个 is_authenticated 属性:如果用户已登录,该属性为 True ,否则为 False .

在这里,我们向已登录的用户显示一条问候语。对于已投过身份验证的用户,还设置了属性 username ,我们使用这个属性来个性化问候语,让用户知道已登录了,而未登录用户则显示一个登陆页面的链接。


  1. 使用登陆页面

前面建立了一个用户账户,下面来登陆一下,看看登陆页面是否管用,访问 http://localhost:8000/admin/ ,如果依然是以管理员身份登陆,请在最上面找到注销链接并点击它。

20201201225234374.png20201201225252981.png

注销

现在需要提供一个让用户注销的途径。我们不创建用于注销的页面,而让用户只需要单击一个链接就能够注销并返回主页,为此,将为注销链接定义一个 URL 模式,编写一个视图函数,并在 base.html 中添加一共注销链接。


  1. 注销 URL

下面的代码为注销定义了 URL 模式,该模式与 URL http://localhost:8000/users/login 匹配,修改后的 user/urls.py 如下:

'''为应用 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'^$', views.index, name='index'),
    #登陆页面
    #path(r'^login/$', login, {'template_name':'users/login.html'}, name='login'),
    url(r'^login/$',LoginView.as_view(template_name='users/login.html'),name='login'),
    url(r'^logout/$', views.logout_view, name='logout'),
]


  1. 视图函数 logout_view()

函数 logout_view() 很简单:只是导入 Django 函数 logout_view() ,并调用它,再重定向到主页。打开 users/views.py 添加如下代码:

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth import logout
# Create your views here.
def logout_view(request):
    '''注销用户'''
    logout(request)
    return HttpResponseRedirect(reverse('learning_logs:index')


  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:login' %}">log in</a>
  {% endif %}
</p>
{% block content %}{% endblock content %}


  1. 完成运行:

20201201231822459.png20201201231833194.png



相关文章
|
8月前
|
人工智能 Linux 开发工具
Python从零到一:手把手带你写出第一个实用程序
Python语法简洁易懂,适合编程新手入门。它广泛应用于人工智能、自动化办公、Web开发等领域。学习Python可快速搭建项目,拥有丰富库支持和强大社区资源。通过本教程,你将掌握基础语法、环境搭建、程序逻辑控制及实战项目开发,开启编程之旅。
1115 0
|
7月前
|
设计模式 决策智能 Python
Python条件控制:让程序学会"思考"的魔法
本文深入浅出地讲解Python条件控制,从基础if语句到多分支、嵌套结构,再到简洁的三元表达式与Python 3.10新增的match-case模式匹配,结合电商折扣、会员等级、ATM系统等实战案例,全面掌握程序“智能决策”的核心逻辑。
511 0
|
10月前
|
PyTorch 算法框架/工具 C++
人工智能算法python程序运行环境安装步骤整理
本教程详细介绍Python与AI开发环境的配置步骤,涵盖软件下载、VS2017安装、Anaconda配置、PyCharm设置及组件安装等内容,适用于Windows系统,助你快速搭建开发环境。
|
6月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
549 4
|
10月前
|
缓存 JavaScript 前端开发
鸿蒙5开发宝藏案例分享---Web开发优化案例分享
本文深入解读鸿蒙官方文档中的 `ArkWeb` 性能优化技巧,从预启动进程到预渲染,涵盖预下载、预连接、预取POST等八大优化策略。通过代码示例详解如何提升Web页面加载速度,助你打造流畅的HarmonyOS应用体验。内容实用,按需选用,让H5页面快到飞起!
|
10月前
|
JavaScript 前端开发 API
鸿蒙5开发宝藏案例分享---Web加载时延优化解析
本文深入解析了鸿蒙开发中Web加载完成时延的优化技巧,结合官方案例与实际代码,助你提升性能。核心内容包括:使用DevEco Profiler和DevTools定位瓶颈、四大优化方向(资源合并、接口预取、图片懒加载、任务拆解)及高频手段总结。同时提供性能优化黄金准则,如首屏资源控制在300KB内、关键接口响应≤200ms等,帮助开发者实现丝般流畅体验。
|
前端开发 JavaScript Shell
鸿蒙5开发宝藏案例分享---Web页面内点击响应时延分析
本文为鸿蒙开发者整理了Web性能优化的实战案例解析,结合官方文档深度扩展。内容涵盖点击响应时延核心指标(≤100ms)、性能分析工具链(如DevTools时间线、ArkUI Trace抓取)以及高频优化场景,包括递归函数优化、网络请求阻塞解决方案和setTimeout滥用问题等。同时提供进阶技巧,如首帧加速、透明动画陷阱规避及Web组件初始化加速,并通过优化前后Trace对比展示成果。最后总结了快速定位问题的方法与开发建议,助力开发者提升Web应用性能。
|
10月前
|
JSON 开发框架 自然语言处理
【HarmonyOS Next之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(三)
本文主要介绍了应用开发中的三大核心内容:生命周期管理、资源限定与访问以及多语言支持。在生命周期部分,详细说明了应用和页面的生命周期函数及其触发时机,帮助开发者更好地掌控应用状态变化。资源限定与访问章节,则聚焦于资源限定词的定义、命名规则及匹配逻辑,并阐述了如何通过 `$r` 引用 JS 模块内的资源。最后,多语言支持部分讲解了如何通过 JSON 文件定义多语言资源,使用 `$t` 和 `$tc` 方法实现简单格式化与单复数格式化,为全球化应用提供便利。
339 104
|
10月前
|
JavaScript 前端开发 API
【HarmonyOS Next之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(二)
本文介绍了HarmonyOS应用开发中的HML、CSS和JS语法。HML作为标记语言,支持数据绑定、事件处理、列表渲染等功能;CSS用于样式定义,涵盖尺寸单位、样式导入、选择器及伪类等特性;JS实现业务逻辑,包括ES6语法支持、对象属性、数据方法及事件处理。通过具体代码示例,详细解析了页面构建与交互的实现方式,为开发者提供全面的技术指导。
359 104

推荐镜像

更多