文章目录

  • 1 BBS项目介绍、表设计
    • 项目开发流程
    • 表设计
  • 2 表创建同步、注册、登录功能
    • 数据库表创建及同步
    • 注册功能
    • 登陆功能
  • 3 登录功能、首页搭建、admin、头像、图片防盗、个人站点、侧边栏筛选
    • 登陆功能
    • 首页搭建
    • admin后台管理
    • 用户头像展示
    • 图片防盗链
    • 个人站点
    • 侧边栏筛选功能
  • 4 文章的详情页、点赞点踩、评论等功能
    • 扩展:admin路由分发的本质
    • 文章详情页
    • 文章点赞点踩
    • 文章评论
  • 5 后台管理、添加文章、修改用户头像、BBS总结
    • 后台管理
    • 添加文章
    • kindeditor富文本编辑器
    • 编辑器上传图片
    • 修改用户头像
    • bbs项目总结

1 BBS项目介绍、表设计

项目开发流程

# 1.需求分析架构师+产品经理+开发者组长在跟客户谈需求之前,会大致先了解客户的需求,然后自己先设计一套比较好写方案在跟客户沟通交流中引导客户往我们之前想好的方案上面靠形成一个初步的方案# 2.项目设计架构师干的活编程语言选择框架选择数据库选择主库:MySQL,postgreSQL,...缓存数据库:redis、mongodb、memcache...功能划分将整个项目划分成几个功能模块找组长开会给每个组分发任务项目报价技术这块需要多少人,多少天(一个程序员一天1500~2000计算(大致))产品经理公司层面 再加点钱公司财务签字确认公司老板签字确认产品经理去跟客户沟通后续需要加功能 继续加钱# 3.分组开发组长找组员开会,安排各自功能模块我们其实就是在架构师设计好的框架里面填写代码而已(码畜)我们在写代码的时候 写完需要自己先测试是否有bug如果是一些显而易见的bug,你没有避免而是直接交给了测试部门测出来那你可能就需要被扣绩效了(一定要跟测试小姐姐搞好关系)薪资组成    15K(合理合规合法的避税)底薪    10K绩效   3K岗位津贴 1K生活补贴 1K# 4.测试测试部门测试你的代码压力测试...
# 5.交付上线1.交给对方的运维人员2.直接上线到我们的服务器上 收取维护费用3.其他...

表设计

"""
一个项目中最最最重要的不是业务逻辑的书写
而是前期的表设计,只要将表设计好了,后续的功能书写才会一帆风顺bbs表设计1.用户表继承AbstractUser扩展phone 电话号码avatar  用户头像create_time  创建时间外键字段一对一个人站点表2.个人站点表site_name 站点名称site_title   站点标题site_theme 站点样式3.文章标签表name     标签名外键字段一对多个人站点4.文章分类表name       分类名外键字段一对多个人站点5.文章表title    文章标题desc    文章简介content 文章内容create_time 发布时间数据库字段设计优化(******)(虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率)up_num                  点赞数down_num             点踩数comment_num      评论数外键字段一对多个人站点多对多文章标签一对多文章分类6.点赞点踩表记录哪个用户给哪篇文章点了赞还是点了踩user                      ForeignKey(to="User")                article                 ForeignKey(to="Article") is_up                       BooleanField()1             1               11              2               11              3               02              1               17.文章评论表记录哪个用户给哪篇文章写了哪些评论内容user                     ForeignKey(to="User")                article                 ForeignKey(to="Article")content                  CharField()comment_time     DateField()# 自关联parent                  ForeignKey(to="Comment",null=True)      # ORM专门提供的自关联写法 parent                  ForeignKey(to="self",null=True)id   user_id         article_id              parent_id1       1                      1                                       2        2                      1                                       1                   根评论子评论的概念根评论就是直接评论当前发布的内容的子评论是评论别人的评论1.PHP是世界上最牛逼的语言1.1 python才是最牛逼的1.2 java才是根评论与子评论是一对多的关系"""

2 表创建同步、注册、登录功能

数据库表创建及同步

"""
由于django自带的sqlite数据库对日期不敏感,所以我们换成MySQL
"""
from django.db import models# Create your models here.
"""
先写普通字段
之后再写外键字段
"""
from django.contrib.auth.models import AbstractUserclass UserInfo(AbstractUser):phone = models.BigIntegerField(verbose_name='手机号',null=True)# 头像avatar = models.FileField(upload_to='avatar/',default='avatar/default.png',verbose_name='用户头像')"""给avatar字段传文件对象 该文件会自动存储到avatar文件下 然后avatar字段只保存文件路径avatar/default.png"""create_time = models.DateField(auto_now_add=True)blog = models.OneToOneField(to='Blog',null=True)class Blog(models.Model):site_name = models.CharField(verbose_name='站点名称',max_length=32)site_title = models.CharField(verbose_name='站点标题',max_length=32)# 简单模拟 带你认识样式内部原理的操作site_theme = models.CharField(verbose_name='站点样式',max_length=64)  # 存css/js的文件路径class Category(models.Model):name = models.CharField(verbose_name='文章分类',max_length=32)blog = models.ForeignKey(to='Blog',null=True)class Tag(models.Model):name = models.CharField(verbose_name='文章标签',max_length=32)blog = models.ForeignKey(to='Blog', null=True)class Article(models.Model):title = models.CharField(verbose_name='文章标题',max_length=64)desc = models.CharField(verbose_name='文章简介',max_length=255)# 文章内容有很多 一般情况下都是使用TextFieldcontent = models.TextField(verbose_name='文章内容')create_time = models.DateField(auto_now_add=True)# 数据库字段设计优化up_num = models.BigIntegerField(verbose_name='点赞数',default=0)down_num = models.BigIntegerField(verbose_name='点踩数',default=0)comment_num = models.BigIntegerField(verbose_name='评论数',default=0)# 外键字段blog = models.ForeignKey(to='Blog', null=True)category = models.ForeignKey(to='Category',null=True)tags = models.ManyToManyField(to='Tag',through='Article2Tag',through_fields=('article','tag'))class Article2Tag(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')is_up = models.BooleanField()  # 传布尔值 存0/1class Comment(models.Model):user = models.ForeignKey(to='UserInfo')article = models.ForeignKey(to='Article')content = models.CharField(verbose_name='评论内容',max_length=255)comment_time = models.DateTimeField(verbose_name='评论时间',auto_now_add=True)# 自关联parent = models.ForeignKey(to='self',null=True)  # 有些评论就是根评论

注册功能

"""
我们之前是直接在views.py中书写的forms组件代码
但是为了接耦合 应该将所有的forms组件代码单独写到一个地方如果你的项目至始至终只用到一个forms组件那么你可以直接建一个py文件书写即可myforms.py
但是如果你的项目需要使用多个forms组件,那么你可以创建一个文件夹在文件夹内根据
forms组件功能的不同创建不同的py文件myforms文件夹regform.pyloginform.pyuserform.pyorderform.py...
"""
def register(request):form_obj = MyRegForm()if request.method == 'POST':back_dic = {"code": 1000, 'msg': ''}# 校验数据是否合法form_obj = MyRegForm(request.POST)# 判断数据是否合法if form_obj.is_valid():# print(form_obj.cleaned_data)  # {'username': 'jason', 'password': '123', 'confirm_password': '123', 'email': '123@qq.com'}clean_data = form_obj.cleaned_data  # 将校验通过的数据字典赋值给一个变量# 将字典里面的confirm_password键值对删除clean_data.pop('confirm_password')  # {'username': 'jason', 'password': '123', 'email': '123@qq.com'}# 用户头像file_obj = request.FILES.get('avatar')"""针对用户头像一定要判断是否传值 不能直接添加到字典里面去"""if file_obj:clean_data['avatar'] = file_obj# 直接操作数据库保存数据models.UserInfo.objects.create_user(**clean_data)back_dic['url'] = '/login/'else:back_dic['code'] = 2000back_dic['msg'] = form_obj.errorsreturn JsonResponse(back_dic)return render(request,'register.html',locals())<script>$("#myfile").change(function () {// 文件阅读器对象// 1 先生成一个文件阅读器对象let myFileReaderObj = new FileReader();// 2 获取用户上传的头像文件let fileObj = $(this)[0].files[0];// 3 将文件对象交给阅读器对象读取myFileReaderObj.readAsDataURL(fileObj)  // 异步操作  IO操作// 4 利用文件阅读器将文件展示到前端页面  修改src属性// 等待文件阅读器加载完毕之后再执行myFileReaderObj.onload = function(){$('#myimg').attr('src',myFileReaderObj.result)}})$('#id_commit').click(function () {// 发送ajax请求     我们发送的数据中即包含普通的键值也包含文件let formDataObj = new FormData();// 1.添加普通的键值对{#console.log($('#myform').serializeArray())  // [{},{},{},{},{}]  只包含普通键值对#}$.each($('#myform').serializeArray(),function (index,obj) {{#console.log(index,obj)#}  // obj = {}formDataObj.append(obj.name,obj.value)});// 2.添加文件数据formDataObj.append('avatar',$('#myfile')[0].files[0]);// 3.发送ajax请求$.ajax({url:"",type:'post',data:formDataObj,// 需要指定两个关键性的参数contentType:false,processData:false,success:function (args) {if (args.code==1000){// 跳转到登陆页面window.location.href = args.url}else{// 如何将对应的错误提示展示到对应的input框下面// forms组件渲染的标签的id值都是 id_字段名$.each(args.msg,function (index,obj) {{#console.log(index,obj)  //  username        ["用户名不能为空"]#}let targetId = '#id_' + index;$(targetId).next().text(obj[0]).parent().addClass('has-error')})}}})})// 给所有的input框绑定获取焦点事件$('input').focus(function () {// 将input下面的span标签和input外面的div标签修改内容及属性$(this).next().text('').parent().removeClass('has-error')})
</script># 扩展
"""
一般情况下我们在存储用户文件的时候为了避免文件名冲突的情况
会自己给文件名加一个前缀    uuid随机字符串...
"""

登陆功能

"""
img标签的src属性1.图片路径2.url3.图片的二进制数据我们的计算机上面致所有能够输出各式各样的字体样式
内部其实对应的是一个个.ttf结尾的文件字体网站链接: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
""""""
图片相关的模块pip3 install pillow
"""
from PIL import Image,ImageDraw,ImageFont
"""
Image:生成图片
ImageDraw:能够在图片上乱涂乱画
ImageFont:控制字体样式
"""
from io import BytesIO,StringIO
"""
内存管理器模块
BytesIO:临时帮你存储数据 返回的时候数据是二进制
StringIO:临时帮你存储数据 返回的时候数据是字符串
"""
import random
def get_random():return random.randint(0,255),random.randint(0,255),random.randint(0,255)
def get_code(request):# 推导步骤1:直接获取后端现成的图片二进制数据发送给前端# with open(r'static/img/111.jpg','rb') as f:#     data = f.read()# return HttpResponse(data)# 推导步骤2:利用pillow模块动态产生图片# img_obj = Image.new('RGB',(430,35),'green')# img_obj = Image.new('RGB',(430,35),get_random())# # 先将图片对象保存起来# with open('xxx.png','wb') as f:#     img_obj.save(f,'png')# # 再将图片对象读取出来# with open('xxx.png','rb') as f:#     data = f.read()# return HttpResponse(data)# 推导步骤3:文件存储繁琐IO操作效率低  借助于内存管理器模块# img_obj = Image.new('RGB', (430, 35), get_random())# io_obj = BytesIO()  # 生成一个内存管理器对象  你可以看成是文件句柄# img_obj.save(io_obj,'png')# return HttpResponse(io_obj.getvalue())  # 从内存管理器中读取二进制的图片数据返回给前端# 最终步骤4:写图片验证码img_obj = Image.new('RGB', (430, 35), get_random())img_draw = ImageDraw.Draw(img_obj)  # 产生一个画笔对象img_font = ImageFont.truetype('static/font/222.ttf',30)  # 字体样式 大小# 随机验证码  五位数的随机验证码  数字 小写字母 大写字母code = ''for i in range(5):random_upper = chr(random.randint(65,90))random_lower = chr(random.randint(97,122))random_int = str(random.randint(0,9))# 从上面三个里面随机选择一个tmp = random.choice([random_lower,random_upper,random_int])# 将产生的随机字符串写入到图片上"""为什么一个个写而不是生成好了之后再写因为一个个写能够控制每个字体的间隙 而生成好之后再写的话间隙就没法控制了"""img_draw.text((i*60+60,-2),tmp,get_random(),img_font)# 拼接随机字符串code += tmpprint(code)# 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到request.session['code'] = codeio_obj = BytesIO()img_obj.save(io_obj,'png')return HttpResponse(io_obj.getvalue())# 点图片进行刷新验证码<script>$("#id_img").click(function () {// 1 先获取标签之前的srclet oldVal = $(this).attr('src');$(this).attr('src',oldVal += '?')})
</script>

3 登录功能、首页搭建、admin、头像、图片防盗、个人站点、侧边栏筛选

登陆功能

def login(request):if request.method == 'POST':back_dic = {'code':1000,'msg':''}username = request.POST.get('username')password = request.POST.get('password')code = request.POST.get('code')# 1 先校验验证码是否正确      自己决定是否忽略            统一转大写或者小写再比较if request.session.get('code').upper() == code.upper():# 2 校验用户名和密码是否正确user_obj = auth.authenticate(request,username=username,password=password)if user_obj:# 保存用户状态auth.login(request,user_obj)back_dic['url'] = '/home/'else:back_dic['code'] = 2000back_dic['msg'] = '用户名或密码错误'else:back_dic['code'] = 3000back_dic['msg'] = '验证码错误'return JsonResponse(back_dic)return render(request,'login.html')

首页搭建

# 1.动态展示用户名称{% if request.user.is_authenticated %}<li><a href="#">{{ request.user.username }}</a></li><li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多操作 <span class="caret"></span></a><ul class="dropdown-menu"><li><a href="#">修改密码</a></li><li><a href="#">修改头像</a></li><li><a href="#">后台管理</a></li><li role="separator" class="divider"></li><li><a href="#">退出登陆</a></li></ul></li>{% else %}<li><a href="{% url 'reg' %}">注册</a></li><li><a href="{% url 'login' %}">登陆</a></li>{% endif %}# 更多操作

admin后台管理

"""
django给你提供了一个可视化的界面用来让你方便的对你的模型表
进行数据的增删改查操作如果你先想要使用amdin后台管理操作模型表
你需要先注册你的模型表告诉admin你需要操作哪些表去你的应用下的admin.py中注册你的模型表from django.contrib import adminfrom app01 import models# Register your models here.admin.site.register(models.UserInfo)admin.site.register(models.Blog)admin.site.register(models.Category)admin.site.register(models.Tag)admin.site.register(models.Article)admin.site.register(models.Article2Tag)admin.site.register(models.UpAndDown)admin.site.register(models.Comment)
"""
# admin会给每一个注册了的模型表自动生成增删改查四条url
http://127.0.0.1:8000/admin/app01/userinfo/  查
http://127.0.0.1:8000/admin/app01/userinfo/add/  增
http://127.0.0.1:8000/admin/app01/userinfo/1/change/  改
http://127.0.0.1:8000/admin/app01/userinfo/1/delete/  删http://127.0.0.1:8000/admin/app01/blog/  查
http://127.0.0.1:8000/admin/app01/blog/add/  增
http://127.0.0.1:8000/admin/app01/blog/1/change/  改
http://127.0.0.1:8000/admin/app01/blog/1/delete/  删
"""
关键点就在于urls.py中的第一条自带的url前期我们需要自己手动苦逼的录入数据,自己克服一下
"""# 1.数据绑定尤其需要注意的是用户和个人站点不要忘记绑定了# 2.标签# 3.标签和文章千万不要把别人的文章绑定标签

用户头像展示

"""
1 网址所使用的静态文件默认放在static文件夹下
2 用户上传的静态文件也应该单独放在某个文件夹下media配置该配置可以让用户上传的所有文件都固定存放在某一个指定的文件夹下# 配置用户上传的文件存储位置MEDIA_ROOT = os.path.join(BASE_DIR,'media')  # 文件名 随你 自己会自动创建多级目录如何开设后端指定文件夹资源首先你需要自己去urls.py书写固定的代码from django.views.static import servefrom BBS14 import settings# 暴露后端指定文件夹资源url(r'^media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})"""

图片防盗链

# 如何避免别的网站直接通过本网站的url访问本网站资源# 简单的防盗我可以做到请求来的时候先看看当前请求是从哪个网站过来的如果是本网站那么正常访问如果是其他网站直接拒绝请求头里面有一个专门记录请求来自于哪个网址的参数Referer: http://127.0.0.1:8000/xxx/# 如何避免1.要么修改请求头referer2.直接写爬虫把对方网址的所有资源直接下载到我们自己的服务器上

个人站点

# 全是每个用户都可以有自己的站点样式
<link rel="stylesheet" href="/media/css/{{ blog.site_theme }}/">id      content               create_time                   month
1           111                          2020-11-11                 2020-11
2           222                          2020-11-12                 2020-11
3           333                          2020-11-13                 2020-11
4           444                          2020-11-14                 2020-11
5           555                          2020-11-15                 2020-11
"""
django官网提供的一个orm语法from django.db.models.functions import TruncMonth
-官方提供from django.db.models.functions import TruncMonthSales.objects.annotate(month=TruncMonth('timestamp'))  # Truncate to month and add to select list.values('month')  # Group By month.annotate(c=Count('id'))  # Select the count of the grouping.values('month', 'c')  # (might be redundant, haven't tested) select month and count时区问题报错
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = True
"""

侧边栏筛选功能

https://www.cnblogs.com/jason/tag/Python/                 标签
https://www.cnblogs.com/jason/category/850028.html 分类
https://www.cnblogs.com/jason/archive/2016/10.html 日期https://www.cnblogs.com/jason/tag/1/                标签
https://www.cnblogs.com/jason/category/1             分类
https://www.cnblogs.com/jason/archive/2020-11/ 日期def site(request,username,**kwargs):""":param request::param username::param kwargs: 如果该参数有值 也就意味着需要对article_list做额外的筛选操作:return:"""# 先校验当前用户名对应的个人站点是否存在user_obj = models.UserInfo.objects.filter(username=username).first()# 用户如果不存在应该返回一个404页面if not user_obj:return render(request,'errors.html')blog = user_obj.blog# 查询当前个人站点下的所有的文章article_list = models.Article.objects.filter(blog=blog)  # queryset对象 侧边栏的筛选其实就是对article_list再进一步筛选if kwargs:# print(kwargs)  # {'condition': 'tag', 'param': '1'}condition = kwargs.get('condition')param = kwargs.get('param')# 判断用户到底想按照哪个条件筛选数据if condition == 'category':article_list = article_list.filter(category_id=param)elif condition == 'tag':article_list = article_list.filter(tags__id=param)else:year,month = param.split('-')  # 2020-11  [2020,11]article_list = article_list.filter(create_time__year=year,create_time__month=month)# 1 查询当前用户所有的分类及分类下的文章数category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')# print(category_list)  # <QuerySet [('jason的分类一', 2), ('jason的分类二', 1), ('jason的分类三', 1)]># 2 查询当前用户所有的标签及标签下的文章数tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')# print(tag_list)  # <QuerySet [('tank的标签一', 1), ('tank的标签二', 1), ('tank的标签三', 2)]># 3 按照年月统计所有的文章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')# print(date_list)return render(request,'site.html',locals())

4 文章的详情页、点赞点踩、评论等功能

扩展:admin路由分发的本质

# 路由分发本质    include  可以无限制的嵌套N多层url(r'^index/',([],None,None))# url(r'^index/',([#         url(r'^index_1/',([#                 url(r'^index_1_1',index),#                 url(r'^index_1_2',index),#                 url(r'^index_1_3',index),#                           ],None,None)),#         url(r'^index_2/',index),#         url(r'^index_3/',index),#                 ],None,None)),

“”"

文章详情页

# url设计
/username/article/1# 先验证url是否会被其他url顶替# 文章详情页和个人站点基本一致 所以用模版继承# 侧边栏的渲染需要传输数据才能渲染 并且该侧边栏在很多页面都需要使用1.哪个地方用就拷贝需要的代码(不推荐 有点繁琐)2.将侧边栏制作成inclusion_tag    """步骤1.在应用下创建一个名字必须叫templatetags文件夹2.在该文件夹内创建一个任意名称的py文件3.在该py文件内先固定写两行代码from django import templateregister = template.Library()# 自定义过滤器# 自定义标签# 自定义inclusion_tag"""# 自定义inclusion_tag
@register.inclusion_tag('left_menu.html')
def left_menu(username):# 构造侧边栏需要的数据user_obj = models.UserInfo.objects.filter(username=username).first()blog = user_obj.blog# 1 查询当前用户所有的分类及分类下的文章数category_list = models.Category.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name', 'count_num', 'pk')# print(category_list)  # <QuerySet [('jason的分类一', 2), ('jason的分类二', 1), ('jason的分类三', 1)]># 2 查询当前用户所有的标签及标签下的文章数tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article__pk')).values_list('name','count_num','pk')# print(tag_list)  # <QuerySet [('tank的标签一', 1), ('tank的标签二', 1), ('tank的标签三', 2)]># 3 按照年月统计所有的文章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')# print(date_list)return locals()

文章点赞点踩

"""
浏览器上你看到的花里胡哨的页面,内部都是HTML(前端)代码那现在我们的文章内容应该写什么???    >>> html代码如何拷贝文章copy outerhtml1.拷贝文章
2.拷贝点赞点踩1.拷贝前端点赞点踩图标 只拷了html2.css也要拷贝由于有图片防盗链的问题 所以将图片直接下载到本地课下思考:前端如何区分用户是点了赞还是点了踩1.给标签各自绑定一个事件两个标签对应的代码其实基本一样,仅仅是是否点赞点踩这一个参数不一样而已2.二合一给两个标签绑定一个事件//   给所有的action类绑定事件$('.action').click(function () {alert($(this).hasClass('diggit'))})由于点赞点踩内部有一定的业务逻辑,所以后端单独开设视图函数处理"""
# 个人建议:写代码先把所有正确的逻辑写完再去考虑错误的逻辑 不要试图两者兼得
import json
from django.db.models import F
def up_or_down(request):"""1.校验用户是否登陆2.判断当前文章是否是当前用户自己写的(自己不能点自己的文章)3.当前用户是否已经给当前文章点过了4.操作数据库了:param request::return:"""if request.is_ajax():back_dic = {'code':1000,'msg':''}# 1 先判断当前用户是否登陆if request.user.is_authenticated():article_id = request.POST.get('article_id')is_up = request.POST.get('is_up')# print(is_up,type(is_up))  # true <class 'str'>is_up = json.loads(is_up)  # 记得转换# print(is_up, type(is_up))  # True <class 'bool'># 2 判断当前文章是否是当前用户自己写的  根据文章id查询文章对象 根据文章对象查作者 根request.user比对article_obj = models.Article.objects.filter(pk=article_id).first()if not article_obj.blog.userinfo == request.user:# 3 校验当前用户是否已经点了      哪个地方记录了用户到底点没点is_click = models.UpAndDown.objects.filter(user=request.user,article=article_obj)if not is_click:# 4 操作数据库 记录数据      要同步操作普通字段# 判断当前用户点了赞还是踩 从而决定给哪个字段加一if is_up:# 给点赞数加一models.Article.objects.filter(pk=article_id).update(up_num = F('up_num') + 1)back_dic['msg'] = '点赞成功'else:# 给点踩数加一models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)back_dic['msg'] = '点踩成功'# 操作点赞点踩表models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up)else:back_dic['code'] = 1001back_dic['msg'] = '你已经点过了,不能再点了'  # 这里你可以做的更加的详细 提示用户到底点了赞还是点了踩else:back_dic['code'] = 1002back_dic['msg'] = '你个臭不要脸的!'else:back_dic['code'] = 1003back_dic['msg'] = '请先<a href="/login/">登陆</a>'return JsonResponse(back_dic)<script>//   给所有的action类绑定事件$('.action').click(function () {{#alert($(this).hasClass('diggit'))#}let isUp = $(this).hasClass('diggit');let $div = $(this);// 朝后端发送ajax请求$.ajax({url:'/up_or_down/',type:'post',data:{'article_id':'{{ article_obj.pk }}','is_up':isUp,'csrfmiddlewaretoken':'{{ csrf_token }}'},success:function (args) {if(args.code == 1000){$('#digg_tips').text(args.msg)// 将前端的数字加一// 先获取到之前的数字let oldNum = $div.children().text();  // 文本 是字符类型// 易错点$div.children().text(Number(oldNum) + 1)  // 字符串拼接了 1+1 = 11  11 + 1 = 111}else{$('#digg_tips').html(args.msg)}}})})</script>

文章评论

"""
我们先写根评论
再写子评论点击评论按钮需要将评论框里面的内容清空根评论有两步渲染方式1.DOM临时渲染2.页面刷新render渲染子评论点击回复按钮发生了几件事1.评论框自动聚焦2.将回复按钮所在的那一行评论人的姓名@username3.评论框内部自动换行根评论子评论都是点击一个按钮朝后端提交数据的parent_id
根评论子评论区别在哪?parent_id
"""

5 后台管理、添加文章、修改用户头像、BBS总结

后台管理

"""
当一个文件夹下文件比较多的时候 你还可以继续创建文件夹分类处理templates文件夹backend文件夹应用1文件夹应用2文件夹
"""

添加文章

有两个需要注意的问题1.文章的简介不能直接切去应该先想办法获取到当前页面的文本内容之后截取150个文本字符2.XSS攻击针对支持用户直接编写html代码的网址针对用户直接书写的script标签 我们需要处理1.注视标签内部的内容2.直接将script删除如何解决?我们自己的话针对1 后端通过正则表达式筛选针对2 首先需要确定及获取script标签这两步都好烦 有木有人来帮我一下beautifulsoup模块         bs4模块专门用来帮你处理html页面内的该模块主要用于爬虫程序下载千万不要下错了pip3 install beautifulsoup4# 模块使用soup = BeautifulSoup(content,'html.parser')tags = soup.find_all()# 获取所有的标签for tag in tags:# print(tag.name)  # 获取页面所有的标签# 针对script标签 直接删除if tag.name == 'script':# 删除标签tag.decompose()# 文章简介# 1 先简单暴力的直接切去content 150个字符# desc = content[0:150]# 2 截取文本150个desc = soup.text[0:150]
"""
当你发现一个数据处理起来不是很方便的时候
可以考虑百度搜搜有没有现成的模块帮你完成相应的功能
"""

kindeditor富文本编辑器

编辑器的种类有很多,你可以自己去网上搜索

编辑器上传图片

别人写好了接口 但是接口不是你自己的
你需要手动去修改# 在使用别人的框架或者模块的时候 出现了问题不要慌 看看文档可能会有对应的处理方法

修改用户头像

@login_required
def set_avatar(request):if request.method == 'POST':file_obj = request.FILES.get('avatar')# models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=file_obj)  # 不会再自动加avatar前缀# 1.自己手动加前缀# 2.换一种更新方式user_obj = request.useruser_obj.avatar = file_objuser_obj.save()return redirect('/home/')blog = request.user.blogusername = request.user.usernamereturn render(request,'set_avatar.html',locals())

bbs项目总结

"""
在开发任意的web项目的时候 其实到了后期需要写的代码会越来越少
都是用已经写好的url填写到a标签href属性完成跳转即可
"""
主要功能总结表设计   开发流程(粗燥流程 还可以细化)注册功能forms组件使用头像动态展示错误信息提示登陆功能图片验证码滑动验证码首页展示media配置主动暴露任意资源接口个人站点展示侧边栏展示侧边栏筛选侧边栏inclusion_tag文章详情页点赞点踩评论后台管理
"""
针对bbs需要你掌握每一个功能的书写思路 内部逻辑
之后再去敲代码熟悉 找感觉
"""

Django框架项目——BBS项目介绍、表设计、表创建同步、注册、登录功能、登录功能、首页搭建、admin、头像、图片防盗、个人站点、侧边栏筛选、文章的详情页、点赞点踩、评论、后台管理、添加文章、头像相关推荐

  1. Django框架学习(一)Django框架安装和项目创建详解

    Django框架学习(一)Django框架安装和项目创建详解 文章目录 Django框架学习(一)Django框架安装和项目创建详解 一.简介 1.1介绍 1.2 URL 1.3.框架原理 二.安装 ...

  2. Django框架的电商商城的设计与实现python语言

     摘要 随着计算机技术,网络技术的迅猛发展,Internet 的不断普及,网络在各个领域里发挥了越来越重要的作用.特别是随着近年人民生活水平不断提高,电商商城给商家的业务带来了更大的发展机遇. 在经济 ...

  3. python制作物联网控制软件下载_基于Python和Django框架的物联网智能设备管理系统的设计与实现...

    论文写作指导:请加QQ229366758 基于Python和Django框架的物联网智能设备管理系统的设计与实现 作者:未知 摘 要:针对目前日益增多的智能设备提出了兼容性好,稳定性高,易于管理的管理 ...

  4. 基于Python的Django框架的二手物品交易平台的设计与实现

    基于Python的Django框架的二手物品交易平台的设计与实现  源码获取:https://www.bilibili.com/video/BV1Ne4y1g7dC/ 由于科学技术的快速进步,社会的每 ...

  5. BBS论坛 文章详情、点赞、评论

    六.文章详情.点赞.评论 文章详情页面: def article_detail(request, username, article_id):# user_obj = models.UserInfo. ...

  6. python管理系统设计_基于Python和Django框架的物联网智能设备管理系统的设计与实现

    杨武帅 万启元 桑贤伯 摘 要:针对目前日益增多的智能设备提出了兼容性好,稳定性高,易于管理的管理系统的设计方法.在服务器端使用Python和Django框架进行与设备的数据交互及设备管理网站的构架, ...

  7. [精选]万能节日国庆头像小程序(可引流,开通流量主,带pc后台管理)

    前言 牛云万能节日头像小程序,流量主变现,外卖cps权益变现,uniCloud云开发无需购买服务器和域名,助力每一位创业者. 技术优势 基于 uniapp + uniCloud 研发,无需购买服务器和 ...

  8. 最新微信文章编辑器源码PHP+MYSQL架构后台可添加或删除模版

    1.首页部分内容,可以自行修改替换为您的相关内容 2.如需要添加了后台mysql数据库管理,方便管理微信样式,好评后与果优联系. 4.2015版,应用户要求,添加了后台,方便管理微信样式,可以添加,修 ...

  9. python毕业设计 基于django框架校园鲜花水果商城毕业设计设计与实现

    商城功能概要 首页.分类.商品详情页面,包含加入购物车.收藏商品.立即购买.用户登录.订单管理.地址管理:完整的购物流程,商品的加入.编辑.删除.批量选择,收货地址的选择,下单.会员中心(订单.收藏. ...

最新文章

  1. 漫画:百度从Google学来的面试题,想进大厂必备!
  2. 【Flutter】Animation 动画 ( Flutter 动画基本流程 | 创建动画控制器 | 创建动画 | 设置值监听器 | 设置状态监听器 | 布局中使用动画值 | 动画运行 )
  3. Docker常用操作命令
  4. 放置奇兵 算法 月度活动 破碎时空记录 第五关 丁丁(瓦伦丁)+杰赫拉
  5. mysql从多个表查询数据类型_MySQL 之 多表查询
  6. 把东西从学校搬回来了
  7. 成功数据恢复一例LINUX EXT3 下误删除ORACLE数据库
  8. 昔年浅谈电商服务业务应如何推广
  9. IIS 配置 PHP 环境搭建:web 文件管理器
  10. 水星路由器DNS服务器未响应,为什么新版水星路由器不能上网
  11. php源码加密 zend_zend_guard对PHP代码进行加密教程
  12. python:文献引文网络构建——基于web of science
  13. (赴日流程)家属滞在签证
  14. 百度计算机调试员论文,基于西门子工业软件的仿真专业系统设计一阶惯性加纯滞后对象百度精.doc...
  15. 微信小程序开发之录音机 音频播放 动画 (真机可用)
  16. python学习,共同成长,招集python+odoo共同创业合伙人
  17. Python 跳出嵌套循环的5种方法
  18. 2020Java开发工程师面试题
  19. 前端 各种API网站,教程网站,素材网站,工具网站,非常好用
  20. 笑话大全查询易语言代码

热门文章

  1. 【HTML+CSS】代码:品优购网站
  2. unity烘培单个物体_Unity烘焙步骤.doc
  3. 好商品、好价格、好场景、好服务,苏宁双十一“四好”实力宠粉
  4. 【Android-通讯-蓝牙】你还不会 BLE?最全入门指南?
  5. 毕业暑假在家躺平半个月之后。。
  6. 在线电晕处理器 孔隙度探测器 实验室火花检漏仪 实验室电晕处理器 实验室电晕处理机 有哪些?
  7. python列表练习
  8. DDay.iCal - an iCalendar class library
  9. 使用Okhttp上传图片
  10. 雾霾来临,出门在外的led显示屏应该怎么保护好自己?