BBS博客系统(注册+登录)
BBS需求分析
1. 扩展auth_user表(继承AbstractUser) phone avatar create_time # 一对一站点表 2. 站点表 blog site_name 站点名称 site_title 站点标题 site_theme 站点主题 css/mycss.css 3. 标签表 name 标签名 # 一对多站点表 4. 分类表 name 分类名称 # 一对多站点表 5. 文章表 title 标题 desc 简介 content 内容 create_time 创建时间 #############优化字段######################### up_num 点赞数 down_num 点踩数 comment_num 评论数 blog = ForeignKey(to='Blog')# 一对多站点表 tags = ManyToMany() category = ForeignKey(to='Category') 6. 点赞点踩表 # 哪个用户给那篇文章点了赞还是踩 user ForeignKey(to='User') article ForeignKey(to='Article') is_up BooleanField() # 存0/1 7. 评论表 # 那个人给那篇文章什么时间评论了什么内容 user ForeignKey(to='User') article ForeignKey(to='Article') content create_time # 自关联 parent_id ForeignKey(to='Comment') # ORM提供的 parent_id ForeignKey(to='self') id user article parent_id 1 1 1 0 2 2 1 1 根评论与子评论的概念 # 根评论就是评论这篇文章的 # 子评论 1. PHP是世界上最好的语言 1.1 python才是 1.2 你滚蛋
{{ form.auto_id }} 自动获取form组件渲染的input框的id值
步骤:
数据表设计
forms组件代码书写
注册页面搭建
用户头像实时展示
注册功能实现
登录页面搭建(图片验证码)
登录功能实现
创建数据表
models.py
from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. class UserInfo(AbstractUser): phone = models.BigIntegerField(null=True) create_time = models.DateField(auto_now_add=True) # 该字段会将接受到文件自动存放到avatar文件夹下,只存该文件的路径 比如:avatar/111.png avatar = models.FileField(upload_to='avatar/',default='avatar/default.png') blog = models.OneToOneField(to='Blog',null=True) class Blog(models.Model): site_name = models.CharField(max_length=32) site_title = models.CharField(max_length=64) # 个人站点的样式文件 存该样式文件的路径 theme = models.CharField(max_length=64) class Category(models.Model): name = models.CharField(max_length=64) blog = models.ForeignKey(to='Blog',null=True) class Tag(models.Model): name = models.CharField(max_length=32) blog = models.ForeignKey(to='Blog',null=True) class Article(models.Model): title = models.CharField(max_length=64) desc = models.CharField(max_length=255) # 存大段文本 content = models.TextField() create_time = models.DateField(auto_now_add=True) # 查询优化 # 评论数 comment_num = models.IntegerField() # 点赞数 up_num = models.IntegerField() # 点踩数 down_num = models.IntegerField() blog = models.ForeignKey(to='Blog',null=True) category = models.ForeignKey(to='Category',null=True) tags = models.ManyToManyField(to='Tag',through='Article2Tags',through_fields=('article','tag')) class Article2Tags(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') # 存0/1 is_up = models.BooleanField() class Comment(models.Model): user = models.ForeignKey(to='UserInfo') article = models.ForeignKey(to='Article') content = models.CharField(max_length=255) create_time = models.DateField(auto_now_add=True)
# 跟评论和子评论一对多的关系
# 自关联
# parent_id = models.ForeignKey(to='Comment', null=True)
# ORM提供的自关联
parent = models.ForeignKey(to='self',null=True)
myform.py 注意需要在settings.py中添加
AUTH_USER_MODEL='app01.UserInfo'
from django import forms from django.forms import widgets from app01 import models class MyForm(forms.Form): username = forms.CharField(max_length=8,min_length=3,label='用户名',error_messages={ 'required':'用户名不能为空', 'max_length':'用户名最大8位', 'min_length':'用户名最小3位', },widget=widgets.TextInput(attrs={'class':'form-control'})) password = forms.CharField(max_length=8, min_length=3, label='密码',error_messages={ 'required': '密码不能为空', 'max_length': '密码最大8位', 'min_length': '密码最小3位', }, widget=widgets.PasswordInput(attrs={'class': 'form-control'})) confirm_password = forms.CharField(max_length=8, min_length=3, label='确认密码',error_messages={ 'required': '确认密码不能为空', 'max_length': '确认密码最大8位', 'min_length': '确认密码最小3位', }, widget=widgets.PasswordInput(attrs={'class': 'form-control'})) email = forms.EmailField(label='邮箱', error_messages={ 'required': '邮箱不能为空', 'invalid': '邮箱格式错误', }, widget=widgets.EmailInput(attrs={'class': 'form-control'})) # 局部钩子校验用户名是否存在 def clean_username(self): username = self.cleaned_data.get('username') user_obj = models.UserInfo.objects.filter(username=username).first() if user_obj: 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,redirect
from app01 import myforms
from app01 import models
from django.http import JsonResponse
from django.contrib import auth
from django.conf import settings
# Create your views here.
def register(request):
back_dic = {'code':100,'msg':''}
form_obj = myforms.MyForm()
if request.method == 'POST':
form_obj = myforms.MyForm(request.POST)
if form_obj.is_valid():
data = form_obj.cleaned_data
# 将confirm_password去掉
data.pop('confirm_password')
# 获取用户上传的文件对象
file_obj = request.FILES.get('myfile')
# 判断用户是否上传了自己的头像
if file_obj:
# 往data添加一组键值
data['avatar'] = file_obj
models.UserInfo.objects.create_user(**data)
back_dic['msg'] = '注册成功'
back_dic['url'] = '/login/'
else:
back_dic['code'] = 101
back_dic['msg'] = form_obj.errors
return JsonResponse(back_dic)
return render(request,'register.html',locals())
def login(request):
back_dic = {'code':100,'msg':''}
# 判断是ajax请求还是正常form表单请求 request.is_ajax()
if request.method == 'POST':
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(username=username,password=password)
if user_obj:
# 登录成功记录当前用户状态
auth.login(request,user_obj)
back_dic['msg'] = '登录成功'
back_dic['url'] = '/home/'
else:
back_dic['code'] = 102
back_dic['msg'] = '用户名或密码错误'
else:
back_dic['code'] = 103
back_dic['msg'] = '验证码错误'
return JsonResponse(back_dic)
return render(request,'login.html')
from PIL import Image,ImageDraw,ImageFont,ImageFilter
# Image用来生成图片 ImageDraw在图片上写'字' ImageFont字体样式
import random
from io import BytesIO
# 能够帮你保存数据 并且在取的时候会以二进制的形式返回给你
# 随机生成rgb参数
def get_random():
return random.randint(0,255),random.randint(0,255),random.randint(0,255)
def get_code(request):
# 推导步骤1:打开本地文件发送二进制数据
# with open(r'D:\fullstack_s4\BBS\avatar\002_wLZfWbk.jpg','rb') as f:
# data = f.read()
# return HttpResponse(data)
# 推导步骤2:动态生成图片发送二进制数据
# img_obj = Image.new('RGB',(310,35),'green') # 第三个参数既可以传颜色英文也可以传rgb参数
# img_obj = Image.new('RGB',(310,35),(128,128,128)) # 第三个参数既可以传颜色英文也可以传rgb参数
# # 先保存成文件
# with open('demo.png','wb') as f:
# img_obj.save(f)
# # 再以二进制模式读取发送数据
# with open('demo.png','rb') as f:
# data = f.read()
# return HttpResponse(data)
# 推导步骤3:图片颜色动态变化 图片存放不再依赖于文件的形式
# img_obj = Image.new('RGB',(310,35),get_random())
# # 生成一个BytesIO对象
# io_obj = BytesIO() # 将这个对象看成文件句柄
# img_obj.save(io_obj,'png') # 将图片数据存入内存管理器中 需要指定图片格式
# return HttpResponse(io_obj.getvalue()) # 将保存的数据以二进制的数据返回出来
# 最终不删改版
img_obj = Image.new('RGB',(310,35),get_random())
# 生成一个画笔对象
img_draw = ImageDraw.Draw(img_obj) # 你的画笔就可以在该图片上为所欲为
# 生成一个字体对象
img_font = ImageFont.truetype('static/font/mo.ttf',35)
# 随机验证码: 数字+小写字母+大写字母
code = '' # 定义一个变量存储最终验证码
for i in range(5):
random_int = str(random.randint(0,9))
random_lower = chr(random.randint(97,122))
random_upper = chr(random.randint(65,90))
temp_code = random.choice([random_int,random_lower,random_upper])
# 将产生的字一个一个的写到图片上
img_draw.text((60+i*45,0),temp_code,get_random(),img_font)
# code记录
code += temp_code
print(code)
# 将code存放到session表中
request.session['code'] = code
# 生成io对象
io_obj = BytesIO()
# 图片模糊
# img_obj = img_obj.filter(ImageFilter.BLUR)
img_obj.save(io_obj,'png')
return HttpResponse(io_obj.getvalue())
def home(request):
return render(request,'home.html')
def logout(request):
auth.logout(request)
return redirect('/home/')
def set_password(request):
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
confirm_password = request.POST.get('confirm_password')
# 先判断旧密码是否正确
res = request.user.check_password(old_password)
if res:
# 再来比对新密码是否一致
if new_password == confirm_password:
request.user.set_password(new_password)
request.user.save()
return redirect('/login/')
return render(request,'set_password.html')
register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css"> <script src="/static/bootstrap-3.3.7/js/bootstrap.min.js"></script> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <h2 class="text-center">注册</h2> <hr> <form id="myform"> {% csrf_token %} {% for form in form_obj %} <div class="form-group"> <label for="{{ form.auto_id }}">{{ form.label }}</label> {{ form }} <span class="errors pull-right" style="color: red"></span> </div> {% endfor %} </form> <div class="form-group"> <label for="id_myfile">头像 <img src="/static/img/default.png" alt="" width="80" style="margin-left: 20px" id="id_img"> </label> <input type="file" name="myfile" id="id_myfile" style="display: none"> </div> <button class="btn btn-primary pull-right" id="id_submit">注册</button> </div> </div> </div> <script> $('#id_myfile').change(function () { // 先获取用户上传的文件对象 let fileObj = this.files[0]; // 生成一个内置对象 let fileReader = new FileReader(); // 将文件对象传递给内置对象 fileReader.readAsDataURL(fileObj); // 将读取出文件对象替换到img标签 fileReader.onload = function(){ // 等待文件阅读器读取完毕再渲染图片 $('#id_img').attr('src',fileReader.result) } }); // ajax提交数据 $('#id_submit').click(function () { // 生成一个Formdata对象 let formData = new FormData(); // 往Formdata对象中添加键值 // console.log($('#myform').serializeArray()); $.each($('#myform').serializeArray(),function (index,obj) { // console.log(index,obj) formData.append(obj.name,obj.value) }); // 手动添加文件数据 formData.append('myfile',$('#id_myfile')[0].files[0]); $.ajax({ url:'', type:'post', data:formData, // 需要指定的两个参数 processData:false, contentType:false, success:function (data) { if (data.code == 100){ // 跳转到登录页面 location.href = data.url } else{ $.each(data.msg,function (index,obj) { // console.log(index,obj) let targetId = '#id_' + index; // id_username,id_password... $(targetId).next().html(obj[0]).parent().addClass('has-error') }) } } }) }); $('input').focus(function () { $(this).next().html('').parent().removeClass('has-error') }) </script> </body> </html>
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> <link rel="stylesheet" href="/static/bootstrap-3.3.7/css/bootstrap.min.css"> <script src="/static/bootstrap-3.3.7/js/bootstrap.min.js"></script> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-6 col-md-offset-3"> <h2 class="text-center">登录</h2> {% csrf_token %} <div class="form-group"> <label for="id_username">用户名</label> <input type="text" name="username" id="id_username" class="form-control"> </div> <div class="form-group"> <label for="id_password">密码</label> <input type="password" name="password" id="id_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" name="code" id="id_code" class="form-control"> </div> <div class="col-md-6"> <img src="/get_code/" alt="" width="310" height="35" id="id_img"> </div> </div> </div> <button class="btn btn-success" id="id_button">登录</button> <span class="errors" style="color: red" id="id_error"></span> </div> </div> </div> <script> $('#id_img').click(function () { // 获取图片src旧的路径 let oldPath = $(this).attr('src'); // 修改图片的src属性 $(this).attr('src',oldPath += '?') }); // ajax发送数据 $('#id_button').click(function () { $.ajax({ url:'', type:'post', data:{ 'username':$('#id_username').val(), 'password':$('#id_password').val(), 'code':$('#id_code').val(), // 'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val(), 'csrfmiddlewaretoken':'{{ csrf_token }}', }, success:function (data) { if(data.code == 100){ location.href = data.url }else{ $('#id_error').html(data.msg) } } }) }) </script> </body> </html>
#我们计算机上所有能够输出各式各样的字体样式,是因为有.ttf文件(需要下载,放入到static里去)