使用 Django 构建和提交 HTML 表单——第 4 部分

举报
Yuchuan 发表于 2022/01/20 08:20:23 2022/01/20
【摘要】 在构建这个项目的过程中,您已经学会了如何: 从头到尾构建一个Django 项目 Django模型之间的实现OneToOne和ForeignKey 关系 使用自定义模型扩展 Django用户模型Profile 自定义Django 管理界面 集成Bulma CSS为您的应用设置样式

目录

在这个由四部分组成的教程系列中,您将使用 Django 构建一个可以在您的作品集中展示的社交网络。这个项目正在加强您对 Django 模型之间关系的理解,并向您展示如何使用表单,以便用户可以与您的应用程序以及彼此交互。您还可以通过使用 Bulma CSS 框架使您的网站看起来不错。

在本系列的前一部分中,您添加了功能,以便用户可以在后端创建 dweets 并在前端显示它们。此时,您的用户可以发现和关注其他用户,并阅读他们关注的个人资料的内容。如果他们想停止阅读他们的内容,他们可以单击一个按钮,该按钮发送由 Django 处理的 HTTP POST 请求以取消关注个人资料。

在本教程系列的第四部分中,您将学习如何:

  • 从您的模型创建和呈现Django 表单Dweet
  • 防止重复提交显示有用的错误消息
  • 使用动态 URL 链接应用程序的页面
  • 重构视图函数
  • 使用QuerySet字段查找在后端过滤您的数据

完成本教程的最后一部分后,您将拥有一个使用 Django 构建的功能齐全的基本社交网络。它将允许您的用户创建基于文本的简短消息,发现和关注其他用户,并阅读他们关注的个人资料的内容。如果他们想停止阅读他们的内容,他们还可以取消关注个人资料。

此外,您将展示您可以使用 CSS 框架使您的 Web 应用程序看起来很棒,而无需太多额外的努力。

您还将学习如何使用 CSS 框架 Bulma 为您的应用程序提供用户友好的外观,并使其成为您可以自豪地炫耀的投资组合项目。

在本教程系列的第四部分也是最后一部分中,您将学习如何在现有模型之上构建 Django 表单。您还将设置和处理更多 HTTP POST 请求提交,以便您的用户可以发布基于文本的消息。

在本教程结束时,您将完成使用 Django 构建的基本社交网络。届时,您的用户将能够导航到个人资料列表和个人个人资料页面,关注和取消关注其他用户,并在他们的仪表板上查看他们关注的个人资料的 dweets。他们还可以通过仪表板上的表格提交 dweets。

项目概况

在本节中,您将大致了解本教程系列的最后一部分将涵盖哪些主题。您还将有机会重新访问完整的项目实施步骤,以防您需要跳回到您在本系列的前一部分中完成的上一个步骤。

至此,您应该已经完成了本教程系列的第一三部分。恭喜!您已经进入最后一部分,重点是构建表单和处理表单提交:

完成本系列最后一部分的步骤后,您就完成了 Django 社交网络的基本版本。您将准备好自己采取任何后续步骤,以使该项目在您的 Web 开发人员组合中脱颖而出。

要深入了解本系列中关于构建 Django 社交网络的最后一部分如何适应整个项目的上下文,您可以展开下面的可折叠部分:

完整的项目实施步骤显示隐藏

您正在通过本系列的多个单独教程中的多个步骤来实施该项目。有很多内容要介绍,并且您将在此过程中详细介绍:

✅ 第 1 部分:模型和关系

  • 第 1 步:设置基础项目
  • 第 2 步:扩展 Django 用户模型
  • 第 3 步:实施保存后挂钩

✅ 第 2 部分:模板和前端样式

  • 第 4 步:使用 Bulma 创建基本模板
  • 第 5 步:列出所有用户配置文件
  • 第 6 步:访问个人资料页面

✅ 第 3 部分:关注和 Dweets

  • 第 7 步:关注和取消关注其他个人资料
  • 第 8 步:为 Dweets 创建后端逻辑
  • 第 9 步:在前端显示 Dweets

📍 第 4 部分:表格和提交

  • 第 10 步:通过 Django 表单提交 Dweets
  • 第 11 步:防止重复提交并处理错误
  • 第 12 步:改善前端用户体验

这些步骤中的每一个都将提供指向任何必要资源的链接。通过一次一个地接近这些步骤,您将有机会暂停并稍后返回,以防您想休息一下。

考虑到本教程系列的高级结构,您对自己所处的位置以及可能必须赶上哪些实施步骤(如果您尚未完成它们)有一个很好的了解。

在开始下一步之前,请快速查看先决条件,以浏览可能在此过程中有用的其他资源的任何链接。

先决条件

要成功完成项目的最后一部分,您需要完成关于模型和关系第一部分,关于模板和样式的第二部分,以及关于关注和 dweets 的第三部分。请确认您的项目按照那里的描述工作。您还应该熟悉以下概念:

确保您已完成本系列的前三部分。这最后一部分将在您在第三部分结束时离开的地方继续。

注意:如果您没有准备好前面部分的工作项目,您将无法学习本教程系列的这一部分。

有关其他要求和更多链接,请查看本教程系列第一部分中提到的关于在 Django 中构建基本社交网络的先决条件。

第 10 步:使用 Django 表单提交 Dweets

为了本教程系列,您很早就决定在 Django admin中处理用户创建。您的小型社交网络仅供邀请,您是决定创建用户帐户的人。

注意:请随意使用Django 的用户管理系统对此进行扩展,并按照链接教程构建必要的模板。

但是,一旦您的用户进入您的社交网络应用程序,您就会希望为他们提供创建内容的机会。他们将无法访问 Django 管理界面,并且您的 Dwitter 将是贫瘠的,没有任何用户创建内容的机会。您需要另一个表单作为用户提交 dweets 的界面。

创建文本输入表单

如果您熟悉 HTML 表单,那么您可能知道可以通过创建另一个<form>具有特定<input>元素的 HTML 元素来处理文本提交。但是,它必须看起来与您为按钮构建的表单有点不同。

在本教程中,您将学习如何使用Django 表单创建 HTML 表单。您将编写一个 Django 表单,Django 将<form>在呈现页面时将其转换为 HTML 元素。

要开始本教程的这一部分,请在您的 Djangodwitter应用程序中创建一个新文件,并将其命名为forms.py. 该文件可以包含您的项目可能需要的所有表单。您只需要一个表单,以便您的用户可以提交他们的 dweets:

 1# dwitter/forms.py
 2
 3from django import forms
 4from .models import Dweet
 5
 6class DweetForm(forms.ModelForm):
 7    body = forms.CharField(required=True)
 8
 9    class Meta:
10        model = Dweet
11        exclude = ("user", )

在此代码片段中,您创建DweetForm继承自 Django 的ModelForm. 以这种方式创建表单在很大程度上依赖于 Django 设置的抽象,这意味着在本教程中,您需要自己定义很少的内容来获得工作表单:

  • 第 3 到 4 行:您导入 Django 的内置forms模块和Dweet您在本教程系列的前一部分中创建的模型。

  • 第 6 行:创建一个新类DweetForm,它继承自forms.ModelForm

  • 第 7 行:传递希望表单呈现的字段,并定义其类型。在这种情况下,您需要一个字符字段来允许文本输入。body是唯一的字段,您将其设为必填字段,这样就不会有任何空的 dweets。

  • Meta第 9行您在DweetForm. 此选项类允许您将任何不是字段的信息传递给您的表单类。

  • 第 10 行:您需要定义ModelForm应该从哪个模型获取信息。因为您想制作一个允许用户创建 dweets 的表单,Dweet所以这里是正确的选择。

  • 第 11 行:通过将要排除的模型字段的名称添加到exclude元组中,可以确保 Django 在创建表单时将其省略。记得在后面添加一个逗号 ( ,),"user"以便 Python 为您创建一个元组!

您想让 dweet 提交内容尽可能方便用户使用。用户登录后只能在您的社交网络上发表推文,并且他们只能为自己创建推文。因此,您不需要在表单中明确传递哪个用户正在发送 dweet。

注意:将 dweet 与用户关联是必要的,但您将在后端处理它。

本教程中描述的设置包含 Django 创建 HTML 表单所需的所有信息,这些表单在前端捕获您需要的所有信息。是时候从那一端看看了。

在模板中呈现表单

在中创建DweetFormforms.py,您可以将其导入代码逻辑并将信息发送到您的仪表板模板:

# dwitter/views.py

from django.shortcuts import render
from .forms import DweetForm
from .models import Profile

def dashboard(request):
    form = DweetForm()
    return render(request, "dwitter/dashboard.html", {"form": form})

通过对 的这些更改views.py,您首先DweetFormforms.py. 然后,您创建了一个新DweetForm实例,将其分配给form,并将其传递到您的上下文字典中的仪表板模板中的 key 下"form"。此设置允许您在模板中访问和呈现表单:

<!-- dwitter/templates/dwitter/dashboard.html -->

{% extends 'base.html' %}

{% block content %}

<div class="column">
    {% for followed in user.profile.follows.all %}
        {% for dweet in followed.user.dweets.all %}
            <div class="box">
                {{dweet.body}}
                <span class="is-small has-text-grey-light">
                    ({{ dweet.created_at }} by {{ dweet.user.username }}
                </span>
            </div>
        {% endfor %}
    {% endfor %}
</div>

<div class="column is-one-third">
    {{ form.as_p }}
</div>

{% endblock content %}

您分配给该<div>元素的 HTML 类使用 Bulma 的 CSS 规则在您的仪表板页面上创建一个新列。这个额外的列使页面感觉不那么拥挤,并将提要内容与表单分开。然后,您使用{{ form.as_p }}. 确实,出现了一个输入框:

显示带有标签文本的普通输入框的仪表板

此设置显示您的 Django 表单的最小显示。它只有一个字段,就像您在DweetForm. 但是,它看起来不太好,文本字段似乎太小了,并且在输入字段旁边有一个标签阅读Body 。你没有要求那个!

您可以通过向 in 中添加自定义小部件来改进 Django表单显示:forms.CharFieldforms.py

 1# dwitter/forms.py
 2
 3from django import forms
 4from .models import Dweet
 5
 6class DweetForm(forms.ModelForm):
 7    body = forms.CharField(
 8        required=True,
 9        widget=forms.widgets.Textarea(
10            attrs={
11                "placeholder": "Dweet something...",
12                "class": "textarea is-success is-medium",
13            }
14        ),
15        label="",
16    )
17
18    class Meta:
19        model = Dweet
20        exclude = ("user", )

通过向 中添加Django 小部件CharField您可以控制 HTML 输入元素如何表示的几个方面:

  • 第 9 行:在这一行中,您选择 Django 应该使用的输入元素类型并将其设置为Textarea. 该Textarea小部件将呈现为HTML<textarea>元素,为您的用户提供更多空间来输入他们的 dweets。

  • 第 10 到 13 行:您可以Textarea使用attrs. 这些设置呈现为<textarea>元素上的 HTML 属性。

  • 第 11 行:您添加占位符文本,当用户单击表单字段输入他们的 dweet 时,该占位符文本将显示在输入框中并消失。

  • 第 12 行:添加 HTML 类"textarea",它与Bulma 定义的 textarea CSS 样式规则相关,将使您的输入框更有吸引力,并与页面的其余部分更好地匹配。您还添加了两个额外的类is-successis-medium,它们分别以绿色勾勒输入字段并增加文本大小。

  • 第 15 行:您设置label为一个空字符串 ( ""),这将删除之前显示的正文文本,因为 Django 默认设置将表单字段的名称呈现为其标签。

只需在 中进行一些自定义Textarea,您就可以使输入框更好地适应页面的现有样式:

带有 Dweet Textarea 但没有按钮的仪表板

输入框看起来不错,但还不是功能形式。有人要求提交按钮吗?

使表单提交成为可能

Django 表单可以省去创建表单域和设置表单域样式的麻烦。但是,您仍然需要将 Django 表单包装到 HTML<form>元素中并添加一个按钮。要创建允许 POST 请求的函数式表单,您还需要相应地定义 HTTP 方法:

 1<!-- dwitter/templates/dwitter/dashboard.html -->
 2
 3{% extends 'base.html' %}
 4
 5{% block content %}
 6
 7<div class="column">
 8    {% for followed in user.profile.follows.all %}
 9        {% for dweet in followed.user.dweets.all %}
10            <div class="box">
11                {{dweet.body}}
12                <span class="is-small has-text-grey-light">
13                    ({{ dweet.created_at }} by {{ dweet.user.username }}
14                </span>
15            </div>
16        {% endfor %}
17    {% endfor %}
18</div>
19
20<div class="column is-one-third">
21    <form method="post">
22        {% csrf_token %}
23        {{ form.as_p }}
24        <button class="button is-success is-fullwidth is-medium mt-5"
25                type="submit">Dweet
26        </button>
27    </form>
28</div>
29
30{% endblock content %}

通过对 HTML 代码的另一次增量更新,您完成了 dweet 提交表单的前端设置:

  • 第 21 行和第 27 行:您将表单代码包装到设置为的 HTML<form>元素中,因为您希望通过 POST 请求发送用户提交的消息。method"post"
  • 第 22 行:您添加了一个 CSRF 令牌,使用的模板标签与创建关注和取消关注配置文件时使用的模板标签相同。
  • 第 24 到 26 行:您通过class属性添加了一个带有一些 Bulma 样式的按钮来完成表单,这允许您的用户提交他们输入的文本。

表单看起来不错,似乎已经准备好接收您的输入:

显示带有提交按钮的输入 Textarea 框的仪表板

当您单击Dweet按钮时会发生什么?不多,因为您还没有设置任何代码逻辑来补充您的前端代码。您的下一步是在以下位置实现提交功能views.py

 1# dwitter/views.py
 2
 3def dashboard(request):
 4    if request.method == "POST":
 5        form = DweetForm(request.POST)
 6        if form.is_valid():
 7            dweet = form.save(commit=False)
 8            dweet.user = request.user
 9            dweet.save()
10    form = DweetForm()
11    return render(request, "dwitter/dashboard.html", {"form": form})

通过对 的一些补充dashboard(),您可以让视图处理提交的数据并在数据库中创建新的 dweets:

  • 第 4 行:如果用户使用 HTTP POST 请求提交表单,那么您要处理该表单数据。如果由于 HTTP GET 请求调用了视图函数,您将直接跳过整个代码块进入第 10 行,并在第 11 行以空表单呈现页面。

  • 第 5 行:填写DweetForm通过 POST 请求传入的数据。根据您在 中的设置forms.py,Django 会将数据传递到bodycreated_at将自动填充,并且您明确排除user,因此暂时保持空白。

  • 第 6 行: Django 表单对象有一个名为 的方法.is_valid(),它将提交的数据与表单中定义的预期数据和相关的模型限制进行比较。如果一切正常,该方法将返回True。只有在提交的表单有效时,您才允许您的代码继续执行。

  • 第 7 行:如果您的表单已经包含创建新数据库条目所需的所有信息,那么您可以.save()不带任何参数使用。但是,您仍然缺少user关联 dweet 所需的条目。通过添加commit=False,您可以阻止将条目提交到数据库。

  • 第 8 行:您从 Django 的对象中选择当前登录的用户对象request并将其保存到dweet您在上一行中创建的。通过这种方式,您通过建立与当前用户的关联添加了缺失的信息。

  • 第 9 行:最后,您dweet拥有所需的所有信息,因此您可以在关联表中成功创建新条目。您现在可以使用 将信息写入数据库.save()

  • 第 10 到 11 行:无论您是否处理过 POST 提交,您总是将一个新的空DweetForm实例传递给render(). 此函数调用会重新显示页面,其中包含一个新的空白表单,为您的更多想法做好准备。

这样,您就成功地创建了文本输入表单并将其连接到您的代码逻辑,因此提交将被正确处理。在本教程系列的这一部分中,您还了解了 Django 表单。您在模板中呈现了一个表单,然后通过自定义小部件中的属性对其应用 Bulma 样式Textarea

然而,在您准备好向现实生活中的用户开放您的 Django 社交网络之前,您需要解决一个问题。如果你写了一个dweet并现在提交它,它会被添加,但是如果你在提交后重新加载页面,同样的dweet会被再次添加!

第 11 步:防止重复提交并处理错误

此时,您可以通过应用程序的前端创建新的 dweets,并查看您自己的 dweets 以及您在仪表板上关注的个人资料的 dweets。在这一步结束时,您将防止双重 dweet 提交并了解 Django 如何显示文本输入错误。

但首先,您应该了解问题所在。转到您的仪表板,写一篇鼓舞人心的 dweet,然后单击Dweet提交它。您会看到它出现在时间轴中显示的 dweets 列表中,并且 dweet 表单将再次显示为空。

不做任何其他事情,使用键盘快捷键重新加载页面:

  • Cmd+R在 macOS 上
  • Ctrl+R在 Windows 和 Linux 上

您的浏览器可能会通过弹出窗口提示您是否要再次发送表单。如果出现此消息,请按发送确认。现在您会注意到您之前发送的相同 dweet 再次出现在您的仪表板上。您可以根据需要多次执行此操作:

由于双重提交错误,仪表板多次显示相同的 Dweet

发布 dweet 后,如果您重新加载页面,Django 会发送另一个具有相同数据的 POST 请求并在您的数据库中创建另一个条目。你会看到 dweet 第二次弹出。第三次。第四次。Django 将不断重复 dweets,就像你不断重新加载一样频繁。你不想要那个!

防止重复提交

为避免重复提交,您必须阻止您的应用程序保留请求数据,以便重新加载没有机会重新提交数据。您可以通过使用Django 重定向来做到这一点:

# dwitter/views.py

from django.shortcuts import render, redirect

# ...

def dashboard(request):
    if request.method == "POST":
        form = DweetForm(request.POST)
        if form.is_valid():
            dweet = form.save(commit=False)
            dweet.user = request.user
            dweet.save()
            return redirect("dwitter:dashboard")
    form = DweetForm()
    return render(request, "dwitter/dashboard.html", {"form": form})

在成功将新提交的 dweet 添加到数据库后,通过导入redirect()并返回对它的调用,您可以将用户发送回同一页面。但是,这次您在重定向时发送了一个 GET 请求,这意味着任何数量的页面重新加载都只会显示已经存在的 dweets,而不是创建大量克隆的 dweets。

您可以通过引用您在 URL 配置中定义的app_name变量和name关键字参数来设置它:path()

  • "dwitter"app_name描述应用程序命名空间的变量。:您可以在传递给的字符串参数中的冒号 () 之前找到它redirect()
  • "dashboard"是指向name的条目的关键字参数的值。您需要在传递给的字符串参数中的冒号 ()之后添加它。path()dashboard():redirect()

redirect()如上所示使用,您需要相应地设置命名空间dwitter/urls.py,您在教程系列的前一部分中已经这样做了:

# dwitter/urls.py

# ...

app_name = "dwitter"

urlpatterns = [
    path("", dashboard, name="dashboard"),

    # ...

通过urls.py如上所示的设置,您可以redirect()在成功处理来自表单提交的 POST 请求后,使用 GET 请求将用户指向他们的仪表板页面。

在条件语句结束时返回后redirect(),任何重新加载都只会加载页面而不重新提交表单。您的用户现在可以安全地提交简短的 dweets,而不会出现意外结果。但是,当 dweet 超过 140 个字符限制时会发生什么?

尝试输入超过 140 个字符限制的长 dweet 并提交。怎么了?没有什么!但是也没有错误消息,因此您的用户甚至可能不知道他们做错了什么。

此外,您输入的文本消失了,这是设计不佳的用户表单的主要烦恼。因此,您可能希望通过通知用户他们做错了什么并保留他们输入的文本来为您的用户提供更好的体验!

处理提交错误

您在模型中定义了基于文本的消息的最大长度为 140 个字符,并且您在用户提交文本时强制执行此操作。但是,当他们超过字符限制时,您不会告诉他们。当他们提交过长的 dweet 时,他们的输入就会丢失。

好消息是,您可以使用 Django 表单{{ form.as_p }}来显示与表单对象一起发送的错误消息,而无需添加任何代码。这些错误消息可以显着改善用户体验。

但是目前,您看不到任何错误消息,那为什么呢?再看看dashboard()

 1# dwitter/views.py
 2
 3# ...
 4
 5def dashboard(request):
 6    if request.method == "POST":
 7        form = DweetForm(request.POST)
 8        if form.is_valid():
 9            dweet = form.save(commit=False)
10            dweet.user = request.user
11            dweet.save()
12            return redirect("dwitter:dashboard")
13    form = DweetForm()
14    return render(request, "dwitter/dashboard.html", {"form": form})

在突出显示的行中,您可以看到您正在创建两个不同DweetForm对象之一,绑定或未绑定表单

  1. 第 7 行:如果您的函数是从 POST 请求中调用的,则使用请求DweetForm随附的数据进行实例化。Django 创建一个绑定表单,该表单可以访问数据并且可以得到验证。
  2. 第 13 行:如果您的页面通过 GET 请求被调用,则您正在实例化一个未绑定的表单,该表单没有任何关联的数据。

到目前为止,此设置运行良好并且有意义。如果用户通过导航访问页面,您希望显示一个空表单,并且如果用户编写 dweet 并将其发送到数据库,您希望验证和处理表单中提交的数据。

然而,症结在于这里的细节。您可以而且应该验证绑定的表单,这在第 8 行中执行。如果验证通过,则 dweet 将被写入您的数据库。但是,如果用户添加了太多字符,那么您的表单验证将失败,并且您的条件语句中的代码不会被执行。

formPython 将执行跳转到第 13 行,在这里您用一个空的未绑定DweetForm对象覆盖。此表单是发送到您的模板并呈现的内容。由于您使用未绑定的表单覆盖了包含验证错误信息的绑定表单,因此 Django 不会显示任何发生的验证错误。

如果发生验证错误,要将绑定的表单发送到模板,您需要稍微更改代码:

# dwitter/views.py

# ...

def dashboard(request):
    form = DweetForm(request.POST or None)
    if request.method == "POST":
        if form.is_valid():
            dweet = form.save(commit=False)
            dweet.user = request.user
            dweet.save()
            return redirect("dwitter:dashboard")
    return render(request, "dwitter/dashboard.html", {"form": form})

通过此更改,您删除了 的重复实例化,DweetForm因此无论用户是否提交了有效表单,都只会将一个实例form传递给您的模板。

注意: Python 的布尔or运算符是一个短路运算符。这意味着它仅在第一个参数为Falsefalsy时才评估第二个参数。

您用于此更改的语法可能看起来不熟悉。所以这就是发生的事情:

  • POST 请求:如果您dashboard()使用包含任何数据的 POST 请求进行调用,request.POST QueryDict则将包含您的表单提交数据。该request.POST对象现在有一个值,Python 将短路or操作符以返回 的值request.POST。这样,您将在实例化 时将表单内容作为参数传递DweetForm,就像您之前使用form = DweetForm(request.POST).

  • GET 请求:如果您dashboard()使用 GET 请求调用,request.POST将为,这是一个值。Python 将继续计算or表达式并返回第二个值None. 因此,Django 将实例DweetForm化为一个未绑定的表单对象,就像您之前对form = DweetForm().

这种设置的优点是即使表单验证失败,您现在也可以将绑定的表单传递给模板,这允许 Django{{ form.as_p }}为您的用户提供开箱即用的描述性错误消息:

仪表板显示 Django 在尝试提交超出字符限制的 Dweet 时发送的表单错误消息

提交超出您在 中定义的字符限制的文本后Dweet,您的用户将看到在表单输入字段正上方弹出的描述性错误消息。此消息向他们提供反馈,说明他们的 dweet 尚未提交,提供有关为什么会发生这种情况的信息,甚至提供有关他们当前文本有多少字符的信息。

注意:您无需向模板添加任何 HTML 即可进行此更改。当表单提交错误在 {{ form.is_p }} 标记内的绑定表单对象中发送时,Django 知道如何呈现表单提交错误。

此更改的最佳之处在于您传递了绑定的表单对象,该对象保留了用户在表单中输入的文本数据。没有数据丢失,他们可以使用有用的建议来编辑他们的 dweet 并将其成功提交到数据库。

第 12 步:改善前端用户体验

此时,您拥有一个使用 Django Web 框架构建的功能性社交媒体应用程序。您的用户可以发布基于文本的消息,关注和取消关注其他用户个人资料,并在他们的仪表板视图中查看 dweets。在此步骤结束时,您将通过添加额外的导航链接并对 dweets 进行排序以首先显示最新的 dweets,从而改善应用的用户体验。

改进导航

您的社交网络具有用户可能希望在不同时间访问的三个不同页面:

  1. 空 URL 路径 ( /) 指向仪表板页面。
  2. /profile_listURL 路径指向配置文件列表。
  3. /profile/<int>URL 路径指向特定用户的个人资料页面。

您的用户已经可以通过他们各自的 URL slug 访问所有这些页面。但是,虽然您的用户可以通过单击所有配置文件列表中的用户名卡来访问配置文件页面,但目前没有直接导航来访问配置文件列表或仪表板页面。是时候添加更多链接了,以便用户可以方便地在 Web 应用程序的不同页面之间移动。

回到您的模板文件夹并打开dashboard.html. 在 dweet 表单上方添加两个按钮,以允许您的用户导航到应用程序中的不同页面:

  1. 个人资料列表页面
  2. 他们的个人资料页面

您可以将动态 URL 模式与您以前使用过的 Django 的 {% url %} 标签一起使用:

<!-- dwitter/templates/dwitter/dashboard.html -->

<!-- ... -->

<div class="block">
    <a href="{% url 'dwitter:profile_list' %} ">
        <button class="button is-dark is-outlined is-fullwidth">
            All Profiles
        </button>
    </a>
</div>
<div class="block">
    <a href="{% url 'dwitter:profile' request.user.profile.id %} ">
        <button class="button is-success is-light is-outlined is-fullwidth">
            My Profile
        </button>
    </a>
</div>

<!-- ... -->

您可以将此代码作为前两个元素添加到<div class="column is-one-third">. 您还可以在 dweet 表单上方添加一个标题,以更清楚地解释该表单的用途:

<!-- dwitter/templates/dwitter/dashboard.html -->

<!-- ... -->

<div class="block">
    <div class="block">
        <h2 class="title is-2">Add a Dweet</p>
    </div>
    <div class="block">
        <form method="post">
            {% csrf_token %}
            {{ form.as_p }}
            <button class="button is-success is-fullwidth is-medium mt-5"
                    type="submit">Dweet
            </button>
        </form>
    </div>
</div>

<!-- ... -->

通过这两个添加,您使用"block"类将三个<div>元素排列在一起,并添加了合理的导航按钮,以增强仪表板页面上的用户体验:

完成的仪表板页面,左侧显示关注的 dweets,右侧显示带有导航按钮的 Dweet 表单。

添加所有这些更改后,您的仪表板模板将完成。您可以将您编写的代码与下面的模板进行比较:

dwitter/模板/dwitter/dashboard.html显示隐藏

<!-- dwitter/templates/dwitter/dashboard.html -->

{% extends 'base.html' %}

{% block content %}

<div class="column">
    {% for followed in user.profile.follows.all %}
        {% for dweet in followed.user.dweets.all %}
            <div class="box">
                {{dweet.body}}
                <span class="is-small has-text-grey-light">
                    ({{ dweet.created_at }} by {{ dweet.user.username }}
                </span>
            </div>
        {% endfor %}
    {% endfor %}
</div>

<div class="column is-one-third">

    <div class="block">
        <a href="{% url 'dwitter:profile_list' %} ">
            <button class="button is-dark is-outlined is-fullwidth">
                All Profiles
            </button>
        </a>
    </div>

    <div class="block">
        <a href="{% url 'dwitter:profile' request.user.profile.id %} ">
            <button class="button is-success is-light is-outlined is-fullwidth">
                My Profile
            </button>
        </a>
    </div>

    <div class="block">
        <div class="block">
            <h2 class="title is-2">Add a Dweet</p>
        </div>
        <div class="block">
            <form method="post">
                {% csrf_token %}
                {{ form.as_p }}
                <button class="button is-success is-fullwidth is-medium mt-5"
                        type="submit">Dweet
                </button>
            </form>
        </div>
    </div>

</div>

{% endblock content %}

您的仪表板页面功能强大,看起来很棒!这很重要,因为它很可能是您的用户在与您的社交网络交互时花费大部分时间的页面。因此,您还应该让您的用户有足够的机会在他们导航到他们的个人资料页面后返回仪表板页面。

为此,您可以在所有页面的顶部添加一个指向仪表板页面的链接,方法是将其添加到您编写的应用程序标题中base.html

<!-- templates/base.html -->

<!-- ... -->

<a href="{% url 'dwitter:dashboard' %} ">
<section class="hero is-small is-success mb-4">
    <div class="hero-body">
        <h1 class="title is-1">Dwitter</h1>
        <p class="subtitle is-4">
            Your tiny social network built with Django
        </p>
    </div>
</section>
</a>

<!-- ... -->

通过将 HTML 元素包装<section>在链接元素中,您可以使整个英雄可点击,并为您的用户提供一种从应用程序中的任何位置返回到他们的仪表板页面的快速方法。

通过这些更新的链接,您显着改善了应用的用户体验。最后,如果您的用户想要及时了解他们的网络正在发布的内容,您需要更改 dweet 显示以首先显示最新的 dweets,而与谁编写文本无关。

对 Dweets 进行排序

有几种方法可以对 dweets 进行排序,以及一些可以这样做的地方,即

  1. 在你的模型中
  2. 在您的视图功能中
  3. 在您的模板中

到目前为止,您已经在仪表板模板中构建了相当多的代码逻辑。但是分离关注点是有原因的。正如您将在下面了解到的,您应该在视图中处理应用程序的大部分代码逻辑。

如果您想对 dweets 进行排序以首先显示最新的 dweet,而与编写 dweet 的人无关,您可能会对如何for使用您当前在仪表板模板中使用的嵌套循环语法来执行此操作一头雾水。

练习:探索显示隐藏

你知道为什么这会变得困难吗?前往dashboard.html并检查当前设置:

{% for followed in user.profile.follows.all %}
    {% for dweet in followed.user.dweets.all %}
        <div class="box">
            {{dweet.body}}
            <span class="is-small has-text-grey-light">
                ({{ dweet.created_at }} by {{ dweet.user.username }})
            </span>
        </div>
    {% endfor %}
{% endfor %}

您将如何尝试使用此设置进行排序?你认为你可能会在哪里遇到困难,为什么?花点时间拿出你的铅笔和笔记本。自由地使用您喜欢的搜索引擎,看看您是否可以提出解决方案或解释为什么这可能难以解决。

与其在模板中处理如此多的代码逻辑,不如直接在内部执行此操作dashboard()并将排序的结果传递给模板以进行显示。

到目前为止,您已经使用了仅处理表单提交并定义要呈现的模板的视图函数。您没有编写任何额外的逻辑来确定要从数据库中获取哪些数据。

在您的视图函数中,您可以使用带有修饰符的 Django ORM 调用来精确获取您正在寻找的 dweets。

您将从用户在您的视图函数中关注的所有配置文件中获取所有 dweets。然后,您将按日期和时间对它们进行排序,并将一个名为的新排序迭代传递dweet给您的模板。您将使用此可迭代对象在从最新到最旧的时间轴中显示所有这些 dweets:

 1# dwitter/views.py
 2
 3from django.shortcuts import render, redirect
 4from .forms import DweetForm
 5from .models import Dweet, Profile
 6
 7def dashboard(request):
 8    form = DweetForm(request.POST or None)
 9    if request.method == "POST":
10        if form.is_valid():
11            dweet = form.save(commit=False)
12            dweet.user = request.user
13            dweet.save()
14            return redirect("dwitter:dashboard")
15
16    followed_dweets = Dweet.objects.filter(
17        user__profile__in=request.user.profile.follows.all()
18    ).order_by("-created_at")
19
20    return render(
21        request,
22        "dwitter/dashboard.html",
23        {"form": form, "dweets": followed_dweets},
24    )

在此更新中dashboard(),您进行了一些值得进一步关注的更改:

  • 第 5 行:Dweet为模型添加导入。到目前为止,您不需要在视图中处理任何 dweet 对象,因为您在模板中处理它们。由于您现在要过滤它们,因此您需要访问您的模型。

  • 第 16 行:在这一行中,您使用.filter()on Dweet.objects,它允许您根据字段查找从表中选择特定的 dweet 对象。您将此调用的输出保存到followed_dweets.

  • 第 17 行(关键字):首先,定义查询集字段查找,这是 SQLWHERE子句主要部分的 Django ORM 语法。__您可以使用特定于 Django ORM的双下划线语法 ( ) 来跟踪数据库关系。您写入user__profile__in以访问用户的个人资料并查看该个人资料是否在您将作为值传递给字段查找关键字参数的集合中。

  • 第 17 行(值):在此行的第二部分,您提供了字段查找的第二部分。这部分需要是一个QuerySet包含配置文件对象的对象。.follows您可以通过访问当前登录用户配置文件 ( request.user.profile)中的所有配置文件对象,从数据库中获取相关配置文件。

  • 第 18 行:在这一行中,将另一个方法调用链接到数据库查询的结果,并声明 Django 应按created_at.

  • 第 23 行:最后,在上下文字典中添加一个新条目,并在其中传递followed_dweets. 该followed_dweets变量包含QuerySet当前用户关注的所有配置文件的所有 dweets 的对象,按最新的 dweet 排序。您将它传递给您的模板下的键 name dweets

您现在可以更新模板dashboard.html以反映这些更改并减少您需要在模板中编写的代码逻辑量,从而有效地摆脱嵌套for循环:

<!-- dwitter/templates/dwitter/dashboard.html -->

<!-- ... -->

{% for dweet in dweets %}
    <div class="box">
        {{dweet.body}}
        <span class="is-small has-text-grey-light">
            ({{ dweet.created_at }} by {{ dweet.user.username }}
        </span>
    </div>
{% endfor %}

<!-- ... -->

您已将预先选择和预先排序的 dweets 提供给您的模板,名为dweetsQuerySet现在,您可以使用单个循环遍历该对象for并访问 dweet 属性,而无需单步执行模板中的任何模型关系。

进行此更改后,继续并重新加载页面。您现在可以查看您关注的所有用户的所有 dweets,并按最新的 dweets 排序。如果您在关注自己的帐户时添加新的 dweet,它将显示在此列表的顶部:

仪表板 dweets 在顶部显示最新的 dweets

此更改完成了您需要进行的最终更新,以便您的 Django 社交媒体应用程序提供用户友好的体验。您现在可以声明您的 Django 社交媒体应用程序功能完整并开始邀请用户。

结论

在本教程中,您使用 Django 构建了一个小型社交网络。您的应用程序用户可以关注和取消关注其他用户个人资料,发布基于文本的简短消息,以及查看他们关注的其他个人资料的消息。

在构建这个项目的过程中,您已经学会了如何:

  • 从头到尾构建一个Django 项目
  • Django模型之间的实现OneToOneForeignKey 关系
  • 使用自定义模型扩展 Django用户模型Profile
  • 自定义Django 管理界面
  • 集成Bulma CSS为您的应用设置样式

您已经在本教程中了解了很多内容,并构建了一个可以与您的朋友和家人分享的应用程序。您还可以将其显示为潜在雇主的投资组合项目。

下一步

如果您已经创建了作品集网站,请在此处添加您的项目以展示您的作品。您可以不断改进您的 Django 社交网络以添加功能并使其更加令人印象深刻。

这里有一些想法可以让你的项目更上一层楼:

你还能想出什么其他想法来扩展这个项目?在下面的评论中分享您的项目链接和进一步发展的想法!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200