目录
- 1、项目开发流程
- 2、bbs表设计
- 3、数据库表创建及同步
- 4、注册功能
- forms组件
- 注册页面
- 5、登录功能
- 实现图片验证码
- 6、搭建BBS首页导航条
- 修改密码
- 退出登录
- 7、admin后台管理
- 8、首页文章展示
- media配置及用户头像展示
- 9、图片防盗链
- 10、个人站点页面搭建
1、项目开发流程
- 需求分析
架构师+产品经理+开发组组长,在跟客户沟通交流中引导客户往我们之前想好的方案上靠。形成一个初步的方案 - 项目设计
架构师进行项目设计:
编程语言的选择、
框架的选择(flask、Django、Tornado)、
数据库的选择(主库mysql postgresql、缓存数据库redis MongoDB memcache)、
功能划分(将项目划分成几个模块)、
组长开会(分发任务)、
项目报价 - 分组开发
组长找组员开会,安排各自功能模块,我们就是在架构师设计好的框架中填写自己的代码 - 测试
测试部门对代码进行压力测试 - 交付上线
交给运维人员
2、BBS表设计(https://www.cnblogs.com/)
一个项目中最重要的不是业务逻辑的书写而是前期表的设计,只要将表设计好了,后续功能书写才会一帆风顺。
- 用户表
继承AbstractUser
扩展字段:phoneavatarcreate_time
外键字段:
一对一“个人站点表” - 个人站点表
site_namesite_title|site_theme - 文章标签表
name
外键字段:
一对多“个人站点表” - 文章分类表
name
外键字段:
一对多“个人站点表” - 文章表
titledesc文章简介content文章内容create_time发布时间
数据库字段设计优化(虽然点赞数点踩数评论数可以从其他表跨表查询得到,但是频繁跨表效率低)
up_num点赞数down_num点踩数comment_num评论数(在点赞点踩表评论表增加数据时,给这三个普通字段数值同步更新+1)
外键字段:
一对多“个人站点表”
多对多“文章标签表“
一对多”文章分类表” - 点赞点踩表(记录哪个用户给哪篇文章点了赞还是点了踩)
user ForeignKey(to='User')
article ForeignKey(to='Article')
is_up BooleanField() - 文章评论表
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)
根评论:直接评论当前发布的内容
子评论:评论别人的评论
(根评论与子评论是一对多的关系)
3、数据库表创建及同步
配置MySQL数据库参数
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'bbs',
'USER':'root',
'PASSWORD':'dingding',
'PORT':'3306',
'HOST':'127.0.0.1',
'CHARSET':'utf8'
}
}
init文件中进行代码声明:
import pymysql
pymysql.install_as_MySQLdb()
models.py文件
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)
# 头像
# 给avatar字段传文件对象,该文件会自动存储到avatar文件夹下,设置一个默认头像
avatar = models.FileField(upload_to='avatar/',default='avatar/default.jpg',verbose_name='用户头像')
create_time = models.DateField(auto_now_add = True)
# 一对一关系
blog = models.OneToOneField(to='Blog',null=True)
class Blog(models.Model):
site_name = models.CharField(max_length=32,verbose_name='站点名称')
site_title = models.CharField(max_length=32,verbose_name='站点标题')
# site_theme站点样式中存储css/js的文件路径
site_theme = models.CharField(max_length=64,verbose_name='站点样式')
class Category(models.Model):
name = models.CharField(max_length=32,verbose_name='文章分类')
# 外键字段
blog = models.ForeignKey(to='Blog',null=True)
class Tag(models.Model):
name = models.CharField(max_length=32,verbose_name='文章标签')
blog = models.ForeignKey(to='Blog',null=True)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField(max_length=64,verbose_name='文章标题')
desc = models.CharField(max_length=255,verbose_name='文章简介')
# 文章内容一般用TextField
content = models.TextField(verbose_name='文章内容')
create_time = models.DateField(auto_now_add=True)
# 数据库字段优化设计
up_num = models.BigIntegerField(default=0,verbose_name='点赞数')
down_num = models.BigIntegerField(default=0,verbose_name='点踩数')
comment_num = models.BigIntegerField(default=0,verbose_name='评论数')
# 外键字段
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='是否点赞')
class Comment(models.Model):
user = models.ForeignKey(to = 'UserInfo')
article = models.ForeignKey(to = 'Article')
content = models.CharField(max_length=255,verbose_name='评论内容')
comment_time = models.DateTimeField(verbose_name='评论时间',auto_now_add=True)
# 自关联
parent = models.ForeignKey(to='self',null=True)
到settings文件中添加代码:AUTH_USERMODEL = 'app01.UserInfo'
告诉Django用UserInfo替代auth_user表。
然后执行数据库迁移命令。
4、注册功能
forms组件
我们之前是直接在views.py中书写的forms组件代码,但是为了解耦合,应该将所有的forms组件代码单独写到一个地方。
如果你的项目自始至终只用到一个forms组件,那么建一个py文件即可。但是如果你的项目需要使用很多个forms组件,那么可以创建一个文件夹,在文件夹内根据forms组件功能的不同创建不同的py文件(userform.pyorderform.py).
app01文件夹下创建myforms.py文件:
from django import forms
from app01 import models
# 书写针对用户表的forms组件代码
class MyForm(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(min_length=3, max_length=8, label='密码',
error_messages={
'required': '密码不能为空',
'min_length': '密码最少3位',
'max_length': '密码最多8位'
},
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
)
confirm_password = forms.CharField(min_length=3, max_length=8, label='确认密码',
error_messages={
'required': '密码不能为空',
'min_length': '密码最少3位',
'max_length': '密码最多8位'
},
widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
)
email = forms.EmailField(label='邮箱',
error_messages={
'invalid': '邮箱格式不正确',
'required': '邮箱不能为空'
},
widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})
)
# 钩子函数
# 局部钩子:校验用户名是否存在
def clean_username(self):
username = self.cleaned_data.get('username')
# 去数据库中校验
is_exist = models.UserInfo.objects.filter(username=username)
if is_exist:
# 提示信息
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
注册页面
views.py文件:
from django.shortcuts import render,HttpResponse
from app01.myforms import MyRegForm
from app01 import models
from django.http import JsonResponse
# Create your views here.
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': 'jack', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'}
# 将校验通过的数据字典赋值个一个变量
clean_data = form_obj.cleaned_data
# 将字典中的confirm_password键值对删除
clean_data.pop('confirm_password')
# 获取用户头像
file_obj = request.FILES.get('avatar')
# 针对用户头像一定要判断是否传值,不能直接添加到字典中去
if file_obj:
clean_data['avatar'] = file_obj
# 直接操作数据库保存数据
models.UserInfo.objects.create_user(**clean_data)
# 注册成功之后要跳转到一个登录界面,所以给这个前后端交互的字典加上一个url
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())
register.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="jQuery.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1 class="text-center">注册页面</h1>
{# 里我们不用form表单提交数据,只是单纯用一下form标签,#}
{# 之后在ajax请求中会用到$('#myform').serializeArray()这个方法,能够获取到form标签内所有用户普通键值对数据 :[{},{},{},{}]#}
<form id="myform">
{% csrf_token %}
{% for form in form_obj %}
<div class="form-group">
{# form.auto_id找到标签所对应的id值#}
<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 id="myimg" src="{% static 'img/default.jpg' %}" alt="" width="80" style="margin-left: 1px">
{# 只要是点击label内的内容,都会跳转到for指定的标签上 #}
</label>
<input type="file" id="myfile" name="avatar" style="display: none">
</div>
{# 不要写submit,会出发form表单的提交#}
<input id="id_commit" type="button" value="注册" class="btn btn-primary pull-right">
</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操作,所以要等待文件阅读器读取完毕之后再进行文件的展示,否则头像不显示
// 需要用到onload功能
// 4、利用文件阅读器将文件展示到前端页面
// 修改src属性
myFileReaderObj.onload = function(){
$('#myimg').attr('src',myFileReaderObj.result)
}
})
{#发送ajax请求,我们的数据中既包含普通键值对,也包含文件,所以用到了FormData内置对象#}
$('#id_commit').click(function(){
let formDataObj = new FormData();
// 1、添加普通键值对
$.each($('#myform').serializeArray(),function (index,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) {
let targetId = '#id_'+index;
$(targetId).next().text(obj[0]).parent().addClass('has-error')
})
}
}
})
})
// 当框变成红色并且有错误提示时,要达到鼠标点击框则红色框错误提示都消失的效果:
// 给所有的input框绑定获取焦点事件
$('input').focus(function () {
// 将input下面的span标签和input外面的div标签修改内容及属性
// jQuery的链式操作
$(this).next().text('').parent().removeClass('has-error')
})
</script>
</body>
</html>
5、登陆功能
urls.py文件
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^register/',views.register,name='reg'),
url(r'^login/',views.login,name='login'),
# 图片验证码相关操作
url(r'^get_code/',views.get_code,name='get_code')
]
views.py文件
from django.shortcuts import render,HttpResponse
from app01.myforms import MyRegForm
from app01 import models
from django.http import JsonResponse
from django.contrib import auth
def login(request):
if request.method == 'POST':
# 针对ajax的前后端交互,通常会定义一个字典
back_dic = {'code':1000,'msg':''}
username = request.POST.get('username')
password = request.POST.get('password')
code = request.POST.get('code')
# 校验验证码是否正确
if request.session.get('code').upper() == code.upper(): # 都转成大写来比较(忽略大小写)
# 校验用户名和密码是否正确
user_obj = auth.authenticate(request,username=username,password=password)
if user_obj:
# 保存用户状态
auth.login(request,user_obj)
# 校验成功的话,给字典增加一个url,之后再跳转到home首页
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')
# 图片相关的模块
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'/Users/dingding/PycharmProjects/day72_BBS/static/img/default.jpg','rb') as f:
# data = f.read()
# return HttpResponse(data)
# 推导步骤2:利用pillow模块动态产生图片,io操作频繁,效率低
# img_obj = Image.new('RGB',(430,35),'red')
# img_obj = Image.new('RGB',(430,35),get_random())
# 先将图片对象保存起来
# with open('x.png','wb') as f:
# img_obj.save(f,'png')
# 再将图片对象读取出来
# with open('x.png','rb') as f:
# data = f.read()
# return HttpResponse(data)
# 推导步骤3:
# img_obj = Image.new('RGB', (430, 35), get_random())
# 生成一个io内存管理器对象,可以看成是文件句柄
# 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)
# 字体样式(.ttf格式的文件)
img_font = ImageFont.truetype('static/font/杨任东竹石体-Semibold.ttf',30) # 要加上字体大小
# 接下来产生随机验证码(包含5位数的数字、大写小写字母)
code = ''
for i in range(5):
random_upper = chr(random.randint(65,90)) # chr会将数字通过ASCII表转成数字对应的字母
random_lower = chr(random.randint(97,122))
random_int = str(random.randint(0,9)) # 转成字符串
# 从上面3个随机选择一个
tmp = random.choice([random_upper,random_lower,random_int])
# 将产生的随机字符串一个个写入到图片上(一个个写可以控制每个字之间的间隙,生成之后再写就没法控制间隙了)
img_draw.text((i*60+50,0),tmp,get_random(),img_font)
code += tmp
print(code)
# 随机验证码在登录的视图函数中需要用到,要进行校验,所以需要存起来,并且其他视图函数也要能拿到,可以存到session中。
request.session['code'] = code
io_obj = BytesIO()
img_obj.save(io_obj,'png')
return HttpResponse(io_obj.getvalue())
login.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="jQuery.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
{% load static %}
</head>
<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 col-xs-6">
<input type="text" name="code" id="id_code" class="form-control">
</div>
<div class="col-md-6 col-xs-6">
{# <img src="{% static 'img/default.jpg' %}" alt="" height="35" width="350" >#}
{# 动态展示验证码图片,这个页面只要一加载出来就会朝/get_code/这个页面发get请求#}
{# 给这个图片验证码绑定一个点击事件,使得点击后图片会刷新#}
<img id="get_code" src="/get_code/" alt="" height="35" width="350">
</div>
</div>
</div>
<input id="id_commit" type="button" class="btn btn-success pull-right" value="登陆">
<span style="color: red" id="error"></span>
</div>
</div>
</div>
<script>
$('#get_code').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>
补充:img标签src属性后面可以写的内容:
1、直接写网络图片地址
2、url后缀(自动朝该url发送get请求获取数据)
3、图片二进制数据
6、搭建BBS首页导航条
导航条以及修改密码、退出登录功能
views.py文件
def home(request):
return render(request,'home.html')
@ login_required
def set_password(request):
# 直接判断是不是ajax请求,只处理ajax请求
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_new_password = request.POST.get('confirm_new_password')
is_right = request.user.check_password(old_password) # 自动转成加密密码并校验
if is_right:
if new_password == confirm_new_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)
# 注销后重定向到home页面
return redirect('/home/')
home.html页面
<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="#">修改头像</a></li>
<li><a href="#">后台管理</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url 'logout' %}">退出登录</a></li>
</ul>
<!-- Large modal -->
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog"
aria-labelledby="myLargeModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<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="text" id="id_old_password" class="form-control">
</div>
<div class="form-group">
<label for="">新密码</label>
<input type="text" id="id_new_password" class="form-control">
</div>
<div class="form-group">
<label for="">确认密码</label>
<input type="text" id="id_confirm_new_password" class="form-control">
</div>
<div class="modal-footer">
<button id="id_edit" type="button" class="btn btn-primary">修改</button>
<button type="button" class="btn btn-default" data-dismiss="modal">
取消
</button>
<span id="password_errors" style="color: red"></span>
</div>
</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>
<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_new_password': $('#id_confirm_new_password').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (args) {
if (args.code == 1000) {
window.location.reload() // 如果修改成功可以刷新一下页面或者跳转到登录页面让用户重新登录
} else {
$("#password_errors").text(args.msg)
}
}
})
})
</script>
</body>
7、admin后台管理
先创建一个超级管理员:createsuperuser。
Django给你提供了一个管理员可视化界面让你方便的对模型表进行增删改查操作。
如果你想要使用admin后台管理操作这些模型表,就需要先注册你的模型表(告诉admin你需要操作哪些模型表)。
注册模型表app01/admin.py文件:
from django.contrib import admin
from app01 import models
# Register your models here.
admin.site.register(models.UserInfo)
admin.site.register(models.Blog)
admin.site.register(models.Category)
admin.site.register(models.Tag)
admin.site.register(models.Article)
admin.site.register(models.Article2Tag)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)
修改admin后台管理默认显示的表名,models.py文件后面加上class Meta::
from django.db import models
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
phone = models.BigIntegerField(verbose_name='手机号',null=True,blank=True)
# null=True 表示数据库中该字段可以为空
# blank=True 表示admin后台管理该字段可以为空
# 头像
# 给avatar字段传文件对象,该文件会自动存储到avatar文件夹下,设置一个默认头像
avatar = models.FileField(upload_to='avatar/',default='avatar/default.jpg',verbose_name='用户头像')
create_time = models.DateField(auto_now_add = True)
# 一对一关系
blog = models.OneToOneField(to='Blog',null=True)
class Meta:
# verbose_name = '用户表' # admin后台管理默认显示的表名后面会加s
verbose_name_plural = '用户表' # 修改admin后台管理默认显示的表名
def __str__(self):
return self.username
admin会给每一个注册了的模型表自动生成增删改查四条url:
以UserInfo表为例:http://127.0.0.1:8000/admin/app01/userinfo/add/http://127.0.0.1:8000/admin/app01/userinfo/1/delete/http://127.0.0.1:8000/admin/app01/userinfo/1/change/
http://127.0.0.1:8000/admin/app01/userinfo/
数据绑定需要注意的是:
先去文章表绑定数据
个人站点
文章分类
将用户和个人站点绑定关系标签和文章(不要把别人的文章绑定到其他人的标签)。
8、首页文章展示
media配置
网站所使用的静态文件默认放在static文件夹下,用户上传的静态文件也应该保存下来。
media配置:该配置可以让用户上传的所有文件都固定存放在某一个指定的文件夹下。
在settings.py文件中配置:
# 配置用户上传的文件存储位置
MEDIA_ROOT = os.path.join(BASE_DIR,'media') # 文件名可修改
等下次用户上传了文件时,系统会自动在根目录下生成文件夹media/avatar/存放文件。我们之前在根目录下手动创建的avatar文件夹就不需要了。
如何开设后端指定文件夹资源
urls.py中增加代码:
from django.conf.urls import url
from django.contrib import admin
from app01 import views
from django.views.static import serve
from day72_BBS import settings
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^register/',views.register,name='reg'),
url(r'^login/',views.login,name='login'),
# 图片验证码相关操作
url(r'^get_code/',views.get_code,name='get_code'),
# 首页
url(r'^home/',views.home,name='home'),
# 退出登录
url(r'^logout/',views.logout,name='logout'),
# 修改密码
url(r'^set_password/',views.set_password,name='set_password'),
# 暴露后端指定文件夹资源(MEDIA_ROOT的路径)
# 固定写法,不要自己改动
url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})
]
home.html首页文章展示部分代码:
<div class="container-fluid">
<div class="col-md-2 col-xs-2">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">广告1</h3>
</div>
<div class="panel-body">
内容1
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">广告2</h3>
</div>
<div class="panel-body">
内容2
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">广告3</h3>
</div>
<div class="panel-body">
内容3
</div>
</div>
</div>
<div class="col-md-8 col-xs-8">
{# 文章展示 #}
<ul class="media-list">
{% for article_obj in article_queryset %}
<li class="media">
<h4 class="media-heading"><a href="">{{ article_obj.title }}</a></h4>
<div class="media-left">
<a href="#">
{# 图片的路径需要我们手动加上media前缀#}
<img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..." width="60"
height="60">
</a>
</div>
<div class="media-body">{{ article_obj.desc }}</div>
<br>
<div>
{# 通过文章拿到用户对象#}
<span><a href="">{{ article_obj.blog.userinfo.username }}</a></span>
<span> 发布于 </span>
<span><a href=""> {{ article_obj.create_time|date:"Y-m-d" }} </a></span>
<span>
<span class="glyphicon glyphicon-comment"></span>
评论({{ article_obj.comment_num }})
</span>
<span>
<span class="glyphicon glyphicon-thumbs-up"></span>
点赞({{ article_obj.up_num }})
</span>
</div>
</li>
<hr>
{% endfor %}
</ul>
</div>
<div class="col-md-2 col-xs-2">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">广告1</h3>
</div>
<div class="panel-body">
内容1
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">广告2</h3>
</div>
<div class="panel-body">
内容2
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">广告3</h3>
</div>
<div class="panel-body">
内容3
</div>
</div>
</div>
</div>
9、图片防盗链
如何避免别的网站直接通过本网站的url访问本网站资源:
简单的防盗:可以做到请求来的时候先看看当前请求是从哪个网站过来的,如果是本网站那么可以正常访问,如果是其它网站则直接拒绝。
请求头里有一个专门记录请求来自于哪个网址的参数:Referer: http://127.0.0.1:8000/asfdasf/
绕过别人的防盗方式:
1、修改请求头Referer
2、写爬虫将对方网址的所有资源直接下载到我们自己的服务器上
10、个人站点页面搭建
ps:由于url方法第一个参数是正则表达式,所以当路由特别多的时候,可能会出现被顶替的情况,针对这种情况有两种解决方式:
1、修改正则表达式
2、调整url方法的位置
添加个人站点的路由:url(r'^(?P<username>w+)/$',views.site,name='site')
增加视图函数:
def site(request,username):
# 先校验当前用户名对应的个人站点是否存在
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)
return render(request,'site.html',locals())
个人站点页面site.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="jQuery.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
{# 这里要加上导航条的代码 #}
<div class="container-fluid">
<div class="row">
<div class="col-md-3 col-xs-3">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">广告1</h3>
</div>
<div class="panel-body">
内容1
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">广告2</h3>
</div>
<div class="panel-body">
内容2
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">广告3</h3>
</div>
<div class="panel-body">
内容3
</div>
</div>
</div>
<div class="col-md-9 col-xs-9">
<ul class="media-list">
{% for article_obj in article_list %}
<li class="media">
<h4 class="media-heading"><a href="">{{ article_obj.title }}</a></h4>
<div class="media-left">
<a href="#">
{# 图片的路径需要我们手动加上media前缀#}
<img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}" alt="..."
width="60"
height="60">
</a>
</div>
<div class="media-body">{{ article_obj.desc }}</div>
<div class="pull-right">
<span>posted </span>
<span>@ </span>
<span>{{ article_obj.create_time|date:"Y-m-d" }} </span>
<span>{{ article_obj.blog.userinfo.username }} </span>
<span>
<span class="glyphicon glyphicon-comment"></span>
评论({{ article_obj.comment_num }})
</span>
<span>
<span class="glyphicon glyphicon-thumbs-up"></span>
点赞({{ article_obj.up_num }})
</span>
<span><a href="">编辑</a></span>
</div>
</li>
<hr>
{% endfor %}
</ul>
</div>
</div>
</div>
</body>
</html>