Django项目实战:BBS+Blog项目开发之二页面数据和渲染
上接Django项目实战:BBS+Blog项目开发之一登录和注册功能
二九. 博客系统之系统首页的导航区域 index.thml
29.1 最终效果
29.2 从bootstrap中复制相关的样式,修改index.html
上bootstrap网站, ---组件---导航条----默认样式的导航条,复制代码到index.html,并修改,删除不用的部分.
最终效果
29.3 index.html源代码, 注意判断用户是否登录右上角的不同,使用{% if request.user.is_authenticated %}判断是否登录状态.
注意:用户登录名的左边有个人型头像,是bootstrap组件中的user字体图标. <style>#avater_icon{vertical-align: -3px}<style>微调头像的上下位置,与后面的名字持平.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <style> #avater_icon{ font-size: 5px; margin-right: 10px; } </style> </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">博客园</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">随笔 <span class="sr-only">(current)</span></a></li> <li><a href="#">新闻</a></li> <li><a href="#">博文</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {# 下面这句等同于{% if request.user.username %}, 就是判断用户是否已经登录 #} {% if request.user.is_authenticated %} <li><a href="#"><span id="avater_icon" class="glyphicon glyphicon-user"></span>{{ request.user.username }}</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">修改密码</a></li> <li><a href="#">修改头像</a></li> <li><a href="/logout/">注销</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </li> {% else %} {# 用户未登录显示 注册 登录 #} <li><a href="/login/">登录</a></li> <li><a href=/reg/>注册</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> </body> </html>
29.4 注销功能视图
def logout(request): auth.logout(request) # 等同于执行了,request.session.flush() return redirect('/login/')
三零. 博客系统之系统首页的主体布局
30.1 主体部分布局,采用左中右布局, bootstrap网站---全局css样式----栅格系统-----实例:流式布局容器-----实例:从堆叠到水平排列
<div class="container-fluid"> <div class="row"> {# 左中右布局 #} <div class="col-md-3">111</div> <div class="col-md-6">222</div> <div class="col-md-3">333</div> </div>
30.2 bootstrap网站---组件------面板. 将面板内容复制到col列中,放置多个面板,修改panel-primary, 这里在左右部分一共放了六个面板, 六中颜色情境效果.
<div class="col-md-3"> {# bootstrap,组件中的面板 #} <div class="panel panel-primary"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> </div>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <style> #avater_icon { font-size: 5px; margin-right: 10px; } </style> </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">博客园</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">随笔 <span class="sr-only">(current)</span></a></li> <li><a href="#">新闻</a></li> <li><a href="#">博文</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {# 下面这句等同于{% if request.user.username %}, 就是判断用户是否已经登录 #} {% if request.user.is_authenticated %} <li><a href="#"><span id="avater_icon" class="glyphicon glyphicon-user"></span>{{ request.user.username }}</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">修改密码</a></li> <li><a href="#">修改头像</a></li> <li><a href="/logout/">注销</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </li> {% else %} {# 用户未登录显示 注册 登录 #} <li><a href="/login/">登录</a></li> <li><a href=/reg/>注册</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container-fluid"> <div class="row"> {# 左中右布局 #} <div class="col-md-3"> {# bootstrap,组件中的面板 #} <div class="panel panel-primary"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-success"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-info"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> </div> <div class="col-md-6">222</div> <div class="col-md-3"> <div class="panel panel-warning"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-danger"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-default"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> </div> </div> </div> </body> </html>
30.3 完整效果
三一. 博客系统之admin的简单实用 网站后台管理, 方便管理数据库
31.1 明确一点,必须是超级用户才能用admin. admin是Django系统默认开启的功能.
31.2 使用前必须先把models.py中的表注册一下
在blog文件夹下admin.py文件中注册, 注意注册表的固定写法
from django.contrib import admin from blog import models # Register your models here. # 注册数据库中的表, 注册过就可以在admin后台看到了 admin.site.register(models.UserInfo) admin.site.register(models.Blog) admin.site.register(models.Category) admin.site.register(models.Tag) admin.site.register(models.Article) admin.site.register(models.Article2Tag) admin.site.register(models.ArticleUpDown) admin.site.register(models.Comment)
31.3 后台登录效果
三二. 博客系统之基于admin录入文章数据
32.1 按照下面的顺序添加:
先有用户user,再有博客blogs, 然后每个博客中增加分类categorys, 最后录入文章内容articles.录入文章时,注意作者和对应的分类不能弄混淆.
三三. 博客园系统之系统首页的文章列表的渲染1
33.1 最终效果, 渲染了文章标题, 头像, 摘要,下划线
33.2 视图函数中关键代码, 取出文章表中所有信息
def index(request): article_list = models.Article.objects.all() return render(request, 'index.html', {'article_list': article_list})
33.3 index.html模板中关键代码, 注意循环取出文章, 头像的路径, 头像和摘要是通过media-left和media-right来分成左右两边的.
<div class="col-md-6"> <div class="article-list"> {% for article in article_list %} <div class="article-item"> <h5><a href="">{{ article.title }}</a></h5> <div class="article-desc"> <span class="media-left"> <a href=""><img width="56" height="56" srcset="/media/{{ article.user.avatar }}"></a> </span> <span class="media-right"> {{ article.desc }} </span> </div> </div> <hr> {% endfor %} </div> </div>
三四. 博客系统之系统首页的文章列表的渲染2
34.1 效果, 渲染文章作者姓名, 发布时间, 评论数, 点赞数,这里的图标使用了bootstrap字体图标组件
34.2 index.html关键代码
.pub-info{
margin-top: 10px;
}
{# small表示让字小一些 #} <div class="small pub-info"> <span><a href="">{{ article.user.username }}</a></span> <span>发布于 {{ article.create_time|date:"Y-m-d H:i" }}</span> <span class="glyphicon glyphicon-comment"></span>评论数({{ article.comment_count }}) <span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article.up_count }}) </div>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <style> #avater_icon { font-size: 5px; margin-right: 10px; } .pub-info{ margin-top: 10px; } </style> </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">博客园</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">随笔 <span class="sr-only">(current)</span></a></li> <li><a href="#">新闻</a></li> <li><a href="#">博文</a></li> </ul> <ul class="nav navbar-nav navbar-right"> {# 下面这句等同于{% if request.user.username %}, 就是判断用户是否已经登录 #} {% if request.user.is_authenticated %} <li><a href="#"><span id="avater_icon" class="glyphicon glyphicon-user"></span>{{ request.user.username }}</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">修改密码</a></li> <li><a href="#">修改头像</a></li> <li><a href="/logout/">注销</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </li> {% else %} {# 用户未登录显示 注册 登录 #} <li><a href="/login/">登录</a></li> <li><a href=/reg/>注册</a></li> {% endif %} </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <div class="container-fluid"> <div class="row"> {# 左中右布局 #} <div class="col-md-3"> {# bootstrap,组件中的面板 #} <div class="panel panel-primary"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-success"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-info"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> </div> <div class="col-md-6"> <div class="article-list"> {% for article in article_list %} <div class="article-item"> <h5><a href="">{{ article.title }}</a></h5> <div class="article-desc"> <span class="media-left"> <a href=""><img width="56" height="56" srcset="/media/{{ article.user.avatar }}"></a> </span> <span class="media-right"> {{ article.desc }} </span> </div> </div> {# small表示让字小一些 #} <div class="small pub-info"> <span><a href="">{{ article.user.username }}</a></span> <span>发布于 {{ article.create_time|date:"Y-m-d H:i" }}</span> <span class="glyphicon glyphicon-comment"></span>评论数({{ article.comment_count }}) <span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article.up_count }}) </div> <hr> {% endfor %} </div> </div> <div class="col-md-3"> <div class="panel panel-warning"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-danger"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> <div class="panel panel-default"> <div class="panel-heading">Panel heading without title</div> <div class="panel-body"> Panel content </div> </div> </div> </div> </div> </body> </html>
三五. 博客系统之个人站点页面的文章查询
35.1 个人站点页面设计(ORM跨表与分组查询)
35.2 通过URL输入用户名称访问个人博客站点, 关键代码.
# urls.py # 个人站点页面 re_path(r'^(?P<username>\w+)$', views.home_site),
# views.py def home_site(request, username): """ 个人站点视图函数 :param request: :param username: URL中的路径,即博客用户的名称 :return: """ # UserInfo.objects.filter(username=username).exists() # 判断是否有该用户, 和下面的功能基本相同 user = UserInfo.objects.filter(username=username).first() # 判断用户是否存在 if not user: return render(request, 'not_fount.html') # 查询当前站点对象 blog = user.blog # 获取该用户或站点对象对应的所有文章,两种查询方法. ORM跨表与分组查询 # 基于对象查询,反向查询,由用户查出文章 # article_list = user.article_set.all() # 基于双下划线查询__ article_list = models.Article.objects.filter(user=user) return render(request, 'home_site.html')
三六. 博客系统值个人站点页面的标签与分类查询
36.1 查询每一个标签的文章总数, 每一个分类的文章总数等, 就是要查询下图所示的内容.
36.2 查询代码, ORM, 正向查询,反向查询, 基于双下划线查询方法等等
def home_site(request, username): user = UserInfo.objects.filter(username=username).first() if not user: return render(request, 'not_fount.html') # 查询username用户的blog对象 blog = user.blog # print(user.email) # print(blog) # (反向查询)查询username用户的所有文章 article_list = models.Article.objects.filter(user=user) article_list2 = user.article_set.all() # print(article_list) # print(article_list2) # 查询当前站点的每一个分类名称以及对应的文章数,分两步: # 1.查询每一个分类名称以及对应的文章数 ret = models.Category.objects.values('pk').annotate(c=Count('article__title')).values('title', "c") # 2.查询当前站点的每一个分类名称以及对应的文章数 ret = models.Category.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values('title', "c") # 查询当前站点的每一个标签名称以及对应的文章数,分两步: # 1.查询每一个标签名称以及对应的文章数 ret = models.Tag.objects.values('pk').annotate(c=Count('article__title')).values('title', 'c') print(ret) # 2.查询当前站点的每一个标签名称以及对应的文章数 # ret = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values('title', 'c') ret = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list('title', 'c') print(ret) """ <QuerySet [{'title': '明星Tag', 'c': 2}, {'title': '旧闻Tag', 'c': 1}, {'title': 'Vue3技术Tag', 'c': 1}]> <QuerySet [('明星Tag', 2), ('旧闻Tag', 1)]> """ return render(request, 'home_site.html')
三七. 博客系统值个人站点的日期查询1 待熟练后再学习这一块内容
37.1 博客中一般会按年月对文章进行归档(按年月计算文章数量)
问题Articles表中存储的create_time创建文章的日期是年月日时分秒毫秒格式,不是年月的格式,怎么办?
解决问题办法: 在文章表中创建一个字段,用户存储年月,就可以了.
37.2 如何在文章中创建年月字段?
三八. 博客系统之个人站点页面的日期查询2
三九. 博客系统之个人站点页面的日期查询3 以后再学
四零. 博客系统之个人站点页面的日期查询4 django模块的解决模块
# 日期归档(django的解决方案) from django.db.models.functions import TruncMonth # 指截断日期到月 ret = models.Article.objects.filter(user=user)\ .annotate(month=TruncMonth('create_time'))\ .values('month')\ .annotate(c=Count('nid'))\ .values('month', 'c') print(ret) # <QuerySet [{'month': datetime.datetime(2021, 2, 1, 0, 0, tzinfo=<UTC>), 'c': 2}]>
四一. 博客系统之个人站点的渲染布局1 头部
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> *{ margin: 0; padding: 0; } .header{ width: 100%; height: 60px; background-color: #369; } .header .title{ font-size: 18px; font-weight: 100; line-height: 60px; color: white; margin-left: 10px; margin-top: -10px; } .backend{ float: right; color: white; text-decoration: none; font-size: 14px; margin-right: 10px; margin-top: 10px; } </style> </head> <body> <div class="header"> <div class="content"> <p class="title"> <span>{{ blog.title }}</span> <a href="" class="backend">管理</a> </p> </div> </div> </body> </html>
四二. 博客系统之个人站点页面的渲染布局2 右边 文章列表
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> * { margin: 0; padding: 0; } .header { width: 100%; height: 60px; background-color: #369; } .header .title { font-size: 18px; font-weight: 100; line-height: 60px; color: white; margin-left: 10px; margin-top: -10px; } .backend { float: right; color: white; text-decoration: none; font-size: 14px; margin-right: 10px; margin-top: 10px; } .pub-info { color: slategray; margin-top: 10px; } </style> </head> <body> <div class="header"> <div class="content"> <p class="title"> <span>{{ blog.title }}</span> <a href="" class="backend">管理</a> </p> </div> </div> <div class="container"> <div class="row"> <div class="col-md-3"></div> <div class="col-md-9"> <div class="article-list"> {% for article in article_list %} <div class="article-item clearfix"> <h5><a href="">{{ article.title }}</a></h5> <div class="article-desc"> {{ article.desc }} </div> <div class="small pub-info pull-right"> <span>发布于 {{ article.create_time|date:"Y-m-d H:i" }}</span> <span class="glyphicon glyphicon-comment"></span>评论数({{ article.comment_count }}) <span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article.up_count }}) </div> </div> <hr> {% endfor %} </div> </div> </div> </div> </body> </html>
四三. 博客系统之个人站点页面的渲染布局3 左侧 标签,分类,归档
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> * { margin: 0; padding: 0; } .header { width: 100%; height: 60px; background-color: #369; } .header .title { font-size: 18px; font-weight: 100; line-height: 60px; color: white; margin-left: 10px; margin-top: -10px; } .backend { float: right; color: white; text-decoration: none; font-size: 14px; margin-right: 10px; margin-top: 10px; } .pub-info { color: slategray; margin-top: 10px; } </style> </head> <body> <div class="header"> <div class="content"> <p class="title"> <span>{{ blog.title }}</span> <a href="" class="backend">管理</a> </p> </div> </div> <div class="container"> <div class="row"> <div class="col-md-3"> <div class="panel panel-warning"> <div class="panel-heading">我的标签</div> <div class="panel-body"> {% for tag in tag_list %} <p>{{ tag.0 }}({{ tag.1 }})</p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading">随笔分类</div> <div class="panel-body"> {% for category in category_list %} <p>{{ category.0 }}({{ category.1 }})</p> {% endfor %} </div> </div> <div class="panel panel-success"> <div class="panel-heading">日期归档</div> <div class="panel-body"> {% for date in date_list %} <p>{{ date.0|date:"Y-m" }}({{ date.1 }})</p> {% endfor %} </div> </div> </div> <div class="col-md-9"> <div class="article-list"> {% for article in article_list %} <div class="article-item clearfix"> <h5><a href="">{{ article.title }}</a></h5> <div class="article-desc"> {{ article.desc }} </div> <div class="small pub-info pull-right"> <span>发布于 {{ article.create_time|date:"Y-m-d H:i" }}</span> <span class="glyphicon glyphicon-comment"></span>评论数({{ article.comment_count }}) <span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article.up_count }}) </div> </div> <hr> {% endfor %} </div> </div> </div> </div> </body> </html>
from django.shortcuts import render, HttpResponse, redirect from django.contrib import auth from django.http import JsonResponse from django.db.models import Count # 导入聚合函数 from blog import models from blog.models import UserInfo from blog.myforms import RegForm # 导入自定义验证 def reg(request): if request.is_ajax(): # 和request.method == "POST"效果一样 # print(request.POST) form = RegForm(request.POST) # 把传来的数据进行校验 response = {'user': None, 'msg': None} if form.is_valid(): response['user'] = form.cleaned_data.get('user') # 生成一条用户记录 user = form.cleaned_data.get('user') pwd = form.cleaned_data.get('pwd') email = form.cleaned_data.get('email') avatar_obj = request.FILES.get('avatar') # 注意凡是客户端传过来的文件,都从FILES中取 extra = {} if avatar_obj: # 当用户上传了头像时 extra['avatar'] = avatar_obj UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra) else: # print(form.cleaned_data) # 所有校验成功的信息 # print(form.errors) # 所有校验失败的信息 response['msg'] = form.errors return JsonResponse(response) form = RegForm() return render(request, 'reg.html', {'form': form}) def login(request): if request.method == 'POST': response = {'user': None, 'msg': None} user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') valid_code_str = request.session.get('valid_code') if valid_code.upper() == valid_code_str.upper(): user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) response['user'] = user.username else: response['msg'] = '用户名或密码错误' else: response['msg'] = '验证码错误' return JsonResponse(response) return render(request, 'login.html') def logout(request): auth.logout(request) # 等同于执行了,request.session.flush() return redirect('/login/') def index(request): article_list = models.Article.objects.all() return render(request, 'index.html', {'article_list': article_list}) def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img data = get_validCode_img(request) # 将图片渲染到模板页面 return HttpResponse(data) def home_site(request, username): """ 个人站点视图函数 :param request: :param username: URL中的路径,即博客用户的名称 :return: """ # UserInfo.objects.filter(username=username).exists() # 判断是否有该用户, 和下面的功能基本相同 user = UserInfo.objects.filter(username=username).first() # 判断用户是否存在 if not user: return render(request, 'not_fount.html') # 查询当前站点对象 blog = user.blog # 获取该用户或站点对象对应的所有文章,两种查询方法 # 基于对象查询,反向查询,由用户查出文章 # article_list = user.article_set.all() # 基于双下划线查询__ article_list = models.Article.objects.filter(user=user) # 查询当前站点的每一个分类名称以及对应的文章数 category_list = models.Category.objects.all().values('pk').filter(blog=blog).annotate(c=Count('article__title')).values_list('title', 'c') # print(ret) # 查询当前站点的每一个标签名称以及对应的文章数 tag_list = models.Tag.objects.all().values('pk').filter(blog=blog).annotate(c=Count('article__title')).values_list('title', 'c') # print(ret) # 日期归档(django的解决方案) from django.db.models.functions import TruncMonth # 指截断日期到月 date_list = models.Article.objects.filter(user=user)\ .annotate(month=TruncMonth('create_time'))\ .values('month')\ .annotate(c=Count('nid'))\ .values_list('month', 'c') # print(date_list) # <QuerySet [(datetime.datetime(2021, 2, 1, 0, 0, tzinfo=<UTC>), 2)]> return render(request, 'home_site.html', {'blog': blog, 'article_list': article_list, 'category_list': category_list, 'tag_list': tag_list, 'date_list': date_list})
四四. 博客系统之个人站点页面的跳转过滤功能的实现1 URL
re_path(r'^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)/$', views.home_site), # archive用于日期归档的路径
四五. 博客系统之个人站点页面的跳转过滤功能的实现2 视图
45.1 最终效果, 通过URL跳转到指定的分类中
45.2 关键代码:本质是从URL中提取出路径,然后对文章进行筛选, 注意kwargs.
def home_site(request, username, **kwargs): user = UserInfo.objects.filter(username=username).first() if not user: return render(request, 'not_fount.html') # 查询username用户的blog对象 blog = user.blog # print(user.email) # print(blog) if kwargs: # 个人站点跳转 condition = kwargs.get('condition') # 使用**kwargs目的是可以接受任意多个参数 param = kwargs.get('param') if condition == 'tag': article_list = models.Article.objects.filter(user=user).filter(tags__title=param) # print(article_list) elif condition == 'category': article_list = models.Article.objects.filter(user=user).filter(category__title=param) else: # 日期归档 year, month = param.split('-') article_list = models.Article.objects.filter(user=user).filter(create_time__year=year, create_time__month=month) else: # 个人站点 # (反向查询)查询username用户的所有文章 article_list = models.Article.objects.filter(user=user) # article_list2 = user.article_set.all() # print(article_list) # print(article_list2) # 查询当前站点的每一个分类名称以及对应的文章数,分两步: # 1.查询每一个分类名称以及对应的文章数 # ret = models.Category.objects.values('pk').annotate(c=Count('article__title')).values('title', "c") # 2.查询当前站点的每一个分类名称以及对应的文章数 category_list = models.Category.objects.filter(blog=blog).values('pk').annotate( c=Count('article__title')).values_list('title', "c") # 查询当前站点的每一个标签名称以及对应的文章数,分两步: # 1.查询每一个标签名称以及对应的文章数 # ret = models.Tag.objects.values('pk').annotate(c=Count('article__title')).values('title', 'c') # 2.查询当前站点的每一个标签名称以及对应的文章数 # ret = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values('title', 'c') tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list( 'title', 'c') # 按月份将文章归档 from django.db.models.functions import TruncMonth date_list = models.Article.objects.filter(user=user) \ .annotate(month=TruncMonth('create_time')) \ .values('month') \ .annotate(c=Count('nid')) \ .values_list('month', 'c') return render(request, 'home_site.html', {'article_list': article_list, 'category_list': category_list, 'tag_list': tag_list, 'date_list': date_list, 'blog': blog})
"""cnblog URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.1/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path, re_path from django.views.static import serve from blog import views from cnblog import settings urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('logout/', views.logout), path('get_validCode_img/', views.get_validCode_img), path('reg/', views.reg), re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), # 个人站点跳转 re_path(r'^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)/$', views.home_site), # 个人站点 re_path(r'^(?P<username>\w+)/$', views.home_site), ]
四六. 博客系统值个人站点页面的跳转过滤功能的实现3
优化代码,和增加<a>标签,点击实现跳转
"""cnblog URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.1/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path, re_path from django.views.static import serve # serve器 from blog import views from cnblog import settings urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('logout/', views.logout), path('get_validCode_img/', views.get_validCode_img), path('reg/', views.reg), # media配置, 外网直接访问用户上传文件的media文件夹的路由 re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), # 固定写法 # 个人站点页面 re_path(r'^(?P<username>\w+)/$', views.home_site), # 个人站点页面跳转 re_path(r'^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)/$', views.home_site), ]
from django.shortcuts import render, HttpResponse, redirect from django.contrib import auth from django.http import JsonResponse from django.db.models import Count # 导入聚合函数 from blog import models from blog.models import UserInfo from blog.myforms import RegForm # 导入自定义验证 def reg(request): if request.is_ajax(): # 和request.method == "POST"效果一样 # print(request.POST) form = RegForm(request.POST) # 把传来的数据进行校验 response = {'user': None, 'msg': None} if form.is_valid(): response['user'] = form.cleaned_data.get('user') # 生成一条用户记录 user = form.cleaned_data.get('user') pwd = form.cleaned_data.get('pwd') email = form.cleaned_data.get('email') avatar_obj = request.FILES.get('avatar') # 注意凡是客户端传过来的文件,都从FILES中取 extra = {} if avatar_obj: # 当用户上传了头像时 extra['avatar'] = avatar_obj UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra) else: # print(form.cleaned_data) # 所有校验成功的信息 # print(form.errors) # 所有校验失败的信息 response['msg'] = form.errors return JsonResponse(response) form = RegForm() return render(request, 'reg.html', {'form': form}) def login(request): if request.method == 'POST': response = {'user': None, 'msg': None} user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') valid_code_str = request.session.get('valid_code') if valid_code.upper() == valid_code_str.upper(): user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) response['user'] = user.username else: response['msg'] = '用户名或密码错误' else: response['msg'] = '验证码错误' return JsonResponse(response) return render(request, 'login.html') def logout(request): auth.logout(request) # 等同于执行了,request.session.flush() return redirect('/login/') def index(request): article_list = models.Article.objects.all() return render(request, 'index.html', {'article_list': article_list}) def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img data = get_validCode_img(request) # 将图片渲染到模板页面 return HttpResponse(data) def home_site(request, username, **kwargs): # kwargs是用于区分是否是个人页面跳转 """ 个人站点视图函数 :param request: :param username: URL中的路径,即博客用户的名称 :return: """ # UserInfo.objects.filter(username=username).exists() # 判断是否有该用户, 和下面的功能基本相同 user = UserInfo.objects.filter(username=username).first() # 判断用户是否存在 if not user: return render(request, 'not_fount.html') # 查询当前站点对象 blog = user.blog # 获取该用户或站点对象对应的所有文章,两种查询方法 # 基于对象查询,反向查询,由用户查出文章 # article_list = user.article_set.all() # 基于双下划线查询__ article_list = models.Article.objects.filter(user=user) if kwargs: condition = kwargs.get('condition') param = kwargs.get('param') if condition == 'tag': article_list = article_list.filter(tags__title=param) elif condition == 'category': article_list = article_list.filter(category__title=param) else: year, month = param.split('-') article_list = article_list.filter(create_time__year=year, create_time__month=month) # 查询当前站点的每一个分类名称以及对应的文章数 category_list = models.Category.objects.all().values('pk').filter(blog=blog).annotate(c=Count('article__title')).values_list('title', 'c') # print(ret) # 查询当前站点的每一个标签名称以及对应的文章数 tag_list = models.Tag.objects.all().values('pk').filter(blog=blog).annotate(c=Count('article__title')).values_list('title', 'c') # print(ret) # 日期归档(django的解决方案) from django.db.models.functions import TruncMonth # 指截断日期到月 date_list = models.Article.objects.filter(user=user)\ .annotate(month=TruncMonth('create_time'))\ .values('month')\ .annotate(c=Count('nid'))\ .values_list('month', 'c') # print(date_list) # <QuerySet [(datetime.datetime(2021, 2, 1, 0, 0, tzinfo=<UTC>), 2)]> return render(request, 'home_site.html', {'username': username, 'blog': blog, 'article_list': article_list, 'category_list': category_list, 'tag_list': tag_list, 'date_list': date_list})
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> * { margin: 0; padding: 0; } .header { width: 100%; height: 60px; background-color: #369; } .header .title { font-size: 18px; font-weight: 100; line-height: 60px; color: white; margin-left: 10px; margin-top: -10px; } .backend { float: right; color: white; text-decoration: none; font-size: 14px; margin-right: 10px; margin-top: 10px; } .pub-info { color: slategray; margin-top: 10px; } </style> </head> <body> <div class="header"> <div class="content"> <p class="title"> <span>{{ blog.title }}</span> <a href="" class="backend">管理</a> </p> </div> </div> <div class="container"> <div class="row"> <div class="col-md-3"> <div class="panel panel-warning"> <div class="panel-heading">我的标签</div> <div class="panel-body"> {% for tag in tag_list %} <p><a href="/{{ username }}/tag/{{ tag.0 }}/">{{ tag.0 }}({{ tag.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading">随笔分类</div> <div class="panel-body"> {% for category in category_list %} <p><a href="/{{ username }}/category/{{ category.0 }}/">{{ category.0 }}({{ category.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-success"> <div class="panel-heading">日期归档</div> <div class="panel-body"> {% for date in date_list %} <p><a href="/{{ username }}/archive/{{ date.0|date:"Y-m" }}/">{{ date.0|date:"Y-m" }}({{ date.1 }})</a></p> {% endfor %} </div> </div> </div> <div class="col-md-9"> <div class="article-list"> {% for article in article_list %} <div class="article-item clearfix"> <h5><a href="">{{ article.title }}</a></h5> <div class="article-desc"> {{ article.desc }} </div> <div class="small pub-info pull-right"> <span>发布于 {{ article.create_time|date:"Y-m-d H:i" }}</span> <span class="glyphicon glyphicon-comment"></span>评论数({{ article.comment_count }}) <span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article.up_count }}) </div> </div> <hr> {% endfor %} </div> </div> </div> </div> </body> </html>
四七. 博客系统值文章详情页的设计 详情页的url,
通过类型/papa/article/1/路径访问详情页
# 文章详情页 re_path(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/$', views.article_detail),
四八. 博客系统文章详情页的数据构建
48.1 文章详情页和个人站点首页的头部、左侧是相同的,这里可以使用模板继承进行
先建立一个base.thml基础模板,存放顶部和左侧内容.留出右侧,做个空盒子.
空盒子关键代码: 其它页面继承该页面后,在盒子处进行扩展
<div class="col-md-9"> {% block content %} {# 预留盒子, 进行扩展 #} {% endblock %} </div>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> *{ margin: 0; padding: 0; } .header{ width: 100%; height: 60px; background-color: #369; } .header .title{ margin-left: 10px; font-weight: 100; line-height: 60px; color: white; font-size: 22px; } .backend{ float: right; margin-right: 10px; font-size: 14px; color: white; margin-top: 30px; text-decoration: none; } .pub-info{ margin-top: 10px; color: darkslategray; } </style> </head> <body> <div class="header"> <div class="content clearfix"> <span class="title">{{ blog.title }}</span> <a href="" class="backend">管理</a> </div> </div> <div class="container"> <div class="row"> <div class="col-md-3"> <div class="panel panel-warning"> <div class="panel-heading">我的标签</div> <div class="panel-body"> {% for tag in tag_list %} <p><a href="/{{ username }}/tag/{{ tag.0 }}/">{{ tag.0 }}({{ tag.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading">文章分类</div> <div class="panel-body"> {% for category in category_list %} <p><a href="/{{ username }}/category/{{ category.0 }}/">{{ category.0 }}({{ category.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-info"> <div class="panel-heading">日期归档</div> <div class="panel-body"> {% for date in date_list %} <p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}/">{{ date.0|date:'Y-m' }}({{ date.1 }})</a></p> {% endfor %} </div> </div> </div> <div class="col-md-9"> {% block content %} {# 预留盒子, 进行扩展 #} {% endblock %} </div> </div> </div> </body> </html>
48.2 个人站点首页home_site.html,和文章详情页面article_detail.html,先继承base.html,然后再扩写.就可以拥有相同的顶部和左侧.
继承{% extends 'base.html' %}, 扩写看下面.
{% extends 'base.html' %}
{% block content %}
{# 在这里扩写 #}
{% endblock %}
{% extends 'base.html' %} {% block content %} <div class="article-list"> {% for article in article_list %} <div class="article-item clearfix"> <h5><a href="">{{ article.title }}</a></h5> <div class="article-desc"> {{ article.desc }} </div> <div class="small pub-info pull-right"> <span>发布于 {{ article.create_time|date:'Y-m-d H:i' }}</span> <span class="glyphicon glyphicon-comment">评论数({{ article.comment_count }})</span> <span class="glyphicon glyphicon-thumbs-up">点赞数({{ article.up_count }})</span> </div> </div> <hr> {% endfor %} </div> {% endblock %}
{# 继承 #}
{% extends 'base.html' %}
{% block content %}
{# 扩写盒子 #}
{% endblock %}
48.3 视图函数中建立文章详请页面的函数article_detail, 注意这里为了减少重复代码,将页面顶部和左侧涉及的数据封装成了函数.返回值是{'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list}
def get_query_data(username): """优化代码,代码复用""" user = UserInfo.objects.filter(username=username).first() blog = user.blog # article_list = models.Article.objects.filter(user=user) category_list = models.Category.objects.filter(blog=blog).values('pk').annotate( c=Count('article__title')).values_list('title', "c") tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list( 'title', 'c') date_list = models.Article.objects.filter(user=user) \ .annotate(month=TruncMonth('create_time')) \ .values('month') \ .annotate(c=Count('nid')) \ .values_list('month', 'c') return {'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list}
def article_detail(request, username, article_id): """ 文章详情页视图 http://127.0.0.1:8000/papa/article/2/ """ context = get_query_data(username) return render(request, 'article_detail.html', context) # 注意这里的返回值参数context其是上面的字典, 在模板中可直接使用字典中的键.
from django.shortcuts import render, HttpResponse, redirect from django.http import JsonResponse # 传递json数据类型 from django.contrib import auth # 导入用户组件,用于auth_user表的操作 from django.db.models import Avg, Max, Min, Count from django.db.models.functions import TruncMonth from blog import models from blog.models import UserInfo from blog.myforms import RegForm # 导入自定义forms验证规则 def reg(request): """用户注册视图""" if request.is_ajax(): form = RegForm(request.POST) respone = {'user': None, 'msg': None} if form.is_valid(): respone['user'] = form.cleaned_data.get('user') user = form.cleaned_data.get('user') pwd = form.cleaned_data.get('pwd') email = form.cleaned_data.get('email') avatar_obj = request.FILES.get('avatar') extra = {} if avatar_obj: extra['avatar'] = avatar_obj user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra) else: respone['msg'] = form.errors return JsonResponse(respone) form = RegForm() # 带入规则 return render(request, 'reg.html', {"form": form}) def login(request): if request.method == "POST": response = {'user': None, 'msg': None} # 构造登录提示信息 user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') # 获取用户输入的验证码 valid_code_str = request.session.get('valid_code') # 从session中提取自动生成的验证码字符串 if valid_code_str.upper() == valid_code.upper(): # 全部转为大写字母 user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量 response['user'] = user.username # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. else: response['msg'] = '用户名或密码错误!' else: response['msg'] = '验证码错误!' return JsonResponse(response) # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. return render(request, 'login.html') def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img # 导入自定义的生成验证码功能函数 data = get_validCode_img(request) return HttpResponse(data) def index(request): article_list = models.Article.objects.all() return render(request, 'index.html', {'article_list': article_list}) def logout(request): auth.logout(request) return redirect('/login/') def get_query_data(username): """优化代码,代码复用""" user = UserInfo.objects.filter(username=username).first() blog = user.blog # article_list = models.Article.objects.filter(user=user) category_list = models.Category.objects.filter(blog=blog).values('pk').annotate( c=Count('article__title')).values_list('title', "c") tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list( 'title', 'c') date_list = models.Article.objects.filter(user=user) \ .annotate(month=TruncMonth('create_time')) \ .values('month') \ .annotate(c=Count('nid')) \ .values_list('month', 'c') return {'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list} def home_site(request, username, **kwargs): # 此处也可以去除重复的代码. user = UserInfo.objects.filter(username=username).first() if not user: return render(request, 'not_fount.html') # 查询username用户的blog对象 blog = user.blog article_list = models.Article.objects.filter(user=user) if kwargs: # 个人站点跳转 condition = kwargs.get('condition') # 使用**kwargs目的是可以接受任意多个参数 param = kwargs.get('param') if condition == 'tag': article_list = article_list.filter(tags__title=param) # print(article_list) elif condition == 'category': article_list = article_list.filter(category__title=param) else: # 日期归档 year, month = param.split('-') article_list = article_list.filter(create_time__year=year, create_time__month=month) category_list = models.Category.objects.filter(blog=blog).values('pk').annotate( c=Count('article__title')).values_list('title', "c") tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list( 'title', 'c') # 按月份将文章归档 from django.db.models.functions import TruncMonth date_list = models.Article.objects.filter(user=user) \ .annotate(month=TruncMonth('create_time')) \ .values('month') \ .annotate(c=Count('nid')) \ .values_list('month', 'c') return render(request, 'home_site.html', {'article_list': article_list, 'category_list': category_list, 'tag_list': tag_list, 'date_list': date_list, 'blog': blog, 'username': username}) def article_detail(request, username, article_id): """ 文章详情页视图 http://127.0.0.1:8000/papa/article/2/ """ context = get_query_data(username) return render(request, 'article_detail.html', context)
四九. 博客系统值文章详情页的inclution_tag 类似模板自定义标签过滤器
数据传递的另一种方法
49.1 一个自定义的inclution_tag,模板自定义标签过滤器.实现乘法3*7=21. 直接在模板中得出结果.
在blog文件夹下新建templatetags文件夹(这个名字不能改成其他名字),新建my_tags.py文件
# my_tags.py from django import template register = template.Library() # 自定义乘法标签过滤器 @register.simple_tag # 可在模板中直接调用这个函数 def multi_tag(x, y): # 实现乘法 return x * y
# article_detail.html
{% extends 'base.html' %} {% block content %} {# 引入自定义的标签 3*7 #} {% load my_tags %} {% multi_tag 3 7 %} {% endblock %}
# views.py def article_detail(request, username, article_id): """ 文章详情页视图 http://127.0.0.1:8000/papa/article/2/ """ return render(request, 'article_detail.html')
49.2 另一中数据传递给模板的的方法, 类似于上面的模板自定义函数标签, 的inclutigon_tag方法
将数据与样式结合成一体, 直接在模板中调用标签就可以了.
首先, 将views.py中的get_classification_style()函数移动到my_tags.py中,
@register.inclusion_tag('calssification.html') def get_classification_style(username): """ 获取个人站点左侧数据, (文章详情页左侧数据) 自定义顶部与左侧的获取数据的标签 get_classification_style函数获取到数据后,直接传递给模板classification.html :param username: 当前地址栏中的用户名 :return: 左侧所需的所有数据 """ user = models.UserInfo.objects.filter(username=username).first() blog = user.blog # article_list = models.Article.objects.filter(user=user) category_list = models.Category.objects.filter(blog=blog).values('pk').annotate( c=Count('article__title')).values_list('title', "c") tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list( 'title', 'c') date_list = models.Article.objects.filter(user=user) \ .annotate(month=TruncMonth('create_time')) \ .values('month') \ .annotate(c=Count('nid')) \ .values_list('month', 'c') return {'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list}
from django import template from blog import models from django.db.models import Count from django.db.models.functions import TruncMonth from django.shortcuts import render register = template.Library() # 自定义乘法标签过滤器 @register.simple_tag # 可在模板中直接调用这个函数 def multi_tag(x, y): # 实现乘法 return x * y @register.inclusion_tag('calssification.html') def get_classification_style(username): """ 获取个人站点左侧数据, (文章详情页左侧数据) 自定义顶部与左侧的获取数据的标签 get_classification_style函数获取到数据后,直接传递给模板classification.html :param username: 当前地址栏中的用户名 :return: 左侧所需的所有数据 """ user = models.UserInfo.objects.filter(username=username).first() blog = user.blog # article_list = models.Article.objects.filter(user=user) category_list = models.Category.objects.filter(blog=blog).values('pk').annotate( c=Count('article__title')).values_list('title', "c") tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list( 'title', 'c') date_list = models.Article.objects.filter(user=user) \ .annotate(month=TruncMonth('create_time')) \ .values('month') \ .annotate(c=Count('nid')) \ .values_list('month', 'c') return {'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list}
然后, 建立calssification.html模板文件,将base.thml中的网站左侧部分移动到新建的模板文件中.下面的代码是base.thml中左侧移动后的:
<div class="col-md-3"> {# 个人站点左侧数据和模板 username是参数 #} {% load my_tags %} {% get_classification_style username %} </div>
{# 网站左侧模板和数据 #} <div> <div class="panel panel-warning"> <div class="panel-heading">我的标签</div> <div class="panel-body"> {% for tag in tag_list %} <p><a href="/{{ username }}/tag/{{ tag.0 }}/">{{ tag.0 }}({{ tag.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-danger"> <div class="panel-heading">文章分类</div> <div class="panel-body"> {% for category in category_list %} <p><a href="/{{ username }}/category/{{ category.0 }}/">{{ category.0 }}({{ category.1 }})</a></p> {% endfor %} </div> </div> <div class="panel panel-info"> <div class="panel-heading">日期归档</div> <div class="panel-body"> {% for date in date_list %} <p><a href="/{{ username }}/archive/{{ date.0|date:'Y-m' }}/">{{ date.0|date:'Y-m' }}({{ date.1 }})</a></p> {% endfor %} </div> </div> </div>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> *{ margin: 0; padding: 0; } .header{ width: 100%; height: 60px; background-color: #369; } .header .title{ margin-left: 10px; font-weight: 100; line-height: 60px; color: white; font-size: 22px; } .backend{ float: right; margin-right: 10px; font-size: 14px; color: white; margin-top: 30px; text-decoration: none; } .pub-info{ margin-top: 10px; color: darkslategray; } </style> </head> <body> <div class="header"> <div class="content clearfix"> <span class="title">{{ blog.title }}</span> <a href="" class="backend">管理</a> </div> </div> <div class="container"> <div class="row"> <div class="col-md-3"> {# 个人站点左侧数据和模板 username是参数 #} {% load my_tags %} {% get_classification_style username %} </div> <div class="col-md-9"> {% block content %} {# 预留盒子, 进行扩展 #} {% endblock %} </div> </div> </div> </body> </html>
最后, 修改views.py中的个人站点视图
def home_site(request, username, **kwargs): user = UserInfo.objects.filter(username=username).first() if not user: return render(request, 'not_fount.html') # 查询username用户的blog对象 blog = user.blog article_list = models.Article.objects.filter(user=user) if kwargs: # 个人站点跳转 condition = kwargs.get('condition') # 使用**kwargs目的是可以接受任意多个参数 param = kwargs.get('param') if condition == 'tag': article_list = article_list.filter(tags__title=param) elif condition == 'category': article_list = article_list.filter(category__title=param) else: # 日期归档 year, month = param.split('-') article_list = article_list.filter(create_time__year=year, create_time__month=month) return render(request, 'home_site.html', {'username': username, 'blog': blog,'article_list': article_list}) def article_detail(request, username, article_id): """ 文章详情页视图 http://127.0.0.1:8000/papa/article/2/ """ # context = get_query_data(username) # return render(request, 'article_detail.html', context) user = UserInfo.objects.filter(username=username).first() if not user: return render(request, 'not_fount.html') # 查询username用户的blog对象 blog = user.blog return render(request, 'article_detail.html', locals()) # locals()不能丢
from django.shortcuts import render, HttpResponse, redirect from django.http import JsonResponse # 传递json数据类型 from django.contrib import auth # 导入用户组件,用于auth_user表的操作 from django.db.models import Avg, Max, Min, Count from django.db.models.functions import TruncMonth from blog import models from blog.models import UserInfo from blog.myforms import RegForm # 导入自定义forms验证规则 def reg(request): """用户注册视图""" if request.is_ajax(): form = RegForm(request.POST) respone = {'user': None, 'msg': None} if form.is_valid(): respone['user'] = form.cleaned_data.get('user') user = form.cleaned_data.get('user') pwd = form.cleaned_data.get('pwd') email = form.cleaned_data.get('email') avatar_obj = request.FILES.get('avatar') extra = {} if avatar_obj: extra['avatar'] = avatar_obj user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra) else: respone['msg'] = form.errors return JsonResponse(respone) form = RegForm() # 带入规则 return render(request, 'reg.html', {"form": form}) def login(request): if request.method == "POST": response = {'user': None, 'msg': None} # 构造登录提示信息 user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') # 获取用户输入的验证码 valid_code_str = request.session.get('valid_code') # 从session中提取自动生成的验证码字符串 if valid_code_str.upper() == valid_code.upper(): # 全部转为大写字母 user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量 response['user'] = user.username # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. else: response['msg'] = '用户名或密码错误!' else: response['msg'] = '验证码错误!' return JsonResponse(response) # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. return render(request, 'login.html') def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img # 导入自定义的生成验证码功能函数 data = get_validCode_img(request) return HttpResponse(data) def index(request): article_list = models.Article.objects.all() return render(request, 'index.html', {'article_list': article_list}) def logout(request): auth.logout(request) return redirect('/login/') # def get_query_data(username): # """优化代码,代码复用""" # # user = UserInfo.objects.filter(username=username).first() # blog = user.blog # # article_list = models.Article.objects.filter(user=user) # category_list = models.Category.objects.filter(blog=blog).values('pk').annotate( # c=Count('article__title')).values_list('title', "c") # tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list( # 'title', 'c') # # date_list = models.Article.objects.filter(user=user) \ # .annotate(month=TruncMonth('create_time')) \ # .values('month') \ # .annotate(c=Count('nid')) \ # .values_list('month', 'c') # return {'username': username, 'category_list': category_list, "tag_list": tag_list, 'date_list': date_list} def home_site(request, username, **kwargs): user = UserInfo.objects.filter(username=username).first() if not user: return render(request, 'not_fount.html') # 查询username用户的blog对象 blog = user.blog article_list = models.Article.objects.filter(user=user) if kwargs: # 个人站点跳转 condition = kwargs.get('condition') # 使用**kwargs目的是可以接受任意多个参数 param = kwargs.get('param') if condition == 'tag': article_list = article_list.filter(tags__title=param) # print(article_list) elif condition == 'category': article_list = article_list.filter(category__title=param) else: # 日期归档 year, month = param.split('-') article_list = article_list.filter(create_time__year=year, create_time__month=month) # # category_list = models.Category.objects.filter(blog=blog).values('pk').annotate( # c=Count('article__title')).values_list('title', "c") # # tag_list = models.Tag.objects.filter(blog=blog).values('pk').annotate(c=Count('article__title')).values_list( # 'title', 'c') # # # 按月份将文章归档 # from django.db.models.functions import TruncMonth # date_list = models.Article.objects.filter(user=user) \ # .annotate(month=TruncMonth('create_time')) \ # .values('month') \ # .annotate(c=Count('nid')) \ # .values_list('month', 'c') # return render(request, 'home_site.html', # {'article_list': article_list, 'category_list': category_list, 'tag_list': tag_list, # 'date_list': date_list, 'blog': blog, 'username': username}) return render(request, 'home_site.html', {'username': username, 'blog': blog,'article_list': article_list}) def article_detail(request, username, article_id): """ 文章详情页视图 http://127.0.0.1:8000/papa/article/2/ """ # context = get_query_data(username) # return render(request, 'article_detail.html', context) user = UserInfo.objects.filter(username=username).first() if not user: return render(request, 'not_fount.html') # 查询username用户的blog对象 blog = user.blog return render(request, 'article_detail.html', locals()) # locals()不能丢
目录结构:
五零. 博客系统值文章详情页渲染的标签字符串转义1
50.1 渲染出文章详情页后,会发现内容部分是纯粹的无格式的杂乱字符串.无法显示图片等相关的html内容, 这就是django模板语法的转义.
五一. 博客系统值文章详情页渲染的标签字符串转义2 加safe不让其转义
# article_detail.html {% extends 'base.html' %} {% block content %} <h3 class="text-center">{{ article_obj.title }}</h3> <div class="cont"> {{ article_obj.content|safe }} </div> {% endblock %}
显示就正确了,渲染出了网页效果.
转义的目的是,安全,防止用户输入<script>等代码.防止xss攻击.会有安全隐患.
在用户提交文章内容时,就应当拒绝用户存储<script>等有安全隐患的代码.
五二. 博客系统之文章点赞样式的构建
52.1 将base.html中的样式与模板进行分离, 在静态文件夹下建立home_site.css和article_detail.css样式文件.点赞的html css代码可以直接从博客园中复制.
博客园有反爬虫机制, 需要把点赞和反对的静态图片下载到本地.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <link rel="stylesheet" href="/static/css/home_site.css"> <link rel="stylesheet" href="/static/css/article_detail.css"> </head> <body> <div class="header"> <div class="content clearfix"> <span class="title">{{ blog.title }}</span> <a href="" class="backend">管理</a> </div> </div> <div class="container"> <div class="row"> <div class="col-md-3"> {# 个人站点左侧数据和模板 username是参数 #} {% load my_tags %} {% get_classification_style username %} </div> <div class="col-md-9"> {% block content %} {# 预留盒子, 进行扩展 #} {% endblock %} </div> </div> </div> </body> </html>
*{
margin: 0;
padding: 0;
}
.header{
width: 100%;
height: 60px;
background-color: #369;
}
.header .title{
margin-left: 10px;
font-weight: 100;
line-height: 60px;
color: white;
font-size: 22px;
}
.backend{
float: right;
margin-right: 10px;
font-size: 14px;
color: white;
margin-top: 30px;
text-decoration: none;
}
.pub-info{
margin-top: 10px;
color: darkslategray;
}
.container{
margin-top: 10px;
}
.article_info .title{
margin-bottom: 20px;
}
#div_digg{
word-break: break-all;
line-height: 1.5;
color: #000;
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 125px;
text-align: center;
margin-top: 10px;
}
#div_digg .diggit{
word-break: break-all;
line-height: 1.5;
color: #000;
font-size: 12px;
float: left;
width: 46px;
height: 52px;
background: url('https://static.cnblogs.com/images/upup.gif') no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
#div_digg .buryit{
word-break: break-all;
line-height: 1.5;
color: #000;
font-size: 12px;
float: right;
margin-left: 20px;
width: 46px;
height: 52px;
background: url('https://static.cnblogs.com/images/upup.gif') no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
#div_digg .diggword{
word-break: break-all;
line-height: 1.5;
text-align: center;
margin-top: 5px;
margin-left: 0;
font-size: 12px;
color: red;
}
#div_digg .clear{
clear: both;
}
五三. 博客系统值文章点赞的事件绑定
{# 继承 #} {% extends 'base.html' %} {% block content %} <div class="article_info"> <h3 class="text-center">{{ article_obj.title }}</h3> <div class="cont"> {{ article_obj.content|safe }} </div> <div id="div_digg"> {# diggit action 推荐 #} <div class="diggit action"> <span class="diggnum" id="digg_count">1</span> </div> {# diggit action 反对 #} <div class="buryit action"> <span class="burynum" id="bury_count">0</span> </div> <div class="clear"></div> <div class="diggword" id="digg_tips" style="color: red;"></div> </div> <script> // 注意 class属性是.action的有两个, 那么到底是点了哪一个, $('.action').click(function () { var is_up = $(this).hasClass('diggit'); // 有diggit属性,就是True, 否则就是False alert(is_up); }) </script> </div> {% endblock %}
五四. 博客系统值文章点赞的保存
54.1 article_detail.html关键代码
<script> // 注意 class属性是.action的有两个, 那么到底是点了哪一个, $('.action').click(function () { var is_up = $(this).hasClass('diggit'); // 有diggit属性,就是True, 否则就是False $.ajax({ url: '/digg/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'is_up': is_up, }, success: function (data) { console.log(data) } }) }) </script>
{# 继承 #} {% extends 'base.html' %} {% block content %} <div class="article_info"> {% csrf_token %} <h3 class="text-center">{{ article_obj.title }}</h3> <div class="cont"> {{ article_obj.content|safe }} </div> <div id="div_digg"> {# diggit action 推荐 #} <div class="diggit action"> <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span> </div> {# diggit action 反对 #} <div class="buryit action"> <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span> </div> <div class="clear"></div> <div class="diggword" id="digg_tips" style="color: red;"></div> </div> <script> // 注意 class属性是.action的有两个, 那么到底是点了哪一个, $('.action').click(function () { var is_up = $(this).hasClass('diggit'); // 有diggit属性,就是True, 否则就是False $.ajax({ url: '/digg/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'is_up': is_up, }, success: function (data) { console.log(data) } }) }) </script> </div> {% endblock %}
54.2 url.py建立路由, 注意路由向上放.防止被下面的通配符通配.
# 点赞 path('digg/', views.digg),
"""cnblog URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.1/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path, re_path from django.views.static import serve # serve器 from blog import views from cnblog import settings urlpatterns = [ path('admin/', admin.site.urls), path('login/', views.login), path('index/', views.index), path('logout/', views.logout), path('get_validCode_img/', views.get_validCode_img), path('reg/', views.reg), path('digg/', views.digg), # media配置, 外网直接访问用户上传文件的media文件夹的路由 re_path(r'media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), # 固定写法 # 个人站点页面 re_path(r'^(?P<username>\w+)/$', views.home_site), # 个人站点页面跳转 re_path(r'^(?P<username>\w+)/(?P<condition>tag|category|archive)/(?P<param>.*)/$', views.home_site), # 个人站点文章详情页面 re_path(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/$', views.article_detail), ]
54.3 视图文件,建立点赞视图
def digg(request):
print(request.user)
import json
article_id = request.POST.get('article_id')
is_up = json.loads(request.POST.get('is_up')) # 传递过来的是"True"字符串, 所以这里需要反序列化为布尔值
user_id = request.user.pk # 直接获取user_id,没有必要通过客户端传送
ard = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up)
return HttpResponse('OK')
五五. 博客系统之纹章点赞数的数据同步 用到F查询,点赞总数自加1
def digg(request): print(request.user) import json article_id = request.POST.get('article_id') is_up = json.loads(request.POST.get('is_up')) # 传递过来的是"True"字符串, 所以这里需要反序列化为布尔值 user_id = request.user.pk # 直接获取user_id,没有必要通过客户端传送 ard = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up) from django.db.models import F if is_up: models.Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1) return HttpResponse('OK')
from django.shortcuts import render, HttpResponse, redirect from django.http import JsonResponse # 传递json数据类型 from django.contrib import auth # 导入用户组件,用于auth_user表的操作 from django.db.models import Avg, Max, Min, Count from django.db.models.functions import TruncMonth from blog import models from blog.models import UserInfo from blog.myforms import RegForm # 导入自定义forms验证规则 def reg(request): """用户注册视图""" if request.is_ajax(): form = RegForm(request.POST) respone = {'user': None, 'msg': None} if form.is_valid(): respone['user'] = form.cleaned_data.get('user') user = form.cleaned_data.get('user') pwd = form.cleaned_data.get('pwd') email = form.cleaned_data.get('email') avatar_obj = request.FILES.get('avatar') extra = {} if avatar_obj: extra['avatar'] = avatar_obj user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra) else: respone['msg'] = form.errors return JsonResponse(respone) form = RegForm() # 带入规则 return render(request, 'reg.html', {"form": form}) def login(request): if request.method == "POST": response = {'user': None, 'msg': None} # 构造登录提示信息 user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') # 获取用户输入的验证码 valid_code_str = request.session.get('valid_code') # 从session中提取自动生成的验证码字符串 if valid_code_str.upper() == valid_code.upper(): # 全部转为大写字母 user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量 response['user'] = user.username # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. else: response['msg'] = '用户名或密码错误!' else: response['msg'] = '验证码错误!' return JsonResponse(response) # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. return render(request, 'login.html') def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img # 导入自定义的生成验证码功能函数 data = get_validCode_img(request) return HttpResponse(data) def index(request): article_list = models.Article.objects.all() return render(request, 'index.html', {'article_list': article_list}) def logout(request): auth.logout(request) return redirect('/login/') def home_site(request, username, **kwargs): user = UserInfo.objects.filter(username=username).first() if not user: return render(request, 'not_fount.html') # 查询username用户的blog对象 blog = user.blog article_list = models.Article.objects.filter(user=user) if kwargs: # 个人站点跳转 condition = kwargs.get('condition') # 使用**kwargs目的是可以接受任意多个参数 param = kwargs.get('param') if condition == 'tag': article_list = article_list.filter(tags__title=param) # print(article_list) elif condition == 'category': article_list = article_list.filter(category__title=param) else: # 日期归档 year, month = param.split('-') article_list = article_list.filter(create_time__year=year, create_time__month=month) return render(request, 'home_site.html', {'username': username, 'blog': blog, 'article_list': article_list}) def article_detail(request, username, article_id): """ 文章详情页视图 http://127.0.0.1:8000/papa/article/2/ """ user = UserInfo.objects.filter(username=username).first() if not user: return render(request, 'not_fount.html') # 查询username用户的blog对象 blog = user.blog article_obj = models.Article.objects.filter(pk=article_id).first() return render(request, 'article_detail.html', locals()) # locals()不能丢 def digg(request): print(request.user) import json article_id = request.POST.get('article_id') is_up = json.loads(request.POST.get('is_up')) # 传递过来的是"True"字符串, 所以这里需要反序列化为布尔值 user_id = request.user.pk # 直接获取user_id,没有必要通过客户端传送 ard = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up) from django.db.models import F if is_up: models.Article.objects.filter(pk=article_id).update(up_count=F("up_count")+1) return HttpResponse('OK')
五六. 博客系统之文章点赞的提示重复操作 判断是否已经点过赞,已经点过怎么处理
56.1 views.py
def digg(request): print(request.user) import json article_id = request.POST.get('article_id') is_up = json.loads(request.POST.get('is_up')) # 传递过来的是"True"字符串, 所以这里需要反序列化为布尔值 user_id = request.user.pk # 直接获取user_id,没有必要通过客户端传送 # 判断是否已经点过赞 response = {'state': True} # 返回的状态,将传递给模板中的回调函数,默认是True成功, 表示为点过赞或者反对. ret_obj = models.ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first() if not ret_obj: # 没点过赞 ard = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up) # 将点赞写入数据库中 from django.db.models import F article_obj = models.Article.objects.filter(pk=article_id) if is_up: article_obj.update(up_count=F("up_count")+1) else: article_obj.update(down_count=F("down_count")+1) else: # 已经点过赞或反对了 response['state'] = False # 表示失败 response['handled'] = ret_obj.is_up # 获取当前数据库中是点赞过还是点反对过. return JsonResponse(response) # 将状态传递给模板中的回调函数
<script> // 注意 class属性是.action的有两个, 那么到底是点了哪一个, $('.action').click(function () { var is_up = $(this).hasClass('diggit'); // 有diggit属性,就是True, 否则就是False $.ajax({ url: '/digg/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'is_up': is_up, }, success: function (data) { if (data.state){ {# state为True表示之前未点过 #} }else{ {# 之前已经点过赞或者点过反对了 #} if (data.handled){ $(".diggword").html('您已经点过赞了!') }else { $(".diggword").html('您已经点过反对了!') }; setTimeout(function () { $(".diggword").html('') }, 1000) } } }) }) </script>
五七. 博客系统之文章点赞数的Ajax更新
57.1 模板中如何更新点赞数,直接在前端完成,自加1, 无需后台传送数据.
<script> // 注意 class属性是.action的有两个, 那么到底是点了哪一个, $('.action').click(function () { var is_up = $(this).hasClass('diggit'); // 有diggit属性,就是True, 否则就是False $.ajax({ url: '/digg/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'is_up': is_up, }, success: function (data) { if (data.state) { {# state为True表示之前未点过 #} {# is_up为True表示是点赞, False表示反对 #} if (is_up) { var val = parseInt($("#digg_count").text()); $("#digg_count").text(val + 1); $(".diggword").html('点赞成功!') } else { var val = parseInt($("#bury_count").text()); $("#bury_count").text(val + 1); $(".diggword").html('反对成功!') } } else { {# 之前已经点过赞或者点过反对了 #} if (data.handled) { $(".diggword").html('您已经点过赞了!') } else { $(".diggword").html('您已经点过反对了!') } ; setTimeout(function () { $(".diggword").html('') }, 1000) } } }) }) </script>
五八. 博客系统之文章点赞代码优化
<script> // 注意 class属性是.action的有两个, 那么到底是点了哪一个, $('.action').click(function () { var is_up = $(this).hasClass('diggit'); // 有diggit属性,就是True, 否则就是False var $count_obj = $(this).children('span'); // 获取用于显示点赞数\或者是反对数的span标签对象 $.ajax({ url: '/digg/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'is_up': is_up, }, success: function (data) { if (data.state) { {# state为True表示之前未点过 #} var val = parseInt($count_obj.text()); // 获取点赞(反对)的数值 $count_obj.text(val+1); // 点赞(反对)数值加1 } else { {# 三元运算符, True时,val=冒号前的内容, False时val=冒号后的内容 #} var val = data.handled?'您已经点过赞了!':"您已经点过反对了!" $(".diggword").html(val); setTimeout(function () { $(".diggword").html('') }, 1000) } } }) }) </script>
{# 继承 #} {% extends 'base.html' %} {% block content %} <div class="article_info"> {% csrf_token %} <h3 class="text-center">{{ article_obj.title }}</h3> <div class="cont"> {{ article_obj.content|safe }} </div> <div id="div_digg"> {# diggit action 推荐 #} <div class="diggit action"> <span class="diggnum" id="digg_count">{{ article_obj.up_count }}</span> </div> {# diggit action 反对 #} <div class="buryit action"> <span class="burynum" id="bury_count">{{ article_obj.down_count }}</span> </div> <div class="clear"></div> <div class="diggword" id="digg_tips" style="color: red;"></div> </div> <script> // 注意 class属性是.action的有两个, 那么到底是点了哪一个, $('.action').click(function () { var is_up = $(this).hasClass('diggit'); // 有diggit属性,就是True, 否则就是False var $count_obj = $(this).children('span'); // 获取用于显示点赞数\或者是反对数的span标签对象 $.ajax({ url: '/digg/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'is_up': is_up, }, success: function (data) { if (data.state) { {# state为True表示之前未点过 #} var val = parseInt($count_obj.text()); // 获取点赞(反对)的数值 $count_obj.text(val+1); // 点赞(反对)数值加1 } else { {# 三元运算符, True时,val=冒号前的内容, False时val=冒号后的内容 #} var val = data.handled?'您已经点过赞了!':"您已经点过反对了!" $(".diggword").html(val); setTimeout(function () { $(".diggword").html('') }, 1000) } } }) }) </script> </div> {% endblock %}
from django.shortcuts import render, HttpResponse, redirect from django.http import JsonResponse # 传递json数据类型 from django.contrib import auth # 导入用户组件,用于auth_user表的操作 from django.db.models import Avg, Max, Min, Count from django.db.models.functions import TruncMonth from blog import models from blog.models import UserInfo from blog.myforms import RegForm # 导入自定义forms验证规则 def reg(request): """用户注册视图""" if request.is_ajax(): form = RegForm(request.POST) respone = {'user': None, 'msg': None} if form.is_valid(): respone['user'] = form.cleaned_data.get('user') user = form.cleaned_data.get('user') pwd = form.cleaned_data.get('pwd') email = form.cleaned_data.get('email') avatar_obj = request.FILES.get('avatar') extra = {} if avatar_obj: extra['avatar'] = avatar_obj user_obj = UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra) else: respone['msg'] = form.errors return JsonResponse(respone) form = RegForm() # 带入规则 return render(request, 'reg.html', {"form": form}) def login(request): if request.method == "POST": response = {'user': None, 'msg': None} # 构造登录提示信息 user = request.POST.get('user') pwd = request.POST.get('pwd') valid_code = request.POST.get('valid_code') # 获取用户输入的验证码 valid_code_str = request.session.get('valid_code') # 从session中提取自动生成的验证码字符串 if valid_code_str.upper() == valid_code.upper(): # 全部转为大写字母 user = auth.authenticate(username=user, password=pwd) if user: auth.login(request, user) # yanz 成功在session中注册当前用户信息, 就有了request.user全局变量 response['user'] = user.username # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. else: response['msg'] = '用户名或密码错误!' else: response['msg'] = '验证码错误!' return JsonResponse(response) # 这里是ajax验证,所以不能用redirect('index.html')进行跳转.一般应当发送json数据到模板,由模板进行跳转. return render(request, 'login.html') def get_validCode_img(request): """生成验证码图片""" from blog.utils.validCode import get_validCode_img # 导入自定义的生成验证码功能函数 data = get_validCode_img(request) return HttpResponse(data) def index(request): article_list = models.Article.objects.all() return render(request, 'index.html', {'article_list': article_list}) def logout(request): auth.logout(request) return redirect('/login/') def home_site(request, username, **kwargs): user = UserInfo.objects.filter(username=username).first() if not user: return render(request, 'not_fount.html') # 查询username用户的blog对象 blog = user.blog article_list = models.Article.objects.filter(user=user) if kwargs: # 个人站点跳转 condition = kwargs.get('condition') # 使用**kwargs目的是可以接受任意多个参数 param = kwargs.get('param') if condition == 'tag': article_list = article_list.filter(tags__title=param) # print(article_list) elif condition == 'category': article_list = article_list.filter(category__title=param) else: # 日期归档 year, month = param.split('-') article_list = article_list.filter(create_time__year=year, create_time__month=month) return render(request, 'home_site.html', {'username': username, 'blog': blog, 'article_list': article_list}) def article_detail(request, username, article_id): """ 文章详情页视图 http://127.0.0.1:8000/papa/article/2/ """ user = UserInfo.objects.filter(username=username).first() if not user: return render(request, 'not_fount.html') # 查询username用户的blog对象 blog = user.blog article_obj = models.Article.objects.filter(pk=article_id).first() return render(request, 'article_detail.html', locals()) # locals()不能丢 def digg(request): print(request.user) import json article_id = request.POST.get('article_id') is_up = json.loads(request.POST.get('is_up')) # 传递过来的是"True"字符串, 所以这里需要反序列化为布尔值 user_id = request.user.pk # 直接获取user_id,没有必要通过客户端传送 # 判断是否已经点过赞 response = {'state': True} # 返回的状态,将传递给模板中的回调函数,默认是True成功, 表示为点过赞或者反对. ret_obj = models.ArticleUpDown.objects.filter(user_id=user_id, article_id=article_id).first() if not ret_obj: # 没点过赞 ard = models.ArticleUpDown.objects.create(user_id=user_id, article_id=article_id, is_up=is_up) # 将点赞写入数据库中 from django.db.models import F article_obj = models.Article.objects.filter(pk=article_id) if is_up: article_obj.update(up_count=F("up_count")+1) else: article_obj.update(down_count=F("down_count")+1) else: # 已经点过赞或反对了 response['state'] = False # 表示失败 response['handled'] = ret_obj.is_up # 获取当前数据库中是点赞过还是点反对过. return JsonResponse(response) # 将状态传递给模板中的回调函数