BBS项目

项目的前期准备

1.django2.2     创建一个django目录
(需要配置环境变量和数据库)

'DIRS': [os.path.join(BASE_DIR, 'templates'), ]  
    
2.数据库准备 我这里创建了一个库名为sql0107的数据库

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

第一步是创建表 

表分析
	先确定表的数量 再确定表的基础字段 最后确定表的外键字段
		1.用户表
		2.个人站点表
		3.文章表
		4.文章分类表
		5.文章标签表
 		6.点赞点踩表
		7.文章评论表
基础字段分析
	'''下列表字段设计仅供参考 你可以有更多的想法'''
	用户表
		替换auth_user表并扩展额外的字段
  		 	电话号码、头像、注册时间
	个人站点表
    	站点名称(jason\lili\kevin)
    	站点标题(努力奋斗去他妹的)
		站点样式(css文件)
	文章表
    	文章标题
    	文章简介
   	文章内容
    	发布时间

 	文章分类表
    	分类名称

	文章标签表
    	标签名称

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

	文章评论表:记录哪个用户给哪篇文章评论了什么内容
   		用户字段(用户主键)>>>:外键字段
 		文章字段(文章主键)>>>:外键字段
        评论内容
   	评论时间
    	外键字段(自关联)
		"""
		id	user_id  article_id  content parent_id
		1      1         1        哈哈哈    null
		2      2         1        哈你妹     1
		3      3         1        讲文明     2
		"""  
外键字段
	用户表
		用户与个人站点是一对一外键关系
        
	个人站点表
    	
	文章表
    	文章表与个人站点表是一对多外键关系
   		文章表与文章分类表是一对多外键关系
    	文章表与文章标签表是多对多外键关系
       '''
       数据库字段优化设计:我们想统计文章的评论数 点赞数
       		通过文章数据跨表查询到文章评论表中对应的数据统计即可
       但是文章需要频繁的展示 每次都跨表查询的话效率极低
            我们在文章表中再创建三个普通字段
            之后只需要确保每次操作评论表或者点赞点踩表时同步修改上述三个普通字段即可
       '''
    	文章评论数
        文章点赞数
    	文章点踩数

 	文章分类表
    	文章分类与个人站点是一对多外键关系
        
	文章标签表
    	文章标签与个人站点是一对多外键关系

模型层代码:models.py

特别重要:
创建表之前因为用户表继承AbstractUser 要在设置里面加上
AUTH_USER_MODEL = 'app01.UserInfo'

模型层代码:
from django.db import models

# Create your models here.

from django.contrib.auth.models import AbstractUser


class UserInfo(AbstractUser):
    """用户表"""
    phone = models.BigIntegerField(verbose_name='手机号', null=True, blank=True)
    avatar = models.FileField(upload_to='media/avatar/', default='media/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_theme}'


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)

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

    """文章与个人站点一对多外键"""
    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'), )

    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)

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


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='点赞点踩')

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


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)

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

然后执行数据库迁移命令 添加数据库 (这里使用mysql)

注册功能

1.先开启路由
2.返回一个用户的注册页面
3.引入静态文件 创建static  引入bootstrap文件
4.构建前端注册页面
    1.渲染前端标签    (采用一个空的form标签)
    2.获取用户数据
    3.校验用户数据
    4.展示错误提示
ps: 用forms组件、modelform组件
    单独开设接口进行编写   解耦合
5.用户头像的实时展示 固定代码  注意标签名字
6.给注册按钮绑定点击事件
   1.循环form标签内数据  添加数据  加上头像文件
   2.发送ajax请求携带文件数据必要配置
          contentType:false,
          processData: false,
        注册完成后跳转
        循环发送form内数据
        渲染错误信息(查找标签)
        
   3.给所有标签渲染错误样式  聚焦移除错误样式
7.后端代码的编写
     用户上传头像的判断
     创建信息时注意用create_user 替换了auth表(**clean_data)

forms校验文件代码

单独创建一个myforms.py文件

from django import forms
from app01 import models
from django.forms import widgets


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

以下前端校验的截图 有几个点需要注意一下:

BBS升级版-小白菜博客
image

image

forms校验文件代码

from django import forms
from app01 import models
from django.forms import widgets


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

注册功能前端代码

<!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.min.css' %}">
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</head>
<body>
    <div class="container">
        <div class="col-md-8 col-md-offset-2">
            <h2 class="text-center">用户注册</h2>
            <form id="form"> <!--不使用form表单提交数据 但是用一下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="" id="myimg" width="120">
                    </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 myFilReaderObj = new FileReader();
            // 2.获取用户上传的头像文件
            let fileObj = this.files[0];
            //3.将文件对象交给阅读器对象读取
            myFilReaderObj.readAsDataURL(fileObj); //异步
            // 等待文件阅读器对象加载完毕之后再修改src
            myFilReaderObj.onload = function (){
             //4.修改img标签的src 属性展示图片
            $('#myimg').attr('src',myFilReaderObj.result)
            }

        })

        //2.给注册按钮绑定点击事件 发送ajax 携带了文件数据
        $('#subBtn').click(function (){
            //1.先产生一个内置对象
            let myFormDataObj = new FormData();
            //2.添加普通数据(单个单个的编写效率极低)
            {#console.log($('#form').serializeArray())  // 可以一次性获取form标签内所有普通字段数据 [{数组},{数组},{数组}]#}
            $.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{
                        {#console.log(args.msg)#}
                        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>

注册功能后端代码

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

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)  # 上述处理字典的目的就是为了创建数据省事
            # return HttpResponse('注册成功!!!!')
            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())

登录功能

1.开设路由
2.后端先返回一个loginPage.html页面
3.登录页面搞一个图片验证码的效果(后端代码编写)
      需要用到:1.pillow模块
               2.io模块
               3.random模块   
    下载模块经常会报错 因为源有问题
     这里直接终端执行  注意模块名手写
      pip3.8 install 模块名 -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com

      点击图片实时刷新验证码
      保存验证码便于后端校验
4.渲染登录按钮
    发送ajax请求
    正确就跳转到主页
    或者显示错误提示
        1.alert(错误内容)
        2.使用sweetalert 插件美化展示 需要引入sweeralert  cdn
        <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>

5.后端代码编写
      auth模块帮助校验
      1.先搞个验证字典
         back_dict = {'code': 10000, 'msg': ''}
      2.获取前端数据 比对 
            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)
       3.保存用户登录状态
       4.错误信息添加

路由展示

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

图片验证码代码:

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):
    # 1.推导步骤1:直接读取图片文件返回
    # with open(r'D:\djangoProject\BBS\app01\avatar\555.jpg', 'rb') as f:
    #     data = f.read()
    # return HttpResponse(data)
    # # 2.推导步骤2:随机产生图片动态返回  pillow模块
    # img_obj = Image.new('RGB',(350,35),'yellow')
    # with open(r'xxx.png','wb')as f:
    #     img_obj.save(f,'png')
    # with open(r'xxx.png','rb')as f:
    #     data = f.read()
    # return HttpResponse(data)
    # 3.推导步骤3:针对图片的保存与读取 做优化 内存管理器
    # img_obj = Image.new('RGB', (350, 35), 'yellow')
    # io_obj = BytesIO()
    # img_obj.save(io_obj, 'png')
    # return HttpResponse(io_obj.getvalue())
    # 4.推导步骤4:图片颜色可以随机变换
    # img_obj = Image.new('RGB', (350, 35), get_random())
    # io_obj = BytesIO()
    # img_obj.save(io_obj, 'png')
    # return HttpResponse(io_obj.getvalue())
    # 5.推导步骤5:编写验证码
    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 * 45 + 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())

前端代码

<!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.min.css' %}">
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
    <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.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" id="d1" height="35">
                </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 {
                    // 课下可以使用sweetalert 插件美化展示 需要引入sweetalert   cdn
                    alert(args.msg)
                    {#swal(args.msg,'验证码输错了!!!')#}
                }
            }
        })
    })
</script>

</body>
</html>

后端代码(获取图片验证码的代码在上面)

import json

from django.shortcuts import render, HttpResponse, redirect
from app01 import models
from app01 import myforms
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)  # 执行之后就可以使用request.user获取登录用户对象
                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')

网页主页

1.开设路由
      # 网站首页
      path('home/', views.home_func, name='home_view'),
2.返回html页面
3.首页导航条
      1.引入静态文件
      2.复制导航条页面
      3.后端导入auth模块配合
          from django.contrib import auth
          文章过多的话可以添加 分页器
          创建一个mypage.py的文件 CV代码
      4.修改密码
         1.开设路由
             # 修改密码功能
             path('set_pwd/', views.set_pwd_func),
         2.使用装饰器进行校验是否登录
         from django.contrib.auth.decorators import login_required
         @login_required
          这个模块可以在全局设置里面配置
           LOGIN_URL = '/login/'
         3.考虑用模态框直接进行修改  需要到 v3里面复制粘贴
         4.前端发送ajax请求
         5.后端代码获取前端修改的数据 进行处理 错误信息添加到 back_dict字典内
         6.前端错误信息渲染
      5.修改头像
         1.开设修改头像路由   配置暴露资源接口
              # 用户头像修改
              path('set_avatar/', views.set_avatar_func),

             from django.views.static import serve
             from django.conf import settings
             # 自定义暴露资源接口
             re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
             还需要在全局配置里配置  MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
         2.使用装饰器进行校验是否登录
         from django.contrib.auth.decorators import login_required
         @login_required
         3.考虑用模态框直接进行修改  需要到 v3里面复制粘贴
            头像的实时展示 和注册时一样
         4.前端发送ajax请求
         5.后端代码获取前端修改的数据 进行处理 错误信息添加到 back_dict字典内
         6.前端错误信息渲染
     6.注销登录
         1.开设路由
             # 注销登录功能
             path('logout/', views.logout),
         2.后端代码用auth模块进行处理  跳转到主页   
         3.注销登录的按钮路由添加
4.主页内容的添加(可以考虑把内容区域继承    block )
     1.先分区(两边是广告  中间是文章   需要先在数据库里面绑定   使用命令创建超级管理员  createsuperuser)
     2.准备数据   需要在admin.py中添加表的绑定关系 在表中绑数据 (注意个人站点绑定别绑错了)
           from app01 import models
           dmin.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)
     3.添加用户名 时间 点赞 点踩 评论等

BBS升级版-小白菜博客
首页功能前端代码

<!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.min.css' %}">
    <script src="{% static 'bootstrap-3.4.1-dist/js/bootstrap.min.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>

            </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 style="width: 38px;height: 38px;border-radius: 50%; overflow: hidden;display: block"><img src="/media/{{ request.user.avatar }}/" alt="" style="max-width: 100%"></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="#" data-toggle="modal" data-target="#myavatar">修改头像</a></li>
                            <li><a href="/backend/">后台管理</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="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="" id="myimg" width="120">
                    </label>
                    <input type="file" id="myfile" style="display: none" name="new_avatar" >

                </div>

                   <input type="submit" class="btn btn-warning btn-block">
                </form>

            </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">
                        抓紧练习:wuyong
                      </div>
                    </div>
                    <div class="panel panel-warning">
                      <div class="panel-heading">
                        <h3 class="panel-title">百万大奖</h3>
                      </div>
                      <div class="panel-body">
                        共享你幸运儿:22222
                    </div>
                 </div>
                    <div class="panel panel-success">
                      <div class="panel-heading">
                        <h3 class="panel-title">广告找找</h3>
                      </div>
                      <div class="panel-body">
                        旺铺难求
                      </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.site.userinfo.username }}/article/{{ article_obj.pk }}">{{ 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 }}/">{{ article_obj.site.userinfo.username }}&nbsp; &nbsp;</a></span>
                        <span>{{ article_obj.create_time|date:'Y-m-d H:i:s' }}&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">
                        抓紧练习:wuyong
                      </div>
                    </div>
                    <div class="panel panel-warning">
                      <div class="panel-heading">
                        <h3 class="panel-title">百万大奖</h3>
                      </div>
                      <div class="panel-body">
                        共享你幸运儿:22222
                    </div>
                 </div>
                    <div class="panel panel-danger">
                      <div class="panel-heading">
                        <h3 class="panel-title">广告找找</h3>
                      </div>
                      <div class="panel-body">
                        旺铺难求
                      </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)
                }

            }
        })
    })

       //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)
            }

        })
</script>

{% block js %}

{% endblock %}

</body>
</html>

首页功能后端代码

分页器代码
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)



首页路由代码
from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse
from django.contrib import auth
from django.contrib.auth.decorators import login_required
from app01 import mypage

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


@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')

个人站点功能