BBS项目

项目开发流程

1. 需求分析

        架构师+产品经理+开发组组长

        在跟客户谈需求之前,会大致先了解客户的需求,然后自己先设计一套比较好写的方案

        在跟客户沟通交流中引导客户往我们之前想好的方案上面靠

        形成一个初步的方案

2. 项目设计

        架构师干的活

                编程语言的选择

                框架选择

                数据库选择

                        主库:Mysql,posggreSQL...

                        缓存数据库:redis, mongodb, memcacha...

                功能划分

                        将整个项目划分成几个功能模块

                找组长开会

                        给每个组分发任务

                项目报价

                        技术这块需要多少人,多少天

                        产品经理公司层面  再加点钱

                                公司财务签字确认

                                公司老板签字确认

                        产品经理去跟客户沟通

                        后续需要加功能 继续加钱

3. 分组开发

        组长找组员开会,安排各自功能模块

        其实就是在架构师设计好的框架里面填写代码而已

        在写代码的时候,写完需要自己先测试是否有bug

4. 测试

        测试部门测试代码

               功能测试

                压力测试

                ...

5. 交付上线

        1. 交给堆放的运维人员

        2. 直接上线到我们的服务器上 收取维护费用

数据库表设计


数据库的表设计是整个项目的重点所在
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                1
        1                2                1
        1                3                0
        2                1                1
        
    
    7.文章评论表
        记录哪个用户给哪篇文章写了哪些评论内容
        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_id
    1         1                        1                                        
    2         2                        1                                        1                    
        
根评论子评论的概念
    根评论就是直接评论当前发布的内容的
        
    子评论是评论别人的评论
        1.PHP是世界上最牛逼的语言
            1.1 python才是最牛逼的
                1.2 java才是
        
    根评论与子评论是一对多的关系
    
        

表创建及同步

from django.db import models

# Create your models here.


from django.contrib.auth.models import AbstractUser

"""
先写普通字段
之后在写外键字段
"""


class 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, on_delete=models.CASCADE)


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, on_delete=models.CASCADE)


class Tag(models.Model):
    name = models.CharField(verbose_name='文章标签', max_length=32)
    blog = models.ForeignKey(to='Blog', null=True, on_delete=models.CASCADE)


class Article(models.Model):
    name = models.CharField(verbose_name='文章标题', max_length=64)
    desc = models.CharField(verbose_name='文章简介', max_length=255)
    # 文章内容有很多 一般情况下都是使用TextField
    content = 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, on_delete=models.CASCADE)
    category = models.ForeignKey(to='Category', null=True, on_delete=models.CASCADE)
    tags = models.ManyToManyField(to='Tag',through='Article2Tag',through_fields=('article','tag'))


class Article2Tag(models.Model):
    article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
    tag = models.ForeignKey(to='Tag', on_delete=models.CASCADE)


class UpAndDown(models.Model):
    user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE)
    article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
    is_up = models.BooleanField()  # 传布尔值 存0或1


class Comment(models.Model):
    user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE)
    article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
    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, on_delete=models.CASCADE)  # 有些评论就是跟评论

注册功能

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>BBS</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
          integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css"
          integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"
            integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"
            crossorigin="anonymous"></script>


</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <h1 class="text-center">注册</h1>
            <form action="" id="myform"> <!--这里我们不用form表单提交数据 只是单纯的用一下form标签而已-->
                {% csrf_token %}
                {#                {% for form in form_obj %}#}
                {#                    <label for="">{{ form.label }}</label>{{ form }} <span style="color: red">{{ form.errors.0 }}</span>#}
                {#                {% endfor %}#}
                {% for form in form_obj %}
                    <div class="form-group">
                        <label for="{{ form.auto_id }}">{{ form.label }}</label>{{ form }}
                        <span style="color: red" class="pull-right">{{ form.error.0 }}</span>
                    </div>
                {% endfor %}
                <div class="form-group">
                    <label for="myfile">头像
                        {% load static %}
                        <img src="{% static 'img/default.png' %}" alt="" width="100" style="margin-left: 10px"
                             id="myimg"></label>
                    <input type="file" id="myfile" name="avatar" style="display: none">
                </div>


                <input type="button" class="btn btn-primary  pull-right btn-block" value="注册" id="id_commit">
            </form>

        </div>
    </div>
</div>
<script>
    $('#myfile').change(function () {
        {#alert(123)#}
        // 文件阅读器对象
        // 1. 先生成一个文件阅读器对象
        let myFileReaderObj = new FileReader()
        // 2. 获取用户上传的头像文件
        let fileObj = $(this)[0].files[0]
        console.log(fileObj)
        // 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])
        // 发送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框下面
                    console.log(args.msg)
                    // 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>

</body>
</html>
def register(request):
    form_obj = MyRegForm()
    if request.method == 'POST':
        back_dic = {'code': 1000, 'msg': ''}

        # 校验数据是否合法
        form_obj = MyRegForm(request.POST)
        # print('previous_form_obj:',form_obj)
        # print('form_obj',form_obj)
        # 判断数据是否合法
        if form_obj.is_valid():
            # print('form_obj:',form_obj.cleaned_data)
            clean_data = form_obj.cleaned_data # 将校验通过的数据字典赋值给一个变量
            # 将字典里面的confirm_password键值对删除
            clean_data.pop('confirm_password')
            # print(clean_data)
            # 用户头像
            file_obj = request.FILES.get('avatar')
            print(file_obj)
            """针对用户头像一定有判断是否传值 不能直接添加到字典里去"""
            if file_obj:
                clean_data['avatar'] = file_obj
            # 直接操作数据库保存数据
            models.UserInfo.objects.create_user(**clean_data)
            back_dic['url'] ='/login/'
        else:
            back_dic['code'] = 2000
            back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)
    return render(request,'register.html',locals())

登录功能

img标签的src属性

        1. 图片路径

        2. url

        3. 图片的二进制数据

我们的计算机上面之所以能够输出各式各样的字体样式,内部其实对应的是一个个.ttf结尾的文件

字体高级筛选-找字网,您的字体管家!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
          integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css"
          integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"
            integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"
            crossorigin="anonymous"></script>
    {% load static %}


</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-offset-2 col-md-8">
            <h1 class="text-center">登录</h1>
            <div class="form-group">
                <label for="username">用户名</label>
                <input type="text" name="username" id="username" class="form-control">
            </div>
            <div class="form-group">
                <label for="password">密码</label>
                <input type="password" name="password" id="password" class="form-control">
            </div>
            <div class="form-group">
                <label for="">验证码</label>
                <div class="row">
                    <div class="col-md-6">
                        <input type="text" name="code" id="id_code" class="form-control">

                    </div>
                    <div class="col-md-6">
                        <img src="/get_code/" alt="" width="410px" height="35px" id="id_img">
                    </div>
                </div>
            </div>
            <input type="button" class="btn btn-success" value="登录" id="id_commit">
            <span style="color: red" id="error"></span>
        </div>
    </div>
</div>
<script>
    $('#id_img').click(function (){
        // 1. 先获取标签之前的src
        let oldVal = $(this).attr('src')
        $(this).attr('src',oldVal+='?')
    })
    // 点击按钮发送ajax请求
    $('#id_commit').click(function (){
        $.ajax({
            url:'',
            type:'post',
            data:{
                'username':$('#username').val(),
                'password':$('#password').val(),
                'code':$('#id_code').val(),
                // 自己结合需求 合理选择
                'csrfmiddlewaretoken':'{{ csrf_token }}'
            },
            success:function (args){
                if(args.code===1000){
                    // 跳转到首页
                    window.location.href = args.url
                }else{
                    // 渲染错误信息
                    $('#error').text(args.msg)
                }
            }
        })
    })
</script>
</body>
</html>
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'] = 2000
                back_dic['msg'] = '用户名或密码错误'
        else:
            back_dic['code'] = 3000
            back_dic['msg'] = '验证码错误'
        return JsonResponse(back_dic)


    return render(request,'login.html',locals())

主页

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>BBS</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
          integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css"
          integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"
            integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd"
            crossorigin="anonymous"></script>


</head>
<body>
<nav class="navbar navbar-inverse">
    <div class="container-fluid">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">BBS</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
                <li><a href="#">文章</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">More <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">Action</a></li>
                        <li><a href="#">Another action</a></li>
                        <li><a href="#">Something else here</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">Separated link</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">One more separated link</a></li>
                    </ul>
                </li>
            </ul>
            <form class="navbar-form navbar-left">
                <div class="form-group">
                    <input type="text" class="form-control" placeholder="Search">
                </div>
                <button type="submit" class="btn btn-default">Submit</button>
            </form>
            <ul class="nav navbar-nav navbar-right">
                {% 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="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>
                            <li><a href="#">修改头像</a></li>
                            <li><a href="#">后台管理</a></li>
                            <li role="separator" class="divider"></li>
                            {#                            <li><a href="{% url 'logout' %}">退出登录</a></li>#}
                            <li><a href="{% url 'logout' %}">退出登录</a></li>
                        </ul>

                    </li>
                {% else %}
                    <li><a href="{% url 'reg' %}">注册</a></li>
                    <li><a href="{% url 'login' %}">登录</a></li>
                {% endif %}
                {#                <button type="button" class="btn btn-primary" data-toggle="modal" data-target=".bs-example-modal-lg">#}
                {#                    Large modal#}
                {#                </button>#}

            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel ">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <h1 class="text-center">修改密码</h1>
            <div class="row">
                <div class="col-md-8 col-md-offset-2">
                    <div class="form-group">
                        <label for="">用户名</label>
                        <input type="text" disabled value="{{ request.user.username }}" class="form-control">
                    </div>
                    <div class="form-group">
                        <label for="">原密码</label>
                        <input type="password" name="old_password" class="form-control" id="id_old_password">
                    </div>
                    <div class="form-group">
                        <label for="">新密码</label>
                        <input type="password" name="new_password" class="form-control" id="id_new_password">
                    </div>
                    <div class="form-group">
                        <label for="">确认密码</label>
                        <input type="password" name="confirm_password" class="form-control" id="id_confirm_password">
                    </div>
                    <div class="modal-dialog">
                        <button type="button" class="btn btn-default " data-dismiss="modal">Cancel</button>
                        <button class="btn btn-primary" id="id_edit">Modify</button>
                        <span style="color: red" id="password_error"></span>
                    </div>

                </div>
            </div>
        </div>
    </div>
</div>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-2">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">重金求子</h3>
                </div>
                <div class="panel-body">
                    事成之后,上海别墅一套外加现金500万
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">千万大奖</h3>
                </div>
                <div class="panel-body">
                    抓紧联系:18605811419
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">线上</h3>
                </div>
                <div class="panel-body">
                    在线,你还在等什么
                </div>
            </div>
        </div>
        <div class="col-md-8">

        </div>
        <div class="col-md-2">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">重金求子</h3>
                </div>
                <div class="panel-body">
                    事成之后,上海别墅一套外加现金500万
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">千万大奖</h3>
                </div>
                <div class="panel-body">
                    抓紧联系:18605811419
                </div>
            </div>
            <div class="panel panel-info">
                <div class="panel-heading">
                    <h3 class="panel-title">线上</h3>
                </div>
                <div class="panel-body">
                    在线,你还在等什么
                </div>
            </div>
        </div>
    </div>
</div>
<script>
    $('#id_edit').click(function () {
        $.ajax({
            url: '/set_password/',
            type: 'post',
            data: {
                'old_password': $('#id_old_password').val(),
                'new_password': $('#id_new_password').val(),
                'confirm_password': $('#id_confirm_password').val(),
                'csrfmiddlewaretoken': '{{ csrf_token }}'
            },
            success: function (args) {
                if (args.code === 1000) {
                    {#window.location.href = '/login/'#}
                    window.location.reload()
                } else {
                    $('#password_error').text(args.msg)
                }
            }
        })
    })
</script>
</body>
</html>

admin后台管理

django给你提供了一个可视化的界面用来让你方便的对你的模型表进行数据的增删改查操作

如果你想要使用admin后台管理操作模型表

你需要先注册你的模型表告诉admin你需要操作哪些表

去应用下的admin.py中注册你的模型表

from app01 import models

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/ 删

关键点就在于urls.py中的第1条自带的url

前期我们需要自己手动录入数据

1. 数据绑定尤其需要注意的是用户和个人站点不要忘记绑定了

2. 标签

3. 标签和文章

        千万不要把别人的文章绑定标签

用户头像展示

1 网址所使用的静态文件默认放在static文件夹下
2 用户上传的静态文件也应该单独放在某个文件夹下

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

图片防盗链

如何避免别的网站直接通过本网站的url访问本网站资源

简单的防盗
    我可以做到请求来的时候先看看当前请求是从哪个网站过来的
  如果是本网站那么正常访问
  如果是其他网站直接拒绝
      请求头里面有一个专门记录请求来自于哪个网址的参数
        Referer: http://127.0.0.1:8000/xxx/

 如何避免
    1.要么修改请求头referer
  2.直接写爬虫把对方网址的所有资源直接下载到我们自己的服务器上

个人站点

每个用户都可以有自己的站点样式

    <link rel="stylesheet" href="/media/css/{{ blog.site_theme }}">

id        content        create_time

1        111                  2020-11-11

django 官网提供的一个orm语法

    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')

侧边栏筛选功能 

侧边栏筛选功能
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())
```

 文章详情页

url设计

/username/article/1

先验证url是否会被其他url顶替

文章详情页和个人站点页基本一致,所以用模板的继承

侧边栏的渲染需要传入数据才能渲染        并且该侧边栏在很多也要使用

        1. 哪个地方用就拷贝需要的代码(不推荐 有点繁琐)

        2. 将侧边栏制作成 inclusion_tag

                步骤:

                        1. 在应用下创建一个名字必须叫templatetags文件夹

                        2. 在该文件夹内创建一个任意名称的py文件

                        3. 在该py文件内先固定写两行代码

                                from django import template

                                register = template.Library()

                                # 自定义过滤器

                                # 自定义标签

                                # 自定义inclusion_tag

                                

from django import template
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth

register = template.Library()

# 自定义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')).values_list('name',
                                                                                                               'count_num',
                                                                                                               'pk')

    # print(category_list)  #<QuerySet [('jeffrey的分类1', 2), ('jeffrey的分类2', 1), ('jeffrey的分类3', 1)]>
    # 2 查询当前用户所有的标签及标签下的文章数
    tag_list = models.Tag.objects.filter(blog=blog).annotate(count_num=Count('article')).values_list('name',
                                                                                                     'count_num', 'pk')
    # print('tag_list',tag_list) #tag_list <QuerySet [('jeffrey的标签1', 1), ('jeffrey的标签2', 2), ('jeffrey的标签3', 1)]>

    # 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')
    return locals()

文章点赞点踩

 浏览器上你看到的花里胡哨的页面,内部都是HTML(前端)代码

那现在我们的文章内容应该写什么??? >>> HTML 代码

如何拷贝文章

        copy outerhtml

1. 拷贝文章

2. 拷贝点赞点踩

        1. 拷贝前端点赞点踩图标 只拷了html        

        2. css也要拷

                由于有图片防盗链的问题 所以将图片直接下载到本地

思考:

        前端如何区分用户是点了赞还是点了踩

        1. 给标签各自绑定一个事件

                两个标签对应的代码其实基本一样,仅仅是是否点赞点踩这一个参数不一样而已

        2. 二合一

                给两个标签绑定一个事件

                给所有的action类绑定事件

        $('.action').click(function () {
            console.log($(this).hasClass('diggit'))
        })

由于点赞点踩内部有一定的业务逻辑,所以后端单独开设视图函数处理

# 写代码先把所有正确的逻辑写完再去考虑错误的逻辑 不要想着两者兼得

{% extends 'base.html' %}

{% block css %}
    <style>
        #div_digg {
            float: right;
            margin-bottom: 10px;
            margin-right: 30px;
            font-size: 12px;
            width: 125px;
            text-align: center;
            margin-top: 10px;
        }

        .diggit {
            float: left;
            width: 46px;
            height: 52px;
            background: url('/static/img/upup.gif') no-repeat;
            text-align: center;
            cursor: pointer;
            margin-top: 2px;
            padding-top: 5px;
        }

        .buryit {
            float: right;
            margin-left: 20px;
            width: 46px;
            height: 52px;
            background: url('/static/img/downdown.gif') no-repeat;
            text-align: center;
            cursor: pointer;
            margin-top: 2px;
            padding-top: 5px;
        }

        .clear {
            clear: both;
        }

        .diggword {
            margin-top: 5px;
            margin-left: 0;
            font-size: 12px;
            color: #808080;
        }
    </style>
{% endblock %}

{% block content %}
    <h1>{{ article_obj.title }}</h1>
    <div class="article-content">
        {{ article_obj.content|safe }}
    </div>

    {#    点赞点踩样式开始#}
    <div>
        <div id="div_digg ">

            <div class="buryit pull-right action">
                <span class="burynum" id="bury_count">{{ article_obj.down_num }}</span>
            </div>
            <div class="diggit pull-right action">
                <span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword pull-right" id="digg_tips" style="color: red">
            </div>
        </div>
    </div>
    {#    点赞点踩样式结束#}
{% endblock %}

{% block js %}
    <script>
        // 给所有的action类绑定事件
        $('.action').click(function () {
            {#console.log($(this).hasClass('diggit'))#}
            let is_Up = $(this).hasClass('diggit')
            let $div = $(this)
            // 朝后端发送ajax请求
            $.ajax({
                url:'/up_or_down/',
                type:'post',
                data:{
                    'article_id':'{{ article_obj.pk }}',
                    'is_up':is_Up,
                    'csrfmiddlewaretoken':'{{ csrf_token }}',
                },
                success:function (args){
                    if (args.code===1000){
                        $('#digg_tips').text(args.msg)
                        // 将前端的数字+1
                        // 先获取到之前的数字
                        let oldNum = $div.children().text() //文本是字符类型
                        // 易错点
                        $div.children().text(Number(oldNum)+1)  // 字符串拼接  1+1 = 11 11+1 =111

                    }else {
                        $('#digg_tips').html(args.msg)
                    }

                }
            })
        })
    </script>
{% endblock %}

def up_or_down(request):
    """
    1. 校验用户是否登录
    2. 判断当前文章是否是当前用户自己写的(自己不能点自己的文章)
    3. 当前用户是否已经给当前文章点过了(点过了就不能再点)
    4. 操作数据库了
    :param request:
    :return:
    """
    if request.is_ajax():
        back_dic = {'code':1000,'msg':''}
        print('is_authenticated:',request.user.is_authenticated)
        # 1 先判断当前用户是否登录
        if request.user.is_authenticated:
            article_id = request.POST.get('article_id')
            is_up = request.POST.get('is_up')
            is_up = json.loads(is_up)   # 记得转换
            # print(is_up,type(is_up))
            # 2. 判断当前文章是否是当前用户自己写的 根据文章id查询文章对象 根据文章对象查询作者 跟request.user比对
            article_obj = models.Article.objects.filter(pk=article_id).first()
            # print('request.user:', request.user, type(request.user))
            # print('article_obj.blog.userinfo:', article_obj.blog.userinfo, type(article_obj.blog.userinfo))
            print(article_obj.blog.userinfo==request.user)
            if not article_obj.blog.userinfo == request.user:
                print('request.user:',request.user,type(request.user))
                print('article_obj.blog.userinfo:',article_obj.blog.userinfo,type(article_obj.blog.userinfo))
                # 3. 校验当前用户是否已经点了 哪个地方记录了用户到底点没点
                is_click = models.UpAndDown.objects.filter(user=request.user,article=article_obj)
                if not is_click:
                    # 4. 操作数据库记录数据 要同步操作普通字段
                    # 判断当前用户点了赞还是踩  从而决定给哪个字段+1
                    if is_up:
                        # 给点赞数+1
                        models.Article.objects.filter(pk=article_id).update(up_num = F('up_num') + 1)
                        back_dic['msg'] = '点赞成功'
                    else:
                        # 给点踩数+1
                        models.Article.objects.filter(pk=article_id).update(down_num=F('down_num')+1)
                        back_dic['msg'] = '点踩成功'
                    # 操作点赞点踩表 UpAndDown
                    models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up)
                else:
                    back_dic['code'] =1001
                    back_dic['msg'] = '你已经点过了,不能再点了' # 这里你可以做的更加的详细,提示用户到底点了赞还是点了踩
            else:
                back_dic['code'] =1002
                back_dic['msg'] = '你个臭不要脸的!自己不能给自己点'
        else:
            back_dic['code'] = 1003
            back_dic['msg'] = '请先<a href="/login/">登录</a>'
        return JsonResponse(back_dic)

文章评论

我们先写根评论

再写子评论

点击评论按钮需要将评论框里面的内容清空

根评论有两步渲染方式 

        1. DOM 临时渲染

        2. 页面刷新render渲染

子评论

        点击回复按钮发生了几件事

                1. 评论框自动聚焦

                2. 将回复按钮所在的那一行评论人的姓名

                        @username

                3. 评论框内部自动换行

根评论子评论都是点击一个按钮朝后端提交数据的

        parent_id

根评论子评论区别在哪?

        parent_id

 后台管理

当一个文件夹下面文件比较多的时候 可以继续创建文件夹分类处理

templates文件夹

        backend文件夹

        应用1文件夹

        应用2文件夹

添加文章

有两个需要注意的问题

        1. 文章的简介

                不能直接切取

                应该先想办法渠道当前页面的文本内容之后切取150个

        2. XSS攻击

                针对支持用户直接编写HTML代码的网址

                针对用户直接书写的script标签 需要处理

                1. 注释标签内部的内容

                2. 直接将script删除

如何解决?

        自己解决的话:

                针对1 后端通过正则表达式筛选

                针对2 首先需要确定及获取script标签

        bs4- beautifulsoup模块

                专门用来处理HTML页面内容

                该模块主要用于爬虫程序

                pip install beautifulsoup4

# 模块使用
        soup = BeautifulSoup(content,'html.parser')

        tags = soup.find_all()
        # 获取所有的标签

        print('tags:',tags)
        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富文本编辑器

kindeditor富文本编辑器

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

编辑器上传图片 

别人写好了接口 但是接口不是你的

需要手动去修改

使用别人的框架或者模块的时候 看看文档会有对应的处理方法 

修改用户头像

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

bbs项目总结

在开发任意的web项目的时候 其实到了后期需要写的代码会越来越少

都是用已经写好的url填写到a标签href属性中完成跳转即可 

主要功能总结

        表设计 开发流程

        注册功能

                forms组件使用

                头像动态展示

                错误信息提示

        登录功能

                图片验证码

                滑动验证码

         首页展示

                media配置

                主动暴露任意资源接口

        个人站点展示

                侧边栏展示

                侧边栏筛选

                侧边栏inclusion_tag制作

        文章详情页

                点赞点踩

                评论

        后台管理

 针对bbs需要掌握每一个功能的书写思路 内部逻辑

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值