Django项目实战:BBS+Blog项目开发之三评论功能与后台设计
上接:Django项目实战:BBS+Blog项目开发之二页面数据和渲染
五九. 博客系统之评论功能的实现流程
59.1 评论可以看成是BBS. 如果没有子评论, 则做法和点赞是差不多的. 难点在子评论的树型显示(有父子关系). 楼层显示方式比较简单.
两种显示方法: rander 加载整个网页
ajax-dom 局部刷新
六零. 博客系统之评论样式 在文章详情页面构建发布评论的样式
60.1 最终效果
60.2 关键代码
<div class="comment"> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea id="comment-content" cols="60" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div>

{# 继承 #} {% 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 class="clearfix"> <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> </div> <div class="comment"> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea id="comment-content" cols="60" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </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 %}
六一. 博客系统之提交根评论
61.1 分析数据库的comment评论表的字段, 搞清楚哪些数据需要ajax从前端获取,并传递给后端.
nid 自动生成
user 从视图中request直接获取
article_id 前端post传送
create_itme 自动生成
content 从前端post传送
parent_comment 前端post传送
61.2 前端绑定事件,获取数据,传递数据
<div class="comment"> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea id="comment-content" cols="60" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 评论 $('.comment-btn').click(function () { var pid = ''; // 父评论id, pid为空则表示当前为父评论. var content = $('#comment-content').val(); // 获取评论内容 $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment':pid, }, success: function (data) { console.log(data); $('#comment-content').val(''); // 将评论内容框清空 } }) }) </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 class="clearfix"> <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> </div> <div class="comment"> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea id="comment-content" cols="60" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 点赞 $('.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) } } }) }) // 评论 $('.comment-btn').click(function () { var pid = ''; // 父评论id, pid为空则表示当前为父评论. var content = $('#comment-content').val(); // 获取评论内容 $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment':pid, }, success: function (data) { console.log(data); $('#comment-content').val(''); // 将评论内容框清空 } }) }) </script> </div> {% endblock %}
61.2 后端视图,获取数据,写入数据库
def comment(request): """ 发布评论 """ print(request.POST) # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('pid') # 从当前用户登录信息中获取 user_id = request.user.pk print("user_id:", user_id) # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) return HttpResponse('comment')

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) # 将状态传递给模板中的回调函数 def comment(request): """ 发布评论 :param request: :return: """ print(request.POST) # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('pid') # 从当前用户登录信息中获取 user_id = request.user.pk print("user_id:", user_id) # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) return HttpResponse('comment')
61.3 url加入评论的路由
path('comment/', views.comment), # 评论

"""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), # 点赞 path('comment/', views.comment), # 评论 # 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), # ]
六二. 博客系统之render显示评论 render方式就是从新加载网页
62.1 最终效果
62.2 前端样式
<div class="comment"> <div id="commnet-list"> <p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> <div class="pull-right"><a href="">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> </div>

{# 继承 #} {% 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 class="clearfix"> <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> </div> <div class="comment"> <div id="commnet-list"> <p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> <div class="pull-right"><a href="">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> </div> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea id="comment-content" cols="60" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 点赞 $('.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) } } }) }) // 评论 $('.comment-btn').click(function () { var pid = ''; // 父评论id, pid为空则表示当前为父评论. var content = $('#comment-content').val(); // 获取评论内容 $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment':pid, }, success: function (data) { console.log(data); $('#comment-content').val(''); // 将评论内容框清空 } }) }) </script> </div> {% endblock %}
62.3 后端从数据库获取评论数据
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() # 获取当前文章的所有评论 comment_list = models.Comment.objects.filter(article_id=article_id) 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 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() # 获取当前文章的所有评论 comment_list = models.Comment.objects.filter(article_id=article_id) 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) # 将状态传递给模板中的回调函数 def comment(request): """ 发布评论 :param request: :return: """ print(request.POST) # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('pid') # 从当前用户登录信息中获取 user_id = request.user.pk print("user_id:", user_id) # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) return HttpResponse('comment')
六三. 博客系统之Ajax显示根评论 从数据库提取刚刚存入的数据,再渲染在模板上
63.1 最终效果
63.2 视图函数提取数据
def comment(request): """ 发布评论 :param request: :return: """ print(request.POST) # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('pid') # 从当前用户登录信息中获取 user_id = request.user.pk print("user_id:", user_id) # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username return JsonResponse(response)

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() # 获取当前文章的所有评论 comment_list = models.Comment.objects.filter(article_id=article_id) 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) # 将状态传递给模板中的回调函数 def comment(request): """ 发布评论 :param request: :return: """ print(request.POST) # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('pid') # 从当前用户登录信息中获取 user_id = request.user.pk print("user_id:", user_id) # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username return JsonResponse(response)
63.3 前端渲染
// 评论 $('.comment-btn').click(function () { var pid = ''; // 父评论id, pid为空则表示当前为父评论. var content = $('#comment-content').val(); // 获取评论内容 $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment':pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> <p>${content}</p> </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 } }) })

{# 继承 #} {% 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 class="clearfix"> <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> </div> <div class="comment"> <div id=""> <p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group commnet-list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> <div class="pull-right"><a href="">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> </div> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea id="comment-content" cols="60" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 点赞 $('.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) } } }) }) // 评论 $('.comment-btn').click(function () { var pid = ''; // 父评论id, pid为空则表示当前为父评论. var content = $('#comment-content').val(); // 获取评论内容 $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment':pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> <p>${content}</p> </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 } }) }) </script> </div> {% endblock %}
六四. 博客系统之回复按钮事件 子评论事件
64.1 做的工作
1. 点击回复按钮, 光标转到发布评论的文本框中; 2. 增加@发文用户,另起一行
64.2 最终效果
64.3 关键代码article_detail.html
<p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group commnet-list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> {# 为了方便回复,这里增加了自定义属性username,让它等于发文用户名 #} <div class="pull-right"><a username="{{ comment.user.username }}" class="reply-btn">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul>
// 回复评论
$('.reply-btn').click(function () {
// 点击回复,光标定位到下面的输入框中, 获取焦点
$('#comment-content').focus();
// 拼接加在回复框前的@用户,\n是换行.
var val = '@' + $(this).attr('username') + '\n';
$('#comment-content').val(val);
})

{# 继承 #} {% 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 class="clearfix"> <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> </div> <div class="comment"> <div id=""> <p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group commnet-list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> {# 为了方便回复,这里增加了自定义属性username,让它等于发文用户名 #} <div class="pull-right"><a username="{{ comment.user.username }}" class="reply-btn">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> </div> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea class="form-control" id="comment-content" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 点赞 $('.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) } } }) }); // 评论 $('.comment-btn').click(function () { var pid = ''; // 父评论id, pid为空则表示当前为父评论. var content = $('#comment-content').val(); // 获取评论内容 $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment':pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> <p>${content}</p> </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 } }) }); // 回复评论 $('.reply-btn').click(function () { // 点击回复,光标定位到下面的输入框中, 获取焦点 $('#comment-content').focus(); // 拼接加在回复框前的@用户,\n是换行. var val = '@' + $(this).attr('username') + '\n'; $('#comment-content').val(val); }) </script> </div> {% endblock %}
六五. 博客系统值提交子评论
65.1 实现功能
1. 提交数据到数据库,但不能有@用户名,这个需要截断. 2. 注意这里的pid不能再是空了.回复谁,就要获取谁的pid.把pid变成全局变量.设定判断是否有值.
65.2 关键代码
// 评论 var pid = ''; // 父评论id, pid为空则表示当前为父评论, 默认为空. $('.comment-btn').click(function () { // 截断评框中@用户名 var content = $('#comment-content').val(); // 获取评论内容 // pid有值说明是子评论, 需要切断换行符之前的内容, 取其后的内容 if (pid){ var index = content.indexOf('\n'); content = content.slice(index+1); }; $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment':pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> <p>${content}</p> </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 // 将pid设为空 pid = ''; } }) }); // 回复评论 $('.reply-btn').click(function () { // 点击回复,光标定位到下面的输入框中, 获取焦点 $('#comment-content').focus(); // 拼接加在回复框前的@用户,\n是换行. var val = '@' + $(this).attr('username') + '\n'; $('#comment-content').val(val); // 获取回复的pid值, 'comment_pk'是该标签的自定义属性 pid = $(this).attr('comment_pk'); })

{# 继承 #} {% 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 class="clearfix"> <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> </div> <div class="comment"> <div id=""> <p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group commnet-list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> {# 新增自定义comment_pk属性目的是方便获取回复的pid值 #} <div class="pull-right"><a username="{{ comment.user.username }}" comment_pk = "{{ comment.pk }}" class="reply-btn">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> </div> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea class="form-control" id="comment-content" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 点赞 $('.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) } } }) }); // 评论 var pid = ''; // 父评论id, pid为空则表示当前为父评论, 默认为空. $('.comment-btn').click(function () { // 截断评框中@用户名 var content = $('#comment-content').val(); // 获取评论内容 // pid有值说明是子评论, 需要切断换行符之前的内容, 取其后的内容 if (pid){ var index = content.indexOf('\n'); content = content.slice(index+1); }; console.log(pid); $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment':pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> <p>${content}</p> </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 // 将pid设为空 pid = ''; } }) }); // 回复评论 $('.reply-btn').click(function () { // 点击回复,光标定位到下面的输入框中, 获取焦点 $('#comment-content').focus(); // 拼接加在回复框前的@用户,\n是换行. var val = '@' + $(this).attr('username') + '\n'; $('#comment-content').val(val); // 获取回复的pid值, 'comment_pk'是该标签的自定义属性 pid = $(this).attr('comment_pk'); }) </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()
# 获取当前文章的所有评论
comment_list = models.Comment.objects.filter(article_id=article_id)
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) # 将状态传递给模板中的回调函数
def comment(request):
"""
发布评论
:param request:
:return:
"""
# 前端ajax发送数据, 这里予以获取
article_id = request.POST.get('article_id')
content = request.POST.get('content')
pid = request.POST.get('parent_comment')
# 从当前用户登录信息中获取
user_id = request.user.pk
print("pid:", pid)
# 写入到数据库comment表
comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content,
parent_comment_id=pid)
# 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗?
create_time = comment_obj.create_time.strftime('%Y-%m-%d %X')
content = comment_obj.content
username = request.user.username
response = {}
response["create_time"] = create_time
response['content'] = content
response['username'] = username
return JsonResponse(response)
六六. 博客系统之render显示子评论
66.1 最终效果
66.2 步骤 1. 先获取子评论的父评论对象 2. 渲染出该父对象
66.3 关键代码
<ul class="list-group commnet-list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> {# 新增自定义comment_pk属性目的是方便获取回复的pid值 #} <div class="pull-right"><a username="{{ comment.user.username }}" comment_pk = "{{ comment.pk }}" class="reply-btn">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> {# 如果该评论是子评论,则需要将其父评论也显示出来 well嵌入效果#} {% if comment.parent_comment_id %} <div class="well">{{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }}</div> {% endif %} <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul>

{# 继承 #} {% 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 class="clearfix"> <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> </div> <div class="comment"> <div id=""> <p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group commnet-list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> {# 新增自定义comment_pk属性目的是方便获取回复的pid值 #} <div class="pull-right"><a username="{{ comment.user.username }}" comment_pk = "{{ comment.pk }}" class="reply-btn">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> {# 如果该评论是子评论,则需要将其父评论也显示出来 #} {% if comment.parent_comment_id %} <div class="well">{{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }}</div> {% endif %} <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> </div> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea class="form-control" id="comment-content" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 点赞 $('.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) } } }) }); // 评论 var pid = ''; // 父评论id, pid为空则表示当前为父评论, 默认为空. $('.comment-btn').click(function () { // 截断评框中@用户名 var content = $('#comment-content').val(); // 获取评论内容 // pid有值说明是子评论, 需要切断换行符之前的内容, 取其后的内容 if (pid){ var index = content.indexOf('\n'); content = content.slice(index+1); }; console.log(pid); $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment':pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> <p>${content}</p> </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 // 将pid设为空 pid = ''; } }) }); // 回复评论 $('.reply-btn').click(function () { // 点击回复,光标定位到下面的输入框中, 获取焦点 $('#comment-content').focus(); // 拼接加在回复框前的@用户,\n是换行. var val = '@' + $(this).attr('username') + '\n'; $('#comment-content').val(val); // 获取回复的pid值, 'comment_pk'是该标签的自定义属性 pid = $(this).attr('comment_pk'); }) </script> </div> {% endblock %}
六七. 博客系统之Ajax显示子评论的思路
67.1 点击评论按按钮中增加是否是子评论的判断
67.2 关键代码, 改动发表评论的js代码
// 评论 var pid = ''; // 父评论id, pid为空则表示当前为父评论, 默认为空. $('.comment-btn').click(function () { // 截断评框中@用户名 var content = $('#comment-content').val(); // 获取评论内容 // pid有值说明是子评论, 需要切断换行符之前的内容, 取其后的内容 if (pid) { var index = content.indexOf('\n'); content = content.slice(index + 1); } ; console.log(pid); $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment': pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. if (pid) { // 增加父评论的内容 var p_username = data.p_username; var p_content = data.p_content; var p_s = ` <div class="well">${p_username}: ${p_content}</div> <p>${content}</p> `; } else { // 如果不是字评论,则不需加父评论信息 var p_s = `<p>${content}</p>`; } ; var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> ${p_s} </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 // 将pid设为空 pid = ''; } }) });

{# 继承 #} {% 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 class="clearfix"> <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> </div> <div class="comment"> <div id=""> <p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group commnet-list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> {# 新增自定义comment_pk属性目的是方便获取回复的pid值 #} <div class="pull-right"><a username="{{ comment.user.username }}" comment_pk="{{ comment.pk }}" class="reply-btn">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> {# 如果该评论是子评论,则需要将其父评论也显示出来 #} {% if comment.parent_comment_id %} <div class="well">{{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }}</div> {% endif %} <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> </div> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea class="form-control" id="comment-content" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 点赞 $('.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) } } }) }); // 评论 var pid = ''; // 父评论id, pid为空则表示当前为父评论, 默认为空. $('.comment-btn').click(function () { // 截断评框中@用户名 var content = $('#comment-content').val(); // 获取评论内容 // pid有值说明是子评论, 需要切断换行符之前的内容, 取其后的内容 if (pid) { var index = content.indexOf('\n'); content = content.slice(index + 1); } ; console.log(pid); $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment': pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. if (pid) { // 增加父评论的内容 var p_username = data.p_username; var p_content = data.p_content; var p_s = ` <div class="well">${p_username}: ${p_content}</div> <p>${content}</p> `; } else { // 如果不是字评论,则不需加父评论信息 var p_s = `<p>${content}</p>`; } ; var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> ${p_s} </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 // 将pid设为空 pid = ''; } }) }); // 回复评论 $('.reply-btn').click(function () { // 点击回复,光标定位到下面的输入框中, 获取焦点 $('#comment-content').focus(); // 拼接加在回复框前的@用户,\n是换行. var val = '@' + $(this).attr('username') + '\n'; $('#comment-content').val(val); // 获取回复的pid值, 'comment_pk'是该标签的自定义属性 pid = $(this).attr('comment_pk'); }) </script> </div> {% endblock %}
六八. 博客系统之评论树简介
68.1 树形结构
68.2 展示树形,最大的难题是不知道有多少层级. 可以用递归来解决.
六九. 博客系统之评论树的请求数据
69.1 效果
69.2 urls.py
path('get_comment_tree/', views.get_comment_tree), # 获取文章的树形结构评论

"""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), # 点赞 path('comment/', views.comment), # 评论 path('get_comment_tree/', views.get_comment_tree), # 获取文章的树形结构评论 # 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), # ]
69.3 模板页面article_detail.html
{# 评论树点击可以展开此文章评论, 以树形结构显示, 这里完全动态 #} <p class="tree-btn">评论树</p> <div class="comment-tree"> </div> <script> // 获取评论树 $('.tree-btn').click(function () { $.ajax({ url: '/get_comment_tree/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, }, 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 class="clearfix"> <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> </div> <div class="comment"> <div id=""> {# 评论树点击可以展开此文章评论, 以树形结构显示, 这里完全动态 #} <p class="tree-btn">评论树</p> <div class="comment-tree"> </div> <script> // 获取评论树 $('.tree-btn').click(function () { $.ajax({ url: '/get_comment_tree/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, }, success: function (data) { console.log(data); } }) }) </script> <p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group commnet-list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> {# 新增自定义comment_pk属性目的是方便获取回复的pid值 #} <div class="pull-right"><a username="{{ comment.user.username }}" comment_pk="{{ comment.pk }}" class="reply-btn">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> {# 如果该评论是子评论,则需要将其父评论也显示出来 #} {% if comment.parent_comment_id %} <div class="well">{{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }}</div> {% endif %} <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> </div> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea class="form-control" id="comment-content" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 点赞 $('.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) } } }) }); // 评论 var pid = ''; // 父评论id, pid为空则表示当前为父评论, 默认为空. $('.comment-btn').click(function () { // 截断评框中@用户名 var content = $('#comment-content').val(); // 获取评论内容 // pid有值说明是子评论, 需要切断换行符之前的内容, 取其后的内容 if (pid) { var index = content.indexOf('\n'); content = content.slice(index + 1); } ; console.log(pid); $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment': pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. if (pid) { // 增加父评论的内容 var p_username = data.p_username; var p_content = data.p_content; var p_s = ` <div class="well">${p_username}: ${p_content}</div> <p>${content}</p> `; } else { // 如果不是字评论,则不需加父评论信息 var p_s = `<p>${content}</p>`; } ; var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> ${p_s} </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 // 将pid设为空 pid = ''; } }) }); // 回复评论 $('.reply-btn').click(function () { // 点击回复,光标定位到下面的输入框中, 获取焦点 $('#comment-content').focus(); // 拼接加在回复框前的@用户,\n是换行. var val = '@' + $(this).attr('username') + '\n'; $('#comment-content').val(val); // 获取回复的pid值, 'comment_pk'是该标签的自定义属性 pid = $(this).attr('comment_pk'); }) </script> </div> {% endblock %}
69.4 视图中获取数据库数据
def get_comment_tree(request): article_id = request.POST.get('article_id') # list强制将querset类型转换为list,然后再序列化. ret = list(models.Comment.objects.filter(article_id=article_id).values('pk', 'comment', 'parent_comment')) print(ret) return JsonResponse(ret, safe=False) # 非字典序列化必须加上safe=False参数

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() # 获取当前文章的所有评论 comment_list = models.Comment.objects.filter(article_id=article_id) 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) # 将状态传递给模板中的回调函数 def comment(request): """ 发布评论 :param request: :return: """ # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('parent_comment') # 从当前用户登录信息中获取 user_id = request.user.pk print("pid:", pid) # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username # 如果是子评论,则还需要获取其父评论的内容,姓名和评论内容 if pid: parent_obj = models.Comment.objects.filter(pk=pid).first() response['p_username'] = parent_obj.user.username response['p_content'] = parent_obj.content return JsonResponse(response) def get_comment_tree(request): article_id = request.POST.get('article_id') # list强制将querset类型转换为list,然后再序列化. ret = list(models.Comment.objects.filter(article_id=article_id).values('pk', 'comment', 'parent_comment')) print(ret) return JsonResponse(ret, safe=False) # 非字典序列化必须加上safe=False参数
七零. 博客系统之展开评论树 显示展开根评论
70.1 效果, 单击评论树, 展示出了所有的根评论
70.2 模板代码
{# 评论树点击可以展开此文章评论, 以树形结构显示, 这里完全动态 #} <p class="tree-btn">评论树</p> <div class="comment-tree"> </div> <script> // 获取评论树 $('.tree-btn').click(function () { $.ajax({ url: '/get_comment_tree/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, }, success: function (data) { console.log(data); // 传过来的data是数组, 内部套多个字典. index是数组的序号, comment_dic是数组内的每一个元素的值(这里是字典) // each是循环取出数组内每一个值,相当于python的for循环 $.each(data, function (index, comment_dic) { var pk = comment_dic.pk; var comment = comment_dic.content; var parent_comment_id = comment_dic.parent_comment_id; // !parent_comment_id 感叹号表示非. 父id没有值,就是该评论没有父评论,它就是根评论. if(!parent_comment_id){ var s = "<div comment_id="+pk+"><span>"+comment+"</span></div>"; $('.comment-tree').append(s); } }) } }) }) </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 class="clearfix"> <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> </div> <div class="comment"> <div id=""> {# 评论树点击可以展开此文章评论, 以树形结构显示, 这里完全动态 #} <p class="tree-btn">评论树</p> <div class="comment-tree"> </div> <script> // 获取评论树 $('.tree-btn').click(function () { $.ajax({ url: '/get_comment_tree/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, }, success: function (data) { console.log(data); // 传过来的data是数组, 内部套多个字典. index是数组的序号, comment_dic是数组内的每一个元素的值(这里是字典) // each是循环取出数组内每一个值,相当于python的for循环 $.each(data, function (index, comment_dic) { var pk = comment_dic.pk; var comment = comment_dic.content; var parent_comment_id = comment_dic.parent_comment_id; // !parent_comment_id 感叹号表示非. 父id没有值,就是该评论没有父评论,它就是根评论. if(!parent_comment_id){ var s = "<div comment_id="+pk+"><span>"+comment+"</span></div>"; $('.comment-tree').append(s); } }) } }) }) </script> <p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group commnet-list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> {# 新增自定义comment_pk属性目的是方便获取回复的pid值 #} <div class="pull-right"><a username="{{ comment.user.username }}" comment_pk="{{ comment.pk }}" class="reply-btn">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> {# 如果该评论是子评论,则需要将其父评论也显示出来 #} {% if comment.parent_comment_id %} <div class="well">{{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }}</div> {% endif %} <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> </div> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea class="form-control" id="comment-content" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 点赞 $('.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) } } }) }); // 评论 var pid = ''; // 父评论id, pid为空则表示当前为父评论, 默认为空. $('.comment-btn').click(function () { // 截断评框中@用户名 var content = $('#comment-content').val(); // 获取评论内容 // pid有值说明是子评论, 需要切断换行符之前的内容, 取其后的内容 if (pid) { var index = content.indexOf('\n'); content = content.slice(index + 1); } ; console.log(pid); $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment': pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. if (pid) { // 增加父评论的内容 var p_username = data.p_username; var p_content = data.p_content; var p_s = ` <div class="well">${p_username}: ${p_content}</div> <p>${content}</p> `; } else { // 如果不是字评论,则不需加父评论信息 var p_s = `<p>${content}</p>`; } ; var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> ${p_s} </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 // 将pid设为空 pid = ''; } }) }); // 回复评论 $('.reply-btn').click(function () { // 点击回复,光标定位到下面的输入框中, 获取焦点 $('#comment-content').focus(); // 拼接加在回复框前的@用户,\n是换行. var val = '@' + $(this).attr('username') + '\n'; $('#comment-content').val(val); // 获取回复的pid值, 'comment_pk'是该标签的自定义属性 pid = $(this).attr('comment_pk'); }) </script> </div> {% endblock %}
七一. 博客系统之展开评论树2 将子评论放到根评论的下面
71.1 效果
71.2 难点, 在(已渲染好的所有父评论)网页中, 找到子评论的父标签, 这是树形结构的好的解决方法.
// 获取评论树 $('.tree-btn').click(function () { $.ajax({ url: '/get_comment_tree/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, }, success: function (data) { console.log(data); // 传过来的data是数组, 内部套多个字典. index是数组的序号, comment_dic是数组内的每一个元素的值(这里是字典) // each是循环取出数组内每一个值,相当于python的for循环 $.each(data, function (index, comment_dic) { var pk = comment_dic.pk; var comment = comment_dic.content; var parent_comment_id = comment_dic.parent_comment_id; // 子评论需要整体向后移动,所以增加class 和 style var s = "<div class='comment-item' style='margin-left: 20px' comment_id="+pk+"><span>"+comment+"</span></div>"; // !parent_comment_id 感叹号表示非. 父id没有值,就是该评论没有父评论,它就是根评论. if(!parent_comment_id){ $('.comment-tree').append(s); // 根评论直接加到标签中 }else{ $("[comment_id="+parent_comment_id+"]").append(s); // 找到父评论标签, 把子评论标签添加进去 } }) } })

{# 继承 #} {% 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 class="clearfix"> <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> </div> <div class="comment"> <div id=""> {# 评论树点击可以展开此文章评论, 以树形结构显示, 这里完全动态 #} <p class="tree-btn">评论树</p> <div class="comment-tree"> </div> <script> // 获取评论树 $('.tree-btn').click(function () { $.ajax({ url: '/get_comment_tree/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, }, success: function (data) { console.log(data); // 传过来的data是数组, 内部套多个字典. index是数组的序号, comment_dic是数组内的每一个元素的值(这里是字典) // each是循环取出数组内每一个值,相当于python的for循环 $.each(data, function (index, comment_dic) { var pk = comment_dic.pk; var comment = comment_dic.content; var parent_comment_id = comment_dic.parent_comment_id; // 子评论需要整体向后移动,所以增加class 和 style var s = "<div class='comment-item' style='margin-left: 20px' comment_id="+pk+"><span>"+comment+"</span></div>"; // !parent_comment_id 感叹号表示非. 父id没有值,就是该评论没有父评论,它就是根评论. if(!parent_comment_id){ $('.comment-tree').append(s); // 根评论直接加到标签中 }else{ $("[comment_id="+parent_comment_id+"]").append(s); // 找到父评论标签, 把子评论标签添加进去 } }) } }) }) </script> <p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group commnet-list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> {# 新增自定义comment_pk属性目的是方便获取回复的pid值 #} <div class="pull-right"><a username="{{ comment.user.username }}" comment_pk="{{ comment.pk }}" class="reply-btn">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> {# 如果该评论是子评论,则需要将其父评论也显示出来 #} {% if comment.parent_comment_id %} <div class="well">{{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }}</div> {% endif %} <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> </div> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea class="form-control" id="comment-content" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 点赞 $('.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) } } }) }); // 评论 var pid = ''; // 父评论id, pid为空则表示当前为父评论, 默认为空. $('.comment-btn').click(function () { // 截断评框中@用户名 var content = $('#comment-content').val(); // 获取评论内容 // pid有值说明是子评论, 需要切断换行符之前的内容, 取其后的内容 if (pid) { var index = content.indexOf('\n'); content = content.slice(index + 1); } ; console.log(pid); $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment': pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. if (pid) { // 增加父评论的内容 var p_username = data.p_username; var p_content = data.p_content; var p_s = ` <div class="well">${p_username}: ${p_content}</div> <p>${content}</p> `; } else { // 如果不是字评论,则不需加父评论信息 var p_s = `<p>${content}</p>`; } ; var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> ${p_s} </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 // 将pid设为空 pid = ''; } }) }); // 回复评论 $('.reply-btn').click(function () { // 点击回复,光标定位到下面的输入框中, 获取焦点 $('#comment-content').focus(); // 拼接加在回复框前的@用户,\n是换行. var val = '@' + $(this).attr('username') + '\n'; $('#comment-content').val(val); // 获取回复的pid值, 'comment_pk'是该标签的自定义属性 pid = $(this).attr('comment_pk'); }) </script> </div> {% endblock %}
七二. 博客系统之评论树的思考1
72.1 ajax的回调函数的参数,建议将名称改为comment_list.
<script> // 获取评论树 $('.tree-btn').click(function () { $.ajax({ url: '/get_comment_tree/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, }, success: function (comment_list) { console.log(comment_list); // 传过来的data是数组, 内部套多个字典. index是数组的序号, comment_dic是数组内的每一个元素的值(这里是字典) // each是循环取出数组内每一个值,相当于python的for循环 $.each(comment_list, function (index, comment_obj) { var pk = comment_obj.pk; var comment = comment_obj.content; var parent_comment_id = comment_obj.parent_comment_id; // 子评论需要整体向后移动,所以增加class 和 style var s = "<div class='comment-item' style='margin-left: 20px' comment_id="+pk+"><span>"+comment+"</span></div>"; // !parent_comment_id 感叹号表示非. 父id没有值,就是该评论没有父评论,它就是根评论. if(!parent_comment_id){ $('.comment-tree').append(s); // 根评论直接加到标签中 }else{ $("[comment_id="+parent_comment_id+"]").append(s); // 找到父评论标签, 把子评论标签添加进去 } }) } }) }) </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 class="clearfix"> <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> </div> <div class="comment"> <div id=""> {# 评论树点击可以展开此文章评论, 以树形结构显示, 这里完全动态 #} <p class="tree-btn">评论树</p> <div class="comment-tree"> </div> <script> // 获取评论树 $('.tree-btn').click(function () { $.ajax({ url: '/get_comment_tree/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, }, success: function (comment_list) { console.log(comment_list); // 传过来的data是数组, 内部套多个字典. index是数组的序号, comment_dic是数组内的每一个元素的值(这里是字典) // each是循环取出数组内每一个值,相当于python的for循环 $.each(comment_list, function (index, comment_obj) { var pk = comment_obj.pk; var comment = comment_obj.content; var parent_comment_id = comment_obj.parent_comment_id; // 子评论需要整体向后移动,所以增加class 和 style var s = "<div class='comment-item' style='margin-left: 20px' comment_id="+pk+"><span>"+comment+"</span></div>"; // !parent_comment_id 感叹号表示非. 父id没有值,就是该评论没有父评论,它就是根评论. if(!parent_comment_id){ $('.comment-tree').append(s); // 根评论直接加到标签中 }else{ $("[comment_id="+parent_comment_id+"]").append(s); // 找到父评论标签, 把子评论标签添加进去 } }) } }) }) </script> <p>评论列表</p> {# 复制bootstrap网站的组件中的列表组样式代码 #} <ul class="list-group commnet-list"> {% for comment in comment_list %} <li class="list-group-item"> <div> <a href="">#{{ forloop.counter }}楼</a> <span>{{ comment.create_time|date:"Y-m-d H:i" }}</span> <a href="">{{ comment.user.username }}</a> {# 新增自定义comment_pk属性目的是方便获取回复的pid值 #} <div class="pull-right"><a username="{{ comment.user.username }}" comment_pk="{{ comment.pk }}" class="reply-btn">回复</a></div> </div> <div id="content-show" style="margin-top: 10px"> {# 如果该评论是子评论,则需要将其父评论也显示出来 #} {% if comment.parent_comment_id %} <div class="well">{{ comment.parent_comment.user.username }}: {{ comment.parent_comment.content }}</div> {% endif %} <p>{{ comment.content }}</p> </div> </li> {% endfor %} </ul> </div> <div id="comment_title"> <p>发表评论:</p> 昵称 <span class="glyphicon glyphicon-user"></span> <input disabled="disabled" value="{{ request.user.username }}"> </div> <div>评论内容</div> <div><textarea class="form-control" id="comment-content" rows="10"></textarea></div> <p> <button class="btn btn-default comment-btn">提交评论</button> </p> </div> <script> // 点赞 $('.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) } } }) }); // 评论 var pid = ''; // 父评论id, pid为空则表示当前为父评论, 默认为空. $('.comment-btn').click(function () { // 截断评框中@用户名 var content = $('#comment-content').val(); // 获取评论内容 // pid有值说明是子评论, 需要切断换行符之前的内容, 取其后的内容 if (pid) { var index = content.indexOf('\n'); content = content.slice(index + 1); } ; console.log(pid); $.ajax({ url: '/comment/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, 'content': content, 'parent_comment': pid, }, success: function (data) { // 展示刚刚提交的评论, 注意这里是从后台数据库中获取数据. var create_time = data.create_time; var content = data.content; var username = data.username; // 将变量嵌入到字符串中, es6语法使用反引号将字符串包住, 使用${}嵌入变量. 如果不用es6语法则需要用+拼接字符串. if (pid) { // 增加父评论的内容 var p_username = data.p_username; var p_content = data.p_content; var p_s = ` <div class="well">${p_username}: ${p_content}</div> <p>${content}</p> `; } else { // 如果不是字评论,则不需加父评论信息 var p_s = `<p>${content}</p>`; } ; var s = ` <li class="list-group-item"> <div> <span>${create_time}</span> <a href="">${username}</a> </div> <div id="content-show" style="margin-top: 10px"> ${p_s} </div> </li> `; // 将新构建的评论加入到评论列表后面 $('ul.commnet-list').append(s); $('#comment-content').val(''); // 将评论内容框清空 // 将pid设为空 pid = ''; } }) }); // 回复评论 $('.reply-btn').click(function () { // 点击回复,光标定位到下面的输入框中, 获取焦点 $('#comment-content').focus(); // 拼接加在回复框前的@用户,\n是换行. var val = '@' + $(this).attr('username') + '\n'; $('#comment-content').val(val); // 获取回复的pid值, 'comment_pk'是该标签的自定义属性 pid = $(this).attr('comment_pk'); }) </script> </div> {% endblock %}
72.2 视图函数获取数据库评论,建议加上order_by('pk'),即按键值顺序取出.默认也是按键值顺序,这里强化一下.
def get_comment_tree(request): article_id = request.POST.get('article_id') # list强制将querset类型转换为list,然后再序列化. values中是取需要的字段 ret = list(models.Comment.objects.filter(article_id=article_id).order_by('pk').values('pk', 'content', 'parent_comment_id')) print(ret) return JsonResponse(ret, safe=False) # 非字典序列化必须加上safe=False参数

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() # 获取当前文章的所有评论 comment_list = models.Comment.objects.filter(article_id=article_id) 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) # 将状态传递给模板中的回调函数 def comment(request): """ 发布评论 :param request: :return: """ # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('parent_comment') # 从当前用户登录信息中获取 user_id = request.user.pk print("pid:", pid) # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username # 如果是子评论,则还需要获取其父评论的内容,姓名和评论内容 if pid: parent_obj = models.Comment.objects.filter(pk=pid).first() response['p_username'] = parent_obj.user.username response['p_content'] = parent_obj.content return JsonResponse(response) def get_comment_tree(request): article_id = request.POST.get('article_id') # list强制将querset类型转换为list,然后再序列化. values中是取需要的字段 ret = list(models.Comment.objects.filter(article_id=article_id).order_by('pk').values('pk', 'content', 'parent_comment_id')) print(ret) return JsonResponse(ret, safe=False) # 非字典序列化必须加上safe=False参数
七三. 博客系统之评论树的思考2
73.1 前面的设计是,点击评论树三个字,然后列出所有评论树, 现在想载入网页时能直接出现评论树. 只需要将ajax前的click事件去掉即可.
<script> // 获取评论树 去掉下面这句$('.tree-btn').click(function () {代码就可以了 $('.tree-btn').click(function () { $.ajax({ url: '/get_comment_tree/', type: 'post', data: { 'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val(), 'article_id': {{ article_obj.pk }}, }, success: function (comment_list) { console.log(comment_list); // 传过来的data是数组, 内部套多个字典. index是数组的序号, comment_dic是数组内的每一个元素的值(这里是字典) // each是循环取出数组内每一个值,相当于python的for循环 $.each(comment_list, function (index, comment_obj) { var pk = comment_obj.pk; var comment = comment_obj.content; var parent_comment_id = comment_obj.parent_comment_id; // 子评论需要整体向后移动,所以增加class 和 style var s = "<div class='comment-item' style='margin-left: 20px' comment_id="+pk+"><span>"+comment+"</span></div>"; // !parent_comment_id 感叹号表示非. 父id没有值,就是该评论没有父评论,它就是根评论. if(!parent_comment_id){ $('.comment-tree').append(s); // 根评论直接加到标签中 }else{ $("[comment_id="+parent_comment_id+"]").append(s); // 找到父评论标签, 把子评论标签添加进去 } }) } }) }) </script>
七四. 博客系统之评论事务操作
74.1 事务的意思是, 比如甲向乙转账, 从甲账户上减, 乙账户上同时加上, 为了保持减加的一致, 就可以加上事务操作. 有事务操作时, 如果甲减了, 而乙没有加上, 则会回滚回转账之前.
74.2 本博客系统, 评论增加了一条, 在文章表中有一个计数, 也应当同时加1. 为了保证一致, 可以加上事务操作.
74.3 关键代码, 视图函数
from django.db.models import F # 数据库表中自加1 from django.db import transaction # 事务操作(用于保证多个数据的同步一致) def comment(request): """ 发布评论 :param request: :return: """ # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('parent_comment') # 从当前用户登录信息中获取 user_id = request.user.pk print("pid:", pid) with transaction.atomic(): # 事务同步, 保证评论内容写入comment表,同时,将article表中的评论数计数器加1 # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 同步写入评论数加1的数据到文章表中 models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count')+1) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username # 如果是子评论,则还需要获取其父评论的内容,姓名和评论内容 if pid: parent_obj = models.Comment.objects.filter(pk=pid).first() response['p_username'] = parent_obj.user.username response['p_content'] = parent_obj.content return JsonResponse(response)

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 F # 数据库表中自加1 from django.db import transaction # 事务操作(用于保证多个数据的同步一致) 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() # 获取当前文章的所有评论 comment_list = models.Comment.objects.filter(article_id=article_id) 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) # 将点赞写入数据库中 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) # 将状态传递给模板中的回调函数 def comment(request): """ 发布评论 :param request: :return: """ # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('parent_comment') # 从当前用户登录信息中获取 user_id = request.user.pk print("pid:", pid) with transaction.atomic(): # 事务同步, 保证评论内容写入comment表,同时,将article表中的评论数计数器加1 # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 同步写入评论数加1的数据到文章表中 models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count')+1) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username # 如果是子评论,则还需要获取其父评论的内容,姓名和评论内容 if pid: parent_obj = models.Comment.objects.filter(pk=pid).first() response['p_username'] = parent_obj.user.username response['p_content'] = parent_obj.content return JsonResponse(response) def get_comment_tree(request): article_id = request.POST.get('article_id') # list强制将querset类型转换为list,然后再序列化. values中是取需要的字段 ret = list(models.Comment.objects.filter(article_id=article_id).order_by('pk').values('pk', 'content', 'parent_comment_id')) print(ret) return JsonResponse(ret, safe=False) # 非字典序列化必须加上safe=False参数
七五. 博客系统之评论的邮件发送new 文章有了新评论后,给作者发邮件提醒
75.1 settings.py中配置发件邮箱
# 配置邮件发送 EMAIL_HOST = 'smtp.qq.com' # 如果是163 改成 smtp.163.com EMAIL_PORT = 465 # 163端口25 EMAIL_HOST_USER = '' # 发件账户 EMAIL_HOST_PASSWORD = "" # 密码,客户端授权码, 不是邮箱密码 # DEFAULT_FORM_EMAIL = EMAIL_HOST_USER # 配置默认发件账户 EMAIL_USE_SSL = True # ssl证书支持
75.2 在视图函数中增加发邮件的功能
def comment(request): """ 发布评论 :param request: :return: """ # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('parent_comment') # 从当前用户登录信息中获取 user_id = request.user.pk print("pid:", pid) with transaction.atomic(): # 事务同步, 保证评论内容写入comment表,同时,将article表中的评论数计数器加1 # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 同步写入评论数加1的数据到文章表中 models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count')+1) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username # 如果是子评论,则还需要获取其父评论的内容,姓名和评论内容 if pid: parent_obj = models.Comment.objects.filter(pk=pid).first() response['p_username'] = parent_obj.user.username response['p_content'] = parent_obj.content # 发送邮件通知文章作者,你的文章有了新的评论 from django.core.mail import send_mail # 发送邮件模块 from cnblog import settings # 导入配置 import threading # 为了提高效率, 另开一个线程发送邮件 article_obj = models.Article.objects.filter(pk=article_id).first() # 获取当前文章对象 email = article_obj.user.email # 获取文章的作者的邮箱 print(email, type(email)) # send_mail( # '您的文章%s新增了一条评论内容' % article_obj.title, # '评论内容:%s' % content, # settings.EMAIL_HOST_USER, # 配置发送邮件的邮箱账户, 如果在settings中配置了默认邮箱,则这里可以不写 # [email, '123456789@qq.com'], # 作者邮箱列表 # ) # 开多线程,提高效率 sendMail = threading.Thread(target=send_mail, args=( '您的文章%s新增了一条评论内容' % article_obj.title, '评论内容:%s' % content, settings.EMAIL_HOST_USER, [email, '123456789@qq.com'], )) sendMail.start() # 多线程千万别忘了这一步.这里才是真正的执行发送任务.上面是配置. return JsonResponse(response)

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 F # 数据库表中自加1 from django.db import transaction # 事务操作(用于保证多个数据的同步一致) 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() # 获取当前文章的所有评论 comment_list = models.Comment.objects.filter(article_id=article_id) 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) # 将点赞写入数据库中 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) # 将状态传递给模板中的回调函数 def comment(request): """ 发布评论 :param request: :return: """ # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('parent_comment') # 从当前用户登录信息中获取 user_id = request.user.pk print("pid:", pid) with transaction.atomic(): # 事务同步, 保证评论内容写入comment表,同时,将article表中的评论数计数器加1 # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 同步写入评论数加1的数据到文章表中 models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count')+1) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username # 如果是子评论,则还需要获取其父评论的内容,姓名和评论内容 if pid: parent_obj = models.Comment.objects.filter(pk=pid).first() response['p_username'] = parent_obj.user.username response['p_content'] = parent_obj.content # 发送邮件通知文章作者,你的文章有了新的评论 from django.core.mail import send_mail # 发送邮件模块 from cnblog import settings # 导入配置 import threading # 为了提高效率, 另开一个线程发送邮件 article_obj = models.Article.objects.filter(pk=article_id).first() # 获取当前文章对象 email = article_obj.user.email # 获取文章的作者的邮箱 print(email, type(email)) # send_mail( # '您的文章%s新增了一条评论内容' % article_obj.title, # '评论内容:%s' % content, # settings.EMAIL_HOST_USER, # 配置发送邮件的邮箱账户, 如果在settings中配置了默认邮箱,则这里可以不写 # [email, '123456789@qq.com'], # 作者邮箱列表 # ) # 开多线程,提高效率 sendMail = threading.Thread(target=send_mail, args=( '您的文章%s新增了一条评论内容' % article_obj.title, '评论内容:%s' % content, settings.EMAIL_HOST_USER, [email, '123456789@qq.com'], )) sendMail.start() # 多线程千万别忘了这一步.这里才是真正的执行发送任务.上面是配置. return JsonResponse(response) def get_comment_tree(request): article_id = request.POST.get('article_id') # list强制将querset类型转换为list,然后再序列化. values中是取需要的字段 ret = list(models.Comment.objects.filter(article_id=article_id).order_by('pk').values('pk', 'content', 'parent_comment_id')) print(ret) return JsonResponse(ret, safe=False) # 非字典序列化必须加上safe=False参数
七六. 博客系统之后台团了页面文本编辑的功能
76.1 后台管理界面效果
76.2 后台管理模板代码 新建顶部左侧被继承模板backed_base.html, 后台管理首页文章列表backed_index.html, 添加文章add_article.html三个模板

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <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" type="text/css" charset="UTF-8"> <link rel="stylesheet" href="/static/css/article_detail.css" type="text/css" charset="UTF-8"> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <style> .header .backend{ color: white; font-size: 14px; padding-right: 10px; } .header .backend a{ color: white; } </style> </head> <body> <div class="header"> <div class="content clearfix"> <span class="title">网站管理后台</span> <div class="backend pull-right"> <span class="glyphicon glyphicon-user"></span> <span>{{ request.user.username }}</span> <a href="/logout/">注销</a> </div> </div> </div> <div class="container"> <div class="row"> <div class="col-md-2"> <div class="panel panel-default text-center"> <div class="panel-heading">功 能</div> <div class="panel-body"> <div><span class="glyphicon glyphicon-circle-arrow-right"></span> <a href="/add_article/">添加文章</a></div> <hr> <div><span class="glyphicon glyphicon-circle-arrow-right"></span> <a href="/backed_index/">所有文章</a></div> <hr> <div><span class="glyphicon glyphicon-circle-arrow-right"></span> <a href="">修改密码</a></div> <hr> <div><span class="glyphicon glyphicon-circle-arrow-right"></span> <a href="">修改头像</a></div> </div> </div> </div> <div class="col-md-10"> {% block backed_content %} {# 预留盒子, 进行扩展 #} {% endblock %} </div> </div> </div> </body> </html>

{% extends 'backed_base.html' %} {% block backed_content %} <h2>{{ request.user.username }}的所有文章</h2> <hr> <table class="table table-hover"> <thead> <tr> <th>ID</th> <th>文章标题</th> <th>评论数</th> <th>点赞数</th> <th>发文时间</th> <th>操作</th> <th>操作</th> </tr> </thead> <tbody> {% for article in article_list %} <tr> <td>{{ article.pk }}</td> <td>{{ article.title }}</td> <td>{{ article.comment_count }}</td> <td>{{ article.up_count }}</td> <td>{{ article.create_time|date:"Y-m-d H:i" }}</td> <td><a href="" class="btn btn-danger">修改</a></td> <td><a href="" class="btn btn-warning">删除</a></td> </tr> {% endfor %} </tbody> </table> {% endblock %}

{% extends 'backed_base.html' %} {% block backed_content %} <h2>新增{{ request.user.username }}的文章</h2> <hr> <div class="container"> <div class="row"> <div class="col-md-9"> <form action="" method="post"> {% csrf_token %} <div class="form-group"> <label for="title">文章标题</label> <input type="text" id="title" name="title" class="form-control"> </div> <div class="form-group"> <label for="desc">文章描述</label> <input type="text" id="desc" name="desc" class="form-control"> </div> <div class="form-group"> <label for="content">文章详细内容</label> <textarea cols="30" id="content" name="content" class="form-control"></textarea> </div> <div class="form-group"> <input type="submit" class="btn btn-success"> </div> </form> </div> </div> </div> {% endblock %}
76.3 后台管理视图代码, 使用用户认证装饰器,

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 F # 数据库表中自加1 from django.db import transaction # 事务操作(用于保证多个数据的同步一致) from django.contrib.auth.decorators import login_required # 登录认证装饰器 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() # 获取当前文章的所有评论 comment_list = models.Comment.objects.filter(article_id=article_id) 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) # 将点赞写入数据库中 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) # 将状态传递给模板中的回调函数 def comment(request): """ 发布评论 :param request: :return: """ # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('parent_comment') # 从当前用户登录信息中获取 user_id = request.user.pk print("pid:", pid) with transaction.atomic(): # 事务同步, 保证评论内容写入comment表,同时,将article表中的评论数计数器加1 # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 同步写入评论数加1的数据到文章表中 models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count')+1) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username # 如果是子评论,则还需要获取其父评论的内容,姓名和评论内容 if pid: parent_obj = models.Comment.objects.filter(pk=pid).first() response['p_username'] = parent_obj.user.username response['p_content'] = parent_obj.content # 发送邮件通知文章作者,你的文章有了新的评论 from django.core.mail import send_mail # 发送邮件模块 from cnblog import settings # 导入配置 import threading # 为了提高效率, 另开一个线程发送邮件 article_obj = models.Article.objects.filter(pk=article_id).first() # 获取当前文章对象 email = article_obj.user.email # 获取文章的作者的邮箱 print(email, type(email)) # send_mail( # '您的文章%s新增了一条评论内容' % article_obj.title, # '评论内容:%s' % content, # settings.EMAIL_HOST_USER, # 配置发送邮件的邮箱账户, 如果在settings中配置了默认邮箱,则这里可以不写 # [email, '123456789@qq.com'], # 作者邮箱列表 # ) # 开多线程,提高效率 sendMail = threading.Thread(target=send_mail, args=( '您的文章%s新增了一条评论内容' % article_obj.title, '评论内容:%s' % content, settings.EMAIL_HOST_USER, [email, '123456789@qq.com'], )) sendMail.start() # 多线程千万别忘了这一步.这里才是真正的执行发送任务.上面是配置. return JsonResponse(response) def get_comment_tree(request): article_id = request.POST.get('article_id') # list强制将querset类型转换为list,然后再序列化. values中是取需要的字段 ret = list(models.Comment.objects.filter(article_id=article_id).order_by('pk').values('pk', 'content', 'parent_comment_id')) print(ret) return JsonResponse(ret, safe=False) # 非字典序列化必须加上safe=False参数 @login_required def backed_index(request): """管理后台""" user_id = request.user.pk article_list = models.Article.objects.filter(user_id=user_id) return render(request, 'backed_index.html', locals()) @login_required def add_article(request): """新增文章""" return render(request, 'add_article.html', locals())

"""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), # 点赞 path('comment/', views.comment), # 评论 path('get_comment_tree/', views.get_comment_tree), # 获取文章的树形结构评论 path('backed_index/', views.backed_index), # 获取文章的树形结构评论 path('add_article/', views.add_article), # 获取文章的树形结构评论 # 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), # ]

""" Django settings for cnblog project. Generated by 'django-admin startproject' using Django 3.1.7. For more information on this file, see https://docs.djangoproject.com/en/3.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.1/ref/settings/ """ from pathlib import Path import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '43@l3&_w6qq@q3@$5u8gpzbz7ti_dgd2ppwcse%m8@t#zn*l#j' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'blog.apps.BlogConfig', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'cnblog.urls' 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', ], }, }, ] WSGI_APPLICATION = 'cnblog.wsgi.application' # Database # https://docs.djangoproject.com/en/3.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = False # 将评论时间调整为东八区 # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static') ] AUTH_USER_MODEL = 'blog.UserInfo' # 配置用户上传的文件存放位置, 项目根目录下的media目录. 当有了此配置后,Django会将用户上传的文件存储在此. MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/' # 设置后用于可在外网直接通过url访问MEDIA_ROOT设置的文件夹 # 配置邮件发送 EMAIL_HOST = 'smtp.qq.com' # 如果是163 改成 smtp.163.com EMAIL_PORT = 465 # 163端口25 EMAIL_HOST_USER = '407928668@qq.com' # 发件账户 EMAIL_HOST_PASSWORD = "jjucyxowtmmlbgbd" # 密码,客户端授权码, 不是邮箱密码 # DEFAULT_FORM_EMAIL = EMAIL_HOST_USER # 配置默认发件账户 EMAIL_USE_SSL = True # ssl证书支持 LOGIN_URL = '/login/' # 配合登录验证装饰器, 设置跳转的路径
76.4 单选,多选数据的添加保存到数据库, 文章分类和标签

{% extends 'backed_base.html' %} {% block backed_content %} <h2>新增{{ request.user.username }}的文章</h2> <hr> <div class="container"> <div class="row"> <div class="col-md-9"> <form action="" method="post"> {% csrf_token %} <div class="form-group"> <label for="title">文章标题</label> <input type="text" id="title" name="title" class="form-control"> </div> <div class="form-group"> <label for="desc">文章描述</label> <input type="text" id="desc" name="desc" class="form-control"> </div> <div class="form-group"> <label>文章所属分类</label> {% for category in category_list %} <label class="radio-inline"> <input type="radio" name="category_id" value="{{ category.pk }}"> {{ category.title }} </label> {% endfor %} </div> <div class="form-group"> <label>标签(关键字)</label> {% for tag in tag_list %} <label class="checkbox-inline"> <input type="checkbox" name='tag_id_list' value="{{ tag.pk }}"> {{ tag.title }} </label> {% endfor %} </div> <div class="form-group"> <label for="content">文章详细内容</label> <textarea cols="30" id="content" name="content" class="form-control"></textarea> </div> <div class="form-group"> <input type="submit" class="btn btn-success"> </div> </form> </div> </div> </div> <script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script> <script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script> <script> KindEditor.ready(function (K) { {# content是上面textarea标签的ID值 #} window.editor = K.create('#content', { height: '500px', }); }); </script> {% 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 F # 数据库表中自加1 from django.db import transaction # 事务操作(用于保证多个数据的同步一致) from django.contrib.auth.decorators import login_required # 登录认证装饰器 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() # 获取当前文章的所有评论 comment_list = models.Comment.objects.filter(article_id=article_id) 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) # 将点赞写入数据库中 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) # 将状态传递给模板中的回调函数 def comment(request): """ 发布评论 :param request: :return: """ # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('parent_comment') # 从当前用户登录信息中获取 user_id = request.user.pk print("pid:", pid) with transaction.atomic(): # 事务同步, 保证评论内容写入comment表,同时,将article表中的评论数计数器加1 # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 同步写入评论数加1的数据到文章表中 models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count') + 1) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username # 如果是子评论,则还需要获取其父评论的内容,姓名和评论内容 if pid: parent_obj = models.Comment.objects.filter(pk=pid).first() response['p_username'] = parent_obj.user.username response['p_content'] = parent_obj.content # 发送邮件通知文章作者,你的文章有了新的评论 from django.core.mail import send_mail # 发送邮件模块 from cnblog import settings # 导入配置 import threading # 为了提高效率, 另开一个线程发送邮件 article_obj = models.Article.objects.filter(pk=article_id).first() # 获取当前文章对象 email = article_obj.user.email # 获取文章的作者的邮箱 print(email, type(email)) # send_mail( # '您的文章%s新增了一条评论内容' % article_obj.title, # '评论内容:%s' % content, # settings.EMAIL_HOST_USER, # 配置发送邮件的邮箱账户, 如果在settings中配置了默认邮箱,则这里可以不写 # [email, '123456789@qq.com'], # 作者邮箱列表 # ) # 开多线程,提高效率 sendMail = threading.Thread(target=send_mail, args=( '您的文章%s新增了一条评论内容' % article_obj.title, '评论内容:%s' % content, settings.EMAIL_HOST_USER, [email, '123456789@qq.com'], )) sendMail.start() # 多线程千万别忘了这一步.这里才是真正的执行发送任务.上面是配置. return JsonResponse(response) def get_comment_tree(request): article_id = request.POST.get('article_id') # list强制将querset类型转换为list,然后再序列化. values中是取需要的字段 ret = list(models.Comment.objects.filter(article_id=article_id).order_by('pk').values('pk', 'content', 'parent_comment_id')) print(ret) return JsonResponse(ret, safe=False) # 非字典序列化必须加上safe=False参数 @login_required def backed_index(request): """管理后台""" user_id = request.user.pk article_list = models.Article.objects.filter(user_id=user_id) return render(request, 'backed_index.html', locals()) @login_required def add_article(request): """新增文章""" category_list = models.Category.objects.filter(blog__userinfo__nid=request.user.pk).values('pk', 'title') tag_list = models.Tag.objects.filter(blog__userinfo__nid=request.user.pk).values('pk', 'title') if request.method == 'POST': title = request.POST.get('title') desc = request.POST.get('desc') content = request.POST.get('content') category_id = request.POST.get('category_id') print(category_id) tag_id_list = request.POST.getlist('tag_id_list') # 多选取值 print(tag_id_list) article_obj = models.Article.objects.create(title=title, desc=desc, content=content, user_id=request.user.pk, category_id=category_id) # 多对多的article2tag表数据添加, 文章添加关键字 article_obj.tags.add(*tag_id_list) return redirect('/backed_index/') return render(request, 'add_article.html', locals())
七七. 博客系统之后台管理的编辑器引入和参数
77.1 效果, kineditor富文本编辑器, 参考网站:http://kindeditor.net/docs/usage.html
77.2 关键代码
<script> KindEditor.ready(function (K) { {# content是上面textarea标签的ID值 #} window.editor = K.create('#content',{ height: '500px', {# 配置编辑器的参数 #} }); }); </scrip
配置编辑器:编辑器的工具栏配置http://kindeditor.net/docs/option.html#items
七八 七九. 博客系统之文本编辑器的上传功能 上传图片
from django.views.decorators.clickjacking import xframe_options_sameorigin # 富文本编辑器上传图片,一直卡,需要这个模块
@xframe_options_sameorigin # 富文本编辑器上传图片一直卡,需要使用这个装饰器 def upload(request): print(request.FILES) img = request.FILES.get('upload_img') # <MultiValueDict: {'upload_img': [<InMemoryUploadedFile: Jellyfish.jpg (image/jpeg)>]}> print(img.name) # 读取文件对象,写入服务器指定位置 import os path = os.path.join(settings.MEDIA_ROOT, 'add_article_img', img.name) with open(path, 'wb') as f: for line in img: f.write(line) # 将上传图片的数据传递给模板的富文本编辑器 response={ 'error': 0, # 0没有任何错误 'url': '/media/add_article_img%s' % img.name, # 通过网络可以访问该图片的路径 } return HttpResponse(JsonResponse(response)) # 注意这里的传递方法
<script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script> <script> KindEditor.ready(function (K) { {# article-content是上面textarea标签的ID值 #} window.editor = K.create('#article-content', { height: '500px', {# 文件上传路由配置 #} uploadJson: "/upload/", extraFileUploadParams: { csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val() }, {# 指定上传文件对象的键的名称 <MultiValueDict: {'upload_img': [<InMemoryUploadedFile: Hydrangeas.jpg (image/jpeg)>]}> #} filePostName: 'upload_img' }); }); </script>

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 F # 数据库表中自加1 from django.db import transaction # 事务操作(用于保证多个数据的同步一致) from django.contrib.auth.decorators import login_required # 登录认证装饰器 from cnblog import settings # 导入配置 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() # 获取当前文章的所有评论 comment_list = models.Comment.objects.filter(article_id=article_id) 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) # 将点赞写入数据库中 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) # 将状态传递给模板中的回调函数 def comment(request): """ 发布评论 :param request: :return: """ # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('parent_comment') # 从当前用户登录信息中获取 user_id = request.user.pk print("pid:", pid) with transaction.atomic(): # 事务同步, 保证评论内容写入comment表,同时,将article表中的评论数计数器加1 # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 同步写入评论数加1的数据到文章表中 models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count') + 1) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username # 如果是子评论,则还需要获取其父评论的内容,姓名和评论内容 if pid: parent_obj = models.Comment.objects.filter(pk=pid).first() response['p_username'] = parent_obj.user.username response['p_content'] = parent_obj.content # 发送邮件通知文章作者,你的文章有了新的评论 from django.core.mail import send_mail # 发送邮件模块 import threading # 为了提高效率, 另开一个线程发送邮件 article_obj = models.Article.objects.filter(pk=article_id).first() # 获取当前文章对象 email = article_obj.user.email # 获取文章的作者的邮箱 print(email, type(email)) # send_mail( # '您的文章%s新增了一条评论内容' % article_obj.title, # '评论内容:%s' % content, # settings.EMAIL_HOST_USER, # 配置发送邮件的邮箱账户, 如果在settings中配置了默认邮箱,则这里可以不写 # [email, '123456789@qq.com'], # 作者邮箱列表 # ) # 开多线程,提高效率 sendMail = threading.Thread(target=send_mail, args=( '您的文章%s新增了一条评论内容' % article_obj.title, '评论内容:%s' % content, settings.EMAIL_HOST_USER, [email, '123456789@qq.com'], )) sendMail.start() # 多线程千万别忘了这一步.这里才是真正的执行发送任务.上面是配置. return JsonResponse(response) def get_comment_tree(request): article_id = request.POST.get('article_id') # list强制将querset类型转换为list,然后再序列化. values中是取需要的字段 ret = list(models.Comment.objects.filter(article_id=article_id).order_by('pk').values('pk', 'content', 'parent_comment_id')) print(ret) return JsonResponse(ret, safe=False) # 非字典序列化必须加上safe=False参数 @login_required def backed_index(request): """管理后台""" user_id = request.user.pk article_list = models.Article.objects.filter(user_id=user_id) return render(request, 'backed_index.html', locals()) @login_required def add_article(request): """新增文章""" category_list = models.Category.objects.filter(blog__userinfo__nid=request.user.pk).values('pk', 'title') tag_list = models.Tag.objects.filter(blog__userinfo__nid=request.user.pk).values('pk', 'title') if request.method == 'POST': title = request.POST.get('title') desc = request.POST.get('desc') content = request.POST.get('content') category_id = request.POST.get('category_id') print(category_id) tag_id_list = request.POST.getlist('tag_id_list') # 多选取值 print(tag_id_list) article_obj = models.Article.objects.create(title=title, desc=desc, content=content, user_id=request.user.pk, category_id=category_id) # 多对多的article2tag表数据添加, 文章添加关键字 article_obj.tags.add(*tag_id_list) return redirect('/backed_index/') return render(request, 'add_article.html', locals()) @login_required def upload(request): print(request.FILES) img = request.FILES.get('upload_img') # <MultiValueDict: {'upload_img': [<InMemoryUploadedFile: Jellyfish.jpg (image/jpeg)>]}> print(img.name) # 读取文件对象,写入服务器指定位置 import os path = os.path.join(settings.MEDIA_ROOT, 'add_article_img', img.name) with open(path, 'wb') as f: for line in img: f.write(line) # 将上传图片的数据传递给模板的富文本编辑器 response={ 'error': 0, # 0没有任何错误 'url': '/media/add_article_img%s' % img.name, # 通过网络可以访问该图片的路径 } return HttpResponse(JsonResponse(response)) # 注意这里的传递方法

"""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), # 点赞 path('comment/', views.comment), # 评论 path('get_comment_tree/', views.get_comment_tree), # 获取文章的树形结构评论 path('backed_index/', views.backed_index), # 获取文章的树形结构评论 path('add_article/', views.add_article), # 获取文章的树形结构评论 path('upload/', views.upload), # 文件上传处理 # 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), # ]

{% extends 'backed_base.html' %} {% block backed_content %} <h2>新增{{ request.user.username }}的文章</h2> <hr> <div class="container"> <div class="row"> <div class="col-md-9"> <form action="" method="post"> {% csrf_token %} <div class="form-group"> <label for="title">文章标题</label> <input type="text" id="title" name="title" class="form-control"> </div> <div class="form-group"> <label for="desc">文章描述</label> <input type="text" id="desc" name="desc" class="form-control"> </div> <div class="form-group"> <label>文章所属分类</label> {% for category in category_list %} <label class="radio-inline"> <input type="radio" name="category_id" value="{{ category.pk }}"> {{ category.title }} </label> {% endfor %} </div> <div class="form-group"> <label>标签(关键字)</label> {% for tag in tag_list %} <label class="checkbox-inline"> <input type="checkbox" name='tag_id_list' value="{{ tag.pk }}"> {{ tag.title }} </label> {% endfor %} </div> <div class="form-group"> <label for="content">文章详细内容</label> <textarea cols="30" id="article-content" name="content" class="form-control"></textarea> </div> <div class="form-group"> <input type="submit" class="btn btn-success"> </div> </form> </div> </div> </div> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <script charset="utf-8" src="/static/kindeditor/kindeditor-all.js"></script> <script> KindEditor.ready(function (K) { {# article-content是上面textarea标签的ID值 #} window.editor = K.create('#article-content', { height: '500px', {# 文件上传路由配置 #} uploadJson: "/upload/", extraFileUploadParams: { csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val() }, {# 指定上传文件对象的键的名称 <MultiValueDict: {'upload_img': [<InMemoryUploadedFile: Hydrangeas.jpg (image/jpeg)>]}> #} filePostName: 'upload_img' }); }); </script> {% endblock %}

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <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" type="text/css" charset="UTF-8"> <link rel="stylesheet" href="/static/css/article_detail.css" type="text/css" charset="UTF-8"> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script> <style> .header .backend{ color: white; font-size: 14px; padding-right: 10px; } .header .backend a{ color: white; } </style> </head> <body> <div class="header"> <div class="content clearfix"> <span class="title">网站管理后台</span> <div class="backend pull-right"> <span class="glyphicon glyphicon-user"></span> <span>{{ request.user.username }}</span> <a href="/logout/">注销</a> </div> </div> </div> <div class="container"> <div class="row"> <div class="col-md-2"> <div class="panel panel-default text-left"> <div class="panel-heading">功 能</div> <div class="panel-body"> <div><span class="glyphicon glyphicon-circle-arrow-right"></span> <a href="/add_article/">添加文章</a></div> <hr> <div><span class="glyphicon glyphicon-circle-arrow-right"></span> <a href="/backed_index/">所有文章</a></div> <hr> <div><span class="glyphicon glyphicon-circle-arrow-right"></span> <a href="">新增文章分类</a></div> <hr> <div><span class="glyphicon glyphicon-circle-arrow-right"></span> <a href="">新增标签关键字</a></div> <hr> <div><span class="glyphicon glyphicon-circle-arrow-right"></span> <a href="">修改密码</a></div> <hr> <div><span class="glyphicon glyphicon-circle-arrow-right"></span> <a href="">修改头像</a></div> </div> </div> </div> <div class="col-md-10"> {% block backed_content %} {# 预留盒子, 进行扩展 #} {% endblock %} </div> </div> </div> </body> </html>
八零. 博客系统之文章摘要的保存
80.1 自动截取文章生成摘要. 一般摘要只需要纯文字. 这样会产生问题: 当文章内容中包含html标签时,会把标签当成文字截取,造成摘要显示出问题.
desc = content[0:150] # 直接从内容中截取文字,当摘要用.
八一. 博客系统之bs4的简单应用
81.1 通过bs4将含有html标签的文章内容提取出全部的纯文字.所有的标签被遗弃.
from bs4 import BeautifulSoup soup = BeautifulSoup(content, 'html.parser') desc = soup.text[0:150] # 直接从内容中截取文字,当摘要用.
八二. 博客系统之基于bs4模块防御xss攻击
82.1 过滤文章中的script标签
from bs4 import BeautifulSoup soup = BeautifulSoup(content, 'html.parser') # 过滤有害的script标签 for tag in soup.find_all(): # 遍历所有标签 if tag.name == 'script': tag.decompose() # 删除script desc = str(soup.text[0:150]) # 直接从内容中截取文字,当摘要用. 过滤过后需要用str()包裹起来.

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 F # 数据库表中自加1 from django.db import transaction # 事务操作(用于保证多个数据的同步一致) from django.contrib.auth.decorators import login_required # 登录认证装饰器 from cnblog import settings # 导入配置 from django.views.decorators.clickjacking import xframe_options_sameorigin # 富文本编辑器上传图片,一直卡,需要这个模块 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() # 获取当前文章的所有评论 comment_list = models.Comment.objects.filter(article_id=article_id) 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) # 将点赞写入数据库中 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) # 将状态传递给模板中的回调函数 def comment(request): """ 发布评论 :param request: :return: """ # 前端ajax发送数据, 这里予以获取 article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('parent_comment') # 从当前用户登录信息中获取 user_id = request.user.pk print("pid:", pid) with transaction.atomic(): # 事务同步, 保证评论内容写入comment表,同时,将article表中的评论数计数器加1 # 写入到数据库comment表 comment_obj = models.Comment.objects.create(user_id=user_id, article_id=article_id, content=content, parent_comment_id=pid) # 同步写入评论数加1的数据到文章表中 models.Article.objects.filter(pk=article_id).update(comment_count=F('comment_count') + 1) # 从数据库提取刚刚存入的数据, 为啥非要从数据库中立即再次取出, 不能直接在前端提交时,在前端直接取数据吗? create_time = comment_obj.create_time.strftime('%Y-%m-%d %X') content = comment_obj.content username = request.user.username response = {} response["create_time"] = create_time response['content'] = content response['username'] = username # 如果是子评论,则还需要获取其父评论的内容,姓名和评论内容 if pid: parent_obj = models.Comment.objects.filter(pk=pid).first() response['p_username'] = parent_obj.user.username response['p_content'] = parent_obj.content # 发送邮件通知文章作者,你的文章有了新的评论 from django.core.mail import send_mail # 发送邮件模块 import threading # 为了提高效率, 另开一个线程发送邮件 article_obj = models.Article.objects.filter(pk=article_id).first() # 获取当前文章对象 email = article_obj.user.email # 获取文章的作者的邮箱 print(email, type(email)) # send_mail( # '您的文章%s新增了一条评论内容' % article_obj.title, # '评论内容:%s' % content, # settings.EMAIL_HOST_USER, # 配置发送邮件的邮箱账户, 如果在settings中配置了默认邮箱,则这里可以不写 # [email, '123456789@qq.com'], # 作者邮箱列表 # ) # 开多线程,提高效率 sendMail = threading.Thread(target=send_mail, args=( '您的文章%s新增了一条评论内容' % article_obj.title, '评论内容:%s' % content, settings.EMAIL_HOST_USER, [email, '123456789@qq.com'], )) sendMail.start() # 多线程千万别忘了这一步.这里才是真正的执行发送任务.上面是配置. return JsonResponse(response) def get_comment_tree(request): article_id = request.POST.get('article_id') # list强制将querset类型转换为list,然后再序列化. values中是取需要的字段 ret = list(models.Comment.objects.filter(article_id=article_id).order_by('pk').values('pk', 'content', 'parent_comment_id')) print(ret) return JsonResponse(ret, safe=False) # 非字典序列化必须加上safe=False参数 @login_required def backed_index(request): """管理后台""" user_id = request.user.pk article_list = models.Article.objects.filter(user_id=user_id) return render(request, 'backed_index.html', locals()) @login_required def add_article(request): """新增文章""" category_list = models.Category.objects.filter(blog__userinfo__nid=request.user.pk).values('pk', 'title') tag_list = models.Tag.objects.filter(blog__userinfo__nid=request.user.pk).values('pk', 'title') if request.method == 'POST': title = request.POST.get('title') content = request.POST.get('content') # 注意这里content没有做script筛选 # desc = request.POST.get('desc') from bs4 import BeautifulSoup soup = BeautifulSoup(content, 'html.parser') # 过滤有害的script标签 for tag in soup.find_all(): # 遍历所有标签 if tag.name == 'script': tag.decompose() # 删除script desc = str(soup.text[0:150]) # 直接从内容中截取文字,当摘要用. 过滤过后需要用str()包裹起来. category_id = request.POST.get('category_id') tag_id_list = request.POST.getlist('tag_id_list') # 多选取值 article_obj = models.Article.objects.create(title=title, desc=desc, content=content, user_id=request.user.pk, category_id=category_id) # 多对多的article2tag表数据添加, 文章添加关键字 article_obj.tags.add(*tag_id_list) return redirect('/backed_index/') return render(request, 'add_article.html', locals()) @xframe_options_sameorigin # 富文本编辑器上传图片,一直卡,需要这个模块 def upload(request): print(request.FILES) img = request.FILES.get('upload_img') # <MultiValueDict: {'upload_img': [<InMemoryUploadedFile: Jellyfish.jpg (image/jpeg)>]}> print(img.name) # 读取文件对象,写入服务器指定位置 import os path = os.path.join(settings.MEDIA_ROOT, 'add_article_img', img.name) with open(path, 'wb') as f: for line in img: f.write(line) # 将上传图片的数据传递给模板的富文本编辑器 response={ 'error': 0, # 0没有任何错误 'url': '/media/add_article_img/%s' % img.name, # 通过网络可以访问该图片的路径 } return HttpResponse(JsonResponse(response)) # 注意这里的传递方法
结束. 博客系统之完整代码(待整理)
结束. 博客园完整代码博客园完整代码
parent_comment_id
$('.tree-btn').click(function () {
博
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
· 零经验选手,Compose 一天开发一款小游戏!