美文网首页Django学习Tech我是程序员;您好程先生;叫我序员就好了
Django 学习小组:博客开发实战第二周教程 —— 博客详情页

Django 学习小组:博客开发实战第二周教程 —— 博客详情页

作者: 追梦人物 | 来源:发表于2016-05-27 14:09 被阅读10131次

    本教程内容已过时,更新版教程请访问: Django 博客开发入门教程

    上周我们完成了博客的 Model 部分,以及 Blog 的首页视图 IndexView。

    相关教程:
    第一周Django 学习小组:博客开发实战第一周教程 —— 编写博客的 Model 与首页面
    第三周Django 学习小组:博客开发实战第三周教程——文章列表分页和代码语法高亮
    第四周Django 学习小组:博客开发实战第四周——标签云与文章归档
    第五周Django 学习小组:博客开发实战第五周——基于类的通用视图详解(一)
    第六周Django 学习小组:博客开发实战第六周教程 —— 实现评论功能

    我们继续给博客添加功能,以及改善前面不合理的部分。本教程将带你完成 Blog 的详情页面,即用户点击首页的文章标题或者阅读全文按钮将跳转到文章的详情页面来阅读整篇文章。其次将调整一些目录结构以使其在实践应用中更加合理。

    提示:在阅读教程的过程中,如有任何问题请访问我们项目的 GithHub 或评论留言以获取帮助,本教程的相关代码已全部上传在 GitHub 的 blog-tutorial 分支 。如果你对我们的教程或者项目有任何改进建议,请您通过随时告知我们。更多交流请加入我们的邮件列表 django_study@groups.163.com

    重写URL

    对于一个有多个 app 的项目,把所有的 urlpatterns 都放在项目的 urls.py 似乎不是一个很合适的选择,为此我们需要在 blog 文件夹下新建一个文件 urls.py ,把跟这个 app 相关的 urlpatterns 都放在这个文件里。
    文件中的 urlpatterns 看不懂暂时没关系,下面很快就会介绍它。

    # blog/urls.py
    from django.conf.urls import url
    from blog import views
    
    urlpatterns = [
        url(r'^blog/$', views.IndexView.as_view(), name='index'),
        url(r'^blog/article/(?P<article_id>\d+)$', views.ArticleDetailView.as_view(), name='detail'),
        url(r'^blog/category/(?P<cate_id>\d+)$', views.CategoryView.as_view(), name='category'),
    ]
    # 使用(?P<>\d+)的形式捕获值给<>中得参数,比如(?P<article_id>\d+),当访问/blog/article/3时,将会将3捕获给article_id,这个值会传到views.ArticleDetailView,这样我们就可以判断展示哪个Article了
    

    然后在项目的 urls.py 中包含(include)它:

    # DjangoBlog/blog_project/urls.py
    from django.conf.urls import url, include
    from django.contrib import admin
    from blog import views
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'', include('blog.urls', namespace='blog', app_name='blog'))
    ]
    # 其中namespace参数为我们指定了命名空间,这说明这个urls.py中的url是blog app下的,这样即使不同的app下有相同url也不会冲突了。
    

    这样,我们就重写了 URL,看起来是不是更有条理了?

    新增文章详情页

    class ArticleDetailView(DetailView):
    # Django有基于类的视图DetailView,用于显示一个对象的详情页,我们继承它
        model = Article
        # 指定视图获取哪个model
        
        template_name = "blog/detail.html"
        # 指定要渲染的模板文件
        
        context_object_name = "article"
        # 在模板中需要使用的上下文名字
        
        pk_url_kwarg = 'article_id'
        # 这里注意,pk_url_kwarg用于接收一个来自url中的主键,然后会根据这个主键进行查询
        # 我们之前在urlpatterns已经捕获article_id
    
        # 指定以上几个属性,已经能够返回一个DetailView视图了,为了让文章以markdown形式展现,我们重写get_object()方法。
        def get_object(self):
            obj = super(ArticleDetailView, self).get_object()
            obj.body = markdown2.markdown(obj.body)
            return obj
    

    get_object() 返回该视图要显示的对象。如果有设置 queryset,该queryset 将用于对象的源;否则,将使用get_queryset(). get_object()从视图的所有参数中查找 pk_url_kwarg 参数; 如果找到了这个参数,该方法使用这个参数的值执行一个基于主键的查询。

    新建一个模板 detail.html 来展示我们的文章详情

    blog/templates/blog/detail.html
    
    {% extends 'base.html' %}
    {% block content %}
        <div id="bd" class="wrp clear-fix">
            <div id="main">
                <div id="detail-title">
                    <ul id="single-nav">
                        <li><a href="{% url 'blog:index' %}">首页</a></li>
                        <li>></li>
                        <li>
                            <ul class="post-categories">
                                <li><a href="" title=""
                                       rel="category">{{ article.category.name }}</a>
                                </li>
                            </ul>
                        </li>
                        <li>></li>
                        <li class="title-active"><a href="{% url 'blog:detail' article.pk %}"
                                                    rel="bookmark">{{ article.title }}</a>
                        </li>
                    </ul>
                </div>
                <div id="post-1951"
                     class="post-1951 post type-post status-publish format-standard hentry category-meida-report">
                    <div class="post-hd">
                        <h1 class="title">{{ article.title }}</h1>
                    </div>
                    <div class="date-read">
                        <i class="icon-date"></i><span class="date">{{ article.last_modified_time|date:"Y年n月d日" }}</span>
                    </div>
                    <div class="post-bd">
                        {{ article.body |safe }}
                    </div>
                </div>
            </div>
        </div>
        <div id="previous-next-nav">
        </div>
    {% endblock %}
    

    整个执行流程就是这样的:

    假设用户要访问某篇文章,比如他点击了某篇文章的标题,在模板文件中(首页的模板,代码可以参见 GitHub 的 blog-tutorial 分支上的 index.html),他点击的就是这样一个标签:

    <h1 class="title">
      <a href="{% url 'blog:detail' article.pk %}">{{ article.title }}</a>
    </h1>
    

    <a> 标签是一个超链接,用户点击后会跳转到由 href 指定的 url,这里我们使用了 django 自带的模板标签 url 标签,它会自动解析 blog:detail 这个视图函数对应的 url,并且把 article.pk(文章的主键)传递给detail 视图函数 。detail 的 url 是这样定义的:

    url(r'^blog/article/(?P<article_id>\d+)$', views.ArticleDetailView.as_view(), name='detail')
    

    假设用户点击了第三篇文章,那么该 url 会被解析成:/blog/article/3,其中 3 被传递给了详情页面视图函数。

    现在视图函数被调用,它首先根据传给它的参数获自动调用 get_object 方法取到文章的 model,然后根据 context_object_name = "article" 把 article 加入到上下文中(可以理解为携带着这个变量及其值并要传递给模板文件的对象,模板文件从这个对象中取出模板变量对应的值并替换。),之后渲染 template_name = "blog/detail.html" 指定的模板文件,至此用户就跳转到了文章详情页,效果如下:

    文章详情页

    新增分类视图

    点击某个分类,展示该分类下所有文章,其逻辑和首页展示全部文章列表是一样的,唯一不同的是我们获取的不是全部文章,而是该分类下的文章。代码如下:

    class CategoryView(ListView):
    # 继承自ListView,用于展示一个列表
    
        template_name = "blog/index.html"
        # 指定需要渲染的模板
        
        context_object_name = "article_list"
        # 指定模板中需要使用的上下文对象的名字
    
        def get_queryset(self):
            #get_queryset 的作用已在第一篇中有介绍,不再赘述
            article_list = Article.objects.filter(category=self.kwargs['cate_id'],status='p')
            # 注意在url里我们捕获了分类的id作为关键字参数(cate_id)传递给了CategoryView,传递的参数在kwargs属性中获取。
            for article in article_list:
                article.body = markdown2.markdown(article.body, )
            return article_list
        
        # 给视图增加额外的数据
        def get_context_data(self, **kwargs):
            kwargs['category_list'] = Category.objects.all().order_by('name')
            # 增加一个category_list,用于在页面显示所有分类,按照名字排序
            return super(CategoryView, self).get_context_data(**kwargs)
    

    这里我们复用的是主页的模板(因为展示的东西都是一样的),点击相应的分类,展示该分类下所有文章。同样别忘了如果要用户点击分类按钮跳转到分类页面的话,要指定 <a> 标签的 href 属性,善用 url 模板标签,防止硬编码 url,像这样:

    <li class="cat-item">
      <a href="{% url 'blog:category' category.pk %}">{{ category.name }}</a>
    </li>
    

    分类显示效果如下,显示分类二下的全部文章:

    分类显示效果图

    接下来做什么?

    至此,我们完成了博客的首页,详情展示页以及分类功能,基本的框架算是完成了。接下来我们会为我们的 Blog 添加更多高级的功能,包括有标签云、文章归档、文章分页等。敬请期待我们下一周的教程。如果你希望为你的 Blog 添加其他更加独特的功能,也请随时告诉我们。代码获取请点击:GitHub 的 blog-tutorial 分支

    Django学习小组简介

    django学习小组是一个促进 django 新手互相学习、互相帮助的组织。

    小组在一边学习 django 的同时将一起完成几个项目,包括:

    • 一个简单的 django 博客,用于发布小组每周的学习和开发文档;
    • django中国社区,为国内的 django 开发者们提供一个长期维护的 django 社区;

    上面所说的这个社区类似于 segmentfault 和 stackoverflow ,但更加专注(只专注于 django 开发的问题)。

    目前小组正在完成第一个项目,本文即是该项目第二周的相关文档。

    更多的信息请关注我们的 github 组织,本教程项目的相关源代码也已上传到 GitHub 的 blog-tutorial 分支 上。

    同时,你也可以加入我们的邮件列表 django_study@groups.163.com ,随时关注我们的动态。我们会将每周的详细开发文档和代码通过邮件列表发出。

    如有任何建议,欢迎提 issue,欢迎 fork,pr,当然也别忘了 star 哦!

    合作推广

    本系列教程将首发于 编程派 微信公众号,编程派(微信号:codingpy)是一个专注Python编程的公众号,每天更新有关Python的国外教程和优质书籍等精选干货,扫描下方二维码以关注:

    编程派

    相关文章

      网友评论

      • 19ba1f210aa3:'paginate_tags' is not a registered tag library. Must be one of:
        admin_list
        admin_modify
        admin_static
        admin_urls
        cache
        future
        i18n
        l10n
        log
        static
        staticfiles
        tz
        请问这个怎么解决?
        追梦人物:@19ba1f210aa3 有没有把 blog 加入到app?templatetags 文件夹里有没有 init.py文件?
      • 横店追梦人:你好 请教个问题,

        class ArticleDetailView(DetailView):

        model = Article
        template_name = "detail.html"
        context_object_name = "article"
        pk_url_kwarg = 'article_id'

        def get_object(self):
        obj = super(ArticleDetailView, self).get_object()

        这一块,总有个错误提示
        signature of method 'ArticleDetailView.get_object()' does not match signature of base method in class 'SingleObjectMixin'

        请问是什么原因?如何更正?
        横店追梦人:你好,已经解决了,是因为重写的方法argument跟父类方法不一致,def get_object(self)改成def get_obj(self, queryset=None) 就好了
        追梦人物:@横店追梦人 django和python的版本?
      • 商学人:Page not found,在github的分支上
        追梦人物:@商学人 仓库地址改了:https://github.com/zmrenwu/django-blog-tutorial
      • 托爸:在查看文章详情的时候报这个错:
        django.core.exceptions.ImproperlyConfigured: DetailView is missing a QuerySet. Define DetailView.model, DetailView.queryset, or override DetailView.get_queryset()。
        是一定要有queryset方法吗。
        我是初学者,麻烦帮忙看看,谢谢。
        托爸:找到原因了,ulr里的ArticleDetailView写成DetailView :joy:
        托爸:已经加了,下面是代码:
        model = Article
        template_name = "blog/detail.html"
        context_object_name = "article"
        pk_url_kwarg = 'article_id'
        def get_object(self,queryset=None):
        obj = super(ArticleDetailView, self).get_object()
        obj.body = markdown2.markdown(obj.body)
        return obj
        追梦人物:@托爸 在视图中指定 model=文章类,表明你要获取地是哪个模型
      • iseeican:还是用bootstrap设计前端比较好,比较整洁
        追梦人物:@iseeican 嗯,我重做了一套模板,过段时间更新。
      • mhye:不得不吐槽下,一点都不循序渐进,模板继承没设计,直接就跳出模板来了
        如果参考你们的源码,只能看到最终版本
        不过倒是有点好处,之前做过完整的练习的话,倒是可以把一口的地方补上
      • 897751fadbb7:请问你们是如何做到在后台admin管理界面可以添加中文数据的呢?之前一直提示unicode错误,跟ascii有关,后来在manage.py文件里面添加了reload(sys)以及sys.setdefaultencoding('utf-8'),这个问题才解决,我看了你们在github里面的源码,你们的manage.py文件里面并没有我加入的那两句话,你们是如何解决这个问题的呢?
        追梦人物:@ZhaoYingJie 有可能,我没用过py2,因此没对py2做兼容,所以用py2可能会遇到一些意想不到的问题。
        897751fadbb7:@追梦人物 对。。py2.7.10
        是这个的问题吗=。=
        追梦人物:@ZhaoYingJie 你用的是py2么?
      • 5a4d80a8b05c:我按照你写的代码,写了之后出现TemplateDoesNotExist at /blog/
        blog/index.html, blog/article_list.html是我哪里没配置好的原因吗?
        追梦人物:@2x_o小子 配置文件里没有设置模板路径?
      • c765a853fbdc:我现在的文章详情只有一个可以正常进入,其它点击都提示404未找到页面,我也是醉醉的
      • c765a853fbdc:index.html里面看不懂那
      • c765a853fbdc:Reverse for 'category' with arguments '(2L,)' and keyword arguments '{}' not found. 0 pattern(s) tried: []
        我出现了这个 是撒子意思呀
        897751fadbb7:@2x_o小子 好像不是这里的问题,是HTML文件里面要求传入键值
        5a4d80a8b05c:@ZhaoYingJie
        BASE_DIR = os.path.dirname(os.path.dirname(__file__))
        TEMPLATE_DIRS=(
        os.path.join(BASE_DIR,'templates'),
        )
        我是这样配置的,但是还是不行
        897751fadbb7:@Seletc index.html文件里面{{article.pk}}这种类似的键值需要传入,不然会报错,我也是在这里卡住了。。目前还没找到解决办法
      • c765a853fbdc:你好 admin部分有没有讲解呢,
        追梦人物:嗯,暂时没有做的计划了,请参考一下其他相关的教程吧。
      • 87ab4a69783c:你好,请问django中可以使用mysql的视图吗。我现在需要做两张表的连接查询,想直接用查询数据库视图的方式。
        追梦人物:@87ab4a69783c model的row方法可以执行原生的sql语句。
      • 16dda9e2f284:重写URL部分,在include的地方设置url(r'^blog/', include('blog.urls', namespace='blog', app_name='blog')),后面blog.urls中的blog就可以省去,看起来更简洁点?
        16dda9e2f284:@追梦人物 我试了不会
        追梦人物:@saturnisbig 省去会报错么?我没有试过。
      • 田飞雨:有没有关于基于类的视图的相关说明,看了官方文档也不是很懂
        追梦人物:@strugglingyouth 好的,出完基本教程出几篇类的通用视图的教程
      • 理性观看:你好,怎么加入邮件列表?
      • tenlee:前端界面确实做的不好看,有的地方排版还乱了,但是为什么不用bootstrap呢,有好多现成的模板
        追梦人物:@tenlee 目前正在优化,现在这前端页面都是抄的,如有好的推荐请告知我们。
      • d7d59ca6c6af:的确更新的有点慢。
      • Wayne_Echo:一周一篇太慢了
        EarlGrey:@Wayne_Echo 我好像在好友里搜到你了 :blush:
        Wayne_Echo:@EarlGrey 哈哈,有加你微信哦
        EarlGrey:@Wayne_Echo 不能只等着教程的,毕竟要花时间。建议自己根据教程里的链接去拓展学习

      本文标题:Django 学习小组:博客开发实战第二周教程 —— 博客详情页

      本文链接:https://www.haomeiwen.com/subject/olffdttx.html