BBS项目

仿造博客园项目(bbs项目)分析

使用的技术:Django(2.2.22)、MySQL(5.6)、python3.8、jquery3.6.1、bootstrap3.4.1
实现的功能:
注册 (forms校验,页面渲染,上传头像)
登录 (自定义图片验证码)
首页:文章展示、侧边栏过滤(分类,标签,时间)
文章详情:点赞点踩、评论(父评论和子评论)
后台管理:当前用户文章展示(文章增删改查)
发布文章
核心:文章的增删改查

表分析
先确定表的数量 再确定表的基础字段 最后确定表的外键字段
1.用户表
2.个人站点表
3.文章表
4.文章分类表
5.文章标签表
6.点赞点踩表
7.文章评论表

表之间关系

image

先确定表的数量 再确定表的基础字段 最后确定表的外键字段
用户表
替换auth_user表并扩展额外的字段(电话号码、头像、注册时间)

class UserInfo(AbstractUser):
    """用户表"""
    phone = models.BigIntegerField(verbose_name='手机号', null=True)
    avatar = models.FileField(upload_to='avatar/', default='avatar/default.jpg', verbose_name='用户头像')
    register_time = models.DateTimeField(verbose_name='注册时间', auto_now_add=True)

    site = models.OneToOneField(to='Site', on_delete=models.CASCADE, null=True)

个人站点表
站点名称、站点标题、站点样式

class Site(models.Model):
    """个人站点表"""
    site_name = models.CharField(verbose_name='站点名称', max_length=32)
    site_title = models.CharField(verbose_name='站点标题', max_length=32)
    site_theme = models.CharField(verbose_name='站点样式', max_length=32, null=True)  # 简单模拟样式文件

文章表
文章标题、文章简介、文章内容、发布时间

class Article(models.Model):
    """文章表"""
    title = models.CharField(verbose_name='文章标题', max_length=32)
    desc = models.CharField(verbose_name='文章简介', max_length=255)
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
    # 三个优化字段
    comment_num = models.IntegerField(verbose_name='评论数', default=0)
    up_num = models.IntegerField(verbose_name='点赞数', default=0)
    down_num = models.IntegerField(verbose_name='点踩数', default=0)

    site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)
    category = models.ForeignKey(to='Category', on_delete=models.CASCADE, null=True)
    tags = models.ManyToManyField(to='Tag',
                                  through='Article2Tag',
                                  through_fields=('article', 'tag'),
                                  null=True
                                  )


文章表与个人站点表是一对多外键关系
文章表与文章分类表是一对多外键关系
文章表与文章标签表是多对多外键关系
       '''
       数据库字段优化设计:我们想统计文章的评论数 点赞数
       		通过文章数据跨表查询到文章评论表中对应的数据统计即可
       但是文章需要频繁的展示 每次都跨表查询的话效率极低
       		我们在文章表中再创建三个普通字段
       之后只需要确保每次操作评论表或者点赞点踩表时同步修改上述三			个普通字段即可
       '''

文章分类表
分类名称

class Category(models.Model):
    """文章分类表"""
    name = models.CharField(verbose_name='分类名称', max_length=32)
    site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)

文章标签表
标签名称

class Tag(models.Model):
    """文章标签表"""
    name = models.CharField(verbose_name='标签名称', max_length=32)
    site = models.ForeignKey(to='Site', on_delete=models.CASCADE, null=True)

自定义多对多表(文章表和标签表)

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

点赞点踩表
记录哪个用户给哪篇文章点了推荐(赞)还是反对(踩)
用户字段(用户主键)>>>:外键字段
文章字段(文章主键)>>>:外键字段
点赞点踩

class UpAndDown(models.Model):
    """文章点赞点踩表"""
    user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True)
    article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True)
    is_up = models.BooleanField(verbose_name='点赞点踩')  # 传布尔值存 0或者1

文章评论表:记录哪个用户给哪篇文章评论了什么内容
用户字段(用户主键)>>>:外键字段
文章字段(文章主键)>>>:外键字段
评论内容
评论时间
外键字段(自关联)

class Comment(models.Model):
    """文章评论表"""
    user = models.ForeignKey(to='UserInfo', on_delete=models.CASCADE, null=True)
    article = models.ForeignKey(to='Article', on_delete=models.CASCADE, null=True)
    content = models.TextField(verbose_name='评论内容')
    comment_time = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
    parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True)

注册功能

settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'bbs01',
        'USER':'root',
        'PASSWORD':'Abcd1234',
        'HOST':'127.0.0.1',
        'PORT':3306,
        'CHARSET':'utf8',
    }
}

STATICFILES_DIRS = [os.path.join(BASE_DIR,'static'),]

AUTH_USER_MODEL = 'app01.UserInfo'

models.py

from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.


class UserInfo(AbstractUser):
    """用户表"""
    phone = models.BigIntegerField(verbose_name='手机号',null=True)
    avatar = models.FileField(upload_to='avatar/',default='avatar/default.jpg',verbose_name='用户头像')
    register_time = models.DateTimeField(verbose_name='注册时间',auto_now_add=True)

    site = models.OneToOneField(to='Site',on_delete=models.CASCADE,null=True)

	 # 修改admin后台管理的表名
    class Meta:
        verbose_name_plural = '用户表'

    def __str__(self):
        return  f'用户对象:{self.username}'
class Site(models.Model):
    """个人站点表"""
    site_name = models.CharField(verbose_name='站点名称',max_length=32)
    site_title = models.CharField(verbose_name='站点标题',max_length=32)
    site_theme = models.CharField(verbose_name='站点样式',max_length=32,null=True)  # 简单模拟样式文件

	 # 修改admin后台管理的表名
    class Meta:
        verbose_name_plural = '个人站点表'

    def __str__(self):
        return  f'个人站点:{self.site_name}'

class Article(models.Model):
    """文章表"""
    title = models.CharField(verbose_name='文章标题',max_length=32)
    desc = models.CharField(verbose_name='文章简介',max_length=255)
    content = models.TextField(verbose_name='文章内容')
    create_time = models.DateTimeField(verbose_name='创建时间',auto_now_add=True)
    # 三个优化字段
    comment_num = models.IntegerField(verbose_name='评论数',default=0)
    up_num = models.IntegerField(verbose_name='点赞数',default=0)
    down_num = models.IntegerField(verbose_name='点踩数',default=0)

    site = models.ForeignKey(to='Site',on_delete=models.CASCADE,null=True)
    category = models.ForeignKey(to='Category',on_delete=models.CASCADE,null=True)
    tags = models.ManyToManyField(to='Tag',through='Article2Tag',through_fields=('article','tag'),null=True)

	 # 修改admin后台管理的表名
    class Meta:
        verbose_name_plural = '文章表'

    def __str__(self):
        return  f'文章对象:{self.title}'

class Category(models.Model):
    """文章分类表"""
    name = models.CharField(verbose_name='分类名称',max_length=32)
    site = models.ForeignKey(to='Site',on_delete=models.CASCADE,null=True)

		# 修改admin后台管理的表名
    class Meta:
        verbose_name_plural = '文章分类表'

    def __str__(self):
        return  f'文章分类:{self.name}'

class Tag(models.Model):
    """文章标签表"""
    name = models.CharField(verbose_name='标签名称',max_length=32)
    site = models.ForeignKey(to='Site',on_delete=models.CASCADE,null=True)

	 # 修改admin后台管理的表名
    class Meta:
        verbose_name_plural = '文章标签表'

    def __str__(self):
        return  f'文章标签:{self.name}'


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


class UpAndDown(models.Model):
    """文章点赞点踩表"""
    user = models.ForeignKey(to='UserInfo',on_delete=models.CASCADE,null=True)
    article = models.ForeignKey(to='Article',on_delete=models.CASCADE,null=True)
    is_up = models.BooleanField(verbose_name='点赞点踩')  # 传布尔值存0或者1


class Comment(models.Model):
    """文章评论表"""
    user = models.ForeignKey(to='UserInfo',on_delete=models.CASCADE,null=True)
    article = models.ForeignKey(to='Article',on_delete=models.CASCADE,null=True)
    content = models.TextField(verbose_name='评论内容')
    comment_time = models.DateTimeField(auto_now_add=True,verbose_name='评论时间')
    parent = models.ForeignKey(to='self',on_delete=models.CASCADE,null=True)

urls.py

    from app01 import views
    # 用户注册功能
    path('register/',views.register_func,name='register_view'),

myforms.py

from django import forms
from app01 import models


class RegisterForm(forms.Form):
    """用户注册form类"""
    username = forms.CharField(min_length=3, max_length=8, label='用户名',
                               error_messages={
                                   'min_length': '用户名最短三位',
                                   'max_length': '用户名最长八位',
                                   'required': '用户名不能为空'
                               },
                               widget=forms.widgets.TextInput(attrs={'class': 'form-control'})
                               )
    password = forms.CharField(min_length=3, max_length=8, label='密码',
                               error_messages={
                                   'min_length': '密码最短三位',
                                   'max_length': '密码最长八位',
                                   'required': '密码不能为空'
                               },
                               widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'})
                               )
    confirm_password = forms.CharField(min_length=3, max_length=8, label='确认密码',
                                       error_messages={
                                           'min_length': '密码最短三位',
                                           'max_length': '密码最长八位',
                                           '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'})
                             )

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

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

views.py

from django.shortcuts import render,HttpResponse,redirect
from app01 import myforms
from app01 import models
from django.http import  JsonResponse
# Create your views here.

def register_func(request):
    # 前后端ajax交互 通常采用字段作为交互对象
    back_dict = {'code': 10000,'msg': ''}
    # 1.先产生一个空的form_obj
    form_obj = myforms.RegisterForm()
    if request.method == 'POST':
        form_obj = myforms.RegisterForm(request.POST)  # username password confirm_password email csrfmiddlewaretoken
        if form_obj.is_valid():
            clean_data = form_obj.cleaned_data  # 存储符号校验的数据 {username password confirm_password email}
            # 将confirm_password键值对移除
            clean_data.pop('confirm_password')  # {username password email}
            # 获取用户上传的头像文件
            avatar_obj = request.FILES.get('avatar')  # 用户可能没有上传
            if avatar_obj:
                clean_data['avatar'] = avatar_obj  # {username password  email avatar}
            # 创建用户数据
            models.UserInfo.objects.create_user(**clean_data)  # 上述处理字典的目的就是为了创建数据省事
            back_dict['msg'] = '注册成功'
            back_dict['url'] = '/login/'
        else:
            back_dict['code'] = 10001
            back_dict['msg'] = form_obj.errors
        return JsonResponse(back_dict)
    return render(request,'registerPage.html',locals())

registerPage.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.css' %}">
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.js' %}"></script>
</head>
<body>
    <div class="container">
        <div class="col-md-8 col-md-offset-2">
            <h2 class="text-center">用户注册</h2>
            <form id="form">
                {% csrf_token %}
                {% for form in form_obj %}

                <div class="form-group"> <!-- 目的是让多个获取用户数据的标签上下间距更大一些 -->
                    <label for="{{ form.auto_id }}">{{ form.label }}</label>  <!--form.auto_id自动获取渲染的标签id值 -->
                    {{ form }}
                    <span style="color: red" class="pull-right"></span>
                </div>
                {% endfor %}
            <!--用户头像自己编写相关标签获取 -->
            <div class="form-group">
                <label for="myfile">头像
                    <img src="/static/img/default.jpg" alt="" width="120" id="myimg">
                </label>
                <input type="file" id="myfile" style="display: none">
            </div>
                <input type="button" id="subBtn" class="btn btn-primary btn-block" value="注册">
            </form>

        </div>
    </div>
    <script>
        // 1.用户头像的实时展示
        $('#myfile').change(function (){
            // 1.产生一个文件阅读器对象
            let myFileReaderObj = new FileReader();
            // 2.获取用户上传的头像文件
            let fileObj = this.files[0];
            // 3.将文件对象交给阅读器对象读取
            myFileReaderObj.readAsDataURL(fileObj);
            // 等待文件阅读器对象加载完毕之后在修改src
            myFileReaderObj.onload = function (){
                // 4.修改img标签的src属性展示图片
                $('#myimg').attr('src',myFileReaderObj.result)
            }
        })

        // 2.给注册按钮绑定点击事件  发送ajax 携带了文件数据
        $('#subBtn').click(function (){
            // 1.先产生一个内置对象
            let myFormDataObj = new FormData();
            // 2.添加普通数据(单个单个的编写效率较低)
            $.each($('#form').serializeArray(),function (index, dataObj){ //对结果for循环 然后交给后面的函数处理
                myFormDataObj.append(dataObj.name, dataObj.value)  // {'name':'','value':''}
            })
            // 3.添加文件数据
            myFormDataObj.append('avatar', $('#myfile')[0].files[0])
            // 4.发送ajax请求
            $.ajax({
                url:'',
                type:'post',
                data:myFormDataObj,

                contentType:false,
                processData: false,

                success:function (args) {
                    if (args.code === 10000){
                        window.location.href = args.url
                    }else{
                        let dataObj = args.msg;
                        //如何针对性的渲染错误提示{'username'} id_username
                        $.each(dataObj, function (k, msgArray) {
                            // 拼接标签的id值
                            let eleId = '#id_' + k
                            // 根据id查找标签 修改下面的span标签的内容 并给父标签添加错误样式
                            $(eleId).next().text(msgArray[0]).parent().addClass('has-error')
                        })
                    }
                }
            })
        })

        // 3.给所有的input标签绑定获取焦点事件 移除错误样式
        $('input').focus(function (){
            $(this).next().text('').parent().removeClass('has-error')
        })

    </script>
</body>
</html>

image
image

用户登录

urls.py

# 用户登录功能
path('login/',views.login_func,name = 'login_view'),
# 图片验证码相关功能
path('get_code/',views.get_code_func),

"""
img标签的src属性
	1.可以直接填写图片地址
	2.还可以填写一个路由 会自动朝该路由发送get请求
    如果结果是图片的二进制数据 那么自动渲染图片
"""

views.py


from django.http import JsonResponse
from django.contrib import auth

def login_func(request):
    back_dict = {'code': 10000, 'msg':''}
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        code = request.POST.get('code')
        if code.upper() == request.session.get('code').upper():
            user_obj = auth.authenticate(request,username=username,password=password)
            if user_obj:
                # 保存用户登录状态
                auth.login(request,user_obj)
                back_dict['msg'] = '登录成功'
                back_dict['url'] = '/home/'
            else:
                back_dict['code'] = 10001
                back_dict['msg'] = '用户名或密码'
        else:
            back_dict['code'] = 10002
            back_dict['msg'] = '验证码错误'
        return JsonResponse(back_dict)

    return  render(request,'loginPage.html')

#PIL依赖第三方模块 pillow
from PIL import Image,ImageFont,ImageDraw
"""
Image  产生图片  ImageFont 字体样式  ImageDraw 画笔对象
"""
from io import BytesIO, StringIO
"""BytesIO:在内存中临时存储 读取的时候以bytes格式为准
StringIO:在内存中临时存储 读取的时候以字符串格式为准
"""

import random

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

def get_code_func(request):
    # 先产生图片对象
    img_obj = Image.new('RGB',(350, 35), get_random())
    # 将图片对象交给画笔对象
    draw_obj = ImageDraw.Draw(img_obj)
    # 确定字体样式(ttf文件)
    font_obj = ImageFont.truetype('static/font/111.ttf', 35)
    # 产生随机验证码
    code = ''
    for i in range(5):
        random_upper = chr(random.randint(65, 90))
        random_lower = chr(random.randint(97,122))
        random_int = str(random.randint(1,9))
        # 三选一
        temp_choice = random.choice([random_upper, random_lower, random_int])
        # 写到图片上
        draw_obj.text((i * 60 + 45,0), temp_choice, font=font_obj)
        code += temp_choice
    # 后端保存验证码 便于后续的比对
    request.session['code'] = code
    io_obj = BytesIO()
    img_obj.save(io_obj, 'png')
    return HttpResponse(io_obj.getvalue())

loginPage.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
     <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
    {% load static %}
    <link rel="stylesheet" href="{%  static 'bootstrap-3.4.1-dist/css/bootstrap.css' %}">
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.js' %}"></script>
</head>
<body>
    <div class="container">
        <div class="col-md-8 col-md-offset-2">
            <h2 class="text-center">用户登录</h2>
            {% csrf_token %}
            <div class="form-group">
                <label for="name">用户名</label>
                <input type="text" id="name" class="form-control" name="username">
            </div>
            <div class="form-group">
                <label for="password">密码</label>
                <input type="password" id="password" class="form-control" name="password">
            </div>
            <div class="form-group">
                <label for="code">验证码</label>
                <div class="row">
                    <div class="col-md-6">
                        <input type="text" id="code" class="form-control" name="code">
                    </div>
                    <div class="col-md-6">
                        <img src="/get_code/" alt="" width="350" height="35" id="d1">
                    </div>
                </div>
            </div>
            <input type="button" class="btn btn-success btn-block" value="登录" id="loginBtn">
        </div>
    </div>
    <script>
        // 1.验证码动态刷新
        $('#d1').click(function (){
            let oldSrc = $(this).attr('src');
            $(this).attr('src',oldSrc + '?')
        })

        // 2.登录按钮发送ajax请求
        $('#loginBtn').click(function (){
            // 可以再次使用form标签序列化功能 也可以自己挨个获取
            $.ajax({
                url:'',
                type:'post',
                data:{'username':$('#name').val(),'password':$('#password').val(),'code':$('#code').val(),'csrfmiddlewaretoken':'{{ csrf_token }}'},
                success: function (args) {
                    if(args.code === 10000){
                        window.location.href = args.url
                    }else{
                        alert(args.msg)
                    }
                }
            })
        })
    </script>
</body>
</html>

"""
img标签的src属性
	1.可以直接填写图片地址
	2.还可以填写一个路由 会自动朝该路由发送get请求
    如果结果是图片的二进制数据 那么自动渲染图片
"""

image

首页搭建(导航条相关功能)

urls.py

# 网址首页
path('home/',views.home_func, name='home_view'),

views.py

def home_func(request):
    return render(request,'homePage.html',locals())

homePage.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
     <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
    {% load static %}
    <link rel="stylesheet" href="{%  static 'bootstrap-3.4.1-dist/css/bootstrap.css' %}">
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.js' %}"></script>
</head>
<body>
    <nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">BBS</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
        <li><a href="#">文章</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>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="搜索">
        </div>
        <button type="submit" class="btn btn-default">提交</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
          {% if request.user.is_authenticated %}
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多操作<span class="caret"></span></a>
                      <ul class="dropdown-menu">
                        <li><a href="#">修改密码</a></li>
                        <li><a href="#">修改头像</a></li>
                        <li><a href="#">后台管理</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="/logout/">注销登录</a></li>
                      </ul>
                    </li>
              {% else %}
                    <li><a href="{% url 'register_view' %}">注册</a></li>
                    <li><a href="{% url 'login_view' %}">登录</a></li>
          {% endif %}

      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
</body>
</html>

image

修改密码与注销登录

urls.py

# 修改密码功能
path('set_pwd/',views.set_pwd_func),
# 注销登录功能
path('logout/',views.logout),

views.py

from django.contrib.auth.decorators import login_required
@login_required
def set_pwd_func(request):
    back_dict = {'code':10000, 'msg':''}
    if request.method == 'POST':
        old_pwd = request.POST.get('old_pwd')
        new_pwd =request.POST.get('new_pwd')
        confirm_pwd = request.POST.get('confirm_pwd')
        # 先校验原密码是否正确
        if request.user.check_password(old_pwd):
            # 先校验两次密码是否一致 并且不能为空
            if new_pwd == confirm_pwd and new_pwd:
                request.user.set_password(new_pwd)
                request.user.save()
                back_dict['msg'] = '密码修改成功'
                back_dict['url'] = '/login/'
            else:
                back_dict['code'] = 10001
                back_dict['msg'] = '两次密码不一致或者为空'
        else:
            back_dict['code'] = 10002
            back_dict['msg'] = '原密码错误'
        return JsonResponse(back_dict)

@login_required
def logout(request):
    auth.logout(request)
    return redirect('home_view')

settings.py

LOGIN_URL = '/login/'

homePage.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
     <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
    {% load static %}
    <link rel="stylesheet" href="{%  static 'bootstrap-3.4.1-dist/css/bootstrap.css' %}">
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.js' %}"></script>
</head>
<body>
<!--导航条开始-->
    <nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">BBS</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
        <li><a href="#">文章</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>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="搜索">
        </div>
        <button type="submit" class="btn btn-default">提交</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
          {% if request.user.is_authenticated %}
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多操作<span class="caret"></span></a>
                      <ul class="dropdown-menu">
                        <li><a href="#" data-toggle="modal" data-target="#myModal">修改密码</a></li>
                        <li><a href="#">修改头像</a></li>
                        <li><a href="#">后台管理</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="/logout/">注销登录</a></li>
                      </ul>
                    </li>
              {% else %}
                    <li><a href="{% url 'register_view' %}">注册</a></li>
                    <li><a href="{% url 'login_view' %}">登录</a></li>
          {% endif %}

      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
<!--导航条结束-->

<!--模态框开始-->

<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title text-center" id="myModalLabel">修改密码</h4>
      </div>
      <div class="modal-body">
         <div class="form-group">
             <label for="">用户名</label>
             <input type="text" value="{{ request.user.username }}" disabled class="form-control">
         </div>
          <div class="form-group">
             <label for="">原密码</label>
             <input type="text" id="old_pwd" class="form-control">
         </div>
          <div class="form-group">
             <label for="">新密码</label>
             <input type="text" id="new_pwd" class="form-control">
         </div>
          <div class="form-group">
             <label for="">确认密码</label>
             <input type="text" id="confirm_pwd" class="form-control">
         </div>
      </div>
      <div class="modal-footer">
          <span id="error" style="color: red"></span>
        <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
        <button type="button" class="btn btn-warning" id="setBtn">修改</button>
      </div>
    </div>
  </div>
</div>
<!--模态框结束-->

<script>
    $('#setBtn').click(function (){
        $.ajax({
            url:'/set_pwd/',
            type:'post',
            data: {
                'old_pwd': $('#old_pwd').val(),
                'new_pwd': $('#new_pwd').val(),
                'confirm_pwd': $('#confirm_pwd').val(),
                'csrfmiddlewaretoken': '{{ csrf_token }}',
            },
            success: function (args) {
                if (args.code === 10000) {
                    window.location.href = args.url
                }else{
                    $('#error').text(args.msg)
                }
            }
        })
    })
</script>
</body>
</html>

image

后台数据绑定

创建admin后台超级管理员
image
image
image
admin.py

在admin后台注册表
from app01 import models
'''只要注册了 admin就会自动产生针对该注册表的增删改查至少四个功能'''
admin.site.register(models.UserInfo)
admin.site.register(models.Site)
admin.site.register(models.Article)
admin.site.register(models.Category)
admin.site.register(models.Tag)
admin.site.register(models.Article2Tag)
admin.site.register(models.UpAndDown)
admin.site.register(models.Comment)

homePage.html

添加两边侧边栏内容:
<!--模态框结束-->
<!--内容区域开始 -->
    <div class="container-fluid">
        <div class="row">
            <div class="col-md-2">
                <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title">重金求子</h3>
                  </div>
                  <div class="panel-body">
                    事后必有重谢:wuyong123
                  </div>
                </div>
                 <div class="panel panel-warning">
                  <div class="panel-heading">
                    <h3 class="panel-title">百万大奖</h3>
                  </div>
                  <div class="panel-body">
                    恭喜你幸运儿:zhanghong321
                  </div>
                </div>
                 <div class="panel panel-danger">
                  <div class="panel-heading">
                    <h3 class="panel-title">广告招租</h3>
                  </div>
                  <div class="panel-body">
                    旺铺招租:sui321
                  </div>
                </div>
            </div>
            <div class="col-md-8"></div>
            <div class="col-md-2">
                  <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title">重金求子</h3>
                  </div>
                  <div class="panel-body">
                    事后必有重谢:wuyong123
                  </div>
                </div>
                 <div class="panel panel-warning">
                  <div class="panel-heading">
                    <h3 class="panel-title">百万大奖</h3>
                  </div>
                  <div class="panel-body">
                    恭喜你幸运儿:zhanghong321
                  </div>
                </div>
                 <div class="panel panel-danger">
                  <div class="panel-heading">
                    <h3 class="panel-title">广告招租</h3>
                  </div>
                  <div class="panel-body">
                    旺铺招租:sui321
                  </div>
                </div>
            </div>
        </div>
    </div>
<!--内容区域结束 -->

后台数据绑定

image
image
image
image
models.py

   # 修改admin后台管理的表名
    class Meta:
        verbose_name_plural = '用户表'

    def __str__(self):
        return  f'用户对象:{self.username}'

# 修改admin后台管理的表名
    class Meta:
        verbose_name_plural = '个人站点表'

    def __str__(self):
        return  f'个人站点:{self.site_name}'

# 修改admin后台管理的表名
    class Meta:
        verbose_name_plural = '文章表'

    def __str__(self):
        return  f'文章对象:{self.title}'


    # 修改admin后台管理的表名
    class Meta:
        verbose_name_plural = '文章分类表'

    def __str__(self):
        return  f'文章分类:{self.name}'


    # 修改admin后台管理的表名
    class Meta:
        verbose_name_plural = '文章标签表'

    def __str__(self):
        return  f'文章标签:{self.name}'

image
image
image
image
image
image
添加文章,确定好分类和站点,不要搞混了
image
image
image
image
winter站点添加文章
image
image
image
image
用户站点绑定
winter用户:
image
models.py

class UserInfo(AbstractUser):
    """用户表"""
    phone = models.BigIntegerField(verbose_name='手机号',null=True,blank=True) # blank参数用于控制admin后台管理 与数据库无关

image
jason用户:
image
创建标签,用于后续标签和文章绑定
image
image
image
image
winter添加标签
image
image
image
文章绑定标签
image
image
image
image
image
winter用户添加标签
image
image
image
image

首页样式搭建

mypage.py

class Pagination(object):
    def __init__(self, current_page, all_count, per_page_num=10, pager_count=11):
        """
        封装分页相关数据
        :param current_page: 当前页
        :param all_count:    数据库中的数据总条数
        :param per_page_num: 每页显示的数据条数
        :param pager_count:  最多显示的页码个数
        """
        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)

views.py

from app01 import mypage

def home_func(request):
    # 查询所有用户编写的文章
    article_queryset = models.Article.objects.all()
    '''文章过多的情况下应该考虑添加分页器'''
    page_obj = mypage.Pagination(current_page=request.GET.get('page'),all_count=article_queryset.count())
    page_queryset = article_queryset[page_obj.start:page_obj.end]
    return render(request,'homePage.html',locals())

homePage.html

<div class="col-md-8">
                {% for article_obj in page_queryset %}
                        <div class="media">
                        <h4 class="media-heading"><a href="#">{{ article_obj.title }}</a></h4>
                          <div class="media-left">
                            <a href="#">
                              <img class="media-object" src="/static/img/default.jpg" alt="..." width="80">
                            </a>
                          </div>
                          <div class="media-body">

                            {{ article_obj.desc }}
                          </div>
                            <br>
                            <div>
                                <span><a href="#">{{ article_obj.site.userinfo.username }}&nbsp;&nbsp;</a></span>
                                <span>{{ article_obj.create_time|date:'Y-m-d H:i' }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-thumbs-up">{{ article_obj.up_num }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-thumbs-down">{{ article_obj.down_num }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-comment">{{ article_obj.comment_num }}&nbsp;&nbsp;</span>
                            </div>
                        </div>
                    <hr>
                {% endfor %}
                <div class="text-center">{{ page_obj.page_html|safe }}</div>
            </div>

media媒体目录

settings.py

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

url.py

from django.urls import path,re_path
from django.views.static import serve
from django.conf import settings

# 自定义暴露资源接口
    re_path('media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT}),

homePage.html

            <div class="col-md-8">
                {% for article_obj in page_queryset %}
                        <div class="media">
                        <h4 class="media-heading"><a href="#">{{ article_obj.title }}</a></h4>
                          <div class="media-left">
                            <a href="#">
                              <img class="media-object" src="/media/{{ article_obj.site.userinfo.avatar }}" alt="..." width="80">
                            </a>

个人站点页面搭建

urls.py

 # 个人站点接口
path('<str:username>/',views.site_func)

views.py

def site_func(request,username):
    # 查询个人站点是否存在
    site_obj = models.Site.objects.filter(site_name=username).first()
    if not site_obj:
        return render(request,'errorPage.html')
    # 查询个人站点下所有的文章
    article_queryset = models.Article.objects.filter(site=site_obj)
    return render(request,'sitePage.html',locals())

homePage.html

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

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
        <li><a href="#">文章</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多详情 <span class="caret"></span></a>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="搜索">
        </div>
        <button type="submit" class="btn btn-default">提交</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
          {% if request.user.is_authenticated %}
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多操作<span class="caret"></span></a>
                      <ul class="dropdown-menu">
                        <li><a href="#" data-toggle="modal" data-target="#myModal">修改密码</a></li>
                        <li><a href="#">修改头像</a></li>
                        <li><a href="#">后台管理</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="/logout/">注销登录</a></li>
                      </ul>
                    </li>
              {% else %}
                    <li><a href="{% url 'register_view' %}">注册</a></li>
                    <li><a href="{% url 'login_view' %}">登录</a></li>
          {% endif %}

      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
<!--导航条结束-->

<!--模态框开始-->

<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title text-center" id="myModalLabel">修改密码</h4>
      </div>
      <div class="modal-body">
         <div class="form-group">
             <label for="">用户名</label>
             <input type="text" value="{{ request.user.username }}" disabled class="form-control">
         </div>
          <div class="form-group">
             <label for="">原密码</label>
             <input type="text" id="old_pwd" class="form-control">
         </div>
          <div class="form-group">
             <label for="">新密码</label>
             <input type="text" id="new_pwd" class="form-control">
         </div>
          <div class="form-group">
             <label for="">确认密码</label>
             <input type="text" id="confirm_pwd" class="form-control">
         </div>
      </div>
      <div class="modal-footer">
          <span id="error" style="color: red"></span>
        <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
        <button type="button" class="btn btn-warning" id="setBtn">修改</button>
      </div>
    </div>
  </div>
</div>
<!--模态框结束-->

<!--内容区域开始 -->
    <div class="container-fluid">
        <div class="row">
        {% block content %}
            <div class="col-md-2">
                <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title">重金求子</h3>
                  </div>
                  <div class="panel-body">
                    事后必有重谢:wuyong123
                  </div>
                </div>
                 <div class="panel panel-warning">
                  <div class="panel-heading">
                    <h3 class="panel-title">百万大奖</h3>
                  </div>
                  <div class="panel-body">
                    恭喜你幸运儿:zhanghong321
                  </div>
                </div>
                 <div class="panel panel-danger">
                  <div class="panel-heading">
                    <h3 class="panel-title">广告招租</h3>
                  </div>
                  <div class="panel-body">
                    旺铺招租:sui321
                  </div>
                </div>
            </div>
            <div class="col-md-8">
                {% for article_obj in page_queryset %}
                        <div class="media">
                        <h4 class="media-heading"><a href="#">{{ article_obj.title }}</a></h4>
                          <div class="media-left">
                            <a href="#">
                              <img class="media-object" src="/media/{{ article_obj.site.userinfo.avatar }}" alt="..." width="80">
                            </a>
                          </div>
                          <div class="media-body">

                            {{ article_obj.desc }}
                          </div>
                            <br>
                            <div>
                                <span><a href="/{{article_obj.site.userinfo.username}}/">&nbsp;{{ article_obj.site.userinfo.username }}&nbsp;&nbsp;</a></span>
                                <span>{{ article_obj.create_time|date:'Y-m-d H:i' }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-thumbs-up">{{ article_obj.up_num }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-thumbs-down">{{ article_obj.down_num }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-comment">{{ article_obj.comment_num }}&nbsp;&nbsp;</span>
                            </div>
                        </div>
                    <hr>
                {% endfor %}
                <div class="text-center">{{ page_obj.page_html|safe }}</div>
            </div>
            <div class="col-md-2">
                  <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title">重金求子</h3>
                  </div>
                  <div class="panel-body">
                    事后必有重谢:wuyong123
                  </div>
                </div>
                 <div class="panel panel-warning">
                  <div class="panel-heading">
                    <h3 class="panel-title">百万大奖</h3>
                  </div>
                  <div class="panel-body">
                    恭喜你幸运儿:zhanghong321
                  </div>
                </div>
                 <div class="panel panel-danger">
                  <div class="panel-heading">
                    <h3 class="panel-title">广告招租</h3>
                  </div>
                  <div class="panel-body">
                    旺铺招租:sui321
                  </div>
                </div>
            </div>
        {% endblock %}
        </div>
    </div>
<!--内容区域结束 -->

<script>
    $('#setBtn').click(function (){
        $.ajax({
            url:'/set_pwd/',
            type:'post',
            data: {
                'old_pwd': $('#old_pwd').val(),
                'new_pwd': $('#new_pwd').val(),
                'confirm_pwd': $('#confirm_pwd').val(),
                'csrfmiddlewaretoken': '{{ csrf_token }}',
            },
            success: function (args) {
                if (args.code === 10000) {
                    window.location.href = args.url
                }else{
                    $('#error').text(args.msg)
                }
            }
        })
    })
</script>
{% block js %}

{% endblock %}
</body>

sitePage.html

{% extends 'homePage.html' %}

{% block css %}
    <link rel="stylesheet" href="media/css/{{ site_obj.site_theme }}">
{% endblock %}

{% block title %}
    {{ site_obj.site_title }}
{% endblock %}

 {% block content %}
    <div class="col-md-2">
                <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title">文章分类</h3>
                  </div>
                  <div class="panel-body">

                  </div>
                </div>
                 <div class="panel panel-warning">
                  <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">
                  </div>
                </div>

    </div>
    <div class="col-md-10">
        {% for article_obj in article_queryset %}
         <div class="media">
                        <h4 class="media-heading"><a href="#">{{ article_obj.title }}</a></h4>
                          <div class="media-body">
                            {{ article_obj.desc }}
                          </div>
                            <br>
                            <div class="pull-right">
                                <span>posted&nbsp;&nbsp;@</span>
                                <span>{{ article_obj.create_time|date:'Y-m-d H:i' }}&nbsp;&nbsp;</span>
                                <span>{{ article_obj.site.userinfo.username }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-thumbs-up">{{ article_obj.up_num }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-thumbs-down">{{ article_obj.down_num }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-comment">{{ article_obj.comment_num }}&nbsp;&nbsp;</span>
                            </div>
                        </div>
            <hr>
        {% endfor %}

    </div>
 {% endblock %}

winter.css

a  {
    color: green;
}

jason.css

a {
    color: red;
}

errorPage.html

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <link rel="icon" href="//common.cnblogs.com/favicon.ico" type="image/x-icon" />
    <title>404 页面不存在 - 博客园</title>
    <style type='text/css'>
        body {
            margin: 8% auto 0;
            max-width: 400px;
            min-height: 200px;
            padding: 10px;
            font-family: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
            font-size: 14px;
            padding-right: 200px;
            position: relative;
        }
        p { color: #555;margin: 15px 0px; }
        img { border: 0px; }
        .d { color: #404040; }
        .robot img { max-width: 192px; }
        .robot { position: absolute; top: 0; right: 0; }
    </style>
</head>
<body>
    <p style="margin-left: 5px;"><a href="/home/"><img src="//common.cnblogs.com/logo.svg" style="height:45px" alt="cnblogs"></a></p>
    <div style="margin-top:20px">
        <p style=""><b style="">404.</b> 抱歉,您访问的资源不存在。</p>
        <p class="d">可能是网址有误,或者对应的内容被删除,或者处于私有状态。</p>
        <p style="color:#777;">代码改变世界,联系邮箱 contact@cnblogs.com</p>
    </div>
    <div class="robot"><a href="//www.cnblogs.com/cmt/articles/13940458.html"><img src="//common.cnblogs.com/images/404-robot.png" alt="404 robot" /></a></div>
    <script async src="https://www.googletagmanager.com/gtag/js?id=G-4CQQXWHK3C"></script>
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());

      gtag('config', 'G-4CQQXWHK3C');
    </script>
</body>
</html>

image

个人站点侧边栏展示

views.py

def site_func(request,username):
    # 查询个人站点是否存在
    site_obj = models.Site.objects.filter(site_name=username).first()
    if not site_obj:
        return render(request,'errorPage.html')
    # 查询个人站点下所有的文章
    article_queryset = models.Article.objects.filter(site=site_obj)
    # 查询个人站点下所有的分类名称及每个分类下的文章数
    category_queryset = models.Category.objects.filter(site=site_obj).annotate(article_num=Count('article__pk')).values('name','article_num')
    # 查询个人站点下所有的标签名称及每个名称下的文章数
    tag_queryset = models.Tag.objects.filter(site=site_obj).annotate(article_num=Count('article__pk')).values('name','article_num')
    # 年月分组并统计文章个数
    from django.db.models.functions import TruncMonth
    date_queryset = models.Article.objects.filter(site=site_obj).annotate(month=TruncMonth('create_time')).values('month').annotate(article_num=Count('pk')).values('month','article_num')
    return render(request,'sitePage.html',locals())

sitePage.html

{% extends 'homePage.html' %}

{% block css %}
    <link rel="stylesheet" href="media/css/{{ site_obj.site_theme }}">
{% endblock %}

{% block title %}
    {{ site_obj.site_title }}
{% endblock %}

 {% block content %}
    <div class="col-md-2">
                <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title">文章分类</h3>
                  </div>
                  <div class="panel-body">
                      {% for category_obj in category_queryset %}
                      <p><a href="#">{{ category_obj.name }}({{ category_obj.article_num }})</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 tag_obj in tag_queryset %}
                        <p><a href="#">{{ tag_obj.name }}({{ tag_obj.article_num }})</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 date_obj in date_queryset %}
                        <p><a href="">{{ date_obj.month|date:'Y年m月' }}({{ date_obj.article_num }})</a></p>
                      {% endfor %}

                  </div>
                </div>

    </div>
    <div class="col-md-10">
        {% for article_obj in article_queryset %}
         <div class="media">
                        <h4 class="media-heading"><a href="#">{{ article_obj.title }}</a></h4>
                          <div class="media-body">
                            {{ article_obj.desc }}
                          </div>
                            <br>
                            <div class="pull-right">
                                <span>posted&nbsp;&nbsp;@</span>
                                <span>{{ article_obj.create_time|date:'Y-m-d H:i' }}&nbsp;&nbsp;</span>
                                <span>{{ article_obj.site.userinfo.username }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-thumbs-up">{{ article_obj.up_num }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-thumbs-down">{{ article_obj.down_num }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-comment">{{ article_obj.comment_num }}&nbsp;&nbsp;</span>
                            </div>
                        </div>
            <hr>
        {% endfor %}

    </div>
 {% endblock %}

settings.py

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False

image

侧边栏筛选功能

urls.py

   # 侧边栏筛选接口
    # path('<str:username>/category/<int:category_id>/', views.site_func),
    # path('<str:username>/tag/<int:tag_id>/', views.site_func),
    # path('<str:username>/archive/<str:yearAndmonth>/', views.site_func),
    # 上述三个路由可以合并成一个路由
    re_path('^(?P<username>\w+)/(?P<condition>category|tag|archive)/(?P<params>.*?)/',views.site_func),

views.py

def site_func(request,username, **kwargs):
    """
    :param kwargs: 接收多余的关键字参数 代码通过该参数是否有值从而得出是个人站点还是侧边栏筛选
    """
    # 查询个人站点是否存在
    site_obj = models.Site.objects.filter(site_name=username).first()
    if not site_obj:
        return render(request,'errorPage.html')
    # 查询个人站点下所有的文章
    article_queryset = models.Article.objects.filter(site=site_obj)
    if kwargs:
        condition = kwargs.get('condition')
        params = kwargs.get('params')
        if condition == 'category':
            article_queryset = article_queryset.filter(category_id=params)
        elif condition == 'tag':
            article_queryset = article_queryset.filter(tags__pk=params)
        else: # 年-月
            year, month = params.split('-')
            article_queryset = article_queryset.filter(create_time__year=year,create_time__month=month)
    # 查询个人站点下所有的分类名称及每个分类下的文章数
    category_queryset = models.Category.objects.filter(site=site_obj).annotate(article_num=Count('article__pk')).values('name','article_num','pk')
    # 查询个人站点下所有的标签名称及每个名称下的文章数
    tag_queryset = models.Tag.objects.filter(site=site_obj).annotate(article_num=Count('article__pk')).values('name','article_num','pk')
    # 年月分组并统计文章个数
    from django.db.models.functions import TruncMonth
    date_queryset = models.Article.objects.filter(site=site_obj).annotate(month=TruncMonth('create_time')).values('month').annotate(article_num=Count('pk')).values('month','article_num')
    return render(request,'sitePage.html',locals())

sitePage.html

{% extends 'homePage.html' %}

{% block css %}
    <link rel="stylesheet" href="media/css/{{ site_obj.site_theme }}">
{% endblock %}

{% block title %}
    {{ site_obj.site_title }}
{% endblock %}

 {% block content %}
    <div class="col-md-2">
                <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title">文章分类</h3>
                  </div>
                  <div class="panel-body">
                      {% for category_obj in category_queryset %}
                      <p><a href="/{{ site_obj.site_name }}/category/{{ category_obj.pk }}/">{{ category_obj.name }}({{ category_obj.article_num }})</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 tag_obj in tag_queryset %}
                        <p><a href="/{{ site_obj.site_name }}/tag/{{ tag_obj.pk }}/">{{ tag_obj.name }}({{ tag_obj.article_num }})</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 date_obj in date_queryset %}
                        <p><a href="/{{ site_obj.site_name }}/archive/{{ date_obj.month|date:'Y-m' }}/">{{ date_obj.month|date:'Y年m月' }}({{ date_obj.article_num }})</a></p>
                      {% endfor %}

                  </div>
                </div>

    </div>
    <div class="col-md-10">
        {% for article_obj in article_queryset %}
         <div class="media">
                        <h4 class="media-heading"><a href="#">{{ article_obj.title }}</a></h4>
                          <div class="media-body">
                            {{ article_obj.desc }}
                          </div>
                            <br>
                            <div class="pull-right">
                                <span>posted&nbsp;&nbsp;@</span>
                                <span>{{ article_obj.create_time|date:'Y-m-d H:i' }}&nbsp;&nbsp;</span>
                                <span>{{ article_obj.site.userinfo.username }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-thumbs-up">{{ article_obj.up_num }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-thumbs-down">{{ article_obj.down_num }}&nbsp;&nbsp;</span>
                                <span class="glyphicon glyphicon-comment">{{ article_obj.comment_num }}&nbsp;&nbsp;</span>
                            </div>
                        </div>
            <hr>
        {% endfor %}

    </div>
 {% endblock %}

侧边栏封装

urls.py

# 文章详情页
path('<str:username>/article/<int:article_id>/',views.article_detail_func),

views.py

def site_func(request,username, **kwargs):
    """
    :param kwargs: 接收多余的关键字参数 代码通过该参数是否有值从而得出是个人站点还是侧边栏筛选
    """
    # 查询个人站点是否存在
    site_obj = models.Site.objects.filter(site_name=username).first()
    if not site_obj:
        return render(request,'errorPage.html')
    # 查询个人站点下所有的文章
    article_queryset = models.Article.objects.filter(site=site_obj)
    if kwargs:
        condition = kwargs.get('condition')
        params = kwargs.get('params')
        if condition == 'category':
            article_queryset = article_queryset.filter(category_id=params)
                                        #category_id正向查询,外键字段自动加_id
        elif condition == 'tag':
            article_queryset = article_queryset.filter(tags__pk=params) # 反向查询,表名小写,多对多.
        else: # 年-月
            year, month = params.split('-')
            article_queryset = article_queryset.filter(create_time__year=year,create_time__month=month)

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


def article_detail_func(request,username,article_id):
    # 筛选谋篇具体的文章对象
    article_obj = models.Article.objects.filter(site__site_name=username).filter(pk=article_id).first()
    site_obj = models.Site.objects.filter(site_name=username).first()
    # 获取当前文章所有的评论数据
    comment_list = models.Comment.objects.filter(article=article_obj)
    return render(request,'articleDetailPage.html',locals())

mytag.py(app01应用下创建templatetags目录)

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

register = template.Library()


@register.inclusion_tag('leftmenu.html',name='mymenu')
def index(username):
    site_obj = models.Site.objects.filter(site_name=username).first()
    # 查询个人站点下所有的分类名称及每个分类下的文章数
    category_queryset = models.Category.objects.filter(site=site_obj).annotate(article_num=Count('article__pk')).values(
        'name', 'article_num', 'pk')
    # 查询个人站点下所有的标签名称及每个名称下的文章数
    tag_queryset = models.Tag.objects.filter(site=site_obj).annotate(article_num=Count('article__pk')).values('name','article_num','pk')
    # 年月分组并统计文章个数
    from django.db.models.functions import TruncMonth
    date_queryset = models.Article.objects.filter(site=site_obj).annotate(month=TruncMonth('create_time')).values('month').annotate(article_num=Count('pk')).values('month','article_num')
    return locals()

leftmenu.html

  <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title">文章分类</h3>
                  </div>
                  <div class="panel-body">
                      {% for category_obj in category_queryset %}
                      <p><a href="/{{ site_obj.site_name }}/category/{{ category_obj.pk }}/">{{ category_obj.name }}({{ category_obj.article_num }})</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 tag_obj in tag_queryset %}
                        <p><a href="/{{ site_obj.site_name }}/tag/{{ tag_obj.pk }}/">{{ tag_obj.name }}({{ tag_obj.article_num }})</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 date_obj in date_queryset %}
                        <p><a href="/{{ site_obj.site_name }}/archive/{{ date_obj.month|date:'Y-m' }}/">{{ date_obj.month|date:'Y年m月' }}({{ date_obj.article_num }})</a></p>
                      {% endfor %}

                  </div>
                </div>

articleDetailPage.html

{% extends 'homePage.html' %}


{% block css %}
    <link rel="stylesheet" href="media/css/{{ site_obj.site_theme }}">
{% endblock %}

{% block title %}
    {{ site_obj.site_title }}
{% endblock %}

 {% block content %}
    <div class="col-md-2">
    {% load mytag %}
    {% mymenu username %}
    </div>
    <div class="col-md-10">
        <h2 class="text-center">{{ article_obj.title }}</h2>
        {{ article_obj.content }}
    </div>
 {% endblock %}

homePage.html

 <div class="col-md-8">
                {% for article_obj in page_queryset %}
                        <div class="media">
                        <h4 class="media-heading"><a href="/{{ article_obj.site.userinfo.username }}/article/{{ article_obj.pk }}/">{{ article_obj.title }}</a></h4>

sitePage.html

    <div class="col-md-10">
        {% for article_obj in article_queryset %}
         <div class="media">
                        <h4 class="media-heading"><a href="/{{ article_obj.site.userinfo.username }}/article/{{ article_obj.pk }}/">{{ article_obj.title }}</a></h4>

文章详情内容准备
image
image

点赞点踩样式搭建

articleDetailPage.html

{% extends 'homePage.html' %}


{% block css %}
    <link rel="stylesheet" href="media/css/{{ site_obj.site_theme }}">
    <style>
        #div_digg {
            float: right;
            margin-bottom: 10px;
            margin-right: 30px;
            font-size: 12px;
            width: 125px;
            text-align: center;
            margin-top: 10px;
        }

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

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

        .clear {
            clear: both;
        }

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

{% block title %}
    {{ site_obj.site_title }}
{% endblock %}

 {% block content %}
    <div class="col-md-2">
    {% load mytag %}
    {% mymenu username %}
    </div>
    <div class="col-md-10">
        <h2 class="text-center">{{ article_obj.title }}</h2>
        {{ article_obj.content|safe }}
         {#            文章点赞点踩样式开始#}
        <div class="clearfix">
            <div id="div_digg">
                <div class="diggit upordown">
                    <span class="diggnum" id="digg_count">{{ article_obj.up_num }}</span>
                </div>
                <div class="buryit upordown">
                    <span class="burynum" id="bury_count">{{ article_obj.down_num }}</span>
                </div>

                <div class="clear"></div>
                <span style="color: red" id="d1"></span>
                <div class="diggword" id="digg_tips">
                </div>
            </div>
        </div>

        {#            文章点赞点踩样式结束#}
    </div>
 {% endblock %}

下载点赞点踩图片到static目录下的img图片
image
最后实现效果如下:
image

点赞点踩功能

urls.py

#放置在注销登录功能路由之下
# 文章点赞点踩
path('up_or_down/',views.up_or_down_func),

views.py

import json
from django.db.models import F

def up_or_down_func(request):
    """
    1.校验用户是否登录
    2.校验当前文章是否当前用户自己的
    3.校验当前文章是否已经被当前用户点过
    4.创建点赞点踩记录(不要忘记文章表中的优化字段 同步自增)
    """
    back_dict = {'code':10000, 'msg':''}
    if request.method == 'POST':
        if request.user.is_authenticated:
            article_pk = request.POST.get('article_pk')
            is_up = request.POST.get('is_up')  # true 普通的字符串
            article_obj = models.Article.objects.filter(pk=article_pk).first()
            if not article_obj.site.userinfo == request.user:
                is_click = models.UpAndDown.objects.filter(user=request.user,article=article_obj)#判断这个表是否有用户对这篇文章有做啥操作(赞或踩无所谓)
                if not is_click:
                    is_up = json.loads(is_up)  # 自动转换成python中布尔值
                    if is_up:
                        models.Article.objects.filter(pk=article_pk).update(up_num=F('up_num') +1)
                        back_dict['msg'] = '点赞成功'
                    else:
                        models.Article.objects.filter(pk=article_pk).update(down_num=F('down_num') + 1)
                        back_dict['msg'] = '点踩成功'
                    models.UpAndDown.objects.create(user=request.user,article=article_obj,is_up=is_up)
                else:
                    back_dict['code'] = 10001
                    back_dict['msg'] = '你已经点过了'
            else:
                back_dict['code'] = 10002
                back_dict['msg'] = '不能给自己点'
        else:
            back_dict['code'] = 10003
            back_dict['msg'] = '请先<a href="/login/">登录</a>'
        return JsonResponse(back_dict)

articleDetailPage.html

# 最下方
{% block js %}
    <script>
        // 给点赞点踩图标绑定点击事件
        $('.upordown').click(function (){
            let currentEre = $(this);
            let isUp = $(this).hasClass('diggit')  // 判断标签是否含有某个class值 从而二选一区分赞和踩
            // 发送ajax请求
            $.ajax({
                url:'/up_or_down/',  //点赞点踩有一定的逻辑 单独开设接口处理
                type: 'post',
                data:{
                    'csrfmiddlewaretoken':'{{ csrf_token }}',
                    'article_pk':'{{ article_obj.pk }}',
                    'is_up':isUp,
                },
                success: function (args) {
                    if (args.code === 10000){
                        currentEre.children().first().text(Number(currentEre.children().first().text())+1)
                    }
                   $('#d1').html(args.msg)
                }
            })
        })
    </script>
{% endblock %}

文章根评论

urls.py

# 文章评论
path('comment/',views.comment_func),

views.py

@login_required
def comment_func(request):
    back_dict = {'code':10000, 'msg':''}
    if request.method == 'POST':
        article_pk = request.POST.get('article_pk')
        content = request.POST.get('content')
        models.Article.objects.filter(pk=article_pk).update(comment_num=F('comment_num') +1)
        models.Comment.objects.create(user=request.user,article_id=article_pk,content=content)
        back_dict['msg'] = '评论成功'
        return JsonResponse(back_dict)

def article_detail_func(request,username,article_id):
	 # 获取当前文章所有的评论数据
    comment_list = models.Comment.objects.filter(article=article_obj)

articleDetailPage.html

       {#            文章点赞点踩样式结束#}
{#    文章评论楼的渲染开始#}
        <div class="comment_list">
            <ul class="list-group">

            {% for comment_obj in comment_list %}
                <li class="list-group-item">
                <span><a href="#">#{{ forloop.counter }}楼</a></span>
                <span>{{ comment_obj.comment_time|date:'Y-m-d H:i' }}</span>
                <span><a href="/{{ comment_obj.user.username }}/">{{ comment_obj.user.username }}</a></span>
                <p>
                    {{ comment_obj.content }}
                </p>
                </li>
            {% endfor %}
            </ul>
        </div>
        {#        文章评论楼的渲染结束#}
         {#        文章评论样式开始#}
        {% if request.user.is_authenticated %}
            <div class="comment_area">
                <p><span class="glyphicon glyphicon-comment"></span>发表评论</p>
                <textarea name="" id="comment" cols="30" rows="10" class="form-control"></textarea>
                <button class="btn btn-primary" id="commentBtn">提交评论</button>
            </div>
        {% else %}
            <p>
                <a href="/register/">注册</a>
                <a href="/login/">登录</a>
            </p>
        {% endif %}
     {#        文章评论样式结束#}

{% block js %}
    <script>
	        // 给提交评论的按钮绑定点击事件
        $('#commentBtn').click(function () {
            $.ajax({
                url: '/comment/',
                type: 'post',
                data: {
                    'csrfmiddlewaretoken': '{{ csrf_token }}',
                    'article_pk': '{{ article_obj.pk }}',
                    'content': $('#comment').val(),
                },
                success: function (args) {

                }
            })
        })

    </script>
{% endblock %}

根评论完善

articleDetailPage.html

        // 给提交评论的按钮绑定点击事件
        $('#commentBtn').click(function () {
            // 获取用户评论的内容
            let commentMsg = $('#comment').val();
            let currentUserName = '{{ request.user.username }}'
            $.ajax({
                url: '/comment/',
                type: 'post',
                data: {
                    'csrfmiddlewaretoken': '{{ csrf_token }}',
                    'article_pk': '{{ article_obj.pk }}',
                    'content': $('#comment').val(),
                },
                success: function (args) {
                    if (args.code === 10000){
                        //清空评论框里面的内容
                        $('#comment').val('');
                        // 动态创建标签并添加到评论楼中
                        let tempComment = `
                             <li class="list-group-item">
                            <span class="glyphicon glyphicon-comment"><a href="/${currentUserName}/">${currentUserName}</a></span>
                            <p>
                                ${commentMsg}
                            </p>
                            </li>
                        `
                        // 查找ul标签然后添加上述的标签即可
                        $('.list-group').append(tempComment)
                    }
                }
            })
        })

子评论回复按钮

articleDetailPage.html

 {% for comment_obj in comment_list %}
                <li class="list-group-item">
                <span><a href="#">#{{ forloop.counter }}楼</a></span>
                <span>{{ comment_obj.comment_time|date:'Y-m-d H:i' }}</span>
                <span><a href="/{{ comment_obj.user.username }}/">{{ comment_obj.user.username }}</a></span>
                    <p class="pull-right"><a href="#">引用&nbsp;&nbsp;</a></p>
                    <p class="pull-right"><a href="#" class="reply" username="{{ comment_obj.user.username }}">回复&nbsp;&nbsp;</a></p>
                    <p>
                    {{ comment_obj.content }}
                </p>
                </li>

 //给回复按钮绑定点击事件
        $('.reply').click(function (){
            //获取回复按钮所在的评论用户名
            let targetUserName = $(this).attr('username');
            $('#comment').val('@'+targetUserName +'\n').focus()
        })

子评论功能完善

views.py

@login_required
def comment_func(request):
    back_dict = {'code':10000, 'msg':''}
    if request.method == 'POST':
        article_pk = request.POST.get('article_pk')
        content = request.POST.get('content')
        parent_id = request.POST.get('parent_id')  # 直接获取即可,无需关心是否有值
        models.Article.objects.filter(pk=article_pk).update(comment_num=F('comment_num') +1)
        models.Comment.objects.create(user=request.user,article_id=article_pk,content=content,parent_id=parent_id)
        back_dict['msg'] = '评论成功'
        return JsonResponse(back_dict)

articleDetailPage.html

       {#        文章评论楼的渲染开始#}
        <div class="comment_list">
            <ul class="list-group">
                {% for comment_obj in comment_list %}
                    <li class="list-group-item">
                        <span><a href="#">#{{ forloop.counter }}楼</a></span>
                        <span>{{ comment_obj.comment_time|date:'Y-m-d H:i' }}</span>
                        <span><a href="/{{ comment_obj.user.username }}/">{{ comment_obj.user.username }}</a></span>
                        <p class="pull-right"><a href="#">引用&nbsp;&nbsp;</a></p>
                        <p class="pull-right"><a href="#" class="reply" username="{{ comment_obj.user.username }}" comment_id="{{ comment_obj.pk }}">回复&nbsp;&nbsp;</a></p>
                        <p>
                            {% if comment_obj.parent_id %}
                                @{{ comment_obj.parent.user.username }}
                            {% endif %}
                        </p>
                        <p>
                            {{ comment_obj.content }}
                        </p>
                    </li>
                {% endfor %}
            </ul>
        </div>
        {#        文章评论楼的渲染结束#}
        {#        文章评论样式开始#}
        {% if request.user.is_authenticated %}
            <div class="comment_area">
                <p><span class="glyphicon glyphicon-comment"></span>发表评论</p>
                <textarea name="" id="comment" cols="30" rows="10" class="form-control"></textarea>
                <button class="btn btn-primary" id="commentBtn">提交评论</button>
            </div>
        {% else %}
            <p>
                <a href="/register/">注册</a>
                <a href="/login/">登录</a>
            </p>
        {% endif %}

        {#        文章评论样式结束#}



        // 提前创建一个全局变量 用于存储评论主键值
        let parentId = null;
        // 给提交评论的按钮绑定点击事件
        $('#commentBtn').click(function () {
            // 获取用户评论的内容
            let commentMsg = $('#comment').val();
            let currentUserName = '{{ request.user.username }}';
            let oldCommentMsg = commentMsg;
            // 如果发送的是子评论 那么需要处理掉前缀内容(前端可以做 后端也可以做)
            if(parentId){
                commentMsg = commentMsg.slice(commentMsg.indexOf('\n') + 1)
            }
            $.ajax({
                url: '/comment/',
                type: 'post',
                data: {
                    'csrfmiddlewaretoken': '{{ csrf_token }}',
                    'article_pk': '{{ article_obj.pk }}',
                    'content': commentMsg,
                    'parent_id':parentId
                },
                success: function (args) {
                    if (args.code === 10000) {
                        // 清空评论框里面的内容
                        $('#comment').val('');
                        // 动态创建标签并添加到评论楼中
                        let tempComment = `
                        <li class="list-group-item">
                            <span class="glyphicon glyphicon-comment"><a href="/${currentUserName}/">${currentUserName}</a></span>
                            <p>
                                ${oldCommentMsg}
                            </p>
                        </li>
                        `
                        // 查找ul标签然后添加上述的标签即可
                        $('.list-group').append(tempComment)
                        // 清空全局变量
                        parentId = null;
                    }
                }
            })
        })

        // 给回复按钮绑定点击事件
        $('.reply').click(function () {
            // 获取回复按钮所在的评论用户名
            let targetUserName = $(this).attr('username');
            // 获取回复按钮所在的评论主键值 修改全局变量
            parentId = $(this).attr('comment_id');
            $('#comment').val('@' + targetUserName + '\n').focus();

        })
    </script>

后台管理页面搭建

urls.py

# 后台管理接口
path('backend/',views.backend_func),

views.py

@login_required
def backend_func(request):
    site_obj = models.Site.objects.filter(site_name=request.user.username).first()
    # 获取当前站点下所有的文章
    article_queryset = models.Article.objects.filter(site=site_obj)

    page_obj = mypage.Pagination(current_page=request.GET.get('page'), all_count=article_queryset.count())
    page_queryset = article_queryset[page_obj.start:page_obj.end]
    return render(request,'backend/backend.html',locals())

backendBasePage.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
      <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
    {% load static %}
    <link rel="stylesheet" href="{%  static 'bootstrap-3.4.1-dist/css/bootstrap.css' %}">
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.js' %}"></script>
    {% block css %}

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

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">博客 <span class="sr-only">(current)</span></a></li>
        <li><a href="#">文章</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多详情 <span class="caret"></span></a>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="搜索">
        </div>
        <button type="submit" class="btn btn-default">提交</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
          {% if request.user.is_authenticated %}
                    <li><a href="#">{{ request.user.username }}</a></li>
                    <li class="dropdown">
                      <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">更多操作<span class="caret"></span></a>
                      <ul class="dropdown-menu">
                        <li><a href="#" data-toggle="modal" data-target="#myModal">修改密码</a></li>
                        <li><a href="#">修改头像</a></li>
                        <li><a href="#">后台管理</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="/logout/">注销登录</a></li>
                      </ul>
                    </li>
              {% else %}
                    <li><a href="{% url 'register_view' %}">注册</a></li>
                    <li><a href="{% url 'login_view' %}">登录</a></li>
          {% endif %}

      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
<!--导航条结束-->

<!--模态框开始-->

<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title text-center" id="myModalLabel">修改密码</h4>
      </div>
      <div class="modal-body">
         <div class="form-group">
             <label for="">用户名</label>
             <input type="text" value="{{ request.user.username }}" disabled class="form-control">
         </div>
          <div class="form-group">
             <label for="">原密码</label>
             <input type="text" id="old_pwd" class="form-control">
         </div>
          <div class="form-group">
             <label for="">新密码</label>
             <input type="text" id="new_pwd" class="form-control">
         </div>
          <div class="form-group">
             <label for="">确认密码</label>
             <input type="text" id="confirm_pwd" class="form-control">
         </div>
      </div>
      <div class="modal-footer">
          <span id="error" style="color: red"></span>
        <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
        <button type="button" class="btn btn-warning" id="setBtn">修改</button>
      </div>
    </div>
  </div>
</div>
<!--模态框结束-->

<!--内容区域开始 -->
    <div class="container-fluid">
        <div class="row">
            <div class="col-md-2">
                <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
                <div class="panel panel-default">
                    <div class="panel-heading" role="tab" id="headingOne">
                        <h4 class="panel-title">
                            <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne"
                               aria-expanded="true" aria-controls="collapseOne">
                                博客后台
                            </a>
                        </h4>
                    </div>
                    <div id="collapseOne" class="panel-collapse collapse in" role="tabpanel"
                         aria-labelledby="headingOne">
                        <div class="panel-body">
                            <p><a href="/add_article/">添加文章</a></p>
                            <p><a href="#">添加分类</a></p>
                            <p><a href="#">添加标签</a></p>
                            <p><a href="#">更多操作</a></p>
                        </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">
                            Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad
                            squid.
                            3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt
                            laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin
                            coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes
                            anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings
                            occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't
                            heard
                            of them accusamus labore sustainable VHS.
                        </div>
                    </div>
                </div>
                <div class="panel panel-default">
                    <div class="panel-heading" role="tab" id="headingThree">
                        <h4 class="panel-title">
                            <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion"
                               href="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
                                友情链接
                            </a>
                        </h4>
                    </div>
                    <div id="collapseThree" class="panel-collapse collapse" role="tabpanel"
                         aria-labelledby="headingThree">
                        <div class="panel-body">
                            Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad
                            squid.
                            3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt
                            laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin
                            coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes
                            anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings
                            occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't
                            heard
                            of them accusamus labore sustainable VHS.
                        </div>
                    </div>
                </div>
            </div>
            </div>

         {% block content %}
            <div class="col-md-10">
                <div>

                    <!-- Nav tabs -->
                    <ul class="nav nav-tabs" role="tablist">
                        <li role="presentation" class="active"><a href="#home" aria-controls="home" role="tab"
                                                                  data-toggle="tab">文章</a></li>
                        <li role="presentation"><a href="#profile" aria-controls="profile" role="tab"
                                                   data-toggle="tab">随笔</a></li>
                        <li role="presentation"><a href="#messages" aria-controls="messages" role="tab"
                                                   data-toggle="tab">日记</a>
                        </li>
                        <li role="presentation"><a href="#settings" aria-controls="settings" role="tab"
                                                   data-toggle="tab">配置</a>
                        </li>
                        <li role="presentation"><a href="#others" 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 articleConetent %}

                            {% 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 role="tabpanel" class="tab-pane" id="others">其他列表</div>
                    </div>

                </div>
            </div>
        {% endblock %}
        </div>
    </div>
<!--内容区域结束 -->

<script>
    $('#setBtn').click(function (){
        $.ajax({
            url:'/set_pwd/',
            type:'post',
            data: {
                'old_pwd': $('#old_pwd').val(),
                'new_pwd': $('#new_pwd').val(),
                'confirm_pwd': $('#confirm_pwd').val(),
                'csrfmiddlewaretoken': '{{ csrf_token }}',
            },
            success: function (args) {
                if (args.code === 10000) {
                    window.location.href = args.url
                }else{
                    $('#error').text(args.msg)
                }
            }
        })
    })
</script>
{% block js %}

{% endblock %}

</body>
</html>

backend.html

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

{% block css %}
    <link rel="stylesheet" href="media/css/{{ site_obj.site_theme }}">
{% endblock %}

{% block title %}
    {{ site_obj.site_title }}
{% endblock %}

{% block articleConetent %}
    <table class="table table-hover table-striped">
    <thead>
    <tr>
        <th>标题</th>
        <th>时间</th>
        <th>评论数</th>
        <th>点赞数</th>
        <th>点踩数</th>
        <th>操作</th>
        <th>操作</th>
    </tr>
    </thead>
    <tbody>
    {% for article_obj in page_queryset %}
        <tr>
        <td><a href="/{{ site_obj.site_name }}/article/{{ article_obj.pk }}/">{{ article_obj.title }}</a></td>
        <td>{{ article_obj.create_time|date:'Y-m-d H:i:s' }}</td>
        <td>{{ article_obj.comment_num }}</td>
        <td>{{ article_obj.up_num }}</td>
        <td>{{ article_obj.down_num }}</td>
        <td><a href="#">编辑</a></td>
        <td><a href="#">删除</a></td>
    </tr>
    {% endfor %}


    </tbody>
    </table>
    <div class="pull-right">{{ page_obj.page_html|safe }}</div>
{% endblock %}

image
image

后台文章添加简易页面搭建

urls.py

# 后台管理之添加文章接口
path('add_article/',views.add_article_func),

views.py

@login_required
def add_article_func(request):
    site_obj = models.Site.objects.filter(site_name=request.user.username).first()
    category_list = models.Category.objects.filter(site=site_obj)
    tag_list = models.Tag.objects.filter(site=site_obj)
    return render(request,'backend/addArticlePage.html',locals())

addArticlePage.html

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


{% block css %}
    <link rel="stylesheet" href="media/css/{{ site_obj.site_theme }}">
{% endblock %}

{% block title %}
    {{ site_obj.site_title }}
{% endblock %}

{% block articleConetent %}
    <h2 class="text-center">添加文章</h2>
    <form action="" method="post">
    {% csrf_token %}
    <p>文章标题</p>
        <input type="text" name="title" class="form-control">
    <p>文章内容</p>
        <textarea name="content" id="mycontent" cols="30" rows="10" class="form-control"></textarea>
    <p>文章分类</p>
    <p>
        {% for category_obj in category_list %}
            <input type="radio" name="category" value="{{ category_obj.pk }}"> {{ category_obj.name }}
        {% endfor %}
    </p>
    <p>文章标签</p>
    <p>
        {% for tag_obj in tag_list %}
            <input type="checkbox" name="tag" value="{{ tag_obj.pk }}">{{ tag_obj.name }}
        {% endfor %}
    </p>
        <input type="submit" class="form-control btn btn-success btn-block" value="发布">

    </form>
{% endblock %}

image

富文本编辑器kindeditor

下载地址:http://kindeditor.net/down.php
官网地址:http://kindeditor.net/docs/usage.html
参数参考:http://kindeditor.net/docs/option.html
addArticlePage.html

{% block js %}
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
<script>
        KindEditor.ready(function(K) {
                window.editor = K.create('#mycontent',{
                    width: '100%',
                    height:'600px',
                    resizeType:'1',
                });
        });
</script>
{% endblock %}

image

添加文章初步实现

views.py

@login_required
def add_article_func(request):
    site_obj = models.Site.objects.filter(site_name=request.user.username).first()
    if request.method == 'POST':
        title= request.POST.get('title')
        content = request.POST.get('content')
        category_id = request.POST.get('category')
        tag_list = request.POST.getlist('tag')
        article_obj = models.Article.objects.create(
            title=title,
            desc=content[0:150],  # 简单处理
            content=content,
            site=site_obj,
            category_id=category_id
        )
        # 自己去操作文章和标签的第三张表 无法使用add set remove clear
        for tag_id in tag_list:
            models.Article2Tag.objects.create(article=article_obj,tag_id=tag_id)
        return redirect('/backend/')
    category_list = models.Category.objects.filter(site=site_obj)
    tag_list = models.Tag.objects.filter(site=site_obj)
    return render(request,'backend/addArticlePage.html',locals())

添加文章代码优化

下载第三方模块bs4和lxml
views.py

from bs4 import BeautifulSoup
@login_required
def add_article_func(request):
    site_obj = models.Site.objects.filter(site_name=request.user.username).first()
    if request.method == 'POST':
        title= request.POST.get('title')
        content = request.POST.get('content')
        category_id = request.POST.get('category')
        tag_list = request.POST.getlist('tag')
        # 将文章内容交给bs4模块处理
        soup = BeautifulSoup(content,'lxml')  # 第二个参数是解析器 不同的解析器功能不一样 最好用是lxml 需要提前下载
        tags = soup.find_all()
        for tag in tags:
            if tag.name == 'script':
                tag.decompose()  # 删除script标签
        article_obj = models.Article.objects.create(
            title=title,
            desc=soup.text[0:150],  # 获取文本然后切割150个字符
            content=str(soup),  # 使用处理之后的内容(不包含script标签)
            site=site_obj,
            category_id=category_id
        )
        # 自己去操作文章和标签的第三张表 无法使用add set remove clear
        # for tag_id in tag_list:
        #     models.Article2Tag.objects.create(article=article_obj,tag_id=tag_id)
        tag_obj_list = []
        for tag_id in tag_list:
            article2tag_obj = models.Article2Tag(article=article_obj,tag_id=tag_id)
            tag_obj_list.append(article2tag_obj)
        models.Article2Tag.objects.bulk_create(tag_obj_list)  # 这里还可以使用列表生成式再次缩进代码
        return redirect('/backend/')
    category_list = models.Category.objects.filter(site=site_obj)
    tag_list = models.Tag.objects.filter(site=site_obj)
    return render(request,'backend/addArticlePage.html',locals())

编辑器上传图片

urls.py

# 后台管理之文章上传图片
path('upload_img/',views.upload_img_func),

views.py

from django.conf import settings
import os

@login_required
def upload_img_func(request):
    # 返回的数据格式也是有要求的
    # http: // kindeditor.net / docs / option.html  # extrafileuploadparams
    # http: // kindeditor.net / docs / upload.html
    """
        //成功时
        {
                "error" : 0,
                "url" : "http://www.example.com/path/to/file.ext"
        }
        //失败时
        {
                "error" : 1,
                "message" : "错误信息"
        }
    """
    back_dict = {
        "error":0,
    }
    # 获取编辑器上传的图片数据
    img_obj = request.FILES.get('imgFile')
    # 拼接文章图片所存放的路径
    img_dir_path = os.path.join(settings.BASE_DIR,'media','article')
    if not os.path.exists(img_dir_path):
        os.makedirs(img_dir_path)
    img_file_path = os.path.join(img_dir_path,f'{img_obj.name}')  # 针对文件名 为了防止冲突可以添加唯一标识 比如:用户名+当前时间+文件名
    with open(img_file_path,'wb') as f:
        for line in img_obj:
            f.write(line)
    back_dict['url'] = '/media/article/%s' % img_obj.name  # 不能返回后端绝对路径 而应该是暴露的接口路径
    return JsonResponse(back_dict)

addArticlePage.html

<script>
        KindEditor.ready(function(K) {
                window.editor = K.create('#mycontent',{
                    width: '100%',
                    height:'600px',
                    resizeType:'1',
                    uploadJson: '/upload_img/',
                    extraFileUploadParams: {
                        csrfmiddlewaretoken:'{{ csrf_token }}',
                    }
                });
        });
</script>

后台管理之文章编辑

urls.py

# 后台管理之文章编辑
path('edit_article/<int:article_pk>/',views.edit_article_func),

views.py

@login_required
def edit_article_func(request,article_pk):
    site_obj = models.Site.objects.filter(site_name=request.user.username).first()
    article_obj = models.Article.objects.filter(pk=article_pk).first()
    if request.method == 'POST':
        title = request.POST.get('title')
        content = request.POST.get('content')
        category_id = request.POST.get('category')
        tag_list = request.POST.getlist('tag')
        # 将文章内容交给bs4模块处理
        soup = BeautifulSoup(content,'lxml')  #第二个参数是解析器 不同的解析器功能不一样 最好用是lxml 需要提前下载
        tags = soup.find_all()
        for tag in tags:
            if tag.name == 'script':
                tag.decompose()  # 删除script标签
        models.Article.objects.filter(pk=article_pk).update(
            title=title,
            desc=soup.text[0:150], # 获取文本然后切割150个字符
            content=str(soup),  # 使用处理之后的内容(不包含script标签)
            site=site_obj,
            category_id=category_id
        )
        models.Article2Tag.objects.filter(article_id=article_pk).delete()
        tag_obj_list = []
        for tag_id in tag_list:
            article2tag_obj = models.Article2Tag(article=article_obj,tag_id=tag_id)
            tag_obj_list.append(article2tag_obj)
        models.Article2Tag.objects.bulk_create(tag_obj_list)
        return redirect('/backend/')
    category_list = models.Category.objects.filter(site=site_obj)
    tag_list = models.Tag.objects.filter(site=site_obj)
    return render(request,'backend/editArticlePage.html',locals())

editArticlePage.html

# 从添加文章页面拷贝改改即可
{% extends 'backend/backendBasePage.html' %}


{% block css %}
    <link rel="stylesheet" href="media/css/{{ site_obj.site_theme }}">
{% endblock %}

{% block title %}
    {{ site_obj.site_title }}
{% endblock %}

{% block articleConetent %}
    <h2 class="text-center">编辑文章</h2>
    <form action="" method="post">
    {% csrf_token %}
    <p>文章标题</p>
        <input type="text" name="title" class="form-control" value="{{ article_obj.title }}">
    <p>文章内容</p>
        <textarea name="content" id="mycontent" cols="30" rows="10" class="form-control">{{ article_obj.content }}</textarea>
    <p>文章分类</p>
    <p>
        {% for category_obj in category_list %}
            {% if article_obj.category == category_obj %}
                <input type="radio" name="category" value="{{ category_obj.pk }}" checked> {{ category_obj.name }}
            {% else %}
            <input type="radio" name="category" value="{{ category_obj.pk }}"> {{ category_obj.name }}
            {% endif %}
        {% endfor %}
    </p>
    <p>文章标签</p>
    <p>
        {% for tag_obj in tag_list %}
            {% if tag_obj in article_obj.tags.all %}
            <input type="checkbox" name="tag" value="{{ tag_obj.pk }}" checked>{{ tag_obj.name }}
            {% else %}
            <input type="checkbox" name="tag" value="{{ tag_obj.pk }}">{{ tag_obj.name }}
             {% endif %}
        {% endfor %}
    </p>
        <input type="submit" class="form-control btn btn-success btn-block" value="发布">

    </form>
{% endblock %}

{% block js %}
    <script charset="utf-8" src="/static/kindeditor/kindeditor-all-min.js"></script>
    <script charset="utf-8" src="/static/kindeditor/lang/zh-CN.js"></script>
<script>
        KindEditor.ready(function(K) {
                window.editor = K.create('#mycontent',{
                    width: '100%',
                    height:'600px',
                    resizeType:'1',
                    uploadJson: '/upload_img/',
                    extraFileUploadParams: {
                        csrfmiddlewaretoken:'{{ csrf_token }}',
                    }
                });
        });
</script>
{% endblock %}

backend.html

<td><a href="/edit_article/{{ article_obj.pk }}/">编辑</a></td>

后台管理之文章删除

urls.py

# 后台管理之文章删除
path('delete_article/',views.delete_article_func),

views.py

@login_required
def delete_article_func(request):
    back_dict = {'code': 10000, 'msg':''}
    if request.method == 'POST':
        article_pk = request.POST.get('article_pk')
        models.Article.objects.filter(pk=article_pk).delete()
        models.Article2Tag.objects.filter(article_id=article_pk).delete()
        back_dict['msg'] = '删除成功 准备跑路'
        return JsonResponse(back_dict)

backend.html

<td><a href="#" class="delBtn" article_pk="{{ article_obj.pk }}">删除</a></td>
{% block js %}
    <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>

{#  官网地址参考  https://sweetalert.js.org/guides/#}
    <script>
    $('.delBtn').click(function (){
        let currentBtn = $(this);
    swal({
        title: "你确认要删除吗?",
        text: "一旦删除了 后果自负哦!",
        icon: "warning",
        buttons: true,
        dangerMode: true,
    })
        .then((willDelete) => {
            if (willDelete) {
                // 用户点击ok按钮  发送ajax请求
                $.ajax({
                    url:'/delete_article/',
                    type:'post',
                    data:{'csrfmiddlewaretoken':'{{ csrf_token }}','article_pk':$(this).attr('article_pk')},
                    success:function (args){
                        if(args.code === 10000){
                            swal(args.msg, {
                            icon: "success",
                            });
                            //使用DOM操作 临时刷新页面
                            currentBtn.parent().parent().remove()
                        }   else{
                            swal(args.msg);
                        }
                    }
                })

                } else {
                    swal("数据都不敢删!");
                }
        });
   })
    </script>
{% endblock %}

后台管理之修改头像

urls.py

# 用户头像修改
path('set_avatar/',views.set_avatar_func),

views.py

@login_required
def set_avatar_func(request):
    new_avatar = request.FILES.get('new_avatar')
    # models.UserInfo.objects.filter(pk=request.user.pk).update(avatar=new_avatar)  # update不会再自动加avatar前缀
    user_obj = models.UserInfo.objects.filter(pk=request.user.pk).first()
    user_obj.avatar = new_avatar
    user_obj.save()  # 保存数据
    return redirect('/backend/')

backendBasePage.html

{% if request.user.is_authenticated %}
                    <li><a href="#">{{ request.user.username }}</a></li>
# 增加入下内容
<li style="width:50px;height: 50px;border-radius: 50%;overflow: hidden;display: block"><img
                            src="/media/{{ request.user.avatar }}/" alt="" style="max-width: 100%"></li>
#修改
<li><a href="#" data-toggle="modal" data-target="#myavatar">修改头像</a></li>
# 增加模态框
<div class="modal fade" id="myavatar" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title text-center" id="myModalLabel">修改头像</h4>
      </div>
      <div class="modal-body">
          <form action="/set_avatar/" method="post" enctype="multipart/form-data">
              {% csrf_token %}
              <div class="form-group">
                  <label for="">用户名</label>
              <input type="text" value="{{ request.user.username }}" disabled class="form-control">
              </div>
              <div class="form-group">
                  <label for="">原头像</label>
                  <img src="media/{{ request.user.avatar }}/" alt="" width="120">
              </div>
              <div class="form-group">
                  <label for="myfile">新头像
                      <img src="/static/img/default.jpg" alt="" width="120" id="myimg">
                  </label>
                  <input type="file" id="myfile" style="display: none" name="new_avatar">
              </div>
              <input type="submit" class="btn btn-warning btn-block" value="提交">
          </form>

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

script标签下增加入下内容:
 // 1.用户头像的实时展示
        $('#myfile').change(function (){
            // 1.产生一个文件阅读器对象
            let myFileReaderObj = new FileReader();
            // 2.获取用户上传的头像文件
            let fileObj = this.files[0];
            // 3.将文件对象交给阅读器对象读取
            myFileReaderObj.readAsDataURL(fileObj);
            // 等待文件阅读器对象加载完毕之后再修改src
            myFileReaderObj.onload = function (){
                // 4.修改img标签的src属性展示图片
                $('#myimg').attr('src',myFileReaderObj.result)
            }
        })
点击查看代码
 $('#myfile').change(function (){
            // 1.产生一个文件阅读器对象
            let myFileReaderObj = new FileReader();
            // 2.获取用户上传的头像文件
            let fileObj = this.files[0];
            // 3.将文件对象交给阅读器对象读取
            myFileReaderObj.readAsDataURL(fileObj);
            // 等待文件阅读器对象加载完毕之后再修改src
            myFileReaderObj.onload = function (){
                // 4.修改img标签的src属性展示图片
                $('#myimg').attr('src',myFileReaderObj.result)
            }
        })
posted @   悠悠-winter  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示