BBS升级版
BBS项目
项目的前期准备
1.django2.2 创建一个django目录
(需要配置环境变量和数据库)
'DIRS': [os.path.join(BASE_DIR, 'templates'), ]
2.数据库准备 我这里创建了一个库名为sql0107的数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'sql0107',
'USER': 'root',
'PASSWORD': '222',
'HOST': '127.0.0.1',
'PORT': 3306,
'CHARSET': 'utf8',
}
}
第一步是创建表
表分析
先确定表的数量 再确定表的基础字段 最后确定表的外键字段
1.用户表
2.个人站点表
3.文章表
4.文章分类表
5.文章标签表
6.点赞点踩表
7.文章评论表
基础字段分析
'''下列表字段设计仅供参考 你可以有更多的想法'''
用户表
替换auth_user表并扩展额外的字段
电话号码、头像、注册时间
个人站点表
站点名称(jason\lili\kevin)
站点标题(努力奋斗去他妹的)
站点样式(css文件)
文章表
文章标题
文章简介
文章内容
发布时间
文章分类表
分类名称
文章标签表
标签名称
点赞点踩表:记录哪个用户给哪篇文章点了推荐(赞)还是反对(踩)
用户字段(用户主键)>>>:外键字段
文章字段(文章主键)>>>:外键字段
点赞点踩
文章评论表:记录哪个用户给哪篇文章评论了什么内容
用户字段(用户主键)>>>:外键字段
文章字段(文章主键)>>>:外键字段
评论内容
评论时间
外键字段(自关联)
"""
id user_id article_id content parent_id
1 1 1 哈哈哈 null
2 2 1 哈你妹 1
3 3 1 讲文明 2
"""
外键字段
用户表
用户与个人站点是一对一外键关系
个人站点表
文章表
文章表与个人站点表是一对多外键关系
文章表与文章分类表是一对多外键关系
文章表与文章标签表是多对多外键关系
'''
数据库字段优化设计:我们想统计文章的评论数 点赞数
通过文章数据跨表查询到文章评论表中对应的数据统计即可
但是文章需要频繁的展示 每次都跨表查询的话效率极低
我们在文章表中再创建三个普通字段
之后只需要确保每次操作评论表或者点赞点踩表时同步修改上述三个普通字段即可
'''
文章评论数
文章点赞数
文章点踩数
文章分类表
文章分类与个人站点是一对多外键关系
文章标签表
文章标签与个人站点是一对多外键关系
模型层代码:models.py
特别重要:
创建表之前因为用户表继承AbstractUser 要在设置里面加上
AUTH_USER_MODEL = 'app01.UserInfo'
模型层代码:
from django.db import models
# Create your models here.
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
"""用户表"""
phone = models.BigIntegerField(verbose_name='手机号', null=True, blank=True)
avatar = models.FileField(upload_to='media/avatar/', default='media/avatar/default.jpg', verbose_name='用户头像')
register_time = models.DateTimeField(verbose_name='注册时间', auto_now_add=True)
"""用户表与个人站点一对一外键"""
site = models.OneToOneField(to='Site', on_delete=models.CASCADE, null=True)
# 修改admin 后天管理的表名
# class Meta:
# verbose_name_plural = '用户表'
def __str__(self):
return f'用户对象:{self.username}'
class Site(models.Model):
"""个人站点表"""
site_name = models.CharField(verbose_name='站点名称', max_length=32)
site_title = models.CharField(verbose_name='站点标题', max_length=32)
site_theme = models.CharField(verbose_name='站点样式', max_length=32, null=True) # 模拟站点样式文件
# 修改admin 后台管理的表名
# class Meta:
# verbose_name_plural = '个人站点表'
def __str__(self):
return f'个人站点:{self.site_theme}'
class Article(models.Model):
"""文章表"""
title = models.CharField(verbose_name='文章标题', max_length=32)
desc = models.CharField(verbose_name='文章简介', max_length=255)
content = models.TextField(verbose_name='文章内容')
create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
# 三个优化字段
comment_num = models.IntegerField(verbose_name='评论数', default=0)
up_num = models.IntegerField(verbose_name='点赞数', default=0)
down_num = models.IntegerField(verbose_name='点踩数', default=0)
# 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章表'
"""文章与个人站点一对多外键"""
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='Article2Tag',
through_fields=('article', 'tag'), )
def __str__(self):
return f'文章对象:{self.title}'
class Category(models.Model):
"""文章分类表"""
name = models.CharField(verbose_name='分类名称', max_length=32)
"""个人站点与文章分类的外键"""
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
# 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章分类表'
def __str__(self):
return f'文章分类:{self.name}'
class Tag(models.Model):
"""文章标签表"""
name = models.CharField(verbose_name='标签名称', max_length=32)
"""个人站点和文章标签的外键"""
site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
# 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章标签表'
def __str__(self):
return f'文章标签:{self.name}'
"""文章与标签表是多对多 半自动创建"""
class Article2Tag(models.Model):
article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True)
tag = models.ForeignKey(to='Tag', on_delete=models.CASCADE, null=True)
# 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章表与标签表关系'
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='点赞点踩')
# # 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章点赞点踩表'
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)
# # 修改admin后台管理的表名
# class Meta:
# verbose_name_plural = '文章评论表'
然后执行数据库迁移命令 添加数据库 (这里使用mysql)
注册功能
1.先开启路由
2.返回一个用户的注册页面
3.引入静态文件 创建static 引入bootstrap文件
4.构建前端注册页面
1.渲染前端标签 (采用一个空的form标签)
2.获取用户数据
3.校验用户数据
4.展示错误提示
ps: 用forms组件、modelform组件
单独开设接口进行编写 解耦合
5.用户头像的实时展示 固定代码 注意标签名字
6.给注册按钮绑定点击事件
1.循环form标签内数据 添加数据 加上头像文件
2.发送ajax请求携带文件数据必要配置
contentType:false,
processData: false,
注册完成后跳转
循环发送form内数据
渲染错误信息(查找标签)
3.给所有标签渲染错误样式 聚焦移除错误样式
7.后端代码的编写
用户上传头像的判断
创建信息时注意用create_user 替换了auth表(**clean_data)
forms校验文件代码
单独创建一个myforms.py文件
from django import forms
from app01 import models
from django.forms import widgets
class RegisterForm(forms.Form):
"""用户注册form类"""
username = forms.CharField(min_length=3, max_length=8, label='用户注册',
error_messages={
'min_length': '用户名最短三位',
'max_length': '用户名最长八位',
'required': '用户名不能为空',
}, widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
)
password = forms.CharField(min_length=3, max_length=8, label='密码',
error_messages={
'min_length': '密码最短三位',
'max_length': '密码最长八位',
'required': '密码不能为空',
}, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
)
confirm_password = forms.CharField(min_length=3, max_length=8, label='确认密码',
error_messages={
'min_length': '密码最短三位',
'max_length': '密码最长八位',
'required': '密码不能为空',
}, 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')
res = models.UserInfo.objects.filter(username=username)
if res:
self.add_error('username', '用户名已存在')
return username
# 全局钩子校验两次密码是否一致
def clean(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
以下前端校验的截图 有几个点需要注意一下:
forms校验文件代码
from django import forms
from app01 import models
from django.forms import widgets
class RegisterForm(forms.Form):
"""用户注册form类"""
username = forms.CharField(min_length=3, max_length=8, label='用户注册',
error_messages={
'min_length': '用户名最短三位',
'max_length': '用户名最长八位',
'required': '用户名不能为空',
}, widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
)
password = forms.CharField(min_length=3, max_length=8, label='密码',
error_messages={
'min_length': '密码最短三位',
'max_length': '密码最长八位',
'required': '密码不能为空',
}, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
)
confirm_password = forms.CharField(min_length=3, max_length=8, label='确认密码',
error_messages={
'min_length': '密码最短三位',
'max_length': '密码最长八位',
'required': '密码不能为空',
}, 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')
res = models.UserInfo.objects.filter(username=username)
if res:
self.add_error('username', '用户名已存在')
return username
# 全局钩子校验两次密码是否一致
def clean(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
注册功能前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="container">
<div class="col-md-8 col-md-offset-2">
<h2 class="text-center">用户注册</h2>
<form id="form"> <!--不使用form表单提交数据 但是用一下form标签 它有一个序列化功能-->
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group"> <!--目的是让多个获取用户数据的标签上下间距更大一些-->
<label for="{{ form.auto_id }}">{{ form.label }}</label> <!--form.auto_id 自动获取渲染的标签id值-->
{{ form }}
<span style="color: red" class="pull-right"></span>
</div>
{% endfor %}
<!--用户头像自己编写相关标签获取-->
<div class="form-group">
<label for="myfile">头像
<img src="/static/img/default.jpg" alt="" id="myimg" width="120">
</label>
<input type="file" id="myfile" style="display: none" >
</div>
<input type="button" id="subBtn" class="btn btn-primary btn-block" value="用户注册">
</form>
</div>
</div>
<script>
//1.用户头像的实时展示
$('#myfile').change(function (){
//1.产生一个文件阅读器对象
let myFilReaderObj = new FileReader();
// 2.获取用户上传的头像文件
let fileObj = this.files[0];
//3.将文件对象交给阅读器对象读取
myFilReaderObj.readAsDataURL(fileObj); //异步
// 等待文件阅读器对象加载完毕之后再修改src
myFilReaderObj.onload = function (){
//4.修改img标签的src 属性展示图片
$('#myimg').attr('src',myFilReaderObj.result)
}
})
//2.给注册按钮绑定点击事件 发送ajax 携带了文件数据
$('#subBtn').click(function (){
//1.先产生一个内置对象
let myFormDataObj = new FormData();
//2.添加普通数据(单个单个的编写效率极低)
{#console.log($('#form').serializeArray()) // 可以一次性获取form标签内所有普通字段数据 [{数组},{数组},{数组}]#}
$.each($('#form').serializeArray(),function (index,dataObj){ // 对结果for循环 然后交给后面的函数处理
myFormDataObj.append(dataObj.name,dataObj.value) // {'name':'','value':'',}
})
//3.添加文件数据
myFormDataObj.append('avatar',$('#myfile')[0].files[0])
//4.发送ajax请求
$.ajax({
url:'',
type: 'post',
data:myFormDataObj,
contentType:false,
processData: false,
success: function (args){
if(args.code === 10000){
window.location.href = args.url
}else{
{#console.log(args.msg)#}
let dataObj = args.msg;
// 如何针对性的渲染错误提示 {'username'} id_username
$.each(dataObj,function (k,msgArray){
// 拼接标签的id值
let eleId = '#id_' + k
// 根据id查找标签 修改下面span标签的内容 并给父标签添加错误样式
$(eleId).next().text(msgArray[0]).parent().addClass('has-error')
})
}
}
})
})
//3.给所有的input标签绑定获取焦点事件 移除错误样式
$('input').focus(function (){
$(this).next().text('').parent().removeClass('has-error')
})
</script>
</body>
</html>
注册功能后端代码
from django.shortcuts import render, HttpResponse, redirect
from app01 import models
from app01 import myforms
from django.http import JsonResponse
def register_func(request):
# 前后端ajax交互 通常采用字段作为交互对象
back_dict = {'code': 10000, 'msg': ''}
# 1.先产生一个空的form_obj
form_obj = myforms.RegisterForm()
if request.method == 'POST':
form_obj = myforms.RegisterForm(request.POST) # username password confirm_password email csrfmiddlewaretoken
if form_obj.is_valid():
clean_data = form_obj.cleaned_data # 存储符合校验的数据 {username password confirm_password email}
# 将confirm_password键值对移除
clean_data.pop('confirm_password') # {username password email}
# 获取用户上传的头像文件
avatar_obj = request.FILES.get('avatar') # 用户有可能没有上传
if avatar_obj:
clean_data['avatar'] = avatar_obj # {username password email avatar}
# 创建用户数据
models.UserInfo.objects.create_user(**clean_data) # 上述处理字典的目的就是为了创建数据省事
# return HttpResponse('注册成功!!!!')
back_dict['msg'] = '注册功能'
back_dict['url'] = '/login/'
else:
back_dict['code'] = 10001
back_dict['msg'] = form_obj.errors
return JsonResponse(back_dict)
return render(request, 'registerPage.html', locals())
登录功能
1.开设路由
2.后端先返回一个loginPage.html页面
3.登录页面搞一个图片验证码的效果(后端代码编写)
需要用到:1.pillow模块
2.io模块
3.random模块
下载模块经常会报错 因为源有问题
这里直接终端执行 注意模块名手写
pip3.8 install 模块名 -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
点击图片实时刷新验证码
保存验证码便于后端校验
4.渲染登录按钮
发送ajax请求
正确就跳转到主页
或者显示错误提示
1.alert(错误内容)
2.使用sweetalert 插件美化展示 需要引入sweeralert cdn
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
5.后端代码编写
auth模块帮助校验
1.先搞个验证字典
back_dict = {'code': 10000, 'msg': ''}
2.获取前端数据 比对
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
code = request.POST.get('code')
if code.upper() == request.session.get('code').upper():
user_obj = auth.authenticate(request, username=username, password=password)
3.保存用户登录状态
4.错误信息添加
路由展示
# 登录功能
path('login/', views.login_func, name='login_view'),
# 图片验证码相关功能
path('get_code/', views.get_code_func),
图片验证码代码:
from PIL import Image, ImageFont, ImageDraw
"""
Image: 产生图片
ImageFont: 字体样式
ImageDraw: 画笔对象
"""
from io import BytesIO, StringIO
"""
BytesIO: 在内存中临时存储 读取的时候以bytes格式为准
StringIO: 在内存中临时存储 读取的时候以字符串格式为准
"""
import random
def get_random():
return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
def get_code_func(request):
# 1.推导步骤1:直接读取图片文件返回
# with open(r'D:\djangoProject\BBS\app01\avatar\555.jpg', 'rb') as f:
# data = f.read()
# return HttpResponse(data)
# # 2.推导步骤2:随机产生图片动态返回 pillow模块
# img_obj = Image.new('RGB',(350,35),'yellow')
# with open(r'xxx.png','wb')as f:
# img_obj.save(f,'png')
# with open(r'xxx.png','rb')as f:
# data = f.read()
# return HttpResponse(data)
# 3.推导步骤3:针对图片的保存与读取 做优化 内存管理器
# img_obj = Image.new('RGB', (350, 35), 'yellow')
# io_obj = BytesIO()
# img_obj.save(io_obj, 'png')
# return HttpResponse(io_obj.getvalue())
# 4.推导步骤4:图片颜色可以随机变换
# img_obj = Image.new('RGB', (350, 35), get_random())
# io_obj = BytesIO()
# img_obj.save(io_obj, 'png')
# return HttpResponse(io_obj.getvalue())
# 5.推导步骤5:编写验证码
img_obj = Image.new('RGB', (350, 35), get_random()) # 先产生图片对象
# 将图片对象交给画笔对象
draw_obj = ImageDraw.Draw(img_obj)
# 确定字体样式(ttf文件)
font_obj = ImageFont.truetype('static/font/111.ttf', 35)
# 产生随机验证码
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(1, 9))
# 三选一
temp_choice = random.choice([random_upper, random_lower, random_int])
# 写到图片上
draw_obj.text((i * 45 + 45, 0), temp_choice, font=font_obj)
code += temp_choice
# 后端保存验证码 便于后续的比对
request.session['code'] = code
io_obj = BytesIO()
img_obj.save(io_obj, 'png')
return HttpResponse(io_obj.getvalue())
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
</head>
<body>
<div class="container">
<div class="col-md-8 col-md-offset-2">
<h2 class="text-center">用户登录</h2>
{% csrf_token %}
<div class="form-group">
<label for="name">用户名</label>
<input type="text" id="name" class="form-control" name="username">
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" class="form-control" name="password">
</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" name="code">
</div>
<div class="col-md-6">
<img src="/get_code/" alt="" width="350" id="d1" height="35">
</div>
</div>
</div>
<input type="button" class="btn btn-success btn-block" value="用户登录" id="loginBtn">
</div>
</div>
<script>
// 1.图片验证码动态刷新
$('#d1').click(function () {
let oldSrc = $(this).attr('src');
$(this).attr('src', oldSrc + '?')
})
// 2.登录按钮发送ajax请求
$('#loginBtn').click(function () {
// 可以再次使用form标签序列化功能 也可以自己挨个获取
$.ajax({
url: '',
type: 'post',
data: {
'username': $('#name').val(),
'password': $('#password').val(),
'code': $('#code').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (args) {
if (args.code === 10000) {
window.location.href = args.url
} else {
// 课下可以使用sweetalert 插件美化展示 需要引入sweetalert cdn
alert(args.msg)
{#swal(args.msg,'验证码输错了!!!')#}
}
}
})
})
</script>
</body>
</html>
后端代码(获取图片验证码的代码在上面)
import json
from django.shortcuts import render, HttpResponse, redirect
from app01 import models
from app01 import myforms
from django.http import JsonResponse
from django.contrib import auth
def login_func(request):
back_dict = {'code': 10000, 'msg': ''}
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
code = request.POST.get('code')
if code.upper() == request.session.get('code').upper():
user_obj = auth.authenticate(request, username=username, password=password)
if user_obj:
# 保存用户登录状态
auth.login(request, user_obj) # 执行之后就可以使用request.user获取登录用户对象
back_dict['msg'] = '登录成功!!!!'
back_dict['url'] = '/home/'
else:
back_dict['code'] = 10001
back_dict['msg'] = '用户名或密码不正确'
else:
back_dict['code'] = 10002
back_dict['msg'] = '验证码错误'
return JsonResponse(back_dict)
return render(request, 'loginPage.html')
网页主页
1.开设路由
# 网站首页
path('home/', views.home_func, name='home_view'),
2.返回html页面
3.首页导航条
1.引入静态文件
2.复制导航条页面
3.后端导入auth模块配合
from django.contrib import auth
文章过多的话可以添加 分页器
创建一个mypage.py的文件 CV代码
4.修改密码
1.开设路由
# 修改密码功能
path('set_pwd/', views.set_pwd_func),
2.使用装饰器进行校验是否登录
from django.contrib.auth.decorators import login_required
@login_required
这个模块可以在全局设置里面配置
LOGIN_URL = '/login/'
3.考虑用模态框直接进行修改 需要到 v3里面复制粘贴
4.前端发送ajax请求
5.后端代码获取前端修改的数据 进行处理 错误信息添加到 back_dict字典内
6.前端错误信息渲染
5.修改头像
1.开设修改头像路由 配置暴露资源接口
# 用户头像修改
path('set_avatar/', views.set_avatar_func),
from django.views.static import serve
from django.conf import settings
# 自定义暴露资源接口
re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
还需要在全局配置里配置 MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
2.使用装饰器进行校验是否登录
from django.contrib.auth.decorators import login_required
@login_required
3.考虑用模态框直接进行修改 需要到 v3里面复制粘贴
头像的实时展示 和注册时一样
4.前端发送ajax请求
5.后端代码获取前端修改的数据 进行处理 错误信息添加到 back_dict字典内
6.前端错误信息渲染
6.注销登录
1.开设路由
# 注销登录功能
path('logout/', views.logout),
2.后端代码用auth模块进行处理 跳转到主页
3.注销登录的按钮路由添加
4.主页内容的添加(可以考虑把内容区域继承 block )
1.先分区(两边是广告 中间是文章 需要先在数据库里面绑定 使用命令创建超级管理员 createsuperuser)
2.准备数据 需要在admin.py中添加表的绑定关系 在表中绑数据 (注意个人站点绑定别绑错了)
from app01 import models
dmin.site.register(models.UserInfo)
admin.site.register(models.Site)
admin.site.register(models.Article)
admin.site.register(models.Category)
admin.site.register(models.Tag)
admin.site.register(models.Article2Tag)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)
3.添加用户名 时间 点赞 点踩 评论等
首页功能前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
{% 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="#">
{% block title %}
BBS
{% endblock %}
</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>
</ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="搜索">
</div>
<button type="submit" class="btn btn-default">搜索</button>
</form>
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
<li><a href="#">{{ request.user.username }}</a></li>
<li style="width: 38px;height: 38px;border-radius: 50%; overflow: hidden;display: block"><img src="/media/{{ request.user.avatar }}/" alt="" style="max-width: 100%"></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="#" data-toggle="modal" data-target="#myavatar">修改头像</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="{% url 'register_view' %}">注册</a></li>
<li><a href="{% url 'login_view' %}">登录</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<!--导航条结束-->
<!--模态框开始-->
<!--修改密码模态框-->
<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>
</div>
<div class="modal-body">
<div class="form-group">
<label for="">用户名</label>
<input type="text" value="{{ request.user.username }}" disabled class="form-control">
</div>
<div class="form-group">
<label for="">原密码</label>
<input type="text" id="old_pwd" class="form-control">
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="text" id="new_pwd" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="text" id="confirm_pwd" class="form-control">
</div>
</div>
<div class="modal-footer">
<span id="error" style="color: red" ></span>
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-warning" id="setBtn">修改</button>
</div>
</div>
</div>
</div>
<!--改头像模态框-->
<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>
</div>
<div class="modal-body">
<form action="/set_avatar/" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<label for="">用户名</label>
<input type="text" value="{{ request.user.username }}" disabled class="form-control">
</div>
<div class="form-group">
<label for="">原头像</label>
<img src="/media/{{ request.user.avatar }}" alt="" width="120">
</div>
<div class="form-group">
<label for="myfile">新头像
<img src="/static/img/default.jpg" alt="" id="myimg" width="120">
</label>
<input type="file" id="myfile" style="display: none" name="new_avatar" >
</div>
<input type="submit" class="btn btn-warning btn-block">
</form>
</div>
</div>
</div>
</div>
<!--模态框结束-->
<!--内容区开始-->
<div class="container-fluid">
<div class="row">
{% block content %}
<div class="col-md-2">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">中秋节苦熬了</h3>
</div>
<div class="panel-body">
抓紧练习:wuyong
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">百万大奖</h3>
</div>
<div class="panel-body">
共享你幸运儿:22222
</div>
</div>
<div class="panel panel-success">
<div class="panel-heading">
<h3 class="panel-title">广告找找</h3>
</div>
<div class="panel-body">
旺铺难求
</div>
</div>
</div>
<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>
<br>
<div>
<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-comment">{{ article_obj.comment_num }} </span>
</div>
</div>
<hr>
{% endfor %}
<div class="text-center">{{ page_obj.page_html|safe }}</div>
</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">
抓紧练习:wuyong
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">百万大奖</h3>
</div>
<div class="panel-body">
共享你幸运儿:22222
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">广告找找</h3>
</div>
<div class="panel-body">
旺铺难求
</div>
</div>
</div>
{% endblock %}
</div>
</div>
<!--内容区结束-->
<script>
$('#setBtn').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{
$('#error').text(args.msg)
}
}
})
})
//1.用户头像的实时展示
$('#myfile').change(function (){
//1.产生一个文件阅读器对象
let myFileReaderObj = new FileReader();
// 2.获取用户上传的头像文件
let fileObj = this.files[0];
//3.将文件对象交给阅读器对象读取
myFileReaderObj.readAsDataURL(fileObj); //异步
// 等待文件阅读器对象加载完毕之后再修改src
myFileReaderObj.onload = function (){
//4.修改img标签的src 属性展示图片
$('#myimg').attr('src',myFileReaderObj.result)
}
})
</script>
{% block js %}
{% endblock %}
</body>
</html>
首页功能后端代码
分页器代码
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=10, pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
if current_page < 1:
current_page = 1
self.current_page = current_page
self.all_count = all_count
self.per_page_num = per_page_num
# 总页码
all_pager, tmp = divmod(all_count, per_page_num)
if tmp:
all_pager += 1
self.all_pager = all_pager
self.pager_count = pager_count
self.pager_count_half = int((pager_count - 1) / 2)
@property
def start(self):
return (self.current_page - 1) * self.per_page_num
@property
def end(self):
return self.current_page * self.per_page_num
def page_html(self):
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示11/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
# 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_end = self.all_pager + 1
pager_start = self.all_pager - self.pager_count + 1
else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1
page_html_list = []
# 添加前面的nav和ul标签
page_html_list.append('''
<nav aria-label='Page navigation>'
<ul class='pagination'>
''')
first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
page_html_list.append(first_page)
if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
page_html_list.append(prev_page)
for i in range(pager_start, pager_end):
if i == self.current_page:
temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
else:
temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
page_html_list.append(temp)
if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
page_html_list.append(next_page)
last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
page_html_list.append(last_page)
# 尾部添加标签
page_html_list.append('''
</nav>
</ul>
''')
return ''.join(page_html_list)
首页路由代码
from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse
from django.contrib import auth
from django.contrib.auth.decorators import login_required
from app01 import mypage
ef home_func(request):
# 查询所有的用户编写的文章
article_queryset = models.Article.objects.all()
'''文章过多的情况下应该考虑添加分页器'''
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, 'homePage.html', locals())
@login_required
def set_pwd_func(request):
back_dict = {'code': 10000, 'msg': ''}
if request.method == 'POST':
old_pwd = request.POST.get('old_pwd')
new_pwd = request.POST.get('new_pwd')
confirm_pwd = request.POST.get('confirm_pwd')
# 先校验原密码是否正确
if request.user.check_password(old_pwd):
# 再校验两次密码是否一致 并且不能为空
if new_pwd == confirm_pwd and new_pwd:
request.user.set_password(new_pwd)
request.user.save()
back_dict['msg'] = '密码修改成功!!!'
back_dict['url'] = '/login/'
else:
back_dict['code'] = 10001
back_dict['msg'] = '两次密码不一致或者为空'
else:
back_dict['code'] = 10002
back_dict['msg'] = '原密码错误'
return JsonResponse(back_dict)
@login_required
def logout(request):
auth.logout(request)
return redirect('home_view')
个人站点功能
BBS升级版的更多相关文章
- Python之路【第十八篇】Django小项目简单BBS论坛部分内容知识点
开发一个简单的BBS论坛 项目需求: 整体参考“抽屉新热榜” + “虎嗅网” 实现不同论坛版块 帖子列表展示 帖子评论数.点赞数展示 在线用户展示 允许登录用户发贴.评论.点赞 允许上传文件 帖子可被 ...
- python 学习笔记二十 django项目bbs论坛
项目:开发一个简单的BBS论坛 需求: 整体参考“抽屉新热榜” + “虎嗅网” 实现不同论坛版块 帖子列表展示 帖子评论数.点赞数展示 在线用户展示 允许登录用户发贴.评论.点赞 允许上传文件 帖子可 ...
- http://bbs.ednchina.com/BLOG_345002072_2001308.HTM
http://bbs.ednchina.com/BLOG_345002072_2001308.HTM
- 客户访问站点将bbs/链接 跳转至forum/链接下的两种方式
显性 302 暂时重定向跳转 server { listen 80 ; server_name localhost; index index.html index.htm index.php; roo ...
- 4829 [DP]数字三角形升级版
4829 [DP]数字三角形升级版 时间限制: 1 s 空间限制: 16000 KB 题目等级 : 黄金 Gold 题解 题目描述 Description 从数字三角形的顶部(如图, ...
- 前博客 http://bbs.landingbj.com/mytopic.jsp?action=mytopic&username=57071
在工作学习的过程中,遇到了亮眼的技术点,或者 学习的心得体会,总想要记录下来,或是方便自己,或是帮助如同自己现在这般的新人.前人种树,后人乘凉.享受了前人留下的阴凉,也会考虑自己给后来者种下几棵树苗. ...
- bbs树形打印(一)
前言:大家在bbs回帖时常常可以看到树形的回复形式. dfs设计 (1) 为使得Connection仅打开一次,因此以conn作为其中一个递归参数,在递归全程不关闭conn; (2)根据存入数据的树状 ...
- python小打小闹之简陋版BBS
闲的蛋疼,索性写个东西玩,于是,写个类似于BBS的念头就开始了. 我们考虑到需要实现的功能: 1 只有登陆的用户才可以点赞,如果没有登陆,那么不可以点赞,点赞次数只可以为1. 2 只有登陆的用户可以评 ...
- http://bbs.tianya.cn/post-stocks-1665898-1.shtml
http://bbs.tianya.cn/post-stocks-1295707-1.shtml 写过交易计划吗?有没有提前关注过某些板块或者某些股票呢?自选股里边有多少只股票?你平时复盘都是什么习惯 ...
随机推荐
- 1、使用简单工厂模式设计能够实现包含加法(+)、减法(-)、乘法(*)、除法(/)四种运算的计算机程序,要求输入两个数和运算符,得到运算结果。要求使用相关的工具绘制UML类图并严格按照类图的设计编写程
1.使用简单工厂模式设计能够实现包含加法(+).减法(-).乘法(*).除法(/)四种运算的计算机程序,要求输入两个数和运算符,得到运算结果.要求使用相关的工具绘制UML类图并严格按照类图的设计编写程 ...
- 齐博x2模型里边钩子的创建与使用
在模型里边的钩子创建与使用方法跟在控制器里边的钩子创建及使用方法是有所区别的在模型里边创建的钩子,你可以理解为执行一个函数,是无法调用模型里边的类的方法及属性的.比如系统文件\application\ ...
- 网络工程知识(二)VLAN的基础和配置:802.1q帧;Access、Trunk、Hybrid接口工作模式过程与配置;VLANIF的小实验
介绍-VLAN VLAN(Virtual Local Area Network)即虚拟局域网,工作在数据链路层. 交换机将通过:接口.MAC.基于子网.协议划分(IPv4和IPv6).基于策略的方式划 ...
- Chrony时间同步服务
概: 网络时间协议(Network Time Protocol,NTP)是用于网络时间同步的协议.提供NTP时间同步服务的软件有很多,这里采用Chrony软件来实现时间同步 chrony 的优势: ...
- antd 批量上传文件逻辑
基本步骤 通过 antd 框架的 Upload 控件,采用手动上传的方式,先选择需要上传的文件(控制文件数量以及大小),再根据所选的文件列表,循环上传,期间通过 Spin 控件提示上传中. 效果展示 ...
- Ignite实战
1.概述 本篇博客将对Ignite的基础环境.集群快照.分布式计算.SQL查询与处理.机器学习等内容进行介绍. 2.内容 2.1 什么是Ignite? 在学习Ignite之前,我们先来了解一下什么是I ...
- Web自动化---解决登录页面随机验证码问题
一.抛出问题 在日常的测试工作中,遇到了这样一个登录页面,如下图: 像我们之前做过UI自动化的同学就知道,自动输入账号和密码,这个简单,但是怎么样来识别验证码呢?验证码的形式有多种,有纯数字的,纯字母 ...
- Nginx配置-1
1.绑定nginx到指定cpu [root@nginx conf.d]# vim /apps/nginx/conf/nginx.conf worker_processes 2; worker_cpu_ ...
- 苹果 App Store 开始支持隐藏上架应用:只能通过链接下载
据MacRumors报道,苹果公司最近宣布,正如其开发者网站上所概述的那样,App Store现在支持只能通过直接链接才能发现的隐藏应用. 图片来自 Apple 拥有不适合公开发布的应用的开发 ...
- VBA_BASIC
字符串相关 判断单元格是否包含特定字符串,以"P"为例. if cells(1,1) Like "*P*" Then cells(1,2) = "ha ...