BBS项目博客论坛系统
1.BBS项目开发流程
1 # 1.需求分析 2 架构师+产品经理+开发者组长 3 在跟客户谈需求之前,会大致先了解客户的需求,然后自己先设计一套比较好写方案 4 在跟客户沟通交流中引导客户往我们之前想好的方案上面靠 5 形成一个初步的方案 6 7 # 2.项目设计 8 架构师干的活 9 编程语言选择 10 框架选择 11 数据库选择 12 主库:MySQL,postgreSQL,... 13 缓存数据库:redis、mongodb、memcache... 14 功能划分 15 将整个项目划分成几个功能模块 16 找组长开会 17 给每个组分发任务 18 项目报价 19 技术这块需要多少人,多少天(一个程序员一天1500~2000计算(大致)) 20 产品经理公司层面 再加点钱 21 公司财务签字确认 22 公司老板签字确认 23 产品经理去跟客户沟通 24 25 后续需要加功能 继续加钱 26 27 # 3.分组开发 28 组长找组员开会,安排各自功能模块 29 我们其实就是在架构师设计好的框架里面填写代码而已(码畜) 30 31 我们在写代码的时候 写完需要自己先测试是否有bug 32 如果是一些显而易见的bug,你没有避免而是直接交给了测试部门测出来 33 那你可能就需要被扣绩效了(一定要跟测试小姐姐搞好关系) 34 薪资组成 15K(合理合规合法的避税) 35 底薪 10K 36 绩效 3K 37 岗位津贴 1K 38 生活补贴 1K 39 40 # 4.测试 41 测试部门测试你的代码 42 压力测试 43 ... 44 # 5.交付上线 45 1.交给对方的运维人员 46 2.直接上线到我们的服务器上 收取维护费用 47 3.其他... 48
2.表设计
1 """ 2 一个项目中最最最重要的不是业务逻辑的书写 3 而是前期的表设计,只要将表设计好了,后续的功能书写才会一帆风顺 4 5 bbs表设计 6 1.用户表 7 继承AbstractUser 8 扩展 9 phone 电话号码 10 avatar 用户头像 11 create_time 创建时间 12 13 外键字段 14 一对一个人站点表 15 16 2.个人站点表 17 site_name 站点名称 18 site_title 站点标题 19 site_theme 站点样式 20 21 3.文章标签表 22 name 标签名 23 24 外键字段 25 一对多个人站点 26 27 4.文章分类表 28 name 分类名 29 30 外键字段 31 一对多个人站点 32 33 5.文章表 34 title 文章标题 35 desc 文章简介 36 content 文章内容 37 create_time 发布时间 38 39 数据库字段设计优化(******) 40 (虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率低) 41 up_num 点赞数 42 down_num 点踩数 43 comment_num 评论数 44 45 外键字段 46 一对多个人站点 47 多对多文章标签 48 一对多文章分类 49 50 51 52 6.点赞点踩表 53 记录哪个用户给哪篇文章点了赞还是点了踩 54 user ForeignKey(to="User") 55 article ForeignKey(to="Article") 56 is_up BooleanField() 57 58 1 1 1 59 1 2 1 60 1 3 0 61 2 1 1 62 63 64 7.文章评论表 65 记录哪个用户给哪篇文章写了哪些评论内容 66 user ForeignKey(to="User") 67 article ForeignKey(to="Article") 68 content CharField() 69 comment_time DateField() 70 # 自关联 71 parent ForeignKey(to="Comment",null=True) 72 # ORM专门提供的自关联写法 73 parent ForeignKey(to="self",null=True) 74 75 id user_id article_id parent_id 76 1 1 1 77 2 2 1 1 78 79 根评论子评论的概念 80 根评论就是直接评论当前发布的内容的 81 82 子评论是评论别人的评论 83 1.PHP是世界上最牛逼的语言 84 1.1 python才是最牛逼的 85 1.2 java才是 86 87 根评论与子评论是一对多的关系 88 89 90 """ 91
3.models表创建
由于在用户表中我们需要扩展额外的字段
所以我们需要自己在 models.py 文件中创建一个 UserInfo 表,来代替auth模块创建的User表
在 settings.py 配置文件中进行如下配置:
# settings.py AUTH_USER_MODEL = 'app01.UserInfo' # 应用名.类名 2 # 指定用UserInfo表扩展User表
由于Django中自带的sqlite数据库对数据不敏感,所以我们需要将数据库换成MySQL。在BBS项目中,我们需要创建8张表。注意:我们在创建表的时候,通常先创建普通字段,然后再创建外键字段。
1 from django.db import models 2 from django.contrib.auth.models import User,AbstractUser 3 4 # Create your models here. 5 6 class UserInfo(AbstractUser): # 扩展表需要继承AbstractUser类 7 phone = models.BigIntegerField(verbose_name='手机号', null=True) 8 avatar = models.FileField(upload_to='avatar/',default='avatar/default.png',verbose_name='用户头像') 9 create_time = models.DateTimeField(auto_now_add=True) 10 # 外键字段 11 blog = models.OneToOneField(to='Blog',null=True) # 用户表跟站点表是一对一关系 12 13 14 class Blog(models.Model): 15 site_name = models.CharField(max_length=32,verbose_name='站点名') 16 site_title = models.CharField(max_length=64,verbose_name='站点标题') 17 site_theme = models.CharField(max_length=64,verbose_name='站点样式') # 存css/js链接 18 19 20 class Category(models.Model): 21 name = models.CharField(max_length=32,verbose_name='文章分类') 22 # 外键字段 23 blog = models.ForeignKey(to='Blog') # 一个站点有多个分类,外键在多的一方 24 25 class Tag(models.Model): 26 name = models.CharField(max_length=32,verbose_name='文章标签') 27 # 外键字段 28 blog = models.ForeignKey(to='Blog',null=True) # 一个站点有多个标签,外键在多的一方 29 30 class Article(models.Model): 31 title = models.CharField(max_length=32,verbose_name='文章标题') 32 desc = models.CharField(max_length=255,verbose_name='文章简介') 33 content = models.TextField(verbose_name='文章内容') 34 create_time = models.DateTimeField(auto_now_add=True) 35 # 数据库字段优化,如果每次都用联表查询点赞/踩数、评论数,对数据库压力太大,所以可以用普通字段 36 up_num = models.BigIntegerField(default=0,verbose_name='点赞数') 37 down_num = models.BigIntegerField(default=0,verbose_name='点踩数') 38 comment_num = models.BigIntegerField(default=0,verbose_name='留言数') 39 # 外键字段 40 blog = models.ForeignKey(to='Blog',null=True) # 一个站点有多篇文章 41 category = models.ForeignKey(to='Category',null=True) # 一个分类有多篇文章 42 tags = models.ManyToManyField(to='Tag', 43 through='Tag2Article', 44 through_fields=('article','tag')) 45 46 47 48 class UpAndDown(models.Model): 49 user = models.ForeignKey(to='UserInfo') 50 article = models.ForeignKey(to='Article') 51 is_up = models.BooleanField() # 传布尔值,存0/1 52 53 54 class Comment(models.Model): 55 user = models.ForeignKey(to='UserInfo') 56 article = models.ForeignKey(to='Article') 57 comment_content = models.CharField(max_length=255,verbose_name='评论内容') 58 comment_time = models.DateTimeField(auto_now_add=True,verbose_name='评论时间') 59 # 自关联 60 parent = models.ForeignKey(to='self',null=True) 61 62 63 class Tag2Article(models.Model): 64 article = models.ForeignKey(to='Article') 65 tag = models.ForeignKey(to='Tag')
一、注册功能实现
4.1.创建注册功能 forms 组件
为了项目的解耦合程度,通常我们需要有一个地方用于存储 forms 组件代码,如果我们的项目至始至终只用到一个 forms 组件那么你可以直接建一个py文件书写即可(这里我们就只创建一个 myforms.py 文件),但是如果你的项目需要使用多个 forms 组件,那么你可以创建一个文件夹在文件夹内根据 orms 组件功能的不同创建不同的py文件如下

myforms.py
1 from django import forms 2 from app01 import models 3 4 5 class MyRegisterForm(forms.Form): 6 username = forms.CharField(label='用户名', max_length='8', min_length=3, 7 error_messages={ 8 'required': '用户名不能为空', 9 'max_length': '用户名最多8位', 10 'min_length': '用户名最少3位', }, 11 widget=forms.widgets.TextInput(attrs={'class': 'form-control'})) 12 13 email = forms.EmailField(label='邮箱', 14 error_messages={ 15 'invalid': '邮箱格式不正确', 16 'required': '邮箱密码不能为空', 17 }, 18 widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})) 19 20 password = forms.CharField(label='密码', max_length=8, min_length=3, 21 error_messages={ 22 'required': '密码不能为空', 23 'max_length': '密码最多8位', 24 'min_length': '密码最少3位', 25 }, 26 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})) 27 28 re_password = forms.CharField(label='确认密码', max_length=8, min_length=3, 29 error_messages={ 30 'required': '确认密码不能为空', 31 'max_length': '确认密码最多8位', 32 'min_length': '确认密码最少3位', 33 }, 34 widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})) 35 36 def clean_username(self): 37 username = self.cleaned_data.get('username') 38 is_exists = models.UserInfo.objects.filter(username=username) 39 if is_exists: 40 self.add_error('username', '用户名已存在!') 41 return username # 将数据返回给username 42 43 def clean(self): 44 password = self.cleaned_data.get('password') 45 re_password = self.cleaned_data.get('re_password') 46 if not password == re_password: 47 self.add_error('re_password', '两次密码不一致!') 48 return self.cleaned_data
register.html
1 # myforms.py 组件代码写好后, 2 # 我们需要在 views.py 中实例化出一个 form_obj对象, 3 # 然后通过 render 传给前端,通过 forms 组件渲染搭建注册页面, 4 5 # 注意:这里我们不用 form 表单提交数据, 6 # 而是用 ajax 进行前后端数据交互,不需要用到 form 表单提交数据, 7 # 但是我们需要用 form 标签来包含我们所有获取数据的html代码, 8 # 并在 form 标签中定义一个id, 9 # 通过$('#id_form').serializeArray()获取到form标签内所有用户普通键值对的数据,数据格式为列表套字典的格式[{},{},{},{}]
1 DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Titletitle> 6 {% load static %} 7 <script src="{% static 'jquery-3.6.0.min.js' %}">script> 8 <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}"> 9 <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}">script> 10 <link rel="stylesheet" href="{% static 'dist/sweetalert.css' %}"> 11 <script src="{% static 'dist/sweetalert.min.js' %}">script> 12 <script src="{% static 'js/mysetup.js' %}">script> 13 14 head> 15 <body> 16 <div class="container"> 17 <div class="row"> 18 <div class="col-md-6 col-md-offset-3"> 19 <h3 class="text-center">注册h3> 20 <form id="id_form"> 21 {% csrf_token %} 22 {% for form in form_obj %} 23 <div class="form-group"> 24 <label for="{{ form.auto_id }}">{{ form.label }}label> 25 {{ form }} 26 <span style="color: red" class="pull-right">span> 27 div> 28 {% endfor %} 29 <label for="id_file"> 30 <p>上传文件p> 31 <img src="{% static 'img/default.png' %}" alt="" id="id_img" width="100px"> 32 label> 33 <input type="file" id="id_file" name="avatar" style="display: none"> 34 <input type="button" id="id_btn" value="注册" class="btn btn-info btn-block"> 35 form> 36 div> 37 div> 38 div> 39 40 <script> 41 // 实时渲染头像 可以为头像添加文本域变换事件 42 $('#id_file').change(function(){ 43 // 要先读取更换的文件的信息 所以要用到文件阅读器 44 let fileReaderObj = new FileReader(); //生成文件阅读器 DOM对象 45 let fileObj = $(this)[0].files[0]; // 获取文件对象 46 fileReaderObj.readAsDataURL(fileObj) // fileObj传给阅读器读取 IO操作 异步操作 47 // 上面一步操作属于IO操作 又属于异步操作 所以我们需要等到文件读取完毕 再进行下一步操作 48 fileReaderObj.onload = function(){ 49 // 文件阅读器读取加载完毕后 我们需要修改img的src属性 达到头像实时渲染的目的 50 $('#id_img').attr('src',fileReaderObj.result) // 修改src的值为fileReaderObj读取的结果 51 // attr()内如果只有一个参数就是获取该属性的值,如果有两个,就是修改值 52 } 53 }) 54 55 56 // ajax提交数据 57 $('#id_btn').click(function(){ 58 // 因为ajax提交不止有普通键值对数据 还有头像图片的文件数据 59 // 所以我们需要发送FormData编码格式的数据 需要借助FormData对象 60 let formDataObj = new FormData() 61 // 如何获取form表单的数据 这里需要用到jQuery中的序列化方法 serializeArray 62 // console.log($('#id_form').serializeArray()) // 数据格式:[{},{},{}...] 63 // 通过$.each()来循环取值 可以得到一个个字典 {name: 'username', value: ''} 64 $.each($('#id_form').serializeArray(),function(index,obj){ 65 // console.log(index,obj) // obj得到的就是字典 66 // 将字典对应的值添加到formDataObj对象中 67 formDataObj.append(obj.name,obj.value) 68 }); 69 // 将头像的文件数据也添加到formDataObj对象中 70 formDataObj.append('avatar',$('#id_file')[0].files[0]) 71 // 将数据通过ajax请求发送给后端 72 $.ajax({ 73 url:'', // 不写默认朝当前地址提交 74 type:'post', 75 data:formDataObj, 76 processData:false, // ajax发送formData数据一定要加的两个参数之一 77 contentType:false, // ajax发送formData数据一定要加的两个参数之一 78 success:function(args){ 79 if(args.code === 1000){ 80 window.location.href = args.url 81 }else { 82 // 因为我们用的是ajax提交数据 错误信息无法像用form表单提交一样返回给对应标签 83 // 所以我们需要获取对应的标签的id 才能指定该标签返回错误信息 84 // 通过观察forms组件渲染出来的input标签 发现input框的id值都为 id_字段名 的格式 85 // 因此我们就可以手动拼接来得到input框对应的id 而循环后端返回过来的args.msg可以获取错误的字段名 86 $.each(args.msg,function(name,error_msg){ 87 // console.log(name,error_msg) // 数据格式: username ['用户名最少3位'] 88 let targetId = '#id_' + name // 直接在id前加#号 89 // 通过jQuery链式调用获取对应的标签 并且将对应的input框颜色变红 90 $(targetId).next().text(error_msg).parent().addClass('has-error') 91 }) 92 93 } 94 } 95 }) 96 }) 97 98 // 通过input框文本聚焦 移除错误信息和边框颜色 99 $('input').focus(function(){ 100 $(this).next().text('').parent().removeClass('has-error') 101 }) 102 script> 103 104 body> 105 html>
views.py
register.html 页面搭建完毕后,我们通过 ajax 提交数据 然后再 views.py 中书写代码如下:
1 def register(request): 2 form_obj = myforms.MyRegForm() 3 if request.is_ajax(): 4 # 用ajax前后端交互 必须定义一个字典 用于给前端传输数据 5 back_dict = {'code':1000,'msg':''} 6 if request.method == 'POST': 7 # 将数据传给form组件进行校验 8 form_obj = myforms.MyRegForm(request.POST) 9 if form_obj.is_valid(): 10 # {'username': 'yesir', 'email': '983410683@qq.com', 'password': '123', 're_password': '123'} 11 # print(form_obj.cleaned_data) 12 cleaned_data = form_obj.cleaned_data 13 cleaned_data.pop('re_password') 14 # 通过request.FILES获取头像文件的数据 15 file_obj = request.FILES.get('avatar') 16 # 判断头像文件是否有值 如果没有说明用户没传 就用默认头像 17 if file_obj: 18 cleaned_data['avatar'] = file_obj # 这里的键一定要是models.UserInfo中的avatar字段 19 models.UserInfo.objects.create_user(**cleaned_data) # 拆包操作 直接将数据一次性存入数据库 20 back_dict['url'] = '/app01/login/' # 如果成功 默认code为1000 然后跳转到url路径 21 else: 22 back_dict['code'] = 1001 23 back_dict['msg'] = form_obj.errors # 直接将错误信息返回给前端 24 return JsonResponse(back_dict) 25 return render(request,'register.html',locals())
这样,我们就完成了整个注册功能的搭建。
二、登录功能实现
1 # 搭建登录页面的时候我们需要用到验证码 2 # 所以我们在视图函数中创建出随机验证码的图片 3 # 然后html页面通过访问验证码对应的路由路径 得到该验证码 4 # 并通过验证码点击事件实现验证码刷新 5 # 通过ajax提交数据 并在视图层编写逻辑代码进行 交由数据库验证 6 # 将验证结果通过字典返回给html页面的回调函数 7 # 回调函数接收到数据信息 做相应的操作
urls.py
1 from django.conf.urls import url 2 from app01 import views 3 4 urlpatterns = [ 5 # url(r'^admin/', admin.site.urls), 6 # 注册 7 url(r'^reg/', views.register), 8 # 登录 9 url(r'^login/',views.login), 10 # 登录验证码 11 url(r'^get_code/',views.get_code), 12 13 ]
views.py
1 图片验证码如何手动完成 2 # img标签src属性后面可以写的内容 3 # 1.直接写网络图片地址 4 # 2.仅仅只是一个url后缀(自动朝该url发送get请求获取数据) 5 # 3.图片二进制数据 6 7 # 1.需要借助于pillow模块 8 # Image,ImageDraw,ImageFont 9 # 2.需要借助于内存管理器io模块 10 # BytesIo,StringIO 11 # 3.字体样式其实是受.ttf结尾的文件控制的 12 # 4.手动产生随机验证码(搜狗公司的笔试题) 13 # random模块 14 # chr内置方法 15 # 在session中保存验证码
1 from django.shortcuts import render,HttpResponse,redirect 2 from django.http import JsonResponse 3 from app01 import myforms 4 from app01 import models 5 from PIL import Image,ImageFont,ImageDraw 6 from io import BytesIO,StringIO 7 import random 8 from django.contrib import auth 9 10 11 from app01 import models 12 from django.http import JsonResponse 13 from django.contrib import auth 14 import random 15 from PIL import Image,ImageDraw,ImageFont 16 """ 17 Image:生成图片 18 ImageDraw:能够在图片上乱涂乱画 19 ImageFont:控制字体样式 20 """ 21 from io import BytesIO,StringIO 22 """ 23 内存管理器模块 24 BytesIO:临时帮你存储数据 返回的时候数据是二进制 25 StringIO:临时帮你存储数据 返回的时候数据是字符串 26 """ 27 28 29 def get_random(): 30 # return会默认将他们封装到一个元组中 31 return random.randint(0,255),random.randint(0,255),random.randint(0,255) 32 33 def get_code(request): 34 # # 推导步骤1:直接获取后端现成的图片二进制数据发送给前端 35 # with open(r'static/img/222.jpg','rb') as f: 36 # data = f.read() 37 # return HttpResponse(data) 38 39 # 推导步骤2:利用pillow模块动态产生图片 40 # 生成一个Image对象,固定写法,传入的尺寸要跟页面定义尺寸一致 41 # img_obj = Image.new('RGB',(360,35),'green') 42 # img_obj = Image.new('RGB',(360,35),(188,222,252)) 43 # 利用random模块随机生成图片颜色 44 # img_obj = Image.new('RGB',(360,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',(360,35),get_random()) 55 # 生成一个内存管理器 可以看成是文件句柄fp 56 # io_obj = BytesIO() 57 # 把图片对象存入io_obj,以指定格式 58 # img_obj.save(io_obj,'png') 59 # # 从内存管理器中读取二进制的图片数据返回给前端 60 # return HttpResponse(io_obj.getvalue()) 61 62 # 最终步骤4:写图片验证码 63 img_obj = Image.new('RGB', (430, 35), get_random()) 64 img_draw = ImageDraw.Draw(img_obj) # 产生一个画笔对象,写在图片上 65 # 生成一个字体对象 传入字体样式,字体大小(可以不传 用默认大小) 66 img_font = ImageFont.truetype('static/font/111.ttf',30) 67 # 定义code用于存储获取到的随机验证码 68 code = '' 69 # 随机验证码 五位数的随机验证码 数字 小写字母 大写字母 70 for i in range(5): 71 # 通过chr将随机出来的大写字母、小写字母、数字对应的ascii码转为对应的字符 72 random_upper = chr(random.randint(65,90)) 73 random_lower = chr(random.randint(97,122)) 74 random_int = chr(random.randint(48,57)) 75 # 从上面的三种形式中随机选一个 76 tmp = random.choice((random_upper,random_lower,random_int)) 77 # 将产生的随机字符串写入到图片上 78 """ 79 为什么一个个写而不是生成好了之后再写 80 因为一个个写能够控制每个字体的间隙 而生成好之后再写的话 81 间隙就没法控制了 82 """ 83 # 通过img_draw画笔对象写入文本,传入坐标 内容 颜色 字体4个值 84 img_draw.text((i*60+60,5),tmp,get_random(),img_font) 85 # 拼接随机字符串 86 code += tmp 87 print(code) 88 # 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到 89 request.session['code'] = code 90 io_obj = BytesIO() # 生成一个IO内存管理器 可以看成一个文件句柄 91 img_obj.save(io_obj, 'png') # 保存图片 92 return HttpResponse(io_obj.getvalue()) # 从IO内存管理器对象中获取值 93 94 95 def login(request): 96 if request.method == 'POST': 97 back_dict={'code':1000, 'msg':''} 98 username = request.POST.get('username') 99 password = request.POST.get('password') 100 code = request.POST.get('code') 101 # 判断验证码是否正确,并忽略大小写 102 if request.session.get('code').lower() == code.lower(): 103 # 校验用户名或密码是否正确 返回一个用户对象 104 user_obj = auth.authenticate(username=username,password=password) 105 if user_obj: 106 # 保存用户登录状态 107 auth.login(request,user_obj) 108 back_dict['msg'] = '/app01/home/' 109 else: 110 back_dict['code'] = 2000 111 back_dict['msg'] = '用户名或密码错误!' 112 else: 113 back_dict['code'] = 3000 114 back_dict['msg'] = '验证码错误!' 115 return JsonResponse(back_dict) 116 return render(request,'login.html')
login.html
1 DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Titletitle> 6 {% load static %} 7 <script src="{% static 'jquery-3.6.0.min.js' %}">script> 8 <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}"> 9 <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}">script> 10 <link rel="stylesheet" href="{% static 'dist/sweetalert.css' %}"> 11 <script src="{% static 'dist/sweetalert.min.js' %}">script> 12 <script src="{% static 'js/mysetup.js' %}">script> 13 14 head> 15 <body> 16 <div class="container"> 17 <div class="row"> 18 <div class="col-md-6 col-md-offset-3"> 19 <h3 class="text-center">登录h3> 20 {% csrf_token %} 21 <div class="form-group"> 22 <label for="id_username">用户名label> 23 <input type="text" name="username" id="id_username" class="form-control"> 24 div> 25 <div class="form-group"> 26 <label for="id_password">密码label> 27 <input type="password" name="password" id="id_password" class="form-control"> 28 div> 29 <div class="form-group"> 30 <label for="id_re_password">确认密码label> 31 <input type="password" name="re_password" id="id_re_password" class="form-control"> 32 div> 33 <div class="form-group"> 34 <label for="id_code">验证码label> 35 <div class="row"> 36 <div class="col-md-6"> 37 <input type="text" name="code" id="id_code" class="form-control"> 38 div> 39 <div class="col-md-6"> 40 <img src="/app01/get_code/" alt="" id="id_img" width="260px" height="30px"> 41 div> 42 div> 43 div> 44 <div class="form-group"> 45 <input type="button" id="id_btn" value="登录" class="btn btn-success btn-block"> 46 <span style="color: red" id="id_error">span> 47 div> 48 49 div> 50 div> 51 div> 52 53 <script> 54 // 点击验证码刷新 利用验证码点击事件 55 $('#id_img').click(function () { 56 // 验证码图片对应的src属性后面的值变了的时候 验证码都会刷新 57 // 所以我们可以利用这个机制 点击时让src的值不断自增 就可以一直刷新验证码 58 let oldSrc = $(this).attr('src') // 获取验证码原来的src值 59 $(this).attr('src', oldSrc += 1) 60 }) 61 62 $('#id_btn').click(function(){ 63 $.ajax({ 64 url:'', 65 type:'post', 66 data:{ 67 'username':$('#id_username').val(), 68 'password':$('#id_password').val(), 69 're_password':$('#id_re_password').val(), 70 'code':$('#id_code').val(), 71 'csrfmiddlewaretoken':'{{ csrf_token }}' 72 }, 73 success:function(args){ 74 if(args.code === 1000){ 75 window.location.href = args.msg 76 }else{ 77 $('#id_error').text(args.msg) 78 } 79 }, 80 }) 81 }) 82 script> 83 body> 84 html>
如此,登录功能实现完毕
三、首页功能搭建
1 ''' 2 1.先搭建导航栏,通过bootstrap搭建一个导航栏,用户登录时显示用户名和更多操作,未登录显示登录、注册 3 1.用if request.user.is_authneticated 判断右侧的用户是否登录 4 登录则显示用户名和更多操作,否则显示登录、注册 5 6 2.导航栏的修改密码不再跳转页面 而是直接通过 a 标签触发 模态框来完成 用ajax提交数据 7 1.在模态框内书搭建表单页面,通过ajax跟后端进行数据交互 8 9 3.除导航栏外首页搭建布局利用2-8-2布局 10 1.左右用面板 中间放用户文章简介以及内容 11 12 4.中间文章部分通过 admin 后台管理来完成 13 1.先在terminal窗口通过 python manage.py createsuperuser 创建超级用户 14 2.在 admin.py 中注册我们创建的8张表 15 3.在 models.py 中定义 __str__方法查看对象对应名字 16 1.并定义 class Meta 类 利用 verbose_name_plural 可以修改名字为中文 17 2.models.py 中 null=True只是设置表字段可以为空 blank=True 可以设置后台值可以为空 18 3.绑定用户表 个人站点 文章 标签 分类等表的关系 19 4.添加文章内容 20 5.去数据库中查询文章表 传给前端 利用for循环渲染前端页面 21 '''
urls.py
1 from django.conf.urls import url 2 from app01 import views 3 from bbs1 import settings 4 5 urlpatterns = [ 6 # url(r'^admin/', admin.site.urls), 7 # 注册 8 url(r'^register/', views.register,name='reg'), 9 # 登录 10 url(r'^login/',views.login,name='log'), 11 # 登录验证码 12 url(r'^get_code/',views.get_code), 13 # 首页搭建 14 url(r'^home/',views.home,name='home'),
views.py
1 def home(request): 2 article_list = models.Article.objects.all() # 查询所有文章渲染到前端hml页面 3 return render(request,'home.html',locals())
home.html
1 DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>hometitle> 6 {% load static %} 7 <script src="{% static 'jquery-3.6.0.min.js' %}">script> 8 <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}"> 9 <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}">script> 10 <link rel="stylesheet" href="{% static 'dist/sweetalert.css' %}"> 11 <script src="{% static 'dist/sweetalert.min.js' %}">script> 12 <script src="{% static 'js/mysetup.js' %}">script> 13 14 head> 15 <body> 16 17 <nav class="navbar navbar-inverse"> 18 <div class="container-fluid"> 19 20 <div class="navbar-header"> 21 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" 22 data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> 23 <span class="sr-only">Toggle navigationspan> 24 <span class="icon-bar">span> 25 <span class="icon-bar">span> 26 <span class="icon-bar">span> 27 button> 28 <a class="navbar-brand" href="#">BBSa> 29 div> 30 31 32 <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> 33 <ul class="nav navbar-nav"> 34 <li class="active"><a href="/app01/{{ request.user.username }}/">文章 <span class="sr-only">(current)span>a>li> 35 <li><a href="/app01/{{ request.user.username }}/">随笔a>li> 36 <li class="dropdown"> 37 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" 38 aria-expanded="false">详细信息 <span class="caret">span>a> 39 <ul class="dropdown-menu"> 40 <li><a href="#">Actiona>li> 41 <li><a href="#">Another actiona>li> 42 <li><a href="#">Something else herea>li> 43 <li role="separator" class="divider">li> 44 <li><a href="#">Separated linka>li> 45 <li role="separator" class="divider">li> 46 <li><a href="#">One more separated linka>li> 47 ul> 48 li> 49 ul> 50 <form class="navbar-form navbar-left"> 51 <div class="form-group"> 52 <input type="text" class="form-control" placeholder="搜索文章/作者"> 53 div> 54 <button type="submit" class="btn btn-default">搜索button> 55 form> 56 <ul class="nav navbar-nav navbar-right"> 57 {% if request.user.is_authenticated %} 58 <li><a href="/app01/{{ request.user.username }}/">{{ request.user }}a>li> 59 <li class="dropdown"> 60 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" 61 aria-expanded="false">更多操作 <span class="caret">span>a> 62 <ul class="dropdown-menu"> 63 <li><a href="#" data-toggle="modal" data-target="#myModal">修改密码a>li> 64 <li><a href="#">后台管理a>li> 65 <li><a href="#">修改头像a>li> 66 <li role="separator" class="divider">li> 67 <li><a href="{% url 'log_out' %}">退出登录a>li> 68 ul> 69 li> 70 {% else %} 71 <li><a href="{% url 'reg' %}">注册a>li> 72 <li><a href="{% url 'log' %}">登录a>li> 73 {% endif %} 74 ul> 75 76 77 78 <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> 79 <div class="modal-dialog" role="document"> 80 <div class="modal-content"> 81 <div class="modal-header"> 82 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×span>button> 83 <h4 class="modal-title text-center" id="myModalLabel">修改密码h4> 84 div> 85 <div class="modal-body"> 86 <div class="row"> 87 <div class="col-md-8 col-md-offset-2"> 88 <div class="form-group"> 89 <label for="id_username">用户名label> 90 <input type="text" id="id_username" value="{{ request.user.username }}" class="form-control" disabled> 91 div> 92 <div class="form-group"> 93 <label for="old_pwd">原密码label> 94 <input type="password" id="old_pwd" class="form-control"> 95 div> 96 <div class="form-group"> 97 <label for="new_pwd">新密码label> 98 <input type="password" id="new_pwd" class="form-control"> 99 div> 100 <div class="form-group"> 101 <label for="re_pwd">确认密码label> 102 <input type="password" id="re_pwd" class="form-control"> 103 div> 104 div> 105 div> 106 div> 107 <div class="modal-footer"> 108 <button type="button" class="btn btn-default" data-dismiss="modal">取消button> 109 <button type="button" id="id_btn" class="btn btn-primary">修改button> 110 <span style="color: red">span> 111 div> 112 div> 113 div> 114 div> 115 116 div> 117 div> 118 nav> 119 120 <div class="container-fluid"> 121 <div class="row"> 122 <div class="col-md-2"> 123 <div class="panel panel-primary"> 124 <div class="panel-heading">欢乐斗地主div> 125 <div class="panel-body"> 126 每天领取大量欢乐豆 尽在欢乐斗地主 127 div> 128 div> 129 <div class="panel panel-danger"> 130 <div class="panel-heading">热血江湖div> 131 <div class="panel-body"> 132 有人的地方,就有江湖,我在热血江湖等你哦 133 div> 134 div> 135 <div class="panel panel-success"> 136 <div class="panel-heading">传奇div> 137 <div class="panel-body"> 138 传奇在线,豪华装备爆不停 139 div> 140 div> 141 div> 142 <div class="col-md-8"> 143 {# 血夜之末 2022-04-15 01:40 1 0 #} 144 <ul class="media-list"> 145 {% for article_obj in article_list %} 146 <li class="media"> 147 <h4 class="media-heading"><a href="">{{ article_obj.title }}a>h4> 148 <div class="media-left"> 149 <a href="#"> 150 <img class="media-object" src="/app01/media/{{article_obj.blog.userinfo.avatar}}" alt="..." width="80px"> 151 a> 152 div> 153 <div class="media-body"> 154 {{ article_obj.desc }} 155 div> 156 <br> 157 <div> 158 <span><a href="/app01/{{ article_obj.blog.userinfo.username }}/">{{ article_obj.blog.userinfo.username }} a>span> 159 <span>{{ article_obj.create_time|date:'Y-m-d H:m' }} span> 160 <span>点赞<span class="glyphicon glyphicon-thumbs-up">span> {{ article_obj.up_num }} span> 161 <span>评论<span class="glyphicon glyphicon-comment">span> {{ article_obj.comment_num }}span> 162 div> 163 li> 164 <hr> 165 {% endfor %} 166 ul> 167 168 div> 169 <div class="col-md-2"> 170 <div class="panel panel-primary"> 171 <div class="panel-heading">欢乐斗地主div> 172 <div class="panel-body"> 173 每天领取大量欢乐豆 尽在欢乐斗地主 174 div> 175 div> 176 <div class="panel panel-danger"> 177 <div class="panel-heading">热血江湖div> 178 <div class="panel-body"> 179 有人的地方,就有江湖,我在热血江湖等你哦 180 div> 181 div> 182 <div class="panel panel-success"> 183 <div class="panel-heading">传奇div> 184 <div class="panel-body"> 185 传奇在线,豪华装备爆不停 186 div> 187 div> 188 div> 189 div> 190 div> 191 192 193 <script> 194 // 修改密码 195 $('#id_btn').click(function(){ 196 $.ajax({ 197 url:'/app01/set_password/', 198 type:'post', 199 data:{ 200 'old_password':$('#old_pwd').val(), 201 'new_password':$('#new_pwd').val(), 202 're_password':$('#re_pwd').val(), 203 'csrfmiddlewaretoken':'{{ csrf_token }}' 204 }, 205 success:function(args){ 206 if(args.code === 1000){ 207 window.location.href = args.msg 208 }else{ 209 $('#id_btn').next().text(args.msg) 210 } 211 } 212 }) 213 }) 214 script> 215 body> 216 html>
如此,首页搭建完毕
四、个人站点搭建/侧边栏/侧边栏筛选功能
1 ''' 2 1.个人站点页面和首页区别不大,只是首页是2-8-2布局,个人站点则是3-9布局 3 2.个人站点的路径是输入用户名来访问 4 1.通过正则匹配用户名,结尾必须加$ 5 3.个人站点的导航栏直接用首页的, 6 4.个人站点侧边栏需要通过进行分组查询以及聚合函数查询来获取每个类目的文章数 7 5.个人站点内容区域直接显示用户名下所有的文章 8 6.个人站点的头像渲染需要在settings.py配置文件中如下配置: 9 MEDIA_ROOT=os.path.join(BASE_DIR,"media") 10 7.通过urls.py中配置: 11 url(r'^media/(?P.*)', server,{'document_root':settings.MEDIA_ROOT}) 12 可以开放相对应的资源接口暴露给外界 13 '''
侧边栏筛选功能和个人站点两个url对应同一个视图函数,所以视图函数需要用**kwargs来接收额外的关键字参数
urls.py
1 from django.conf.urls import url,include 2 from django.contrib import admin 3 from app01 import views 4 from django.views.static import serve 5 from bbs import settings 6 7 urlpatterns = [ 8 9 # 注册 10 url(r'^reg/', views.register, name='reg'), 11 # 登录功能 12 url(r'^login/', views.login, name='log'), 13 # 登录验证码 14 url(r'^get_code/', views.get_code, name='g_code'), 15 # 首页 16 url(r'^home/', views.home, name='home'), 17 # 修改密码 18 url(r'^set_password/',views.set_password, name='set_pwd'), 19 # 退出登录 20 url(r'^logout', views.logout,name='logout'), 21 # 指定暴露后端文件资源 22 url(r'media/(?P.*) ',serve,{'document_root':settings.MEDIA_ROOT}), 23 # 个人站点 24 url(r'^(?P\w+)/$ ', views.site,name='site'), 25 # 个人站点侧边栏筛选 26 # url(r'^(?P\w+)/category/(\d+)/',views.site), 27 # url(r'^(?P\w+)/tag/(\d+)/',views.site), 28 # url(r'^(?P\w+)/archive/(\w+)/',views.site), 29 # 以上三个可以合并为一条 30 url(r'^(?P\w+)/(?P ',views.site,name='site'), 31 32 ]category|tag|archive)/(?P.*)/
views.py
1 @login_required 2 def site(request,username,**kwargs): 3 # 通过用户名获取对应的用户对象 4 user_obj = models.UserInfo.objects.filter(username=username).first() 5 if not user_obj: 6 return render(request,'error.html') 7 # 获取用户对象对应的个人站点 8 blog = user_obj.blog 9 # 查询个人站点下的所有文章 10 article_list = models.Article.objects.filter(blog=blog) 11 if kwargs: 12 # print(kwargs) # {'condition': 'tag', 'param': '2'} 13 condition = kwargs.get('condition') 14 param = kwargs.get('param') 15 if condition == 'category': 16 article_list = article_list.filter(category_id=param) 17 elif condition == 'tag': 18 article_list = article_list.filter(tags__pk=param) 19 else: 20 year,month = param 21 article_list = article_list.filter(create_time__year=year,create_time__month=month) 22 # 获取当前用户所有的分类以及分类下的文章数 23 category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk') 24 print(category_list) 25 # 获取当前用户所有的标签及标签下的文章数 26 tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk') 27 print(tag_list) 28 # 将当前用户所有的文章按照年月分组 29 date_list = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(count_num=Count('pk')).values_list('month','count_num') 30 # print(date_list) 31 return render(request,'site.html',locals())
site.html
1 DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>sitetitle> 6 {% load static %} 7 <script src="{% static 'jquery-3.6.0.min.js' %}">script> 8 <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}"> 9 <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}">script> 10 <link rel="stylesheet" href="{% static 'dist/sweetalert.css' %}"> 11 <script src="{% static 'dist/sweetalert.min.js' %}">script> 12 <script src="{% static 'js/mysetup.js' %}">script> 13 14 head> 15 <body> 16 17 <nav class="navbar navbar-inverse"> 18 <div class="container-fluid"> 19 20 <div class="navbar-header"> 21 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" 22 data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> 23 <span class="sr-only">Toggle navigationspan> 24 <span class="icon-bar">span> 25 <span class="icon-bar">span> 26 <span class="icon-bar">span> 27 button> 28 <a class="navbar-brand" href="#">{{ blog.site_title }}a> 29 div> 30 31 32 <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> 33 <ul class="nav navbar-nav"> 34 <li class="active"><a href="#">文章 <span class="sr-only">(current)span>a>li> 35 <li><a href="#">随笔a>li> 36 <li class="dropdown"> 37 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" 38 aria-expanded="false">详细信息 <span class="caret">span>a> 39 <ul class="dropdown-menu"> 40 <li><a href="#">Actiona>li> 41 <li><a href="#">Another actiona>li> 42 <li><a href="#">Something else herea>li> 43 <li role="separator" class="divider">li> 44 <li><a href="#">Separated linka>li> 45 <li role="separator" class="divider">li> 46 <li><a href="#">One more separated linka>li> 47 ul> 48 li> 49 ul> 50 <form class="navbar-form navbar-left"> 51 <div class="form-group"> 52 <input type="text" class="form-control" placeholder="搜索文章/作者"> 53 div> 54 <button type="submit" class="btn btn-default">搜索button> 55 form> 56 <ul class="nav navbar-nav navbar-right"> 57 {% if request.user.is_authenticated %} 58 <li><a href="#">{{ request.user }}a>li> 59 <li class="dropdown"> 60 <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" 61 aria-expanded="false">更多操作 <span class="caret">span>a> 62 <ul class="dropdown-menu"> 63 <li><a href="#" data-toggle="modal" data-target="#myModal">修改密码a>li> 64 <li><a href="#">后台管理a>li> 65 <li><a href="#">修改头像a>li> 66 <li role="separator" class="divider">li> 67 <li><a href="{% url 'log_out' %}">退出登录a>li> 68 ul> 69 li> 70 {% else %} 71 <li><a href="{% url 'reg' %}">注册a>li> 72 <li><a href="{% url 'log' %}">登录a>li> 73 {% endif %} 74 ul> 75 76 77 78 <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> 79 <div class="modal-dialog" role="document"> 80 <div class="modal-content"> 81 <div class="modal-header"> 82 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×span>button> 83 <h4 class="modal-title text-center" id="myModalLabel">修改密码h4> 84 div> 85 <div class="modal-body"> 86 <div class="row"> 87 <div class="col-md-8 col-md-offset-2"> 88 <div class="form-group"> 89 <label for="id_username">用户名label> 90 <input type="text" id="id_username" value="{{ request.user.username }}" class="form-control" disabled> 91 div> 92 <div class="form-group"> 93 <label for="old_pwd">原密码label> 94 <input type="password" id="old_pwd" class="form-control"> 95 div> 96 <div class="form-group"> 97 <label for="new_pwd">新密码label> 98 <input type="password" id="new_pwd" class="form-control"> 99 div> 100 <div class="form-group"> 101 <label for="re_pwd">确认密码label> 102 <input type="password" id="re_pwd" class="form-control"> 103 div> 104 div> 105 div> 106 div> 107 <div class="modal-footer"> 108 <button type="button" class="btn btn-default" data-dismiss="modal">取消button> 109 <button type="button" id="id_btn" class="btn btn-primary">修改button> 110 <span style="color: red">span> 111 div> 112 div> 113 div> 114 div> 115 116 div> 117 div> 118 nav> 119 120 <div class="container-fluid"> 121 <div class="row"> 122 <div class="col-md-3"> 123 <div class="panel panel-primary"> 124 <div class="panel-heading">文章分类div> 125 <div class="panel-body"> 126 {% for category in category_list %} 127 <p><a href="/app01/{{ username }}/category/{{ category.2 }}/">{{ category.0 }}({{ category.1 }})a>p> 128 {% endfor %} 129 130 div> 131 div> 132 <div class="panel panel-danger"> 133 <div class="panel-heading">文章标签div> 134 <div class="panel-body"> 135 {% for tag in tag_list %} 136 <p><a href="/app01/{{ username }}/tag/{{ tag.2 }}">{{ tag.0 }}({{ tag.1 }})a>p> 137 {% endfor %} 138 div> 139 div> 140 <div class="panel panel-success"> 141 <div class="panel-heading ">日期归档div> 142 <div class="panel-body"> 143 {% for date in date_list %} 144 <p><a href="/app01/{{ username }}/archive/{{ date.0|date:'Y-m' }}">{{ date.0|date:'Y年m月' }}({{ date.1 }})a>p> 145 {% endfor %} 146 147 div> 148 div> 149 div> 150 <div class="col-md-9"> 151 <ul class="media-list"> 152 {% for article_obj in article_list %} 153 <li class="media"> 154 <h4 class="media-heading"><a href="">{{ article_obj.title }}a>h4> 155 <div class="media-left"> 156 <a href="#"> 157 <img class="media-object" src="/app01/media/{{article_obj.blog.userinfo.avatar}}" alt="..." width="80px"> 158 a> 159 div> 160 <div class="media-body"> 161 {{ article_obj.desc }} 162 div> 163 <div class="pull-right"> 164 {# posted @ 2020-05-06 09:05 武沛齐 阅读(10622) 评论(43) 推荐(34) 编辑#} 165 <span>posted @ span> 166 <span>{{ article_obj.create_time|date:'Y-m-d H:m' }} span> 167 <span>{{ article_obj.blog.userinfo.username }} span> 168 <span>评论<span class="glyphicon glyphicon-comment">span> {{ article_obj.comment_num }} span> 169 <span>点赞<span class="glyphicon glyphicon-thumbs-up">span> {{ article_obj.up_num }} span> 170 <span><a href="#">编辑a>span> 171 div> 172 li> 173 <hr> 174 {% endfor %} 175 ul> 176 div> 177 div> 178 div> 179 body> 180 html>
如此,个人站点功能完毕
五、