BBS多人博客系统

今日内容

项目开发流程

项目名字:
BBS:多人博客系统

传统软件行业:给第三方做解决方案
互联网行业:饿了么,叮咚买菜...

软件开发流程:
-项目立项(高层,市场人员:市场调研,跟客户对接)
-项目设计(产品经理:设计软件功能,设计原型图)
-项目的具体设计(UI:切图)
-分任务开发
    -前端:pc,小程序,移动端
        -分任务开发
    -后端:
        -架构,数据库设计---(设计表,表关联)
        -多人系统开发
-联调
-测试:功能测试,自动化测试,接口测试、、、
-上线运行:运维

-出现bug,项目维护阶段
-版本迭代

bbs项目表设计及关联

# 要开发的功能
-注册功能
-登录功能
-首页:文章展示,导航栏,用户中心,广告位
-个人主页:文章展示,侧边栏过滤(分类,标签,时间)
-文章详情:点赞点踩,评论(父评论,子评论)
-后台管理:这个人文章展示(增加,删除,修改文章)
-发布文章:富文本编辑器,xss攻击处理

 
# 技术选型:python3.8  django2.2.2  mysql:5.7  jquery2.x   bootstrap@3


# 设计数据库---》数据库名字 bbs
-用户表(基于auth的user表扩写,扩写字段)
-博客表(跟用户表一对一)
-标签表
-分类表
-文章表
-点赞点踩表
-评论表


# 表的关联关系
-用户表(基于auth的user表扩写,扩写字段)
-博客表(跟用户表一对一)
-标签表:跟博客表一对多,跟文章是多对多
-分类表:跟博客表一对多,跟文章是一对多
-文章表:跟博客表是一对多
-点赞点踩表:跟用户一对多,跟文章表一对多
-评论表:跟用户一对多,跟文章表一对多

补充:
OneToOneField,ForeignKey,ManyToManyField
-related_name:反向操作时,使用的字段名,用于代替原反向查询时的’表名_set’。
-related_query_name:反向查询操作时,使用的连接前缀,用于替换表名

img

项目表字段编写和表迁移

创建项目

# 第一步:安装djagno 2.2.2
pip3 install django==2.2.2
# 第二步:使用pycharm创建项目
# 第三步:配置文件
-58行:
    'DIRS': [os.path.join(BASE_DIR , 'templates')]

-国际化
    LANGUAGE_CODE = 'zh-hans'   # 语言中文
    TIME_ZONE = 'Asia/Shanghai' # 时区使用上海时区
    USE_I18N = True
    USE_L10N = True
    USE_TZ = False

-使用mysql
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'HOST': '127.0.0.1',
            'PORT': 3306,
            'NAME': 'bbs',  # 去创建数据库---》navicat创建
            'USER': 'root',
            'PASSWORD': '123',
        }
    }
-创建bbs数据库
    navicat创建

# 扩写auth的user表
AUTH_USER_MODEL = 'blog.UserInfo'

在models中写表模型

from django.db import models
from django.contrib.auth.models import AbstractUser

# 7 张表
# 继承AbstractUser,扩写字段:头像字段,手机号字段, 一对一的博客字段
class UserInfo(AbstractUser):
    # username,password,email。。。。
    phone = models.CharField(max_length=32, null=True)
    # 存文件的字段---》本质还是varchar,可以把文件自动保存(avatar文件夹下),存文件地址
    avatar = models.FileField(upload_to='avatar', default='avatar/default.png')
    blog = models.OneToOneField(to='Blog', on_delete=models.CASCADE, null=True)  # 一对一关联了blog表


class Blog(models.Model):
    title = models.CharField(max_length=32, null=True, verbose_name='主标题')
    site_name = models.CharField(max_length=32, null=True, verbose_name='副标题')
    site_style = models.CharField(max_length=32, null=True, verbose_name='个人站点样式')


class Tag(models.Model):
    name = models.CharField(max_length=32, verbose_name='标签名字')
    # on_delete可以有很多选项,目前先用级联删除(很危险)
    blog = models.ForeignKey(to='Blog', on_delete=models.CASCADE)


class Category(models.Model):
    name = models.CharField(max_length=32, verbose_name='分类名字')
    # on_delete可以有很多选项,目前先用级联删除(很危险)
    blog = models.ForeignKey(to='Blog', on_delete=models.CASCADE)


class Article(models.Model):
    title = models.CharField(max_length=32, verbose_name='文章名字')
    desc = models.CharField(max_length=255, verbose_name='文章摘要')
    content = models.TextField(verbose_name='文章内容')  # 大文本
    # auto_now_add   auto_now
    create_time = models.DateTimeField(auto_now_add=True,verbose_name='文章创建时间')         #auto_now_add=True 新增文章这个字段可以不传,自动把当前时间加上

    blog = models.ForeignKey(to='Blog', on_delete=models.CASCADE)
    category = models.ForeignKey(to='Category', on_delete=models.CASCADE)
    # 多对多关系,需要创建第三张表,
    #  django的orm的ManyToManyField字段可以自动创建第三张表(ArticleToTag)
    #  手动创建第三张表:ManyToManyField一定要加两个参数,through=中间表 , through_fields通过哪两个字段关联
    tag = models.ManyToManyField(to='Tag')


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(verbose_name="点赞或点踩")
    create_time = models.DateTimeField(auto_now_add=True)


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(max_length=64, verbose_name="评论的内容")
    create_time = models.DateTimeField(auto_now_add=True)
    # parent=models.ForeignKey(to='Comment', on_delete=models.CASCADE)
    # parent=models.IntegerField(null=True)
    parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True)

    # 自关联字段---》父评论--》子评论
    '''
    id    user    article    content             parent
    1       2        1       写的真好               空
    2       2        2       写的一般               空
    3       3        2       人家明明写的很好         2
    '''


# 安装pymysql
    pip3 install pymysql
# 在__ini__.py中加入:djagno默认操作mysql数据库使用的是 mysqlDB模块,在python2中没问题,但是在python3中已经不维护了,不支持,python3中操作mysql咱么用pymysql比较多,但是需要加下面两句话才能正常使用-----》猴子补丁(动态替换--把源码使用mysqlDB替换成pymysql的东西)
# 但是在django2.0.7及以后版本,需要改源码才能使用:operations.py---》146行,改成query = query.encode(errors='replace')
    import pymysql
    pymysql.install_as_MySQLdb()

# 以后直接使用 mysqlclient:有可能装不上---》看人品
    pip3 install mysqlclient

注册forms编写

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError
from models import  UserInfo  # 模块导入:相对导入和绝对导入

class RegisterForm(forms.Form):
    # 用户名,密码,确认密码,邮箱
    username = forms.CharField(max_length=8, min_length=3, required=True, error_messages={
        'max_length': '太长了',
        'min_length': '太短了',
        'required': '这个必填'
    }, widget=widgets.TextInput(attrs={'class': 'form-control'}))
    password = forms.CharField(max_length=8, min_length=3, required=True, error_messages={
        'max_length': '太长了',
        'min_length': '太短了',
        'required': '这个必填'
    }, widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    re_password = forms.CharField(max_length=8, min_length=3, required=True, error_messages={
        'max_length': '太长了',
        'min_length': '太短了',
        'required': '这个必填'
    }, widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    email = forms.EmailField(widget=widgets.EmailInput(attrs={'class': 'form-control'}))

    # 局部钩子校验
    # 方案一:
    def clean_username(self):
        username=self.cleaned_data.get('username')
        user=UserInfo.objects.filter(username=username).first()
        if user:
            # 已经存在,不合理
            raise ValidationError('该用户名已经存在')
        else:
            return username

    #方案二:
    # def clean_username(self):
    #     username=self.cleaned_data.get('username')
    #     try:
    #         UserInfo.objects.get(username=username) #有且只有一条才ok,否则就抛异常
    #         raise ValidationError('该用户名已经存在')
    #     except Exception:
    #         return username


    # 全局钩子校验
    def clean(self):
        # 比较两个密码是否一致  :cleaned_data存的是校验过后的数据,是个字典
        password = self.cleaned_data.get('password')
        re_password = self.cleaned_data.get('re_password')
        if password == re_password:
            # 合理,返回校验过后的数据
            return self.cleaned_data
        else:
            # 不合理,抛出校验失败的异常
            raise ValidationError('两次密码不一致!!')

注册页面搭建

# bootstrap 搭建
    -settings.py 中 加入static
        STATIC_URL = '/static/'
        STATICFILES_DIRS=[
            os.path.join(BASE_DIR,'static')
        ]
    -把bootstrap的静态资源copy到static文件夹下
# 新建模板文件 register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1 class="text-center">注册功能</h1>
            <form id="id_form">

                {% for item in form %}
                    <div class="form-group">
                        <label for="{{ item.id_for_label }}">{{ item.label }}</label>
                        {{ item }}
                        <span class="pull-right text-danger"></span>
                    </div>
                {% endfor %}

                <div class="form-group">
                    <label for="id_file">头像
                        <img src="/static/img/default.png" alt="" height="80px" width="80px" style="margin-left: 10px">
                    </label>
                    <input type="file" id="id_file" accept="image/*" style="display: none">
                </div>
                <div class="form-group text-center">
                    {#                    如果input类型是submit或者button标签,放在form表单中,如果点提交,触发form的提交,如果我们写了ajax提交,会触发两次提交#}
                    <input type="button" value="注册" class="btn btn-danger" id="id_submit">
                    <span class="text-danger"></span>
                </div>
            </form>
        </div>
    </div>
</div>

</body>
</html>

头像动态显示

# js 控制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1 class="text-center">注册功能</h1>
            <form id="id_form">

                {% for item in form %}
                    <div class="form-group">
                        <label for="{{ item.id_for_label }}">{{ item.label }}</label>
                        {{ item }}
                        <span class="pull-right text-danger"></span>
                    </div>
                {% endfor %}

                <div class="form-group">
                    <label for="id_file">头像
                        <img src="/static/img/default.png" alt="" height="80px" width="80px" style="margin-left: 10px" id="id_img">
                    </label>
                    <input type="file" id="id_file" accept="image/*" style="display: none">
                </div>
                <div class="form-group text-center">
                    {#                    如果input类型是submit或者button标签,放在form表单中,如果点提交,触发form的提交,如果我们写了ajax提交,会触发两次提交#}
                    <input type="button" value="注册" class="btn btn-danger" id="id_submit">
                    <span class="text-danger"></span>
                </div>
            </form>
        </div
    </div>
</div>
</body>
<script>
    $("#id_file").change(function () {
        // 把当前图片,放到img标签中
        // 把图片地址放到img标签上,标签就显示了图片
        //$("#id_img")[0].src='https://tva1.sinaimg.cn/large/00831rSTly1gd1u0jw182j30u00u043b.jpg'

        // 1 id_file这个标签的图片读出来,借助于文件阅读器,js提供的一个类
        var reader=new FileReader()
        // 2 拿到文件对象,赋值给一个变量
        var file =$("#id_file")[0].files[0]
        // 3 把文件读到文件阅读器中
        reader.readAsDataURL(file)
        // 4 等读完后,把文件阅读器的内容写到img标签上
        //$("#id_img")[0].src=reader.result
        reader.onload=function (){
            //$("#id_img")[0].src=reader.result
            $('#id_img').attr('src', reader.result)
        }
    })
</script>
</html>

注册功能后端

def register(request):
    if request.method == 'GET':
        register_form = RegisterForm()
        # context: 上下文
        return render(request, 'register.html', context={'form': register_form})
    else:  # post请求的时候
        res = {'code': 100, 'msg': '注册成功'}
        # 取出用户名密码,使用form校验数据,如果校验通过,存到数据库中,如果校验不通过,返回错误信息
        register_form = RegisterForm(data=request.POST)
        if register_form.is_valid():
            # 数据字段自己的规则,局部钩子,全局钩子都校验过后,通过了
            # 1  re_password字段,不存到数据库的,剔除
            register_data = register_form.cleaned_data
            register_data.pop('re_password')
            # 2 头像:如果携带了要存,头像是文件,在request.FILES中
            my_file = request.FILES.get('my_file')
            if my_file:
                register_data['avatar'] = my_file
            # 2 存到数据库
            UserInfo.objects.create_user(**register_data)
            # UserInfo.objects.create_user(username=register_data.get('username'),) 等同于上面,但是麻烦
            return JsonResponse(res)
        else:
            res['code'] = 101
            res['msg'] = '注册失败'
            res['errors'] = register_form.errors
            return JsonResponse(res)

注册功能前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1 class="text-center">注册功能</h1>
            <form id="id_form" >
                {% csrf_token %}

                {% for item in form %}
                    <div class="form-group">
                        <label for="{{ item.id_for_label }}">{{ item.label }}</label>
                        {{ item }}
                        <span class="pull-right text-danger"></span>
                    </div>
                {% endfor %}

                <div class="form-group">
                    <label for="id_file">头像
                        <img src="/static/img/default.png" alt="" height="80px" width="80px" style="margin-left: 10px"
                             id="id_img">
                    </label>
                    <input type="file" id="id_file" accept="image/*" style="display: none">
                </div>
                <div class="form-group text-center">
                    {#                    如果input类型是submit或者button标签,放在form表单中,如果点提交,触发form的提交,如果我们写了ajax提交,会触发两次提交#}
                    <input type="button" value="注册" class="btn btn-danger" id="id_submit">
                    <span class="text-danger"></span>
                </div>
            </form>
        </div>
    </div>
</div>
</body>

<script>
    $("#id_file").change(function () {
        // 把当前图片,放到img标签中
        // 把图片地址放到img标签上,标签就显示了图片
        //$("#id_img")[0].src='https://tva1.sinaimg.cn/large/00831rSTly1gd1u0jw182j30u00u043b.jpg'

        // 1 id_file这个标签的图片读出来,借助于文件阅读器,js提供的一个类
        var reader = new FileReader()
        // 2 拿到文件对象,赋值给一个变量
        var file = $("#id_file")[0].files[0]
        // 3 把文件读到文件阅读器中
        reader.readAsDataURL(file)
        // 4 等读完后,把文件阅读器的内容写到img标签上
        //$("#id_img")[0].src=reader.result
        reader.onload = function () {
            //$("#id_img")[0].src=reader.result
            $('#id_img').attr('src', reader.result)
        }
    })

    // 当点击注册按钮,发送ajax请求到后端的注册功能
    $("#id_submit").click(function () {
        // 上传文件,借助于formdata对象
        var formdata = new FormData()
        // 方式一
    /*formdata.append('username',$('#id_username').val())
        formdata.append('password',$('#id_username').val())
        formdata.append('re_password',$('#id_username').val())
        formdata.append('my_file',$('#id_file')[0].files[0])
        // csrftoken也要加上*/
        // 方式二:借助于form表单批量处理
        var data = $("#id_form").serializeArray()
        console.log(data)
        // 使用for循环,把data中得数据,转存到formdata中  jquery的each循环
        $.each(data, function (i, v) {
            //console.log("索引是:",i)
            //console.log("值是:",v)
            //console.log("------")
            formdata.append(v.name, v.value)
        })
        // 文件单独再放进去
        formdata.append('my_file', $('#id_file')[0].files[0])

        // 使用ajax向后端发送请求
        // 1 三种编码格式:urlencoded,form-data,json
        // {name:lqz,age:19}--->name=lqz&age=19
        $.ajax({
            url: '/register/',
            type: 'post',
            processData: false,
            contentType: false,
            data: formdata,
            success: function (data) {
                console.log(data)
                if(data.code==100){
                    // 表示注册成功,跳转到登录页面
                    location.href='/login/'
                }else {
                    // 在前端显示错误信息
                }
            }
        })
    })
</script>
</html>

头像上传路径问题

# 咱们写项目后台静态资源中得图片,一般放在static文件夹下
# 用户上传的文件,图片等,一般放在media文件夹下
    -我们想做的是,avatar文件夹要在media文件夹下

# django中只需要在配置文件中加一句话
# 以后再上传的文件路径是从media文件夹下开始
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

注册错误信息渲染

$("#id_submit").click(function () {
        // 上传文件,借助于formdata对象
        var formdata = new FormData()
        // 方式一
        /*
        formdata.append('username',$('#id_username').val())
        formdata.append('password',$('#id_username').val())
        formdata.append('re_password',$('#id_username').val())
        formdata.append('my_file',$('#id_file')[0].files[0])
        // csrftoken也要加上
        */
        // 方式二:借助于form表单批量处理
        var data = $("#id_form").serializeArray()
        console.log(data)
        // 使用for循环,把data中得数据,转存到formdata中  jquery的each循环
        $.each(data, function (i, v) {
            //console.log("索引是:",i)
            //console.log("值是:",v)
            //console.log("------")
            formdata.append(v.name, v.value)
        })
        // 文件单独再放进去
        formdata.append('my_file', $('#id_file')[0].files[0])

        // 使用ajax向后端发送请求
        // 1 三种编码格式:urlencoded,form-data,json
        // {name:lqz,age:19}--->name=lqz&age=19
        $.ajax({
            url: '/register/',
            type: 'post',
            processData: false,
            contentType: false,
            data: formdata,
            success: function (data) {
                console.log(data)
                if(data.code==100){
                    // 表示注册成功,跳转到登录页面
                    location.href='/login/'
                }else {
                    // 在前端显示错误信息
                    console.log(data)
                    // 两次密码不一致的错误渲染
                    /*
                    if(data.errors['__all__']){
                        $(".error").html(data.errors['__all__'][0])
                    }*/
                    // 其他标签的错误渲染
                    $.each(data.errors,function (k,v){
                        if (k=='__all__'){ //两次密码不一致的错误渲染
                            $(".error").html(v[0])
                        }else {
                            // 链式调用,在对应的input后的span中插入错误文字,把父div加入has-error类,整个框变红
                            $("#id_"+k).next().html(v[0]).parent().addClass('has-error')
                        }
                    })
                    // 起一个定时任务
                    setTimeout(function (){
                        // 把所有span的文字去掉,把父div中得has-error类去掉
                        $('.text-danger').html("").parent().removeClass('has-error')
                    },3000)
                }
            }
        })
    })

用户存在校验功能

后端校验接口

# 前端通过get把用户名传入,我们根据用户名查询数据库,如果用户名存在,返回存在,如果不存在,返回不存在
/check_username/?username=lqz

def check_username(request):
    # /check_username/?username=lqz
    res = {'code': 100, 'msg': '用户存在'}
    username = request.GET.get('username')
    user = UserInfo.objects.filter(username=username).first()
    if user:
        # 用户存在
        return JsonResponse(res)
    else:
        # 用户不存在
        res['code'] = 101
        res['msg'] = '用户不存在'
        return JsonResponse(res)

前端

// username输入框,失去焦点,触发ajax执行
$('#id_username').blur(function () {

    $.ajax({
        url: '/check_username/?username=' + $(this).val(),
        type: 'get',
        success: function (data) {
            if (data.code == 100) {
                // 在span中插入错误信息
                {#alert(data.msg)#}
                $('#id_username').next().html(data.msg)
            }
        }
    })
})

登录页面搭建

1 创建login.html
2 登录后台,get返回该页面


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1 class="text-center">登录功能</h1>
            <form id="id_form">
                {% csrf_token %}
                <div class="form-group">
                    <label for="id_username">用户名</label>
                    <input type="text" id="id_username" name="username" class="form-control">
                </div>
                <div class="form-group">
                    <label for="id_password">密码</label>
                    <input type="password" id="id_password" name="password" class="form-control">
                </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="/static/img/default.png" alt="" width="500px" height="40px">
                            </div>
                    </div>
                </div>

                <div class="form-group text-center">
                    <input type="button" value="登录" class="btn btn-danger" id="id_submit">
                    <span class="text-danger error"></span>
                </div>
            </form>
        </div>
    </div>
</div>
</body>
</html>

自定义图片验证码

# 验证码:数字,大写字母,小写字母的组合 5位
# 第三方的模块,可以直接生成
# 自己写
    # 方式一,直接返回一张图片
    # 方式二:自己生成一张图片,返回
    # 方式三:把生成的图片写到内存中
    # 方法四:在图片上写文字
    # 方式五:图片上写文字,字体是指定的字体,字的颜色随机
    # 方式6 最终方案

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


def get_code(request):
    # 方式一,直接返回一张图片
    # with open('./media/avatar/2.jpeg', 'rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 方式二:自己生成一张图片,返回  --->借助模块,pillow
    # # rgb:三原色 red green blue
    # # 宽度和高度
    # # 三原色的色值  0-255
    # #1 创建图片
    # img=Image.new('RGB',(500,40),(0,255,255))
    # #2 保存到本地
    # with open('code.png','wb') as f:
    #     img.save(f)
    # # 3 打开图片,返回给前端
    # with open('./code.png', 'rb') as f:
    #     data = f.read()
    # return HttpResponse(data)

    # 方式三:把生成的图片写到内存中
    # # 1 创建图片
    # img = Image.new('RGB', (500, 40), (0, 0, 0))
    # # 2 保存到内存
    # byte_io = BytesIO()
    # img.save(byte_io,'png') # 指定图片格式
    # # 3 从BytesIO取出二进制,返回给前端
    # return HttpResponse(byte_io.getvalue())

    # 方法四:在图片上写文字
    # # 1 创建图片
    # img = Image.new('RGB', (500, 40), (0, 255, 0))
    # # 2 在图片上写文字 ,相当于画板
    # img_draw=ImageDraw.Draw(img)
    # img_draw.text((0,0),"lqz")
    # # 2 保存到内存
    # byte_io = BytesIO()
    # img.save(byte_io,'png') # 指定图片格式
    # # 3 从BytesIO取出二进制,返回给前端
    # return HttpResponse(byte_io.getvalue())

    # 方式五:图片上写文字,字体是指定的字体,字的颜色随机
    # img = Image.new('RGB', (500, 40), get_rgb())
    #
    # # 2 创建一个字体对象
    # font=ImageFont.truetype('./static/font/xgdl.ttf',30)
    # # 2 在图片上写文字 ,相当于画板
    # img_draw = ImageDraw.Draw(img)
    # img_draw.text((0, 0), "lqz",font=font)
    # # 2 保存到内存
    # byte_io = BytesIO()
    # img.save(byte_io, 'png')  # 指定图片格式
    # # 3 从BytesIO取出二进制,返回给前端
    # return HttpResponse(byte_io.getvalue())

    # 第六:终极方案自己造 验证码
    # img = Image.new('RGB', (500, 40), get_rgb())
    img = Image.new('RGB', (500, 40), (255,255,255))

    # 2 创建一个字体对象
    font = ImageFont.truetype('./static/font/ss.TTF', 30)
    # 3 在图片上写文字 ,相当于画板
    img_draw = ImageDraw.Draw(img)
    # 4 在图片上写文字(数字,大写字母,小写字母组合  5个)
    code_str = ''
    for i in range(5):
        num = random.randint(0, 9)
        upper = chr(random.randint(65, 90))
        low = chr(random.randint(97, 122))
        ran = str(random.choice([num, upper, low]))
        code_str += ran # python 是强类型语言,不同类型直接不能直接相加,需要类型转换
        img_draw.text((60+i*60, 0), ran, fill=get_rgb(),font=font)
    request.session['code'] = code_str
    #5 在图片上画点画线
    width = 450
    height = 30
    for i in range(10):
        x1 = random.randint(0, width)
        x2 = random.randint(0, width)
        y1 = random.randint(0, height)
        y2 = random.randint(0, height)
        # 在图片上画线
        img_draw.line((x1, y1, x2, y2), fill=get_rgb())

    for i in range(50):
        # 画点
        img_draw.point([random.randint(0, width), random.randint(0, height)], fill=get_rgb())
        x = random.randint(0, width)
        y = random.randint(0, height)
        # 画弧形
        # img_draw.arc((x, y, x + 4, y + 4), 0, 90, fill=get_rgb())
    # 2 保存到内存
    byte_io = BytesIO()
    img.save(byte_io, 'png')  # 指定图片格式
    # 3 从BytesIO取出二进制,返回给前端
    return HttpResponse(byte_io.getvalue())

点击图片验证码更新

$('#id_img').click(function (){
    // 把图片的src地址变成原来地址?
    //alert($(this)[0].src)
    let timestamp=new Date().getTime()
    $(this)[0].src='/get_code/?t='+timestamp
    })

登录功能后端

def login(request):
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        # 前端传入的用户名,密码,验证码,  校验验证码   去数据库比较用户名和密码
        res = {'code': 100, 'msg': '成功'}
        # 1 拿出用户名,密码,验证码,校验验证码
        print(request.POST)
        code = request.POST.get('code')
        username = request.POST.get('username')
        password = request.POST.get('password')
        old_code = request.session.get('code')
        if code.lower() == old_code.lower():  # 不区分大小写
            # 2 校验用户名或密码
            # user=UserInfo.objects.filter(username=username,password=password) # 这个不行
            user = authenticate(username=username, password=password)
            if user:
                # 3 登录成功,返回json格式字符串
                return JsonResponse(res)
            else:
                res['code'] = 101
                res['msg'] = '用户名或密码错误'
                return JsonResponse(res)
        else:
            res['code'] = 102
            res['msg'] = '验证码错误'
            return JsonResponse(res)

登录功能前端

    $("#id_submit").click(function () {
        // 取出填入的数据,发送ajax的post请求
        //$('#id_form').serializeArray()
        $.ajax({
            url:'/login/',
            type:'post',
            data:{
                'username':$('#id_username').val(),
                'password':$('#id_password').val(),
                'code':$('#id_code').val(),
                'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val(),  //根据属性取标签
            },
            success:function (data){
                console.log(data)
                if(data.code==100){
                    location.href='/'
                }else {
                    $('.error').html(data.msg)
                }
            }
        })
    })

首页导航和首页轮播图

新建index.html---》写一个路由---》get请求返回index.html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
    <script src="/static/bootstrap/js/bootstrap.min.js"></script>
    <title>首页</title>
</head>

<body>
<div class="mynavbar">
    <nav class="navbar navbar-default">
        <div class="container-fluid">
            <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="#">博客园</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>
                </ul>
                <form class="navbar-form navbar-left">
                    <div class="form-group">
                        <input type="text" class="form-control" placeholder="搜索文章">
                    </div>
                    <button type="submit" class="btn btn-default"></button>
                </form>
                <ul class="nav navbar-nav navbar-right">
                    <li><a href="#">用户名</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>
                </ul>
            </div><!-- /.navbar-collapse -->
        </div><!-- /.container-fluid -->
    </nav>
</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">
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                    <hr>
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                    <hr>
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                </div>
            </div>
            <div class="panel panel-warning">
                <div class="panel-heading">
                    <h3 class="panel-title">广告招商</h3>
                </div>
                <div class="panel-body">
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                    <hr>
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                    <hr>
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                </div>
            </div>
        </div>
        <div class="col-md-7">
            <div class="top">
                <div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
                    <ol class="carousel-indicators">
                        <li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li>
                        <li data-target="#carousel-example-generic" data-slide-to="1"></li>
                    </ol>

                    <div class="carousel-inner" role="listbox">
                        <div class="item active">
                            <img src="/static/img/banner1.jpg" alt="...">
                            <div class="carousel-caption">
                                博客开博了
                            </div>
                        </div>
                        <div class="item">
                            <img src="/static/img/banner2.jpg" alt="...">
                            <div class="carousel-caption">
                                欢迎大家投稿
                            </div>
                        </div>
                    </div>

                    <!-- Controls -->
                    <a class="left carousel-control" href="#carousel-example-generic" role="button" data-slide="prev">
                        <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
                        <span class="sr-only">Previous</span>
                    </a>
                    <a class="right carousel-control" href="#carousel-example-generic" role="button" data-slide="next">
                        <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
                        <span class="sr-only">Next</span>
                    </a>
                </div>
            </div>

            <div class="article">
                文章详情
            </div>

        </div>
        <div class="col-md-3">
            <div class="panel panel-primary">
                <div class="panel-heading">
                    <h3 class="panel-title">广告招商</h3>
                </div>
                <div class="panel-body">
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                    <hr>
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                    <hr>
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                </div>
            </div>
            <div class="panel panel-warning">
                <div class="panel-heading">
                    <h3 class="panel-title">广告招商</h3>
                </div>
                <div class="panel-body">
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                    <hr>
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                    <hr>
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                </div>
            </div>
            <div class="panel panel-danger">
                <div class="panel-heading">
                    <h3 class="panel-title">广告招商</h3>
                </div>
                <div class="panel-body">
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                    <hr>
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                    <hr>
                    <div>
                        重金求子 <a href="">点我</a>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

</body>
</html>

首页文章列表页面

# admin 后台录入数据  文章表录入数据

# admin 是djagno的一个app,它是一个后台管理,内置的,可以方便我们做二次开发和录入数据
-在app的admin.py 把表注册一下 ,登录到后台就可以看到了
-直接录入数据,跟在数据表中录入数据本质一样的

开启media访问

# 上传的图片,目前访问不到,要开启media的访问,才能看到图片
# 开启media访问,本质就是把项目中某个目录可以让客户端(浏览器)直接访问,很危险,咱们只能开启media,以后meida下不能放重要的东西
# 本身static 文件夹从浏览器可以访问,默认这个目录已经开放了

# 如何开启
-1 在配置文件中
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
-2 在路由中
    path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT}),

图片防盗链

# 有的网站有上传图片功能,我们可以把图片传上去,然后在自己的网站中使用,这样不会耗费我们自己网站的带宽,比如cnblogs,cnblogs就屏蔽了这种行为,这就是图片防盗链

# 本质原理是:
因为发送http请求,请求头中有个参数叫referer,是一个url地址,它表示上一次访问的地址
图片防盗链可以通过这个来控制,判断一下referer是不是我们自己的地址,如果不是就不返回
通过请求头中得referer来控制不被非自己的地址请求

个人站点页面搭建

路由

# 一定要放在最下边
path('<str:name>/', views.site )

视图函数

def site(request, name):
    # 根据人名查到数据库中,才返回个人站点,如果没有,返回404页面
    user = UserInfo.objects.filter(username=name).first()
    if user:
        # 返回当前这个人所有文章
        article_list = Article.objects.all().filter(blog=user.blog)
        # article_list = user.blog.article_set.all()
        return render(request, 'site.html', locals())
    else:
        return render(request, '404.html')

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block title %}

        {% endblock %}
    </title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
    <script src="/static/bootstrap/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="/static/font-awesome-4.7.0/css/font-awesome.min.css">
    <link rel="stylesheet" href="/static/css/common.css">
    {% block link %}

    {% endblock %}
</head>
<body>
<div class="main">
    <div class="header">
        {% block header %}

        {% endblock %}
    </div>
    <div class="container-fluid">
        <div class="row">
            <div class="col-md-2">
                <div class="panel panel-success">
                    <div class="panel-heading">
                        <h3 class="panel-title">我的标签</h3>
                    </div>
                    <div class="panel-body">
                        <div>
                            {% for tag in tag_res %}
                                <hr>
                                <a href="">
                                    <span>{{ tag.1 }}</span>
                                    <span>({{ tag.2}})</span>
                                </a>
                            {% endfor %}
                        </div>

                    </div>
                </div>
                <div class="panel panel-danger">
                    <div class="panel-heading">
                        <h3 class="panel-title">随笔分类</h3>
                    </div>
                    <div class="panel-body">
                            <div>
                            {% for category in category_res %}
                                <hr>
                                <a href="">
                                    <span>{{ category.1 }}</span>
                                    <span>({{ category.2}})</span>
                                </a>
                            {% endfor %}
                        </div>
                    </div>
                </div>
                <div class="panel panel-warning">
                    <div class="panel-heading">
                        <h3 class="panel-title">随笔档案</h3>
                    </div>
                    <div class="panel-body">
                        <div>
                            重金求子 <a href="">点我</a>
                        </div>
                        <hr>
                        <div>
                            重金求子 <a href="">点我</a>
                        </div>
                        <hr>
                        <div>
                            重金求子 <a href="">点我</a>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-md-10">
                {% block article %}

                {% endblock %}
            </div>
        </div>
    </div>
</div>

</body>
{% block js %}

{% endblock %}
</html>

site.html

{% extends 'base.html' %}

{% block title %}
    {{ user.blog.title }}
{% endblock %}

{% block link %}
    <link rel="stylesheet" href="/static/css/{{ user.blog.site_style }}">
{% endblock %}

{% block header %}
<nav class="navbar navbar-default common">
            <div class="container-fluid">
                <div class="pull-right">
                    <button type="button" class="btn btn-default navbar-btn">管理</button>
                </div>
                <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="#">{{ user.blog.title }}</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><a href="/">首页 <span class="sr-only">(current)</span></a></li>
                        <li><a href="/">新闻 <span class="sr-only">(current)</span></a></li>
                    </ul>
                </div><!-- /.navbar-collapse -->
            </div><!-- /.container-fluid -->
        </nav>
{% endblock %}

{% block article %}
    <div class="article">
        {% for article in article_list %}

            <div class="media">
                <h4 class="media-heading"><a href="">{{ article.title }}</a></h4>
                <hr>
                <div class="media-body">
                    {{ article.desc }}
                </div>
                <div class="article-bottom pull-right" style="margin-top: 20px">
                    <span>posted @</span>
                    <span style="padding: 5px;font-size: 15px">{{ article.blog.userinfo.username }}</span>
                    <span style="padding: 5px;font-size: 15px">{{ article.create_time|date:"Y-m-d H:s" }}</span>
                    <span style="padding: 5px;font-size: 15px"><i class="fa fa-lock" aria-hidden="true"
                                                                  style="margin-right: 5px"></i>{{ article.up_num }}</span>
                    <span style="padding: 5px;font-size: 15px"><i class="fa fa-hand-scissors-o" aria-hidden="true"
                                                                  style="margin-right: 5px"></i>{{ article.down_num }}</span>
                    <span style="padding: 5px;font-size: 15px"><i class="fa fa-hand-rock-o" aria-hidden="true"
                                                                  style="margin-right: 5px"></i>{{ article.comment_num }}</span>
                    <span><a href="">编辑</a></span>

                </div>
            </div>
        {% endfor %}

    </div>
{% endblock %}

{% block js %}

{% endblock %}

左侧标签

# 分组查询---》mysql 的查询
-分类的id和名字
    select id,name from category;
-查询当前博客下所有的分类和名字
    select id,name from category where blog=user.blog
-查询当前博客下所有的分类和名字和分类下的文章数   ----》分组
-一个分类是一组,所有一分类id作为 group by category.id
    select id,name,count(article.id),article.title from category,article where category.id=article.category and article.blog=user.blog   group by category.id

    # 分类表
    id  name
    1   lqz分类1
    2   lqz分类2
    3   lqz分类3
    4   张三分类1

    # 文章表
    id  title    category      blog
    1    go          1           1
    2    python      1           1
    3    java        2           1
    4    js          4           2

    # 连起来
    id     name          id    title    category
    1     lqz分类1        1     go          1
    1     lqz分类1        2    python       1

    2      lqz分类2       3    java         2
    # 1    lqz分类1     2
    # 2    lqz分类2     1
    # 3    lqz分类3     0

# 原生sql转成orm
    filter 在annotate前,表示where 条件
    valeus 在annotate前,表示分组依据(group by 谁里面就写谁)
    filter 在annotate后,表示having 条件
    valeus 在annotate后,表示取字段  (id ,name ,c)	       Category.objects.all().filter(blog=user.blog).values('id').annotate(c=Count('article__id')).values_list('id', 'name', 'c')


# 随笔档案的查询数据结构
-分组,单表  文章表---发布时间(年月日十分秒)
-文章表的数据,在单表中增加一个year_month字段,以它作为分组依据
    id    title          create_time                     year_month
    1      go         2022918102314202293      java       2022914102314202292      python     2022710102314202274      js         202281210231420228'''
    Sales.objects.all().
    .annotate(year_month=TruncMonth('create_time'))  # Truncate to month and add to select list
    .values('year_month')  # Group By year_month
    .annotate(c=Count('id'))  # Select the count of the grouping
    .values('year_month', 'c')  # (might be redundant, haven't tested) select month and count
    '''

视图函数

def site(request, name):
    # 根据人名查到数据库中,才返回个人站点,如果没有,返回404页面
    user = UserInfo.objects.filter(username=name).first()
    if user:
        # 返回当前这个人所有文章
        article_list = Article.objects.all().filter(blog=user.blog)
        # article_list = user.blog.article_set.all()
        category_res = Category.objects.all().filter(blog=user.blog).values('id').annotate(
            c=Count('article__id')).values_list('id', 'name', 'c')
        tag_res = Tag.objects.all().filter(blog=user.blog).values('id').annotate(c=Count('article__id')).values_list(
            'id', 'name', 'c')
        date_res = Article.objects.all().filter(blog=user.blog).annotate(year_month=TruncMonth('create_time')).values(
            'year_month').annotate(c=Count('id')).values_list('year_month', 'c')
        print(tag_res)
        print(category_res)
        print(date_res)
        return render(request, 'site.html', locals())
    else:
        return render(request, '404.html')

前端页面

base.html,代码28行开始

<div class="col-md-2">
                <div class="panel panel-success">
                    <div class="panel-heading">
                        <h3 class="panel-title">我的标签</h3>
                    </div>
                    <div class="panel-body">
                        <div>
                            {% for tag in tag_res %}
                                <hr>
                                <a href="/{{ user.username }}/tag/{{ tag.0 }}.html">
                                    <span>{{ tag.1 }}</span>
                                    <span>({{ tag.2 }})</span>
                                </a>
                            {% endfor %}
                        </div>

                    </div>
                </div>
                <div class="panel panel-danger">
                    <div class="panel-heading">
                        <h3 class="panel-title">随笔分类</h3>
                    </div>
                    <div class="panel-body">
                        <div>
                            {% for category in category_res %}
                                <hr>
                                <a href="/{{ user.username }}/category/{{ category.0 }}.html">
                                    <span>{{ category.1 }}</span>
                                    <span>({{ category.2 }})</span>
                                </a>
                            {% endfor %}
                        </div>
                    </div>
                </div>
                <div class="panel panel-warning">
                    <div class="panel-heading">
                        <h3 class="panel-title">随笔档案</h3>
                    </div>
                    <div class="panel-body">
                        <div>
                            {% for data_itme in date_res %}
                                <hr>
                                <a href="/{{ user.username }}/archive/{{ data_itme.0|date:'Ym' }}.html">
                                    <span>{{ data_itme.0|date:'Y年m月' }}</span>
                                    <span>({{ data_itme.1 }})</span>
                                </a>
                            {% endfor %}
                        </div>
                    </div>
                </div>
            </div>

随笔档案,标签,分类点击过滤

路由

# 三个路由,标签的过滤,分类的过滤,随笔档案过滤
# lqz/tag/1.html
path('<str:name>/tag/<int:tag>.html', views.site),
#lqz/category/1.html
path('<str:name>/category/<int:category>.html', views.site),
# lqz/archive/2022/09.html
path('<str:name>/archive/<int:year>/<int:month>.html', views.site),

视图函数

# 带过滤的中级版本

def site(request, name, **kwargs):
    # 根据人名查到数据库中,才返回个人站点,如果没有,返回404页面
    user = UserInfo.objects.filter(username=name).first()
    if user:
        # 返回当前这个人所有文章
        article_list = Article.objects.all().filter(blog=user.blog)
        # 要么根据tag,category或时间过滤
        tag = kwargs.get('tag')
        category = kwargs.get('category')
        year = kwargs.get('year')
        month = kwargs.get('month')
        if tag:
            article_list = article_list.filter(tag__id=tag)  # 跨表到tag表,过滤tag的id为传入的id号

        elif category:
            article_list = article_list.filter(category_id=category)  # 跨表到tag表,过滤tag的id为传入的id号
        elif year and month:
            article_list = article_list.filter(create_time__year=year, create_time__month=month)

        category_res = Category.objects.all().filter(blog=user.blog).values('id').annotate(
            c=Count('article__id')).values_list('id', 'name', 'c')
        tag_res = Tag.objects.all().filter(blog=user.blog).values('id').annotate(c=Count('article__id')).values_list(
            'id', 'name', 'c')
        date_res = Article.objects.all().filter(blog=user.blog).annotate(year_month=TruncMonth('create_time')).values(
            'year_month').annotate(c=Count('id')).values_list('year_month', 'c')
        print(tag_res)
        print(category_res)
        print(date_res)
        return render(request, 'site.html', locals())
    else:
        return render(request, '404.html')

左侧标签,分类,归档终极方案(3条路由合一)

路由

# 上面的三个路由改成一个路由
re_path('^(?P<name>\w+)/(?P<type_name>tag|category|archive)/(?P<condition>\d+).html', views.site),

视图函数

# 三条路由合并成一条的版本
def site(request, name, **kwargs):
    # 根据人名查到数据库中,才返回个人站点,如果没有,返回404页面
    user = UserInfo.objects.filter(username=name).first()
    if user:
        # 返回当前这个人所有文章
        article_list = Article.objects.all().filter(blog=user.blog)
        # 要么根据tag,category或时间过滤
        type_name = kwargs.get('type_name')
        condition = kwargs.get('condition')
        if type_name == 'tag':
            article_list = article_list.filter(tag__id=condition)  # 跨表到tag表,过滤tag的id为传入的id号
        elif type_name == 'category':
            article_list = article_list.filter(category_id=condition)  # 跨表到tag表,过滤tag的id为传入的id号
        elif type_name == 'archive':
            year = str(condition)[:4]
            month = str(condition)[4:]
            article_list = article_list.filter(create_time__year=year, create_time__month=month)

        category_res = Category.objects.all().filter(blog=user.blog).values('id').annotate(
            c=Count('article__id')).values_list('id', 'name', 'c')
        tag_res = Tag.objects.all().filter(blog=user.blog).values('id').annotate(c=Count('article__id')).values_list(
            'id', 'name', 'c')
        date_res = Article.objects.all().filter(blog=user.blog).annotate(year_month=TruncMonth('create_time')).values(
            'year_month').annotate(c=Count('id')).values_list('year_month', 'c')
        print(tag_res)
        print(category_res)
        print(date_res)
        return render(request, 'site.html', locals())
    else:
        return render(request, '404.html')

模板语言和js语法比较

# 模板语言---》模板---》不是前端页面---》区分开
    -django中:模板语法,dtl:django template language(django自己设计,写的)
    -flask中:模板语法:jinja2,第三方
    -java: jsp  理解为:java web 的模板语言
    -php:  php  <? php语言 >

    -xx.html 中写了python代码,这个不是前端页面,叫模板

# js语法
<script>
    // 可以在js代码用模板渲染,但是只能用数字,字符串(加引号),列表  不能用元组,字典,对象   test
    // 后端模板中得name给js的name
    var name ='{{name}}'
    // 后端模板中得age给js的age
    var age = {{ age }}
    var l= {{ test }}
    //var d={'name': 'lyf', 'age': 40}

    console.log(age)
    console.log(name)
    console.log(l)
    //console.log(d)

    $('h2').html(age)

    // 把js的变量l,给python的视图函数中得l
</script>

左侧使用inclusion_tag方案实现

# inclusion_tag 是tag的一种
 {{  }}
 {% %}

# 自定义标签
-第一步:在app中创建包:templatetags
-第二步:在包下新建py文件:如:my_tags.py
-第三步:在py文件中导入
    from django import template
-第四步:实例化得到对象
    register = template.Library()
-第五步:使用register装饰函数
@register.inclusion_tag('left.html', name='left') 
    def left(name):
        return {}  # 字典的数据可以在left.html中使用

-第六步:在想引入inclusion_tag使用---》base.html 的栅格左侧占2个栅格的位置
    {% load my_tags %}
    {% left  name%}

my_tag.py

from django import template
from blog.models import Category, Tag, Article, UserInfo
from django.db.models import Count
from django.db.models.functions import TruncMonth

register = template.Library()


@register.inclusion_tag('left.html', name='left')  # 返回html片段,第一个参数是html文件
def left(name):
    # user 当前根据用户名查到的用户,需要传入用户名,一定会有user
    user = UserInfo.objects.filter(username=name).first()

    # 标签id,名字和标签下的文章数
    category_res = Category.objects.all().filter(blog=user.blog).values('id').annotate(
        c=Count('article__id')).values_list('id', 'name', 'c')
    tag_res = Tag.objects.all().filter(blog=user.blog).values('id').annotate(c=Count('article__id')).values_list(
        'id', 'name', 'c')
    date_res = Article.objects.all().filter(blog=user.blog).annotate(year_month=TruncMonth('create_time')).values(
        'year_month').annotate(c=Count('id')).values_list('year_month', 'c')
    print(tag_res)
    print(category_res)
    print(date_res)

    return {'tag_res': tag_res, 'category_res': category_res, 'date_res': date_res}  # 返回的字典,可以在left.html中使用

left.html

<div>

    <div class="panel panel-success">
        <div class="panel-heading">
            <h3 class="panel-title">我的标签</h3>
        </div>
        <div class="panel-body">
            <div>
                {% for tag in tag_res %}
                    <hr>
                    <a href="/{{ user.username }}/tag/{{ tag.0 }}.html">
                        <span>{{ tag.1 }}</span>
                        <span>({{ tag.2 }})</span>
                    </a>
                {% endfor %}
            </div>

        </div>
    </div>
    <div class="panel panel-danger">
        <div class="panel-heading">
            <h3 class="panel-title">随笔分类</h3>
        </div>
        <div class="panel-body">
            <div>
                {% for category in category_res %}
                    <hr>
                    <a href="/{{ user.username }}/category/{{ category.0 }}.html">
                        <span>{{ category.1 }}</span>
                        <span>({{ category.2 }})</span>
                    </a>
                {% endfor %}
            </div>
        </div>
    </div>
    <div class="panel panel-warning">
        <div class="panel-heading">
            <h3 class="panel-title">随笔档案</h3>
        </div>
        <div class="panel-body">
            <div>
                {% for data_itme in date_res %}
                    <hr>
                    <a href="/{{ user.username }}/archive/{{ data_itme.0|date:'Ym' }}.html">
                        <span>{{ data_itme.0|date:'Y年m月' }}</span>
                        <span>({{ data_itme.1 }})</span>
                    </a>
                {% endfor %}
            </div>
        </div>
    </div>

</div>

点赞点踩样式和前后端

# 点赞或点踩流程
-1 用户点击赞标签
-2 发送ajax请求到后端
-3 后端判断一下是是否点过,如果点过了,就返回点过了
-4 如果没有点过,点赞表插入一条记录
-5 在文章表的点赞数+1
-6 如果没有登录,返回让它登录

点赞点踩前端样式

<div id="div_digg">
    <div class="diggit up">
        <span class="diggnum" id="digg_count">{{ article_detail.up_num }}</span>
    </div>
    <div class="buryit up">
        <span class="burynum" id="bury_count">{{ article_detail.down_num }}</span>
    </div>
    <div class="clear"></div>
    <div class="diggword" id="digg_tips"></div>
</div>

article.css

#div_digg {
    float: right;
    margin-bottom: 10px;
    margin-right: 30px;
    font-size: 12px;
    width: 128px;
    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: red;
}

点赞点踩前端js代码

// 点赞点踩写成1个事件
$(".up").click(function () {
            var up = $(this).hasClass('diggit')  // 判断点击的这个标签有没有diggit类,如果有就是true,up是true表示点赞,否则表示点踩
            // 先取出点击的span
            var span = $(this).children('span')
            $.ajax({
                url: '/up_and_down/',
                type: 'post',
                data: {
                    // 谁给那篇文章点赞或点踩---》谁可以不传,当前登录用户就是点赞人
                    //article_id:$('h2').attr('name') // 方式1:把文章id,放在某个标签中,使用jq取到id
                    article_id: '{{ article_detail.id }}',
                    up_or_down: up,
                    csrfmiddlewaretoken: '{{ csrf_token }}'
                },
                success: function (data) {
                    $('#digg_tips').html(data.msg)
                    if (data.code == 100) {
                        // 在点击的div内的span的数字要+1
                        span.html(Number(span.html()) + 1)
                }
            }
        })
    })

点赞后端


import json
from django.db.models import F
from django.db import transaction
def up_and_down(request):
    res = {'code': 100, 'msg': '点赞成功'}
    # 1 判断用户是否登录
    if request.user.is_authenticated:  # 只要登录了就是当前登录用户,如果没登录就是匿名用户
        # 2 取出文件id,用户id,点赞或点踩
        article_id = request.POST.get('article_id')
        up = json.loads(request.POST.get('up_or_down'))  # 坑:up是什么类型?<class 'str'>
        # 把str类型的up:'true'--->转成---》python中布尔类型:up:True

        # 3 根据是点赞还是点踩向点赞点踩表中存数据
        count = UpAndDown.objects.filter(user=request.user, article_id=article_id).count()
        if count:
            res['code'] = 101
            res['msg'] = '您已经点过了'
            return JsonResponse(res)
        else:
            with transaction.atomic(): #开启事务,保证,插入点赞点踩表和文章表增加1 ,要么都成功,要么都失败
                UpAndDown.objects.create(user=request.user, article_id=article_id, is_up=up)
                # 4 在aritlcle表中点赞或点踩数+1

                if up:
                    Article.objects.filter(pk=article_id).update(up_num=F('up_num')+1)
                    res['msg'] = '点赞成功'
                else:
                    Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
                    res['msg'] = '点踩成功'


        return JsonResponse(res)
    else:
        res['code'] = 102
        res['msg'] = '您没有登录,请先 <a href="/login/">登录</a>'
        return JsonResponse(res)

根评论子评论后端

def article_detail(request, name, pk):
    user = UserInfo.objects.filter(username=name).first()
    article = Article.objects.filter(pk=pk).first()
    # 该文章所有的评论--->就加了这一样
    comment_list=Comment.objects.filter(article=article)
    if user and article:
        return render(request, 'article.html', context={'name': name, 'article_detail': article, 'user': user,'comment_list':comment_list})
    else:
        return render(request, '404.html')

根评论子评论样式前端

<div>
            <ul class="list-group">
                {% for comment in comment_list %}
                    <li class="list-group-item">
                        <p>
                            <span># {{ forloop.counter }} 楼</span>
                            <span>{{ comment.create_time|date:'Y-m-d H:i' }}</span>
                            <span><a href="/{{ comment.user.username }}/">{{ comment.user.username }}</a></span>
                        </p>
                        {% if comment.parent_id %}
                            <p>
                                @{{ comment.parent.user.username }}
                            <p>{{ comment.content }}</p>
                            </p>
                        {% else %}
                            <p>
                                {{ comment.content }}
                            </p>
                        {% endif %}

                    </li>
                {% endfor %}

            </ul>
        </div>

评论功能后端

根评论后端

def comment(request):
    res = {'code': 100, 'msg': '评论成功'}
    # 1 判断用户是否登录
    if request.user.is_authenticated:
        # 2 登录了后,取出前端传入的文章id,评论内容,取出当前登录用户
        article_id = request.POST.get('article_id')
        content = request.POST.get('content')
        # 3 存到评论表中
        Comment.objects.create(user=request.user, article_id=article_id, content=content)
        # 4 在文章表的评论数+1
        Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1)
        # 还要返回一些东西:评论内容,评论人名
        res['content'] = content
        res['username'] = request.user.username
        return JsonResponse(res)
    else:
        res['code'] = 102
        res['msg'] = '您没有登录'
        return JsonResponse(res)

子评论和根评论后端

def comment(request):
    res = {'code': 100, 'msg': '评论成功'}
    # 1 判断用户是否登录
    if request.user.is_authenticated:
        # 2 登录了后,取出前端传入的文章id,评论内容,取出当前登录用户
        article_id = request.POST.get('article_id')
        content = request.POST.get('content')
        # 取出父评论id号
        parent_id=request.POST.get('parent_id')
        # 3 存到评论表中,子评论和根评论一样的
        res_comment=Comment.objects.create(user=request.user, article_id=article_id, content=content,parent_id=parent_id)
        # 4 在文章表的评论数+1
        Article.objects.filter(pk=article_id).update(comment_num=F('comment_num') + 1)
        # 还要返回一些东西:评论内容,评论人名
        res['content'] = content
        res['username'] = request.user.username
        if parent_id: # 子评论,返回评论的评论人的名字
            res['parent_name']=res_comment.parent.user.username
        return JsonResponse(res)
    else:
        res['code'] = 102
        res['msg'] = '您没有登录'
        return JsonResponse(res)

评论功能前端,ajax显示

根评论前端,ajax显示

# javascript:ECMAScript 13+bom+dom
# ECMAScript 最新是13
# es6的语法 字符串的 `` 用来做字符串格式化
'我的名字:'+name+'我的年龄:'+age
`我的名字:${name}我的年龄:${age}`
// 评论事件
$("#btn_submit").click(function () {

    $.ajax({
        url: '/comment/',
        type: 'post',
        data: {
            article_id: '{{ article_detail.id }}',
            content: $("#id_content").val(),
            csrfmiddlewaretoken: '{{ csrf_token }}'
        },
        success: function (data) {
            console.log(data)
            $("#id_content").val('')
            // 把评论内容,拼接到前端 jq 的dom操作
            var username=data.username
            var res_content=data.content
            // 字符串 ''   " "    ``:es6的语法
            var s=`
            <li class="list-group-item">
                <p>
                    <span class='fa fa-address-card-o'></span>
                    <span>${username}:</span>
                </p>
                <p>${res_content}</p>
            </li>
            `
            // 把格式化完成的字符串拼接到评论后面
            $('.list-group').append(s)
        }
    })
})

子评论前端ajax显示

        $("#btn_submit").click(function () {

            var content = $("#id_content").val()
            if (parent_id) {// 如果是子评论,把输入框中得 @某人截断不要了
                var i = content.indexOf('\n') // 返回括号内字符的位置
                content = content.slice(i + 1) //把字符串从回车换行后截取,只要后面的
            }
            $.ajax({
                url: '/comment/',
                type: 'post',
                data: {
                    article_id: '{{ article_detail.id }}',
                    content: content,
                    parent_id: parent_id,
                    csrfmiddlewaretoken: '{{ csrf_token }}'
                },
                success: function (data) {
                    console.log(data)
                    // 把输入框内容清空
                    $("#id_content").val('')
                    if (data.code == 100) {
                        // 把评论内容,拼接到前端 jq 的dom操作
                        // 取出后端返回的用户名,评论内容
                        var username = data.username
                        var res_content = data.content

                        // 字符串 ''   " "    ``:es6的语法,字符串格式化后,拼接到评论列表后端
                        var s = ''
                        if (parent_id) {
                            var parent_name = data.parent_name
                            s = `
                    <li class="list-group-item">
                        <p>
                            <span class='fa fa-address-card-o'></span>
                            <span>${username}:</span>
                        </p>
                           <p>@${parent_name}</p>
                        <p>${res_content}</p>
                    </li>
                    `
                        } else {
                            s = `
                    <li class="list-group-item">
                        <p>
                            <span class='fa fa-address-card-o'></span>
                            <span>${username}:</span>
                        </p>
                        <p>${res_content}</p>
                    </li>
                    `
                        }

                        // 把格式化完成的字符串拼接到评论后面
                        $('.list-group').append(s)
                    }else{
                        $('.error').html(data.msg)
                    }

                },
                error:function (data){ // 当次请求失败了
                     $('.error').html("小伙子,出错了")
                }
            })
        })
 $('.replay').click(function () {
            var name = $(this).attr('username')
            //alert(name)
            // 把用户名写在输入框中
            $('#id_content').val('@' + name + '\n').focus()
            // 父评论id号,全局变量
            parent_id = $(this).attr('comment')
        })

后台管理页面

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>
        {% block title %}

        {% endblock %}
    </title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
    <script src="/static/jquery-3.3.1/jquery-3.3.1.min.js"></script>
    <script src="/static/bootstrap/js/bootstrap.min.js"></script>
    <link rel="stylesheet" href="/static/font-awesome-4.7.0/css/font-awesome.min.css">
    {% block link %}

    {% endblock %}
</head>
<body>
<div class="main">
    <div class="header">
        <nav class="navbar navbar-default common navbar-inverse">
            <div class="container-fluid">

                <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="#">博客园-后台管理</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">
                    </ul>

                </div><!-- /.navbar-collapse -->

            </div><!-- /.container-fluid -->
        </nav>
    </div>
    <div class="container-fluid">
        <div class="row">
            <div class="col-md-2">
                <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
                    <div class="panel panel-default">
                        <div class="panel-heading" role="tab" id="headingOne">
                            <h4 class="panel-title">
                                <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"
                                   aria-expanded="true" aria-controls="collapseOne">
                                    文章操作
                                </a>
                            </h4>
                        </div>
                        <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
                             aria-labelledby="headingOne">
                            <div class="panel-body">
                                <ul class="nav">
                                    <li><a href="">新增文章</a></li>
                                    <li><a href="">修改文章</a></li>
                                    <li><a href="">修改头像</a></li>
                                </ul>
                            </div>
                        </div>
                    </div>
                    <div class="panel panel-default">
                        <div class="panel-heading" role="tab" id="headingTwo">
                            <h4 class="panel-title">
                                <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion"
                                   href="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
                                    其他操作
                                </a>
                            </h4>
                        </div>
                        <div id="collapseTwo" class="panel-collapse collapse" role="tabpanel"
                             aria-labelledby="headingTwo">
                            <div class="panel-body">
                                <ul class="nav">
                                    <li><a href="">修改头像</a></li>
                                    <li><a href="">修改密码</a></li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-md-10">
                <div>
                    <!-- Nav tabs -->
                    <ul class="nav nav-tabs" role="tablist">
                        <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab"
                                                                  data-toggle="tab">文章</a></li>
                        <li role="presentation"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">随笔</a>
                        </li>
                        <li role="presentation"><a href="#messages" aria-controls="messages" role="tab"
                                                   data-toggle="tab">评论</a></li>
                        <li role="presentation"><a href="#settings" aria-controls="settings" role="tab"
                                                   data-toggle="tab">相册</a></li>
                    </ul>

                    <!-- Tab panes -->
                    <div class="tab-content">
                        <div role="tabpanel" class="tab-pane active" id="home">
                            {% block article %}

                            {% endblock %}
                        </div>
                        <div role="tabpanel" class="tab-pane" id="profile">随笔内容</div>
                        <div role="tabpanel" class="tab-pane" id="messages">评论</div>
                        <div role="tabpanel" class="tab-pane" id="settings">相册</div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
{% block js %}

{% endblock %}
</html>

index.html

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

{% block title %}
    后台管理
{% endblock %}
{% block article %}

    <table class="table table-hover table-striped">
        <thead>
        <tr>
            <th>文章id</th>
            <th>文章名</th>
            <th>发布时间</th>
            <th>评论数</th>
            <th>操作</th>
            <th>操作</th>
        </tr>
        </thead>
        <tbody>
        {% for article in article_list %}
            <tr>
                <th scope="row">{{ article.id }}</th>
                <td>{{ article.title }}</td>
                <td>{{ article.create_time|date:'Y-m-d H-i' }}</td>
                <td>{{ article.comment_num }}</td>
                <td><a href="">删除</a></td>
                <td><a href="">修改</a></td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
{% endblock %}

backend视图函数

from django.contrib.auth.decorators import login_required
@login_required(login_url='/login/')
def backend(request):
    article_list=Article.objects.filter(blog=request.user.blog)
    return render(request,'backend/index.html',context={'article_list':article_list})

删除文章

后端

@login_required(login_url='/login/')
def delete(request):
    pk = request.GET.get('id')
    Article.objects.filter(pk=pk).delete()
    return redirect('/backend/')

前端

<td><a href="/delete/?id={{ article.id }}">删除</a></td>

新增文章功能

前端

{% extends 'backend/base.html' %}
{% block title %}
    新增文章
{% endblock %}

{% block link %}
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
{% endblock %}

{% block article %}
    <div>
        <h2>新增文章</h2>
        <form action="" method="post">
            {% csrf_token %}
            <div class="form-group">
                <label for="">标题</label>
                <input type="text" name="title" class="form-control">
            </div>
            <div class="form-group">
                <label for="">内容</label>
                <textarea name="content" id="editor_id" cols="300" rows="20"></textarea>
            </div>
            <div class="form-group">
                <label for="">分类</label>
                <select class="form-control" name="category">
                    {% for category in category_list %}
                        <option value="{{ category.id }}">{{ category.name }}</option>
                    {% endfor %}
                </select>
            </div>

            <div class="form-group">
                <label for="">标签</label>
                <select multiple class="form-control" name="tag">
                    {% for tag in tag_list %}
                        <option value="{{ tag.id }}">{{ tag.name }}</option>
                    {% endfor %}
                </select>
            </div>
            <div class="text-center">
                <input type="submit" value="提交" class="btn btn-success">
            </div>
        </form>
    </div>
{% endblock %}

{% block js %}
    <script>
        KindEditor.ready(function (K) {
            window.editor = K.create('#editor_id',
                {
                    width: '100%',
                    height: '500px',
                    resizeType: 0,
                    uploadJson: '/put_img/',
                    filePostName: 'myfile',
                     //额外带的参数
                    extraFileUploadParams: {
                        'csrfmiddlewaretoken': '{{ csrf_token }}'
                    }
                });
        });
    </script>
{% endblock %}

后端

@login_required(login_url='/login/')
def add_article(request):
    if request.method == 'GET':
        # 当前博客下所有的分类
        category_list = Category.objects.filter(blog=request.user.blog)
        # 当前博客下所有的标签
        tag_list = Tag.objects.filter(blog=request.user.blog)
        return render(request, 'backend/add_article.html', locals())
    else:
        # 新增文章
        # 1 文章标题,文章摘要内容中截取出摘要,文章内容,blog:当前登录用户,category,tag存中间表
        title = request.POST.get('title')
        content = request.POST.get('content')
        # BeautifulSoup第一个参数是html内容,第二个参数:使用的解析器
        soup = BeautifulSoup(content, 'html.parser')
        desc = soup.text.replace('/n', '').replace('/r', '')[:70]  # 把html的内容也截取出来,只截取文字
        # 剔除script标签
        script_list = soup.findAll('script')  # 搜索到html中所有的script标签
        for script in script_list:
            script.decompose()  # 把搜到的script标签一个个删除

        category = request.POST.get('category')
        tags = request.POST.getlist('tag')
        article = Article.objects.create(title=title, desc=desc, content=str(soup), blog=request.user.blog,
                                         category_id=category)
        # 存标签,在第三张表中
        article.tag.add(*tags)

        return redirect('/backend/')

处理xss攻击

# xss攻击:跨站脚本攻击
-在内容中存储了script的脚本,前端渲染的时候,使用了safe,渲染html内容,导致如果存在script脚本,脚本就会执行---》解决方案,在存储的时候,就要把恶意脚本删除
# 后台文章内容存储的时候,要删除script标签
-我们文章中代码里插入的script标签不要删除----》富文本编辑器已经处理了这种标签了
-怕作者直接在html中加入script,这种是危险的

# beautifulsoup4  爬虫会用,解析,修改html文档
-pip3 install beautifulsoup4
-删除script标签
    soup = BeautifulSoup(content, 'html.parser')
    script_list=soup.findAll('script') # 搜索到html中所有的script标签
    for script in script_list:
        script.decompose() # 把搜到的script标签一个个删除
def add_article(request):
    if request.method == 'GET':
        # 当前博客下所有的分类
        category_list = Category.objects.filter(blog=request.user.blog)
        # 当前博客下所有的标签
        tag_list = Tag.objects.filter(blog=request.user.blog)
        return render(request, 'backend/add_article.html', locals())
    else:
        # 新增文章
        # 1 文章标题,文章摘要内容中截取出摘要,文章内容,blog:当前登录用户,category,tag存中间表
        title = request.POST.get('title')
        content = request.POST.get('content')
        # BeautifulSoup第一个参数是html内容,第二个参数:使用的解析器
        soup = BeautifulSoup(content, 'html.parser')
        desc = soup.text.replace('/n', '').replace('/r', '')[:70]  # 把html的内容也截取出来,只截取文字
        # 剔除script标签
        script_list = soup.findAll('script')  # 搜索到html中所有的script标签
        for script in script_list:
            script.decompose()  # 把搜到的script标签一个个删除

        category = request.POST.get('category')
        tags = request.POST.getlist('tag')
        article = Article.objects.create(title=title, desc=desc, content=str(soup), blog=request.user.blog,
                                         category_id=category)
        # 存标签,在第三张表中
        article.tag.add(*tags)

        return redirect('/backend/')

富文本编辑器上传图片

前端

  <script>
        KindEditor.ready(function (K) {
            window.editor = K.create('#editor_id',
                {
                    width: '100%',
                    height: '500px',
                    resizeType: 0,
                    uploadJson: '/put_img/',
                    filePostName: 'myfile',
                     //额外带的参数
                    extraFileUploadParams: {
                        'csrfmiddlewaretoken': '{{ csrf_token }}'
                    }
                });
        });
    </script>

后端

def put_img(request):
    # img=request.FILES.get('imgFile')
    img = request.FILES.get('myfile')
    img_name = os.path.join(settings.MEDIA_ROOT, 'upload', img.name)
    with open(img_name, 'wb') as f:
        for line in img:
            f.write(line)

    return JsonResponse(
        {
            "error": 0,
            "url": "http://127.0.0.1:8000/media/upload/"+img.name
        })

首页用户信息显示,退出登录

前端 index.html

  {% if request.user.is_authenticated %}
                    <ul class="nav navbar-nav navbar-right">
                        <li><a href="#">{{ request.user.username }}</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">更多 <span class="caret"></span></a>
                            <ul class="dropdown-menu">
                                <li><a href="#">修改密码</a></li>
                                <li><a href="/backend/">后台管理</a></li>
                                <li><a href="#">修改头像</a></li>
                                <li role="separator" class="divider"></li>
                                <li><a href="/logout/">退出</a></li>
                            </ul>
                        </li>
                    </ul>
                {% else %}
                    <ul class="nav navbar-nav navbar-right">
                        <li><a href="/login/">登录</a></li>
                        <li><a href="/register/">注册</a></li>
                    </ul>
                {% endif %}

退出后台

def logout(request):
    # 如何就算退出了? 删除session,和cookie
    out(request)
    return redirect('/')

修改头像

前端update_head.html

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

{% block title %}
    修改头像
{% endblock %}

{% block article %}
    <div style="padding: 20px">
{#    一定要注意,如果是form表单提交文件指定编码格式#}
        <form action="" method="post" enctype="multipart/form-data">
            {% csrf_token %}
            <div class="form-group">
                <label for="">原头像</label>
                <img src="/media/{{ request.user.avatar }}" alt="" width="80px" height="80px">
            </div>
            <hr>
            <div class="form-group">
                <label for="id_file">新头像
                    <img src="/static/img/default.png" alt="" height="80px" width="80px" style="margin-left: 10px"
                         id="id_img">
                </label>
                <input type="file" name='head_file' id="id_file" accept="image/*" style="display: none">
            </div>
            <input type="submit" value="提交" class="btn btn-default">

        </form>
    </div>
{% endblock %}

{% block js %}
    <script>
        $("#id_file").change(function () {
            // 把当前图片,放到img标签中
            // 把图片地址放到img标签上,标签就显示了图片
            //$("#id_img")[0].src='https://tva1.sinaimg.cn/large/00831rSTly1gd1u0jw182j30u00u043b.jpg'

            // 1 id_file这个标签的图片读出来,借助于文件阅读器,js提供的一个类
            var reader = new FileReader()
            // 2 拿到文件对象,赋值给一个变量
            var file = $("#id_file")[0].files[0]
            // 3 把文件读到文件阅读器中
            reader.readAsDataURL(file)
            // 4 等读完后,把文件阅读器的内容写到img标签上
            //$("#id_img")[0].src=reader.result
            reader.onload = function () {
                //$("#id_img")[0].src=reader.result
                $('#id_img').attr('src', reader.result)
            }
        })
    </script>
{% endblock %}

后端

def update_head(request):
    if request.method == 'GET':
        return render(request, 'backend/update_head.html')
    else:
        head_file = request.FILES.get('head_file')
        # 方式一:常用做饭,修改文件
        request.user.avatar = head_file
        request.user.save()
        # 方式二:有问题,不会存前面avatar的路径
        # UserInfo.objects.filter(pk=request.user.id).update(avatar=head_file)
        return redirect('/backend/')

修改密码

前端 update_pwd.html

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

{% block title %}
    修改密码
{% endblock %}

{% block article %}
    <div style="padding: 20px">
        <form action="" method="post">
            {% csrf_token %}
            <div class="form-group">
                <label for="">原密码</label>
                <input type="text" class="form-control" name="old_pwd">
            </div>
            <div class="form-group">
                <label for="">新密码</label>
                <input type="text" class="form-control" name="new_pwd">
            </div>
            <div class="form-group">
                <label for="">确认密码</label>
                <input type="text" class="form-control" name="re_new_pwd">
            </div>

            <input type="submit" value="提交" class="btn btn-default"><span style="color: red">{{ error }}</span>

        </form>
    </div>
{% endblock %}

后端

def update_pwd(request):
    if request.method == 'GET':
        return render(request, 'backend/update_pwd.html')
    else:
        old_pwd = request.POST.get('old_pwd')
        new_pwd = request.POST.get('new_pwd')
        re_new_pwd = request.POST.get('re_new_pwd')
        # 校验原密码是否正确
        if request.user.check_password(old_pwd):
            if new_pwd == re_new_pwd:
                request.user.set_password(new_pwd)
                request.user.save()  # 不要忘了保存
                # 密码修改成功后推出
                logout(request)
                return redirect('/')
            else:
                return render(request, 'backend/update_pwd.html', context={'error': '两次密码不一致'})
        else:
            return render(request, 'backend/update_pwd.html', context={'error': '原密码输入错误'})

修改文章

前端update_article.html

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

{% block title %}
    修改文章
{% endblock %}

{% block link %}
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
{% endblock %}

{% block article %}
    <div>
        <h2>修改文章</h2>
        <form action="" method="post">
            {% csrf_token %}
            <input type="hidden" name="id" value="{{ article.id }}">
            <div class="form-group">

                <label for="">标题</label>
                <input type="text" name="title" class="form-control" value="{{ article.title }}">
            </div>
            <div class="form-group">
                <label for="">内容</label>
                <textarea name="content" id="editor_id" cols="300" rows="20">
                    {{ article.content }}

                </textarea>
            </div>
            <div class="form-group">
                <label for="">分类</label>
                <select class="form-control" name="category">
                    {% for category in category_list %}
                        {% if article.category_id == category.id %}
                            <option value="{{ category.id }}" selected>{{ category.name }}</option>
                        {% else %}
                            <option value="{{ category.id }}">{{ category.name }}</option>
                        {% endif %}
                    {% endfor %}
                </select>
            </div>

            <div class="form-group">
                <label for="">标签</label>
                <select multiple class="form-control" name="tag">
                    {% for tag in tag_list %}
                        {% if tag in article.tag.all %}
                            <option value="{{ tag.id }}" selected>{{ tag.name }}</option>
                        {% else %}
                            <option value="{{ tag.id }}">{{ tag.name }}</option>
                        {% endif %}
                    {% endfor %}
                </select>
            </div>
            <div class="text-center">
                <input type="submit" value="提交" class="btn btn-success">
            </div>
        </form>
    </div>
{% endblock %}

{% block js %}
    <script>
        KindEditor.ready(function (K) {
            window.editor = K.create('#editor_id',
                {
                    width: '100%',
                    height: '500px',
                    resizeType: 0,
                    // 上传图片相关
                    uploadJson: '/put_img/',
                    filePostName: 'myfile',
                    //额外带的参数
                    extraFileUploadParams: {
                        'csrfmiddlewaretoken': '{{ csrf_token }}'
                    }
                });
        });
    </script>
{% endblock %}

后端

@login_required(login_url='/login/')
def update_article(request):
    if request.method == 'GET':
        article_id = request.GET.get('id')
        article = Article.objects.filter(pk=article_id).first()
        # 当前博客下所有的分类
        category_list = Category.objects.filter(blog=request.user.blog)
        # 当前博客下所有的标签
        tag_list = Tag.objects.filter(blog=request.user.blog)
        return render(request, 'backend/update_article.html',
                      context={'article': article, 'category_list': category_list, 'tag_list': tag_list})
    else:
        pk = request.POST.get('id')
        # 1 文章标题,文章摘要内容中截取出摘要,文章内容,blog:当前登录用户,category,tag存中间表
        title = request.POST.get('title')
        content = request.POST.get('content')
        # BeautifulSoup第一个参数是html内容,第二个参数:使用的解析器
        soup = BeautifulSoup(content, 'html.parser')
        desc = soup.text.replace('/n', '').replace('/r', '')[:70] + '...'  # 把html的内容也截取出来,只截取文字
        # 剔除script标签
        script_list = soup.find_all('script')  # 搜索到html中所有的script标签
        for script in script_list:
            script.decompose()  # 把搜到的script标签一个个删除

        category = request.POST.get('category')
        tags = request.POST.getlist('tag')
        article = Article.objects.filter(pk=pk)  # 返回的是queryset对象
        article.update(title=title, desc=desc, content=str(soup), blog=request.user.blog,
                       category_id=category)  # queryset对象继续调用update
        # 存标签,在第三张表中,自动创建的   手动创建的add就用不了了
        article.first().tag.set(tags)  # [1,3,4]  放对象列表和放id列表都可以
        # article.first().tag.clear()
        # article.first().tag.add(*tags)

        return redirect('/backend/')

django发送邮件

# python 代码就可以发送邮件 smtplib 内置模块,可以发送邮件

# 你要发邮件要有个发件箱:就是你的邮箱(qq邮箱,163邮箱。。。)
-配置我们的邮箱,让它可以用python代码发送邮件
-以qq邮箱为例:开启smtp服务:IMAP/SMTP服务
    fjkmdxelrgfzbgae

# django发送右键---》本质是封装了 smtplib 内置模块
-在django中
     from django.core.mail import send_mail
     res1 = send_mail('邮件标题', '邮件内容', settings.EMAIL_HOST_USER, ["2247675450@qq.com"])

BBS项目大总结

# 写了什么
-注册功能
    -1 forms表单的使用:字段类,字段属性
        - 作用:数据校验,渲染前端页面,渲染前端页面错误信息
    -2 for循环form对象渲染页面
    -3 头像上传:头像动态显示
    -4 ajax提交注册信息
        -ajax上传文件:FormData    form表单上传文件:指定编码
        -form表单中:var data = $("#id_form").serializeArray()
        -jq的循环$.each(data,function(i,v){})
        -登录成功: location.href = '/login/'
        -错误:渲染再页面上
        -定时任务:3s后清空错误信息
    -5 后端
        -取出前端传进的数据,校验通过后:剔除re_pwd,把头像给avatar
        -create_user创建用户

-登录
    -布局使用bootstrap实现的
    -图片验证码:自己写的
        -保存在session中

    -前端图片验证码的刷新
        -只要src对应的路径发生变化,就会重新加载 
    -ajax提交登录信息
        -用户名,密码和验证码到后端
    -登录接口后端
        -authenticate 验证用户名密码是否正确(密码是加密的)
        -登录成功,调用auth_login(request, user)---》再session中写入数据,cookie中写入

-首页
    -布局
        -bootstrap的导航条
        -左中右的栅格布局
            -中间位置上下:上面是轮播图,下面是文章列表
            -文章列表:模板语法的for循环 ,media媒体组
            -优化字段:点赞,点踩,评论数量
        -后端接口
            -查询所有文章(缺分页)
            -轮播图内容:模板语法写的====》使用ajax加载轮播图
        -登录成功显示用户名字,退出
        -退出接口
        -没有登录显示登录注册

-个人站点
    -路由设计:一定要放在最下面
    -布局
        -base.html:用来做继承
        -不同用户顶部颜色不一样,不同人博客样式不一样
    -右侧文章:把首页文章复制过来的,最后一行布局推到右侧去了
    -左侧:
        -标签,分类,随笔档案的渲染---》后端接口查询出
        -后端:3个分组查询,TruncMonth后再分组
        -前端:通过后端查出来的渲染
        -使用inclusion_tag重写了左侧栏的渲染

    -左侧的过滤
        -3条路由
        -汇总成1条路由

 -文章详情
    -布局:base.html
        -文章真正的渲染  | safe   文章内容是html
        -点赞点踩的布局:复制粘贴过来的
    -点赞点踩的后端
        -如果点过了就不能点了
        -没登录不能点
        -事务
    -评论
        -render显示根和子评论
        -评论ajax提交根评论
        -评论的ajax提交子评论
        -根评论和子评论的ajax显示

-后台管理
    -布局
    -文章列表的显示:表格
    -删除文章
    -新增文章:富文本编辑器,标签,分类的渲染
    -处理xss攻击
    -修改文章
    -修改头像
    -修改密码
    -django发送邮件

# 可以扩展什么
    -首页轮播图ajax加载
    -标签列表,删除,修改,新增
        -新增标签:Tag.objects.create(name=前端传进来的,blog=request.user.blog)
    -输入密码错误3次,锁定账号,解锁账号
    -修改密码:之前用过的密码不能用了
    -异地登录,发邮件提醒
    -ip地址根之前不同
    -文章存到草稿
posted @   空白o  阅读(120)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示