72-75-django-BBS项目开发
今日内容概要
bbs是一个前后端不分离的全栈项目,前端和后端都需要我们自己一步步的完成
- 表创建及同步
- 注册功能
- forms组件
- 用户头像前端实时展示
- ajax
- 登陆功能
- 自己实现图片验证码
- ajax
今日内容详细
BBS表关系图解
数据库表创建及同步
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 """ 2 由于django自带的sqlite数据库对日期不敏感,所以我们换成MySQL 3 """ 4 from django.db import models 5 6 # Create your models here. 7 """ 8 先写普通字段 9 之后再写外键字段 10 """ 11 from django.contrib.auth.models import AbstractUser 12 13 14 class UserInfo(AbstractUser): 15 phone = models.BigIntegerField(verbose_name='手机号',null=True) 16 # 头像 17 avatar = models.FileField(upload_to='avatar/',default='avatar/default.png',verbose_name='用户头像') 18 """ 19 给avatar字段传文件对象 该文件会自动存储到avatar文件下 然后avatar字段只保存文件路径avatar/default.png 20 """ 21 create_time = models.DateField(auto_now_add=True) 22 23 blog = models.OneToOneField(to='Blog',null=True) 24 25 26 class Blog(models.Model): 27 site_name = models.CharField(verbose_name='站点名称',max_length=32) 28 site_title = models.CharField(verbose_name='站点标题',max_length=32) 29 # 简单模拟 带你认识样式内部原理的操作 30 site_theme = models.CharField(verbose_name='站点样式',max_length=64) # 存css/js的文件路径 31 32 33 class Category(models.Model): 34 name = models.CharField(verbose_name='文章分类',max_length=32) 35 blog = models.ForeignKey(to='Blog',null=True) 36 37 38 class Tag(models.Model): 39 name = models.CharField(verbose_name='文章标签',max_length=32) 40 blog = models.ForeignKey(to='Blog', null=True) 41 42 43 class Article(models.Model): 44 title = models.CharField(verbose_name='文章标题',max_length=64) 45 desc = models.CharField(verbose_name='文章简介',max_length=255) 46 # 文章内容有很多 一般情况下都是使用TextField 47 content = models.TextField(verbose_name='文章内容') 48 create_time = models.DateField(auto_now_add=True) 49 50 # 数据库字段设计优化 51 up_num = models.BigIntegerField(verbose_name='点赞数',default=0) 52 down_num = models.BigIntegerField(verbose_name='点踩数',default=0) 53 comment_num = models.BigIntegerField(verbose_name='评论数',default=0) 54 55 # 外键字段 56 blog = models.ForeignKey(to='Blog', null=True) 57 category = models.ForeignKey(to='Category',null=True) 58 tags = models.ManyToManyField(to='Tag', 59 through='Article2Tag', 60 through_fields=('article','tag') 61 ) 62 63 64 class Article2Tag(models.Model): 65 article = models.ForeignKey(to='Article') 66 tag = models.ForeignKey(to='Tag') 67 68 69 class UpAndDown(models.Model): 70 user = models.ForeignKey(to='UserInfo') 71 article = models.ForeignKey(to='Article') 72 is_up = models.BooleanField() # 传布尔值 存0/1 73 74 75 class Comment(models.Model): 76 user = models.ForeignKey(to='UserInfo') 77 article = models.ForeignKey(to='Article') 78 content = models.CharField(verbose_name='评论内容',max_length=255) 79 comment_time = models.DateTimeField(verbose_name='评论时间',auto_now_add=True) 80 # 自关联 81 parent = models.ForeignKey(to='self',null=True) # 有些评论就是根评论
注册功能
myforms.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 # 书写针对用户表的forms组件代码 2 from django import forms 3 from app01 import models 4 5 6 class MyRegForm(forms.Form): 7 username = forms.CharField(label='用户名', min_length=3, max_length=8, 8 error_messages={ 9 'required': '用户名不能为空', 10 'min_length': "用户名最少3位", 11 'max_length': "用户名最大8位" 12 }, 13 # 还需要让标签有bootstrap样式 14 widget=forms.widgets.TextInput(attrs={'class': 'form-control'}) 15 ) 16 17 password = forms.CharField(label='密码', min_length=3, max_length=8, 18 error_messages={ 19 'required': '密码不能为空', 20 'min_length': "密码最少3位", 21 'max_length': "密码最大8位" 22 }, 23 # 还需要让标签有bootstrap样式 24 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) 25 ) 26 27 confirm_password = forms.CharField(label='确认密码', min_length=3, max_length=8, 28 error_messages={ 29 'required': '确认密码不能为空', 30 'min_length': "确认密码最少3位", 31 'max_length': "确认密码最大8位" 32 }, 33 # 还需要让标签有bootstrap样式 34 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}) 35 ) 36 email = forms.EmailField(label='邮箱', 37 error_messages={ 38 'required': '邮箱不能为空', 39 'invalid': '邮箱格式不正确' 40 }, 41 widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}) 42 ) 43 44 # 钩子函数 45 # 局部钩子:校验用户名是否已存在 46 def clean_username(self): 47 username = self.cleaned_data.get('username') 48 # 去数据库中校验 49 is_exist = models.UserInfo.objects.filter(username=username) 50 if is_exist: 51 # 提示信息 52 self.add_error('username', '用户名已存在') 53 return username 54 55 # 全局钩子:校验两次是否一致 56 def clean(self): 57 password = self.cleaned_data.get('password') 58 confirm_password = self.cleaned_data.get('confirm_password') 59 if not password == confirm_password: 60 self.add_error('confirm_password', '两次密码不一致') 61 return self.cleaned_data
views.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 """ 2 我们之前是直接在views.py中书写的forms组件代码 3 但是为了接耦合 应该将所有的forms组件代码单独写到一个地方 4 5 如果你的项目至始至终只用到一个forms组件那么你可以直接建一个py文件书写即可 6 myforms.py 7 但是如果你的项目需要使用多个forms组件,那么你可以创建一个文件夹在文件夹内根据 8 forms组件功能的不同创建不同的py文件 9 myforms文件夹 10 regform.py 11 loginform.py 12 userform.py 13 orderform.py 14 ... 15 """ 16 def register(request): 17 form_obj = MyRegForm() 18 if request.method == 'POST': 19 back_dic = {"code": 1000, 'msg': ''} 20 # 校验数据是否合法 21 form_obj = MyRegForm(request.POST) 22 # 判断数据是否合法 23 if form_obj.is_valid(): 24 # print(form_obj.cleaned_data) # {'username': 'jason', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'} 25 clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量 26 # 将字典里面的confirm_password键值对删除 27 clean_data.pop('confirm_password') # {'username': 'jason', 'password': '123', 'email': '123@qq.com'} 28 # 用户头像 29 file_obj = request.FILES.get('avatar') 30 """针对用户头像一定要判断是否传值 不能直接添加到字典里面去""" 31 if file_obj: 32 clean_data['avatar'] = file_obj 33 # 直接操作数据库保存数据 34 models.UserInfo.objects.create_user(**clean_data) 35 back_dic['url'] = '/login/' 36 else: 37 back_dic['code'] = 2000 38 back_dic['msg'] = form_obj.errors 39 return JsonResponse(back_dic) 40 return render(request,'register.html',locals()) 41 42 43 # 扩展 44 """ 45 一般情况下我们在存储用户文件的时候为了避免文件名冲突的情况 46 会自己给文件名加一个前缀 47 uuid 48 随机字符串 49 ... 50 """
register.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>注册</title> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> 8 <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> 9 <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> 10 </head> 11 <body> 12 <div class="container-fluid"> 13 <div class="row"> 14 <div class="col-md-8 col-md-offset-2"> 15 <h1 class="text-center">注册</h1> 16 <form id="myform"> <!--这里我们不用form表单提交数据 知识单纯的用一下form标签而已--> 17 {% csrf_token %} 18 {% for form in form_obj %} 19 <div class="form-group"> 20 <label for="{{ form.auto_id }}">{{ form.label }}</label> 21 {{ form }} 22 <span style="color: red" class="pull-right"></span> 23 </div> 24 {% endfor %} 25 <div class="form-group"> 26 <label for="myfile">头像 27 {% load static %} 28 <img src="{% static 'img/default.png' %}" id='myimg' alt="" width="100" style="margin-left: 10px"> 29 </label> 30 <input type="file" id="myfile" name="avatar" style="display: none" > 31 </div> 32 33 <input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit"> 34 </form> 35 </div> 36 </div> 37 </div> 38 39 <script> 40 $("#myfile").change(function () { 41 // 文件阅读器对象 42 // 1 先生成一个文件阅读器对象 43 let myFileReaderObj = new FileReader(); 44 // 2 获取用户上传的头像文件 45 let fileObj = $(this)[0].files[0]; 46 // 3 将文件对象交给阅读器对象读取 47 myFileReaderObj.readAsDataURL(fileObj) // 异步操作 IO操作 48 // 4 利用文件阅读器将文件展示到前端页面 修改src属性 49 // 等待文件阅读器加载完毕之后再执行 50 myFileReaderObj.onload = function(){ 51 $('#myimg').attr('src',myFileReaderObj.result) 52 } 53 }) 54 55 $('#id_commit').click(function () { 56 // 发送ajax请求 我们发送的数据中即包含普通的键值也包含文件 57 let formDataObj = new FormData(); 58 // 1.添加普通的键值对 59 {#console.log($('#myform').serializeArray()) // [{},{},{},{},{}] 只包含普通键值对#} 60 $.each($('#myform').serializeArray(),function (index,obj) { 61 {#console.log(index,obj)#} // obj = {} 62 formDataObj.append(obj.name,obj.value) 63 }); 64 // 2.添加文件数据 65 formDataObj.append('avatar',$('#myfile')[0].files[0]); 66 67 // 3.发送ajax请求 68 $.ajax({ 69 url:"", 70 type:'post', 71 data:formDataObj, 72 73 // 需要指定两个关键性的参数 74 contentType:false, 75 processData:false, 76 77 success:function (args) { 78 if (args.code==1000){ 79 // 跳转到登陆页面 80 window.location.href = args.url 81 }else{ 82 // 如何将对应的错误提示展示到对应的input框下面 83 // forms组件渲染的标签的id值都是 id_字段名 84 $.each(args.msg,function (index,obj) { 85 {#console.log(index,obj) // username ["用户名不能为空"]#} 86 let targetId = '#id_' + index; 87 $(targetId).next().text(obj[0]).parent().addClass('has-error') 88 }) 89 } 90 } 91 }) 92 }) 93 // 给所有的input框绑定获取焦点事件 94 $('input').focus(function () { 95 // 将input下面的span标签和input外面的div标签修改内容及属性 96 $(this).next().text('').parent().removeClass('has-error') 97 }) 98 </script> 99 </body> 100 </html>
登录功能
views.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 """ 2 img标签的src属性 3 1.图片路径 4 2.url 5 3.图片的二进制数据 6 7 我们的计算机上面致所有能够输出各式各样的字体样式 8 内部其实对应的是一个个.ttf结尾的文件 9 10 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 11 """ 12 13 14 def login(request): 15 return render(request,'login.html') 16 17 """ 18 图片相关的模块 19 pip3 install pillow 20 """ 21 from PIL import Image,ImageDraw,ImageFont 22 """ 23 Image:生成图片 24 ImageDraw:能够在图片上乱涂乱画 25 ImageFont:控制字体样式 26 """ 27 from io import BytesIO,StringIO 28 """ 29 内存管理器模块 30 BytesIO:临时帮你存储数据 返回的时候数据是二进制 31 StringIO:临时帮你存储数据 返回的时候数据是字符串 32 """ 33 import random 34 def get_random(): 35 return random.randint(0,255),random.randint(0,255),random.randint(0,255) 36 def get_code(request): 37 # 推导步骤1:直接获取后端现成的图片二进制数据发送给前端 38 # with open(r'static/img/111.jpg','rb') as f: 39 # data = f.read() 40 # return HttpResponse(data) 41 42 # 推导步骤2:利用pillow模块动态产生图片 43 # img_obj = Image.new('RGB',(430,35),'green') 44 # img_obj = Image.new('RGB',(430,35),get_random()) 45 # # 先将图片对象保存起来 46 # with open('xxx.png','wb') as f: 47 # img_obj.save(f,'png') 48 # # 再将图片对象读取出来 49 # with open('xxx.png','rb') as f: 50 # data = f.read() 51 # return HttpResponse(data) 52 53 # 推导步骤3:文件存储繁琐IO操作效率低 借助于内存管理器模块 54 # img_obj = Image.new('RGB', (430, 35), get_random()) 55 # io_obj = BytesIO() # 生成一个内存管理器对象 你可以看成是文件句柄 56 # img_obj.save(io_obj,'png') 57 # return HttpResponse(io_obj.getvalue()) # 从内存管理器中读取二进制的图片数据返回给前端 58 59 60 # 最终步骤4:写图片验证码 61 img_obj = Image.new('RGB', (430, 35), get_random()) 62 img_draw = ImageDraw.Draw(img_obj) # 产生一个画笔对象 63 img_font = ImageFont.truetype('static/font/222.ttf',30) # 字体样式 大小 64 65 # 随机验证码 五位数的随机验证码 数字 小写字母 大写字母 66 code = '' 67 for i in range(5): 68 random_upper = chr(random.randint(65,90)) 69 random_lower = chr(random.randint(97,122)) 70 random_int = str(random.randint(0,9)) 71 # 从上面三个里面随机选择一个 72 tmp = random.choice([random_lower,random_upper,random_int]) 73 # 将产生的随机字符串写入到图片上 74 """ 75 为什么一个个写而不是生成好了之后再写 76 因为一个个写能够控制每个字体的间隙 而生成好之后再写的话 77 间隙就没法控制了 78 """ 79 img_draw.text((i*60+60,-2),tmp,get_random(),img_font) 80 # 拼接随机字符串 81 code += tmp 82 print(code) 83 # 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到 84 request.session['code'] = code 85 io_obj = BytesIO() 86 img_obj.save(io_obj,'png') 87 return HttpResponse(io_obj.getvalue())
login.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>登录</title> 6 <meta name="viewport" content="width=device-width, initial-scale=1"> 7 <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> 8 <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> 9 <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script> 10 {% load static %} 11 </head> 12 <body> 13 <div class="container-fluid"> 14 <div class="row"> 15 <div class="col-md-8 col-md-offset-2"> 16 <h1 class="text-center">登陆</h1> 17 <div class="form-group"> 18 <label for="username">用户名</label> 19 <input type="text" name="username" id="username" class="form-control"> 20 </div> 21 <div class="form-group"> 22 <label for="password">密码</label> 23 <input type="password" name="password" id="password" class="form-control"> 24 </div> 25 <div class="form-group"> 26 <label for="">验证码</label> 27 28 <div class="row"> 29 <div class="col-md-6"> 30 <input type="text" name="code" id="id_code" class="form-control"> 31 </div> 32 <div class="col-md-6"> 33 <img src="/get_code/" alt="" width="430" height="35" id="id_img"> 34 </div> 35 </div> 36 37 </div> 38 <input type="button" class="btn btn-success" value="登陆"> 39 </div> 40 </div> 41 </div> 42 <script> 43 $("#id_img").click(function () { 44 // 1 先获取标签之前的src 45 let oldVal = $(this).attr('src'); 46 $(this).attr('src',oldVal += '?') 47 }) 48 </script> 49 </body> 50 </html>