DjangoBBS项目功能拆分

Posted guyouyin123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DjangoBBS项目功能拆分相关的知识,希望对你有一定的参考价值。

1、随机验证码

技术图片

url(r'^get_code/', views.get_code, name='get_code'),

# 获取随机3个0-255数
def get_random():
    """
    :return: 返回0-255三个随机数,元组
    """
    return random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)


# 获取验证码
def get_code(request):
    # 1.产生一张随机颜色的图片
    img_obj = Image.new('RGB', (350, 35), get_random())
    # 2.产生一只在图片上的画笔
    img_draw = ImageDraw.Draw(img_obj)
    # 3.产生字体样式
    img_font = ImageFont.truetype(r'staticfontfont.ttf', 35)
    io_obj = BytesIO()

    # 产生5个随机验证码
    code = ''
    for i in range(5):
        upper_str = chr(random.randint(65, 90))  # 大写字母
        lower_str = chr(random.randint(97, 122))  # 小写字母
        random_int = str(random.randint(0, 9))  # 数字
        # 随机取一个
        temp_str = random.choice([upper_str, lower_str, random_int])
        # 写在图片上,位置,内容,颜色,字体
        img_draw.text((45 + i * 60, -2), temp_str, get_random(), font=img_font)
        # 储存
        code += temp_str
    print(code)
    img_obj.save(io_obj, 'png')
    request.session['code'] = code
    return HttpResponse(io_obj.getvalue())

前端代码:

<img src="/get_code/" alt="图片验证码" id="id_img">

js代码:

原理:src改变,立马刷新。点击一次图片,给url添加一个?号

$('#id_img').click(function () {
        var oldSrc = $(this).attr('src');
        $(this).attr('src', oldSrc += '?')
    });

2、注册功能

技术图片

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css">
    <script src="https://cdn.bootcss.com/sweetalert/2.0.0/sweetalert.min.js"></script>
    <link rel="https://cdn.bootcss.com/sweetalert/1.1.3/sweetalert.min.css">
    {% load static %}
    <link rel="stylesheet" href="{% static '/bootstrap-3.3.7-dist/css/bootstrap.min.css'%}">
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
<div class="container">
    <div class="row">
        <h2 class="text-center">注册页面</h2>
        <div class="col-md-8 col-md-offset-2">
            <form id="myform">
                {% csrf_token %}

                {% for form in form_obj %}
                    <div class="form-group">
                        <label for="{{ form.id_for_label }}">{{ form.label }}</label>
                        {{ form }}
                        <span style="color: red" class="pull-right"></span>
                    </div>
                {% endfor %}

                <div class="form-group">
                    <label for="id_avatar">头像
                        <img src="/static/images/default.jpg" alt="" width="100" style="margin-left: 10px" id="id_img">
                    </label>
                    <input type="file" name="myfile" id="id_avatar">
                </div>
                <a href="/login/"><input type="button" value="登录" class="btn btn-success pull-left"></a>
                &nbsp;&nbsp;<input type="button" value="注册" class="btn btn-danger" id="id_submit">

            </form>

        </div>
    </div>
</div>

<script>
    $('#id_avatar').change(function () {
        // 1 先获取用户上传的头像文件
        var avatarFile = $(this)[0].files[0];
        // 2 利用文件阅读器对象
        var myFileReader = new FileReader();
        // 3 将文件交由阅读器对象读取
        myFileReader.readAsDataURL(avatarFile);
        // 4 修改img标签的src属性  等待文件阅读器对象读取文件之后再操作img标签
        myFileReader.onload = function(){
            $('#id_img').attr('src',myFileReader.result)
        }

    });

    // 点击按钮触发ajax提交动作
    $('#id_submit').on('click',function () {
        // 1 先生成一个内置对象 FormData
        var myFormData = new FormData();
        // 2 添加普通键值对
        {#console.log($('#myform').serializeArray())#}
        $.each($('#myform').serializeArray(),function (index,obj) {
            myFormData.append(obj.name,obj.value)
        });
        // 3 添加文件数据
        myFormData.append('avatar',$('#id_avatar')[0].files[0]);
        // 4 发送数据
        $.ajax({
            url:'',
            type:'post',
            data:myFormData,
            // 两个关键性参数
            contentType:false,
            processData:false,

            success:function (data) {
                if (data.code===1000){
                    // 注册成功之后 应该跳转到后端返回过来的url
                    location.href = data.url
                }else{
                    $.each(data.msg,function(index,obj){
                        // 1 先手动拼接字段名所对应的input框的id值
                        var targetId = '#id_' + index;  // #id_username
                        // 2 利用id选择器查找标签  并且将div标签添加报错类
                        $(targetId).next().text(obj[0]).parent().addClass('has-error')
                    })
                }
            }
        })
    });
    $('input').focus(function () {
        // 移除span标签内部的文本  还需要移除div标签的class中has-error属性
        $(this).next().text('').parent().removeClass('has-error')
    })
</script>

</body>
</html>

后端urls.py:

# 注册
    url(r'^register/', views.register, name='register'),

views.py:

# 注册
def register(request):
    form_obj = MyRegForm()
    if request.method == 'POST':
        back_dic = {'code': 1000, 'msg': ""}
        # 校验用户名、密码
        form_obj = MyRegForm(request.POST)
        if form_obj.is_valid():
            # 用变量接收正确的结果 clean_data = {'username'   'password'  're_password' 'email'}
            clean_data = form_obj.cleaned_data
            # 将确认密码键值对删除,表中没有re_password
            clean_data.pop('re_password')

            # 把签名、用户CSS名字存进Blog表中
            sign = clean_data.get('sign')
            username = clean_data.get('username')
            site_theme = username + '.css'
            models.Blog.objects.create(site_name=username, site_title=sign, site_theme=site_theme)

            # 添加字段
            clean_data['blog'] = models.Blog.objects.filter(site_name=username).first()
            clean_data.pop('sign')

            # 额外做的事情:给每个新的注册用户添加3个默认的分类和3个默认的标签
            create_list = []
            blog = models.Blog.objects.filter(site_name=username).first()
            for i in ['一', '二', '三']:
                category_name = username+'的分类'+i
                create_list.append(models.Category(name=category_name, blog=blog))
            models.Category.objects.bulk_create(create_list)

            # 添加3个默认标签
            create_list = []
            for i in ['一', '二', '三']:
                tag_name = username + '的标签' + i
                create_list.append(models.Tag(name=tag_name, blog=blog))
            models.Tag.objects.bulk_create(create_list)
            # 获取用户头像文件
            avatar_obj = request.FILES.get('avatar')
            # 判断用户头像文件是否为空,用户没有上传
            if avatar_obj:
                # 用户上传了,添加到clean_data中
                clean_data['avatar'] = avatar_obj  # clean_data = {'username'  'password'  'email' 'avatar'}
            models.UserInfo.objects.create_user(**clean_data)  # 打散传入  ??=??的形式
            back_dic['msg'] = '注册成功'
            back_dic['url'] = '/login/'
        else:
            back_dic['code'] = 2000
            back_dic['msg'] = form_obj.errors
        return JsonResponse(back_dic)
    return render(request, 'register.html', locals())

myforms.py:

from django import forms
from app01 import models

class MyRegForm(forms.Form):
    username = forms.CharField(min_length=3,max_length=8,label='用户名',
                               error_messages={
                                   "min_length":'用户名最短3位',
                                   "max_length":'用户名最长8位',
                                   "required":'用户名不能为空',
                               },widget=forms.widgets.TextInput(attrs={'class':'form-control'})
                               )

    password = forms.CharField(min_length=3, max_length=8, label='密码',
                               error_messages={
                                   "min_length": '密码最短3位',
                                   "max_length": '密码最长8位',
                                   "required": '密码不能为空',
                               }, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                               )

    re_password = forms.CharField(min_length=3, max_length=8, label='确认密码',
                               error_messages={
                                   "min_length": '确认密码最短3位',
                                   "max_length": '确认密码最长8位',
                                   "required": '确认密码不能为空',
                               }, widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                               )

    email = forms.EmailField(label='邮箱',
                             error_messages={
                                 "required": '邮箱不能为空',
                                 "invalid":"邮箱格式不正确"
                             },
                             widget=forms.widgets.EmailInput(attrs={'class': 'form-control'})
                             )
    sign = forms.CharField(min_length=5, max_length=15, label='学习宣言',
                           error_messages={
                               "min_length": '学习宣言最短5位',
                               "max_length": '学习宣言最长15位',
                               "required": '学习宣言不能为空',}
                           , widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
                           )

    # 钩子函数
    # 局部钩子校验用户名是否已存在
    def clean_username(self):
        username = self.cleaned_data.get('username')
        is_alive = models.UserInfo.objects.filter(username=username)
        if is_alive:
            self.add_error('username','用户名已存在')
        return username


    # 全局钩子校验密码与确认密码是否一致
    def clean(self):
        password = self.cleaned_data.get('password')
        re_password = self.cleaned_data.get('re_password')
        if not password == re_password:
            self.add_error('re_password','两次密码不一致')
        return self.cleaned_data

3、登录功能

技术图片

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link rel="stylesheet" href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css">
    <script src="https://cdn.bootcss.com/sweetalert/2.0.0/sweetalert.min.js"></script>
    <link rel="https://cdn.bootcss.com/sweetalert/1.1.3/sweetalert.min.css">
    {% load static %}
    <link rel="stylesheet" href="{% static '/bootstrap-3.3.7-dist/css/bootstrap.min.css'%}">
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>

<div class="container">
    <h2 class="text-center">登录页面</h2>
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="form-group">
                <label for="id_username">用户名</label>
                <input type="text" name="username" class="form-control" id="id_username">
            </div>
            <div class="form-group">
                <label for="id_password">密码</label>
                <input type="password" name="password" class="form-control" id="id_password">
            </div>
            <div class="form-group">
                <label for="id_code">验证码</label>
                <div class="row">
                    <div class="col-md-6">
                        <input type="text" name="code" class="form-control" id="id_code">
                    </div>
                    <div class="col-md-6">
                        <img src="/get_code/" alt="图片验证码" id="id_img">
                    </div>
                </div>
            </div>
            <input type="button" value="登录" class="btn btn-success" id="id_submit">
            <a href="/register/"><input type="button" value="注册" class="btn btn-danger"></a>
            <span style="color: red" id="error"></span>

        </div>
    </div>
</div>
<script>
    $('#id_img').click(function () {
        var oldSrc = $(this).attr('src');
        $(this).attr('src', oldSrc += '?')
    });

    $('#id_submit').click(function () {
        $.ajax({
            url:'',
            type:'post',
            data:{
                'username': $('#id_username').val(),
                'password': $('#id_password').val(),
                'csrfmiddlewaretoken':'{{ csrf_token }}',
                'code':$('#id_code').val()
            },
            success:function (data) {
                if (data.code === 1000){
                    // 登录成功,跳转页面
                    location.href = data.url
                }else {
                    // 点击此按钮,添加文本信息
                    $('#error').text(data.msg)
                }
            }
        })
    })

</script>

</body>
</html>

后端代码:

urls.py:

# 登录
    url(r'^login/', views.login, name='login'),

views.py:

# 登录
def login(request):
    back_dic = {'code': None, 'msg': None}
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')  # 从前端拿过来的验证码
        # 先对比验证码
        if request.session.get('code').lower() == code.lower():
            # 校验用户名和密码
            user_obj = auth.authenticate(username=username, password=password)
            if user_obj:
                # 记录登录状态
                auth.login(request, user_obj)
                back_dic['code'] = 1000
                back_dic['msg'] = '登录成功'
                back_dic['url'] = '/home/'
                # back_dic['url'] = '/%s/' % username
            else:
                back_dic['code'] = 2000
                back_dic['msg'] = '用户名或密码错误'
        else:
            back_dic['code'] = 3000
            back_dic['msg'] = '验证码错误'
        return JsonResponse(back_dic)
    return render(request, 'login.html')

4、登录认证装饰器配置

settings.py:

LOGIN_URL = '/login/'

5、修改密码模态框方法

技术图片

前端代码:

<li><a href="#" data-toggle="modal" data-target=".bs-example-modal-lg">修改密码</a></li>


{#修改密码模态框#}
<div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <h2 class="text-center">修改密码</h2>
            <div class="row">
                <div class="col-md-8 col-md-offset-2">
                    <div class="form-group">
                        <label for="">用户名</label>
                        <input type="text" name="username" value={{ request.user.username }} class="form-control"
                               disabled>
                    </div>
                    <div class="form-group">
                        <label for="id_old_password">原密码</label>
                        <input type="password" name="old_password" class="form-control" id="id_old_password">
                    </div>
                    <div class="form-group">
                        <label for="id_new_password">新密码</label>
                        <input type="password" name="new_password" class="form-control" id="id_new_password">
                    </div>
                    <div class="form-group">
                        <label for="id_confirm_password">确认密码</label>
                        <input type="password" name="confirm_password" class="form-control" id="id_confirm_password">
                    </div>
                    <button class="btn btn-primary" id="id_set">修改</button>
                    <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
                    <span style="color: red"></span>
                </div>

            </div>
            <br>
        </div>
    </div>
</div>

js代码:

<script>
{#修改密码#}
    $('#id_set').click(function () {
        var $btn = $(this);
        $.ajax({
            url: '{% url 'set_password' %}',
            type: 'post',
            data: {
                old_password: $('#id_old_password').val(),
                new_password: $('#id_new_password').val(),
                confirm_password: $('#id_confirm_password').val(),
                csrfmiddlewaretoken: '{{ csrf_token }}'
            },
            success: function (data) {
                if (data.code === 1000) {
                    location.href = data.url
                } else {
                    $btn.next().next().text(data.msg)
                }
            }
        })
    });
 </script>

后端代码:

urls.py:

# 修改密码
    url(r'^set_password', views.set_password, name='set_password'),

views.py:

# 修改密码
@login_required
def set_password(request):
    if request.is_ajax():
        back_dic = {'code': 1000, 'msg': ''}
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')
        if new_password == confirm_password:
            is_right = request.user.check_password(old_password)
            if is_right:
                request.user.set_password(new_password)
                request.user.save()
                back_dic['msg'] = '修改成功'
                back_dic['url'] = reverse('login')
            else:
                back_dic['code'] = 2000
                back_dic['msg'] = '原密码错误'
        else:
            back_dic['code'] = 3000
            back_dic['msg'] = '两次密码不一致'
        return JsonResponse(back_dic)

6、修改头像

技术图片

前端代码:

<li><a href="/set_avatar/">修改头像</a></li>

stt_avatar.html:

{% extends 'base.html' %}

{% block content %}
    <form action="" method="post" enctype="multipart/form-data">
        <p><a href="/home/">返回</a></p>
        <input type="file" name="myfile" id="id_avatar"><br>
        {% csrf_token %}
        <div class="form-group">
            <label for="id_avatar">
                <img src="/static/images/default.jpg" alt="" width="100" id="id_img">&nbsp;<span>新头像</span>
            </label>
        </div>
        <label for="id_avatar">
            <img src="/media/{{ request.user.avatar }}/" width="200" alt="原头像">&nbsp;<span>原头像</span>
        </label>
        <p><input type="submit" class="btn btn-primary"></p>
    </form>



    <script>
        $('#id_avatar').change(function () {
            // 1 先获取用户上传的头像文件
            var avatarFile = $(this)[0].files[0];
            // 2 利用文件阅读器对象
            var myFileReader = new FileReader();
            // 3 将文件交由阅读器对象读取
            myFileReader.readAsDataURL(avatarFile);
            // 4 修改img标签的src属性  等待文件阅读器对象读取文件之后再操作img标签
            myFileReader.onload = function () {
                $('#id_img').attr('src', myFileReader.result)
            }

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

后端代码:

urls.py:

# 修改用户头像
    url(r'^set_avatar/', views.set_avatar, name='set_avatar'),

views.py:

# 修改头像
@ login_required
def set_avatar(request):
    if request.method == 'POST':
        avatar_obj = request.FILES.get('myfile')
        # models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=avatar_obj)   # 不会帮你自动添加前缀
        # 用自己的save方法,自动帮你添加前缀
        request.user.avatar = avatar_obj
        request.user.save()
    return render(request, 'set_avatar.html')

7、修改签名模态框方法

技术图片

前端代码:

<li><a href="#" data-toggle="modal" data-target=".set_sign">编辑签名</a></li>

{#编辑签名模态框#}
<div class="modal fade bs-example-modal-lg set_sign" id="set_sign" tabindex="-1" role="dialog"
     aria-labelledby="myLargeModalLabel">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <h2 class="text-center">编辑签名</h2>
            <div class="row">
                <div class="col-md-8 col-md-offset-2">
                    <div class="form-group">
                        <label for="">用户名</label>
                        <input type="text" name="username" value={{ request.user.username }} class="form-control"
                               disabled>
                    </div>
                    <div class="form-group">
                        <label for="id_old_sign">原签名</label><br>
                        <textarea name="old_sign" id="id_old_sign" cols="81" rows="5"
                                  disabled>{{ request.user.blog.site_title }}</textarea>
                    </div>
                    <div class="form-group">
                        <label for="id_new_sign">新签名</label>
                        <span style="color: red" id="error_sign"></span>
                        <textarea name="new_sign" id="id_new_sign" cols="81" rows="5"></textarea>
                    </div>

                    <button class="btn btn-primary" id="set_sign">修改</button>
                    <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>

                </div>

            </div>
            <br>
        </div>
    </div>
</div>

js代码:

{#修改签名#}
    $("#set_sign").click(function () {
        $.ajax({
            url: '{% url 'set_sign' %}',
            type: 'post',
            data: {
                new_sign: $('#id_new_sign').val(),
                csrfmiddlewaretoken: '{{ csrf_token }}'
            },
            success: function (data) {
                if (data.code === 1000) {
                    location.href = data.url;
                    $('#error_sign').text(data.msg)
                } else {
                    $('#error_sign').text(data.msg)
                }
            }
        })

    });

后端代码:

urls.py:

# 编辑签名
    url(r'^set_sign', views.set_sign, name='set_sign'),

views.py:

# 编辑签名
@login_required
def set_sign(request):
    if request.is_ajax():
        back_dic = {'code': 1000, 'msg': ''}
        site_name = request.user.username
        site_title = request.POST.get('new_sign')
        if len(site_title) < 5:
            back_dic['code'] = 2000
            back_dic['msg'] = '(你的学习宣言必须大于5位)'
        elif len(site_title) > 15:
            back_dic['code'] = 3000
            back_dic['msg'] = '(你的学习宣言必须小于于15位)'
        else:
            back_dic['code'] = 1000
            back_dic['url'] = reverse('home')
            back_dic['msg'] = '修改成功'
            models.Blog.objects.filter(site_name=site_name).update(site_title=site_title)
        return JsonResponse(back_dic)

8、注销功能模态框

技术图片

前端代码:

<li><a href="#" data-toggle="modal" data-target=".bs-example-modal-sm">注销</a></li>


{#    退出确认模态框#}
<div class="modal fade bs-example-modal-sm" tabindex="-1" role="dialog">
    <div class="modal-dialog modal-sm" 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">你忍心离开我吗</h4>
            </div>
            <div class="modal-body">
                <p>确定退出?</p>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
                <button type="button" class="btn btn-primary" id="exit">确定</button>
            </div>
        </div><!-- /.modal-content -->
    </div>
</div>

js代码:

{#注销#}
    $('#exit').click(function () {
        location.href = "/logout/"
    });

后端代码:

urls.py:

# 注销
    url(r'^logout', views.logout, name='logout'),

views.py:

# 注销
@login_required
def logout(request):
    auth.logout(request)  # 原理删除了对应的session值
    return redirect(reverse('home'))

9、用户上传静态文件配置

setting.py文件配置:

配置好之后,文件夹自动创建

# media配置
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')   # 用户上传的文件全部保存该文件下

10、图片防盗链

请求头里面有一个referer请求头,用来标识你上一次是从哪一个网址过来的

判断上一次这个网址是否有权限

自己的项目:把图片所在的文件夹暴露,那么只能访问图片。

别人的图片怎么解决防盗链?:

1.用爬虫将所有的图片资源下载到本地    这是爬虫的价值所在
2.修改请求头参数  百度搜吧

referer属性:

技术图片

11、暴露任意文件的配置

urls.py:

注意:千万不要暴露重要文件资源,否则拍屁股走人

MEDIA_ROOT,一定不要暴露关键文件

from django.views.static import serve
# 暴露任意后端资源配置
    url(r'^media/(?P<path>.*)', serve, {"document_root": settings.MEDIA_ROOT}),

技术图片

图片文件地址:

<img class="media-object" src="/media/{{ article_obj.blog.userinfo.avatar }}"
                                     style="width: 60px" alt="这是你的头像" width="60px;">

技术图片

12、分页器的使用

分页器:新建py文件,把代码复制过来

技术图片

代码:

class Pagination(object):
    def __init__(self,current_page,all_count,per_page_num=2,pager_count=11):
        """
        封装分页相关数据
        :param current_page: 当前页
        :param all_count:    数据库中的数据总条数
        :param per_page_num: 每页显示的数据条数
        :param pager_count:  最多显示的页码个数
        
        用法:
        queryset = model.objects.all()
        page_obj = Pagination(current_page,all_count)
        page_data = queryset[page_obj.start:page_obj.end]
        获取数据用page_data而不再使用原始的queryset
        获取前端分页样式用page_obj.page_html
        """
        try:
            current_page = int(current_page)
        except Exception as e:
            current_page = 1

        if current_page <1:
            current_page = 1

        self.current_page = current_page

        self.all_count = all_count
        self.per_page_num = per_page_num


        # 总页码
        all_pager, tmp = divmod(all_count, per_page_num)
        if tmp:
            all_pager += 1
        self.all_pager = all_pager

        self.pager_count = pager_count
        self.pager_count_half = int((pager_count - 1) / 2)

    @property
    def start(self):
        return (self.current_page - 1) * self.per_page_num

    @property
    def end(self):
        return self.current_page * self.per_page_num

    def page_html(self):
        # 如果总页码 < 11个:
        if self.all_pager <= self.pager_count:
            pager_start = 1
            pager_end = self.all_pager + 1
        # 总页码  > 11
        else:
            # 当前页如果<=页面上最多显示11/2个页码
            if self.current_page <= self.pager_count_half:
                pager_start = 1
                pager_end = self.pager_count + 1

            # 当前页大于5
            else:
                # 页码翻到最后
                if (self.current_page + self.pager_count_half) > self.all_pager:
                    pager_end = self.all_pager + 1
                    pager_start = self.all_pager - self.pager_count + 1
                else:
                    pager_start = self.current_page - self.pager_count_half
                    pager_end = self.current_page + self.pager_count_half + 1

        page_html_list = []
        # 添加前面的nav和ul标签
        page_html_list.append('''
                    <nav aria-label='Page navigation>'
                    <ul class='pagination'>
                ''')
        first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
        page_html_list.append(first_page)

        if self.current_page <= 1:
            prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
        else:
            prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)

        page_html_list.append(prev_page)

        for i in range(pager_start, pager_end):
            if i == self.current_page:
                temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
            else:
                temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
            page_html_list.append(temp)

        if self.current_page >= self.all_pager:
            next_page = '<li class="disabled"><a href="#">下一页</a></li>'
        else:
            next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
        page_html_list.append(next_page)

        last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
        page_html_list.append(last_page)
        # 尾部添加标签
        page_html_list.append('''
                                           </nav>
                                           </ul>
                                       ''')
        return ''.join(page_html_list)

使用方法:

后端代码:

from app01.utils.mypagenation import Pagination  # 分页器导

# 首页
def home(request):
    # 将网站的所有文章展示到前端
    article_list = models.Article.objects.all()
    # 分页处理
    page_obj = Pagination(current_page=request.GET.get('page',1),all_count=article_list.count())
    article_list = article_list[page_obj.start:page_obj.end]
    return render(request, 'home.html', locals())

前端代码:

{#分页器 #}
<div class="text-center">{{ page_obj.page_html|safe }}</div>

13、每个用户拥有自己的css

1.在注册的时候把用户的css文件的名字固定写好,写进数据库

技术图片

2.在用户编辑CSS的时候,再通过文件操作,创建用户固定的CSS文件

技术图片

3.再应用导入自己的CSS文件

{#引用自己的css#}
<link rel="stylesheet" href="/media/css/{{ user_obj.blog.site_theme }}">

前端代码:

<a href="{% url 'blog_css' %}">个人站点CSS设置</a>

urls.py:

# 个人站点CSS设置
    url(r'^blog_css/', views.blog_css, name='blog_css'),

views.py:

# 个人站点CSS设置
@login_required
def blog_css(request):
    username = request.user.username
    site_theme = models.Blog.objects.filter(site_name=username).first().site_theme
    css_dir = f'media/css/{site_theme}'
    if request.method == 'POST':
        new_css = request.POST.get('new_css')
        with open(css_dir, 'w', encoding='utf-8')as f:
            for line in new_css:
                res = line.replace('
', '')
                f.write(res)
            f.close()
        return redirect('/blog_css/')
    if request.method == 'GET':
        # 先判断用户css文件是否存在,不存在就创建。存在就读取
        isfile = os.path.exists(css_dir)
        # 不存在创建空的css文件
        if not isfile:
            with open(css_dir, 'w')as f:
                f.close()
        # 存在就读取
        with open(css_dir, 'r')as f:
            old_css = f.read()
        return render(request, 'backend/blog_css.html', locals())

backend/blog_css.html:

{% extends 'backend/backend_base.html' %}
{% block article %}
    <form action="" method="post">
        {% csrf_token %}
        <h2>页面定制 CSS 代码</h2>
        <textarea name="new_css" id="" cols="100" rows="30">{{ old_css }}</textarea>
        <p>推荐客户端: <a href="">Open Live Writer</a></p>
        <p>MetaWeblog访问地址: <a href="http://127.0.0.1:8000/home">http://127.0.0.1:8000/home</a>/</p>
        <input type="submit" class="btn btn-primary">
    </form>
{% endblock %}

14、分组,按年月等。官方推荐

-官方提供
from django.db.models.functions import TruncMonth
Article.objects
.annotate(month=TruncMonth(‘timestamp‘)) # Truncate to month and add to select list
.values(‘month‘) # Group By month
.annotate(c=Count(‘id‘)) # Select the count of the grouping
.values(‘month‘, ‘c‘) # (might be redundant, haven‘t tested) select month and count

技术图片

技术图片

# 3.按照文章的年月分组
    date_list = models.Article.objects.filter(blog=blog).        annotate(month=TruncMonth('create_time')).values(
        'month').annotate(c=Count('pk')).values('c', 'month')
{% for date in date_list %}
            <p>
                <a href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}/">
                    {{ date.month|date:'Y年m月' }}({{ date.c }})</a>
            </p>
{% endfor %}

15、侧边栏筛选(自定义过滤器方法)

新建文件夹和py文件:

技术图片

mytag.py代码:

from django.template import Library
from app01 import models
from django.db.models import Count
from django.db.models.functions import TruncMonth
register = Library()


# 侧边栏渲染,自定义过滤器方法
@register.inclusion_tag('left_menu.html', name='my_left')
def index(username):
    # 提供left_menu所需要的所有数据
    user_obj = models.UserInfo.objects.filter(username=username).first()
    blog = user_obj.blog
    # 1.查询当前用户的分类及每个分类下的文章数
    category_list = models.Category.objects.all().filter(blog=blog).annotate(article_sum=Count('article__pk')).values(
        'article_sum', 'name', 'pk')

    # 2.查询当前用户的标签,及每个标签下的文章数
    tag_list = models.Tag.objects.all().filter(blog=blog).annotate(tag_sum=Count('article__pk')).values('tag_sum',
                                                                                                        'name', 'pk')

    # 3.按照文章的年月分组
    date_list = models.Article.objects.filter(blog=blog).        annotate(month=TruncMonth('create_time')).values(
        'month').annotate(c=Count('pk')).values('c', 'month')
    return locals()

left_menu.html:

<div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">文章分类</h3>
    </div>
    <div class="panel-body">
        {% for category in category_list %}
            <p><a href="/{{ username }}/category/{{ category.pk }}">{{ category.name }}({{ category.article_sum }})</a>
            </p>
        {% endfor %}
    </div>

</div>
<div class="panel panel-danger">
    <div class="panel-heading">
        <h3 class="panel-title">文章标签</h3>
    </div>
    <div class="panel-body">
        {% for tag in tag_list %}
            <p><a href="/{{ username }}/tag/{{ tag.pk }}">{{ tag.name }}({{ tag.tag_sum }})</a></p>
        {% endfor %}
    </div>
</div>
<div class="panel panel-warning">
    <div class="panel-heading">
        <h3 class="panel-title">日期归档</h3>
    </div>
    <div class="panel-body">
        {% for date in date_list %}
            <p>
                <a href="/{{ username }}/archive/{{ date.month|date:'Y-m' }}/">
                    {{ date.month|date:'Y年m月' }}({{ date.c }})</a>
            </p>
        {% endfor %}

    </div>
</div>

16、点赞点踩

前端样式:可以直接去别人网站拷贝html代码,改改自己用

{#    点赞点踩前端样式#}
        <div class="clearfix">
            <div id="div_digg">
                <div class="diggit jeff">
                    <span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
                </div>
                <div class="buryit jeff">
                    <span class="burynum" id="bury_count">{{ article_obj.down_num }}</span>
                </div>
                <div class="clear"></div>
                <div class="diggword" id="digg_tips" style="color: red">
                </div>
            </div>
        </div>

js代码:

技术图片

<script>
        {#点赞点踩JS代码#}
        $('.jeff').click(function () {
            var $divEle = $(this);
            $.ajax({
                url: '{% url 'updown' %}',
                type: 'post',
                data: {
                    'article_id':{{ article_obj.pk }},
                    'is_up': $(this).hasClass('diggit'),
                    'csrfmiddlewaretoken': '{{ csrf_token }}'
                },
                success: function (data) {
                    if (data.code === 1000) {
                        $('#digg_tips').text(data.msg);
                        $divEle.children().text(Number($divEle.children().text()) + 1)
                    } else {
                        $('#digg_tips').html(data.msg)
                    }

                }
            })

        });
</script>

后端:

urls.py:

# 点赞点踩
    url(r'^up_or_down/', views.up_or_down, name='updown'),

views.py:

# 点赞点踩
import json
from django.contrib import auth
from django.db.models import F
def up_or_down(request):
    bank_dic = {'code': 1000, 'msg': ''}
    if request.is_ajax():
        article_id = request.POST.get('article_id')
        # 注意:前端返回来的bool值是str形式。拿到是点赞还是点踩   赞True 踩false
        is_up = request.POST.get('is_up')
        is_up = json.loads(is_up)  # 转成python形式的bool值
        '''
        1.必须是登录的用户才能点赞点踩,判断用户是否登录
        2.判断当前文章是否是用户自己写的,自己不能给自己点赞点踩
        3.当前用户是否已经给文章点过赞或踩了
        4.操作数据库---操作两张表,优化表字段
        '''
        # 1.判断用户是否已登录
        if request.user.is_authenticated():
            # 2.拿到当前文章,从文章里拿到当前用户,和登录的用户比较。如果用户一样,则证明是自己写的文章,不能点赞踩
            article_obj = models.Article.objects.filter(pk=article_id).first()
            if not article_obj.blog.userinfo.pk == request.user.pk:
                # 3.判断当前用户是否已经给当前文章点过赞或踩了。到点赞点踩表中查询是否有当前用户的记录,如果有,则证明当前用户已经点过了
                is_click = models.UpAndDown.objects.filter(user=request.user.pk, article=article_id)
                if not is_click:
                    # 用户没电点过,操作表数据.第一张表
                    # 点赞给点赞字段+1
                    if is_up:
                        models.Article.objects.filter(pk=article_id).update(up_num=F('up_num') + 1)
                        bank_dic['msg'] = '点赞成功'
                    # 点踩给点踩字段+1
                    else:
                        models.Article.objects.filter(pk=article_id).update(down_num=F('down_num') + 1)
                        bank_dic['msg'] = '点踩成功'
                    # 操作表数据,第二张表
                    models.UpAndDown.objects.create(user=request.user, article=article_obj, is_up=is_up)
                else:
                    bank_dic['code'] = 2000
                    bank_dic['msg'] = '你已经点过了'
            else:
                bank_dic['code'] = 3000
                bank_dic['msg'] = '不能给自己点'
        else:
            bank_dic['code'] = 4000
            bank_dic['msg'] = '请先<a href="/login/">登录</a>'
        return JsonResponse(bank_dic)

17、模板字符串

文章评论零时渲染:

//定义全局变量
        var parentId = null;
        // 文章评论js代码
        $('#id_comment').click(function () {
            var conTent = $('#id_content').val();
            // 如果是根评论不处理,如果是子评论需要处理,将@jeff 切割
            // @jeff 萨尔
            if (parentId) {
                //切割方式  获取第一个
对应的索引
                var indexN = conTent.indexOf('
') + 1  //顾头不顾尾
                // 按照获取的索引切割
                conTent = conTent.slice(indexN)  //将indexN之前的全部切除,中保留之后的
            }
            $.ajax({
                url: '{% url "comment" %}',
                type: 'post',
                data: {
                    "article_id":{{ article_obj.pk }},
                    "content": conTent,
                    "csrfmiddlewaretoken": '{{ csrf_token }}',
                    "parent_id": parentId
                },
                success: function (data) {
                    if (data.code === 1000) {

                        // 临时渲染评论内容
                        var UserName = '{{ request.user.username }}';
                        var conTent = $('#id_content').val();
                        // 将内容临时渲染到ul标签内
                        var temp = `
                        <li class="list-group-item">
                            <span><span class="glyphicon glyphicon-comment"></span><a href="/${UserName}/">${UserName}</a></span>
                            <div>
                                ${conTent}
                            </div>
                            </li>
                        `;
                        $('.list-group').append(temp);

                        // 将获取用户输入评论的内容框清空
                        $('#id_content').val('');
                        // 将全局的parentId清空,否则parentId后续一直有值,就一直是子评论
                        parentId = null
                    }
                }
            })
        });

技术图片

18、KindEditor编辑器使用

看官方文档

技术图片

技术图片

前端内容:

{% extends 'backend/backend_base.html' %}
{% block article %}
    <h2>添加文章</h2>
    <form action="" method="post">
        {% csrf_token %}
        <p>标题</p>
        <p>
            <input type="text" name="title" class="form-control">
        </p>
        <p>内容(使用kindeditor编辑器)</p>
        <p>
            <textarea name="content" id="id_content" cols="60" rows="20"></textarea>
        </p>
        <div>
            <p>文章标签</p>
            <p>
                {% for tag in tag_list %}
                    {{ tag.name }} <input type="checkbox" name="tag" value="{{ tag.pk }}">
                {% endfor %}
            </p>
        </div>
        <div>
            <p>文章分类</p>
            <p>
                {% for category in category_list %}
                    {{ category.name }} <input type="radio" name="category" value="{{ category.pk }}">
                {% endfor %}

            </p>
        </div>
        <input type="submit" class="btn btn-primary" value="添加">
    </form>



<script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
<script>
        KindEditor.ready(function(K) {
                window.editor = K.create('#id_content',{
                    width:'100%',
                    height:'500px',
                    resizeType:0,
                    uploadJson : '/upload_image/',  //控制用户写文章上传文件的后端地址
                    extraFileUploadParams : {
                        'csrfmiddlewaretoken':'{{ csrf_token }}',
                }
                });

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

后端代码:

urls.py:

# 添加文章
url(r'^add_article/', views.add_article, name='add_article'),

views.py:

# 添加随笔
from bs4 import BeautifulSoup
@login_required
def add_article(request):
    if request.method == 'POST':
        # 获取从前端页面传来的文章数据
        title = request.POST.get('title')
        content = request.POST.get('content')
        tag_list = request.POST.get('tag')
        category_id = request.POST.get('category')
        # 先生成一个该模块beautifulsoup4的对象
        soup = BeautifulSoup(content, 'html.parser')
        for tag in soup.find_all():
            # 筛选除script标签直接删除,避免XSS攻击
            if tag.name == 'script':
                tag.decompose()  # 删除该标签

        # desc = content[0:150]   # 截取文章简介,错误示范。会从html代码截取
        desc = soup.text[0:150]  # 通过模块处理,直接从内容截取
        # 写入数据
        article_obj = models.Article.objects.create(title=title, desc=desc, content=str(soup), category_id=category_id, blog=request.user.blog)
        # 手动操作文章与标签的第三张表
        # 用批量插入数据 bulk_create
        b_list = []
        for tag_id in tag_list:
            b_list.append(models.Article2Tag(article=article_obj, tag_id=tag_id))
        models.Article2Tag.objects.bulk_create(b_list)
        return redirect(reverse('backend'))
    # 获取文章分类、文章标签列表,让用户选择添加文章的分类与标签
    category_list = models.Category.objects.filter(blog=request.user.blog)
    tag_list = models.Tag.objects.filter(blog=request.user.blog)
    return render(request, 'backend/add_article.html', locals())

以上是关于DjangoBBS项目功能拆分的主要内容,如果未能解决你的问题,请参考以下文章

软件架构设计之系统耦合性拆分

软件架构设计之系统耦合性拆分

为啥将拆分为 wav 文件的旋律转换为拆分的 mp3 会在片段边界处产生不好的声音?

pandas GroupBy上的方法apply:一般性的“拆分-应用-合并”

项目工程拆分原则?

将 JavaScript 字符串拆分为固定长度的片段