django项目(博客二)
扩展1:admin路由分发的本质
路由分发本质 include 可以无限制的 嵌套N多层
url(r'^index/',([],None,None))
扩展2:
由于url方法第一个参数是正则表达式,所有当路由特别多的时候,可能会出现被顶替的情况,针对这种情况有两种解决方式
建好路由,先和视图函数继续试验一下,避免路由被顶替
1.修改正则表达式
2.调整url方法的位置,越往上,url的优先级越高
扩展3:
时区问题报错,setting.py文件里设置,修改时区
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False
一 文章详情页
个人站点页面和文章详细页面,两个的导航页和左侧边栏相同,就建一个共同的base.html页面,两个页面继承base.html页面,
两个页面的左侧边栏从视图函数要的数据是样的,就自定义inclusion_tag,自定义inclusion_tag的工作就是建mytags.py,
并建left_menu.html文件。base.html的左侧栏,调用mytats.py,再调用left_menu函数,并传username参数。
base.html的左侧面建好,个人站点页面和文章详情页面继承base.html,各自建内容区,需要视图函数的数据,就再各自的视图函数要。
'''
文章详情页和个人站点基本一致,所以用模版继承
个人站点和文章详情的导航条和左侧边栏一样,这该两个html页面就继承base.html,个人站点页面和文章详情页面的内容区,写自己的内容,
个人站点页面和文章详情页面的左侧栏页面,需要向视图函数要同样的数据,解决代码冗余,就用到自定义inclusion_tags 侧边栏的渲染需要传输数据才能渲染,并且该侧边栏在很多页面都需要使用
1. 在那个地方用就拷贝需要的代码(不推荐 有点繁琐)
2. 将侧边栏制作成inclusion_tag,作用就是,不同的页面,需要共同的视图函数数据,就自定义inclusion_tag,不同的页面,都向inclusion要数据,解决代码冗余的问题
'''
url.py
# url设计
https://www.cnblogs.com/xiaoyuanqujing/articles/12208376.html /username/article/1 # 先验证url是否被其他url顶替,排查BUG
# 文章详情页
url(r'^(?P<username>\w+)/article/(?P<article_id>\d+)/', views.article_detail)
views.py
# 文章详情页视图函数
def article_detail(request, username, article_id):
'''
可以先进行用户名校验是否存在
:param request:
:param username:
:param article_id:
:return:
'''
# 筛选出文章对象,显示对应的用户访问对应的文章,文章查个人站点,正向,个人查用户,反向,跨表用__
article_obj = models.Article.objects.filter(id=article_id, blog__userinfo__username=username).first()
if not article_obj:
# return redirect('/errors/') # 从定向,需要走url路由,没设置路由,走不通
return render(request, 'errors.html')
# 导航条需要个人站点名,就从数据库取给前端
user_obj = models.UserInfo.objects.filter(username=username).first()
blog = user_obj.blog
# 获取当前文章所有的评论内容
comment_list = models.Comment.objects.filter(article=article_obj)
return render(request, 'article_detail.html', locals())
base.html
个人站点和文章详情页都从base中模版继承,左侧边栏需要向不同 的视图函数要同样的数据,
用到自定义inclusion_tag
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="referrer" content="no-referrer" />
<title>{{ blog }}的个人站点</title>
<link rel="stylesheet" href="/static/bootstrap-3.4.1-dist/css/bootstrap.min.css">
<script src="/static/js/jQuery-3.6.1.js"></script>
<script src="/static/bootstrap-3.4.1-dist/js/bootstrap.min.js"></script>
<script src="/static/js/my_setup.js"></script>
{% block css %} {% endblock %}
</head>
<body>
<!--个人站点导航条-->
<div class="container-fluid">
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">{{ blog.site_title }}</a>
</div> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
<li><a href="#">test</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li><a href="#">{{ request.user.username }}</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true"
aria-expanded="false">更多 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
<li><a href="#">修改头像</a></li>
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url 'logout' %}" id="logout">退出登录</a></li>
</ul>
<!-- Large modal -->
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog"
aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<h3 class="text-center">修改密码</h3>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
<label for="">用户名</label>
<input type="text" disabled value="{{ request.user.username }}"
class="form-control">
</div>
<div class="form-group">
<label for="old_password">原密码</label>
<input type="password" id="old_password" class="form-control">
<span style="color: red" class="pull-right"></span>
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="password" id="new_password" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="password" id="confirm_password" class="form-control">
<span style="color: red" class="pull-right"></span>
</div>
<div class="form-group">
<button type="button" class="btn btn-default btn-sm"
data-dismiss="modal">
取消
</button>
<button type="button" class="btn btn-primary btn-sm"
id="set_commit">
确认修改
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
{% else %}
<li><a href="{% url 'reg' %}">注册</a></li>
<li><a href="{% url 'log' %}">登录</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
</div>
<!--主板 3,9比例-->
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<!--侧边栏调用mytags.py-->
{% load mytags %}
<!--调用mytags里面的left_menu函数-->
{% left_menu username %}
</div>
<!--主板内容-->
<div class="col-md-9">
{% block content %} {% endblock %}
</div>
</div>
</div> {% block js %} {% endblock %}
</body>
</html>
mytags.py
在应用aap01文件下,建mytags.py
'''
步骤:
1.在应用下创建一个名字必须叫templatetags文件夹
2.在该文件夹内创建一个任意名称的py文件
3.在该py文件内先固定写两行代码
from django import template
register = template.Library()
自定义过滤器
自定义标签
自定义inclusion_tag
'''
from django import template
register = template.Library() # 自定义inclusion_tag
@register.inclusion_tag('left_menu.html')
def left_menu(username):
# 构造侧边栏需要的数据
from app01 import models
from django.db.models import Count
# 先从site视图函数,剪切过来 ,需要什么参数,就给什么参数
user_obj = models.UserInfo.objects.filter(username=username).first()
blog = user_obj.blog
# 1 查询当前用户所有的分类及分类下的文章数
category_queryset = models.Category.objects.filter(blog=blog).annotate(
count_num=Count('article__pk')).values_list('name', 'count_num', 'pk') # pk是分组后新的虚拟结果的主键值,也就是queryset的索引值
# values是列表套字典,values_list是列表套元祖
# 2 查询当前用户所有的标签及标签下的文章数
tag_queryset = models.Tag.objects.filter(blog=blog).annotate(
count_num=Count('article__pk')).values_list('name', 'count_num', 'pk')
# 3 按照年月统计所有的文章
from django.db.models.functions import TruncMonth
# 第一个.annotate是按文章进行分组,第二个.annotate是跟在values('month'),month的虚拟表基础上在进行分组,计数取值
article_month = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values(
'month').annotate(
num=Count('pk')
).values_list('month', 'num')
# print(article_month)
return locals()
left_menu.html
自定义inclusio_tag中的左侧边栏页面
div class="panel panel-info">
<div class="panel-heading text-center">文章分类</div>
<div class="panel-body">
{% for category in category_queryset %}
<p><a href="/{{ username }}/category/{{ category.2 }}">{{ category.0 }}({{ category.1 }})</a></p>
{% endfor %}
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading text-center">文章标签</div>
<div class="panel-body">
{% for tag in tag_queryset %}
<a href="/{{ username }}/tag/{{ tag.2 }}"><p>{{ tag.0 }}<{{ tag.1 }}></p></a>
{% endfor %}
</div>
</div>
<div class="panel panel-success">
<div class="panel-heading text-center">日期归档</div>
<div class="panel-body">
{% for month in article_month %}
<p><a href="/{{ username }}/archive/{{ month.0|date:'Y-m' }}">{{ month.0|date:'Y年m月' }}({{ month.1 }})</a>
</p>
{% endfor %}
</div>
</div>
article_detail.html
文章详情页
{% extends 'base.html' %} {% block content %}
<div class="container">
<h1>{{ article_obj.title }}</h1>
<div>
{{ article_obj.content }}
</div>
</div> {% endblock %}
site.html
通过模版继承后的个人站点页面
{% extends 'base.html' %} {% block content %}
<ul class="media-list">
{% for article_obj in article_list %}
<li class="media">
<h3 class="media-heading"><a href="/{{ username }}/article/{{ article_obj.pk }}">{{ article_obj.title }}</a></h3>
<div class="media-left">
<a href="#">
<img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..."
width="50px"
height="60px">
</a>
</div>
<div class="media-body">
{{ article_obj.desc }}
</div> <div class="pull-right">
<span>posted</span>
<span>@</span>
<span>{{ article_obj.create_time|date:'Y-m-d' }} </span>
<span><a href="#">{{ username }}</a> </span>
<span><span class="glyphicon glyphicon-comment"></span>评论数({{ article_obj.comment_num }}) </span>
<span><span class="glyphicon glyphicon-thumbs-up"></span>点赞数({{ article_obj.up_num }}) </span>
<span><a href="#">编辑</a></span>
</div>
</li>
<hr> <!--分割线-->
{% endfor %}
</ul>
{% endblock %}
二 文章点赞点踩
文章点赞点踩功能是在,文章详情页面
'''
浏览器上你看到的花里胡哨的页面,内部都是HTML(前端)代码
在文章内容应该写什么? --->html代码 如何拷贝文章
copy outerhtml
解决加载不了图片,用到图片防盗链技术
<meta name="referrer" content="no-referrer" /> 1.拷贝文章
2.拷贝点赞点踩
1) 拷贝前端点赞点踩图片,只拷贝html
2) css也要拷贝
由于有图片防盗链的问题,所以将图片直接下载到本地 课下思考:
前端如何区分用户是点了赞还是点了踩
1.给标签各自绑定一个事件
两个标签对应的代码其实基本一样,仅仅是否点赞点踩这一个参数不一样而已
2.二合一
给两个标签绑定一个事件
// 给所有的action类绑定事件
$('.action').click(function () {
alert($(this).hasClass('diggit'))
}) 3.书写ajax代码朝后端提交数
由于点赞点踩内部有一定的业务逻辑,所以后端单独开设视图函数
建议在写项目的时候吗,先把业务逻辑写完,在进行修正 4.后端逻辑书写完毕之后,前端针对点赞点踩动作实现需要动态展示提示信息
1)前端点赞点踩数字自增1 需要注意数据类型的问题
2)用户没有登录 需要展示没有登录提示 并且登录可以点击跳转
html()
|safe
mark_safe() 后端向前端发送html代码
''' '''
后端逻辑(先写成功逻辑,再写错误逻辑
1.先判断用户是否登录
request.user.authenticated()
2.再判断当前文字是否是当前用户自己写的
通过文章主键值获取文章对象
之后利用orm查询获取文章对象对应的用户对象与request.user比对
3.判断当前用户是否已经给当前文章点了
利用article_obj文章对象和request.user用户对象去点赞点踩表中筛选数据如果
有数据则点没哟
4.操作数据库,需要注意要同时操作两张表
前端发送过来的是否点赞是一个字符串 需要你自己转成布尔值或者用字符串判断
'''
'''
总结:在书写较为复杂的业务逻辑的时候,可以先按照一条线写下去
之后再去弥补其他线路情况 类似于先走树的主干 之后再分散
'''
urls.py
# 点赞点踩
url(r'^up_or_down/', views.up_or_down),
views.py
# 点赞点踩视图函数
def up_or_down(request):
'''
1.校验用户是否登录
2.判断当前文章是否是当前用户自己写的(自己不能点自己的文章)
3.当前用户是否已经给当前文章点过了
4.操作数据库
:param request:
:return:
'''
if request.is_ajax():
back_dic = {'code': 1000, 'msg': ''}
# 1.先判断用户是否登录
if request.user.is_authenticated():
article_id = request.POST.get('article_id')
print(article_id, type(article_id))
is_up = request.POST.get('is_up')
import json
# 前端发送过来的,是json格式的,需要转成python格式的bool值
is_up = json.loads(is_up)
print(is_up, type(is_up))
# 2.判断当前文章是否是当前用户自己写的 根据文章id查询文章对象,根据文章对象查作者 跟request.user.username比对
article_obj = models.Article.objects.filter(pk=article_id).first()
print(article_obj.blog.userinfo.username, type(article_obj.blog.userinfo.username))
print(request.user, type(request.user))
if not article_obj.blog.userinfo.username == request.user.username:
# 3. 校验当前用户是否已经点了 那个地方记录了用户到底点没点
is_click = models.UpAndDown.objects.filter(user=request.user, article=article_obj)
if not is_click:
# 4 操作数据库 记录数据 要同步操作普通字段
# 判断当前用户点了赞还是踩 从而决定给那个字段加一
from django.db.models import F # F新的虚拟字段和原有字段进行比对
if is_up:
# 给点赞数加一
models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
back_dic['msg'] = '点赞成功'
else:
# 给点踩数加一
models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
back_dic['msg'] = '点踩成功'
# 操作点赞点踩表,记录数据
models.UpAndDown.objects.create(user=request.user, article=article_obj, is_up=is_up)
else:
back_dic['code'] = 2000
back_dic['msg'] = '你已点过了,不能在点'
else:
back_dic['code'] = 3000
back_dic['msg'] = '不能自己点自己'
else:
back_dic['code'] = 4000
back_dic['msg'] = '需<a href="/login/">登录</a>了,才能点击'
return JsonResponse(back_dic)
article_detail.html
{% extends 'base.html' %} {% block css %} <style>
/* 点赞点踩样式 */
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 128px;
text-align: center;
margin-top: 10px;
} .diggit {
float: left;
width: 46px;
height: 52px;
background: url('/static/img/upup.gif') no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
} .buryit {
float: right;
margin-left: 20px;
width: 46px;
height: 52px;
background: url('/static/img/downdown.gif') no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
} .clear {
/*清除浮动*/
clear: both;
}
</style>
{% endblock %} {% block content %} <h1 class="text-center">{{ article_obj.title }}</h1> <div class="article_content" style="overflow: hidden">
{{ article_obj.content|safe }}
</div>
<!--点赞点踩开始-->
<div class="clearfix">
<div id="div_digg">
<div class="diggit action">
<span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
</div>
<div class="buryit action">
<span class="burynum" id="bury_count">{{ article_obj.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red;"></div>
</div>
</div>
<!--点赞点踩结束--> <!--根评论开始-->
<!--评论楼开始-->
<ul class="list-group" id="id_floor">
{% for comment in comment_list %}
<span>#{{ forloop.counter }}楼</span>
<span>{{ comment.content_time|date:'Y-m-d h:i:s' }}</span>
<span>{{ comment.user.username }}</span> <li class="list-group-item">
<div>
{% if comment.parent_id %}
<!--字段parent是自关联字段,跨表还是跨的自己,在再通过外键user跨表查根评论用户-->
<p>@{{ comment.parent.user.username }}</p>
{% endif %}
<span>{{ comment.content }}</span>
<!--a标签自定义属性-->
<span class="pull-right"><a class="reply" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span>
</div> </li>
{% endfor %}
</ul>
<!--评论楼结束--> <!--前端判断是否登录-->
{% if request.user.is_authenticated %}
<div>
<p><span class="glyphicon glyphicon-comment"></span>发表评论</p>
<div>
<textarea name="comment" id="id_comment" cols="60" rows="10"></textarea>
</div>
<input type="button" class="btn btn-success" id="id_submit" value="提交评论">
<span style="color: red" id="id_error"></span> </div>
{% else %}
<ul class="nav navbar-nav navbar-link">
<li><a href="{% url 'reg' %}">注册</a></li>
<li><a href="{% url 'log' %}">登录</a></li>
</ul>
{% endif %} <!--根评论结束-->
{% endblock %} {% block js %}
<script>
$(function () {
// ajax向后端发送点赞点踩数
$('.action').click(function () {
// alert($(this).hasClass('diggit'))
// isUp是bool值
let isUp = $(this).hasClass('diggit');
// 将点击过的div存起来
let cilckDiv = $(this);
// 朝后端发送ajax请求
$.ajax({
url: '/up_or_down/',
type: 'post',
data: {
'article_id': '{{ article_obj.pk }}',
'is_up': isUp,
},
success: function (args) {
if (args.code == 1000) {
// 取得原来的数,并存给变量Num
let Num = cilckDiv.children().text();
// 点击过,该数加1,并修改过来,记得将数转成数字形,不然就是字符串拼接
cilckDiv.children().text(Number(Num) + 1);
$('#digg_tips').text(args.msg)
} else {
$('#digg_tips').html(args.msg)
}
}
})
});
// ajax向后端发送评论
// 全局设置一个parentId
let parentId = null;
console.log(parentId)
$('#id_submit').click(function () {
let dataComment = $('#id_comment').val();
// 判断当前评论是否是子评论,如果是,需要将我们之前手动渲染的@username去除
if (parentId){
// 找到\n对应的索引值,然后利用切片,但是切片顾头不顾尾,所以索引要加1
let indexNum = dataComment.indexOf('\n')+1
dataComment = dataComment.slice(indexNum) // 把换行符之前的切去
}
$.ajax({
url: '/comment/',
type: 'post',
data: {
'article_id': '{{ article_obj.pk }}',
'comment': dataComment,
// 前端parentId没有值,后端就存null
'parent_id':parentId
},
success: function (args) {
if (args.code = 1000) {
$('#id_error').text(args.msg);
// 情况评论框里的内容
$('#id_comment').val('');
// 临时渲染评论数
let userName = '{{ request.user.username }}'
let temp = `
<span style="color: red">${userName}</span>
<li class="list-group-item">
<div>
${dataComment}
</div>
</li>
`
// 将生成好的标签添加到ul标签内
$('#id_floor').append(temp);
// 提交后,需要将全局变量parentId恢复初始值
parentId = null;
}
}
})
});
// 点击根评论的回复点击事件
$('.reply').click(function () {
// 需要评论对应的评论人姓名 还需要评论的主键值
// 获取用户名
let commentUsername = $(this).attr('username');
// 获取主键值 直接修改parentId全局变量名
parentId = $(this).attr('comment_id'); // 拼接信息,聚焦事件传给评论框
$('#id_comment').val('@'+commentUsername+'\n').focus();
})
})
</script>
{% endblock %}
三 文章评论
'''
先写根评论
再写子评论 点击评论按钮需要将评论框里面的内容清空 根评论有两步渲染方式
1.DOM临时渲染
2.页面刷新render渲染 子评论
点击回复按钮发生了几件事
1.评论框自动聚焦
2.将回复按钮所在的哪一行评论人的姓名
@username
3.评论框内部自动换行 根评论子评论都是点击一个按钮朝后端提交数据的
parent_id
根评论子评论区别在哪?
parent_id
'''
urls.py
# 点赞点踩
url(r'^up_or_down/', views.up_or_down),
views.py
# 评论视图函数
def comment(request):
if request.is_ajax():
back_dic = {'code': 1000, 'msg': ''}
article_id = request.POST.get('article_id')
comment = request.POST.get('comment')
parent_id = request.POST.get('parent_id')
print(comment)
print(parent_id)
# 后端判断用户是否登录
if request.user.is_authenticated():
# 操作数据库,两张表,复习一下数据库的事务(两张表操作成功,才能成功)
from django.db import transaction
# 给文章表的评论数更新数据
with transaction.atomic():
from django.db.models import F
models.Article.objects.filter(pk=article_id).update(comment_num=(F('comment_num') + 1))
# 记录评论表
models.Comment.objects.create(user=request.user, article_id=article_id, content=comment,
parent_id=parent_id)
back_dic['msg'] = '评论成功'
else:
back_dic['code'] = '2000'
back_dic['msg'] = '请先登录'
return JsonResponse(back_dic)
article_detal.html
{% extends 'base.html' %} {% block css %} <style>
/* 点赞点踩样式 */
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 128px;
text-align: center;
margin-top: 10px;
} .diggit {
float: left;
width: 46px;
height: 52px;
background: url('/static/img/upup.gif') no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
} .buryit {
float: right;
margin-left: 20px;
width: 46px;
height: 52px;
background: url('/static/img/downdown.gif') no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
} .clear {
/*清除浮动*/
clear: both;
}
</style>
{% endblock %} {% block content %} <h1 class="text-center">{{ article_obj.title }}</h1> <div class="article_content" style="overflow: hidden">
{{ article_obj.content|safe }}
</div>
<!--点赞点踩开始-->
<div class="clearfix">
<div id="div_digg">
<div class="diggit action">
<span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
</div>
<div class="buryit action">
<span class="burynum" id="bury_count">{{ article_obj.down_num }}</span>
</div>
<div class="clear"></div>
<div class="diggword" id="digg_tips" style="color: red;"></div>
</div>
</div>
<!--点赞点踩结束--> <!--根评论开始-->
<!--评论楼开始-->
<ul class="list-group" id="id_floor">
{% for comment in comment_list %}
<span>#{{ forloop.counter }}楼</span>
<span>{{ comment.content_time|date:'Y-m-d h:i:s' }}</span>
<span>{{ comment.user.username }}</span> <li class="list-group-item">
<div>
{% if comment.parent_id %}
<!--字段parent是自关联字段,跨表还是跨的自己,在再通过外键user跨表查根评论用户-->
<p>@{{ comment.parent.user.username }}</p>
{% endif %}
<span>{{ comment.content }}</span>
<!--a标签自定义属性-->
<span class="pull-right"><a class="reply" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span>
</div> </li>
{% endfor %}
</ul>
<!--评论楼结束--> <!--前端判断是否登录-->
{% if request.user.is_authenticated %}
<div>
<p><span class="glyphicon glyphicon-comment"></span>发表评论</p>
<div>
<textarea name="comment" id="id_comment" cols="60" rows="10"></textarea>
</div>
<input type="button" class="btn btn-success" id="id_submit" value="提交评论">
<span style="color: red" id="id_error"></span> </div>
{% else %}
<ul class="nav navbar-nav navbar-link">
<li><a href="{% url 'reg' %}">注册</a></li>
<li><a href="{% url 'log' %}">登录</a></li>
</ul>
{% endif %} <!--根评论结束-->
{% endblock %} {% block js %}
<script>
$(function () {
// ajax向后端发送点赞点踩数
$('.action').click(function () {
// alert($(this).hasClass('diggit'))
// isUp是bool值
let isUp = $(this).hasClass('diggit');
// 将点击过的div存起来
let cilckDiv = $(this);
// 朝后端发送ajax请求
$.ajax({
url: '/up_or_down/',
type: 'post',
data: {
'article_id': '{{ article_obj.pk }}',
'is_up': isUp,
},
success: function (args) {
if (args.code == 1000) {
// 取得原来的数,并存给变量Num
let Num = cilckDiv.children().text();
// 点击过,该数加1,并修改过来,记得将数转成数字形,不然就是字符串拼接
cilckDiv.children().text(Number(Num) + 1);
$('#digg_tips').text(args.msg)
} else {
$('#digg_tips').html(args.msg)
}
}
})
});
// ajax向后端发送评论
// 全局设置一个parentId
let parentId = null;
console.log(parentId)
$('#id_submit').click(function () {
let dataComment = $('#id_comment').val();
// 判断当前评论是否是子评论,如果是,需要将我们之前手动渲染的@username去除
if (parentId){
// 找到\n对应的索引值,然后利用切片,但是切片顾头不顾尾,所以索引要加1
let indexNum = dataComment.indexOf('\n')+1
dataComment = dataComment.slice(indexNum) // 把换行符之前的切去
}
$.ajax({
url: '/comment/',
type: 'post',
data: {
'article_id': '{{ article_obj.pk }}',
'comment': dataComment,
// 前端parentId没有值,后端就存null
'parent_id':parentId
},
success: function (args) {
if (args.code = 1000) {
$('#id_error').text(args.msg);
// 情况评论框里的内容
$('#id_comment').val('');
// 临时渲染评论数
let userName = '{{ request.user.username }}'
let temp = `
<span style="color: red">${userName}</span>
<li class="list-group-item">
<div>
${dataComment}
</div>
</li>
`
// 将生成好的标签添加到ul标签内
$('#id_floor').append(temp);
// 提交后,需要将全局变量parentId恢复初始值
parentId = null;
}
}
})
});
// 点击根评论的回复点击事件
$('.reply').click(function () {
// 需要评论对应的评论人姓名 还需要评论的主键值
// 获取用户名
let commentUsername = $(this).attr('username');
// 获取主键值 直接修改parentId全局变量名
parentId = $(this).attr('comment_id'); // 拼接信息,聚焦事件传给评论框
$('#id_comment').val('@'+commentUsername+'\n').focus();
})
})
</script>
{% endblock %}
四 后台管理
新建页面,将文章个人文章展示出来,用到table标签,新增文章和其他功能的页面,导航条和左侧边栏一样,需要用到模版继承。
'''
当一个文件夹下文件比较多的时候,你还可以继续创建文件夹分类处理
templates文件夹
backend文件夹
应用1文件
应用2文件
'''
urls.py
# 后台管理
url(r'^backend/$', views.backend),
views.py
@login_required
def backend(request):
article_list = models.Article.objects.filter(blog=request.user.blog)
return render(request, 'backend/backend.html', locals())
base.html
用了 bootstrap中标签页,列表组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ request.user.username }}的后台管理</title>
<link rel="stylesheet" href="/static/bootstrap-3.4.1-dist/css/bootstrap.min.css">
<script src="/static/js/jQuery-3.6.1.js"></script>
<script src="/static/bootstrap-3.4.1-dist/js/bootstrap.min.js"></script>
</head>
<body>
<!--导航条-->
<div class="container">
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/home/">{{ request.user.blog.site_title }}</a>
</div> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
<li><a href="#">test</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">Dropdown <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li><a href="#">{{ request.user.username }}</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true"
aria-expanded="false">更多 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
<li><a href="#">修改头像</a></li>
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url 'logout' %}" id="logout">退出登录</a></li>
</ul>
<!-- Large modal -->
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog"
aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<h3 class="text-center">修改密码</h3>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="form-group">
<label for="">用户名</label>
<input type="text" disabled value="{{ request.user.username }}"
class="form-control">
</div>
<div class="form-group">
<label for="old_password">原密码</label>
<input type="password" id="old_password" class="form-control">
<span style="color: red" class="pull-right"></span>
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="password" id="new_password" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="password" id="confirm_password" class="form-control">
<span style="color: red" class="pull-right"></span>
</div>
<div class="form-group">
<button type="button" class="btn btn-default btn-sm"
data-dismiss="modal">
取消
</button>
<button type="button" class="btn btn-primary btn-sm"
id="set_commit">
确认修改
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
{% else %}
<li><a href="{% url 'reg' %}">注册</a></li>
<li><a href="{% url 'log' %}">登录</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
</div> <!--主板2,10-->
<div class="container">
<div class="row">
<!--左侧栏-->
<div class="col-md-2">
<div class="list-group">
<a href="#" class="list-group-item active">
操作
</a>
<a href="/add/article/" class="list-group-item">新建文章</a>
<a href="#" class="list-group-item">新建随笔</a>
<a href="#" class="list-group-item">回收站</a>
<a href="#" class="list-group-item">建议</a>
</div>
</div>
<!--右侧内容区-->
<div class="col-md-10">
<div> <!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab"
data-toggle="tab">文章</a></li>
<li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">随笔</a>
</li>
<li role="presentation"><a href="#messages" aria-controls="messages" role="tab" data-toggle="tab">文件</a>
</li>
<li role="presentation"><a href="#settings" aria-controls="settings" role="tab" data-toggle="tab">设置</a>
</li>
</ul> <!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="home">
{% block content %} {% endblock %}
</div>
<div role="tabpanel" class="tab-pane" id="profile">随笔页面</div>
<div role="tabpanel" class="tab-pane" id="messages">文件页面</div>
<div role="tabpanel" class="tab-pane" id="settings">设置</div>
</div> </div>
</div>
</div>
</div>
{% block js %} {% endblock %}
</body>
</html>
五 文章添加
属于后台管理页面中的功能
'''
有两个需要注意的问题
1.文章的简介
不能直接切去
应该先想办法获取到当前页面的文本内容之后截取150个文本字符 2.XSS攻击
针对支持用户直接编写html代码的网址
针对用户直接书写的script标签 需要处理
(1)注释标签内部的内容
(2)直接将script删除 如何解决?
针对1 后端通过正则表达式筛选
针对2 首先需要确定及获取script标签 beautifulsoup模块 bs4模块
专门用来帮你处理html页面的
该模块主要用于爬虫程序
pip3 install beautifulsoup4
''' # 模块使用
soup = BeautifulSoup(content, 'html.parser')
# 获取所有的标签
tags = soup.find_all()
for tag in tags:
# print(tag.name) # 获取页面所有的标签
# print(tags)
# 针对script标签 直接删除
if tag.name == 'script':
# 删除标签
tag.decompose() # 文章简介
# 1 先简单暴力的直接切去content 150个字符
# desc = content[0:150]
# 2 截取文本150个
desc = soup.text[0:150] '''
当你发现一个数据起来不是很方便的时候
可以考虑百度搜索有没有现成的模块帮你完成相应的功能
'''
urls.py
# 增加文章
url(r'^add/article/', views.add_article),
views.py
@login_required
def add_article(request):
if request.method == 'POST':
title = request.POST.get('title')
# content是含html标签的字符串
content = request.POST.get('content')
category_id = request.POST.get('category')
# print(category_id)
tag_id_list = request.POST.getlist('tag')
# print(tag_id_list)
# beautifulsoup模块使用
from bs4 import BeautifulSoup
# 'html.parser'说明是用的python内置标准库
soup = BeautifulSoup(content, 'html.parser')
print(soup, type(soup))
# tags是整个html标签页,含文本内容
tags = soup.find_all()
# 从tags中获取标签,只有标签
for tag in tags:
print(tag.name) # 获取页面所有的标签
# 防止xss攻击,删除script标签
if tag.name == 'script':
# 删除script标签,删除给script标签
tag.decompose()
# 文章简介
# 1 截取文本150个字符,之前的直接截取,是含有html标签的
desc = soup.text[0:150]
# 操作数据,记录文章
article_obj = models.Article.objects.create(
title=title,
desc=desc,
# 接受删除script标签的内容
content=str(soup),
category_id=category_id,
blog=request.user.blog
)
# 文章和标签的关系表 半自动是我们自己创建的 没法使用add set remove clear方法
# 自己去操作关系表 一次性可能需要创建多条数据 批量插入bulk_create()
article_obj_list = []
for i in tag_id_list:
# 用批量插入,是对象添加到列表,bulk_create(列表),是对象,不能用orm,不然要报错
tag_article_obj = models.ArticleToTag(article=article_obj, tag_id=i)
print(tag_article_obj)
article_obj_list.append(tag_article_obj)
# 批量插入,文章和标签的第三张表
print(article_obj_list)
try:
models.ArticleToTag.objects.bulk_create(article_obj_list)
except Exception as e:
print(e)
obj = models.ArticleToTag.objects.filter(pk=1).first()
print(obj)
return redirect('/backend/')
category_list = models.Category.objects.filter(blog=request.user.blog)
tag_list = models.Tag.objects.filter(blog=request.user.blog)
return render(request, 'backend/add_article.html', locals())
add_articel.html
{% extends 'backend/base.html' %} {% block content %}
<h3>添加文章</h3>
<form action="" method="post">
{% csrf_token %}
<p>标题</p>
<input type="text" name="title" id="id_title" class="form-control">
<div>
<p>内容</p>
<textarea name="content" id="editor_id" cols="40" rows="10"></textarea>
</div>
<p>分类</p>
<div>
<select name="category">
{% for category in category_list %}
<option value="{{ category.pk }}">{{ category.name }}</option>
{% endfor %}
</select>
</div>
<p>标签</p>
<div>
<select multiple name="tag">
{% for tag in tag_list %}
<option value="{{ tag.pk }}">{{ tag.name }}</option>
{% endfor %}
</select>
</div>
<input type="submit" class="btn btn-danger">
</form> {% endblock %} {% block js %}
{% load static %}
<script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
<script>
KindEditor.ready(function (K) {
window.editor = K.create('#editor_id', {
// 设置编辑 器大小
width: '100%',
height: '600px',
// 编辑器编辑按钮设置
items: [
'source', '|', 'undo', 'redo', '|', 'preview', 'print', 'template', 'code', 'cut', 'copy', 'paste',
'plainpaste', 'wordpaste', '|', 'justifyleft', 'justifycenter', 'justifyright',
'justifyfull', 'insertorderedlist', 'insertunorderedlist', 'indent', 'outdent', 'subscript',
'superscript', 'clearhtml', 'quickformat', 'selectall', '|', 'fullscreen', '/',
'formatblock', 'fontname', 'fontsize', '|', 'forecolor', 'hilitecolor', 'bold',
'italic', 'underline', 'strikethrough', 'lineheight', 'removeformat', '|', 'image', 'multiimage',
'flash', 'media', 'insertfile', 'table', 'hr', 'emoticons', 'baidumap', 'pagebreak',
'anchor', 'link', 'unlink', '|', 'about'
],
// 编辑器,左右固定,上下可以拖动,改变大小
resizeType: 1,
// 上传图片的后端提交路径
uploadJson: '/upload/',
// 上传图片、Flash、视音频、文件时,支持添加别的参数一并传到服务器。
extraFileUploadParams: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
}
});
});
</script>
{% endblock %}
六 kindeditor富文本编辑器
编辑器的种类很多,可以自己去网上搜索,
<a href="http://kindeditor.net/doc.php">kindeditor官网</a>
绑定textarea标签的id
<div>
<p>内容</p>
<textarea name="content" id="editor_id" cols="40" rows="10"></textarea>
</div>
七 编辑器如何上传图片
别人写好了接口 但是接口不是你自己的 需要手动去修改
在使用别人的框架或者模块的时候 出现了问题不要慌 看看文档就用对应的处理方法
urls.py
# 修改用户头像
url(r'^set/avatar/', views.set_avatar),
views.py
# 富文本编辑器上传文件视图函数
def upload(request):
'''
富文本编辑器,需要返回的格式数据
//成功时
{
"error" : 0,
"url" : "http://www.example.com/path/to/file.ext"
}
//失败时
{
"error" : 1,
"message" : "错误信息"
}
:param request:
:return:
'''
# 用户写文章上传的图片 也算静态资源 也应该放到media文件夹下
back_dic = {'error': 0} # 先定义给返回给前端编辑器的数据格式
if request.method == 'POST':
# 获取用户上传的图片对象
print(request.FILES) # {'imgFile': [<InMemoryUploadedFile: 1603159551507.mp4 (video/mp4)>]}
file_obj = request.FILES.get('imgFile')
print(file_obj, file_obj.name)
# 手动拼接存储文件的路径
from bbs01 import settings
import os
file_dir = os.path.join(settings.BASE_DIR, 'media', 'article')
# 优化操作,先判断当前文件夹是否存在 不存在 自动创建
if not os.path.isdir(file_dir):
os.mkdir(file_dir) # 创建一层目录结构 article
# 拼接图片的完整路径,如果要 唯一的文件名,可以用uuid模块
file_path = os.path.join(file_dir, file_obj.name)
print(file_path)
with open(file_path, 'wb') as f:
for line in file_obj:
f.write(line)
# 返回给编辑器的数据
back_dic['url'] = '/media/article/{}'.format(file_obj.name) return JsonResponse(back_dic)
八 修改用户头像
用的方式跟注册用户 头像一致,需要注意的是,修改后,在操作数据库的时候,
不用update方式修改数据库,obj.save(),第一种,要修改保存路径
urls.py
# 修改用户头像
url(r'^set/avatar/', views.set_avatar),
views.py
# 修改用户头像
@login_required
def set_avatar(request):
if request.method == 'POST':
file_obj = request.FILES.get('avatar')
# update的更新方式,会将userinfo表的avatar字段的路径改动了,所以跳转到home页面时,显示不了头像
# models.UserInfo.objects.filter(blog=request.user.blog).update(avatar=file_obj)
# 所以要用一下方式
# 1 自己手动添加avatar路径
# 2 新的更新方式
user_obj = request.user
user_obj.avatar = file_obj
user_obj.save()
return redirect('/home/')
blog = request.user.blog
username = request.user.username
return render(request, 'set_avatar.html', locals())
set_avatar.html
后期,建议用模态框的形式进行修改,或者另起一页
{% extends 'base.html' %} {% block content %}
<h3 class="text-center">修改头像</h3>
<!--form表单上传文件,一定要设置enctype-->
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>
原头像:
<img src="/media/{{ request.user.avatar }}" alt="">
</p>
<p>
新头像:
<label for="my_file">头像
{% load static %}
<img src="{% static 'img/defaut.png' %}" id="my_img" alt="" class="img-rounded"
style="margin-left: 10px">
</label>
<input type="file" id="my_file" name="avatar" style="display: none">
</p>
<input type="submit" class="btn btn-info">
</form>
{% endblock %} {% block js %}
<script>
$(function () {
$("#my_file").change(function () {
// 文件阅读器对象
// 1 先生成一个文件阅读器对象,内置对象
let myFileReaderObj = new FileReader();
// 2 获取用户上传的头像文件
let fileObj = $(this)[0].files[0];
// console.log(fileObj)
// 3 将文件对象交给阅读器对象读取
myFileReaderObj.readAsDataURL(fileObj) // 异步操作 IO操作
// 4 利用文件阅读器将文件展示到前端页面 修改src属性
// 等待文件阅读器加载完毕之后再执行
myFileReaderObj.onload = function () {
// 修改图像地址属性
$('#my_img').attr('src', myFileReaderObj.result)
}
})
})
</script>
{% endblock %}
九 BBS项目总结
在开发任意的web项目的时候,到了后期需要写的代码越来越少 都是用已经写好的url填写到a标签href属性完成跳转即可
主要功能总结
表设计
开发流程(粗糙流程 还可以细化)
注册功能 forms组件使用 头像动态展示 错误信息提示
登录功能 图片验证码 滑动验证码
首页展示 media配置 主动暴露任意资源接口
个人站点展示 侧边栏展示 侧边栏筛选 侧边栏inclusion_tag
文章详情页 点赞点踩 评论
后台管理
针对bbs需要掌握每一个功能的书写思路 内部逻辑 之后再去敲代码熟悉 找感觉
django项目(博客二)的更多相关文章
- Django搭建博客网站(二)
Django搭建自己的博客网站(二) 这里主要讲构建系统数据库Model. Django搭建博客网站(一) model 目前就只提供一个文章model和一个文章分类标签model,在post/mode ...
- Django 系列博客(二)
Django 系列博客(二) 前言 今天博客的内容为使用 Django 完成第一个 Django 页面,并进行一些简单页面的搭建和转跳. 命令行搭建 Django 项目 创建纯净虚拟环境 在上一篇博客 ...
- Django 系列博客(十二)
Django 系列博客(十二) 前言 本篇博客继续介绍 Django 中的查询,分别为聚合查询和分组查询,以及 F 和 Q 查询. 聚合查询 语法:aggregate(*args, **kwargs) ...
- FromBottomToTop第十二周项目博客
FromBottomToTop第十二周项目博客 本周项目计划 设计整体架构,收集素材,制作出静态界面部分 项目进展 已完成游戏整体架构设计 已完成游戏界面.背景音乐等素材的收集 正在进行静态界面部分的 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(二)
系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...
- Django搭建博客网站(四)
Django搭建博客网站(四) 最后一篇主要讲讲在后台文章编辑加入markdown,已经在文章详情页对markdown的解析. Django搭建博客网站(一) Django搭建博客网站(二) Djan ...
- Django搭建博客网站(三)
Django搭建博客网站(三) 第三篇主要记录view层的逻辑和template. Django搭建博客网站(一) Django搭建博客网站(二) 结构 网站结构决定我要实现什么view. 我主要要用 ...
- Django 系列博客(十)
Django 系列博客(十) 前言 本篇博客介绍在 Django 中如何对数据库进行增删查改,主要为对单表进行操作. ORM简介 查询数据层次图解:如果操作 mysql,ORM 是在 pymysql ...
- Django 系列博客(六)
Django 系列博客(六) 前言 本篇博客介绍 Django 中的路由控制部分,一个网络请求首先到达的就是路由这部分,经过路由与视图层的映射关系再执行相应的代码逻辑并将结果返回给客户端. Djang ...
- Django个人博客开发 | 前言
本渣渣不专注技术,只专注使用技术,不是一个资深的coder,是一个不折不扣的copier 1.前言 自学 Python,始于 Django 框架,Scrapy 框架,elasticsearch搜索引擎 ...
随机推荐
- qwb2023落荒而逃版
前言 qwb2023 .12.15 被打废了,N1决赛和qwb,有一个pwn可以做的但是已经在做misc看都不看--无语了. Pyjail ! It's myFILTER !!!|SOLVED|N1n ...
- Linux线程 | 创建 终止 回收 分离
一.线程简介 线程是参与系统调度的最小单位.它被包含在进程之中,是进程中的实际运行单位. 一个进程中可以创建多个线程,多个线程实现并发运行,每个线程执行不同的任务. 每个线程都有其对应的标识,称为线程 ...
- ARP(Address Resolution Protocol) Packet
Address Resolution Protocol The Address Resolution Protocol (ARP) is a communication protocol used f ...
- Windows下,SpringBoot JDBC无法连接的问题
问题症状 在Win7和Win10下启动时均会出现下面的错误,但是在OSX和Linux下没问题 com.mysql.jdbc.exceptions.jdbc4.CommunicationsExcepti ...
- 【OpenGL ES】绘制立方体
1 前言 本文主要介绍使用 OpenGL ES 绘制立方体,读者如果对 OpenGL ES 不太熟悉,请回顾以下内容: 绘制三角形 绘制彩色三角形 绘制正方形 绘制圆形 在绘制立方体的过程中, ...
- IDEA从o开始的一系列操作及修改配置-快捷键汇总
IDEA从o开始的一系列操作及修改配置-快捷键汇总 下载IDEA 启动idea 安装svn插件 功能快捷键 先设置提示快捷键(纯属个人喜好) 入门快捷键 查找 编辑 小功能 自动代码提示 自动导包 T ...
- Java设计模式-代理模式Proxy
介绍 代理模式是一种比较好理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能. ...
- Springboot集成Druid连接池并实现数据库密码加密
Druid介绍 Druid首先是一个数据库连接池.Druid是目前最好的数据库连接池,在功能.性能.扩展性方面,都超过其他数据库连接池,包括DBCP.C3P0.BoneCP.Proxool.JBoss ...
- Vue+SpringBoot+ElementUI实战学生管理系统-2.搭建Vue+elementUI脚手架
1.项目介绍 前一片介绍了项目的整体情况,这一篇开始搭建前端工程,需要的朋友可以拿去自己定制.:) 2.获取源码 源码是捐赠方式获取,详细请QQ联系我 :)! 3.项目截图 登录页 列表操作 动态图 ...
- spring boot与junit集成测试
先创建一个REST接口 package com.laoxu.gamedog.controller; import org.springframework.web.bind.annotation.Req ...