今日内容概要

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

  • 表创建及同步
  • 注册功能
    • forms组件
    • 用户头像前端实时展示
    • ajax
  • 登陆功能
    • 自己实现图片验证码
    • ajax
  • 搭建bbs首页
    • 导航条根据用户是否登陆展示不同的内容

今日内容详细

数据库表创建及同步

  1. """
  2. 由于django自带的sqlite数据库对日期不敏感,所以我们换成MySQL
  3. """
  4. from django.db import models
  5. # Create your models here.
  6. """
  7. 先写普通字段
  8. 之后再写外键字段
  9. """
  10. from django.contrib.auth.models import AbstractUser
  11. class UserInfo(AbstractUser):
  12. phone = models.BigIntegerField(verbose_name='手机号',null=True)
  13. # 头像
  14. avatar = models.FileField(upload_to='avatar/',default='avatar/default.png',verbose_name='用户头像')
  15. """
  16. 给avatar字段传文件对象 该文件会自动存储到avatar文件下 然后avatar字段只保存文件路径avatar/default.png
  17. """
  18. create_time = models.DateField(auto_now_add=True)
  19. blog = models.OneToOneField(to='Blog',null=True)
  20. class Blog(models.Model):
  21. site_name = models.CharField(verbose_name='站点名称',max_length=32)
  22. site_title = models.CharField(verbose_name='站点标题',max_length=32)
  23. # 简单模拟 带你认识样式内部原理的操作
  24. site_theme = models.CharField(verbose_name='站点样式',max_length=64) # 存css/js的文件路径
  25. class Category(models.Model):
  26. name = models.CharField(verbose_name='文章分类',max_length=32)
  27. blog = models.ForeignKey(to='Blog',null=True)
  28. class Tag(models.Model):
  29. name = models.CharField(verbose_name='文章标签',max_length=32)
  30. blog = models.ForeignKey(to='Blog', null=True)
  31. class Article(models.Model):
  32. title = models.CharField(verbose_name='文章标题',max_length=64)
  33. desc = models.CharField(verbose_name='文章简介',max_length=255)
  34. # 文章内容有很多 一般情况下都是使用TextField
  35. content = models.TextField(verbose_name='文章内容')
  36. create_time = models.DateField(auto_now_add=True)
  37. # 数据库字段设计优化
  38. up_num = models.BigIntegerField(verbose_name='点赞数',default=0)
  39. down_num = models.BigIntegerField(verbose_name='点踩数',default=0)
  40. comment_num = models.BigIntegerField(verbose_name='评论数',default=0)
  41. # 外键字段
  42. blog = models.ForeignKey(to='Blog', null=True)
  43. category = models.ForeignKey(to='Category',null=True)
  44. tags = models.ManyToManyField(to='Tag',
  45. through='Article2Tag',
  46. through_fields=('article','tag')
  47. )
  48. class Article2Tag(models.Model):
  49. article = models.ForeignKey(to='Article')
  50. tag = models.ForeignKey(to='Tag')
  51. class UpAndDown(models.Model):
  52. user = models.ForeignKey(to='UserInfo')
  53. article = models.ForeignKey(to='Article')
  54. is_up = models.BooleanField() # 传布尔值 存0/1
  55. class Comment(models.Model):
  56. user = models.ForeignKey(to='UserInfo')
  57. article = models.ForeignKey(to='Article')
  58. content = models.CharField(verbose_name='评论内容',max_length=255)
  59. comment_time = models.DateTimeField(verbose_name='评论时间',auto_now_add=True)
  60. # 自关联
  61. parent = models.ForeignKey(to='self',null=True) # 有些评论就是根评论

注册功能

  1. """
  2. 我们之前是直接在views.py中书写的forms组件代码
  3. 但是为了接耦合 应该将所有的forms组件代码单独写到一个地方
  4. 如果你的项目至始至终只用到一个forms组件那么你可以直接建一个py文件书写即可
  5. myforms.py
  6. 但是如果你的项目需要使用多个forms组件,那么你可以创建一个文件夹在文件夹内根据
  7. forms组件功能的不同创建不同的py文件
  8. myforms文件夹
  9. regform.py
  10. loginform.py
  11. userform.py
  12. orderform.py
  13. ...
  14. """
  15. def register(request):
  16. form_obj = MyRegForm()
  17. if request.method == 'POST':
  18. back_dic = {"code": 1000, 'msg': ''}
  19. # 校验数据是否合法
  20. form_obj = MyRegForm(request.POST)
  21. # 判断数据是否合法
  22. if form_obj.is_valid():
  23. # print(form_obj.cleaned_data) # {'username': 'jason', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'}
  24. clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量
  25. # 将字典里面的confirm_password键值对删除
  26. clean_data.pop('confirm_password') # {'username': 'jason', 'password': '123', 'email': '123@qq.com'}
  27. # 用户头像
  28. file_obj = request.FILES.get('avatar')
  29. """针对用户头像一定要判断是否传值 不能直接添加到字典里面去"""
  30. if file_obj:
  31. clean_data['avatar'] = file_obj
  32. # 直接操作数据库保存数据
  33. models.UserInfo.objects.create_user(**clean_data)
  34. back_dic['url'] = '/login/'
  35. else:
  36. back_dic['code'] = 2000
  37. back_dic['msg'] = form_obj.errors
  38. return JsonResponse(back_dic)
  39. return render(request,'register.html',locals())
  40. <script>
  41. $("#myfile").change(function () {
  42. // 文件阅读器对象
  43. // 1 先生成一个文件阅读器对象
  44. let myFileReaderObj = new FileReader();
  45. // 2 获取用户上传的头像文件
  46. let fileObj = $(this)[0].files[0];
  47. // 3 将文件对象交给阅读器对象读取
  48. myFileReaderObj.readAsDataURL(fileObj) // 异步操作 IO操作
  49. // 4 利用文件阅读器将文件展示到前端页面 修改src属性
  50. // 等待文件阅读器加载完毕之后再执行
  51. myFileReaderObj.onload = function(){
  52. $('#myimg').attr('src',myFileReaderObj.result)
  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. // 3.发送ajax请求
  67. $.ajax({
  68. url:"",
  69. type:'post',
  70. data:formDataObj,
  71. // 需要指定两个关键性的参数
  72. contentType:false,
  73. processData:false,
  74. success:function (args) {
  75. if (args.code==1000){
  76. // 跳转到登陆页面
  77. window.location.href = args.url
  78. }else{
  79. // 如何将对应的错误提示展示到对应的input框下面
  80. // forms组件渲染的标签的id值都是 id_字段名
  81. $.each(args.msg,function (index,obj) {
  82. {#console.log(index,obj) // username ["用户名不能为空"]#}
  83. let targetId = '#id_' + index;
  84. $(targetId).next().text(obj[0]).parent().addClass('has-error')
  85. })
  86. }
  87. }
  88. })
  89. })
  90. // 给所有的input框绑定获取焦点事件
  91. $('input').focus(function () {
  92. // 将input下面的span标签和input外面的div标签修改内容及属性
  93. $(this).next().text('').parent().removeClass('has-error')
  94. })
  95. </script>
  96. # 扩展
  97. """
  98. 一般情况下我们在存储用户文件的时候为了避免文件名冲突的情况
  99. 会自己给文件名加一个前缀
  100. uuid
  101. 随机字符串
  102. ...
  103. """

注意:'login/' 和 '/login/' 不同,前者 直接接在当前路径下面,后者 接在 ip + 端口 后面

$.each(args.msg,functiong(index,obj){}) index,obj 是位置参数!!!

登陆功能

  1. """
  2. img标签的src属性
  3. 1.图片路径
  4. 2.url
  5. 3.图片的二进制数据
  6. 我们的计算机上面致所有能够输出各式各样的字体样式
  7. 内部其实对应的是一个个.ttf结尾的文件
  8. 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
  9. """
  10. """
  11. 图片相关的模块
  12. pip3 install pillow
  13. """
  14. from PIL import Image,ImageDraw,ImageFont
  15. """
  16. Image:生成图片
  17. ImageDraw:能够在图片上乱涂乱画
  18. ImageFont:控制字体样式
  19. """
  20. from io import BytesIO,StringIO
  21. """
  22. 内存管理器模块
  23. BytesIO:临时帮你存储数据 返回的时候数据是二进制
  24. StringIO:临时帮你存储数据 返回的时候数据是字符串
  25. """
  26. import random
  27. def get_random():
  28. return random.randint(0,255),random.randint(0,255),random.randint(0,255)
  29. def get_code(request):
  30. # 推导步骤1:直接获取后端现成的图片二进制数据发送给前端
  31. # with open(r'static/img/111.jpg','rb') as f:
  32. # data = f.read()
  33. # return HttpResponse(data)
  34. # 推导步骤2:利用pillow模块动态产生图片
  35. # img_obj = Image.new('RGB',(430,35),'green')
  36. # img_obj = Image.new('RGB',(430,35),get_random())
  37. # # 先将图片对象保存起来
  38. # with open('xxx.png','wb') as f:
  39. # img_obj.save(f,'png')
  40. # # 再将图片对象读取出来
  41. # with open('xxx.png','rb') as f:
  42. # data = f.read()
  43. # return HttpResponse(data)
  44. # 推导步骤3:文件存储繁琐IO操作效率低 借助于内存管理器模块
  45. # img_obj = Image.new('RGB', (430, 35), get_random())
  46. # io_obj = BytesIO() # 生成一个内存管理器对象 你可以看成是文件句柄
  47. # img_obj.save(io_obj,'png')
  48. # return HttpResponse(io_obj.getvalue()) # 从内存管理器中读取二进制的图片数据返回给前端
  49. # 最终步骤4:写图片验证码
  50. img_obj = Image.new('RGB', (430, 35), get_random())
  51. img_draw = ImageDraw.Draw(img_obj) # 产生一个画笔对象
  52. img_font = ImageFont.truetype('static/font/222.ttf',30) # 字体样式 大小
  53. # 随机验证码 五位数的随机验证码 数字 小写字母 大写字母
  54. code = ''
  55. for i in range(5):
  56. random_upper = chr(random.randint(65,90))
  57. random_lower = chr(random.randint(97,122))
  58. random_int = str(random.randint(0,9))
  59. # 从上面三个里面随机选择一个
  60. tmp = random.choice([random_lower,random_upper,random_int])
  61. # 将产生的随机字符串写入到图片上
  62. """
  63. 为什么一个个写而不是生成好了之后再写
  64. 因为一个个写能够控制每个字体的间隙 而生成好之后再写的话
  65. 间隙就没法控制了
  66. """
  67. img_draw.text((i*60+60,-2),tmp,get_random(),img_font)
  68. # 拼接随机字符串
  69. code += tmp
  70. print(code)
  71. # 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到
  72. request.session['code'] = code
  73. io_obj = BytesIO()
  74. img_obj.save(io_obj,'png')
  75. return HttpResponse(io_obj.getvalue())
  76. <script>
  77. $("#id_img").click(function () {
  78. // 1 先获取标签之前的src
  79. let oldVal = $(this).attr('src');
  80. $(this).attr('src',oldVal += '?')
  81. })
  82. </script>
  1. # 书写针对用户表的forms组件代码
  2. from django import forms
  3. from app01 import models
  4. class MyRegForm(forms.Form):
  5. username = forms.CharField(label='用户名', min_length=3, max_length=8,
  6. error_messages={
  7. 'required': '用户名不能为空',
  8. 'min_length': "用户名最少3位",
  9. 'max_length': "用户名最大8位"
  10. },
  11. # 还需要让标签有bootstrap样式
  12. widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
  13. )
  14. password = forms.CharField(label='密码', min_length=3, max_length=8,
  15. error_messages={
  16. 'required': '密码不能为空',
  17. 'min_length': "密码最少3位",
  18. 'max_length': "密码最大8位"
  19. },
  20. # 还需要让标签有bootstrap样式
  21. widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
  22. )
  23. confirm_password = forms.CharField(label='确认密码', min_length=3, max_length=8,
  24. error_messages={
  25. 'required': '确认密码不能为空',
  26. 'min_length': "确认密码最少3位",
  27. 'max_length': "确认密码最大8位"
  28. },
  29. # 还需要让标签有bootstrap样式
  30. widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
  31. )
  32. email = forms.EmailField(label='邮箱',
  33. error_messages={
  34. 'required': '邮箱不能为空',
  35. 'invalid': '邮箱格式不正确'
  36. },
  37. widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})
  38. )
  39. # 钩子函数
  40. # 局部钩子:校验用户名是否已存在
  41. def clean_username(self):
  42. username = self.cleaned_data.get('username')
  43. # 去数据库中校验
  44. is_exist = models.UserInfo.objects.filter(username=username)
  45. if is_exist:
  46. # 提示信息
  47. self.add_error('username', '用户名已存在')
  48. return username
  49. # 全局钩子:校验两次是否一致
  50. def clean(self):
  51. password = self.cleaned_data.get('password')
  52. confirm_password = self.cleaned_data.get('confirm_password')
  53. if not password == confirm_password:
  54. self.add_error('confirm_password', '两次密码不一致')
  55. return self.cleaned_data

django——bbs的更多相关文章

  1. django BBS project login登录功能实现

    1.models from django.db import models # Create your models here. from django.contrib.auth.models imp ...

  2. django BBS

    https://github.com/triaquae/py_training/tree/master/OldboyBBS2 http://www.cnblogs.com/zhming26/p/592 ...

  3. Python之路【第十八篇】Django小项目简单BBS论坛部分内容知识点

    开发一个简单的BBS论坛 项目需求: 整体参考“抽屉新热榜” + “虎嗅网” 实现不同论坛版块 帖子列表展示 帖子评论数.点赞数展示 在线用户展示 允许登录用户发贴.评论.点赞 允许上传文件 帖子可被 ...

  4. python 学习笔记二十 django项目bbs论坛

    项目:开发一个简单的BBS论坛 需求: 整体参考“抽屉新热榜” + “虎嗅网” 实现不同论坛版块 帖子列表展示 帖子评论数.点赞数展示 在线用户展示 允许登录用户发贴.评论.点赞 允许上传文件 帖子可 ...

  5. Django:之BBS项目

    首先新建一个BBSProject项目,在建一个app,把app导入到项目中. 在项目BBSProject中的settings.py中, INSTALLED_APPS = [ 'django.contr ...

  6. Django小项目简单BBS论坛

    开发一个简单的BBS论坛 项目需求: 1 整体参考"抽屉新热榜" + "虎嗅网" 2 实现不同论坛版块 3 帖子列表展示 4 帖子评论数.点赞数展示 5 在线用 ...

  7. python 自动化之路 day 20 Django进阶/BBS项目【一】

    一.django进阶 1.django orm 增删改查 1.1.创建表: 1 2 3 >>> from blog.models import Blog >>> b ...

  8. Django学习笔记(20)——BBS+Blog项目开发(4)Django如何使用Bootstrap

    本文学习如何通过Django使用Bootstrap.其实在之前好几个Django项目中已经尝试使用过了Bootstrap,而且都留有学习记录,我已经大概有了一个大的框架,那么本文就从头再走一遍流程,其 ...

  9. Django项目BBS博客论坛

    BBS 项目开发逻辑梳理 第一步:先进行数据库设计 数据库设计规则是: 1.先创建基表:用户表.站点表.文章表.标签表.分类表.文章2标签第三张关系表.点赞点踩表.评论表 2.书写表中的基本含有的字段 ...

随机推荐

  1. day06:三级菜单练习0218

    #1:省份数列:data = { "北京":{ "昌平":{ "沙河":["oldboy","电信" ...

  2. C# winform DataGridView 绑定数据的的几种方法

    1.用DataSet和DataTable为DataGridView提供数据源 String strConn = "Data Source=.;Initial Catalog=His;User ...

  3. Poj2109 (1) k^n = p.

    看到1<=p<10101 ,就去想大数操作了,后来看了discuss原来double完全可以放. 类型          长度 (bit)           有效数字          ...

  4. Kubernetes学习笔记(四):服务

    服务介绍 服务是一种为一组相同功能的pod提供单一不变接入点的资源.当服务存在时,他的IP和端口不会改变.客户端通过IP和端口建立连接,这些连接会被路由到任何一个pod上.如此,客户端不需要知道每个单 ...

  5. mvc的视图渲染方式

    ModelAndView ModelAndView vm = new ModelAndView(); //封装要显示在试图上的数据 vm.addObject("msg"," ...

  6. Meta标签基本使用

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><t ...

  7. Android_四大组件之ContentProvider

    一.概述 ContentProvider(内容提供者)管理对结构化数据集的访问,它们封装数据,并提供用于定义数据安全性的机制.其他应用,通过Context的ContentResolver对象 作为客户 ...

  8. [PHP学习教程 - 系统]004.通过ini_set()来设置系统属性(ini_set Method)

    PHP原意:ini_set — 为一个系统配置项设置值 基本信息: string ini_set ( string $varname , string $newvalue). (说明:设置指定配置选项 ...

  9. 50个SQL语句(MySQL版) 问题十二

    --------------------------表结构-------------------------- student(StuId,StuName,StuAge,StuSex) 学生表 tea ...

  10. Alpha冲刺 —— 5.6

    这个作业属于哪个课程 软件工程 这个作业要求在哪里 团队作业第五次--Alpha冲刺 这个作业的目标 Alpha冲刺 作业正文 正文 github链接 项目地址 其他参考文献 无 一.会议内容 1.展 ...