BBS项目

表设计

表设计

1、用户表

继承AbstractUser
扩展字段:
phone 电话号码
avatar 用户头像
create_time 创建时间
外键字段
一对一个人站点表

2、个人站点表

site_name 站点名称
site_title 站点标题
site_theme 站点样式

3、文章标签表

name 标签名
外键字段
一对多个人站点

4、文章分类表

name 分类名
外键字段
一对多个人站点

5、文章表

​```title ````文章标题
desc 文章简介
content 文章内容
create_time 发布时间
数据库字段设计优化(******)
(虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率)
up_num 点赞数
down_num 点踩数
comment_num 评论数
外键字段
一对多个人站点
多对多文章标签
一对多文章分类

6、点赞点踩表

记录哪个用户给哪篇文章点了赞还是点了踩
user ForeignKey(to="User")
article ForeignKey(to="Article")
is_up BooleanField()

7、文章评论表

记录哪个用户给哪篇文章写了哪些评论内容
user ForeignKey(to="User")
article ForeignKey(to="Article")
content CharField()
comment_time DateField()
# 自关联
parent ForeignKey(to="Comment",null=True)
# ORM专门提供的自关联写法
parent ForeignKey(to="self",null=True)
根评论子评论的概念
根评论就是直接评论当前发布的内容的
子评论是评论别人的评论
根评论与子评论是一对多的关系

数据库表创建

数据库选择:MySQL

新建一个bbs14数据库

django连接数据库设置

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'bbs14',
'USER':'root',
'PASSWORD':'123456',
'HOST':'127.0.0.1',
'PORT':3306,
'CHARSET':'utf8'
}
}

在一个init文件中写入以下代码

import pymysql
pymysql.install_as_MySQLdb()

创建表:

继承使用Auth表,扩展使用要先在配置文件setting.py中加入以下代码

AUTH_USER_MODEL = 'app01.UserInfo'
'应用名.表名'

先写普通字段,在写外键字段

数据库创建代码 models.py
# 用户表
class UserInfo(AbstractUser):
phone = models.BigIntegerField(verbose_name='手机号码',null=True)
avatar = models.FileField(verbose_name='头像',upload_to='avatar/',default='avatar/default.png')
create_time = models.DateField(auto_now_add=True)
# 一对一站点表
blog = models.OneToOneField(to='Blog',null=True)
# 个人站点
class Blog(models.Model):
site_name = models.CharField(verbose_name='站点名称',max_length=32)
site_title = models.CharField(verbose_name='站点标题',max_length=32)
# 简单模拟 存css或js的文件路径
site_theme = models.CharField(verbose_name='站点样式',max_length=64)
# 文章分类
class Category(models.Model):
name = models.CharField(verbose_name='文章分类',max_length=32)
# 一对多站点表
blog = models.ForeignKey(to='Blog',null=True)
# 文章标签
class Tag(models.Model):
name = models.CharField(verbose_name='文章标签',max_length=32)
# 一对多站点表
blog = models.ForeignKey(to='Blog',null=True)
# 文章表
class Article(models.Model):
title = models.CharField(verbose_name='文章标题',max_length=64)
desc = models.CharField(verbose_name='文章简介',max_length=256)
# 文章内容有很多,一般使用TextField
content = models.TextField(verbose_name='文章内容')
create_time = models.DateField(auto_now_add=True)
# 数据库优化字段
up_num = models.BigIntegerField(verbose_name='点赞数',default=0)
down_num = models.BigIntegerField(verbose_name='点踩数',default=0)
content_num = models.BigIntegerField(verbose_name='评论数',default=0)
# 一对多站点表
blog = models.ForeignKey(to='Blog',null=True)
# 一对多文章分类
category = models.ForeignKey(to='Category',null=True)
tags = models.ManyToManyField(to='Tag',
through='Article2Tag',
through_fields=('article','tag')
)
# 多对多文章标签
class Article2Tag(models.Model):
article = models.ForeignKey(to='Article')
tag = models.ForeignKey(to='Tag')
# 点赞点踩表
class UpAndDown(models.Model):
user = models.ForeignKey(to='UserInfo')
article = models.ForeignKey(to='Article')
is_up = models.BooleanField(verbose_name='是否点赞') # 存布尔值 0 1
**执行数据库迁移和记录命令**
python3 manage.py makemigrations 将操作记录记录到小本本上(migrations文件夹)
python3 manage.py migrate 将操作真正的同步到数据库中

注册功能

url配置路由

from app01 import views
url(r'^register',views.register)

在app01文件夹下,新建myforms.py文件夹

我们之前是直接在views.py中书写的forms组件代码,但是为了接耦合 ,应该将所有的forms组件代码单独写到一个地方

如果项目至始至终只用到一个forms组件那么你可以直接建一个py文件书写即可
myforms.py
但是如果项目需要使用多个forms组件,那么可以创建一个文件夹在文件夹内根据forms组件功能的不同创建不同的py文件,例如:
myforms文件夹
regform.py
loginform.py
userform.py
orderform.py

form组件代码
# 书写针对用户表的myforms组件代码
from django import forms
from app01 import models
class MyRegForm(forms.Form):
username = forms.CharField(label='用户名',min_length=3,max_length=8,
error_messages={
'required':'用户名不能为空',
'min_length':'用户名最少为3位',
'max_length':'用户名最大为8位'
},
# 添加样式bootstrap
widget = forms.widgets.TextInput(attrs={'class':'form-control'})
)
password = forms.CharField(label='密码',min_length=3,max_length=8,
error_messages={
'required':'密码不能为空',
'min_length':'密码最少为3位',
'max_length':'密码最大为8位'
},
# 添加样式bootstrap
widget=forms.widgets.PasswordInput(attrs={'class':'form-control'})
)
confirm_password = forms.CharField(label='确认密码',min_length=3,max_length=8,
error_messages={
'required':'确认密码不能为空',
'min_length':'确认密码最少为3位',
'max_length':'确认密码最大为8位'
},
# 添加样式bootstrap
widget=forms.widgets.PasswordInput(attrs={'class':'form-control'})
)
email = forms.EmailField(label='邮箱',
error_messages={
'required':'邮箱不能为空',
'invalid':'邮箱格式不正确'
},
widget=forms.widgets.EmailInput(attrs={'class':'form-control'})
)
# 钩子函数,校验用户名是否存在 局部钩子
def clean_username(self):
username = self.cleaned_data.get('username')
is_exists = models.UserInfo.objects.filter(username=username)
if is_exists:
self.add_error('username','用户名已存在')
return username
# 全局钩子 校验密码是否一致
def clean_password(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if not password == confirm_password:
self.add_error('confirm_password','两次密码不一致')
return self.cleaned_data
**配置静态文件**
STATICFILES_DIRS={
os.path.join(BASE_DIR,'static')
}
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
{% load static %}
<style>
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1 class="text-center">注册</h1>
<form id="myform"> <!--这里不用form表单提交数据 只是单纯的用一下form标签而已-->
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group">
<label for="{{ form.auto_id }}">{{ form.label }}</label>
{{ form }}
<span style="color: red" class="pull-right"></span>
</div>
{% endfor %}
<div class="form-group">
<label for="myfile">头像
{% load static %}
<img src="{% static 'img/default.png' %}" id='myimg' alt="" width="100" style="margin-left: 10px">
</label>
<input type="file" id="myfile" name="avatar" style="display: none" >
</div>
<input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit">
</form>
</div>
</div>
</div>
<script>
$("#myfile").change(function () {
// 文件阅读器对象
// 1 先生成一个文件阅读器对象
let myFileReaderObj = new FileReader();
// 2 获取用户上传的头像文件
let fileObj = $(this)[0].files[0];
// 3 将文件对象交给阅读器对象读取
myFileReaderObj.readAsDataURL(fileObj) // 异步操作 IO操作
// 4 利用文件阅读器将文件展示到前端页面 修改src属性
// 等待文件阅读器加载完毕之后再执行
myFileReaderObj.onload = function(){
$('#myimg').attr('src',myFileReaderObj.result)
}
})
$('#id_commit').click(function () {
// 发送ajax请求 我们发送的数据中即包含普通的键值也包含文件
let formDataObj = new FormData();
// 1.添加普通的键值对
// {#console.log($('#myform').serializeArray()) // [{},{},{},{},{}] 只包含普通键值对#}
$.each($('#myform').serializeArray(),function (index,obj) {
//{#console.log(index,obj)#} // obj = {}
formDataObj.append(obj.name,obj.value)
});
// 2.添加文件数据
formDataObj.append('avatar',$('#myfile')[0].files[0]);
// 3.发送ajax请求
$.ajax({
url:"",
type:'post',
data:formDataObj,
// 需要指定两个关键性的参数
contentType:false,
processData:false,
success:function (args) {
if (args.code==1000){
// 跳转到登陆页面
window.location.href = args.url
}else{
// 如何将对应的错误提示展示到对应的input框下面
// forms组件渲染的标签的id值都是 id_字段名
$.each(args.msg,function (index,obj) {
// {#console.log(index,obj) // username ["用户名不能为空"]#}
let targetId = '#id_' + index;
$(targetId).next().text(obj[0]).parent().addClass('has-error')
})
}
}
})
})
// 给所有的input框绑定获取焦点事件
$('input').focus(function () {
// 将input下面的span标签和input外面的div标签修改内容及属性
$(this).next().text('').parent().removeClass('has-error')
})
</script>
</body>
</html>
register的视图代码
# 注册页面
def register(request):
form_obj = MyRegForm()
if request.method == 'POST':
back_dic = {"code": 1000, 'msg': ''}
# 校验数据是否合法
form_obj = MyRegForm(request.POST)
# 判断数据是否合法
if form_obj.is_valid():
# print(form_obj.cleaned_data) # {'username': 'xh', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'}
clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量
# 将字典里面的confirm_password键值对删除
clean_data.pop('confirm_password') # {'username': 'xh', 'password': '123', 'email': '123@qq.com'}
# 用户头像
file_obj = request.FILES.get('avatar')
"""针对用户头像一定要判断是否传值 不能直接添加到字典里面去"""
if file_obj:
clean_data['avatar'] = file_obj
# 直接操作数据库保存数据
models.UserInfo.objects.create_user(**clean_data)
back_dic['url'] = '/login/'
else:
back_dic['code'] = 2000
back_dic['msg'] = form_obj.errors
return JsonResponse(back_dic)
return render(request,'register.html',locals())

登录功能

login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
{% load static %}
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1 class="text-center">登录</h1>
<div class="form-group">
<label for="username">用户名</label>
<input type="text" name="username" id="username" class="form-control">
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" name="password" id="password" class="form-control">
</div>
<div class="form-group">
<label for="">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" name="code" id="id_code" class="form-control">
</div>
<div class="col-md-6">
<img src="/get_code/" alt="" width="370" height="35" id="id_img">
</div>
</div>
</div>
<input type="button" class="btn btn-success" value="登录" id="id_commit">
<span style="color: red" id="error"></span>
</div>
</div>
</div>
<script>
$("#id_img").click(function () {
// 1 先获取标签之前的src
let oldVal = $(this).attr('src');
$(this).attr('src',oldVal += '?')
})
// 点击按钮发送ajax请求
$("#id_commit").click(function () {
$.ajax({
url:'',
type:'post',
data:{
'username':$('#username').val(),
'password':$('#password').val(),
'code':$('#id_code').val(),
// 自己结合自己需求 合理选择
'csrfmiddlewaretoken':'{{ csrf_token }}'
},
success:function (args) {
if (args.code == 1000){
// 跳转到首页
window.location.href = args.url
}else{
// 渲染错误信息
$('#error').text(args.msg)
}
}
})
})
</script>
</body>
</html>
login视图代码
# 登录页面
def login(request):
if request.method == 'POST':
back_dic = {'code':1000,'msg':''}
username = request.POST.get('username')
password = request.POST.get('password')
code = request.POST.get('code')
# 1 先校验验证码是否正确 统一转大写或者小写再比较
if request.session.get('code').upper() == code.upper():
# 2 校验用户名和密码是否正确
user_obj = auth.authenticate(request,username=username,password=password)
if user_obj:
# 保存用户状态
auth.login(request,user_obj)
back_dic['url'] = '/home/'
else:
back_dic['code'] = 2000
back_dic['msg'] = '用户名或密码错误'
else:
back_dic['code'] = 3000
back_dic['msg'] = '验证码错误'
return JsonResponse(back_dic)
return render(request,'login.html')
# 图片验证码
import random
def get_random():
return random.randint(0,255),random.randint(0,255),random.randint(0,255)
def get_code(request):
img_obj = Image.new('RGB',(430,35),get_random())
img_draw = ImageDraw.Draw(img_obj)
img_font = ImageFont.truetype('static1/font/111.ttf',30)
code = ''
for i in range(5):
random_upper = chr(random.randint(65,90))
random_lower = chr(random.randint(97,122))
random_int = str(random.randint(0,9))
tmp = random.choice([random_upper,random_lower,random_int])
img_draw.text((i*60+60,-2),tmp,get_random(),img_font)
code += tmp
print(code)
request.session['code'] = code
io_obj = BytesIO()
img_obj.save(io_obj,'png')
return HttpResponse(io_obj.getvalue())

关于图片验证码

图片验证码代码
"""
图片相关的模块
pip3 install pillow
"""
from PIL import Image,ImageDraw,ImageFont
"""
Image:生成图片
ImageDraw:能够在图片上乱涂乱画
ImageFont:控制字体样式
"""
from io import BytesIO,StringIO
"""
内存管理器模块
BytesIO:临时帮你存储数据 返回的时候数据是二进制
StringIO:临时帮你存储数据 返回的时候数据是字符串
"""
import random
def get_random():
return random.randint(0,255),random.randint(0,255),random.randint(0,255)
def get_code(request):
# 推导步骤1:直接获取后端现成的图片二进制数据发送给前端
# with open(r'static/img/111.jpg','rb') as f:
# data = f.read()
# return HttpResponse(data)
# 推导步骤2:利用pillow模块动态产生图片
# img_obj = Image.new('RGB',(430,35),'green')
# img_obj = Image.new('RGB',(430,35),get_random())
# # 先将图片对象保存起来
# with open('xxx.png','wb') as f:
# img_obj.save(f,'png')
# # 再将图片对象读取出来
# with open('xxx.png','rb') as f:
# data = f.read()
# return HttpResponse(data)
# 推导步骤3:文件存储繁琐IO操作效率低 借助于内存管理器模块
# img_obj = Image.new('RGB', (430, 35), get_random())
# io_obj = BytesIO() # 生成一个内存管理器对象 你可以看成是文件句柄
# img_obj.save(io_obj,'png')
# return HttpResponse(io_obj.getvalue()) # 从内存管理器中读取二进制的图片数据返回给前端
# 最终步骤4:写图片验证码
img_obj = Image.new('RGB', (430, 35), get_random())
img_draw = ImageDraw.Draw(img_obj) # 产生一个画笔对象
img_font = ImageFont.truetype('static/font/222.ttf',30) # 字体样式 大小
# 随机验证码 五位数的随机验证码 数字 小写字母 大写字母
code = ''
for i in range(5):
random_upper = chr(random.randint(65,90))
random_lower = chr(random.randint(97,122))
random_int = str(random.randint(0,9))
# 从上面三个里面随机选择一个
tmp = random.choice([random_lower,random_upper,random_int])
# 将产生的随机字符串写入到图片上
"""
为什么一个个写而不是生成好了之后再写
因为一个个写能够控制每个字体的间隙 而生成好之后再写的话
间隙就没法控制了
"""
img_draw.text((i*60+60,-2),tmp,get_random(),img_font)
# 拼接随机字符串
code += tmp
print(code)
# 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到
request.session['code'] = code
io_obj = BytesIO()
img_obj.save(io_obj,'png')
return HttpResponse(io_obj.getvalue())

首页

home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-inverse">
<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="#">BBS</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
<li><a href="#">文章</a></li>
<li 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="#">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="/set/avatar/">修改头像</a></li>
<li><a href="/backend/">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url 'logout' %}">退出登陆</a></li>
</ul>
<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">
<h1 class="text-center">修改密码</h1>
<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="">原密码</label>
<input type="password" id="id_old_password" class="form-control">
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="password" id="id_new_password" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="password" id="id_confirm_password" class="form-control">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button class="btn btn-primary" id="id_edit">修改</button>
<span style="color: red" id="password_error"></span>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
</li>
{% else %}
<li><a href="{% url 'reg' %}">注册</a></li>
<li><a href="{% url 'login' %}">登陆</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
<div class="col-md-2">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">广告</h3>
</div>
<div class="panel-body">
事成之后,上海别墅一套外加现金500万
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">小红书</h3>
</div>
<div class="panel-body">
抓紧联系:00000000
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title"></h3>
</div>
<div class="panel-body">
你想要的,这里都有
</div>
</div>
</div>
<div class="col-md-8">
<ul class="media-list">
{% for article_obj in article_queryset %}
<li class="media">
<h4 class="media-heading"><a href="/{{ article_obj.blog.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.blog.userinfo.avatar }}" alt="..." width="80">
</a>
</div>
<div class="media-body">
{{ article_obj.desc }}
</div>
{# Newbe36524 发布于 2020-06-11 09:04 评论(0)阅读(23)#}
<br>
<div>
<span><a href="/{{ article_obj.blog.userinfo.username}}/">{{ article_obj.blog.userinfo.username }}&nbsp;&nbsp;</a></span>
<span>发布于&nbsp;&nbsp;</span>
<span>{{ article_obj.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span>
<span><span class="glyphicon glyphicon-comment"></span>评论({{ article_obj.comment_num }})&nbsp;&nbsp;</span>
<span><span class="glyphicon glyphicon-thumbs-up"></span>点赞({{ article_obj.up_num }})</span>
</div>
</li>
<hr>
{% endfor %}
</ul>
</div>
<div class="col-md-2">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">重金求子</h3>
</div>
<div class="panel-body">
事成之后,上海别墅一套外加现金500万
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">千万大奖</h3>
</div>
<div class="panel-body">
抓紧联系:00000000
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">小红书</h3>
</div>
<div class="panel-body">
你想要的这里都有
</div>
</div>
</div>
</div>
<script>
$('#id_edit').click(function () {
$.ajax({
url:'/set_password/',
type:'post',
data:{
'old_password':$('#id_old_password').val(),
'new_password':$('#id_new_password').val(),
'confirm_password':$('#id_confirm_password').val(),
'csrfmiddlewaretoken':'{{ csrf_token }}'
},
success:function (args) {
if (args.code == 1000){
window.location.reload()
}else{
$("#password_error").text(args.msg)
}
}
})
})
</script>
</body>
</html>
修改密码、退出登录
修改密码与退出登录代码
def home(request):
# 查询本网站所有的文章数据展示的前端页面 这里可以使用分页器做分页 但是我不做了 你们自己课下加
article_queryset = models.Article.objects.all()
return render(request,'home.html',locals())
@login_required
def set_password(request):
if request.is_ajax():
back_dic = {'code':1000,'msg':''}
if request.method == 'POST':
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
confirm_password = request.POST.get('confirm_password')
is_right = request.user.check_password(old_password)
if is_right:
if new_password == confirm_password:
request.user.set_password(new_password)
request.user.save()
back_dic['msg'] = '修改成功'
else:
back_dic['code'] = 1001
back_dic['msg'] = '两次密码不一致'
else:
back_dic['code'] = 1002
back_dic['msg'] = '原密码错误'
return JsonResponse(back_dic)
@login_required
def logout(request):
auth.logout(request)
return redirect('/home/')

个人站点

准备继承模板

base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="/media/css/{{ blog.site_theme }}/">
{% block css %}
{% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse">
<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="#">博客 <span class="sr-only">(current)</span></a></li>
<li><a href="#">文章</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="#">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="/set/avatar/">修改头像</a></li>
<li><a href="/backend/">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url 'logout' %}">退出登陆</a></li>
</ul>
<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">
<h1 class="text-center">修改密码</h1>
<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="">原密码</label>
<input type="password" id="id_old_password" class="form-control">
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="password" id="id_new_password" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="password" id="id_confirm_password" class="form-control">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button class="btn btn-primary" id="id_edit">修改</button>
<span style="color: red" id="password_error"></span>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
</li>
{% else %}
<li><a href="{% url 'reg' %}">注册</a></li>
<li><a href="{% url 'login' %}">登陆</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
{% load mytag %}
{% left_menu username %}
</div>
<div class="col-md-9">
{% block content %}
{% endblock %}
</div>
</div>
</div>
{% block js %}
{% endblock %}
</body>
</html>
site.html
{% extends 'base.html' %}
{% block content %}
<ul class="media-list">
{% for article_obj in article_list %}
<li class="media">
<h4 class="media-heading"><a href="/{{ username }}/article/{{ article_obj.pk }}/">{{ article_obj.title }}</a></h4>
<div class="media-left">
<a href="#">
<img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="80">
</a>
</div>
<div class="media-body">
{{ article_obj.desc }}
</div>
{# posted @ 2015-10-20 01:02 武沛齐 阅读(68527) 评论(24) 推荐(58) 编辑)#}
<div class="pull-right">
<span>posted&nbsp;&nbsp;</span>
<span>@&nbsp;&nbsp;</span>
<span>{{ article_obj.create_time|date:'Y-m-d' }}&nbsp;&nbsp;</span>
<span>{{ article_obj.blog.userinfo.username }}&nbsp;&nbsp;</span>
<span><span class="glyphicon glyphicon-comment"></span>评论({{ article_obj.comment_num }})&nbsp;&nbsp;</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 %}
点击查看代码
def site(request,username,**kwargs):
"""
:param kwargs: 如果该参数有值 也就意味着需要对article_list做额外的筛选操作
"""
# 先校验当前用户名对应的个人站点是否存在
user_obj = models.UserInfo.objects.filter(username=username).first()
# 用户如果不存在应该返回一个404页面
if not user_obj:
return render(request,'errors.html')
blog = user_obj.blog
# 查询当前个人站点下的所有的文章
article_list = models.Article.objects.filter(blog=blog) # queryset对象 侧边栏的筛选其实就是对article_list再进一步筛选
if kwargs:
# print(kwargs) # {'condition': 'tag', 'param': '1'}
condition = kwargs.get('condition')
param = kwargs.get('param')
# 判断用户到底想按照哪个条件筛选数据
if condition == 'category':
article_list = article_list.filter(category_id=param)
elif condition == 'tag':
article_list = article_list.filter(tags__id=param)
else:
year,month = param.split('-') # 2020-11 [2020,11]
article_list = article_list.filter(create_time__year=year,create_time__month=month)
# # 1 查询当前用户所有的分类及分类下的文章数
# category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')
# # print(category_list) # <QuerySet [('april的分类一', 2), ('april的分类二', 1), ('april的分类三', 1)]>
#
# # 2 查询当前用户所有的标签及标签下的文章数
# tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')
# # print(tag_list) # <QuerySet [('xh的标签一', 1), ('xh的标签二', 1), ('xh的标签三', 2)]>
#
# # 3 按照年月统计所有的文章
# date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(count_num=Count('pk')).values_list('month','count_num')
# # print(date_list)
return render(request,'site.html',locals())

文章详情

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>{{ article_obj.title }}</h1>
<div class="article_content">
{{ 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>
{# 点赞点踩样式结束#}
{# 评论楼渲染开始#}
{# #3楼 2020-05-14 14:11 代码一字狂#}
<div>
<ul class="list-group">
{% for comment in comment_list %}
<li class="list-group-item">
<span>#{{ forloop.counter }}楼</span>
<span>{{ comment.comment_time|date:'Y-m-d h:i:s' }}</span>
<span>{{ comment.user.username }}</span>
<span><a class="pull-right reply" username="{{ comment.user.username }}" comment_id="{{ comment.pk }}">回复</a></span>
<div>
{# 判断当前评论是否是子评论 如果是需要渲染对应的评论人名#}
{% if comment.parent_id %}
<p>@{{ comment.parent.user.username }}</p>
{% endif %}
{{ comment.content }}
</div>
</li>
{% endfor %}
</ul>
</div>
{# 评论楼渲染结束#}
{# 文章评论样式开始 #}
{% 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>
<button class="btn btn-primary" id="id_submit">提交评论</button>
<span style="color: red" id="errors"></span>
</div>
{% else %}
<li><a href="{% url 'reg' %}">注册</a></li>
<li><a href="{% url 'login' %}">登陆</a></li>
{% endif %}
{# 文章评论样式结束 #}
{% endblock %}
{% block js %}
<script>
// 给所有的action类绑定事件
$('.action').click(function () {
{#alert($(this).hasClass('diggit'))#}
let isUp = $(this).hasClass('diggit');
let $div = $(this);
// 朝后端发送ajax请求
$.ajax({
url:'/up_or_down/',
type:'post',
data:{
'article_id':'{{ article_obj.pk }}',
'is_up':isUp,
'csrfmiddlewaretoken':'{{ csrf_token }}'
},
success:function (args) {
if(args.code == 1000){
$('#digg_tips').text(args.msg)
// 将前端的数字加一
// 先获取到之前的数字
let oldNum = $div.children().text(); // 文本 是字符类型
// 易错点
$div.children().text(Number(oldNum) + 1) // 字符串拼接了 1+1 = 11 11 + 1 = 111
}else{
$('#digg_tips').html(args.msg)
}
}
})
})
// 设置一个全局的parentID字段
let parentId = null;
// 用户点击评论按钮朝后端发送ajax请求
$('#id_submit').click(function () {
// 获取用户评论的内容
let conTent = $('#id_comment').val();
// 判断当前评论是否是子评论 如果是 需要将我们之前手动渲染的@username去除
if(parentId){
// 找到\n对应的索引 然后利用切片 但是前片顾头不顾尾 所以索引+1
let indexNum = conTent.indexOf('\n') + 1;
conTent = conTent.slice(indexNum) // 将indexNum之前的所有数据切除 只保留后面的部分
}
$.ajax({
url:'/comment/',
type:'post',
data:{
'article_id':'{{ article_obj.pk }}',
'content':conTent,
// 如果parantId没有值 那么就是null 后端存储null没有任何关系
'parent_id':parentId,
'csrfmiddlewaretoken':'{{ csrf_token }}'
},
success:function (args) {
if(args.code ==1000){
$('#error').text(args.msg)
// 将评论框里面的内容清空
$('#id_comment').val('');
// 临时渲染评论楼
let userName = '{{ request.user.username }}';
let temp = `
<li class="list-group-item">
<span>${userName}</span>
<span><a href="#" class="pull-right">回复</a></span>
<div>
${conTent}
</div>
</li>
`
// 将生成好的标签添加到ul标签内
$('.list-group').append(temp);
// 清空全局的parentId
parentId = null;
}
}
})
})
// 给回复按钮绑定点击事件
$('.reply').click(function () {
// 需要评论对应的评论人姓名 还需要评论的主键值
// 获取用户名
let commentUserName = $(this).attr('username');
// 获取主键值 直接修改全局
parentId = $(this).attr('comment_id');
// 拼接信息塞给评论框
$('#id_comment').val('@' + commentUserName + '\n').focus()
})
</script>
{% endblock %}
文章详情的视图代码
def article_detail(request,username,article_id):
"""
应该需要校验username和article_id是否存在,但是我们这里先只完成正确的情况
默认不会瞎搞
:param request:
:param username:
:param article_id:
:return:
"""
user_obj = models.UserInfo.objects.filter(username=username).first()
blog = user_obj.blog
# 先获取文章对象
article_obj = models.Article.objects.filter(pk=article_id,blog__userinfo__username=username).first()
if not article_obj:
return render(request,'errors.html')
# 获取当前 文章所有的评论内容
comment_list = models.Comment.objects.filter(article=article_obj)
return render(request,'article_detail.html',locals())
import json
from django.db.models import F
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')
is_up = request.POST.get('is_up')
# print(is_up,type(is_up)) # true <class 'str'>
is_up = json.loads(is_up) # 记得转换
# print(is_up, type(is_up)) # True <class 'bool'>
# 2 判断当前文章是否是当前用户自己写的 根据文章id查询文章对象 根据文章对象查作者 根request.user比对
article_obj = models.Article.objects.filter(pk=article_id).first()
if not article_obj.blog.userinfo == request.user:
# 3 校验当前用户是否已经点了 哪个地方记录了用户到底点没点
is_click = models.UpAndDown.objects.filter(user=request.user,article=article_obj)
if not is_click:
# 4 操作数据库 记录数据 要同步操作普通字段
# 判断当前用户点了赞还是踩 从而决定给哪个字段加一
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'] = 1001
back_dic['msg'] = '你已经点过了,不能再点了' # 这里你可以做的更加的详细 提示用户到底点了赞还是点了踩
else:
back_dic['code'] = 1002
back_dic['msg'] = '你个臭不要脸的!'
else:
back_dic['code'] = 1003
back_dic['msg'] = '请先<a href="/login/">登陆</a>'
return JsonResponse(back_dic)
from django.db import transaction
def comment(request):
# 自己也可以给自己的文章评论内容
if request.is_ajax():
back_dic = {'code': 1000, 'msg': ""}
if request.method == 'POST':
if request.user.is_authenticated():
article_id = request.POST.get('article_id')
content = request.POST.get("content")
parent_id = request.POST.get('parent_id')
# 直接操作评论表 存储数据 两张表
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_id=article_id,content=content,parent_id=parent_id)
back_dic['msg'] = '评论成功'
else:
back_dic['code'] = 1001
back_dic['msg'] = '用户未登陆'
return JsonResponse(back_dic)
from app01.utils.mypage import Pagination
@login_required
def backend(request):
# 获取当前用户对象所有的文章展示到页面
article_list = models.Article.objects.filter(blog=request.user.blog)
page_obj = Pagination(current_page=request.GET.get('page',1),all_count=article_list.count())
page_queryset = article_list[page_obj.start:page_obj.end]
return render(request,'backend/backend.html',locals())
修改头像
save.avatar.html
{% extends 'base.html' %}
{% block content %}
<h3 class="text-center">修改头像</h3>
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>
原头像:
<img src="/media/{{ request.user.avatar }}" alt="">
</p>
<p>
<label for="myfile">新头像:
{% load static %}
<img src="{% static 'img/default.png' %}" id='myimg' alt="" width="100" style="margin-left: 10px">
</label>
<input type="file" id="myfile" name="avatar" style="display: none" >
</p>
<input type="submit" class="btn btn-info">
</form>
{% endblock %}
{% block js %}
<script>
$("#myfile").change(function () {
// 文件阅读器对象
// 1 先生成一个文件阅读器对象
let myFileReaderObj = new FileReader();
// 2 获取用户上传的头像文件
let fileObj = $(this)[0].files[0];
// 3 将文件对象交给阅读器对象读取
myFileReaderObj.readAsDataURL(fileObj) // 异步操作 IO操作
// 4 利用文件阅读器将文件展示到前端页面 修改src属性
// 等待文件阅读器加载完毕之后再执行
myFileReaderObj.onload = function(){
$('#myimg').attr('src',myFileReaderObj.result)
}
})
</script>
{% endblock %}
修改头像视图
@login_required
def set_avatar(request):
if request.method == 'POST':
file_obj = request.FILES.get('avatar')
# models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=file_obj) # 不会再自动加avatar前缀
# 1.自己手动加前缀
# 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())
posted on   AprilX  阅读(46)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示