Advertisement

开发博客文章详情页

阅读量:

声明此《Django分类》下的教程归追梦人物所有。本人在此处仅作复习巩固用途。

首页上呈现的是所有文章的列表页,并在用户发现感兴趣的文章时(即当用户看到感兴趣的文章),他可以通过点击文章标题或点击"继续阅读"按钮的方式触发跳转行为(即自动跳转),使其前往详细阅读页面以获取完整的内容信息。(注:这里补充了"即"字以增强语序逻辑性)。基于此需求背景(即现有系统架构基础),我们开始着手进行博客详情页(即个人博客详细展示页面)的技术开发工作。(注:此处补充了"基于此需求背景"使表述更加完整)。具体而言(即在此阶段),我们需要完成如下工作:首先配置URL(即将相关URL与对应的视图函数进行绑定),随后实现对应的视图函数逻辑(即完成功能模块设计),编写相应的模板文件,并通过视图函数进行渲染处理(完成页面展示效果)。

设计文章详情页的 URL

回顾一下我们首页视图的 URL,在 blog\urls.py 文件里,我们写了:

复制代码
    blog/urls.py
     
    from django.urls import path
     
    from . import views
     
    urlpatterns = [
    path('', views.index, name='index'),
    ]
    
    
      
      
      
      
      
      
      
      
      
    
    AI写代码

去掉域名后的 URL 实际上是一个空字符串。
从文章详情视图的角度来看, 每一篇文章都有其独特的 URL 路径。
例如, 在文章详情页面中设计如下: 当用户访问 <网站域名>/posts/1/, 则会展示第一篇文章; 如果访问 <网站域名>/posts/2/, 则会呈现第二篇文章。
下面依照这个规则来绑定 URL 和视图:

复制代码
    blog/urls.py
     
    from django.urls import path
     
    from . import views
     
    app_name = 'blog'
    urlpatterns = [
    path('', views.index, name='index'),
    path('posts/<int:pk>/', views.detail, name='detail'),
    ]
    
    
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

这里'posts/<int:pk>/'刚好匹配我们上面定义的 URL 规则。这条规则的含义是,以 posts/ 开头,后跟一个整数,并且以 / 符号结尾,如 posts/1/、 posts/255/ 等都是符合规则的,此外这里 <int:pk>是 django 路由匹配规则的特殊写法,其作用是从用户访问的 URL 里把匹配到的数字捕获并作为关键字参数传给其对应的视图函数 detail。比如当用户访问 posts/255/时(注意 django 并不关心域名,而只关心去掉域名后的相对 URL),<int:pk>匹配 255,那么这个 255 会在调用视图函数 detail 时被传递进去,其参数名就是冒号后面指定的名字 pk,实际上视图函数的调用就是这个样子:detail(request, pk=255)。我们这里必须从 URL 里捕获文章的 id,因为只有这样我们才能知道用户访问的究竟是哪篇文章。

Tip:
django 提供了多种路由匹配规则,在本例中我们使用了基于整数类型的匹配。除此之外,请注意还有字符串类型的匹配以及 uuid 类型的匹配等具体实现细节。更多详细信息可以参考官方文档中的 Path converters 部分。

此外,在app_name='blog'处向Django指定 urls.py 模块所属的应用为blog,并将其视为view function namespace的一部分。观察到在 blog/urls.py 文件中有两条view function,并为每条view function设置了唯一的名称:index和detail。然而,在一个复杂的Django项目中,可能会有多个应用使用相同的默认名称(如index和detail)来注册自己的view function。为了避免名称冲突导致的NoMatchReversed异常,请采用app_name指定namespace的方式来区分不同的view function集合。如果你忘记在 blog/urls.py 中添加此设置,则可能会遇到NoMatchReversed异常。

为了快速且简便地生成上述URL,在Post类中我们实现了get_absolute_url方法。需要注意的是,在Post类中存在这样的事实:任何Python类都可以继承并实现其相关的方法。

复制代码
    blog/models.py
     
    from django.contrib.auth.models import User
    from django.db import models
    from django.urls import reverse
    from django.utils import timezone
     
    class Post(models.Model):
    ...
     
    def __str__(self):
        return self.title
     
    # 自定义 get_absolute_url 方法
    # 记得从 django.urls 中导入 reverse 函数
    def get_absolute_url(self):
        return reverse('blog:detail', kwargs={'pk': self.pk})
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

在 URL 配置中...posts/<int:pk>/', views.detail, name='detail'时,在name属性被设置为'detail'的地方起到了关键作用。当我们查看reverse函数时,默认情况下它的第一个参数值是'blog:detail'(即 blog 应用中的name属性设置为detail的那个视图函数)。由于我们在设置app_name='blog'时告诉了Django这个URL模块属于 blog 应用范畴,Django能够顺利找到 blog 应用中名称为 detail 的视图函数(尽管此时还没有真正创建 detail 视图),因此reverse函数会解析该视图函数对应的URL路径(此处对应的是 detail 规则)。我们这里指定的 URL 规则是(posts/int:pk/),其中 int 类型的部分将会由后续传递的 pk 参数取代(此处 pk 和 id 是等价的概念)。因此,当Post对象存储的信息(即id或pk)是255时,调用get_absolute_url方法返回的就是'/posts/255/'这样一个完整的URL路径。这样一来,Post对象就能够自动生成自己的URL地址并正确绑定到数据库中去

编写 detail 视图函数

复制代码
    blog/views.py
     
    from django.shortcuts import render, get_object_or_404
    from .models import Post
     
    def index(request):
    # ...
     
    def detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'blog/detail.html', context={'post': post})
    
    
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

视图函数相对简单,它是根据我们从 URL 中捕获的文章 id(即 pk)来检索数据库中的对应记录,并将其传递给模板使用。其中,在 Django 库中我们调用了 django.shortcuts模块中的 get_object_or_404 方法。其功能在于:当传入的有效 pk 对应于数据库中的 Post 时返回该 Post对象;如果传入无效或不存在,则返回一个 404 错误信息。需要注意的是,在调用此方法之前必须先导入相关的模块和函数。

编写详情页模板

下一步就是要编写模板文件

复制代码
    blogproject\
    manage.py
    blogproject\
        __init__.py
        settings.py
        ...
    blog/
        __init__.py
        models.py
        ,,,
    templates\
        blog\
            index.html
            detail.html
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

在 index 页面上的博客文章列表标题以及继续阅读按钮处添加超链接跳转功能, 以便用户点击后可以直接进入 detail 页面查看详细信息。

复制代码
    templates/blog/index.html
     
    <article class="post post-{{ post.pk }}">
      <header class="entry-header">
    <h1 class="entry-title">
      <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
    </h1>
    ...
      </header>
      <div class="entry-content clearfix">
    ...
    <div class="read-more cl-effect-14">
      <a href="{{ post.get_absolute_url }}" class="more-link">继续阅读 <span class="meta-nav">→</span></a>
    </div>
      </div>
    </article>
    {% empty %}
      <div class="no-post">暂时还没有发布的文章!</div>
    {% endfor %}
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

这里我们修改两个地方,第一个是文章标题处:

复制代码
    <h1 class="entry-title">
      <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
    </h1>
    
    
      
      
      
    
    AI写代码

我们将 a 标签上的 href 属性设置为 {{ post.get_absolute_url }}。让我们回顾一下模板变量的作用方式。因为 get_absolute_url 这个方法(定义于 Post 类中)会返回对应文章的完整URL。因此,在此处使用{{ post.get_absolute_url }}时,它将被替换为该文章自身的URL。

同样,第二处修改的是继续阅读按钮的链接:

复制代码
    <a href="{{ post.get_absolute_url }}" class="more-link">继续阅读 <span class="meta-nav">→</span>
    </a>
    
    
      
      
    
    AI写代码

点击首页文章标题或继续阅读按钮时就会自动跳转至对应的文章详情页。然而尝试访问详情页时会发现样式存在混乱。这部分内容曾在《博客从"裸奔"到"有皮肤"》一文中讨论过。由于我们的开发团队直接采用了现有的模板库,并未对静态资源进行适当管理。通过之前介绍的方法调整静态文件引用路径虽然可行 但很快你会发现在任何页面都需要导入这些静态资源 这会导致频繁手动调整的工作量大幅增加 并且代码 repetitive. 下面我们将讲解如何利用Django的模板继承机制来减少工作量并优化代码结构。

模版继承

注意到 index.html 文件和 detail.html 文件的主要区别仅在于主标签部分的差异性之外,在其余部分存在高度的一致性。因此我们可以选择提取两份文件中共同保留的内容并将其整合至基础模板文件中即为 base.html 。具体操作方法如下:首先应在 templates 目录中创建新的基础模板文件命名为 base.html ,这样你的项目结构将会呈现出这样的布局:

复制代码
    blogproject\
    manage.py
    blogproject\
        __init__.py
        settings.py
        ...
    blog\
        __init__.py
        models.py
        ,,,
    templates\
        base.html
        blog\
            index.html
            detail.html
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

将 index.html 的文件内容全部复制到 base.html 文件中,并彻底删除 main 标签中的所有内容,并将其替换成以下内容。

复制代码
    templates/base.html
     
    ...
    <main class="col-md-8">
    {% block main %}
    {% endblock main %}
    </main>
    <aside class="col-md-4">
      {% block toc %}
      {% endblock toc %}
      ...
    </aside>
    ...
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

此块同样属于一种模板标签。其功能是作为占位使用。例如,在{% block main %}{% endblock main %}之间形成一个占位框。我们也会观察到这一功能的应用。此外,在aside标号内还会新增一个类似结构的块用于目录栏展示:如在 detail.html 文件中放置于aside标内的{% block toc %}{% endblock toc %}会生成目录栏项。若此结构内部无具体内容,则此块不会被展示;若有具体内容则会呈现出来。

在index.html文件中,在开头部分应用{% extends 'base.html' %}来继承base.html文件中的内容,并使base html中的代码被成功地复制到index页面相应的位置;同时,在{% block main %}{% endblock main %}所包围的区域填写index页面应显示的具体内容。

复制代码
    templates/blog/index.html
     
    {% extends 'base.html' %}
     
    {% block main %}
    {% for post in post_list %}
        <article class="post post-1">
          ...
        </article>
    {% empty %}
        <div class="no-post">暂时还没有发布的文章!</div>
    {% endfor %}
    <!-- 简单分页效果
    <div class="pagination-simple">
        <a href="#">上一页</a>
        <span class="current">第 6 页 / 共 11 页</span>
        <a href="#">下一页</a>
    </div>
    -->
    <div class="pagination">
      ...
    </div>
    {% endblock main %}
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

整合base.html中的代码以及位于{% block main %}{% endblock main %}内的内容后,其结果等同于index.html的内容。这体现了模板继承的机制,在此过程中其中公共模块的编码位于base.html中,并通过替换{% block main %}{% endblock main %}占位标签的内容来实现不同页面的独特性。

如果你对这种模板继承机制还存在些许疑惑,在学习过程中可以把这种模板继承机制与Python中类的继承进行对比分析。其中base.html类似于父类结构,在这一基础之上构建出一个新文件如index.html则相当于子类结构。需要注意的是,在子模板中还可以添加一些特有的内容这些新增的内容可以通过覆盖的方式添加到 {% block main %}{% endblock main %}中(将 block 视为父层对象的一种属性)。

处理起来相对简便,并且它继承自base.html 。在Markdown模板中的{% block main %}{% endblock main %} 区域内填充对应于detail.html 的页面内容,在另一区域即{% block toc %}{% endblock toc %} 中补充包含于base.html 但尚未展示的目录条目。目前这些条目仅作为占位符存在,在未来我们将实现从文章文本中自动提取目录功能。

复制代码
    templates/blog/detail.html
     
    {% extends 'base.html' %}
     
    {% block main %}
    <article class="post post-1">
      ...
    </article>
    <section class="comment-area">
      ...
    </section>
    {% endblock main %}
    {% block toc %}
    <div class="widget widget-content">
        <h3 class="widget-title">文章目录</h3>
        <ul>
            <li>
                <a href="#">教程特点</a>
            </li>
            <li>
                <a href="#">谁适合这个教程</a>
            </li>
            <li>
                <a href="#">在线预览</a>
            </li>
            <li>
                <a href="#">资源列表</a>
            </li>
            <li>
                <a href="#">获取帮助</a>
            </li>
        </ul>
    </div>
    {% endblock toc %}
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

修改 article 标签下的一些内容,让其显示文章的实际数据:

复制代码
    <article class="post post-{{ post.pk }}">
      <header class="entry-header">
    <h1 class="entry-title">{{ post.title }}</h1>
    <div class="entry-meta">
      <span class="post-category"><a href="#">{{ post.category.name }}</a></span>
      <span class="post-date"><a href="#"><time class="entry-date"
                                                datetime="{{ post.created_time }}">{{ post.created_time }}</time></a></span>
      <span class="post-author"><a href="#">{{ post.author }}</a></span>
      <span class="comments-link"><a href="#">4 评论</a></span>
      <span class="views-count"><a href="#">588 阅读</a></span>
    </div>
      </header>
      <div class="entry-content clearfix">
    {{ post.body }}
      </div>
    </article>
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    AI写代码

再一次从首页点击一篇文章的标题或者继续阅读按钮,导航至详情页,并可预览其内容。

在这里插入图片描述

全部评论 (0)

还没有任何评论哟~