首页 > 其他分享 >12 BBS博客项目

12 BBS博客项目

时间:2023-02-13 07:44:04浏览次数:59  
标签:12 obj img random request 博客 user article BBS

BBS博客项目

数据库表设计

用户表(uth_user表再扩展几个字段)
   phone
   avatar
   create_time
   blog  一对一个人站点表

个人站点表
   site_name 
   site_title
   site_theme
   
标签表
   name
   blog   一对多个人站点

分类表
   name
   blog   一对多个人站点

文章表
   title
   desc
   content
   create_time
   blog		一对多个人站点
   tag			多对多标签
   category	一对多分类
   # 数据库设计优化(******)
   # 文章的评论数需要跨表查询到评论表筛选出user和article再count,数据库压力大
   # 建comment_num普通字段,通过事务操作评论表时同时comment_num字段加1,再需要文章的评论数只需要拿comment_num字段,不再需要跨表查了
   # 当有个字段需要频繁的跨表查才能查出来并且很麻烦的时候就可以写个普通字段来记录,只需要通过事务保证这个普通字段所对应的数字和需要跨的表所记录的数据一致就行
   comment_num	普通字段
   up_num		普通字段
   down_num	普通字段
   			
点赞点踩表
   user		一对多用户表(点赞点踩表的一条记录能否对应user表的多条记录(不能),user表的一条记录能否对应点赞点踩表的多条记录(能))
   article		一对多文章表   
   is_up		0/1
   #表关系判断
   user	article		is_up
   1		1			1
   1		2			1
   2		1			1

评论表
   user		一对多用户表		
   article		一对多文章表  
   content				
   create_time	    
   parent		一对多评论表(自关联),父评论的id,如果有值说明是子评论,如果没有值说明是父评论,parent = models.ForeignKey(to='self', null=True)  # 或者 to='Comment'
   """
   <div>
   {% if comment.parent %}
   <p>@{{ comment.parent.user.username }}</p>  # comment.parent到了当前评论的父评论
   {% endif %}
   {{ comment.content }}
   </div>
   """

展示用户上传的头像

<body>
<div class="form-group">
    <label for="myfile">头像
        <img src="/static/img/default.webp" alt="" height="80" style="margin-left: 20px;" id="img">
    </label>
    <input type="file" name="avatar" id="myfile" style="display: none">
</div>
</body>
<script>
    $('#myfile').change(function () {
        // 获取用户上传的头像,替换到img标签中
        // 1 获取用户上传的文件对象
        var fileObj = $(this)[0].files[0];
        // 2.利用内置对象FileReader文件阅读器
        var fileReader = new FileReader();  
        // 3.将文件对象交由FileReader文件阅读器读取文件内容
        fileReader.readAsDataURL(fileObj);  // IO操作速度较慢
        // 4.等待文件阅读器读取完文件数据之后,找到img标签,修改src属性
        fileReader.onload = function(){$('#img').attr('src',fileReader.result)}
    });
</script>

图片验证码

<body>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h2 class="text-center">登陆</h2>
            <div class="form-group">
                <label for="id_username">用户名</label>
                <input type="text" name="username" class="form-control" id="id_username">
            </div>
            <div class="form-group">
                <label for="id_password">密码</label>
                <input type="password" name="password" class="form-control" id="id_password">
            </div>
            <div class="form-group">
                <label for="id_code">验证码</label>
                <div class="row">
                    <div class="col-md-6">
                        <input type="text" id="id_code" name="code" class="form-control">
                    </div>
                    <div class="col-md-6">
                        <img src="/get_code/" alt="" height="35" width="360" id="id_img">
                    </div>
                </div>
            </div>
            <button class="btn btn-success" id="id_submit">登陆</button>
            <span class="errors" style="color: red"></span>
        </div>
    </div>
</div>
<script>
    $('#id_img').click(function () {
        // 图片点击后在原src后加个?,即重新向后端要图片验证码
        var oldSrc = $(this).attr('src');
        $(this).attr('src',oldSrc + '?')
    });
</script>
</body>
from PIL import Image,ImageDraw,ImageFont  # pip3 install pillow
"""
Image  生成图片
ImageDraw  在图片上写字
ImageFont  控制字体样式
"""
import random
from io import BytesIO,StringIO
"""
io  内存管理器模块
BytesIO  存储数据 二进制格式
StringIO  存储数据 字符串格式
"""

def get_random():
    return random.randint(0,255),random.randint(0,255),random.randint(0,255)

def get_code(request):
    # 推导步骤1: 直接将本地图片以二进制方式读取发送给前端
    # with open(r'D:\python\BBS\avatar\222.jpg','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)
    
    # 推导步骤2: pillow模块生成图片
    # # img_obj = Image.new('RGB',(360,35),'green')  # image.new(图片格式,(长,宽),颜色)
    # img_obj = Image.new('RGB',(360,35),get_random())
    # 先以文件的形式保存下来
    # with open('xxx','wb') as f:
    #     img_obj.save(f,'png')
    # 然后再打开这个文件发送
    # with open('xxx','rb') as f:
    #     data = f.read()
    # return HttpResponse(data)
    
    # 推导步骤3: BytesIO临时存储文件,避免频繁文件读写操作
    # img_obj = Image.new('RGB',(360,35),get_random())
    # io_obj = BytesIO()  # 实例化产生一个内存管理对象,可以把它当成文件句柄
    # img_obj.save(io_obj,'png')
    # return HttpResponse(io_obj.getvalue())  # 从内存对象中获取二进制的图片数据
    
    # 推导步骤4: 在生成的图片上写验证码
    img_obj = Image.new('RGB', (360,35), get_random())
    img_draw = ImageDraw.Draw(img_obj)  # 生成一个可以在图片上写字的画笔对象
    img_font = ImageFont.truetype('static/font/222.ttf',30)  # 字体样式及大小
    io_obj = BytesIO()
    # 五位验证码,包含数字或大写字母或小写字母
    code = ''
    for i in range(5):
        upper_str = chr(random.randint(65,90))
        low_str = chr(random.randint(97,122))
        random_int = str(random.randint(0,9))
        temp_code = random.choice([upper_str,low_str,random_int])
        # 朝图片上写验证码
        img_draw.text((70+i*45,0),temp_code,get_random(),font=img_font)  # 画笔对象.text((x轴,y轴),写什么,字体颜色,字体样式)
        code += temp_code
    # 将产生的随机验证码存入session中,以便后续其他视图函数获取并校验验证码
    request.session['code'] = code
    img_obj.save(io_obj,'png')
    return HttpResponse(io_obj.getvalue())

前端判断用户是否登陆

{% 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="/login/">登陆</a></li>
    <li><a href="/register/">注册</a></li>
{% endif %}

用户表绑定个人站点表报错

解决:

class UserInfo(AbstractUser):
    phone = models.BigIntegerField(null=True,blank=True)  # blank是用来告诉admin后台该字段可以不填

文章按年月归档

from django.db.models.functions import TruncMonth

def site(request, username):
    date_menu = models.Article.objects.filter(blog=blog).annotate(month=TruncMonth('create_time')).values('month').annotate(c=Count('pk')).values_list('month','c')  # annotate(month=TruncMonth('create_time'))根据create_time字段按年月生成虚拟字段month
    print(date_menu)  # <QuerySet [(datetime.date(2023, 1, 1), 2), (datetime.date(2023, 6, 1), 1), (datetime.date(2023, 3, 1), 1)]> 虽然显示年月日但日都显示1号
    return render(request, 'site.html', locals())
{% for date in date_menu %}
	<p><a href="#">{{ date.0|date:'Y年m月' }}({{ date.1 }})</a></p>
{% endfor %}

KindEditor编辑器

官网:http://kindeditor.net/

下载:http://kindeditor.net/down.php

文档:http://kindeditor.net/doc.php

下载解压后复制到bbs项目的static文件夹下

使用:

add_article.html

{% extends 'backend/base.html' %}

{% block article %}
    <h2>添加文章</h2>
    <p>标题</p>
    <form action="" method="post">
        {% csrf_token %}
        <p><input type="text" name="title" class="form-control" id="id_title"></p>
        <p>内容(kindeditor编辑器,支持拖放/粘贴上传图片)</p>
        <p>
            <textarea name="content" id="id_content" cols="30" rows="10"></textarea>
        </p>

        <div class="panel panel-primary">
            <div class="panel-heading">
                <h3 class="panel-title">文章标签</h3>
            </div>
            <div class="panel-body">
                {% for tag in tag_list %}
                    <input type="checkbox" name="tag" value="{{ tag.pk }}">{{ tag.name }}&nbsp;&nbsp;&nbsp;
                {% endfor %}
            </div>
        </div>
        <div class="panel panel-warning">
            <div class="panel-heading">
                <h3 class="panel-title">文章分类</h3>
            </div>
            <div class="panel-body">
                {% for category in category_list %}
                    <input type="radio" name="category" value="{{ category.pk }}">{{ category.name }}&nbsp;&nbsp;&nbsp;
                {% endfor %}
            </div>
        </div>
        <input type="submit" class="btn btn-danger" value="发布">
    </form>

    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script>
        KindEditor.ready(function (K) {
            window.editor = K.create('#id_content', {
                width: '100%',
                height: '450px',
                resizeType: 1,
                uploadJson : '/upload_img/',
                extraFileUploadParams : {
                	'csrfmiddlewaretoken':'{{ csrf_token }}'
                }
            });
        });
    </script>
{% endblock %}

urls.py

path('upload_img/', views.upload_img),  # 文本编辑器上传图片功能

views.py

def upload_img(request):
    # 接收用户写文章上传的图片资源
    if request.method == 'POST':
        file_obj = request.FILES.get('imgFile')
        # 将文件存入media文件下一个专门用来存储文章图片的文件夹(article_img)
        # 1 先拼接出图片所在的文件夹路径
        base_path = os.path.join(settings.BASE_DIR,'media','article_img')
        if not os.path.exists(base_path):
            os.mkdir(base_path)
        # 2 拼接图片的具体路径
        file_path = os.path.join(base_path,file_obj.name)
        # 3 文件操作
        with open(file_path,'wb') as f:
            for line in file_obj:
                f.write(line)
        """
        KindEditor返回格式(JSON):
        	//成功时
            {
                    "error" : 0,
                    "url" : "http://www.example.com/path/to/file.ext"
            }
            //失败时
            {
                    "error" : 1,
                    "message" : "错误信息"
            }
        """
        back_dic = {
            'error':0,
            'url':'/media/article_img/%s'%file_obj.name
        }
        return JsonResponse(back_dic)

处理xss攻击和文章简介获取

  • 处理xss攻击即防止用户写script脚本(beautifulsoup4模块简称BS4)

    • 安装beautifulsoup4模块:pip3 install beautifulsoup4
    • 方式一:获取用户输入的所有script标签直接删除
    • 方式二:给script转义
  • 文章简介的获取

    • 截取150个中文字符
@login_required
def add_article(request):
    if request.method == 'POST':
        title = request.POST.get('title')
        content = request.POST.get('content')
        tags = request.POST.getlist('tag')
        category_id = request.POST.get('category')
	    # 先生成一个BeautifulSoup对象
        soup = BeautifulSoup(content, 'html.parser')  # content:要处理的文档内容; 指定一个解析器thml.parser
        for tag in soup.find_all():
            # print(tag.name)  # 获取当前html页面所有的标签
            if tag.name == 'script':
                tag.decompose()  # 将script标签删除
        # print(soup.text)  # 获取当前html页面所有的文本
        # desc = content[0:150]  # html标签也截取了
        desc = soup.text[0:150]  # 文章简介应该是150个文本内容
        article_obj = models.Article.objects.create(title=title, desc=desc, content=str(soup), category_id=category_id, blog=request.user.blog)  # content=str(soup)
        article_obj.tag.add(*tags)
        return redirect('/backend/')
    tag_list = models.Tag.objects.filter(blog=request.user.blog)
    category_list = models.Category.objects.filter(blog=request.user.blog)
    return render(request, 'backend/add_article.html', locals())        

标签:12,obj,img,random,request,博客,user,article,BBS
From: https://www.cnblogs.com/jlllog/p/17115191.html

相关文章

  • 12th Feb
    [1]TodoData单例模式类的完成读写文件[2]resources资源文件子文件夹命名com/beans不是com.beans[3]花费比较长的时间去找这个txt的文件;它在为了让被读取......
  • C/C++飞机订票系统[2023-02-12]
    C/C++飞机订票系统[2023-02-12]飞机订票系统1、需求分析航班信息用文件保存,因而要提供文件的输入输出操作:航班信息浏览功能需要提供显示操作;要查询航线需要提供查找功......
  • 刷刷刷 Day 32 | 122. 买卖股票的最佳时机 II
    122.买卖股票的最佳时机IILeetCode题目要求给你一个整数数组prices,其中 prices[i]表示某支股票第i天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任......
  • ~2.12 渐渐恢复状态
      隔了许久没有看论文,最近也在做啥,也还行。早晨起来便阴蒙蒙的,看手机的天气,说是雨夹雪,从窗户看去,只觉得地面湿漉漉的,看不到雪花。便想起今天我要去万圣书园。买了两本不......
  • 2023/2/12 考试总结
    时间安排8.30~9.07写了个T2的70分.9.10~10.00T1感觉题意很绕,理了很久,转化为在一棵二叉树里找到一棵子二叉树的问题。想了很多做法都不太行。10.00~10.30看到100代码......
  • LNMP架构——搭建个人博客系统
    LNMP架构(Linux+Nginx+MySQL+PHP)同LAMP架构(Linux+Apache+MySQL+PHP)一样,都是主要应用于动态网站的web架构,这两种架构具有很多优势,是目前企业部署网络的首选平台。近些年LNMP越......
  • 29th@K古算.垛积需求分析@20230212
    功能(Website)  OEIS解析python网络蜘蛛,抓取OEISFTP上传器数字成语python网络蜘蛛,抓取网络字典网站FTP上传器中国古算 FTP上传器筹算珠算 FTP......
  • [leetcode每日一题]2.12
    ​​1138.字母板上的路径​​难度中等79我们从一块字母板上的位置 ​​(0,0)​​ 出发,该坐标对应的字符为 ​​board[0][0]​​。在本题里,字母板为​​board=["abcde......
  • Tomcat漏洞(CVE-2017-12615)复现
     漏洞原理:漏洞原理:Tomcat配置文件/conf/web.xml配置了可写(readonly=false),导致可以使用PUT方法上传任意文件,攻击者将精心构造的payload向服务器上传包含任意代码的JSP......
  • 博客文章更正系列(3)
    易经占卜学问的再次解惑。一般情况下,我们人决定去做一件事情的时候,一般是产生了思想,就去做那件事了。那么,这件事情未来所会造成的结果是我们人未能预测的。这个时候,假设我们......