72-75-django-BBS项目开发

今日内容概要

bbs是一个前后端不分离的全栈项目,前端和后端都需要我们自己一步步的完成

  • 表创建及同步
  • 注册功能
    • forms组件
    • 用户头像前端实时展示
    • ajax
  • 登陆功能
    • 自己实现图片验证码
    • ajax

今日内容详细

BBS表关系图解

数据库表创建及同步

 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

 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
myforms.py

views.py

 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': '[email protected]'}
25             clean_data = form_obj.cleaned_data  # 将校验通过的数据字典赋值给一个变量
26             # 将字典里面的confirm_password键值对删除
27             clean_data.pop('confirm_password')  # {'username': 'jason', 'password': '123', 'email': '[email protected]'}
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 """
views.py

register.html

  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>
register.html

登录功能

views.py

 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())
views.py

login.html

 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>
login.html

猜你喜欢

转载自www.cnblogs.com/wgwg/p/13089951.html