今日内容
项目开发流程
项目名字:
BBS:多人博客系统
传统软件行业:给第三方做解决方案
互联网行业:饿了么,叮咚买菜...
软件开发流程:
-项目立项(高层,市场人员:市场调研,跟客户对接)
-项目设计(产品经理:设计软件功能,设计原型图)
-项目的具体设计(UI:切图)
-分任务开发
-前端:pc,小程序,移动端
-分任务开发
-后端:
-架构,数据库设计---(设计表,表关联)
-多人系统开发
-联调
-测试:功能测试,自动化测试,接口测试、、、
-上线运行:运维
-出现bug,项目维护阶段
-版本迭代
bbs项目表设计及关联
# 要开发的功能
-注册功能
-登录功能
-首页:文章展示,导航栏,用户中心,广告位
-个人主页:文章展示,侧边栏过滤(分类,标签,时间)
-文章详情:点赞点踩,评论(父评论,子评论)
-后台管理:这个人文章展示(增加,删除,修改文章)
-发布文章:富文本编辑器,xss攻击处理
# 技术选型:python3.8 django2.2.2 mysql:5.7 jquery2.x bootstrap@3
# 设计数据库---》数据库名字 bbs
-用户表(基于auth的user表扩写,扩写字段)
-博客表(跟用户表一对一)
-标签表
-分类表
-文章表
-点赞点踩表
-评论表
# 表的关联关系
-用户表(基于auth的user表扩写,扩写字段)
-博客表(跟用户表一对一)
-标签表:跟博客表一对多,跟文章是多对多
-分类表:跟博客表一对多,跟文章是一对多
-文章表:跟博客表是一对多
-点赞点踩表:跟用户一对多,跟文章表一对多
-评论表:跟用户一对多,跟文章表一对多
补充:
OneToOneField,ForeignKey,ManyToManyField
-related_name:反向操作时,使用的字段名,用于代替原反向查询时的’表名_set’。
-related_query_name:反向查询操作时,使用的连接前缀,用于替换表名
项目表字段编写和表迁移
创建项目
# 第一步:安装djagno 2.2.2
pip3 install django==2.2.2
# 第二步:使用pycharm创建项目
# 第三步:配置文件
-58行:
'DIRS': [os.path.join(BASE_DIR , 'templates')]
-国际化
LANGUAGE_CODE = 'zh-hans' # 语言中文
TIME_ZONE = 'Asia/Shanghai' # 时区使用上海时区
USE_I18N = True
USE_L10N = True
USE_TZ = False
-使用mysql
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1',
'PORT': 3306,
'NAME': 'bbs', # 去创建数据库---》navicat创建
'USER': 'root',
'PASSWORD': '123',
}
}
-创建bbs数据库
navicat创建
# 扩写auth的user表
AUTH_USER_MODEL = 'blog.UserInfo'
在models中写表模型
from django.db import models
from django.contrib.auth.models import AbstractUser
# 7 张表
# 继承AbstractUser,扩写字段:头像字段,手机号字段, 一对一的博客字段
class UserInfo(AbstractUser):
# username,password,email。。。。
phone = models.CharField(max_length=32, null=True)
# 存文件的字段---》本质还是varchar,可以把文件自动保存(avatar文件夹下),存文件地址
avatar = models.FileField(upload_to='avatar', default='avatar/default.png')
blog = models.OneToOneField(to='Blog', on_delete=models.CASCADE, null=True) # 一对一关联了blog表
class Blog(models.Model):
title = models.CharField(max_length=32, null=True, verbose_name='主标题')
site_name = models.CharField(max_length=32, null=True, verbose_name='副标题')
site_style = models.CharField(max_length=32, null=True, verbose_name='个人站点样式')
class Tag(models.Model):
name = models.CharField(max_length=32, verbose_name='标签名字')
# on_delete可以有很多选项,目前先用级联删除(很危险)
blog = models.ForeignKey(to='Blog', on_delete=models.CASCADE)
class Category(models.Model):
name = models.CharField(max_length=32, verbose_name='分类名字')
# on_delete可以有很多选项,目前先用级联删除(很危险)
blog = models.ForeignKey(to='Blog', on_delete=models.CASCADE)
class Article(models.Model):
title = models.CharField(max_length=32, verbose_name='文章名字')
desc = models.CharField(max_length=255, verbose_name='文章摘要')
content = models.TextField(verbose_name='文章内容') # 大文本
# auto_now_add auto_now
create_time = models.DateTimeField(auto_now_add=True,verbose_name='文章创建时间') #auto_now_add=True 新增文章这个字段可以不传,自动把当前时间加上
blog = models.ForeignKey(to='Blog', on_delete=models.CASCADE)
category = models.ForeignKey(to='Category', on_delete=models.CASCADE)
# 多对多关系,需要创建第三张表,
# django的orm的ManyToManyField字段可以自动创建第三张表(ArticleToTag)
# 手动创建第三张表:ManyToManyField一定要加两个参数,through=中间表 , through_fields通过哪两个字段关联
tag = models.ManyToManyField(to='Tag')
class UpAndDown(models.Model):
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE)
article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
is_up = models.BooleanField(verbose_name="点赞或点踩")
create_time = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE)
article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
content = models.CharField(max_length=64, verbose_name="评论的内容")
create_time = models.DateTimeField(auto_now_add=True)
# parent=models.ForeignKey(to='Comment', on_delete=models.CASCADE)
# parent=models.IntegerField(null=True)
parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True)
# 自关联字段---》父评论--》子评论
'''
id user article content parent
1 2 1 写的真好 空
2 2 2 写的一般 空
3 3 2 人家明明写的很好 2
'''
# 安装pymysql
pip3 install pymysql
# 在__ini__.py中加入:djagno默认操作mysql数据库使用的是 mysqlDB模块,在python2中没问题,但是在python3中已经不维护了,不支持,python3中操作mysql咱么用pymysql比较多,但是需要加下面两句话才能正常使用-----》猴子补丁(动态替换--把源码使用mysqlDB替换成pymysql的东西)
# 但是在django2.0.7及以后版本,需要改源码才能使用:operations.py---》146行,改成query = query.encode(errors='replace')
import pymysql
pymysql.install_as_MySQLdb()
# 以后直接使用 mysqlclient:有可能装不上---》看人品
pip3 install mysqlclient
注册forms编写
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError
from models import UserInfo # 模块导入:相对导入和绝对导入
class RegisterForm(forms.Form):
# 用户名,密码,确认密码,邮箱
username = forms.CharField(max_length=8, min_length=3, required=True, error_messages={
'max_length': '太长了',
'min_length': '太短了',
'required': '这个必填'
}, widget=widgets.TextInput(attrs={'class': 'form-control'}))
password = forms.CharField(max_length=8, min_length=3, required=True, error_messages={
'max_length': '太长了',
'min_length': '太短了',
'required': '这个必填'
}, widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
re_password = forms.CharField(max_length=8, min_length=3, required=True, error_messages={
'max_length': '太长了',
'min_length': '太短了',
'required': '这个必填'
}, widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
email = forms.EmailField(widget=widgets.EmailInput(attrs={'class': 'form-control'}))
# 局部钩子校验
# 方案一:
def clean_username(self):
username=self.cleaned_data.get('username')
user=UserInfo.objects.filter(username=username).first()
if user:
# 已经存在,不合理
raise ValidationError('该用户名已经存在')
else:
return username
#方案二:
# def clean_username(self):
# username=self.cleaned_data.get('username')
# try:
# UserInfo.objects.get(username=username) #有且只有一条才ok,否则就抛异常
# raise ValidationError('该用户名已经存在')
# except Exception:
# return username
# 全局钩子校验
def clean(self):
# 比较两个密码是否一致 :cleaned_data存的是校验过后的数据,是个字典
password = self.cleaned_data.get('password')
re_password = self.cleaned_data.get('re_password')
if password == re_password:
# 合理,返回校验过后的数据
return self.cleaned_data
else:
# 不合理,抛出校验失败的异常
raise ValidationError('两次密码不一致!!')
注册页面搭建
# bootstrap 搭建
-settings.py 中 加入static
STATIC_URL = '/static/'
STATICFILES_DIRS=[
os.path.join(BASE_DIR,'static')
]
-把bootstrap的静态资源copy到static文件夹下
# 新建模板文件 register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1 class="text-center">注册功能</h1>
<form id="id_form">
{% for item in form %}
<div class="form-group">
<label for="{{ item.id_for_label }}">{{ item.label }}</label>
{{ item }}
<span class="pull-right text-danger"></span>
</div>
{% endfor %}
<div class="form-group">
<label for="id_file">头像
<img src="/static/img/default.png" alt="" height="80px" width="80px" style="margin-left: 10px">
</label>
<input type="file" id="id_file" accept="image/*" style="display: none">
</div>
<div class="form-group text-center">
{# 如果input类型是submit或者button标签,放在form表单中,如果点提交,触发form的提交,如果我们写了ajax提交,会触发两次提交#}
<input type="button" value="注册" class="btn btn-danger" id="id_submit">
<span class="text-danger"></span>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
头像动态显示
# js 控制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1 class="text-center">注册功能</h1>
<form id="id_form">
{% for item in form %}
<div class="form-group">
<label for="{{ item.id_for_label }}">{{ item.label }}</label>
{{ item }}
<span class="pull-right text-danger"></span>
</div>
{% endfor %}
<div class="form-group">
<label for="id_file">头像
<img src="/static/img/default.png" alt="" height="80px" width="80px" style="margin-left: 10px" id="id_img">
</label>
<input type="file" id="id_file" accept="image/*" style="display: none">
</div>
<div class="form-group text-center">
{# 如果input类型是submit或者button标签,放在form表单中,如果点提交,触发form的提交,如果我们写了ajax提交,会触发两次提交#}
<input type="button" value="注册" class="btn btn-danger" id="id_submit">
<span class="text-danger"></span>
</div>
</form>
</div
</div>
</div>
</body>
<script>
$("#id_file").change(function () {
// 把当前图片,放到img标签中
// 把图片地址放到img标签上,标签就显示了图片
//$("#id_img")[0].src='https://tva1.sinaimg.cn/large/00831rSTly1gd1u0jw182j30u00u043b.jpg'
// 1 id_file这个标签的图片读出来,借助于文件阅读器,js提供的一个类
var reader=new FileReader()
// 2 拿到文件对象,赋值给一个变量
var file =$("#id_file")[0].files[0]
// 3 把文件读到文件阅读器中
reader.readAsDataURL(file)
// 4 等读完后,把文件阅读器的内容写到img标签上
//$("#id_img")[0].src=reader.result
reader.onload=function (){
//$("#id_img")[0].src=reader.result
$('#id_img').attr('src', reader.result)
}
})
</script>
</html>
注册功能后端
def register(request):
if request.method == 'GET':
register_form = RegisterForm()
# context: 上下文
return render(request, 'register.html', context={'form': register_form})
else: # post请求的时候
res = {'code': 100, 'msg': '注册成功'}
# 取出用户名密码,使用form校验数据,如果校验通过,存到数据库中,如果校验不通过,返回错误信息
register_form = RegisterForm(data=request.POST)
if register_form.is_valid():
# 数据字段自己的规则,局部钩子,全局钩子都校验过后,通过了
# 1 re_password字段,不存到数据库的,剔除
register_data = register_form.cleaned_data
register_data.pop('re_password')
# 2 头像:如果携带了要存,头像是文件,在request.FILES中
my_file = request.FILES.get('my_file')
if my_file:
register_data['avatar'] = my_file
# 2 存到数据库
UserInfo.objects.create_user(**register_data)
# UserInfo.objects.create_user(username=register_data.get('username'),) 等同于上面,但是麻烦
return JsonResponse(res)
else:
res['code'] = 101
res['msg'] = '注册失败'
res['errors'] = register_form.errors
return JsonResponse(res)
注册功能前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1 class="text-center">注册功能</h1>
<form id="id_form" >
{% csrf_token %}
{% for item in form %}
<div class="form-group">
<label for="{{ item.id_for_label }}">{{ item.label }}</label>
{{ item }}
<span class="pull-right text-danger"></span>
</div>
{% endfor %}
<div class="form-group">
<label for="id_file">头像
<img src="/static/img/default.png" alt="" height="80px" width="80px" style="margin-left: 10px"
id="id_img">
</label>
<input type="file" id="id_file" accept="image/*" style="display: none">
</div>
<div class="form-group text-center">
{# 如果input类型是submit或者button标签,放在form表单中,如果点提交,触发form的提交,如果我们写了ajax提交,会触发两次提交#}
<input type="button" value="注册" class="btn btn-danger" id="id_submit">
<span class="text-danger"></span>
</div>
</form>
</div>
</div>
</div>
</body>
<script>
$("#id_file").change(function () {
// 把当前图片,放到img标签中
// 把图片地址放到img标签上,标签就显示了图片
//$("#id_img")[0].src='https://tva1.sinaimg.cn/large/00831rSTly1gd1u0jw182j30u00u043b.jpg'
// 1 id_file这个标签的图片读出来,借助于文件阅读器,js提供的一个类
var reader = new FileReader()
// 2 拿到文件对象,赋值给一个变量
var file = $("#id_file")[0].files[0]
// 3 把文件读到文件阅读器中
reader.readAsDataURL(file)
// 4 等读完后,把文件阅读器的内容写到img标签上
//$("#id_img")[0].src=reader.result
reader.onload = function () {
//$("#id_img")[0].src=reader.result
$('#id_img').attr('src', reader.result)
}
})
// 当点击注册按钮,发送ajax请求到后端的注册功能
$("#id_submit").click(function () {
// 上传文件,借助于formdata对象
var formdata = new FormData()
// 方式一
/*formdata.append('username',$('#id_username').val())
formdata.append('password',$('#id_username').val())
formdata.append('re_password',$('#id_username').val())
formdata.append('my_file',$('#id_file')[0].files[0])
// csrftoken也要加上*/
// 方式二:借助于form表单批量处理
var data = $("#id_form").serializeArray()
console.log(data)
// 使用for循环,把data中得数据,转存到formdata中 jquery的each循环
$.each(data, function (i, v) {
//console.log("索引是:",i)
//console.log("值是:",v)
//console.log("------")
formdata.append(v.name, v.value)
})
// 文件单独再放进去
formdata.append('my_file', $('#id_file')[0].files[0])
// 使用ajax向后端发送请求
// 1 三种编码格式:urlencoded,form-data,json
// {name:lqz,age:19}--->name=lqz&age=19
$.ajax({
url: '/register/',
type: 'post',
processData: false,
contentType: false,
data: formdata,
success: function (data) {
console.log(data)
if(data.code==100){
// 表示注册成功,跳转到登录页面
location.href='/login/'
}else {
// 在前端显示错误信息
}
}
})
})
</script>
</html>
头像上传路径问题
# 咱们写项目后台静态资源中得图片,一般放在static文件夹下
# 用户上传的文件,图片等,一般放在media文件夹下
-我们想做的是,avatar文件夹要在media文件夹下
# django中只需要在配置文件中加一句话
# 以后再上传的文件路径是从media文件夹下开始
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
注册错误信息渲染
$("#id_submit").click(function () {
// 上传文件,借助于formdata对象
var formdata = new FormData()
// 方式一
/*
formdata.append('username',$('#id_username').val())
formdata.append('password',$('#id_username').val())
formdata.append('re_password',$('#id_username').val())
formdata.append('my_file',$('#id_file')[0].files[0])
// csrftoken也要加上
*/
// 方式二:借助于form表单批量处理
var data = $("#id_form").serializeArray()
console.log(data)
// 使用for循环,把data中得数据,转存到formdata中 jquery的each循环
$.each(data, function (i, v) {
//console.log("索引是:",i)
//console.log("值是:",v)
//console.log("------")
formdata.append(v.name, v.value)
})
// 文件单独再放进去
formdata.append('my_file', $('#id_file')[0].files[0])
// 使用ajax向后端发送请求
// 1 三种编码格式:urlencoded,form-data,json
// {name:lqz,age:19}--->name=lqz&age=19
$.ajax({
url: '/register/',
type: 'post',
processData: false,
contentType: false,
data: formdata,
success: function (data) {
console.log(data)
if(data.code==100){
// 表示注册成功,跳转到登录页面
location.href='/login/'
}else {
// 在前端显示错误信息
console.log(data)
// 两次密码不一致的错误渲染
/*
if(data.errors['__all__']){
$(".error").html(data.errors['__all__'][0])
}*/
// 其他标签的错误渲染
$.each(data.errors,function (k,v){
if (k=='__all__'){ //两次密码不一致的错误渲染
$(".error").html(v[0])
}else {
// 链式调用,在对应的input后的span中插入错误文字,把父div加入has-error类,整个框变红
$("#id_"+k).next().html(v[0]).parent().addClass('has-error')
}
})
// 起一个定时任务
setTimeout(function (){
// 把所有span的文字去掉,把父div中得has-error类去掉
$('.text-danger').html("").parent().removeClass('has-error')
},3000)
}
}
})
})
用户存在校验功能
后端校验接口
# 前端通过get把用户名传入,我们根据用户名查询数据库,如果用户名存在,返回存在,如果不存在,返回不存在
/check_username/?username=lqz
def check_username(request):
# /check_username/?username=lqz
res = {'code': 100, 'msg': '用户存在'}
username = request.GET.get('username')
user = UserInfo.objects.filter(username=username).first()
if user:
# 用户存在
return JsonResponse(res)
else:
# 用户不存在
res['code'] = 101
res['msg'] = '用户不存在'
return JsonResponse(res)
前端
// username输入框,失去焦点,触发ajax执行
$('#id_username').blur(function () {
$.ajax({
url: '/check_username/?username=' + $(this).val(),
type: 'get',
success: function (data) {
if (data.code == 100) {
// 在span中插入错误信息
{#alert(data.msg)#}
$('#id_username').next().html(data.msg)
}
}
})
})
登录页面搭建
1 创建login.html
2 登录后台,get返回该页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1 class="text-center">登录功能</h1>
<form id="id_form">
{% csrf_token %}
<div class="form-group">
<label for="id_username">用户名</label>
<input type="text" id="id_username" name="username" class="form-control">
</div>
<div class="form-group">
<label for="id_password">密码</label>
<input type="password" id="id_password" name="password" class="form-control">
</div>
<div class="form-group">
<label for="id_code">验证码</label>
<div class="row">
<div class="col-md-6">
<input type="text" id="id_code" name="code" class="form-control">
</div>
<div class="col-md-6">
<img src="/static/img/default.png" alt="" width="500px" height="40px">
</div>
</div>
</div>
<div class="form-group text-center">
<input type="button" value="登录" class="btn btn-danger" id="id_submit">
<span class="text-danger error"></span>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
自定义图片验证码
# 验证码:数字,大写字母,小写字母的组合 5位
# 第三方的模块,可以直接生成
# 自己写
# 方式一,直接返回一张图片
# 方式二:自己生成一张图片,返回
# 方式三:把生成的图片写到内存中
# 方法四:在图片上写文字
# 方式五:图片上写文字,字体是指定的字体,字的颜色随机
# 方式6 最终方案
def get_rgb():
return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
def get_code(request):
# 方式一,直接返回一张图片
# with open('./media/avatar/2.jpeg', 'rb') as f:
# data = f.read()
# return HttpResponse(data)
# 方式二:自己生成一张图片,返回 --->借助模块,pillow
# # rgb:三原色 red green blue
# # 宽度和高度
# # 三原色的色值 0-255
# #1 创建图片
# img=Image.new('RGB',(500,40),(0,255,255))
# #2 保存到本地
# with open('code.png','wb') as f:
# img.save(f)
# # 3 打开图片,返回给前端
# with open('./code.png', 'rb') as f:
# data = f.read()
# return HttpResponse(data)
# 方式三:把生成的图片写到内存中
# # 1 创建图片
# img = Image.new('RGB', (500, 40), (0, 0, 0))
# # 2 保存到内存
# byte_io = BytesIO()
# img.save(byte_io,'png') # 指定图片格式
# # 3 从BytesIO取出二进制,返回给前端
# return HttpResponse(byte_io.getvalue())
# 方法四:在图片上写文字
# # 1 创建图片
# img = Image.new('RGB', (500, 40), (0, 255, 0))
# # 2 在图片上写文字 ,相当于画板
# img_draw=ImageDraw.Draw(img)
# img_draw.text((0,0),"lqz")
# # 2 保存到内存
# byte_io = BytesIO()
# img.save(byte_io,'png') # 指定图片格式
# # 3 从BytesIO取出二进制,返回给前端
# return HttpResponse(byte_io.getvalue())
# 方式五:图片上写文字,字体是指定的字体,字的颜色随机
# img = Image.new('RGB', (500, 40), get_rgb())
#
# # 2 创建一个字体对象
# font=ImageFont.truetype('./static/font/xgdl.ttf',30)
# # 2 在图片上写文字 ,相当于画板
# img_draw = ImageDraw.Draw(img)
# img_draw.text((0, 0), "lqz",font=font)
# # 2 保存到内存
# byte_io = BytesIO()
# img.save(byte_io, 'png') # 指定图片格式
# # 3 从BytesIO取出二进制,返回给前端
# return HttpResponse(byte_io.getvalue())
# 第六:终极方案自己造 验证码
# img = Image.new('RGB', (500, 40), get_rgb())
img = Image.new('RGB', (500, 40), (255,255,255))
# 2 创建一个字体对象
font = ImageFont.truetype('./static/font/ss.TTF', 30)
# 3 在图片上写文字 ,相当于画板
img_draw = ImageDraw.Draw(img)
# 4 在图片上写文字(数字,大写字母,小写字母组合 5个)
code_str = ''
for i in range(5):
num = random.randint(0, 9)
upper = chr(random.randint(65, 90))
low = chr(random.randint(97, 122))
ran = str(random.choice([num, upper, low]))
code_str += ran # python 是强类型语言,不同类型直接不能直接相加,需要类型转换
img_draw.text((60+i*60, 0), ran, fill=get_rgb(),font=font)
request.session['code'] = code_str
#5 在图片上画点画线
width = 450
height = 30
for i in range(10):
x1 = random.randint(0, width)
x2 = random.randint(0, width)
y1 = random.randint(0, height)
y2 = random.randint(0, height)
# 在图片上画线
img_draw.line((x1, y1, x2, y2), fill=get_rgb())
for i in range(50):
# 画点
img_draw.point([random.randint(0, width), random.randint(0, height)], fill=get_rgb())
x = random.randint(0, width)
y = random.randint(0, height)
# 画弧形
# img_draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_rgb())
# 2 保存到内存
byte_io = BytesIO()
img.save(byte_io, 'png') # 指定图片格式
# 3 从BytesIO取出二进制,返回给前端
return HttpResponse(byte_io.getvalue())
点击图片验证码更新
$('#id_img').click(function (){
// 把图片的src地址变成原来地址?
//alert($(this)[0].src)
let timestamp=new Date().getTime()
$(this)[0].src='/get_code/?t='+timestamp
})
登录功能后端
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
else:
# 前端传入的用户名,密码,验证码, 校验验证码 去数据库比较用户名和密码
res = {'code': 100, 'msg': '成功'}
# 1 拿出用户名,密码,验证码,校验验证码
print(request.POST)
code = request.POST.get('code')
username = request.POST.get('username')
password = request.POST.get('password')
old_code = request.session.get('code')
if code.lower() == old_code.lower(): # 不区分大小写
# 2 校验用户名或密码
# user=UserInfo.objects.filter(username=username,password=password) # 这个不行
user = authenticate(username=username, password=password)
if user:
# 3 登录成功,返回json格式字符串
return JsonResponse(res)
else:
res['code'] = 101
res['msg'] = '用户名或密码错误'
return JsonResponse(res)
else:
res['code'] = 102
res['msg'] = '验证码错误'
return JsonResponse(res)
登录功能前端
$("#id_submit").click(function () {
// 取出填入的数据,发送ajax的post请求
//$('#id_form').serializeArray()
$.ajax({
url:'/login/',
type:'post',
data:{
'username':$('#id_username').val(),
'password':$('#id_password').val(),
'code':$('#id_code').val(),
'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val(), //根据属性取标签
},
success:function (data){
console.log(data)
if(data.code==100){
location.href='/'
}else {
$('.error').html(data.msg)
}
}
})
})
首页导航和首页轮播图
新建index.html---》写一个路由---》get请求返回index.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
<title>首页</title>
</head>
<body>
<div class="mynavbar">
<nav class="navbar navbar-default">
<div class="container-fluid">
<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="#">博客园</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">
<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="#">修改密码</a></li>
<li><a href="#">后台管理</a></li>
<li><a href="#">修改头像</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">退出</a></li>
</ul>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
</div>
<div class=container-fluid">
<div class="row">
<div class="col-md-2">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">广告招商</h3>
</div>
<div class="panel-body">
<div>
重金求子 <a href="">点我</a>
</div>
<hr>
<div>
重金求子 <a href="">点我</a>
</div>
<hr>
<div>
重金求子 <a href="">点我</a>
</div>
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">广告招商</h3>
</div>
<div class="panel-body">
<div>
重金求子 <a href="">点我</a>
</div>
<hr>
<div>
重金求子 <a href="">点我</a>
</div>
<hr>
<div>
重金求子 <a href="">点我</a>
</div>
</div>
</div>
</div>
<div class="col-md-7">
<div class="top">
<div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
<li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li>
<li data-target="#carousel-example-generic" data-slide-to="1"></li>
</ol>
<div class="carousel-inner" role="listbox">
<div class="item active">
<img src="/static/img/banner1.jpg" alt="...">
<div class="carousel-caption">
博客开博了
</div>
</div>
<div class="item">
<img src="/static/img/banner2.jpg" alt="...">
<div class="carousel-caption">
欢迎大家投稿
</div>
</div>
</div>
<!-- Controls -->
<a class="left carousel-control" href="#carousel-example-generic" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#carousel-example-generic" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
<div class="article">
文章详情
</div>
</div>
<div class="col-md-3">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">广告招商</h3>
</div>
<div class="panel-body">
<div>
重金求子 <a href="">点我</a>
</div>
<hr>
<div>
重金求子 <a href="">点我</a>
</div>
<hr>
<div>
重金求子 <a href="">点我</a>
</div>
</div>
</div>
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">广告招商</h3>
</div>
<div class="panel-body">
<div>
重金求子 <a href="">点我</a>
</div>
<hr>
<div>
重金求子 <a href="">点我</a>
</div>
<hr>
<div>
重金求子 <a href="">点我</a>
</div>
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title">广告招商</h3>
</div>
<div class="panel-body">
<div>
重金求子 <a href="">点我</a>
</div>
<hr>
<div>
重金求子 <a href="">点我</a>
</div>
<hr>
<div>
重金求子 <a href="">点我</a>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
首页文章列表页面
# admin 后台录入数据 文章表录入数据
# admin 是djagno的一个app,它是一个后台管理,内置的,可以方便我们做二次开发和录入数据
-在app的admin.py 把表注册一下 ,登录到后台就可以看到了
-直接录入数据,跟在数据表中录入数据本质一样的
开启media访问
# 上传的图片,目前访问不到,要开启media的访问,才能看到图片
# 开启media访问,本质就是把项目中某个目录可以让客户端(浏览器)直接访问,很危险,咱们只能开启media,以后meida下不能放重要的东西
# 本身static 文件夹从浏览器可以访问,默认这个目录已经开放了
# 如何开启
-1 在配置文件中
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
-2 在路由中
path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),
图片防盗链
# 有的网站有上传图片功能,我们可以把图片传上去,然后在自己的网站中使用,这样不会耗费我们自己网站的带宽,比如cnblogs,cnblogs就屏蔽了这种行为,这就是图片防盗链
# 本质原理是:
因为发送http请求,请求头中有个参数叫referer,是一个url地址,它表示上一次访问的地址
图片防盗链可以通过这个来控制,判断一下referer是不是我们自己的地址,如果不是就不返回
通过请求头中得referer来控制不被非自己的地址请求
标签:username,form,img,models,BBS,博客,多人,data,id
From: https://www.cnblogs.com/wwjjll/p/16705576.html