BBS项目

BBS项目

项目开发基本流程

1.需求分析---2.架构设计---3.分组开发---4.提交测试---5.交付上线

项目分析

​ 博客园项目我们可以参考博客园来编写程序,考虑其主要拥有哪些功能,经过我们浏览发现。其博客园项目主要是用户对文章的管理,其核心是文章的增删改查。

表分析

先确定表的数量。在确定表的基础字段,最后确定表的外键字段。

1.用户表
2.个人站点表
3.文章表
4.文章分类表
5.文章标签表
6.推荐反对表
7.文章评论表

基础字段分析

1.用户表可以直接继承使用 auth_user 表并扩展额外的字段
电话,头像,注册时间

2.个人站点表
站点名称(/个人标识/ID/姓名/)
站点标题(/标题/)
站点样式(/css文件/)

3.文章表
	文章标题,文章简介,文章内容,文章的发布时间

4.文章分类表
分类名称

5.文章标签表
标签名称

6.推荐反对表
"记录哪个用户给哪篇文章点了推荐还是反对"
用户字段(用户主键)>>>: 外键字段
文章字段(文章主键)>>>: 外键字段
 推荐反对字段 0或1
  评论内容
  评论时间
外键字段(自关联) 
"因含有子评论所以需要根据根评论的ID来判断是否是子评论"

外键字段分析

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

表的创建

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)  # 填写blank 后 在admin管理后台也不需输入手机号
    avatar = models.FileField(upload_to='avatar/', default='avatar/111.jpg')  # upload_to 是可以自动把获取到的文件存取到后面的路径中。
    register_time = models.DateTimeField(verbose_name='注册时间', auto_now_add=True)
    # 建立用户与个人站点表的外键 一对一
    site = models.OneToOneField(to='site', on_delete=models.CASCADE, null=True)

    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_css = models.CharField(verbose_name='站点样式', max_length=32, null=True)

    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)

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

    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(verbose_name='评论时间', auto_now_add=True)
    parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True)  # 自关联字段


用户注册功能

form类验证层

使用forms组件
  1.渲染前端标签
  2.校验用户数据
  3.展示错误信息
forms类单独放在一个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

模板层

<!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表单提交数据 但是它有序列化功能 得用一下 -->
                {% csrf_token %}
                {% for form in form_obj %}
                <div class="form-group"> <!-- form-group 让多个input标签上下间距更大一些 -->
                    <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/默认.png" 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>

        // 用户头像实时展示
        $('#myfile').change(function (){
            // 产生一个文件阅读器对象
            let myFileReaderObj = new FileReader();
            // 获取用户上传的头像
            let fileObj = this.files[0];
            // 将文件对象交给阅读器对象读取
            myFileReaderObj.readAsDataURL(fileObj);  // 异步操作,需要等待完成
            // 等待文件阅读器对象加载完毕后修改
            myFileReaderObj.onload = function (){
                // 修改img标签的src属性
                $('#myimg').attr('src',myFileReaderObj.result)
            }
        })

        // 给注册按钮绑定事件 发送ajax
        $('#subBtn').click(function (){
            // 产生一个内置对象
            let myFormDataObj = new FormData();
            // 添加普通数据
            {#console.log($('#form').serializeArray())  // 一次性获取form表单所有普通字段 [{}.{}.{}]#}
             $.each($('#form').serializeArray(), function (index, dataObj) {  // 对结果for循环 然后交给后面的函数处理
                myFormDataObj.append(dataObj.name, dataObj.value)
            })
            // 添加文件数据
            myFormDataObj.append('avatar',$('#myfile')[0].files[0])
            // 发送请求
            $.ajax({
                url:'',
                type:'post',
                data:myFormDataObj,

                contentType:false,
                processData:false,

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

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

</body>
</html>

视图层

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):
    # 前后端交互通常使用字典
    back_dict = {'code':10000,'msg':''}
    # 产生一个空的form_obj
    form_obj = myforms.RegisterForm()
    if request.method == 'POST':
        form_obj = myforms.RegisterForm(request.POST)
        if form_obj.is_valid():
            # 存储符合校验的数据
            clean_date = form_obj.cleaned_data
            # 删除用不到的数据
            clean_date.pop('confirm_password')
            # 获取用户上传的头像文件
            avatar_boj = request.FILES.get('avatar')
            if avatar_boj:
                clean_date['avatar'] = avatar_boj
            # 创建用户数据
            models.UserInfo.objects.create_user(**clean_date)
            # 登录成功添加后续操作
            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())

登录功能

模板层

<!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>

                <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" id="loginBtn" class="btn btn-success btn-block" value="登录">
        </div>
    </div>

<script>
    // 验证码点击动态刷新
    $('#d1').click(function (){
        let oldSrc = $(this).attr('src');
        $(this).attr('src',oldSrc+'?')
    })

    // 登录按钮发送ajax请求
    $('#loginBtn').click(function (){
        $.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{
                    swal(args.msg)
                }
            }

        })
    })

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

视图层

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

def login_func(request):
    # 前后端ajax交互使用字典
    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')

随机验证码

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', 30)
    # 产生随机验证码
    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())

登录弹窗样式

sweetalert

https://sweetalert.js.org/guides/
    CDN
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>    


swal(args.msg)

修改密码功能

模板层 (含有导航条登录显示用户,未登录显示用户注册)

<!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>
<!--导航条开始-->
<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>
      </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 %}   <!--判断是否是匿名用户或普通用户 返回 false或True-->
                  <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>  <!--添加属性data-toggle="modal" data-target="#myModal" 跳转模态框-->
                        <li><a href="#">修改头像</a></li>
                        <li><a href="#">后台管理</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">注销</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">  <!--disabled属性使input框无法修改 -->
            </div>
            <div class="form-group">
                <label for="old_pwd">原密码</label>
                <input type="text" id="old_pwd" class="form-control">
            </div>
            <div class="form-group">
                <label for="new_pwd">新密码</label>
                <input type="text" id="new_pwd" class="form-control">
            </div>
            <div class="form-group">
                <label for="confirm_pwd">确认密码</label>
                <input type="text" id="confirm_pwd" class="form-control">
      </div>
      <div class="modal-footer">
          <span id="error" style="color: red"></span>  <!-- 弹窗信息通过ajax修改提示内容 -->
        <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>

视图层

from django.contrib.auth.decorators import login_required

@login_required  # 登录后才可以访问  需要在settings里配置如果没登录跳转到登录页面 LOGIN_URL='/login/'
def set_pwd_func(request):
    # ajax 交互使用字典
    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)
    """底层修改密码后 会自动注销 """

注销登录

from django.contrib import auth
@login_required
def logout_func(request):
    auth.logout(request)
    return redirect('home_view')

主页

模板层(含有修改密码部分代码)

<!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 %}   <!--判断是否是匿名用户或普通用户 返回 false或True-->
                  <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>  <!--添加属性data-toggle="modal" data-target="#myModal" 跳转模态框-->
                        <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">  <!--disabled属性使input框无法修改 -->
            </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=d">确认密码</label>
                <input type="text" id="confirm_pwd" class="form-control">
            </div>
      </div>
      <div class="modal-footer">
          <span id="error" style="color: red"></span>  <!-- 弹窗信息通过ajax修改提示内容 -->
        <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">
                        想要挨骂赶紧联系鸡哥
                      </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-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 }}/">{{ 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 }}</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">
                        事后奖励100W营养费
                      </div>
                    </div>
                           <div class="panel panel-warning">
                      <div class="panel-heading">
                        <h3 class="panel-title">想要成为rap Star吗</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>
     {% endblock %}
    </div>
</div>
<!--内容区域结束-->
    <!--模态框修改按钮绑定事件发送ajax修改密码-->
    <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>

模型层

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

个人主页

模板层

404


<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <link rel="icon" href="/home/" 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="" style="height:45px" alt=""></a></p>
    <div style="margin-top:20px">
        <p style=""><b style="">404.</b> 抱歉,您访问的资源不存在。</p>
        <p class="d">可能是网址有误,或者对应的内容被删除,或者处于私有状态。</p>
        <p style="color:#777;">代码改变世界,联系你鸡哥</p>
    </div>
    <div class="robot"><a href="/home/"><img src="/media/avatar/404.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>

个人主站模板层

{% extends 'homePage.html' %}
{% block css %}
    <link rel="stylesheet" href="media/css/{{ site_obj.site_css }}/">
{% 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-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 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;</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 }}</span>
                            </div>
                </div>
    {% endfor %}
    <div class="text-center">{{ page_obj.page_html|safe }}</div>   <!--分页器-->
    
    </div>
{% endblock %}

模型层

# 个人站点
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)
    # 分页器
    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]
    # 查询个人站点下的所有分类名称以及每个分类下的文章
    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.annotate(month = TruncMonth('create_time')).values('month').annotate(article_num=Count('pk')).values('month','article_num')
    return render(request, 'sitePage.html', locals())

注意事项

media媒体目录

当我们多个业务逻辑需要保存不同的图片或头像那么我们需要暴露存取这些媒体对象的文件夹给浏览器。不然显示不出来

配置文件中加参数
MEDIA_ROOT = os.path.join(BASE_DIR,'media')

不光要增加配置还需要暴露媒体目录urls接口

from django.views.static import serve
from django.conf import settings
re_path('media/(?P<path>.*)',serve,{'document_root':settings.MEDIA_ROOT})

时间切割

官网提供了针对日期字段的切割处理

id  content      create_time     month
1   111        2020-11-11     2020-11
2   222        2020-11-12     2020-11
3   333        2020-11-13     2020-11
4   444        2020-11-14     2020-11
5   555        2020-11-15     2020-11
"""
django官网提供的一个orm语法
 from django.db.models.functions import TruncMonth
-官方提供
   from django.db.models.functions import TruncMonth
   Sales.objects
   .annotate(month=TruncMonth('timestamp'))  # Truncate to month and add to select list
   .values('month')  # Group By month
   .annotate(c=Count('id'))  # Select the count of the grouping
   .values('month', 'c')  # (might be redundant, haven't tested) select month and count
   
   
时区问题报错
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False
"""
posted @ 2023-01-03 08:27  李阿鸡  阅读(42)  评论(0编辑  收藏  举报
Title