目录
一、路由设计
from django.conf.urls import url from django.contrib import admin from bbs import views from django.views.static import serve from mybbs.settings import MEDIA_ROOT urlpatterns = [ url(r'^$', views.index), url(r'^admin/', admin.site.urls), url(r'^login/', views.login), # 用于login前端页面对于验证码的获取 url(r'^get_valid_code/', views.get_valid_code), url(r'^index/', views.index), url(r'^register/', views.register), url(r'^logout/', views.logout), url(r'^change_pwd/', views.change_pwd), url(r'^change_avatar/', views.change_avatar), url(r'^media/(?P<path>.*)', serve, {"document_root": MEDIA_ROOT}), # 点赞的路由 url(r'^diggit/$', views.diggit), # 评论路由 url(r'^commit_content/$', views.commit_content), # 三个过滤(分类,标签,归档) - 用于进入点击左边导航后的个人站点的文章显示 # 分组分出三个(用户名,category|tag|archive中的一个,可能是分类id,tag_id,时间) url(r'^(?P<username>[\w]+)/(?P<condition>category|tag|archive)/(?P<param>.*)', views.user_blog), # 个人主页的文章查看路由 url(r'^(?P<username>[\w]+)/article/(?P<id>\d+)', views.article_detail), # 上方未匹配到,接收参数匹配个人站点 有名分组 url(r'^(?P<username>[\w]+)$', views.user_blog), ]
二、视图函数
2-1 文章内容
from django.shortcuts import render, HttpResponse, redirect from bbs import models import json from django.db import transaction from django.db.models import F def article_detail(request, username, id, *args, **kwargs): ''' 文章内容查看,直接更新在个人站点的中心内容内 :param request: :param username: 前端传输当前用户 :param id: 文章的id号 :param args: :param kwargs: :return: ''' username = username user = models.UserInfo.objects.filter(username=username).first() if not user: return render(request, 'error.html') blog = user.blog article = models.Article.objects.filter(pk=id).first() content_list = article.commit_set.all().order_by('pk') return render(request, 'article_detail.html', locals())
2-2 赞踩操作
from django.shortcuts import render, HttpResponse, redirect from bbs import models import json from django.db import transaction from django.db.models import F def diggit(request): ''' 点赞函数 :param request: :return: ''' response = {'status': 100, 'msg': None} if request.user.is_authenticated(): # 从前端传过来的数据,都转成str类型(json格式) article_id = request.POST.get('article_id') is_up = request.POST.get('is_up') is_up = json.loads(is_up) # 原子性操作.用事务;使用F函数对文章的点赞量进行增加操作:一旦其中有一项失败则回滚 # 可以通过数据库查询是否用户对该篇文章进行赞踩操作(不推荐) # ret=models.UpAndDown.objects.filter(user_id=user.pk,article_id=article_id).exists() # 使用异常捕捉获取数据报错 try: with transaction.atomic(): # 使用 UpAndDown 表记录用户和文章的点赞关系 # 若无法创建,表示记录已存在则报错 models.UpAndDown.objects.create(user=request.user, article_id=article_id, is_up=is_up) # models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1) article = models.Article.objects.filter(pk=article_id) if is_up: article.update(up_num=F('up_num') + 1) response['msg'] = '点赞成功' else: article.update(down_num=F('down_num') + 1) response['msg'] = '反对成功' except Exception as e: response['msg'] = '您已经点过赞,请勿重复点赞!' else: response['msg'] = '请先登录' response['status'] = 101 return JsonResponse(response)
2-3 评论操作
from django.shortcuts import render, HttpResponse, redirect from bbs import models import json from django.db import transaction from django.db.models import F def commit_content(request): ''' 评论操作 :param request: :return: ''' response = {'status': 100, 'msg': None} if request.is_ajax(): if request.user.is_authenticated(): user = request.user article_id = request.POST.get('article_id') content = request.POST.get('content') pid = request.POST.get('pid') # 注意字段parent_id_id,由于我的models内外键字段为parent_id,而表内的字段会自动添加_id with transaction.atomic(): ret = models.Commit.objects.create(user=user, article_id=article_id, content=content, parent_id_id=pid) models.Article.objects.filter(pk=article_id).update(commit_num=F('commit_num') + 1) response['msg'] = '评论成功' response['content'] = ret.content # 把datetime类型转成字符串,因为json是无法序列化datetime response['time'] = ret.commit_time.strftime('%Y-%m-%d %X') response['user_name'] = ret.user.username if pid: # 如果是子评论,返回父评论的名字 response['parent_name'] = ret.parent_id.user.username else: response['status'] = 101 response['msg'] = '您没有登录' else: response['status'] = 101 response['msg'] = '您请求非法' return JsonResponse(response)
三、前端设计
3-1 母版
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> {# blog对象的反向查询 表名小写#} <title>{{ blog.userinfo.username }}-的个人博客</title> <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css"> {# 使用当前用户的样式文件 #} <link rel="stylesheet" href="/static/css/{{ blog.theme }}"> <link rel="stylesheet" href="/static/css/default.css"> <script src="/static/jquery-3.3.1.js"></script> <style> * { margin: 0; padding: 0; } </style> </head> <body> <div class="head"> <h1>{{ blog.title }}</h1> </div> <div class="container-fluid"> <div class="row"> {# 侧栏导航 模板的导入#} <div class="col-md-3"> {# 也可以通过自定义过滤器进行互相传参操作#} {% load my_tag %} {% classify username %} {# 通过模板导入具有的局限性:每个视图函数内必须返回所需的参数才能构建一个网站#} {# {% include 'classify.html' %}#} </div> {# 中心内容 模板的继承#} <div class="col-md-9"> {% block content %} {% endblock %} </div> </div> </div> </div> </body> </html>
3-2 继承子版(article_detail.html)
{% extends 'base.html' %} {% block content %} {#{% csrf_token %}#} <div> <p><h4 class="text-center">{{ article.title }}</h4></p> <div> {#safe操作,对代码进行转义显示#} {{ article.content|safe }} </div> {#赞踩部分#} <div class="clearfix"> <div id="div_digg"> <div class="diggit action"> <span class="diggnum" id="digg_count">{{ article.up_num }}</span> </div> <div class="buryit action"> <span class="burynum" id="bury_count">{{ article.down_num }}</span> </div> <div class="clear"></div> <div class="diggword" id="digg_tips" style="color: red;"></div> </div> </div> {#评论显示#} <div> <ul class="list-group cotent_ul"> {% for content in content_list %} <li class="list-group-item"> {# 查看循环信息:{{ forloop }} ;forloop.counter循环的次数#} <p><span>#{{ forloop.counter }}楼</span> <span>{{ content.create_time|date:'Y-m-d H:i:s' }}</span> <span><a href="/{{ content.user.username }}">{{ content.user.username }}</a></span> <span class="pull-right replay" username="{{ content.user.username }}" content_id="{{ content.pk }}">回复</span> </p> {% if content.parent_id %} {#content.parent_id 拿到父评论的对象#} <p class="well">@{{ content.parent_id.user.username }}</p> {% endif %} {{ content.content }} </li> {% endfor %} </ul> </div> {#发表评论#} <div> <p>发表评论</p> <p> 昵称:<input type="text" id="tbCommentAuthor" class="author" disabled="disabled" size="50" value="{{ request.user }}"> </p> <p>评论内容:</p> <p> <textarea name="" id="content" cols="60" rows="10"></textarea> </p> <button class="btn btn-primary submit">提交</button> </div> </div> <script> {#赞踩部分#} $(".action").click(function () { {#若点击类为action的并且是diggit点赞部分的保存isup为true#} var is_up = $(this).hasClass('diggit'); var obj = $(this).children('span'); {# ajax响应赞踩 #} $.ajax({ {#通过路由响应点赞函数#} url: '/diggit/', type: 'post', //当前文章pk可从后台取 data: {article_id: '{{ article.pk }}', is_up: is_up, 'csrfmiddlewaretoken': '{{ csrf_token }}'}, success: function (data) { {#在赞踩部分最后错误信息处展示后台返回信息内容#} $("#digg_tips").html(data.msg); alert(data.msg); {#若赞踩成功,无需刷新直接在前端进行数量增加#} if (data.status == 100) { {#方式一(推荐)#} obj.text(Number(obj.text()) + 1); {# 方式二#} /*if(is_up){ //获取页面内值方式(str):text() //var count= $("#digg_count").text() //把count转成int类型(两种方式parseInt、Number) //$("#digg_count").text(parseInt(count)+1) $("#digg_count").text(Number(count)+1) }else{ var count= $("#bury_count").text() $("#bury_count").text(Number(count)+1) }*/ } } }) }); var pid = ''; {#发表根评论#} $(".submit").click(function () { var content = $("#content").val(); $("#content").val(""); {#对parent内容切掉父级名称#} if (pid) { //拿到content要切的起始位置indexOf(),取到指定值的索引值(0,4) //取到 \n 索引位置的后一位 var index = content.indexOf('\n') + 1; //slice传一个起始位置,一个结束位置,就可以切出来 content = content.slice(index) } $.ajax({ url: '/commit_content/', type: 'post', data: { 'article_id': '{{ article.pk }}', 'content': content, 'pid': pid, 'csrfmiddlewaretoken': '{{ csrf_token }}' }, {#设置提交数据成功之后马上相应的前端操作(假发表)#} success: function (data) { {#console.log(data);#} //拼 用户名:时间 // 评论内容 这些数据都应该从后台返回 var time = data.time; var content = data.content; var user_name = data.user_name; var ss = ''; if (pid) { //需要清空一下父评论的id {#防止发表根评论时候粘留上次的父级id#} pid = ''; var parent_name = data.parent_name; ss = ` <li class="list-group-item"> <p> <span>${ user_name }</span> <span>${ time }</span> </p> <p class="well">@${parent_name }</p> ${content} </li> ` } else { ss = ` <li class="list-group-item"> <p> <span>${ user_name }</span>: <span>${ time }</span> </p> ${content} </li> ` } $(".cotent_ul").append(ss) } }) }); {#发表子评论 点击回复自动在评论框中,将父级用户名和评论内容进行拼接操作,并设置提交评论需要的pid#} $(".replay").click(function () { //拿到span标签中username属性对应的值,attr() 方法设置或返回被选元素的属性值 var username = $(this).attr('username'); //把拼接的字符串放到content内,并且换行 $('#content').val('@' + username + '\n'); //让光标聚焦到这个控件,重置光标位于content内容尾部 $('#content').focus(); //给pid赋值(pid是父评论的id) pid = $(this).attr('content_id') }) </script> {% endblock %}