按着追梦的教程写博客
地址:https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/materials/63/

blog应用

第一步:设置

创建项目、应用

  1. django-admin startproject Thursday
  2. cd Thursday
  3. django-admin startapp blog

配置

  • 注册应用
  • 自定义tmeplate和static位置
    • 默认在应用下的 templates、static文件找。
    • 我们要让它在根目录找,所有在根目录创建 templates、static文件夹
    • image.png ```python import os

40行左右

INSTALLED_APPS = [

  1. 'Thursday',
  2. 'blog', # 注册应用

]

56行,

TEMPLATES = [ { ‘BACKEND’: ‘django.template.backends.django.DjangoTemplates’, ‘DIRS’: [os.path.join(BASE_DIR,’templates’)], # 改这里 ‘APP_DIRS’: True, ‘OPTIONS’: { ‘context_processors’: [ ‘django.template.context_processors.debug’, ‘django.template.context_processors.request’, ‘django.contrib.auth.context_processors.auth’, ‘django.contrib.messages.context_processors.messages’, ], }, }, ]

static-files-dirs 新增这一行

STATICFILES_DIRS = [os.path.join(BASE_DIR,’static’)]

  1. <a name="jJPvi"></a>
  2. ### 函数
  3. - 在template文件夹下创建 blog文件夹,创建 index.html文件。
  4. - blog应用下 viwes.py写函数处理
  5. ```python
  6. from django.shortcuts import render
  7. def index(request):
  8. return render(request,'blog/index.html')

页面

就随便写点

  • 内容就 星期四
  • 有时候内容太少不显示。 模板写多一点再刷新就有了

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <title>Document</title>
    8. </head>
    9. <body>
    10. <h3>星期四了</h3>
    11. <div class="">2222</div>
    12. </body>
    13. </html>

    路由

    主路由 :‘’
    子路由:‘’
    默认地址,直接让index函数显示index页面, http://127.0.0.1:8000/

  • 主路由 ```python from django.contrib import admin from django.urls import path,include

urlpatterns = [ path(‘admin/‘, admin.site.urls), path(‘’,include(‘blog.urls’)),

]

  1. - 子路由
  2. - blog下创建urls.py 文件
  3. ```python
  4. from django.urls import path
  5. from . import views
  6. app_name = 'blog'
  7. urlpatterns = [
  8. path('',views.index,name='index'),

运行效果

运行 py manage.py runserver
打开地址: http://localhost:8000/
报错正常,第一次要么打漏要么打错。要么先复制粘贴,要多打几次熟练就好了
image.png

第二步:准备

模型设计

3模型:Post、Tag、Category

  • 要创建3个模型,顺序: 先有 分类 Category、标签 Tag、再有Post文章
  • 先简单写字段,后面将字段内属性。再写Meta属性、方法 ```python

from django.db import models

from django.utils import timezone from django.contrib.auth.models import User

from django.urls import reverse

Create your models here.

分类、标签、文章

分类标签,就一个字段,name

  1. # 填的最大长度一样, 100

每个模型都写,方便后台管理

  1. # Meta属性, 字段别名 verbore_name
  2. # __str__方法,指向自身第一个字段

文章,3+2+3=8字段

  1. # 标题、内容、摘要
  2. # 标题, 填写最多70
  3. # 内容 text格式
  4. # 摘要,可空; 填写最多200
  5. # 创建时间、修改时间
  6. # 时间日期格式 datetime ,默认现在
  7. # 关联的模型, 作者、分类、标签s
  8. # 关联模型 的别名必须是 verbose_name ,其他 ‘’就行
  9. # 作者,分类都是一对一关系,外键删除-关联都删除
  10. # 标签,多对多,可空

class Category(models.Model): name = models.CharField(max_length=100)

  1. class Meta():
  2. verbose_name = '分类'
  3. verbose_name_plural = verbose_name
  4. def __str__(self) -> str:
  5. return self.name

class Tag(models.Model): name = models.CharField(max_length=100)

  1. class Meta():
  2. verbose_name = '标签'
  3. verbose_name_plural = verbose_name
  4. def __str__(self) -> str:
  5. return self.name

class Post(models.Model): title = models.CharField(‘标签’,max_length=200) body = models.TextField(‘内容’) summary = models.CharField(‘摘要’,max_length=200,blank=True)

  1. created_time = models.DateTimeField('创建时间',default=timezone.now)
  2. modified_time = models.DateTimeField('修改时间')
  3. author = models.ForeignKey(User,on_delete=models.CASCADE,verbose_name='作者')
  4. category = models.ForeignKey(Category,on_delete=models.CASCADE,verbose_name='分类')
  5. tags = models.ManyToManyField(Tag,blank=True,verbose_name='标签')
  6. class Meta():
  7. verbose_name = '文章'
  8. verbose_name_plural = verbose_name
  9. def __str__(self) -> str:
  10. return self.title
  1. django中默认设置的是UTC时区,所以我们数据库中存储时间就是UTC时区的时间<br />django.utils.timeone提供2个方法
  2. - now()根据 USE_TZ,时区时间
  3. - localtime() ,根据TIME_ZONE ,时间
  4. django.contrib是提供功能包
  5. - auth.models 提供身份验证的,User就是其中一个
  6. <a name="Um3v8"></a>
  7. #### 运行迁移
  8. 校验模型 `py manage.py makemigrations`<br />数据库创建表` py manage.py migate`<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/533164/1664434882114-74336d29-ae2f-4c38-b337-0ab3f91c8383.png#clientId=u2cfdf8b9-9643-4&errorMessage=unknown%20error&from=paste&height=108&id=u0f748981&originHeight=97&originWidth=284&originalType=binary&ratio=1&rotation=0&showTitle=false&size=2094&status=error&style=none&taskId=uc2b6f31f-e07a-46fc-8f97-22b41fef3d4&title=&width=315.55556391492325)
  9. <a name="ecZvR"></a>
  10. #### post模型补齐方法
  11. - 2方法,给文章地址,重写save(修改时间、摘要内容)
  12. - 获取绝对地址:get_absolute_url
  13. - 就是 detail 页面地址: post/<int:pk>
  14. - 反向解析,运行detail函数,关键字参数传递 pk
  15. - 重写save
  16. - 修改时间 ,保存当前时间。------改文章才保存才触发
  17. - 摘要 = 文章内容的前25个字
  18. - 编辑文章的格式是 markdown,需要转为html
  19. - 再将html的标签除去,留下文本内容
  20. ```python
  21. from django.db import models
  22. from django.utils import timezone
  23. from django.contrib.auth.models import User
  24. from django.urls import reverse
  25. from django.utils.html import strip_tags
  26. import markdown
  27. #post 多了2个方法。
  28. # 获取绝对地址 get_absoult_url
  29. # 反向解析,获取blog下detail端点的 地址 ,
  30. # 通过关键字参数,将 self.pk 值传递给键 'pk'
  31. # 复写 save方法
  32. # 更新修改时间,获取当前时间复制给 modified
  33. # 实例化 markdown复制给 md
  34. # extensions扩展选项: 额外extra、亮高code hilite
  35. # 取摘要的一部分
  36. # convert方法,将body的内容 md转为html格式,
  37. # strip_tags方法,再 取出标签要文本内容
  38. # [:54],最后切片要前54个字符
  39. # super(),继承保存
  40. class Post(models.Model):
  41. def get_absolute_url(self): # absolute
  42. return reverse('blog:detail',kwargs={'pk':self.pk})
  43. # 重新保存,修改时间为现在的时间。 这样后台管理才能保存,不然报错
  44. def save(self,*args,**kwargs) -> None:
  45. self.modified_time = timezone.now() # 是方法 now()
  46. md = markdown.Markdown(extensions=[
  47. 'markdown.extensions.extra',
  48. 'markdown.extensions.codehilite',
  49. ])
  50. self.summary = strip_tags(md.convert(self.body))[:54]
  51. return super().save(*args,**kwargs)

django.utils除了提供 timeone,还提供其他。如html

  • strip_tags() 从html标签里提取文本,其他删除

    后台数据管理

    创建库表,里面没有数据。下面是用官方提供后台管理系统,手动添加内容。

    admin

  • 创建超级管理员

    • py manage.py createsuperuser
    • 账号 admin 邮箱 admin@example.com
    • 账号密码都是 admin y
  • blog应用下 admin.py 文件注册模型,方便后台增删改查
    • list-display:进去后台,能看到的列表内容。 ——-字段不能是多对多关系的
    • fields: 添加内容,显示/编辑的字段
  • 重写 保存模型
    • 就加一行关联, 请求用户 与 作者是同一个 ```python from django.contrib import admin from .models import Post,Category,Tag

创建文章的后台模型

  1. # 列表显示内容 list_display
  2. # 显示:标题、时间、分类、作者
  3. # 填写显示字段 fields
  4. # 显示:标题、内容、摘要、分类、标签
  5. # 写文件---作者、时间不写
  6. # 重写 保存模型方法
  7. # 请求的用户 赋值给 对象作者

后台注册模型

class PostAdmin(admin.ModelAdmin): list_display = [‘title’,’created_time’,’modified_time’,’category’,’author’] fields = [‘title’,’body’,’summary’,’category’,’tags’]

  1. def save_model(self, request, obj, form, change) -> None:
  2. obj.author = request.user
  3. super().save_model(request, obj, form, change)

admin.site.register(Post,PostAdmin) admin.site.register(Category) admin.site.register(Tag)

  1. <a name="RDkIF"></a>
  2. #### 装饰器写法
  3. ```python
  4. from django.contrib import admin
  5. from .models import Post,Category,Tag
  6. # 管理模型
  7. # 显示 列表的字段: 标题、时间、分类、作者
  8. # 修改显示:标题、内容、摘要、分类标签
  9. # 重写 保持模型方法
  10. # 请求的用户 赋值给 对象作者
  11. @admin.register(Post)
  12. class PostAdmin(admin.ModelAdmin):
  13. list_display = ['title','created_time','modified_time','category','author']
  14. fields = ['title','body','summary','category','tags']
  15. def save_model(self, request, obj, form, change) -> None:
  16. obj.author = request.user
  17. return super().save_model(request, obj, form, change)
  18. admin.site.register(Category)
  19. admin.site.register(Tag)

运行效果

运行 py manage.py runserver
打开地址:http://localhost:8000/admin/
管理员登录
image.pngimage.png

下载静态资源

下载博客模板,https://github.com/jukanntenn/django-blog-tutorial-templates

  • 下载下来是完整html,直接打开就 index.html 看完整代码

将js和css存放在项目下 static目录下 blog文件内3

模板

这模板内容,不是重点,复制粘贴过一下就行了

  • 页面头尾是重复的,将他们提炼出来放在 base.html ```html <!DOCTYPE html>

  1. <a name="umve2"></a>
  2. #### static的指定路径
  3. - 路径可能会跟着项目变而变,才用 static
  4. - 原本 :`href="css/custom.css"`
  5. - 改为:`href="{% static 'blog/css/custom.css' %}"`
  6. - 因为资源存放在 blog目录下
  7. - 外链接就不需要改
  8. - 加载 静态资源 ` {% load static %}`
  9. - 用宏,` {% block %} ` 是变得内容,用块来标识
  10. <a name="TZgEw"></a>
  11. #### base页面
  12. 原本页面应该是这样
  13. - 第一个容器 container
  14. - header > row
  15. - 2个 col 是互不,加起来 = 12
  16. - 写的是 导航栏:
  17. - navbar-header、navbar-collapse
  18. - header-search-box
  19. - search-menu、seacrch-form
  20. - 接着写 container-body
  21. - `main `> article * 4 + pagination分页
  22. - `aside` > widget * 4 (recent-post、archives、category、tag-cloud)、rss
  23. - footer 页脚
  24. 其中 页头、页脚是重复,换句话 main、aside内容一直变,将他们变成 块方便复用<br />他们结构没变,变的只是数据而已。<br />**base相对于父模板,内容不断嵌套在里面就行**
  25. ```html
  26. {% load static %}
  27. <!DOCTYPE html>
  28. <html>
  29. <head>
  30. <title>Black &amp; White</title>
  31. <!-- meta -->
  32. <meta charset="UTF-8">
  33. <meta name="viewport" content="width=device-width, initial-scale=1">
  34. {% static '' %}
  35. <!-- css -->
  36. <link rel="stylesheet" href="{% static 'blog/css/bootstrap.min.css' %}">
  37. <link rel="stylesheet" href="http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
  38. <link rel="stylesheet" href="{% static 'blog/css/pace.css' %}">
  39. <link rel="stylesheet" href="{% static 'css/custom.css' %}">
  40. <!-- js -->
  41. <script src="{% static 'js/jquery-2.1.3.min.js' %}"></script>
  42. <script src="{% static 'js/bootstrap.min.js' %}"></script>
  43. <script src="{% static 'js/pace.min.js' %}"></script>
  44. <script src="{% static 'js/modernizr.custom.js' %}"></script>
  45. </head>
  46. <body>
  47. <div class="container">
  48. <header class="site-header">
  49. <div class="row">
  50. <div class="col-md-4 col-sm-5 col-xs-8">
  51. <div class="logo">
  52. <h1><a href=""> <b>Black</b> &amp; white </a></h1>
  53. </div>
  54. </div>
  55. <div class="col-md-8 col-sm-7 col-xs-4">
  56. <nav class="main-nav" role="navigation">
  57. <div class="navbar-header">
  58. <button class="navbar-toggle" id="trigger-overlay" type="button">
  59. <span class="ion-navicon"></span>
  60. </button>
  61. </div>
  62. <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
  63. <ul class="nav navbar-nav navbar-right">
  64. <li class="cl-effect-11"><a href="" data-hover="首页">首页</a></li>
  65. <li class="cl-effect-11"><a href="" data-hover="博客">博客</a></li>
  66. <li class="cl-effect-11"><a href="" data-hover="关于">关于</a></li>
  67. <li class="cl-effect-11"><a href="" data-hover="联系">联系</a></li>
  68. </ul>
  69. </div>
  70. </nav>
  71. <div id="header-search-box">
  72. <a href="" id="search-menu"> <span id="search-icon" class="ion-ios-search-strong"></span></a>
  73. <div id="search-form" class="search-form">
  74. <form action="" method="get" id="searchform" role="search">
  75. <input type="search" placeholder="搜索" required>
  76. <button type="submit"> <span class="ion-ios-search-strong"></span></button>
  77. </form>
  78. </div>
  79. </div>
  80. </div>
  81. </div>
  82. </header>
  83. </div>
  84. <div class="content-body">
  85. <div class="container">
  86. <div class="row">
  87. <main class="col-md-8">
  88. {% block main %}
  89. {% endblock main %}
  90. </main>
  91. <aside class="col-md-4">
  92. {% block toc %}
  93. {% endblock toc %}
  94. </aside>
  95. </div>
  96. </div>
  97. </div>
  98. <script src="{% static 'js/script.js' %}"></script>
  99. </body>
  100. </html>

index页面

  • extends,继承base页面。 公共部分内容显示
  • block是块,个性化的内容,main是块名
    • {% block 名 %} {% endblock 名%}
  • empty,块是为空的情况显示

html结构

  • areicle
    • entry-title
    • entry-meta
      • category、date、author、comments-link、views-count
    • entry-content clearfix
      • p 摘要内容
      • read-more > a.more-link ```html {% extends ‘base.html ‘%}

{% block main %} {% for post in list_posts %}

{% endfor %} {% endblock main %}

  1. <a name="hla0Y"></a>
  2. #### detail 页面
  3. - 块 toc 存放的是侧边栏
  4. - aside 侧边栏就 5个部分
  5. - 最新文章、归档--时间日期、分类、标签云、RSS订阅
  6. ```html
  7. {% extends 'base.html' %}
  8. {% block main %}
  9. <article class="post post-{{ post.pk }}">
  10. <header class="entry-header">
  11. <h1 class="entry-title">{{ post.title }}</h1>
  12. <div class="entry-meta">
  13. <span class="post-category"><a href="#">{{ post.category.name }}</a></span>
  14. <span class="post-date"><a href="#"><time class="entry-date"
  15. datetime="{{ post.created_time }}">{{ post.created_time }}</time></a></span>
  16. <span class="post-author"><a href="#">{{ post.author }}</a></span>
  17. <span class="comments-link"><a href="{{ post.get_absolute_url }}#comment-area">{{ post.comment_set.count }} 评论</a></span>
  18. <span class="views-count"><a href="#">588 阅读</a></span>
  19. </div>
  20. </header>
  21. <div class="entry-content clearfix">
  22. {{ post.body | safe }}
  23. </div>
  24. </article>
  25. {% endblock main %}
  26. {% block toc %}
  27. <div class="widget widget-recent-posts">
  28. <h3 class="widget-title">最新文章</h3>
  29. <ul>
  30. <li>
  31. <a href="#">Django 博客开发入门教程:前言</a>
  32. </li>
  33. <li>
  34. <a href="#">Django 博客使用 Markdown 自动生成文章目录</a>
  35. </li>
  36. <li>
  37. <a href="#">部署 Django 博客</a>
  38. </li>
  39. </ul>
  40. </div>
  41. <div class="widget widget-archives">
  42. <h3 class="widget-title">归档</h3>
  43. <ul>
  44. <li>
  45. <a href="#">2017 年 5 月</a>
  46. </li>
  47. <li>
  48. <a href="#">2017 年 4 月</a>
  49. </li>
  50. <li>
  51. <a href="#">2017 年 3 月</a>
  52. </li>
  53. </ul>
  54. </div>
  55. <div class="widget widget-category">
  56. <h3 class="widget-title">分类</h3>
  57. <ul>
  58. <li>
  59. <a href="#">Django 博客教程 <span class="post-count">(13)</span></a>
  60. </li>
  61. <li>
  62. <a href="#">Python 教程 <span class="post-count">(11)</span></a>
  63. </li>
  64. <li>
  65. <a href="#">Django 用户认证 <span class="post-count">(8)</span></a>
  66. </li>
  67. </ul>
  68. </div>
  69. <div class="widget widget-tag-cloud">
  70. <h3 class="widget-title">标签云</h3>
  71. <ul>
  72. <li>
  73. <a href="#">Django</a>
  74. </li>
  75. <li>
  76. <a href="#">Python</a>
  77. </li>
  78. <li>
  79. <a href="#">Java</a>
  80. </li>
  81. <li>
  82. <a href="#">笔记</a>
  83. </li>
  84. <li>
  85. <a href="#">文档</a>
  86. </li>
  87. <li>
  88. <a href="#">AngularJS</a>
  89. </li>
  90. <li>
  91. <a href="#">CSS</a>
  92. </li>
  93. <li>
  94. <a href="#">JavaScript</a>
  95. </li>
  96. <li>
  97. <a href="#">Snippet</a>
  98. </li>
  99. <li>
  100. <a href="#">jQuery</a>
  101. </li>
  102. </ul>
  103. </div>
  104. <div class="rss">
  105. <a href=""><span class="ion-social-rss-outline"></span> RSS 订阅</a>
  106. </div>
  107. {% endblock toc %}

第三步

写视图:函数、路由

函数

index函数

  • index页面,给post_list变量
  • post_list 是全部文章数据,要排序 ```python from django.shortcuts import render,get_object_or_404,redirect from django.urls import reverse

from .models import Post,Category,Tag

import markdown import re from django.utils.text import slugify from markdown.extensions.toc import TocExtension

index 函数

  1. # 获取文章的全部数据,排序; 赋值给 post_list
  2. # 渲染到 index.html页面,给post_list

def index(request): post_list = Post.objects.all().order_by(‘-created_time’) return render(request,’blog/index.html’,{‘post_list’:post_list})

  1. <a name="TOJJA"></a>
  2. #### archive函数
  3. - 给` index `页面,给` post_list `变量
  4. - post_list 是**过滤指定年月**的所有文章数据,要排序
  5. ```python
  6. # archive 函数
  7. # 条件过滤获取文章的数据,按创建时间排序。__year __month
  8. # 赋值给 post_list
  9. # 渲染到 index.html页面,给post_list
  10. def archive(request,year,month):
  11. post_list = Post.objects.filter(
  12. created_time__year = year,
  13. created_time__month = month,
  14. ).order_by('-created_time')
  15. return render(request,'blog/index.html',{'post_list':post_list})

category、tag

  • 给 index页面数据,post_list
  • post_list 是 过滤指定分类的 ,排序
    • 那个分类 呢, 赋值给cate ```python

      category 函数

      获取指定分类,赋值给cate

      在文章里过滤指定分类出来,赋值给post_list

      渲染到 index.html页面,给post_list

def category(request,pk): cate = get_object_or_404(Category,pk=pk) post_list = Post.objects.filter(category=cate).order_by(‘-created_time’) return render(request,’blog/index.html’,{‘post_list’:post_list})

tag 函数

  1. # 与category类似
  2. # 获取指定标签,赋值给t
  3. # 在文章里过滤指定标签出来,赋值给post_list
  4. # 渲染到 index.html页面,给post_list

def tag(request,pk): t = get_object_or_404(Tag,pk=pk) post_list = Post.objects.filter(tags=t).order_by(‘-created_time’) return render(request,’blog/index.html’,{‘post_list’:post_list})

  1. <a name="jfdbH"></a>
  2. #### detail 函数
  3. - 给 detail页面,数据 post
  4. - post,是 指定的 文章数据
  5. - 实例化,markdown,赋值给 md
  6. - 将 post.body 转为 html文本,赋值给 post.body
  7. - 正则指定内容,不存在就是空字符串。赋值 post.toc
  8. re.search
  9. - (要的模式,匹配范围,规则) 在字符串中匹配值
  10. - re.group() 返回键值对对象
  11. - re.S 任意字符 I忽略大小写 M对行模式
  12. 正则
  13. - `. `,匹配 0或多次
  14. - `\s`, 出现空白就匹配
  15. - ` \S`, 出现非空白就匹配
  16. `markdown.Markdown().toc` 生成目录结构<br />模型类.objects.filter(属性名__运算符=值)<br /> 比较查询:gt、gte、lt、lte<br /> 日期查询:day、year、month、day、week_day、hour、minute、second
  17. ```python
  18. from django.shortcuts import render,get_object_or_404,redirect
  19. from django.urls import reverse
  20. from .models import Post,Category,Tag
  21. import markdown
  22. import re
  23. from django.utils.text import slugify
  24. from markdown.extensions.toc import TocExtension
  25. # detail函数
  26. # 获取指定文章数据,赋值给 post
  27. # 实例化markdown,赋值给 md
  28. # 扩展选项:常规的扩展、亮高、目录,能识别中文
  29. # extensions、extra、codehilite、TocExtension、slugify
  30. # 将文章内容 转为 html格式,赋值给 post.body
  31. # 正则搜索 符合条件的 包含 (.toc标签 的 ul) 赋值给m
  32. # 包含内容: <div class="toc">
  33. # <ul></ul>
  34. # </div>
  35. # 范围: md.toc
  36. # 规则: re.S 空白/换行符就匹配
  37. # 判断:最终赋值给 post.toc
  38. # 三元写法
  39. # 取键值对对象的第一个, re.group(1),m不是空的话,
  40. # 否则,为空字符串
  41. # 渲染到 detail.html页面,给post
  42. def detail(request,pk):
  43. post = get_object_or_404(Post,pk=pk)
  44. md = markdown.Markdown(extensions=[
  45. 'markdown.extensions.extra',
  46. 'markdown.extensions.codehilite',
  47. TocExtension(slugify= slugify),
  48. ])
  49. post.body = md.convert(post.body) #将md转为html, 也会生成目录 md.toc
  50. m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>',md.toc,re.S)
  51. post.toc = m.group(1) if m is not None else ''
  52. return render(request,'blog/detail.html',{'post':post})

路由

  1. from ctypes.wintypes import tagSIZE
  2. from django.urls import path
  3. from . import views
  4. # 路由
  5. # /
  6. # post/数字,
  7. # archive/年/月
  8. # category/数字
  9. # tags/数字
  10. app_name = 'blog'
  11. urlpatterns = [
  12. path('',views.index,name='index'),
  13. path('posts/<int:pk>',views.detail,name='detail'),
  14. path('archive/<int:year>/<int:month>',views.archive,name='archive'),
  15. path('category/<int:pk>',views.category,name='category'),
  16. path('tags/<int:pk>',views.tag,name='tag'),
  17. ]

运行效果

image.png
类视图函数
路由

comment应用

第一步:评论

comments应用

配置,注册

  1. INSTALLED_APPS = [
  2. 'comments',
  3. ]

模型

  1. from django.db import models
  2. from django.utils import timezone
  3. # 评论模型
  4. # 字段:名字、邮箱、链接、内容、创建时间、关联博客的文章 blog.Post
  5. # Meta属性
  6. # __str__方法
  7. # 将self.name赋值给第一个{}, self.text赋值给第二个{},裁剪前20
  8. class Comment(models.Model):
  9. name = models.CharField('姓名',max_length=50)
  10. email = models.EmailField('邮箱')
  11. url = models.URLField('网站',blank=True)
  12. text = models.TextField('内容')
  13. created_time = models.DateTimeField('创建时间',default=timezone.now)
  14. post = models.ForeignKey('blog.Post',on_delete=models.CASCADE,verbose_name='文章') # 关联到Post
  15. class Meta():
  16. verbose_name = '评论'
  17. verbose_name_plural = verbose_name
  18. def __str__(self) -> str:
  19. return '{}:{}'.format(self.name,self.text[:20])

表单

新建表单

  • 绑定 Comment模型
  • 显示:名、邮箱、网站、内容 ```python from django import forms from .models import Comment

class CommentForm(forms.ModelForm):

  1. class Meta():
  2. model = Comment
  3. fields = ['name','email','url','text']
  1. <a name="KWxe7"></a>
  2. ### 后台管理
  3. ```python
  4. from django.contrib import admin
  5. from .models import Comment
  6. # 管理comment
  7. # 列表显示:名、邮箱、网站、文章、创建时间
  8. # 表单字段显示:名、邮箱、网站、内容、文章
  9. @admin.register(Comment)
  10. class CommentAdmin(admin.ModelAdmin):
  11. list_display = ['name','email','url','post','created_time']
  12. fields = ['name','email','url','text','post']

自定义 “组件”

在comments应用下,创建 templatetags文件

  • 创建 __init__.py 表示是一个包
  • comments_extras.py,这个名字随意,前面的是固定写法

    show_comment_form 函数

    评论表单

  • inclusion/_form.html页面,上下文对象(内容 post,form)

    • 注册自定义组合 @template.Library().inclusion_tag()
      • 指定模板,函数处理后,数据连同一起给
  • 模板怎么用
    • 定义好 “组件”
    • 在 detail页面 用
      • 先加载 “组件”{% load comments_extras %}
      • 调用{% show_comment_form post %} post是父模板给的数据,
        • detial页面的数据 post_list > post ```python

from django import template from ..form import CommentForm

实例化 模板自定义标签 Library,赋值给 register

通过装饰器,注册调用inclusion_tag方法

  1. # 渲染_form页面,数据也能传递给父模板 take_context

函数, show_comment_form 展示评论表单

  1. # 参数:上下文,post,空form
  2. # 判断form,如果为空
  3. # 实例化表单 CommentForm,赋值给 form
  4. # 返回上下文字典:form,post

register = template.Library() # 实例化 自定义标签

@register.inclusion_tag(‘comments/inclusions/_form.html’,takes_context=True) def show_comment_form(context,post,form=None): if form is None: form = CommentForm() return {‘form’:form,’post’:post}

  1. <a name="COMgF"></a>
  2. #### show_comments 函数
  3. 显示评论内容
  4. - 在 detail页面 用
  5. - 先加载 “组件”` {% load comments_extras %}`
  6. - 调用` {% show_comments post %}` post是传递参数,
  7. - detial页面的数据 post_list > post
  8. ```python
  9. # 装饰器,注册自定义标签 @template.Library().inclusion_tag
  10. # 渲染模板 _list.html页面
  11. # 数据也传递给父模板 takes-context=true
  12. # 评论内容
  13. # 模型有Comment.post,反向查询 post.comment_set获取post对应 全部评论
  14. # 排序,赋值给 comment_list
  15. # 评论数, count()方法,赋值给 comment_count
  16. # 返回: 上下文对象(评论列表、总评论数)
  17. # 显示评论内容,show-comments
  18. @register.inclusion_tag('comments/inclusions/_list.html', takes_context=True)
  19. def show_comments(context, post):
  20. # post.comment_set.all() 也等价于 Comment.objects.filter(post=post)
  21. # Post.objects.filter(category=cate) 也可以等价写为 cate.post_set.all()。
  22. comment_list = post.comment_set.all().order_by('-created_time') #获取post对应全部评论
  23. comment_count = comment_list.count()
  24. return {
  25. 'comment_count': comment_count,
  26. 'comment_list': comment_list,

模板

在tmeplates下创建 comments文件夹

  • 创建 prieview.html文件
  • 创建文件夹 inclusions
    • 创建 form、list

image.png

_form 页面

效果是这样的
image.png

  1. <form action="{% url 'comments:comment' post.pk %}" method="post" class="comment-form">
  2. {% csrf_token %}
  3. <div class="row">
  4. <div class="col-md-4">
  5. <label for="{{ form.name.id_for_label }}">{{ form.name.label }}:</label>
  6. {{ form.name }}
  7. {{ form.name.errors }}
  8. </div>
  9. <div class="col-md-4">
  10. <label for="{{ form.email.id_for_label }}">{{ form.email.label }}:</label>
  11. {{ form.email }}
  12. {{ form.email.errors }}
  13. </div>
  14. <div class="col-md-4">
  15. <label for="{{ form.url.id_for_label }}">{{ form.url.label }}:</label>
  16. {{ form.url }}
  17. {{ form.url.errors }}
  18. </div>
  19. <div class="col-md-12">
  20. <label for="{{ form.text.id_for_label }}">{{ form.text.label }}:</label>
  21. {{ form.text }}
  22. {{ form.text.errors }}
  23. <button type="submit" class="comment-btn">发表</button>
  24. </div>
  25. </div> <!-- row -->
  26. </form>

_list 页面

下个如下
image.png

  1. <h3>评论列表,共 <span>{{ comment_count }}</span> 条评论</h3>
  2. <ul class="comment-list list-unstyled">
  3. {% for comment in comment_list %}
  4. <li class="comment-item">
  5. <span class="nickname">{{ comment.name }}</span>
  6. <time class="submit-date" datetime="{{ comment.created_time }}">{{ comment.created_time }}</time>
  7. <div class="text">
  8. {{ comment.text|linebreaks }}
  9. </div>
  10. </li>
  11. {% empty %}
  12. 暂无评论
  13. {% endfor %}
  14. </ul>

preview 页面

  • 提交表单包含错误,渲染 该页面 ```python {% extends ‘base.html’ %} {% load comments_extras %}

{% block main %} {% show_comment_form post form %} {% endblock main%}

  1. <a name="qM8Yw"></a>
  2. ### 函数
  3. <a name="Wu4Oe"></a>
  4. #### comment.
  5. - 添加表单数据, 保存到数据库
  6. - 关联被评论的文章
  7. - 渲染到` preview`页面,参数 form,post
  8. ```python
  9. from django.shortcuts import render,get_object_or_404,redirect
  10. from .form import CommentForm
  11. from blog.models import Post
  12. from django.views.decorators.http import require_POST
  13. from django.contrib import messages
  14. # Create your views here.
  15. # post请求装饰器
  16. # comment函数
  17. # 获取指定文章数据,赋值给 post
  18. # 实例化评论表单,将POST请求数据放进去,赋值给 form
  19. # 检查form
  20. # commit=False 创建模型不给数据库,赋值给 comment
  21. # 将 post 赋值给 comment.post, 被评论的文章关联起来
  22. # 保存 comment.save
  23. # 重定向到 post模型(detail页面)
  24. # 渲染 preview页面
  25. # 实例化message对象,
  26. # 扩展提示 extra-tags
  27. # 将对象,post,form赋值给 context
  28. # 渲染 preview.html页面
  29. @require_POST
  30. def comment(request,post_pk):
  31. post = get_object_or_404(Post,pk=post_pk)
  32. form = CommentForm(request.POST)
  33. if form.is_valid():
  34. comment = form.save(commit=False)
  35. comment.post = post
  36. comment.save()
  37. # 参数:消息级别,额外标签
  38. messages.add_message(request,messages.SUCCESS,'评论发布成功',extra_tags='success')
  39. return redirect(post) # 重定向到模型实例, 调用模型里的 get_absolute_url
  40. context = {
  41. 'post':post,
  42. 'form':form,
  43. }
  44. # messages.add_message(request,messages.ERROR,'评论失败,重新提交',extra_tags='danger')
  45. return render(request,'comments/preview.html',context=context)

messages 对象

在base页面上显示

  1. <header>
  2. ...
  3. </header>
  4. {% if messages %}
  5. {% for message in messages %}
  6. <div class="alert alert-{{ message.tags }} alert-dismissible" role="alert">
  7. <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
  8. aria-hidden="true">&times;</span></button>
  9. {{ message }}
  10. </div>
  11. {% endfor %}
  12. {% endif %}

效果如下
image.png

路由

主路由

  1. from django.contrib import admin
  2. from django.urls import path,include
  3. urlpatterns = [
  4. path('admin/', admin.site.urls),
  5. path('',include('blog.urls')),
  6. path('',include('comments.urls')), # 新增这一行
  7. ]

子路由

在comments应用下,创建urls.py文件.

  1. from django.urls import path
  2. from . import views
  3. app_name = 'comments'
  4. urlpatterns = [
  5. path('comment/<int:post_pk>',views.comment,name='comment'),
  6. ]