BBS项目
目录
项目开发基本流程
1.需求分析
2.架构设计
3.分组开发
4.提交测试
5.交付上线
项目流程
表准备
1.用户表(继承auth表)
"""
普通字段:
phone(电话)
avatar(头像,用文件来存)
create_time(注册时间)
外键字段:
与个人站点一对一,外键在用户表
site = models.OneTooneField(to='Site',on_delete=models.CASCADE,null=true)
"""
2.个人站点表
"""
普通字段:
site_name(站点名称)
site_title(站点标题)
site_them(站点样式)
外键字段:
没有外键字段,与文章表是一对多,多的是文章,与分类是一对多,多的是分类,与标签是一对多,多的是标签
"""
3.文章表
"""
普通字段:
title(文章标题)
desc(文章简介)
content(文章内容)
create_time(创建时间)
优化查询字段:
不与表关联,但是在表增加的时候,先给这几个字段进行添加
comment_num(评论数)
up_num(点赞数)
donw_num(点踩数)
外键字段:
与个人站点是一对多,多的是文章
与分类是一对多,多的是文章
与标签是多对多,所以要创建第三张表
"""
4.分类表
"""
普通字段:
name(分类名称)
外键字段:
与个人站点是一对多,多的是分类,外键在这里
"""
5.标签表
"""
普通字段:
name(标签名称)
外键字段:
与个人站点是一对多,多的是标签,外键在这里
"""
特殊表:文章表与标签表的多对多表
6.点赞点踩表(哪个用户给哪篇文章点了赞还是点了踩)
"""
外键字段:
user(用户的id,一对多)
article(文章的id,一对多)
普通字段:
up_down(存布尔值,赞就是1,踩就是0)
"""
7.评论表(哪个用户给哪篇文章进行了评论)
"""
外键字段:
user(用户id)
article(文章id)
普通字段:
comment(评论内容)
comment_time(评论时间)
特殊资管(判断是否是子评论):
子关联
"""
修改django的时间:
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False
表创建
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
"""用户表"""
phone = models.CharField(max_length=32, verbose_name='电话号码', null=True)
avatar = models.FileField(upload_to='avatar/', default='avatar/default.jpg', verbose_name='用户头像')
register_time = models.DateTimeField(auto_now_add=True, verbose_name='注册时间')
# 用户表与个人站点是一对一
site = models.OneToOneField(to='Site', on_delete=models.CASCADE, null=True)
class Site(models.Model):
"""个人站点表"""
site_name = models.CharField(max_length=32, verbose_name='站点名称')
site_title = models.CharField(max_length=32, verbose_name='站点标题')
site_theme = models.CharField(max_length=255, verbose_name='站点样式', null=True)
class Article(models.Model):
"""文章表"""
title = models.CharField(max_length=64, verbose_name='文章标题')
desc = models.CharField(max_length=255, verbose_name='文章简介')
content = models.TextField(verbose_name='文章内容')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='文章创建时间')
# 特殊字段,优化字段
comment_num = models.BigIntegerField(verbose_name='文章评论数', null=True)
up_num = models.BigIntegerField(verbose_name='文章点赞数', null=True)
down_num = models.BigIntegerField(verbose_name='文章点踩数', null=True)
# 个人站点与文章是一对多
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
# 文章表与分类表是一对多
category = models.ForeignKey(to='Category', on_delete=models.CASCADE, null=True)
# 文章与标签表是多对多关系
tags = models.ManyToManyField(to='Tag', through='ArticleToTag', # 指定第三张表
through_fields=('article', 'tag'), # 指定第三表里面的字段
null=True)
class Category(models.Model):
"""文章分类表"""
name = models.CharField(max_length=32, verbose_name='分类名称', null=True)
# 文章分类与个人站点是一对多关系
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
class Tag(models.Model):
"""文章标签表"""
name = models.CharField(max_length=32, verbose_name='标签名称', null=True)
# 文章标签与个人站点是一对多关系
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
class ArticleToTag(models.Model):
"""文章与标签的多对多表"""
article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True)
tag = models.ForeignKey(to='Tag', on_delete=models.CASCADE, null=True)
class UpAndDown(models.Model):
"""点赞点踩表"""
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True)
article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True)
is_up = models.BooleanField(verbose_name='点赞点踩')
class Comment(models.Model):
"""文章评论表"""
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True)
article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True)
content = models.TextField(verbose_name='评论内容')
comment_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
# 自关联
parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True)
注册功能
"后端"
1.先写一个froms组件,用来进行注册数据的初步校验
关键点:froms最好单独放一个py文件中,这样看起来更清晰,组件的钩子函数可以校验用户名是否已存在,输入的两次密码是否一致,添加到错误信息里面返回到前端
2.注册代码
"""
1.生成一个空的froms组件对象,返回一个注册的页面
2.判断是否是post请求,是的话,把request.POST获的数据直接交给froms组件检验,产生一个froms组件对象跟空的froms组件对象变量名一致
3.判断是否通过forms组件校验,froms_obj.is_valid()
4.把通过的信息字典赋值,将通过的信息里面把确认密码弹出
5.获取头像对象,将头像对象添加到通过的froms信息中
6.直接在用户表中创建数据,要用的create_user(直接将clean_date打散)这样以什么等于什么添加数据
7.返回JsonResponse字典被前端数据接口
注意:成功添加数据,给字典里面添加登录的url,添加失败将错误的信息添加到前端
"""
"""前端"""
1.用froms组件渲染前端页面,添加一个获取用户头像的文件上输入,提交按照绑定click点击事件
前端渲染代码:
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h2 class="text-center" style="color:red;">用户注册</h2>
//用from包起来是为了获取for循环的id,label和span标签
<form id="form">
{% csrf_token %}
# forms组件的模板语法
{% for foo in from_obj %}
<div class="form-group">
// foo.auto_id可以获得当前循环出来的ID值,绑定聚焦
<label for="{{ foo.auto_id }}">{{ foo.label }}</label>
{{ foo }}
<span class="pull-right" style="color: red"></span>
</div>
{% endfor %}
# 这里是用户头像的输入,将label直接跟input绑定,可以直接点就触发input
<div class="form-group">
<label for="myfile">用户头像
# img标签默认显示一张图片
<img src="/static/default.jpg" id="myimg" alt="" width="200" height="200">
</label>
# 获取用户的头像,但是隐藏input框,可以直接只显示默认的头像
<input type="file" id="myfile" style="display: none">
</div>
<input type="button" id="subBtn" class="btn btn-primary btn-block" value="注册">
</form>
</div>
</div>
</div>
"script写入内容"
<script>
//文本域变化,主要功能是实时展示用户上传的头像
$('#myfile').change(function () {
# 产生一个文件阅读器对象,给了变量名
let myfileReaderobj = new FileReader()
# 获取当前标签对象的的值
let fileobj = this.files[0]
# 文件阅读器对象添加当前标签对象
myfileReaderobj.readAsDataURL(fileobj)
# 异步操作,所以等待阅读器对象添加了文件对象,然后直接改变img标签(id的是myimg)的src数据,这里attr就是获取某个对应的属性
myfileReaderobj.onload = function () {
$('#myimg').attr('src', myfileReaderobj.result)
}
})
//提交绑定点击事件
$('#subBtn').click(function () {
# 产生一个空对象
let myFormDataobj = new FormData();
# 对form标签进行循环拿值,添加到对象中,然后发送ajax消息
$.each($('#form').serializeArray(), function (index, dataobj) {
myFormDataobj.append(dataobj.name, dataobj.value)
})
myFormDataobj.append('avatar', $('#myfile')[0].files[0])
$.ajax({
url: '',
type: 'post',
data: myFormDataobj,
contentType: false,
processData: false,
success: function (args) {
if (args.code === 10000){
window.location.href=args.url
# 如果code不是10000,代表是错误信息,循环拿出来错误信息,拼id也就是拼出来label的id,然后下一个标签也就是span标签进行渲染,然后给父标签也就是div标签添加一个错误的classs,变成红色
}else{
let dataobj = args.msg;
$.each(dataobj,function (k,msgArray) {
let Eleid = '#id_'+k
$(Eleid).next().text(msgArray[0]).parent().addClass('has-error')
})
}
}
})
})
# 当用户点击input数据框的时候,聚焦取消掉div的错误class属性
$('input').focus(function (){
$(this).next().text('').parent().removeClass('has-error')
})
</script>
登录功能
登录基本校验
1.先做一个返回给前端的back_dict的字典
2.返回一个登录的html,登录不需要使用froms组件校验
3.判断是POST的请求,获取后端数据,注意判断用户账号密码是否相同使用使用auth模块
import django.contrib import auth
返回的是一个用户对象,判断用户对象是否有值,来确认是否登录成功
user_obj =auth.authenticate(request,username=username,password=password)
登录成功要添加session auth.login(request,user_obj)
"""验证码校验代码"""
需要模块:
from PIL import Image,ImageFont,ImageDraw
需要两个函数
def get_random():
return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
这个函数用来返回RGB的三个数字,0,255
# 产生图片验证码的函数
def get_code_func(request):
# 先产生一张图片
img_obj = Image.new('RGB', (360, 35), get_random())
# 产生画笔对象
draw_obj = ImageDraw.Draw(img_obj)
# 确定字体样式
font_obj = ImageFont.truetype('static/font/111.ttf', 35)
# 验证码代码
code = ''
for i in range(5):
choice_upper = chr(random.randint(65, 90))
choice_lower = chr(random.randint(97, 122))
choice_num = str(random.randint(1, 9))
choice_code = random.choice([choice_upper, choice_lower, choice_num])
# 将随机验证码写到图片上面
draw_obj.text((i * 45 + 45, 0), choice_code, font=font_obj)
code += choice_code # 字符串拼接
# 保存验证码,方便后续比对
request.session['code'] = code
io_obj = BytesIO()
img_obj.save(io_obj, 'png')
return HttpResponse(io_obj.getvalue())
前端代码:
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
{% csrf_token %}
<h2 class="text-center" style="color:red;">用户登录</h2>
<div class="form-group">
<label for="name">用户名</label>
<input type="text" class="form-control" id="name">
</div>
<div class="form-group">
<label for="pwd">密码</label>
<input type="password" class="form-control" id="pwd">
</div>
<div class="form-group">
<label for="code">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" id="code" class="form-control">
</div>
<div class="col-md-6">
<img id="d1" src="/get_code/" alt="" width="360" height="35">
</div>
</div>
</div>
<input id="logtton" type="button" class="btn btn-primary btn-block" value="登录">
</div>
</div>
</div>
"script"
# 点击事件针对的是验证码的图片
$('#d1').click(function () {
# 获取验证码img标签的src属性,将属性的src后面添加?号,这样不可以局部进行刷新
let oldSrc = $(this).attr('src')
$(this).attr('src', oldSrc + '?')
})
# 提交的点击事件
$('#logtton').click(function () {
$.ajax({
url: '',
type: 'post',
data: {
'username': $('#name').val(), 'password': $('#pwd').val(),
'code': $('#code').val(), 'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (args) {
# 如果返回的是10000,那就直接跳转url
if (args.code === 10000) {
window.location.href = args.url
# 如果不是直接将错误信息弹出来
} else {
alert(args.msg)
}
}
})
})
media媒体目录如何暴露
"settings里面设置"
MEDIA_ROOT = 'os.path.join(BASE_DIR,'media')' 这句话表示用户上传的图片都用上传到这个目录下面,会自动创建该目录,我们注册的头像models里面写的avatar目录也会自动创建到该目录下
img标签也可以直接访问路由,所以我们可以暴露media的接口
# 自定义暴露资源接口
导模块
from django.views.static import serve
from django.conf import settings
固定代码:
re_path('^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})
这样media目录下面所有的资源都可以暴露出去
admin数据添加
首先要创建一个超级管理员账号,否则是无法进入管理员后台的
pycharm最上面 Tools里面找到Run manage.py Task或者使用快捷键ctrl+alt+r
输入指令 createsuperuser 就可以了
"""准备工作"""
想要在后台显示表的中文名字可以在表中添加一个
class Meta:
verbose_name = '表名'
方便查看是哪个表可以在表中添加
def __str__(self):
return f'表名对象:{self.name}'
打开admin后台,按照对应关系进行表的添加,先创建没有外键的表数据
主页功能
1.搭建主页架构
1.1上面是导航栏
要注意:登录成功导航栏显示的是用户的头像,用户名,以及一个下拉框用来修改用户信息
1.2左侧,右侧是广告位 class="col-md-2"
1.3中间展示网站所有文章,文章展示要展示文章的作者,作者的头像,创建时间,点赞,点踩以及评论,都是根据文章对象列表for循环来获取
2.后台路由以及视图函数搭建
返回主页的html要把所有文章获取反悔到前端,做一个分页器
"""后端代码"""
def home_func(request):
# 查询所有用户编写的文章
Article_querset = models.Article.objects.all()
# 下面的是分页器
page_obj = mypage.Pagination(current_page=request.GET.get('page'),
all_count=Article_querset.count())
page_queryset = Article_querset[page_obj.start:page_obj.end]
return render(request, 'homePage.html', locals())
"""前端核心代码"""
这里是导航栏用户是否登录,根据是否登录展示不同的导航条
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
# 将用户头像做成圆形展示
<li><img src="/media/{{ request.user.avatar }}/" style="width: 50px;height: 50px;border-radius: 50%" alt=""></li>
<li><a href="/{{ request.user.username }}/">{{ 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="#myModal">修改密码</a></li>
<li><a href="#">更换头像</a></li>
<li><a href="/backend/">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="/logout/">注销登录</a></li>
</ul>
</li>
{% else %}
<li><a href="/register/">注册</a></li>
<li><a href="/login/">登录</a></li>
{% endif %}
</ul>
这里是展示网页的所有文章
<div class="col-md-8">
{% for article_obj in page_queryset %}
<div class="media">
# 这里是拼的是文章标题的路由,可以直接点击标题查看文章
<h4 class="media-heading"><a href="/{{ article_obj.site.userinfo.username }}/article/{{ article_obj.pk }}/">{{ article_obj.title }}</a></h4>
<div class="media-left">
<a href="">
# 这里展示的是作者的头像
<img class="media-object" src="/media/{{ article_obj.site.userinfo.avatar }}" alt="..." width="80">
</a>
</div>
<div class="media-body">
{{ article_obj.desc }}
</div>
<div>
# 这里用来展示用户的名字,a标签可以让用户直接点击名字进入个人站点
<span><a href="/{{article_obj.site.userinfo.username}}/">{{article_obj.site.userinfo.username}} </a></span>
# 这里用来展示文章的实际,用过滤器展示时间
<span>{{ article_obj.create_time|date:'Y-m-d H:i:s' }} </span>
# 这里用来展示点赞数
<span class="glyphicon glyphicon-thumbs-up">{{ article_obj.up_num}} </span>
# 这里用来展示点踩数
<span class="glyphicon glyphicon-thumbs-down ">{{ article_obj.down_num }} </span>
# 这里用来展示评论数
<span class="glyphicon glyphicon-phone">{{ article_obj.comment_num }} </span>
</div>
</div>
<hr>
{% endfor %}
# 这里是分页器
<div class="text-center">
{{ page_obj.page_html|safe }}
</div>
</div>
修改密码功能
后端代码:
1.修改密码需要登录的用户才可以执行,所以我们需要倒一个装饰器
from django.contrib.auth.decorators import login_required
2.给函数添加装饰器,直接获取POST请求,拿到该用户的老密码和新密码以及新密码的二次确认,首先判断新密码以及新密码的二次确认是否一致,如果不一致的话,添加返回给前端的字段,修改code,在判断旧密码是否正确,需要使用request.user.check_password
前端代码:
制作一个模态框,弹出来
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span>
</button>
<h4 class="modal-title text-center" id="myModalLabel">修改密码</h4>
{% csrf_token %}
</div>
<div class="modal-body">
<div class="form-group">
<label for="username">用户</label>
<input type="text" value="{{ request.user.username }}" id="username" class="form-control" disabled>
</div>
<div class="form-group">
<label for="old_pwd">旧密码</label>
<input type="password" id="old_pwd" class="form-control">
</div>
<div class="form-group">
<label for="new_pwd">新密码</label>
<input type="password" id="new_pwd" class="form-control">
</div>
<div class="form-group">
<label for="confirm_pwd">确认新密码</label>
<input type="password" id="confirm_pwd" class="form-control">
</div>
</div>
<div class="modal-footer">
<span id="d2" style="color: red"></span>
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="d1">确认修改</button>
</div>
</div>
</div>
</div>
"ajax代码就是修改代码提交按钮的一个点击事件,将数据发过去就可以,如果修改成功就直接跳转登录页面,如果朽败,就直接在span标签里面添加错误的内容"
$('#d1').click(function () {
$.ajax({
url: '/set_pwd/',
type: 'post',
data: {
'old_pwd': $('#old_pwd').val(), 'new_pwd': $('#new_pwd').val(), 'confirm_pwd': $('#confirm_pwd').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (args) {
if (args.code === 10000) {
window.location.href = args.url
} else {
$('#d2').text(args.msg)
}
}
})
})
注销登录功能
直接写一个注销登录的路由以及视图函数,发送请求即可
后端代码比较简单:
@login_required
def login_out(request):
auth.logout(request)
return redirect('/home/')
个人站点主页面
后端代码:
1.个人站点,那么路由就要能够区分站点是哪个用户的,所以路由使用转换器
path('<str:username>',views.site_func)
2.个人站点需要展示个人的信息,那么就要进行orm操作
2.1我们首先产生一个个人站点的对象,这里我们使用的用户名就是站点的名称,直接用站点名称就可以获取站点对象
2.1通过站点对象我们可以获取到站点下面的所有文章,站点表和文章表是一对多的关系,外键在文章表,所以我们需要进行反向查询
2.2通过站点对象我们获取站点下面的全部分类,以及每个分类的个数,这个时候我们就需要使用分组查询,首先获取个人站点下的所有分类对象,然后进行分组,关键字annotate分组获取每个分组的文章数量(获取的分组对象,直接Count('article__pk')),意思是直接反向查询表名小写,统计id的个数
2.3通过站点对象我们获取站点下面的全部标签,以及每个标签的数量,那个我们获取的是标签对象,标签与文章是多对多,外键字段在文章表,所以还是(Count('article__pk'))获取每个标签下面的文章数量
3.我们需要按照用户点的三个分类来进行筛选,标签或分类或时间来进行筛选,这个时候我们就要用到两个路由用同一个视图函数,写法:re_path('^(?P<username>w+)/(?P<condition>category|tag|archive)/(?P<params>.?)/',) 这句话表示,先匹配字母,在匹配tag这三个中的一个,最后非贪婪匹配一次,这样就有了站点名称,三个分类中的其中一个,最后是三个分类对应的id,时间是对应的时间
4.函数的参数需要多写一个**kwargs,这样可以接收多为的关键字参数,我们判断这个参数有没有值来确定是不是三个分类的查询,如果有值的话,那么我们就获取里面第二个参数以及第三个参数,通过第二个参数来判断是查询哪个筛选分类(分类/标签/时间),再通过第三个参数的id值来进行文章的二次筛选,我们获取的文章是个queryset对象,那么就可以再次进行筛选就可以啦,需要从文章表进入对应的筛选分类表中按照id值进行筛选,注意时间我们用切割,然后筛选使用create_time__year=year,create_time__month==month,双下方法来进行判断
"""
代码展示:我们把查询分类以及分类的数量做成了自定义的模板语法,所以不在函数中
"""
def user_site(request, username, **kwargs):
site_obj = models.Site.objects.filter(site_name=username).first()
if not site_obj:
return render(request, 'errore.html')
# 查询个人站点下所有的文章
article_queryset = models.Article.objects.filter(site=site_obj)
if kwargs:
# kwargs里面是拿到的多余关键字匹配,condition是category|tag|archive中的一个
condition = kwargs.get('condition')
# params是拿到的category|tag|archive所对应需要的id值或者时间值
params = kwargs.get('params')
# 按照关键的查询,如果是按照分组查的,那么继续按照分组的id筛选就可以
if condition == 'category':
article_queryset = article_queryset.filter(category_id=params)
# 如果是按照标签筛选,那么直接再次按照标签id筛选就行
elif condition == 'tag':
article_queryset = article_queryset.filter(tags__id=params)
elif condition == 'archive':
year, month = params.split('-')
article_queryset = article_queryset.filter(create_time__year=year, create_time__month=month)
else:
return render(request, 'errore.html')
# print(date_queryset)
return render(request, 'sitePage.html', locals())
侧边栏封装(选做)
个人站点的侧边栏要用于好几个页面,所以我们决定把侧边栏封装起来做一个inclusion_tag
首先要在应用文件下面创建一个templatetags目录,下面随便创建一个文件,在文件里面将侧边栏的相关代码封装成一个函数,函数需要有固定的写法
先倒模块
from django import template
register = template.Library()
自定义inclusion_tag(局部的html代码)
@register.inclusion_tag('html文件名',name='调用函数的名字')
def index():
侧边栏相关代码
文章详情数据添加
这一步主要目的是为下一步文章内容的大概做准备,在后台进行文章内容的数据
文章内容展示页面之文章内容展示
这一步就需要重新再开一个路由,然后在个人站点页面的文章标签a标签以及主页文章标签的a标签里面添加这个路由
后端代码:
1.先确定路由,我们要展示某个人的文章,那么我们就需要获取这个人是谁,添加一个article这样更容易分辨,最后我们需要添加文章的id,这样才能知道是哪个人的哪篇文章
2.代码首先要获取这个人的个人站点,因为我们有用户名,可以直接通过用户名获取这个用户对应的站点,然后我们获取这个人站点下面的文章,筛选出来对应文章id的文章对象,返回到前端
下面的功能是按照后续的补充
"""下面是按照后续功能补充"""
3.获取该文章对应的评论内容,回想评论表里面字段是用户的id和文章的id还有评论文荣那么我们文章对象查评论是反向查询,直接点小写表名就可以(或者我们直接获取评论对象,这个时候就是正向查询,直接article=我们获取的文章对象就可以),因为评论可能是多个,所以不需要点frist
前端代码:
导航条和侧边栏继续使用个人站点的内容
右侧用来展示具体的文章内容,直接用article_obj文章对象来点出来标题,文章内容(这里要注意,文章内容是html格式,所以我们文章内容需要使用过滤器|safe)来展示文章内容
文章内容展示页面之点赞点踩展示
点赞点踩样式我们自己不会写,所以我们需要复制博客园代码
{# 点赞点踩样式#}
<div class="clearfix">
<div id="div_digg">
<div class="diggit up_or_down">
# 我们展示点赞的数值,可以直接用文章对象点up_num,这个一个普通切记这是一个普通字段
<span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
</div>
<div class="buryit up_or_down">
# 我们展示点赞的数值,可以直接用文章对象点down_num,这个一个普通切记这是一个普通字段
<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>
{# 点赞点踩样式#}
样式要添加CS样式,所以一定要复制
{% block css %}
<style>
#div_digg {
float: right;
margin-bottom: 10px;
margin-right: 30px;
font-size: 12px;
width: 125px;
text-align: center;
margin-top: 10px;
}
.diggit {
float: left;
width: 46px;
height: 52px;
background: url(/static/upup.gif) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
#div_digg .diggnum {
line-height: 1.5em !important;
}
.diggnum {
font-size: 14px;
color: #075db3;
font-family: Verdana;
}
.buryit {
float: right;
margin-left: 20px;
width: 46px;
height: 52px;
background: url(/static/downdown.gif) no-repeat;
text-align: center;
cursor: pointer;
margin-top: 2px;
padding-top: 5px;
}
#div_digg .burynum {
line-height: 1.5em !important;
}
.burynum {
font-size: 14px;
color: #075db3;
font-family: Verdana;
}
.clear {
clear: both;
}
.diggword {
margin-top: 5px;
margin-left: 0;
font-size: 12px;
color: #808080;
}
</style>
{% endblock %}
展示完点赞点踩,我们就需要做一个点赞点踩的动态展示
点赞点踩之动态展示
首先前端我们就需要触发一个点击事件,然后发送ajax请求,我们可以给不管还是点赞还是点踩给一个相同的class属性,这样我们之间选择这个class属性触发点击事件,然后判断这个class属性里面是否还有一个diggit的class属性,如果有的话,那么这个就是点赞,如果没有就是点踩,我们一并把这些发到我们后端去
# 触发一个点击事件
$('.up_or_down').click(function () {
// 这句话表示我们将this也就是这个class的对象保存下来
let is_this = $(this);
let is_up = $(this).hasClass('diggit') //判断点击的对象是否含有diggit的classs属性
# 发送ajax请求,,因为是给那边文章点赞的,所以我们需要发送一个文章的id,然后我们想is_up不管是正确或者错误都发送到后端去,然后后端根据正确或者错误来判断点赞还是点踩
$.ajax({
url: '/up_or_down/',
type: 'post',
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'article_id': '{{ article_obj.pk }}',
'is_up': is_up,
},
success: function (args) {
//这里就是接收的前端字典,如果点赞或者点踩成功,我们就要实时渲染点赞点踩的数字加上去
if (args.code === 10000) {
//我们之间用这个class对象来点他的第一个子标签,就是span标签,然后获取这个子标签里面的值,进行加1,要注意获取的值是前端的字符类型,所以我们要进行一个数组的转换,然后将加1的数字写到span标签的值里面 is_this.children().first().text(Number(is_this.children().first().text()) + 1)
// 如果不是点赞点踩成功,我们直接在下面渲染msg内容就行,因为有一个标签的返回,所以我们之间用html来渲染msg的信息
} else {
$('#digg_tips').html(args.msg)
}
}
})
})
"""后端代码"""
1.首先要写一个点赞点踩的路由,用来接收前端发来的ajax请求
2.函数代码首先要获取前端发来的信息,request.POST来获取点赞文章的id以及是点赞还是不是点赞,
3.我们点赞点踩要进行3个判断
3.1判断当前用户是否已经登录,如果已经登录
3.2判断是不是自己给自己点赞,如果不是
3.3判断是不是已经点过赞或者踩了,如果不是
4.我们用json把后端传过来的true或者false转正我们python里面的,反序列化用loads
5.然后将下面对数据的操操作加入到事务中,事务需要倒一个模块from django.db import transaction,我们用局部的事务,with transaction.atomic(),下面进行数据库的操作
6.先判断是点赞还是点踩
6.1如果是点赞也就是is_up是true,那么就是点赞,我们需要在文章表里面的点赞数量进行+1,这个时候我们就要使用到F查询,先取出来up_num对应的值,然后+1,最后在赋值给up_num这个字段
6.2如果是点踩,也就是is_up不是true,直接用else,然后重复点赞的操作,只是点赞字段的+1变更为点踩字段的+1
不管是点赞还是点踩,我们对文章表进行操作以后就要对点赞点踩表进行创建操作了,点赞点踩表的字段中有用户的id,文章的id,点赞还是点踩(这个字段用布尔值,他会根据true或者false)来存1或者0,所以直接将is_up这个字段存入即可,这样我们正确的思路就完成了
3.3.1如果已经点过赞或者踩,就需要返回给前端字典msg添加一个不能够重复点赞或者点踩
3.2.1如果是自己给自己点赞,msg里面就添加不能够给自己点赞或者点踩
3.1.1如果没有登录需要返回给前端一个登录的提示,这个时候就需要写一个前端的a标签返回过去,对应的路由写上登录的路由
7.将字典返回给前端
文章内容展示页面之评论展示
(展示的数据从文章内容函数获取)
这个时候,我们就要在文章展示的函数中先获取一下该文章对应的评论内容
通过文章展示的函数获取到的该文章对应的评论,那么我们进行for循环就可以获取到单个的文章对象,文章对象点击comment就可以获取到具体的评论内容,我们需要搭建一个评论的样式,这个时候就需要参考博客园的评论有什么了:标题是评论列表,然后下面首先显示第一行是#几楼,评论的时间,评论的用户,在下面是评论的内容,然后是一条横线隔开
"""前端样式代码"""
{# 评论楼开始#}
<div class="conmmtn_list">
<ul class="list-group">
{% for comment_obj in comment_obj_list %}
<li class="list-group-item list-group-item-success">
<span>#{{ forloop.counter }}楼</span>
<span>{{ comment_obj.comment_time|date:'Y-m-d H:i' }}</span>
<span><a href="/{{ comment_obj.user.username }}/">{{ comment_obj.user.username }}</a></span>
<div class="pull-right">
<span><a href="#comment" class="son_comment" username="{{ comment_obj.user.username }}"
parent_id="{{comment_obj.pk }}">回复 </a></span>
<span><a href="#">引用</a></span>
</div>
{% if comment_obj.parent_id %}
<p>@{{comment_obj.parent.user.username}}
<span>{{ comment_obj.parent.content }}</span></p>
<p>{{ comment_obj.content }}</p>
{% else %}
<p>{{ comment_obj.content }}</p>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
{# 评论楼结束#}
文章内容展示页面之评论实时展示
我们前端就需要做一个评论的输入框,样式参考博客园:标题是发表评论,下面是一个文本框输入,然后是一个提交按钮,但是有一个注意的点,评论只有登录的用户才能评论,如果没有登录,那么展示一个注册和登录的按钮,所以需要添加一个if的判断,这里先不考虑子评论的问题
"""前端代码"""
{# 文章评论样式开始#}
<div class="comment_area">
# 这里是用来判断用户是否登录的
{% if request.user.is_authenticated %}
<p><span class="glyphicon glyphicon-bullhorn">发表评论</span></p>
<textarea name="" id="comment" cols="30" rows="10" class="form-control"></textarea>
<button class="btn btn-primary" id="commBtn">提交评论</button>
{% else %}
<p>
<a href="/register/">注册</a>
<a href="/login/">登录</a>
</p>
{% endif %}
</div>
</div>
{# 文章评论样式结束#}
用户写了评论以后,点击提交评论按钮,就需要发送ajax请求到后端,那么就需要给button按钮绑定一个点击事件来发送请求
"""js代码"""
// 定义一个子评论id,先为空
let parent_id = null
$('#commBtn').click(function () {
//保存用户评论内容,将用户评论内容赋值
let commentMsg = $('#comment').val()
// 这里是用来获取当前评论登录用户也就是发表评论用户的名字
let Username = '{{ request.user.username }}'
//点击发表评论触发点击事件就发送ajax请求
$.ajax({
url: '/comment/',
type: 'post',
// 1.发送的内容包含评论的内容,评论文章的id,先不考虑子评论
//2.考虑子评论的问题,那么我们就要获取根评论的id一起发到后端,这个时候子评论id可能是空,也能是下面回复按钮触发以后给子评论id修改为当前回复的跟评论的id
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'comment': commentMsg,
'article_id': '{{ article_obj.pk }}',
'parent_id':parent_id
},
//如果评论成功,那么我们需要先清楚评论框里面的内容,然后临时渲染到评论楼中
success: function (args) {
if (args.code === 10000) {
$('#comment').val('');
//临时渲染样式并添加到评论楼中
let tempComment = `
<li class="list-group-item list-group-item-success">
<span><a href="/${Username}/">${Username}</a></span>
<p>${commentMsg}</p>
</li>`
$('.list-group').append(tempComment);
// 渲染结束记得把parent_id清掉,否则不刷新页面,就会一直是子评论
parent_id = null
}
}
})
})
"""子评论点击事件"""
//回复按钮点击事件
$('.son_comment').click(function () {
//获取当前所要回复的人的名字,我们之间在回复按钮里面添加username的属性
let comment_user = $(this).attr('username')
//然后在评论文本中渲染@ + 回复的id,加换行符然后聚焦
$('#comment').val('@' + comment_user + '\n').focus();
// 给parent_id赋值当前所回复评论的id,也是添加的parent属性
parent_id = $(this).attr('parent_id')
})
"""后端代码"""
'1.先不考虑子评论的问题,那么ajax请求发送过来,我们之间获取评论的内容,评论的文章id以及发表评论的用户,那么我们之间将这些信息写入数据库即可,注意首先我们要给文章的评论数量+1,然后返回评论成功即可'
2.考虑子评论的问题,那么我们就要后端多获取一个parent_id,首先判断这个变量名是否有值,如果有值,证明是子评论,那么我们就需要对评论内容进行切割,因为子评论的评论内容是(@ 回复名字 \n 评论内容),如果没有值,则证明是跟评论,那么不需要对评论内容做切割
3.不管是跟评论还是子评论,我们把评论内容做了处理以后,我们就可以直接写入数据,首先就要给该文章对应的评论数加1,同样还是用F方法+1,然后我们操作评论表,评论表里面的字段有,用户的id,文章的id,评论的内容,评论的时间是自动获取,以及是否是子评论(这个值默认可以为空),所以我们之间创建数据即可,parent_id的值写入,如果没有值就还是空,如果有值,则证明是子评论,最后返回给前端字典即可
# 评论
def comment_func(request):
back_dict = {'code': 10000, 'msg': ''}
if request.method == 'POST':
# 获取ajax发过来的信息
article_id = request.POST.get('article_id')
comment = request.POST.get('comment')
parent_id = request.POST.get('parent_id')
# 判断parent_id是否有值,有的话对文章内容做切割
if parent_id:
*_, comment = comment.split('\n', maxsplit=1)
article_obj = models.Article.objects.filter(pk=article_id).first()
with transaction.atomic():
models.Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1)
models.Comment.objects.create(user=request.user, article=article_obj, content=comment,parent_id=parent_id)
return JsonResponse(back_dict)
后台管理页面之主页
后台管理主要用于文章的增删改查,所以后台主页我们是要在多个页面使用的,我们直接搭建一个模板页面使用其他页面来继承该页面,那么我们搭建一个模板页面,导航条还是首页的导航条,侧边栏我们使用一个侧边栏展示后台管理,文章、分类、标签的添加,另一侧是一个动态的框
主页继承我们的模板页面,但是文章那一栏需要展示我们所有的文章
后端代码:
1.因为我们只有登录的用户才能进入后台管理,所以我们路由直接写死路由就可以啦,写一个后台管理的路由 backend
2.因为我们要才后台管理主页展示个人站点的所有主页,所以我们函数需要获取该站点对应的所有的文章,视图函数我们直接使用登录校验的装饰器,这样我们直接可以获得当前登录的用户,那么我们可以根据当前登录的用户就可以获取个人站点对象,然后通过个人站点对象获取该站点下面的所有的文章
3.分页机可以考虑使用或者不使用
@login_required
def backend_func(request):
# 后端要文章标签,我们就要发送文章对象
site_obj = models.Site.objects.filter(site_name=request.user.username).first()
article_queryset = models.Article.objects.filter(site__site_name=request.user.username)
# 分页器
page_obj = mypage.Pagination(current_page=request.GET.get('page'),
all_count=article_queryset.count())
page_queryset = article_queryset[page_obj.start:page_obj.end]
return render(request, 'backend/backendPage.html', locals())
"前端代码"
<div>
<h2 class="text-center" style="color:red;">文章列表</h2>
//下面是写一个列表,里面展示文章标题,发布时间,评论数,点赞数,点踩数,两个操作
<table class="table table-striped table-hover">
<thead>
<tr>
<th>标题</th>
<th>发布时间</th>
<th>评论数</th>
<th>点赞数</th>
<th>点踩数</th>
<th>操作</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for article_obj in page_queryset %}
<tr>
<td>
<a href="/{{ article_obj.site.userinfo.username }}/article/{{ article_obj.pk }}">{{ article_obj.title }}</a>
</td>
<td>{{ article_obj.create_time|date:'Y-m-d H:i:s' }}</td>
<td>{{ article_obj.comment_num }}</td>
<td>{{ article_obj.up_num }}</td>
<td>{{ article_obj.down_num }}</td>
<td><a href="/editArticle/{{ article_obj.pk }}/">编辑</a></td>
<td><a href="#" class="del_article" article_pk="{{ article_obj.pk }}">删除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="text-center">
{{ page_obj.page_html|safe }}
</div>
</div>
后台管理页面之添加文章
后端设计
1.路由设计,直接写死一个添加文章的路由就可以,视图函数,返回一个添加文章的页面
2.get请求返回一个添加文章的html页面,继承后台管理的模板页面
3.post请求获取前端传来的数据
视图函数:
1.需要拿到个人站点以及个人站点的分类还有个人站点的标签,这样用户前端展示
2.接收post请求,拿到文章的内容,标题,分类id以及标签id(这里注意标签id可能是多个,所以我们需要使用.getlist,复习知识(get是拿到内容列表里面的第一个,getlist是拿到内容列表里面的全部)),
3.需要进行处理,倒木块,from bs4 import BeautifulSoup
4.然后就再写入数据库,添加标签的时候可以使用批量添加
5.最后重定向我们后台管理的主页
代码:
@login_required
def add_article(request):
# 拿到个人站点
site_obj = models.Site.objects.filter(site_name=request.user.username).first()
# 拿到个人站点对应的全部分类
category_queryset = models.Category.objects.filter(site=site_obj)
# 拿到个人站点下全部的标签
tags_queryset = models.Tag.objects.filter(site=site_obj)
if request.method == 'POST':
# 拿到文章内容
article_content = request.POST.get('article_content')
# 拿到文章标题
title = request.POST.get('title')
# 拿到分组id
category_id = request.POST.get('category_id')
# 拿到标签的id因为是多个,所以是列表
tags_id_list = request.POST.getlist('tags_id_list')
# 对文章内容进行处理,使用lxml编辑器产生一个对象
soup = BeautifulSoup(article_content, 'lxml')
# 拿到文章的全部标签
tags = soup.find_all()
# 去掉标签里面的script属性
for tag in tags:
if tag.name == 'script':
tag.decompost() # 移除script标签
with transaction.atomic():
article_obj = models.Article.objects.create(content=str(soup), # 拿到的内容就是处理后的内容
desc=soup.text[0:150], # 这样拿到的soup就是处理后没有script标签的
title=title, site=site_obj,
category_id=category_id)
tag_obj_list = []
for tag_id in tags_id_list:
# 使用批量添加
articletotag_obj = models.ArticleToTag(article=article_obj, tag_id=tag_id)
tag_obj_list.append(articletotag_obj)
models.ArticleToTag.objects.bulk_create(tag_obj_list)
return redirect('/backend/')
return render(request, 'backend/add_article.html', locals())
"""前端页面"""
<h1></h1>
<p style="background-color: #5bc0de;color:white;padding: 5px">添加文章</p>
<form action="" method="post">
{% csrf_token %}
<div class="form-group">标题
<input type="text" class="form-control" name="title">
</div>
<div class="form-group">文章内容
<textarea name="article_content" id="editor_id" cols="30" rows="10" class="form-control"></textarea>
</div>
<div class="form-group"><p style="background-color: #c0a16b;color: white;padding: 6px">个人分类</p>
{% for category_obj in category_queryset %}
<input type="radio" name="category_id" value="{{ category_obj.pk }}">{{ category_obj.name }}
{% endfor %}
</div>
<div class="form-group"><p style="background-color: #2e6da4;color: white;padding: 6px">标签选择</p>
{% for tag_obj in tags_queryset %}
<input type="checkbox" name="tags_id_list" value="{{ tag_obj.pk }}">{{ tag_obj.name }}
{% endfor %}
</div>
<input type="submit" class="btn btn-primary btn-block">
</form>
js固定写法:
//这里是用来引入富文本编辑器的
<script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
<script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
<script>
KindEditor.ready(function(K) {
window.editor = K.create('#editor_id',{
width:'1260px',
height:'400px',
resizeType:0,
// 上传的路由
uploadJson : '/addImg/',
extraFileUploadParams : {
csrfmiddlewaretoken:'{{ csrf_token }}'
}
});
});
</script>
添加文章之添加图片补充
后端设计
1.路由设计,给一个接收图片的路由以及视图函数
2.接收前端发来的图片,request.Files.get('imgFile')
3.我们需要自己拼接一个路径用来存储用户上传的图片,所以这里需要在media目录下拼接一个article文章的目录,然后再目录下面写入我们接收到的图片数据写入
4.需要注意,为了防止用户上传的图片名字一样,我们可以使用hashlib将文件名进行加密
代码展示:
@login_required
def add_img(request):
# 这个字典是富文本编辑器所要求的
back_dict = {'error': 0}
if request.method == 'POST':
# 获取到图片
img_file = request.FILES.get('imgFile')
# 将图片的名字进行切割,然后拿到该图片的后缀
*_, file_end = img_file.name.rsplit('.', maxsplit=1)
# 拼接文章图片存放的路径
article_path = os.path.join(settings.BASE_DIR, 'media', 'article')
if not os.path.exists(article_path): # 判断是否有这个路径
os.mkdir(article_path)
# 这里使用hashlib将图片名字进行加密,然后再讲文件的后缀拼上
md5 = hashlib.md5()
md5.update(img_file.name.encode())
img_hash = md5.hexdigest()
img_hash = img_hash + '.' + file_end
img_path = os.path.join(article_path, f'{img_hash}')
# 最后直接写入就可以啦
with open(img_path, 'wb') as f:
for line in img_file:
f.write(line)
back_dict['url'] = 'media/article/%s' % img_hash
return JsonResponse(back_dict)
后台管理页面之编辑文章
后端设计:
1.路由设计,编辑文章,我们要有文章的id,所以设计为path('editArticle/<int:article_id>/
2.视图函数,根据文章的id获取到该文章,拿到该站点对象这样可以拿到该站点下面的所有分类和标签
3.post请求接收编辑的后的,跟文章添加类似
4.更新操作需要注意,我们直接在文章列表可以更新,但是在文章和标签的多对多列表需要先清除掉之前的绑定关系,然后再重新批量创建即可
代码展示:
@login_required
def edit_article_func(request, article_id):
article_obj = models.Article.objects.filter(pk=article_id).first()
site_obj = models.Site.objects.filter(site_name=request.user.username).first()
# 拿到个人站点对应的全部分类
category_queryset = models.Category.objects.filter(site=site_obj)
# 拿到个人站点下全部的标签
tags_queryset = models.Tag.objects.filter(site=site_obj)
if request.method == 'POST':
article_content = request.POST.get('article_content')
category_id = request.POST.get('category_id')
tags_id_list = request.POST.getlist('tags_id_list')
soup = BeautifulSoup(article_content, 'lxml')
title = request.POST.get('title')
tags = soup.find_all()
for tag in tags:
if tag.name == 'script':
tag.decompost()
with transaction.atomic():
models.Article.objects.filter(pk=article_id).update(content=str(soup), title=title, desc=soup.text[0:150],
category_id=category_id, site=site_obj)
# 删除多对多表中所有的绑定关系,然后再重新绑定
models.ArticleToTag.objects.filter(article_id=article_id).delete()
tag_obj_list = []
for tag_id in tags_id_list:
# 使用批量添加
articletotag_obj = models.ArticleToTag(article=article_obj, tag_id=tag_id)
tag_obj_list.append(articletotag_obj)
models.ArticleToTag.objects.bulk_create(tag_obj_list)
return redirect('/backend/')
return render(request, 'backend/edit_articlePage.html', locals())
"""前端代码展示(这里我们是用(form表单来发的请求)"""
<h1></h1>
<p style="background-color: #5bc0de;color:white;padding: 5px">编辑文章</p>
<form action="" method="post">
{% csrf_token %}
<div class="form-group">标题
<input type="text" class="form-control" name="title" value="{{ article_obj.title }}">
</div>
<div class="form-group">文章内容
<textarea name="article_content" id="editor_id" cols="30" rows="10" class="form-control">{{ article_obj.content }}</textarea>
</div>
<div class="form-group"><p style="background-color: #c0a16b;color: white;padding: 6px">个人分类</p>
{% for category_obj in category_queryset %}
//这里是用来判断如果该文章的分类就是循环得到的分类,那么直接加一个默认值
{% if article_obj.category == category_obj %}
<input type="radio" name="category_id" value="{{ category_obj.pk }}" checked>{{ category_obj.name }}
{% else %}
<input type="radio" name="category_id" value="{{ category_obj.pk }}">{{ category_obj.name }}
{% endif %}
{% endfor %}
</div>
<div class="form-group"><p style="background-color: #2e6da4;color: white;padding: 6px">标签选择</p>
{% for tag_obj in tags_queryset %}
//这里是用来判断如果循环的标签在该文章的标签里面,那么添加一个默认值
{% if tag_obj in article_obj.tags.all %}
<input type="checkbox" name="tags_id_list" value="{{ tag_obj.pk }}" checked>{{ tag_obj.name }}
{% else %}
<input type="checkbox" name="tags_id_list" value="{{ tag_obj.pk }}">{{ tag_obj.name }}
{% endif %}
{% endfor %}
</div>
<input type="submit" class="btn btn-primary btn-block" value="修改">
</form>
后台管理页面之删除文章
我们这里要使用一个插件,所以用的点击事件触发ajax
后端设计:
1.直接写死一个删除的路由以及对应的视图函数
2.获取ajax发送的删除文章的id,然后删除,清楚文章与标签的绑定关系
# 删除文章
def delete_article_func(request):
back_dict = {'code': 10000, 'msg': ''}
article_id = request.POST.get('article_id')
models.Article.objects.filter(pk=article_id).delete()
# 删除文章和标签的绑定关系
models.ArticleToTag.objects.filter(pk=article_id).delete()
back_dict['mas'] = '文章删除成功'
return JsonResponse(back_dict)
"前端代码"
//这里是点击删除框的cnd
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
<script>
$('.del_article').click(function () {
let del_btn = $(this);
swal({
title: "是否确认删除?",
text: "如果删除那么文章就无法被找回!",
icon: "warning",
buttons: true,
dangerMode: true,
})
.then((willDelete) => {
if (willDelete) {
$.ajax({
url: '/delArticle/',
type:'post',
data: {'csrfmiddlewaretoken':'{{ csrf_token }}','article_id':del_btn.attr('article_pk')},
success:function (args) {
if (args.code === 10000){
swal(args.msg, {
icon: "success",
});
// 刷新页面
del_btn.parent().parent().remove()
}
}
})
} else {
swal("不敢删就不要乱点!真是的!!!");
}
});
})
</script>
后台管理页面之修改头像
后端设计:
1.路由还是写死的,对应一个修改头像的路由
2.获取更换后的图片对象
3.拿到用户对象
4.用户对象.avatar为我们修改后的图片对象,然后保存
5.重定向到后台管理页面
代码展示:
# 修改头像
@login_required
def set_avatar_func(request):
if request.method == 'POST':
avatar_file = request.FILES.get('avatar_file')
print(avatar_file)
user_obj = models.UserInfo.objects.filter(pk=request.user.pk).first()
user_obj.avatar = avatar_file
user_obj.save()
return redirect('/backend/')
"""前端设计是一个模态框的修改"""
{#修改头像模态框开始#}
<!-- Button trigger modal -->
<!-- Modal -->
<div class="modal fade" id="myavatar" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span>
</button>
<h4 class="modal-title text-center" id="myModalLabel">修改密码</h4>
{% csrf_token %}
</div>
<form action="/setAvatar/" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal-body">
<div class="form-group">
<label for="username">用户</label>
<input type="text" value="{{ request.user.username }}" id="username" class="form-control" disabled>
</div>
<div class="form-group">
<label for="old_pwd">原头像</label>
<img src="/media/{{ request.user.avatar }}/" alt="" style="width: 80px" >
</div>
<div class="form-group">
<label for="avatar_file">新头像
<img src="/static/default.jpg/" alt="" style="width: 80px" id="myimg">
<span style="color: red">点击图片修改头像</span>
</label>
<input type="file" id="avatar_file" style="display: none" name="avatar_file">
</div>
</div>
<div class="modal-footer">
<span id="d2" style="color: red"></span>
<input type="submit" value="提交" class="btn btn-primary btn-block">
</div>
</form>
</div>
</div>
</div>
{#修改头像模态框结束#}
//文本域变化实时展示头像
//input标签文本域变化
$('#avatar_file').change(function () {
// 产生一个文件阅读器对象
let myfileReaderobj = new FileReader()
let fileobj = this.files[0]
myfileReaderobj.readAsDataURL(fileobj)
myfileReaderobj.onload = function () {
//选择img标签修改src的属性
$('#myimg').attr('src',myfileReaderobj.result)
}
})
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)