BBS论坛项目

1、项目开发流程

1、互联网项目(产品经理提需求),传统行业项目(客户提需求)
2、项目开发流程
   需求分析(组长,项目经理,产品经理)
   原型设计(产品经理)
   美工UI切图
   设计程序,数据库(上面两个可以跟设计数据库同步操作)
   分任务开发(张三写用户相关,李四写订单相关),多人协同开发(git,svn)
   假设过了三个月所有任务开发完了
   测试(专门的测试)
   上线
3、项目开发模式
   瀑布模式
   敏捷开发

2、博客项目需求

多人博客
博客首页
登录: 图片验证码
注册: 上传头像
自己有自己的个人站点(根据分类,标签,时间,过滤文章)
自己的后台管理
	发表博客(富文本编辑器,xss攻击处理)
	查看,删除
文章分类
随笔档案
文章标签
文章
文章详情
评论(根评论,子评论)
点赞点踩

3、bbs项目表设计

# 表设计(8张)
1、用户表: UserInfo表
2、博客表: Blog表
3、分类表: Category
4、标签表: Tag
5、文章: Article(文章和详情一个表)
6、评论: Comment
7、点赞点踩表: UpAndDown
8、文章标签中间表
    
# 表中字段
1、用户表: UserInfo表(通过继承auth_user扩展)
	phone
	avatar(头像)
    blog(关联字段)
    
2、博客表: Blog表
	title
    name
    style
    
3、分类表: Category
	name
    blog
    
4、标签表: Tag
	name
	blog
	
5、文章: Article(文章和详情一个表)
	title
    desc
    content
    create_time
    blog
    category
    tag(多对多)

6、评论: Comment
	user
    article
    create_time
    content
    
7、点赞点踩表: UpAndDown
	user
    article
    is_up
    create_time

4、数据库表创建及同步

models.py

from django.db import models

from django.contrib.auth.models import AbstractUser


class UserInfo(AbstractUser):
    phone = models.CharField(max_length=32, verbose_name='手机号')
    # upload_to文件上传以后存放的路径
    # FileField本质是varchar类型
    avator = models.FileField(upload_to='avator/', default='avator/default.png')
    blog = models.OneToOneField(to='Blog', on_delete=models.CASCADE, null=True)


class Blog(models.Model):
    title = models.CharField(max_length=32, verbose_name='博客标题')
    name = models.CharField(max_length=32, verbose_name='博客描述')
    style = models.CharField(max_length=32, verbose_name='博客样式')


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


class Category(models.Model):
    name = models.CharField(max_length=32, verbose_name='分类名称')
    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=128, verbose_name='文章描述')
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='文章创建时间')

    # 关联字段
    blog = models.ForeignKey(to='Blog', on_delete=models.CASCADE)
    category = models.ForeignKey(to='Category', on_delete=models.CASCADE)
    tag = models.ManyToManyField(to='Tag', through='TagToArticle', through_fields=('article', 'tag'))


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


class UpAndDown(models.Model):
    user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE)
    article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
    is_up = models.BooleanField(verbose_name='是否是点赞')
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='点赞点踩创建时间')


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=255, verbose_name='评论内容')
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='评论创建时间')
    # 存父评论的id号
    comments = models.ForeignKey(to='self', on_delete=models.CASCADE)

执行迁移文件的两条命令

python manage.py makemigrations
python manage.py migrate

5、注册

5.1、注册form组件

blog_forms.py

from django import forms
from django.forms import widgets

from blog.models import UserInfo


class BlogForm(forms.Form):
    username = forms.CharField(min_length=3, max_length=18, required=True, label='用户名',
                               error_messages={'min_length': '最小为3位', 'max_length': '最大为18位', 'required': '该字段必填'},
                               widget=widgets.TextInput(attrs={'class': 'form-control'}))

    password = forms.CharField(min_length=3, max_length=18, required=True, label='密码',
                               error_messages={'min_length': '最小为3位', 'max_length': '最大为18位', 'required': '该字段必填'},
                               widget=widgets.PasswordInput(attrs={'class': 'form-control'}))

    re_password = forms.CharField(min_length=3, max_length=18, required=True, label='确认密码',
                                  error_messages={'min_length': '最小为3位', 'max_length': '最大为18位', 'required': '该字段必填'},
                                  widget=widgets.PasswordInput(attrs={'class': 'form-control'}))

    email = forms.EmailField(required=True, label='邮箱',
                             error_messages={'min_length': '最小为3位', 'max_length': '最大为18位', 'required': '该字段必填',
                                             'invalid': '邮箱不合法'},
                             widget=widgets.TextInput(attrs={'class': 'form-control'}))

    def clean_username(self):
        username = self.cleaned_data.get('username')
        count = UserInfo.objects.filter(username=username).count()
        if count:
            self.add_error('username', '用户名已存在')
        else:
            return username

    def clean(self):
        pwd = self.cleaned_data.get('password')
        re_pwd = self.cleaned_data.get('re_password')
        if pwd == re_pwd:
            return self.cleaned_data
        else:
            self.add_error('re_password', '两次密码输入不一致')

setting中配置媒体资源

# 由于FileField会自动保存文件,所以默认以MEDIA_ROOT开始往下找,如果没有配,默认以根路径开始
# 以后上传的文件,都是从media路径往下找
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
5.2、注册功能页面
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    {% load static %}
    <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="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1 class="text-center">用户注册</h1>
            <form action="" id="id_form">
                {% csrf_token %}
                {% for form in forms %}
                    <div class="form-group">
                        <label for="{{ form.auto_id }}">{{ form.label }}</label>
                        {{ form }}
                        <span class="pull-right text-danger"></span>
                    </div>
                {% endfor %}
                <div class="form-group">
                    <label for="id_myfile">头像
                        <img src="/static/img/default.png" id="id_img" alt=""
                             style="width: 80px;height: 80px;margin-top: 10px;margin-left: 20px;">
                    </label>

                    <input type="file" id="id_myfile" name="avator" style="display: none;">
                </div>
                <div class="text-center"><input type="button" class="btn btn-danger" id='id_submit' value="提交"></div>
            </form>
        </div>
    </div>
</div>

<script>
    $(function () {
        // 获取头像信息
        $('#id_myfile').on('change', function () {
            var filereader = new FileReader();
            filereader.readAsDataURL($(this)[0].files[0]);
            filereader.onload = function () {
                $('#id_img').attr('src', filereader.result);
            }
        });

        // 检测用户名是否存在
        $('#id_username').on('blur', function () {
            var _this = $(this);
            $.ajax({
                url: '/blog/check_username/',
                method: 'post',
                data: {
                    'username': $('#id_username').val(),
                    'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val()
                },
                success: function (data) {
                    if (data !== 100) {
                        _this.next('span').html(data.msg);
                    }
                }
            })
        });

        // 提交数据
        $('#id_submit').on('click', function () {
            var formdata = new FormData();
            formdata.append('avator', $('#id_myfile')[0].files[0]);
            res = $('#id_form').serializeArray();
            $.each(res, function (key, value) {
                formdata.append(value['name'], value['value'])
            });

            // console.log(formdata);

            $.ajax({
                url: '',
                method: 'post',
                data: formdata,
                processData: false,
                contentType: false,
                success: function (data) {
                    if (data.code == 100) {
                        console.log(data.msg);
                        location.href = data.url;
                    } else {
                        $.each(data.error, function (key, value) {
                            $('#id_' + key).next('span').text(value[0]).parent().addClass('has-error')
                        });

                        setTimeout(function () {
                            $('.text-danger').text('').parent().removeClass('has-error')
                        }, 3000);
                    }
                }
            })
        });
    })
</script>
</body>
</html>
5.3、头像实时显示
// 获取头像信息
$('#myfile').on('change', function () {
	// 借助于文件阅读器
    var filereader = new FileReader();
    // 把图片读到filereader对象中
    filereader.readAsDataURL($(this)[0].files[0]);
    // 文件完全读到文件阅读器以后再执行
    filereader.onload = function () {
        $('#img').attr('src', filereader.result);
    }
});
5.4、注册路由配置

总路由

path('blog/', include(('blog.urls','blog'), namespace='blog')),

blog路由

path('register/', views.Register.as_view(), name='register'),
path('login/', views.Login.as_view(), name='login'),
5.5、注册功能视图
from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from django.views import View
from blog.blog_forms import BlogForm
from blog.models import UserInfo

class Register(View):
    def get(self, request, *args, **kwargs):
        forms = BlogForm()
        return render(request, 'register.html', context={'forms': forms})

    def post(self, request, *args, **kwargs):
        res = {'code': 100, 'msg': 'success'}
        forms = BlogForm(request.POST)
        if forms.is_valid():
            data = forms.cleaned_data
            data.pop('re_password')
            files = request.FILES.get('avator')
            if files:
                data['avator'] = files
            UserInfo.objects.create_user(**data)
            res['url'] = '/blog/login/'
            return JsonResponse(res)
        else:
            res['code'] = 101
            res['msg'] = '数据验证失败'
            res['error'] = forms.errors
            return JsonResponse(res)
5.6、注册功能错误渲染
// 提交数据
$('#id_submit').on('click', function () {
    var formdata = new FormData();
    formdata.append('avator', $('#id_myfile')[0].files[0]);
    res = $('#id_form').serializeArray();
    $.each(res, function (key, value) {
        formdata.append(value['name'], value['value'])
    });

    $.ajax({
        url: '',
        method: 'post',
        data: formdata,
        processData: false,
        contentType: false,
        success: function (data) {
            if (data.code == 100) {
                console.log(data.msg);
                location.href = data.url;
            } else {
                $.each(data.error, function (key, value) {
                    $('#id_' + key).next('span').text(value[0]).parent().addClass('has-error')
                });
				// 过3秒钟,错误信息清除,在匿名函数中执行
                setTimeout(function () {
                    $('.text-danger').text('').parent().removeClass('has-error')
                }, 3000);
            }
        }
    })
});
5.7、用户名变化校验
5.7.1、视图函数
def check_username(request):
    res = {'code': 100, 'msg': None}
    if request.is_ajax():
        username = request.POST.get('username')
        count = UserInfo.objects.filter(username=username).first()
        if count:
            res['code'] = 102
            res['msg'] = '该用户已存在'
    return JsonResponse(res)
5.7.2、用户名变化前端校验
// 检测用户名是否存在
// 当光标不再username控件上就发送ajax请求,去后台查询
$('#id_username').on('blur', function () {
    var _this = $(this);
    $.ajax({
        url: '/blog/check_username/',
        method: 'post',
        data: {
            'username': $('#id_username').val(),
            'csrfmiddlewaretoken': $("[name='csrfmiddlewaretoken']").val()
        },
        success: function (data) {
            if (data !== 100) {
                _this.next('span').html(data.msg);
            }
        }
    })
});

6、登录

6.1、登录页面
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    {% load static %}
    <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="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1 class="text-center">用户登录</h1>
            <form action="" id="form">
                {% csrf_token %}
                <div class="form-group">
                    <label for="id_username">用户名</label>
                    <input type="text" name="id_username" id="id_username" class="form-control">
                </div>

                <div class="form-group">
                    <label for="id_password">密码</label>
                    <input type="password" name="id_password" id="id_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" name="id_code" id="id_code" class="form-control">
                        </div>
                        <div class="col-md-6">
                            <img src="/blog/get_code/" alt="" width="450px" height="30px" id="id_img">
                        </div>
                    </div>
                </div>

                <div class="form-group text-center">
                    <input type="button" id="id_submit" class="btn btn-danger" value="提交">
                </div>
            </form>
        </div>
    </div>
</div>
<script>
    $(function () {
        // 点击切换图形验证码
        $('#id_img').on('click', function () {
            $(this).attr('src', $(this).attr('src') + '?' + Math.random())
        });

        $('#id_submit').on('click', function () {
            formdata = new FormData();
            $.each($('#form').serializeArray(), function (key, value) {
                formdata.append(value.name, value.value)
            });

            $.ajax({
                url: '/blog/login/',
                method: 'post',
                data: formdata,
                processData: false,
                contentType: false,
                success: function (data) {
                    if (data.code !== 100) {
                        alert(data.msg)
                    } else {
                        location.href = data['url']
                    }
                }
            })
        });
    })
</script>
</body>
</html>
6.2、 图片验证码
def get_code(request):
	# 生成一张图片(Image模块下的new函数,返回一个Image对象)
    img = Image.new('RGB', (450, 30), generate_rgb())
    # 把图片放到画板上
    img_draw = ImageDraw.Draw(img)
    img_font = ImageFont.truetype('./static/font/ss.TTF', 25)

    code_str = ''
    for i in range(5):
        lower_char = chr(random.randint(97, 122))
        upper_char = chr(random.randint(65, 90))
        num_char = str(random.randint(0, 9))
        res = random.choice([lower_char, upper_char, num_char])
        code_str += res
        img_draw.text((i * 70 + 40, i), str(res), generate_rgb(), font=img_font)

        for i in range(170):
            img_draw.point([random.randint(0, 450), random.randint(0, 30)], fill=generate_rgb())
    # 把验证码存到session中
    request.session['code'] = code_str
    print(code_str)
    # 图片保存(写到内存中)
    fp = BytesIO()
    img.save(fp, 'png')
    # 把内容全取出来
    data = fp.getvalue()
    return HttpResponse(data)
6.3、登录功能
6.3.1、视图函数
class Login(View):
    def get(self, request, *args, **kwargs):
        return render(request, 'login.html')

    def post(self, request, *args, **kwargs):
        res = {'code': 100, 'msg': None}
        if request.is_ajax():
            username = request.POST.get('id_username')
            password = request.POST.get('id_password')
            code = request.POST.get('id_code')
            if request.session['code'].lower() == code.lower():
                user = auth.authenticate(username=username, password=password)
                if user:
                    auth.login(request, user)
                    res['msg'] = '登录成功'
                    res['url'] = '/blog/index/'
                else:
                    res['code'] = 103
                    res['msg'] = '账号或密码有误'
            else:
                res['code'] = 101
                res['msg'] = '验证码错误'
            return JsonResponse(res)
        else:
            res['code'] = 102
            res['msg'] = '请求有误'
            return JsonResponse(res)
6.3.2、模板页面
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    {% load static %}
    <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="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1 class="text-center">用户登录</h1>
            <form action="" id="form">
                {% csrf_token %}
                <div class="form-group">
                    <label for="id_username">用户名</label>
                    <input type="text" name="id_username" id="id_username" class="form-control">
                </div>

                <div class="form-group">
                    <label for="id_password">密码</label>
                    <input type="password" name="id_password" id="id_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" name="id_code" id="id_code" class="form-control">
                        </div>
                        <div class="col-md-6">
                            <img src="/blog/get_code/" alt="" width="450px" height="30px" id="id_img">
                        </div>
                    </div>
                </div>

                <div class="form-group text-center">
                    <input type="button" id="id_submit" class="btn btn-danger" value="提交">
                </div>
            </form>
        </div>
    </div>
</div>
<script>
    $(function () {
        // 点击切换图形验证码
        $('#id_img').on('click', function () {
            $(this).attr('src', $(this).attr('src') + '?' + Math.random())
        });

        $('#id_submit').on('click', function () {
            formdata = new FormData();
            $.each($('#form').serializeArray(), function (key, value) {
                formdata.append(value.name, value.value)
            });

            $.ajax({
                url: '/blog/login/',
                method: 'post',
                data: formdata,
                processData: false,
                contentType: false,
                success: function (data) {
                    if (data.code !== 100) {
                        alert(data.msg)
                    } else {
                        location.href = data['url']
                    }
                }
            })
        });
    })
</script>
</body>
</html>

7、退出

7.1、视图函数
def logout(request):
    auth.logout(request)
    return redirect(reverse('blog:index'))

8、首页

8.1、博客首页导航条
8.1.1、视图函数
class Index(View):
    def get(self, request, *args, **kwargs):
        return render(request, 'index.html')
8.1.2、模板页面
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    {% load static %}
    <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="container-fluid">
    <div class="row">
        <div class="header">
            <nav class="navbar navbar-default">
                <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="{% url 'blog:index' %}">博客公园</a>
                    </div>

                    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                        <ul class="nav navbar-nav">
                            <li><a href="{% url 'blog:index' %}">首页</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="Search">
                            </div>
                            <button type="submit" class="btn btn-default">Submit</button>
                        </form>
                        {% if request.user.is_authenticated %}
                            <ul class="nav navbar-nav navbar-right">
                                <li><a href="#">{{ request.user.username }}</a></li>
                                <li class="dropdown">
                                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
                                       aria-haspopup="true" aria-expanded="false">更多<span class="caret"></span></a>
                                    <ul class="dropdown-menu">
                                        <li><a href="#" id="change_pwd" data-toggle="modal"
                                               data-target=".bs-example-modal-lg">修改密码</a></li>
                                        <li><a href="#">修改头像</a></li>
                                        <li><a href="{% url 'blog:logout' %}">退出</a></li>
                                    </ul>
                                </li>
                            </ul>
                        {% else %}
                            <ul class="nav navbar-nav navbar-right">
                                <li><a href="{% url 'blog:register' %}">注册</a></li>
                                <li><a href="{% url 'blog:login' %}">登录</a></li>
                            </ul>
                        {% endif %}

                    </div><!-- /.navbar-collapse -->
                </div><!-- /.container-fluid -->
            </nav>
        </div>
        <div class="body">
            <div class="content_left col-md-2">111</div>
            <div class="content_middle col-md-7">222</div>
            <div class="content_right col-md-3">333</div>
        </div>
    </div>
</div>
</body>
</html>
8.2、首页页面搭建(轮播图)
8.2.1、视图函数
def get_banner(request):
    banner_info = [
        {'img_url': '/static/img/glasses-banner1.jpg', 'desc': '广告位出租', 'target_url': 'http://www.baidu.com'},
        {'img_url': '/static/img/glasses-banner2.jpg', 'desc': '点我买苹果手机12', 'target_url': 'http://www.sina.com'},
        {'img_url': '/static/img/glasses-banner3.jpg', 'desc': '北京到上海特价飞机票', 'target_url': 'http://www.bilibi.com'},
    ]
    return JsonResponse(banner_info, safe=False)
8.2.2、前端
<div class="content_middle col-md-7">
    <div class="lbt">
        <div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
            <!-- Indicators -->
            <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>
                <li data-target="#carousel-example-generic" data-slide-to="2"></li>
            </ol>

            <!-- Wrapper for slides -->
            <div class="carousel-inner" role="listbox">
                <div class="item active">
                    <img class="banner-img" src="xxx" alt="...">
                    <div class="carousel-caption">
                        <a href="" class="link_a">xxx</a>
                    </div>
                </div>
                <div class="item">
                    <img class="banner-img" src="xxx" alt="...">
                    <div class="carousel-caption">
                        <a href="" class="link_a">xxx</a>
                    </div>
                </div>
                <div class="item">
                    <img class="banner-img" src="xxx" alt="...">
                    <div class="carousel-caption">
                        <a href="" class="link_a">xxx</a>
                    </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">
        {% for article in article_list %}
            <div class="ever" style="margin-top: 15px">
                <h4 class="media-heading">{{article.title}}</h4>
                <div class="media">
                    <div class="media-left">
                        <a href="#">
                            <img class="media-object" src="media/{{ article.blog.userinfo.avator }}" alt="" height="60px;" width="60px;">
                        </a>
                    </div>
                    <div class="media-body">
                        {{ article.desc }}
                    </div>
                    <div class="article_bottom">
                        <span><a href="{% url 'blog:my_site' article.blog.userinfo.username %}">{{ article.blog.userinfo.username }}</a></span>
                        <span>{{ article.create_time|date:'Y-m-d H:i:s' }}</span>
                        <span class="glyphicon glyphicon-thumbs-up">&nbsp;0</span>
                        <span class="glyphicon glyphicon-briefcase">&nbsp;10</span>
                    </div>
                </div>
                <hr>
            </div>
        {% endfor %}
    </div>
</div>

<script>
$.ajax({
    url: '/blog/get_banner/',
    method: 'post',
    data: {'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val()},
    success: function (data) {
        $.each(data, function (key, value) {
            $('.banner-img')[key].src = value.img_url;
            $('.link_a')[key].href = value.target_url;
            $($('.link_a')[key]).html(value['desc']);
        })
    }
})
</script>
8.3、首页文章显示
8.3.1、视图函数
class Index(View):
    def get(self, request, *args, **kwargs):
        banner = ['/static/img/glasses-banner1.jpg', '/static/img/glasses-banner2.jpg',
                  '/static/img/glasses-banner3.jpg']

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

    def post(self, request, *args, **kwargs):
        pass
8.3.2、前端
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
    <style>
        .article_bottom span {
            margin-right: 10px;
        }
    </style>
    <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="container-fluid">
    <div class="row">
        <div class="header">
            <nav class="navbar navbar-default">
                <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="/">博客公园</a>
                    </div>

                    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                        <ul class="nav navbar-nav">
                            <li><a href="/">首页</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="Search">
                            </div>
                            <button type="submit" class="btn btn-default">Submit</button>
                        </form>
                        {% if request.user.is_authenticated %}
                            <ul class="nav navbar-nav navbar-right">
                                <li><a href="#">{{ request.user.username }}</a></li>
                                <li class="dropdown">
                                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
                                       aria-haspopup="true" aria-expanded="false">更多<span class="caret"></span></a>
                                    <ul class="dropdown-menu">
                                        <li><a href="#" id="change_pwd" data-toggle="modal"
                                               data-target=".bs-example-modal-lg">修改密码</a></li>
                                        <li><a href="#">修改头像</a></li>
                                        <li><a href="{% url 'blog:logout' %}">退出</a></li>
                                    </ul>
                                </li>
                            </ul>
                        {% else %}
                            <ul class="nav navbar-nav navbar-right">
                                <li><a href="{% url 'blog:register' %}">注册</a></li>
                                <li><a href="{% url 'blog:login' %}">登录</a></li>
                            </ul>
                        {% endif %}

                    </div><!-- /.navbar-collapse -->
                </div><!-- /.container-fluid -->
            </nav>
        </div>
        <div class="body">
            <div class="content_left col-md-2">
                <div class="panel panel-primary">
                    <div class="panel-heading">
                        <h3 class="panel-title">招商广告</h3>
                    </div>
                    <div class="panel-body">
                        招商广告栏位出租
                    </div>
                </div>

                <div class="panel panel-danger">
                    <div class="panel-heading">
                        <h3 class="panel-title">北京时间</h3>
                    </div>
                    <div class="panel-body">
                        北京时间x点x分
                    </div>
                </div>

                <div class="panel panel-primary">
                    <div class="panel-heading">
                        <h3 class="panel-title">招商广告</h3>
                    </div>
                    <div class="panel-body">
                        招商广告栏位出租
                    </div>
                </div>

                <div class="panel panel-danger">
                    <div class="panel-heading">
                        <h3 class="panel-title">北京时间</h3>
                    </div>
                    <div class="panel-body">
                        北京时间x点x分
                    </div>
                </div>

            </div>
            <div class="content_middle col-md-7">
                <div class="lbt">
                    <div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
                        <!-- Indicators -->
                        <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>
                            <li data-target="#carousel-example-generic" data-slide-to="2"></li>
                        </ol>

                        <!-- Wrapper for slides -->
                        <div class="carousel-inner" role="listbox">
                            <div class="item active">
                                <img class="banner-img" src="xxx" alt="...">
                                <div class="carousel-caption">
                                    <a href="" class="link_a">xxx</a>
                                </div>
                            </div>
                            <div class="item">
                                <img class="banner-img" src="xxx" alt="...">
                                <div class="carousel-caption">
                                    <a href="" class="link_a">xxx</a>
                                </div>
                            </div>
                            <div class="item">
                                <img class="banner-img" src="xxx" alt="...">
                                <div class="carousel-caption">
                                    <a href="" class="link_a">xxx</a>
                                </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">
                    {% for article in article_list %}
                        <div class="ever" style="margin-top: 15px">
                            <h4 class="media-heading">{{article.title}}</h4>
                            <div class="media">
                                <div class="media-left">
                                    <a href="#">
                                        <img class="media-object" src="media/{{ article.blog.userinfo.avator }}" alt="" height="60px;" width="60px;">
                                    </a>
                                </div>
                                <div class="media-body">
                                    {{ article.desc }}
                                </div>
                                <div class="article_bottom">
                                    <span><a href="{% url 'blog:my_site' article.blog.userinfo.username %}">{{ article.blog.userinfo.username }}</a></span>
                                    <span>{{ article.create_time|date:'Y-m-d H:i:s' }}</span>
                                    <span class="glyphicon glyphicon-thumbs-up">&nbsp;0</span>
                                    <span class="glyphicon glyphicon-briefcase">&nbsp;10</span>
                                </div>
                            </div>
                            <hr>
                        </div>
                    {% endfor %}
                </div>
            </div>
            <div class="content_right col-md-3">
                <div class="panel panel-primary">
                    <div class="panel-heading">
                        <h3 class="panel-title">招商广告</h3>
                    </div>
                    <div class="panel-body">
                        招商广告栏位出租
                    </div>
                </div>

                <div class="panel panel-danger">
                    <div class="panel-heading">
                        <h3 class="panel-title">北京时间</h3>
                    </div>
                    <div class="panel-body">
                        北京时间x点x分
                    </div>
                </div>
            </div>
            <div class="content_right col-md-3">
                <div class="panel panel-primary">
                    <div class="panel-heading">
                        <h3 class="panel-title">招商广告</h3>
                    </div>
                    <div class="panel-body">
                        招商广告栏位出租
                    </div>
                </div>

                <div class="panel panel-danger">
                    <div class="panel-heading">
                        <h3 class="panel-title">北京时间</h3>
                    </div>
                    <div class="panel-body">
                        北京时间x点x分
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

{% include 'change_pwd.html' %}
<script>
    $(function () {
        // 校验密码是否正确
        $('#old_password').blur(function (data) {
            var _this = $(this);
            $.ajax({
                url: '/blog/check_old_password/',
                method: 'post',
                data: {'old_password': $(this).val(), 'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val()},
                success: function (data) {
                    if (data.code !== 100) {
                        _this.next('span').html(data.msg).parent().addClass('has-error')
                    } else {
                        _this.next('span').html('').parent().removeClass('has-error')
                    }
                }
            })
        });

        // 修改密码
        $('#btn').on('click', function () {
            if ($('div').hasClass('has-error')) {
                return;
            }
            $.ajax({
                url: '/blog/update_password/',
                method: 'post',
                data: {
                    'new_password': $('#new_password').val(),
                    're_new_password': $('#re_new_password').val(),
                    'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val(),
                },
                success: function (data) {
                    if (data.code == 100) {
                        alert(data.msg);
                        location.reload();
                    } else {
                        $('#re_new_password').next().html(data.msg).parent().addClass('has-error')
                    }
                }
            })
        });

        // 获取banner图
        $.ajax({
            url: '/blog/get_banner/',
            method: 'post',
            data: {'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val()},
            success: function (data) {
                $.each(data, function (key, value) {
                    $('.banner-img')[key].src = value.img_url;
                    $('.link_a')[key].href = value.target_url;
                    $($('.link_a')[key]).html(value['desc']);
                })
            }
        })
    })
</script>
</body>
</html>

9、admin后台管理

# 路由
path('admin/', admin.site.urls),

# admin.py中注册
from django.contrib import admin
from blog import models
admin.site.register(models.UserInfo)
admin.site.register(models.Article)
admin.site.register(models.Blog)
admin.site.register(models.Category)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)
admin.site.register(models.Tag)
admin.site.register(models.TagToArticle)

# setting中配置国际化
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False

# 表名显示中文
class Meta:
    verbose_name_plural='标签表'
# 数据显示中文
def __str__(self):
   return self.name

10、media及头像显示

1、用户上传的头像能够在浏览器中访问到
2、默认情况下,static下的都能访问
3、media文件夹下的图片不能访问,需要手动打开
4、使用方式
	项目根路径下新建media文件夹
    setting中配置MEDIA_ROOT=os.path.join(BASE_DIR,'media')
    路由中配置:
    from django.urls import re_path
	from django.views.static import serve
	from django.conf import settings
    re_path('^media/(?P<path>.*)$', serve,{'document_root': settings.MEDIA_ROOT}),

11、个人站点文章显示

11.1、路由
# 个人站点 访问tag/category/achive
re_path(r'^my_site/(?P<name>\w+)/(?P<query>tag|category|achive)/(?P<condition>.*).html$', views.my_site,name='my_site'),
re_path(r'^my_site/(?P<name>\w+)/', views.my_site, name='my_site'),
11.2、视图函数
def my_site(request, name, **kwargs):
    obj = UserInfo.objects.filter(username=name).first()
    if obj:

        article_list = models.Article.objects.filter(blog__userinfo__username=name)

        query = kwargs.get('query', None)
        if query == 'category':
            condition = kwargs.get('condition')
            article_list = article_list.filter(category_id=condition)
        elif query == 'tag':
            condition = kwargs.get('condition')
            article_list = article_list.filter(tagtoarticle__tag_id=condition)
        elif query == 'achive':
            year, month = kwargs.get('condition').split('/')
            article_list = article_list.filter(create_time__year=year, create_time__month=month)

        return render(request, 'my_site.html', locals())
    else:
        return render(request, 'error.html')
11.3、前端
<!--my_site.html-->
{% extends 'base.html' %}

{% block content %}
    <div class="article">
        {% for article in article_list %}
            <div class="ever" style="margin-top: 15px">
                <h4 class="media-heading"><a href="{% url 'blog:article_detail' name article.id %}">{{ article.title }}</a></h4>
                <div class="media">
                    <div class="media-body">
                        {{ article.desc }}
                    </div>
                    <div class="article_bottom pull-right">
                        @posted by &nbsp;
                        <span>{{ article.blog.userinfo.username }}</span>
                        <span>{{ article.create_time|date:'Y-m-d H:i:s' }}</span>
                        <span><i class="fa fa-hand-pointer-o fa-lg"></i>&nbsp;{{ article.up_num }}</span>
                        <span><i class="fa fa-comment fa-lg"></i>&nbsp;{{ article.comment_num }}</span>
                        <span><a href="">编辑</a></span>
                    </div>
                </div>
                <hr>
            </div>
        {% endfor %}
    </div>
{% endblock %}

<!--error.html-->
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
    <title>Document</title>
</head>
<body>
<div class="error-panel server-error error-404 text-center">
    <img src="//static.hdslb.com/error/very_sorry.png">
    <div style="text-align: center; padding: 0 0 40px 0;">
        <a class="rollback-btn" style="padding: 0 20px; float: none;">返回上一页</a>
    </div>
</div>
</body>
</html>

12、点赞点踩功能

12.1、视图函数
def up_and_down(request):
    res = {'code': 100, 'msg': None}
    if request.is_ajax():
        if request.user.is_authenticated:
            article_id = request.POST.get('article_id')
            obj = models.UpAndDown.objects.filter(user_id=request.user.id, article_id=article_id).first()
            if obj:
                res['code'] = 103
                res['msg'] = '您已经提交过'
            else:
                # 写数据库
                is_up = request.POST.get('is_up')
                is_up = json.loads(is_up)
                # 点赞点踩表增加数据
                models.UpAndDown.objects.create(is_up=is_up, user_id=request.user.id, article_id=article_id)
                # 文章表点赞点踩数量加1
                try:
                    with transaction.atomic():
                        if is_up:
                            models.Article.objects.filter(id=article_id).update(up_num=F('up_num') + 1)
                            res['msg'] = '点赞成功'
                        else:
                            models.Article.objects.filter(id=article_id).update(down_num=F('down_num') + 1)
                            res['msg'] = '点踩成功'
                        res['code'] = 100
                except Exception as e:
                    res['code'] = 104
                    res['msg'] = '数据异常'
        else:
            res['code'] = 102
            res['msg'] = '<span>请<a style="color="red"" href="/blog/login/">登录</a>后再操作</span>'
    else:
        res['code'] = 101
        res['msg'] = '非法操作'
    return JsonResponse(res)
12.2、前端
{% extends 'base.html' %}

{% block ext_css %}
    {% load static %}
    <link rel="stylesheet" href="{% static 'css/my_css.css' %}">
{% endblock %}

{% block content %}
    {% csrf_token %}
    <h2>{{ article.title }}</h2>
    <hr>
    <div class="content" style="text-indent:2em;">{{ article.content }}</div>
    {# 点赞点踩 #}
    <div class="upanddown clearfix">
        <div id="div_digg">
            <div class="diggit up_or_down">
                <span class="diggnum" id="digg_count">{{ article.up_num }}</span>
            </div>
            <div class="buryit up_or_down">
                <span class="burynum" id="bury_count">{{ article.down_num }}</span>
            </div>
            <div class="clear"></div>
            <div class="diggword" id="digg_tips" style="color: red;">
            </div>
        </div>
    </div>
    <hr>
    {# 评论 #}
    <div class="comment_list">
        评论列表
        <ul class="list-group">
            {% for comment in comment_list %}
                <li class="list-group-item">
                    <div>
                        <span>#{{ forloop.counter }}楼</span>
                        <span>{{ comment.create_time|date:'Y-m-d H:i:s' }}</span>
                        <span><a
                                href="{% url 'blog:my_site' comment.user.username %}">{{ comment.user.username }}</a></span>
                        <span class="replay pull-right" parent="{{ comment.pk }}"
                              username="{{ comment.user.username }}"><a>回复</a></span>

                        {% if comment.comments_id %}
                            <p>@{{ comment.article.blog.userinfo.username }}</p>
                            <p><span>{{ comment.content }}</span></p>
                        {% else %}
                            <p><span>{{ comment.content }}</span></p>
                        {% endif %}

                    </div>
                </li>
            {% endfor %}
        </ul>
    </div>
    <hr>
    {% if request.user.is_authenticated %}
        <div class="comment" style="margin-top: 15px;">
            <div class="form-group">
                <div>发表评论</div>
                <textarea name="content" id="content_textarea" cols="100" rows="15"></textarea>
            </div>
            <div class="form-group">
                <button class="btn btn-info" id="id_btn">提交</button>
            </div>
        </div>
    {% else %}
        <div>登录后才能发表评论,立即 <a href="{% url 'blog:login' %}">登录</a><a href="{% url 'blog:register' %}">注册</a>, 访问 <a
                href="/">网站首页</a></div>
    {% endif %}

{% endblock %}

{% block ext_js %}
    <script>
        $(function () {
            parent_id = '';
            $('.up_or_down').on('click', function () {
                var _this = $(this);
                is_up = true;
                if ($(this).children().hasClass('burynum')) {
                    is_up = false;
                }
                $.ajax({
                    url: '/blog/up_and_down/',
                    method: 'post',
                    data: {
                        article_id: '{{ article.id }}',
                        is_up: is_up,
                        csrfmiddlewaretoken: $('input[name="csrfmiddlewaretoken"]').val(),
                    },
                    success: function (data) {
                        if (data.code == 100) {
                            num = Number(_this.children('span').html()) + 1;
                            _this.children('span').html(num);
                        }
                        $('#digg_tips').html(data.msg);
                    }
                })
            });

            $('#id_btn').on('click', function () {
                let content = $('#content_textarea').val();
                if (parent_id) {
                    // 子评论
                    str = content.indexOf('\n') + 1;
                    content = content.slice(str);
                }

                $.ajax({
                    url: '/blog/commit_comment/',
                    method: 'post',
                    data: {
                        article_id: '{{ article.id }}',
                        content: content,
                        parent_id: parent_id,
                        csrfmiddlewaretoken: $('input[name="csrfmiddlewaretoken"]').val(),
                    },
                    success: function (data) {
                        content_text = '';
                        var username = data.username;
                        var content = $('#content_textarea').val();
                        if (data.code == 100) {

                            if (parent_id) {
                                var parent_name = data.parent_name;
                                content_text = `<li class="list-group-item">
                                                <div>
                                                    <span>${username}</span>
                                                </div>
                                                <p>${parent_name}</p>
                                                <div>
                                                    ${content}
                                                </div>
                                            </li>`;
                            } else {
                                content_text = `<li class="list-group-item">
                                                <div>
                                                    <span>${username}</span>
                                                </div>
                                                <div>
                                                    ${content}
                                                </div>
                                            </li>`;
                            }
                            $('#content_textarea').val('');
                            $('.list-group').append(content_text);
                            parent_id = '';
                        }
                    }
                })
            });

            // replay功能
            $('.replay').on('click', function () {
                username = $(this).attr('username');
                parent_id = $(this).attr('parent');
                replay = `@${username}\n`;
                $('#content_textarea').html(replay).focus();
            });
        });
    </script>
{% endblock %}

13、评论功能

13.1、视图函数
def commit_comment(request):
    res = {'code': 100, 'msg': '评论成功'}
    if request.is_ajax():
        if request.user.is_authenticated:
            parent_id = request.POST.get('parent_id')
            user_id = request.user.id
            article_id = request.POST.get('article_id')
            content = request.POST.get('content')

            try:
                with transaction.atomic():
                    models.Comment.objects.create(user_id=user_id, content=content, article_id=article_id,
                                                  comments_id=parent_id)
                    article = models.Article.objects.filter(id=article_id).update(comment_num=F('comment_num') + 1)
                    res['username'] = request.user.username
                    if parent_id:
                        old_user_id = models.Comment.objects.filter(pk=parent_id).first().user_id
                        res['parent_name'] = models.UserInfo.objects.filter(pk=old_user_id).first().username

                    # from django.core.mail import send_mail
                    #
                    # send_mail('您的文章{}被评论了'.format(article.title), '{}评论了{}'.format(settings.EMAIL_HOST_USER,
                    #                                                                content), settings.EMAIL_HOST_USER,
                    #           models.UserInfo.objects.filter(pk=old_user_id).first().email)

            except Exception as e:
                print(e)
                res['code'] = 103
                res['msg'] = '内部错误'
        else:
            res['code'] = 102
            res['msg'] = '用户未登录'
    else:
        res['code'] = 101
        res['msg'] = '非法操作'
    return JsonResponse(res)
13.2、前端

同12.2章节

14、后台管理页面搭建

14.1、base.html
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>后台页面</title>
    {% block ext_css %}

    {% endblock %}
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
    <link rel="stylesheet" href="{% static 'font-awesome-4.7.0/css/font-awesome.min.css' %}">
    <script src="{% static 'jquery-3.3.1/jquery-3.3.1.min.js' %}"></script>
    <link rel="stylesheet" href="{% static 'bootstrap/js/bootstrap.min.js' %}">
</head>
<body>
<div class="header">
    <nav class="navbar navbar-default navbar-inverse">
        <div class="container-fluid">
            <div class="navbar-header">
                <a class="navbar-brand" href="#">后台管理</a>
            </div>

            <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 class=""><a href="{% url 'blog:update_avator' %}">修改头像 <span class="sr-only">(current)</span></a></li>
                </ul>
            </div><!-- /.navbar-collapse -->
        </div><!-- /.container-fluid -->
    </nav>
</div>
</div>
<div class="container-fluid">
    <div class="row">
        <div class="left_content col-md-3">
            <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="{% url 'blog:create_article' %}">新增文章</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">
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="col-md-9">
            <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 content %}

                        {% 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>
{% block ext_js %}

{% endblock %}
</body>
</html>
14.2、后台首页
{% extends 'backend/base.html' %}
{% block content %}
    <table class="table table-hover table-striped">
        <thead>
        <tr>
            <th>标题</th>
            <th>评论数</th>
            <th>点赞数</th>
            <th>操作</th>
            <th>操作</th>
        </tr>
        </thead>
        <tbody>
        {% for article in article_list %}
            <tr>
                <td>
                    <a href="/{{ article.blog.userinfo.username }}/article/{{ article.pk }}.html">{{ article.title }}</a>
                </td>
                <td>{{ article.comment_num }}</td>
                <td>{{ article.up_num }}</td>
                <td><a href="{% url 'blog:update_article' article.id %}">编辑</a></td>
                <td><a href="">删除</a></td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
{% endblock %}
14.3、视图函数
def admin_index(request):
    article_list = models.Article.objects.filter(blog=request.user.blog).all()
    return render(request, 'backend/admin_index.html', locals())

15、新增文章,处理xss攻击

15.1、前端
{% extends 'backend/base.html' %}

{% block content %}
    <form action="" method="post">
        {% csrf_token %}
        <div class="form-group">
            <label for="">文章标题</label>
            <input type="text" class="form-control" name="title" value="{{ article.title }}">
        </div>

        <div class="form-group">
            <label for="">文章内容</label>
            <p><textarea name="content" id="editor_id" cols="30" rows="10">{{ article.content }}</textarea></p>
        </div>

        <div class="form-group">
            <label for="">分类</label>
            {% for category in category_list %}
                <input type="radio" name="category_id" value="{{ category.id }}">{{ category.name }}
            {% endfor %}
        </div>

        <div class="form-group">
            <label for="">标签</label>
            {% for tag in tag_list %}
                <input type="checkbox" name="tag_id" value="{{ tag.id }}">{{ tag.name }}
            {% endfor %}
        </div>

        <div class="text-center">
            <button class="btn btn-primary">创建文章</button>
        </div>
    </form>
{% endblock %}
15.2、视图函数
def create_article(request):
    if request.method == 'GET':
        category_list = models.Category.objects.filter(blog=request.user.blog).all()
        tag_list = models.Tag.objects.filter(blog=request.user.blog).all()
        return render(request, 'backend/create_article.html', locals())
    else:
        title = request.POST.get('title')
        content = request.POST.get('content')
        category_id = request.POST.get('category_id')
        tag_id = request.POST.getlist('tag_id')

        soup = BeautifulSoup(content, 'html.parser')
        desc = soup.text[0:90]
        res_script = soup.find_all('script')
        for script in res_script:
            script.decompose()

        article = models.Article.objects.create(title=title, content=str(soup), description=desc, blog=request.user.blog,
                                                category_id=category_id)

        ll = []
        for tag in tag_id:
            ll.append(models.TagToArticle(tag_id=tag, article_id=article.pk))
        models.TagToArticle.objects.bulk_create(ll)
        return redirect(reverse('blog:admin_index'))

16、修改文章

16.1、前端
{% extends 'backend/base.html' %}

{% block content %}
    <form action="{% url 'blog:update_article' article.id %}" method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <div class="form-group">
            <label for="">文章标题</label>
            <input type="text" class="form-control" name="title" value="{{ article.title }}">
        </div>

        <div class="form-group">
            <label for="">文章描述</label>
            <input type="text" class="form-control" name="desc" value="{{ article.desc }}">
        </div>

        <div class="form-group">
            <label for="">文章内容</label>
            <textarea name="content" id="editor_id" cols="30" rows="10">{{ article.content }}</textarea>
        </div>
        <div class="text-center">
            <button class="btn btn-primary">修改</button>
        </div>
    </form>
{% endblock %}

{% block ext_js %}
    {% load static %}
    <script charset="utf-8" src="{% static 'kindeditor/kindeditor-all.js' %}"></script>
    <script>
        KindEditor.ready(function (K) {
            window.editor = K.create('#editor_id', {
                width: '100%',
                height: '500px',
                uploadJson: '/blog/upload_img/',
                filePostName: 'myfile',
                extraFileUploadParams: {
                    csrfmiddlewaretoken: '{{ csrf_token }}',
                }
            });
        });
    </script>
{% endblock %}
16.2、视图函数
def update_article(request, id):
    if request.method == 'GET':
        article = models.Article.objects.get(pk=id)
        return render(request, 'backend/update_article.html', locals())
    else:
        title = request.POST.get('title')
        desc = request.POST.get('desc')
        content = request.POST.get('content')
        models.Article.objects.filter(pk=id).update(title=title, description=desc, content=content)
        return redirect(reverse('blog:admin_index'))

17、修改头像

17.1、前端
{% extends 'backend/base.html' %}

{% block content %}
    {% csrf_token %}
    <div class="form-group">
        <label for="">原始头像</label>
        <img src="/media/{{ user.avator }}" alt="" width="80" height="80">
    </div>

    <div class="form-group">
        <label for="upload_avator">上传新头像
            <img src="/media/{{ user.avator }}" alt="" width="80" height="80" id="id_img">
        </label>
        <input type="file" name="myfile" value="上传头像" id="upload_avator" style="display: none">
    </div>

    <div class="text-center">
        <button class="btn btn-primary" id="id_btn">修改头像</button>
    </div>

{% endblock %}

{% block ext_js %}
    {% load static %}
    <script src="{% static 'jquery-3.3.1/jquery-3.3.1.min.js' %}"></script>

    <script>
        $(function () {
            $('#upload_avator').on('change', function () {
                filereader = new FileReader();
                formdata = new FormData();
                filereader.readAsDataURL($(this)[0].files[0]);
                filereader.onload = function () {
                    $('#id_img').attr('src', filereader.result);
                    formdata.append('avator', $('#id_img').attr('src'));
                }
            });

            $('#id_btn').on('click', function () {
                formdata.append('myfile', $('#upload_avator')[0].files[0]);
                formdata.append('csrfmiddlewaretoken', $('input[name="csrfmiddlewaretoken"]').val());

                $.ajax({
                    url: '',
                    method: 'post',
                    data: formdata,
                    contentType: false,
                    processData: false,
                    success: function (data) {
                        if (data.code == 100) {
                            window.location.href = data.url
                        } else {
                            alert(data.msg)
                        }
                    }
                })
            });
        })
    </script>
{% endblock %}
17.2、视图函数
@login_required(login_url='/blog/login/')
def update_avator(request):
    res = {'code': 100, 'msg': 'success'}
    if request.is_ajax():
        try:
            request.user.avator = request.FILES.get('myfile')
            request.user.save()
            res['url'] = reverse('blog:update_avator')
        except Exception as e:
            res['code'] = 101
            res['msg'] = 'error'
        return JsonResponse(res)
    else:
        return render(request, 'update_avator.html')
  • 17
    点赞
  • 120
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值