表设计
'''
一个项目中最最最重要的不是业务逻辑的书写
而是前期表设计, 只要将表设计好了, 后续的功能书写才会一帆风顺
bbs表设计
1.用户表
继承AbstractUser
扩展
phone 电话号码
acatar 用户头像
create_time 创建时间
2.个人站点表
3.文章标签表
4.文章分类表
5.文章表
6.点赞点踩表
7.评论表
'''
数据库表创建及同步
由于django自带的sqlite数据库对日期不敏感,所以我们换成MySQL
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 = models.FileField(upload_to='avatar/', default='avatar/default.png', verbose_name='用户头像')
'''
给avatar字段传文件对象, 该文件会自动存储到avatar文件下 然后avatar字段只保存文件路径avatar/default.png
'''
create_time = models.DateField(auto_now_add=True, verbose_name='创建时间')
blog = models.OneToOneField('Blog', on_delete=models.CASCADE, 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 = models.CharField(max_length=64, verbose_name='站点样式') # 存css/js的文件路径
class Category(models.Model):
name = models.CharField(max_length=32, verbose_name='文章分类')
blog = models.ForeignKey('Blog', on_delete=models.CASCADE, null=True)
class Tag(models.Model):
name = models.CharField(max_length=32, verbose_name='文章标签')
blog = models.ForeignKey('Blog', on_delete=models.CASCADE, null=True)
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('Blog', on_delete=models.CASCADE, null=True)
category = models.ForeignKey('Category', on_delete=models.CASCADE, null=True)
tags = models.ManyToManyField('Tag',
through='Article2Tag',
through_fields=('article', 'tag')
)
class Article2Tag(models.Model):
article = models.ForeignKey('Article', on_delete=models.CASCADE)
tag = models.ForeignKey('Tag', on_delete=models.CASCADE)
class UpAndDown(models.Model):
user = models.ForeignKey('UserInfo', on_delete=models.CASCADE)
article = models.ForeignKey('Article', on_delete=models.CASCADE)
is_up = models.BooleanField(null=True, verbose_name='是否点赞')
class Comment(models.Model):
user = models.ForeignKey('UserInfo', on_delete=models.CASCADE)
article = models.ForeignKey('Article', on_delete=models.CASCADE)
content = models.CharField(max_length=255, verbose_name='评论内容')
comment_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
# 自关联
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True) # 有些评论是根评论
注册功能
'''
我们之前是直接在views.py中书写的forms组件代码
但是为了解耦合 应该将所有的forms组件单独写到一个对方
如果你的项目至始至终只用到一个forms组件那么你可以直接建一个py文件书写即可
myform.py
但是如果你的项目需要使用很多个forms组件, 那么你可以创建一个文件夹在文件夹内根据forms组件功能的不同创建不同的py文件
myforms文件夹
regform.py
loginform.py
userform.py
...
'''
'''def register(request)'''
def register(request):
form_obj = MyRegForm()
if request.method == "POST":
# 校验数据是否合法
form_obj = MyRegForm(request.POST)
# print(request.POST)
back_dic = {'code': 1000, 'msg': ''}
# 判断数据是否合法
# print('经过第一轮筛选')
if form_obj.is_valid():
print(form_obj.cleaned_data) # 多的参数不要 MyRegForm只会拿里面有的参数
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) # 创建普通用户 密码自动加密 **clean_data 将内部键值对数据打散 这样就完美符合UserInfo表中的数据了
back_dic['url'] = '/login/' # 存放等下要跳转到哪个页面
else:
back_dic['code'] = 2000
back_dic['msg'] = form_obj.errors # 若是没有通过 forms组件的规则 则将 form_obj.errors 错误信息 放到 back_dic['msg']来存储
return JsonResponse(back_dic) # 字典型数据需要序列化数据给前端
return render(request, 'register.html', locals())
'''
登陆功能
'''
img 标签的src属性
1.图片路径
2.url
3.图片的二进制数据
我们的计算机上面之所以能够输出各式各样的的字体样式, 内部其实对应的是一个个.ttf结尾的文件
http://www.zhaozi.cn/ai/2019/fontlist.php?ph=1&classid=32&softsq=%E5%85%8D%E8%B4%B9%E5%95%86%E7%94%A8
'''
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) # 后续可以通过request.的方式访问里面的属性
back_dic['url'] = '/home/'
else:
back_dic['code'] = 2000
back_dic['msg'] = '用户名或密码错误'
else:
back_dic['code'] = 3000 # 用ajax实现前后端校验 都是通过 一个单独的字典 该字典记录
back_dic['msg'] = '验证码错误' # 若登录成功 该字典里面就有code值为1000 和要跳转的url /home/(主页)
print(back_dic)
return JsonResponse(back_dic) # 若登录错误 该字典里面就有对应到底是用户名、密码错误还是验证码错误的信息 在msg键值里面
回顾注册功能
'''
1.书写了一个注册需要的forms组件
规律: 不同的功能代码应该解耦合,单独存储
1. 只有一个forms组件,那么可以直接创建一个py文件
2. 有多个forms组件,你可以创建文件夹,内部根据功能不同创建不同的py文件
2.利用forms组件渲染前端标签
1.我们不利用form表单提交而是用ajax提交
2.但是我们需要用到form标签来包含我们所以的获取用户数据的html代码
$('#form').serializeArray()
获取到form标签内所有用户普通键值对数据
[ {},{},{} ]
3.
只要是label标签里面的内容 点击都会跳转到label标签里面for指定的标签上
4.如何实时展示用户头像
1. 利用到了文件阅读器
2. change事件
3. onload等待加载完毕
5.一旦用户信息不合法 如何精确的渲染提示信息
1.forms组件渲染的标签id值都有一个固定的特点
id_字段名
ps:如何获取id值呢 form.auto_id
<label for="{{ form.auto_id }}">{{ form.label }}</label>
2.根据后端返回的字段以及字段对应的报错信息
自动手动拼接对应的字段的id值
'''
def register(request):
form_obj = MyRegForm()
if request.method == "POST":
# 校验数据是否合法
form_obj = MyRegForm(request.POST)
# print(request.POST)
back_dic = {'code': 1000, 'msg': ''}
# 判断数据是否合法
# print('经过第一轮筛选')
if form_obj.is_valid():
print(form_obj.cleaned_data) # 多的参数不要 MyRegForm只会拿里面有的参数
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) # 创建普通用户 密码自动加密 **clean_data 将内部键值对数据打散 这样就完美符合UserInfo表中的数据了
back_dic['url'] = '/login/' # 存放等下要跳转到哪个页面
else:
back_dic['code'] = 2000
back_dic['msg'] = form_obj.errors # 若是没有通过 forms组件的规则 则将 form_obj.errors 错误信息 放到 back_dic['msg']来存储
return JsonResponse(back_dic) # 字典型数据需要序列化数据给前端
return render(request, 'register.html', locals())
首页搭建
# 动态展示用户名称
{% 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="#">修改密码</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>
{% else %}
<li><a href="{% url 'login' %}">登录</a></li> <!--反向解析-->
<li><a href="{% url 'reg' %}">注册</a></li>
{% endif %}
admin后台管理
'''
django给你提供了一个可视化的界面用来让你方便的对你的模型表进行数据的增删改查操作
如果你想要使用admin后台管理操作模型表
你需要先注册你的模型并 告诉admin你需要操作哪些表
去你的应用下的admin.py中注册你的模型表
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会给每一个注册了的模型表自动生成增删改查四条url
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/ 查
下面每个表都是这种
# 1. 数据绑定尤其需要注意的是用户和个人站点不要忘记绑定了
# 2.标签
# 3.标签和文章
用户头像展示
1.网站所使用的静态文件默认放在static文件夹下
2.用户上传的静态文件也应该单独放在某个文件夹下
media配置
该配置可以让用户上传的所有文件都固定存放在某一个指定的文件夹下
# 配置用户上传的文件存储位置
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 文件名随你自己
会自动创建多级目录
如何开设后端指定文件夹资源
首先你需要自己去urls.py书写固定代码
# 暴露后端指定文件夹资源 (固定写法)
re_path(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
图片防盗链
# 如何避免别的网站直接通过本网站的url访问本网站资源
# 简单的防盗
我们可以做到请求来到时候先看看当前请求是从哪个网站过来的
如果是本网站那么可以正常访问
如果是其它网站直接拒绝
请求头里面有一个专门记录请求来自于哪个网站的参数
Referer: http://127.0.0.1:8000/xsfsd/
# 如何避免
1.要么修改请求头referer
2.直接写爬虫把对方网址的所有资源直接下载到我们自己的服务器上
个人站点
# 全是每个用户都可以有自己的站点样式
内部给每个人开设了可以自定义css和js的文件接口并且用户自定义后会将用户的文件保存下来,只后在打开用户的界面时候会自动加载用户自己写的css和js从而实现每个用户界面的不一样的情况
3.侧边栏展示问题
只要你orm学的没有问题 再加上url处理的好 就没有上面问题
media配置
'''
网址所使用到的静态资源默认是放在static文件夹下
用户上传的静态文件也应该单独找个位置存放
media配置
setting.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
用户上传文件会自动创建media文件夹然后在该文件夹内存储数据
'''
如何自定义暴露后端资源
你需要自己在urls.py中书写代码
from django.views.static import serve
from BB import settings
# 固定写法 不要改动
re_path(r'^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
文章详情页
# url设计
/username/article/1
# 先验证url是否会被其它url顶替
# 文章详情页和个人站点页基本一致 所以用模板的继承
# 侧边栏的渲染需要传入数据才能渲染 并且该侧边栏再很多页面都需要使用
1.哪个地方用就拷贝需要的代码(不推荐 有点繁琐)
2.将侧边栏制作成inclusion_tag
'''
步骤
1.在应用下创建一个名字“必须”叫templatetags文件夹
2.在该文件夹内创建任意名称 的py文件
3.在该py文件内必须先书写下面两句话
from django import template
register = template.Library()
# 自定义过滤器、
# 标签、
# inclusion_tag
'''
文章点赞点踩
'''
浏览器上你看到的花里胡哨的页面, 内部都是HTML代码(前端)代码
那现在在我们的文章内容应该写什么? 》》 html代码
如何拷贝文章
copy outerhtml
1.拷贝文章
2.拷贝点赞点踩
1.拷贝前端点赞点踩图标
2.拷贝html和css
由于图片有防盗链 所以将图片直接下载到本地
如何区分用户是否点了赞还是踩
1.给标签各自绑定一个事件
两个标签对应的代码其实是一样的, 仅仅是是否点赞点踩这一个参数不一样
2.二合一
给两个标签绑定一个事件
// 给所有的action类绑定事件
$('.action').click(function () {
alert($(this).hasClass('diggit') ) // 谁点就是谁
})
由于点赞点踩内部有一定的业务逻辑,所以后端单独开设视图函数处理
'''
写代码先把所有正确的逻辑写完在去考虑错误的逻辑
文章评论
我们先写跟评论
再写子评论
点击评论按钮需要将评论框里面的内容清空
根评论有两步渲染方式
1.DOM临时渲染
2.页面刷新render渲染
子评论
点击回复按钮发生了几件事
1.评论框自动聚焦
2.将回复按钮所在的那一行评论人的姓名
@username
3.评论框内部自动换行
添加文章
有两个需要注意的问题
1.文章的简介
不能直接切去
应该先想办法获取到当前页面的文本内容之后截取150个文本字符
2.XSS攻击
针对支持用户直接编写html代码的网址
针对用户直接书写的script标签 我们需要处理
1.注释标签内部的内容
2.直接将script删除
如何解决?
针对1 后端通过正则表达式筛选
针对2 首先需要确定及获取script
beautifulsoup模块 bs4模块
专门用来帮你处理html页面
# 模块使用
soup = BeautifulSoup(content, 'lxml') # 使用爬虫 bs4 解析
print(soup)
tags = soup.find_all() # 获取文章页面所有的html标签
for tag in tags:
if tag.name == 'script':
# 删除标签
tag.decompose()
# 文章简介
# 1 先简单暴力的直接切取content 150 个字符
desc = soup.text[0:150]
kindeditor富文本编辑器
编辑器的种类有很多,你可以自己去网上搜索
编辑器上传图片
别人写好了接口 但是接口不是你自己的
你需要手动去修改
在使用别人的框架或者模块的时候 出现了问题不要慌 看看文档可能会有对应的处理方法