首页 > 技术文章 > BBS版本一

mengqingqiang 2021-12-20 20:55 原文

day15 BBS项目

项目开发流程

- 需求分析
- 项目设计
- 分组开发
- 测试
- 上线

表设计

'''
一个项目中最重要的不是业务逻辑的书写
而是前期的表设计,只要将表设计好了,后续的功能书写才会一帆风顺
'''

# 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()

    7.文章评论表
    	记录哪个用哪个户给哪篇文章写了哪些评论内容
        user	   ForeignKey(to="User")
        article     ForeignKey(to="Article")
        content		CharField()
        comment_time  DateField()
        parent		ForeignKey(to="Comment",null=True)  # 自关联
        # parent		ForeignKey(to="self",null=True)  # orm提供的语法自关联
        
        评论分为根评论和子评论(可以评论别人评论的)
        根评论:是评论文章内容的评论
        字评论:是回复别人的评论
       # 外键关系:是一对多的

创建表字段

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

# Create your models here.


# 用户表
class UserInfo(AbstractUser):
    phone = models.CharField(max_length=32, verbose_name='手机号')
    # 头像
    avatar = models.FileField(upload_to='static/img', default='static/img/111.png', verbose_name='头像')
    '''
    给avatar字段传文件对象,该文件会自动存储到avatar文件下,然后avatar字段只保存文件路径
    '''
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')

    # 一对一站点表:用户表和站点表是一对一关系
    blog = models.OneToOneField(to='Blog', null=True, verbose_name='关联站点表')


# 站点表
class Blog(models.Model):
    site_name = models.CharField(max_length=64, verbose_name='站点名称')
    site_title = models.CharField(max_length=64, verbose_name='站点标题')
    site_theme = models.CharField(max_length=64, verbose_name='站点主题')


# 标签表
class Tag(models.Model):
    name = models.CharField(max_length=32)

    # 一对多站点表:标签表和站点表是一对多关系
    blog = models.ForeignKey(to='Blog', verbose_name='关联站点表')


# 分类表
class Category(models.Model):
    name = models.CharField(max_length=32, verbose_name='分类名称')

    # 一对多站点表:分类表和站点表是一对多关系
    blog = models.ForeignKey(to='Blog', verbose_name='关联站点表')


# 文章表
class Article(models.Model):
    title = models.CharField(max_length=128, verbose_name='文章标题')
    desc = models.CharField(max_length=512, verbose_name='文章简介')
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')

    # 优化字段
    up_num = models.IntegerField(verbose_name='点赞数')
    down_num = models.IntegerField(verbose_name='点踩数')
    comment_num = models.IntegerField(verbose_name='评论数')

    # 外键关系
    # 一对多站点表:文章表和站点表是一对多关系
    blog = models.ForeignKey(to='Blog', verbose_name='关联站点表')
    tags = models.ManyToManyField(to='Tag', through='Article2Tag',
                                  through_fields=('article', 'tag'),
                                  verbose_name='关联第三张表'
                                  )
    category = models.ForeignKey(to='Category', verbose_name='关联分类表')


# 自己创建的第三张表,用于扩建字段
class Article2Tag(models.Model):
    article = models.ForeignKey(to='Article')
    tag = models.ForeignKey(to='Tag')


# 点赞点踩表
class UpAndDown(models.Model):
    user = models.ForeignKey(to='UserInfo', verbose_name='关联用户表')
    article = models.ForeignKey(to='Article', verbose_name='关联文章表')
    is_up = models.BooleanField()  # 存的是0/1


# 评论表
class Comment(models.Model):
    user = models.ForeignKey(to='UserInfo', verbose_name='关联用户表')
    article = models.ForeignKey(to='Article', verbose_name='关联文章表')
    content = models.CharField(max_length=512, verbose_name='评论内容')
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')

    # 根评论和子评论一对多的关系,自关联
    parent_id = models.ForeignKey(to='self', null=True)  # self就是关联自己表

搭建注册页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
    <script src="{% static 'layer-v3.5.1/layer/layer.js' %}"></script>
</head>
<body>
    <div class="container-fluid">
        <div class="row">
            <h1 class="text-center">注册页面</h1>
            <div class="col-md-8 col-md-offset-2">
                <form action="">
                    {% csrf_token %}
                    <div class="form-group">
                        <label for="">用户名:</label>
                        <input type="text" id="username" class="form-control">
                    </div>
                    <div class="form-group">
                        <label for="">密码:</label>
                        <input type="password" id="pwd" class="form-control">
                    </div>
                    <div class="form-group">
                        <label for="">确认密码:</label>
                        <input type="password" id="re_pwd" class="form-control">
                    </div>
                    <div class="form-group">
                        <label for="">邮箱:</label>
                        <input type="text" id="email" class="form-control">
                    </div>
                    <div class="form-group">
                        <label for="myfile">头像:
                            {% load static %}
                            <img src="{% static 'img/111.png' %}" alt="" class="img-circle" width="66px" id="myimg">
                        </label>
                        <input type="file" id="myfile" class="form-control" style="display: none">
                    </div>

                    <input type="button" class="btn btn-block btn-success pull-right" value="注册">
                </form>
            </div>
        </div>
    </div>


    <script>

        // 头像实时展示:借助于文件阅读器
        $('#myfile').change(function () {
            // 1.借助于文件阅读器
            var myFileReadObj = new FileReader();

            // 2.获取图片数据
            var myImgObj = $('#myfile')[0].files[0];

            // 3. 把图片数据交给文件阅读器读取
            myFileReadObj.readAsDataURL(myImgObj);  // 异步操作,有io

            // 3.1 因为是异步操作。先让它加载完在执行后面的
            myFileReadObj.onload = function () {
                // 4.改变img的src
                $('#myimg').attr('src',myFileReadObj.result)
            }
        });

        $('.btn').on('click', function () {
            var username = $('#username').val();
            // 简单验证
            if (!username) {
                layer.msg('请输入用户名')
                return
            }

            // 借助于ForData
            var formDataObj = new FormData();

            // 向formDataObj增加普通数据
            formDataObj.append('username', $('#username').val());
            formDataObj.append('pwd', $('#pwd').val());
            formDataObj.append('re_pwd', $('#re_pwd').val());
            formDataObj.append('email', $('#email').val());

            // 添加文件数据
            formDataObj.append('myfile', $('#myfile')[0].files[0]);

            // 发送ajax请求
            $.ajax({
                url:'',
                type:'post',
                data:formDataObj,
                contentType:false,
                processData: false,
                success:function (args) {
                    if (args.code == 200){
                        layer.msg(args.msg,{icon:1}, function () {
                            location.href = args.url
                        })
                    }else {
                        layer.msg(args.msg)
                    }
                }
            })
        })
    </script>
</body>
</html>

'''
其中包换头像上传功能
'''

注册功能实现

from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse
from app01 import models


def register(request):
    if request.method == 'POST':
        # 1.获取用户数据
        back_dic = {'code': 200, 'msg': '注册成功'}
        username = request.POST.get('username')
        pwd = request.POST.get('pwd')
        re_pwd = request.POST.get('re_pwd')
        email = request.POST.get('email')
        avatar = request.FILES.get('myfile')

        # 2.验证参数
        if not username:
            back_dic['code'] = 201
            back_dic['msg'] = '用户名必须填写'
            return JsonResponse(back_dic)

        if not pwd:
            back_dic['code'] = 202
            back_dic['msg'] = '密码必须填写'
            return JsonResponse(back_dic)

        if not re_pwd:
            back_dic['code'] = 203
            back_dic['msg'] = '确认密码必须填写'
            return JsonResponse(back_dic)

        if not email:
            back_dic['code'] = 204
            back_dic['msg'] = '邮箱必须填写'
            return JsonResponse(back_dic)

        res = models.UserInfo.objects.filter(username=username).first()
        email_obj = models.UserInfo.objects.filter(email=email).first()
        # 判断用户是否存在
        if res:
            back_dic['code'] = 205
            back_dic['msg'] = '用户名已存在'
            return JsonResponse(back_dic)

        # 判断密码是否一致
        if not pwd == re_pwd:
            back_dic['code'] = 206
            back_dic['msg'] = '密码两次不一致'
            return JsonResponse(back_dic)

        # 判断邮箱是否已经绑定
        if email_obj:
            back_dic['code'] = 207
            back_dic['msg'] = '邮箱已绑定'
            return JsonResponse(back_dic)

        # 3.业务逻辑
        data_dic = {}
        if avatar:
            data_dic['avatar'] = avatar

        # 给密码加密
        import hashlib
        m = hashlib.md5()
        m.update(pwd.encode('utf-8'))
        pwd = m.hexdigest()
        data_dic['username'] = username
        data_dic['password'] = pwd
        data_dic['email'] = email

        # 4.存入数据库
        models.UserInfo.objects.create(**data_dic)  # 利用可变长参数,传入数据

        # 5.返回格式化数据
        back_dic['url'] = '/login/'
        return JsonResponse(back_dic)
    return render(request, 'register.html')

登录页面搭建

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
    <script src="{% static 'layer-v3.5.1/layer/layer.js' %}"></script>
</head>
<body>
<div class="container-fluid">
    <div class="row">
        <h1 class="text-center">登录页面</h1>
        <div class="col-md-8 col-md-offset-2">
            <form action="">
                <div class="form-group">
                    <label for="">用户名:</label>
                    <input type="text" id="username" class="form-control">
                </div>
                <div class="form-group">
                    <label for="">密码:</label>
                    <input type="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" id="code" class="form-control">
                        </div>
                        <div class="col-md-6">
                            <img src="/get_code/" id="my_img" alt="" style="width: 475px;height: 30px;">
                        </div>
                    </div>
                </div>
                <input type="button" class="btn btn-success btn-block" value="登录">
            </form>
        </div>
    </div>
</div>
<script>
    // 点击图片切换验证码
    $('#my_img').on('click', function () {
        // 拿当前点击图片中的src属性
        var old = $(this).attr('src');
        // 更换src属性
        $(this).attr('src', old += '?')
    })

    // 给登录按钮绑定点击事件
    $('.btn').on('click', function () {
        // 获取输入框中的数据
        var username = $('#username').val();
        var password = $('#password').val();
        var code = $('#code').val();

        // 发送ajax请求
        $.ajax({
            url: '',
            type: 'post',
            data: {'username': username, 'password': password, 'code': code},
            success: function (res) {
                if (res.code == 200) {
                    layer.msg(res.msg, {},function () {
                        location.href = res.url;
                    });
                } else {
                    layer.msg(res.msg, {icon:2});
                }
            }
        })
    })
</script>
</body>
</html>

登录功能实现

def login(request):
    if request.method == 'POST':
        back_dic = {'code': 200, 'msg': '登录成功'}
        # 接收参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')

        # 验证参数
        if not code:
            back_dic['code'] = 208
            back_dic['msg'] = '验证码必须填写'
            return JsonResponse(back_dic)

        if request.session.get('code').upper() != code.upper():
            back_dic['code'] = 209
            back_dic['msg'] = '验证码填写错误'
            return JsonResponse(back_dic)

        # 业务逻辑
        # 给密码加密
        import hashlib
        m = hashlib.md5()
        m.update(password.encode('utf-8'))
        password = m.hexdigest()
        user_obj = models.UserInfo.objects.filter(username=username, password=password).first()
        if not user_obj:
            back_dic['code'] = 210
            back_dic['msg'] = '用户名或者密码错误'
            return JsonResponse(back_dic)

        # 保存用户信息
        request.session['id'] = user_obj.id
        request.session['username'] = user_obj.username

        # 返回数据
        back_dic['url'] = '/home/'
        return JsonResponse(back_dic)

    return render(request, 'login.html')

验证码功能

def get_code(request):
    # 最终步骤4:写图片验证码
    img_obj = Image.new('RGB', (430, 35), get_random())
    img_draw = ImageDraw.Draw(img_obj)  # 产生一个画笔对象
    img_font = ImageFont.truetype('static/font/包图小白体.ttf', 30)  # 字体样式 大小

    # 随机验证码  五位数的随机验证码  数字 小写字母 大写字母
    code = ''
    for i in range(4):
        random_upper = chr(random.randint(65, 90))
        random_lower = chr(random.randint(97, 122))
        random_int = str(random.randint(0, 9))
        # 从上面三个里面随机选择一个
        tmp = random.choice([random_lower, random_upper, random_int])
        # 将产生的随机字符串写入到图片上
        """
        为什么一个个写而不是生成好了之后再写
        因为一个个写能够控制每个字体的间隙 而生成好之后再写的话
        间隙就没法控制了
        """
        img_draw.text((i * 60 + 60, -2), tmp, get_random(), img_font)
        # 拼接随机字符串
        code += tmp
    print(code)
    # 随机验证码在登陆的视图函数里面需要用到 要比对 所以要找地方存起来并且其他视图函数也能拿到
    request.session['code'] = code
    io_obj = BytesIO()
    img_obj.save(io_obj, 'png')
    return HttpResponse(io_obj.getvalue())

搭建首页项目

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
    <script src="{% static 'layer-v3.5.1/layer/layer.js' %}"></script>
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></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="#">Link</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">Dropdown <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">
                <!--判断session中是否有数据,如果有显示登录用户,如果没有显示登录注册按钮-->
                {% if request.session.username %}
                    <li><a href="#">{{ request.session.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="#myModal">修改密码</a></li>
                            <li><a href="#">后台管理</a></li>
                            <li><a href="/logout/">退出登录</a></li>
                            <li role="separator" class="divider"></li>
                        </ul>
                    </li>
                {% else %}
                    <li><a href="/register/">注册</a></li>
                    <li><a href="/login/">登录</a></li>
                {% endif %}

                <!-- Modal -->
                <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
                    <div class="modal-dialog" role="document">
                        <div class="modal-content">
                            <div class="modal-header">
                                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                                        aria-hidden="true">&times;</span></button>
                                <h4 class="modal-title text-center" id="myModalLabel">修改密码</h4>
                            </div>
                            <div class="modal-body">
                                <div class="row">
                                    <div class="col-md-10 col-md-offset-1">
                                        <div class="form_group">
                                            <label for="">原密码</label>
                                            <input type="password" id="old_password" class="form-control">
                                        </div>
                                        <div class="form_group">
                                            <label for="">新密码</label>
                                            <input type="password" id="new_password" class="form-control">
                                        </div>
                                        <div class="form_group">
                                            <label for="">确认密码</label>
                                            <input type="password" id="re_password" class="form-control">
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="modal-footer">
                                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                                <button type="button" class="btn btn-primary commit">提交</button>
                            </div>
                        </div>
                    </div>
                </div>
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

<div class="container-fluid">
    <div class="row">
        <div class="col-md-2">
            <div class="panel panel-primary">
                <div class="panel-body">
                    Panel content
                </div>
                <div class="panel-footer">Panel footer</div>
            </div>
            <div class="panel panel-success">
                <div class="panel-body">
                    Panel content
                </div>
                <div class="panel-footer">Panel footer</div>
            </div>
            <div class="panel panel-default">
                <div class="panel-body">
                    Panel content
                </div>
                <div class="panel-footer">Panel footer</div>
            </div>
        </div>
        <div class="col-md-7">
            {% for article_obj in article_list %}
                <div class="media">
                <h4 class="media-heading"><a href="">{{ article_obj.title }}</a></h4>
                    <div class="media-left">
                        <a href="#">
                            <img class="media-object" width="30" src="/static/img/111.png" alt="...">
                        </a>
                    </div>
                    <div class="media-body">
                        {{ article_obj.desc }}
                    </div>
                    <br>
                    <div>
                        <span><a href="">{{ article_obj.blog.userinfo.username }}&nbsp;&nbsp;</a></span>
                        <span>{{ article_obj.create_time| date:'Y-m-d H:i:s' }}&nbsp;&nbsp;</span>
                        <span><span class="glyphicon glyphicon-thumbs-up"></span>({{ article_obj.up_num }})&nbsp;&nbsp;</span>
                        <span><span class="glyphicon glyphicon-thumbs-down"></span>({{ article_obj.down_num }})&nbsp;&nbsp;</span>
                        <span><span class="glyphicon glyphicon-comment"></span>({{ article_obj.comment_num }})</span>
                    </div>
                </div>
                <hr>
            {% endfor %}


        </div>
        <div class="col-md-3">
            <div class="panel panel-primary">
                <div class="panel-body">
                    Panel content
                </div>
                <div class="panel-footer">Panel footer</div>
            </div>
            <div class="panel panel-success">
                <div class="panel-body">
                    Panel content
                </div>
                <div class="panel-footer">Panel footer</div>
            </div>
            <div class="panel panel-default">
                <div class="panel-body">
                    Panel content
                </div>
                <div class="panel-footer">Panel footer</div>
            </div>
        </div>
    </div>
</div>


<script>
    $('.commit').on('click', function () {
        // 获取输入框中的数据
        var old_password = $('#old_password').val();
        var new_password = $('#new_password').val();
        var re_password = $('#re_password').val();

        // 发送ajax请求
        $.ajax({
            url: '/set_password/',
            type: 'post',
            data: {'old_password': old_password, 'new_password': new_password, 're_password': re_password},
            success: function (res) {
                if (res.code == 200) {
                    layer.msg(res.msg, {}, function () {
                        {#location.href = res.url;#}
                        location.reload()
                    });
                } else {
                    layer.msg(res.msg, {icon: 2});
                }
            }
        })
    })
</script>
</body>
</html>

首页

def home(request):
    article_list = models.Article.objects.all()
    return render(request, 'home.html', locals())

修改密码功能

def get_md5(pwd):
    import hashlib
    m = hashlib.md5()
    m.update(pwd.encode('utf-8'))
    return m.hexdigest()


def set_password(request):
    if request.method == 'POST':
        back_dic = {'code': 200, 'msg': '修改密码成功'}
        # 获取数据
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        re_password = request.POST.get('re_password')

        # 验证参数
        old_password = get_md5(old_password)
        user_obj = models.UserInfo.objects.filter(username=request.session.get('username'),
                                                  password=old_password).first()
        if not user_obj:
            back_dic['code'] = 211
            back_dic['msg'] = '原密码错误'
            return JsonResponse(back_dic)

        if new_password != re_password:
            back_dic['code'] = 212
            back_dic['msg'] = '两次密码不一致'
            return JsonResponse(back_dic)

        # 业务逻辑
        new_password = get_md5(new_password)
        # 修改数据
        models.UserInfo.objects.filter(pk=request.session.get('id')).update(password=new_password)
        return JsonResponse(back_dic)

退出登录功能

def logout(request):
    request.session.flush()
    return redirect('/home/')

个人站点搭建页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
    <script src="{% static 'layer-v3.5.1/layer/layer.js' %}"></script>
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></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="#">Link</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">Dropdown <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">
                <!--判断session中是否有数据,如果有显示登录用户,如果没有显示登录注册按钮-->
                {% if request.session.username %}
                    <li><a href="#">{{ request.session.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="#myModal">修改密码</a></li>
                            <li><a href="#">后台管理</a></li>
                            <li><a href="/logout/">退出登录</a></li>
                            <li role="separator" class="divider"></li>
                        </ul>
                    </li>
                {% else %}
                    <li><a href="/register/">注册</a></li>
                    <li><a href="/login/">登录</a></li>
                {% endif %}

                <!-- Modal -->
                <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
                    <div class="modal-dialog" role="document">
                        <div class="modal-content">
                            <div class="modal-header">
                                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
                                        aria-hidden="true">&times;</span></button>
                                <h4 class="modal-title text-center" id="myModalLabel">修改密码</h4>
                            </div>
                            <div class="modal-body">
                                <div class="row">
                                    <div class="col-md-10 col-md-offset-1">
                                        <div class="form_group">
                                            <label for="">原密码</label>
                                            <input type="password" id="old_password" class="form-control">
                                        </div>
                                        <div class="form_group">
                                            <label for="">新密码</label>
                                            <input type="password" id="new_password" class="form-control">
                                        </div>
                                        <div class="form_group">
                                            <label for="">确认密码</label>
                                            <input type="password" id="re_password" class="form-control">
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="modal-footer">
                                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                                <button type="button" class="btn btn-primary commit">提交</button>
                            </div>
                        </div>
                    </div>
                </div>
            </ul>
        </div><!-- /.navbar-collapse -->
    </div><!-- /.container-fluid -->
</nav>

<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            <div class="panel panel-primary">
                <!-- Default panel contents -->
                <div class="panel-heading">Panel heading</div>
                <div class="panel-body">
                    <p>...</p>
                </div>
            </div>
            <div class="panel panel-info">
                <!-- Default panel contents -->
                <div class="panel-heading">Panel heading</div>
                <div class="panel-body">
                    <p>...</p>
                </div>
            </div>
            <div class="panel panel-success">
                <!-- Default panel contents -->
                <div class="panel-heading">Panel heading</div>
                <div class="panel-body">
                    <p>...</p>
                </div>
            </div>
        </div>
        <div class="col-md-9">
            {% for article_obj in article_list %}
                <div class="media">
                    <h4 class="media-heading"><a href="">{{ article_obj.title }}</a></h4>
                    <div class="media-body">
                        {{ article_obj.desc }}
                    </div>
                    <br>
                    <div class="pull-right">
                        <span>posted @</span>
                        <span>{{ article_obj.create_time| date:'Y-m-d H:i:s' }}&nbsp;&nbsp;</span>
                        <span><a href="">{{ article_obj.blog.userinfo.username }}&nbsp;&nbsp;</a></span>
                        <span>点赞({{ article_obj.up_num }})&nbsp;&nbsp;</span>
                        <span>点踩({{ article_obj.down_num }})&nbsp;&nbsp;</span>
                        <span>评论({{ article_obj.comment_num }})</span>
                    </div>
                </div>
                <hr>
            {% endfor %}
        </div>
    </div>
</div>
</body>
</html>

个人站点实现

def site(request, username):
    # 验证站点名称是否存在
    user_obj = models.UserInfo.objects.filter(username=username).first()
    if not user_obj:
        return render(request, '404.html')

    # 查询当前站点的所有文章
    blog = user_obj.blog
    article_list = models.Article.objects.filter(blog=blog).all()
    return render(request, 'site.html', locals())

推荐阅读