blog_project_django

blog_project

1.新建项目,设置项目的一些简单配置

  1. 使用pycharm 创建一个新的项目

  2. 利用 python manage.py startapp app 创建一个新的 app

  3. 将 app 添加到 项目目录下 同项目名文件夹下的settings.py 中的 INSTALLED_APPS 中, (注册应用)

  4. urls 模块化, 在新创建的 app 下新建一个 urls.py ,在这个文件中写url 和视图的映射关系,在总的urls.py 中 使用 include 包含这个 app 中的urls, 在新建的 url.py 为 app 指定一个命名空间。

  5. 这个项目的前端页面是使用 bootstraps5 来实现的, 要在项目目录下创建一个 static 文件夹,存放静态文件。

  6. 使用 静态文件需要在 相关html 中 使用 {% load static %} 标签 或者 在 settings.py 中的 TEMPLATES 配置中加入一行, 如下图:

    image-20250602175428003

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [BASE_DIR / 'templates']
            ,
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
                'builtins': ["django.templatetags.static"]
            },
        },
    ]
    
  7. 在 setting.py 中设置静态文件的加载路径

    image-20250602180849618

# 静态文件加载路径
STATICFILES_DIRS = [
    BASE_DIR / 'static'
]

2.使用 bootstrap 实现导航条

  1. 进入 bootstrap官网,下载相关的 css 和 js 文件, 加载到 html 文件中。

    # bootstrap官网地址: https://getbootstrap.com/
    

    image-20250602182458511

    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>博客</title>
        <link rel="stylesheet" href="{% static 'bootstrap-5.3.6-dist/css/bootstrap.css' %}">
        <script src="{% static 'bootstrap-5.3.6-dist/js/bootstrap.js' %}"></script>
    </head>
    
  2. 进入 bootstrap 官网,Examples页面 选择需要的前端效果

    image-20250602183029416

  3. 右键页面->检查->定位到需要的前端效果->copy->copy outerHTML (复制相关的前端效果代码)->复制到 index.html 中

    image-20250602183745678

  4. 对导航条的代码做一些调整,使其符合预期的效果。参考 bootstrp 官方文档进行调整。

    <!DOCTYPE html>
    {#{% load static %}#}
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>博客</title>
        <link rel="stylesheet" href="{% static 'bootstrap-5.3.6-dist/css/bootstrap.css' %}">
        <script src="{% static 'bootstrap-5.3.6-dist/js/bootstrap.js' %}"></script>
    </head>
    <body>
    <header class="p-3 text-bg-light border-bottom">
        <div class="container">
            <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
                <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
                    <img src="{% static  'img/blog.png' %}" alt="" height="40">
                </a>
                <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                    <li><a href="#" class="nav-link px-2 text-secondary">首页</a></li>
                    <li><a href="#" class="nav-link px-2 text-secondary">发布博客</a></li>
                </ul>
                <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search"><input type="search"
                                                                                           class="form-control"
                                                                                           placeholder="搜索"
                                                                                           aria-label="Search"></form>
                <div class="text-end">
                    <button type="button" class="btn btn-outline-primary me-2">登录</button>
                    <button type="button" class="btn btn-primary">注册</button>
                </div>
            </div>
        </div>
    </header>
    </body>
    </html>
    

    效果如下

image-20250602185915909

3.使用 bootstrap 实现首页布局

  1. 将body 的背景颜色改成灰色, 在static 文件夹下创建一个 css 的文件夹, 在里面写一个base.css

    /* base.css 将 body 标签的颜色修改成灰色
    
    body{
        background-color: rgba(0, 0, 0, 0.1);
    }
    

    在index.html 中引入 这个 css 文件

    <link rel="stylesheet" href="{% static "css/base.css" %}">
    

    效果如下

    image-20250602201450477

  2. 在 body 中添加 博客列表 的 container

    <main class="container bg-white">
        <h1>博客列表</h1>
    </main>
    

    image-20250602203141445

    博客列表和 导航条的 距离太近了, 可以填加一些距离

    可以在 导航条 header 上 添加一个下边距, 在 header 标签中 加一个 属性 mb-3

    这个 mb-3 是 bootstrap 中定义的, 具体可以参考bootstrp 文档, 效果如下:

    <header class="p-3 text-bg-light border-bottom mb-3">
        ...
    </header>
    

    image-20250602203623446

    给博客列表的container 添加一些内边距, main 标签 添加一个 p-3 属性

    <main class="container bg-white p-3">
        <h1>博客列表</h1>
    </main>
    

    image-20250602203849344

    博客列表这个四边型的角是方角, 不好看, 可以设置成圆角 , main 标签添加一个 属性 rounded

    <main class="container bg-white p-3 rounded">
        <h1>博客列表</h1>
    </main>
    

    image-20250602204214506

  3. 在 main 标签中添加每个 blog 的 信息展示窗口,可以使用 bootstrap 中的 card 实现, 参考 bootstrap 官方文档。

    image-20250602204811372

    # 在pycharm中 书写 html 的 时候, 写一个 标签.属性 按tab 键, 会自动生成标签,且会把点后面的 内容放到class 中
    # div.row -->  <div class="row"></div>
    

    这里设置每一行展示两列的博客信息 在 row 中加入 属性 row-cols-2

        <div class="row row-cols-2">
            <div class="col">
                
                <!-- 下面是从bootstrap 粘贴的 card模版代码, 如上图 -->
                <div class="card text-center">
                    <div class="card-header">
                        Featured
                    </div>
                    <div class="card-body">
                        <h5 class="card-title">Special title treatment</h5>
                        <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
                        <a href="#" class="btn btn-primary">Go somewhere</a>
                    </div>
                    <div class="card-footer text-body-secondary">
                        2 days ago
                    </div>
                </div>
                <!--上面是从bootstrap 粘贴的 card模版代码 -->
                
            </div>
        </div>
    

    image-20250602205931442

    image-20250602210215767

    row-cols-2 属性限制每行显示两列,超过两列就展示到下一行了,如下图:

    image-20250602210344947

    行与行之前紧挨着,不美观,在行与行之前添加一些边距,在行 标签中添加属性 row-gap-4,效果如下图:

    <div class="row row-cols-2 row-gap-4">
        
    </div>
    

    image-20250602210706286

    card-body div 的 高度 会随着 内容的改变而变窄,可以设置 style height 为固定值

    <div class="card-body" style="height: 100px">
    	<p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
    </div>
    

    image-20250602211608806

    如上图,一个盒子中包含了两个小盒子,每个盒子展示在自己的一边,可以使用bootstrap 中的 flex 布局。

    image-20250602212019570

    总的 div 可以使用如下属性, 在这个div 中添加两个 div, 一个 div 会展示在左边, 一个div 会展示在右边

    <div class="d-flex justify-content-between">...</div>
    

    在 左边的div 中添加 头像使用 img 标签,设置圆角用 rounded-circle 属性, 防止图片太大, 给标签设置一下高度和宽度。width=30 height =30

    <img src="{% static "img/avator.png" %}" class="rounded-circle" width="30" height="30" alt="">
    

    flex 布局代码总体如下

    <div class="card-footer text-body-secondary d-flex justify-content-between">
        <div>
            <img src="{% static "img/avator.png" %}" class="rounded-circle" width="30" height="30" alt="">
            小明
        </div>
        <div>
            2025/6/2 21:36
        </div>
    
    </div>
    

    效果图

    image-20250602214137741

    <!DOCTYPE html>
    {#{% load static %}#}
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>博客</title>
        <link rel="stylesheet" href="{% static 'bootstrap-5.3.6-dist/css/bootstrap.css' %}">
        <script src="{% static 'bootstrap-5.3.6-dist/js/bootstrap.js' %}"></script>
        <link rel="stylesheet" href="{% static "css/base.css" %}">
    </head>
    <body>
    <header class="p-3 text-bg-light border-bottom mb-3">
        <div class="container">
            <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
                <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
                    <img src="{% static  'img/blog.png' %}" alt="" height="40">
                </a>
                <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                    <li><a href="#" class="nav-link px-2 text-secondary">首页</a></li>
                    <li><a href="#" class="nav-link px-2 text-secondary">发布博客</a></li>
                </ul>
                <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search"><input type="search"
                                                                                           class="form-control"
                                                                                           placeholder="搜索"
                                                                                           aria-label="Search"></form>
                <div class="text-end">
                    <button type="button" class="btn btn-outline-primary me-2">登录</button>
                    <button type="button" class="btn btn-primary">注册</button>
                </div>
            </div>
        </div>
    </header>
    <main class="container bg-white p-3 rounded">
        <h1>博客列表</h1>
        <div class="row row-cols-2 row-gap-4">
            <div class="col">
                <div class="card">
                    <div class="card-header">
                        <a href="#"> django 基础</a>
                    </div>
                    <div class="card-body" style="height: 100px">
                        <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
                    </div>
                    <div class="card-footer text-body-secondary d-flex justify-content-between">
                        <div>
                            <img src="{% static "img/avator.png" %}" class="rounded-circle" width="30" height="30" alt="">
                            小明
                        </div>
                        <div>
                            2025/6/2 21:36
                        </div>
    
                    </div>
                </div>
            </div>
            <div class="col">
                <div class="card text-center">
                    <div class="card-header">
                        Featured
                    </div>
                    <div class="card-body">
                        <h5 class="card-title">Special title treatment</h5>
                        <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
                        <a href="#" class="btn btn-primary">Go somewhere</a>
                    </div>
                    <div class="card-footer text-body-secondary">
                        2 days ago
                    </div>
                </div>
            </div>
            <div class="col">
                <div class="card text-center">
                    <div class="card-header">
                        Featured
                    </div>
                    <div class="card-body">
                        <h5 class="card-title">Special title treatment</h5>
                        <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
                        <a href="#" class="btn btn-primary">Go somewhere</a>
                    </div>
                    <div class="card-footer text-body-secondary">
                        2 days ago
                    </div>
                </div>
            </div>
        </div>
    </main>
    </body>
    </html>
    

4.使用 bootstrap 实现详情页布局

博客详情页的导航条和 首页的是一样的,复制 首页的代码进行修改。

效果图如下:

image-20250603085421117

代码

<!DOCTYPE html>
{#{% load static %}#}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>博客</title>
    <link rel="stylesheet" href="{% static 'bootstrap-5.3.6-dist/css/bootstrap.css' %}">
    <script src="{% static 'bootstrap-5.3.6-dist/js/bootstrap.js' %}"></script>
    <link rel="stylesheet" href="{% static "css/base.css" %}">
</head>
<body>
<header class="p-3 text-bg-light border-bottom mb-3">
    <div class="container">
        <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
            <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
                <img src="{% static  'img/blog.png' %}" alt="" height="40">
            </a>
            <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                <li><a href="#" class="nav-link px-2 text-secondary">首页</a></li>
                <li><a href="#" class="nav-link px-2 text-secondary">发布博客</a></li>
            </ul>
            <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search"><input type="search"
                                                                                       class="form-control"
                                                                                       placeholder="搜索"
                                                                                       aria-label="Search"></form>
            <div class="text-end">
                <button type="button" class="btn btn-outline-primary me-2">登录</button>
                <button type="button" class="btn btn-primary">注册</button>
            </div>
        </div>
    </div>
</header>
<main class="container bg-white p-3 rounded">
    <h1>django 基础</h1>
    <hr>
    <div class="mt-2">
        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="30" height="30" alt="">
        <span class="ms-2">小明</span>
        <span class="ms-2">于</span>
        <span class="ms-2">2025/6/3 8:21</span>
    </div>
    <hr>
    <div class="py-2">
        博客详情
    </div>
    <hr>
    <div class="mt-2">
        <h3>评论:(10)</h3>
        <form action="" mehtod="POST">
            <div class="mt-2">
                <input type="text" class="form-control" placeholder="请输入评论">
            </div>
            <div class="text-end mt-2">
                <button type="button" class="btn btn-primary">评论</button>
            </div>
        </form>
    </div>
    
    <div class="mt-2">
        <ul class="list-group list-group-flush">
            <li class="list-group-item mt-3">
                <div class="d-flex justify-content-between text-body-secondary">
                    <div class="user-info">
                        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="40" height="40" alt="">
                        <span class="ms-2">小明</span>
                    </div>
                    <div class="create-time" style="line-height: 40px">2025/6/3 8:46</div>
                </div>
                <div class="mt-2">评论内容</div>
            </li>
            <li class="list-group-item mt-3">
                <div class="d-flex justify-content-between text-body-secondary">
                    <div class="user-info">
                        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="40" height="40" alt="">
                        <span class="ms-2">小明</span>
                    </div>
                    <div class="create-time" style="line-height: 40px">2025/6/3 8:46</div>
                </div>
                <div class="mt-2">评论内容</div>
            </li>
        </ul>
    </div>
</main>
</body>
</html>

5.使用 bootstrap 实现发布页面布局

发布页面的一部分代码和 首页相同,使用首页的代码修改,这里涉及到一个富文本编辑器的实现, 参考文档:https://www.wangeditor.com/

效果如下:

image-20250603100023867

代码

<!DOCTYPE html>
{#{% load static %}#}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>博客</title>
    <link rel="stylesheet" href="{% static 'bootstrap-5.3.6-dist/css/bootstrap.css' %}">
    <script src="{% static 'bootstrap-5.3.6-dist/js/bootstrap.js' %}"></script>
    <link rel="stylesheet" href="{% static "css/base.css" %}">
    
    <!-- 下面这两文件 从wangeditor 官网下载-->
    <link rel="stylesheet" href="{% static "wangeditor/css/style.css" %}">
    <script src="{% static 'wangeditor/index.js' %}"></script>
    
    
    <script src="{% static 'js/pub_blog.js' %}"></script>
    <style>
      #editor—wrapper {
        border: 1px solid #ccc;
        z-index: 100; /* 按需定义 */
      }
      #toolbar-container {
        border-bottom: 1px solid #ccc;
      }
      #editor-container {
        height: 500px;
      }
    </style>
</head>
<body>
<header class="p-3 text-bg-light border-bottom mb-3">
    <div class="container">
        <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
            <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
                <img src="{% static  'img/blog.png' %}" alt="" height="40">
            </a>
            <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                <li><a href="#" class="nav-link px-2 text-secondary">首页</a></li>
                <li><a href="#" class="nav-link px-2 text-secondary">发布博客</a></li>
            </ul>
            <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search"><input type="search"
                                                                                       class="form-control"
                                                                                       placeholder="搜索"
                                                                                       aria-label="Search"></form>
            <div class="text-end">
                <button type="button" class="btn btn-outline-primary me-2">登录</button>
                <button type="button" class="btn btn-primary">注册</button>
            </div>
        </div>
    </div>
</header>
<main class="container bg-white p-3 rounded">
    <h1>发布博客</h1>
    <div class="mt-3">
        <form action="" method="POST">
            <div class="mb-3">
                <label class="form-label" for="">标题</label>
                <input type="text" name="title" class="form-control">
            </div>
            
            <div class="mb-3">
                <label class="form-label" for="">分类</label>
                <select name="category" class="form-select">
                    <option value="1">python</option>
                    <option value="2">前端</option>
                </select>
            </div>
            <div class="mb-3">
                <label class='form-label' for="">内容</label>
                    <div id="editor—wrapper">
                      <div id="toolbar-container"><!-- 工具栏 --></div>
                      <div id="editor-container"><!-- 编辑器 --></div>
                    </div>
            </div>
            <div class="mb-3 text-end">
                <button class="btn btn-primary">发布</button>
            </div>
        </form>
    </div>
</main>
</body>
</html>
// 参考上述富文本编辑官网,使用 说明代码
window.onload = function(){

    const { createEditor, createToolbar } = window.wangEditor

    const editorConfig = {
    placeholder: 'Type here...',
    onChange(editor) {
      const html = editor.getHtml()
      console.log('editor content', html)
      // 也可以同步到 <textarea>
    },
    }

    const editor = createEditor({
    selector: '#editor-container',
    html: '<p><br></p>',
    config: editorConfig,
    mode: 'default', // or 'simple'
    })

    const toolbarConfig = {}

    const toolbar = createToolbar({
    editor,
    selector: '#toolbar-container',
    config: toolbarConfig,
    mode: 'default', // or 'simple'
    })
}

6.使用 bootstrap 实现登录和注册页面布局

登录页面效果

image-20250603150513343

<!DOCTYPE html>
{#{% load static %}#}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>博客</title>
    <link rel="stylesheet" href="{% static 'bootstrap-5.3.6-dist/css/bootstrap.css' %}">
    <script src="{% static 'bootstrap-5.3.6-dist/js/bootstrap.js' %}"></script>
    <link rel="stylesheet" href="{% static "css/base.css" %}">
</head>
<body>
<header class="p-3 text-bg-light border-bottom mb-3">
    <div class="container">
        <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
            <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
                <img src="{% static  'img/blog.png' %}" alt="" height="40">
            </a>
            <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                <li><a href="#" class="nav-link px-2 text-secondary">首页</a></li>
                <li><a href="#" class="nav-link px-2 text-secondary">发布博客</a></li>
            </ul>
            <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search"><input type="search"
                                                                                       class="form-control"
                                                                                       placeholder="搜索"
                                                                                       aria-label="Search"></form>
            <div class="text-end">
                <button type="button" class="btn btn-outline-primary me-2">登录</button>
                <button type="button" class="btn btn-primary">注册</button>
            </div>
        </div>
    </div>
</header>
<main class="container bg-white p-3 rounded">
    <div style="max-width: 330px;" class="m-auto">
        <h1>登陆</h1>
        <form action="" method="POST">
            <div class="mb-3">
                <label for="">邮箱</label>
                <input type="email" name="email" class="form-control" placeholder="邮箱">
            </div>
            <div class="mb-3">
                <label for="">密码</label>
                <input type="password" name="password" class="form-control" placeholder="密码">
            </div>
            <div class="form-check mb-3">
              <input class="form-check-input" type="checkbox" name="remember" value="1" id="checkDefault">
              <label class="form-check-label" for="checkDefault">
                记住我
              </label>
            </div>
            <div class="mb-3">
                <button class="btn btn-primary w-100">立即登录</button>
            </div>
        </form>
    </div>
    
</main>
</body>
</html>

注册页面

image-20250603151632504

<!DOCTYPE html>
{#{% load static %}#}
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>博客</title>
    <link rel="stylesheet" href="{% static 'bootstrap-5.3.6-dist/css/bootstrap.css' %}">
    <script src="{% static 'bootstrap-5.3.6-dist/js/bootstrap.js' %}"></script>
    <link rel="stylesheet" href="{% static "css/base.css" %}">
</head>
<body>
<header class="p-3 text-bg-light border-bottom mb-3">
    <div class="container">
        <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
            <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
                <img src="{% static  'img/blog.png' %}" alt="" height="40">
            </a>
            <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                <li><a href="#" class="nav-link px-2 text-secondary">首页</a></li>
                <li><a href="#" class="nav-link px-2 text-secondary">发布博客</a></li>
            </ul>
            <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search"><input type="search"
                                                                                       class="form-control"
                                                                                       placeholder="搜索"
                                                                                       aria-label="Search"></form>
            <div class="text-end">
                <button type="button" class="btn btn-outline-primary me-2">登录</button>
                <button type="button" class="btn btn-primary">注册</button>
            </div>
        </div>
    </div>
</header>
<main class="container bg-white p-3 rounded">
    <div style="max-width: 330px;" class="m-auto">
        <h1>注册</h1>
        <form action="" method="POST">
            <div class="mb-3">
                <label for="">用户名</label>
                <input type="text" name="username" class="form-control" placeholder="用户名">
            </div>
            <div class="mb-3">
                <label for="">邮箱</label>
                <input type="email" name="email" class="form-control" placeholder="邮箱">
            </div>
            <div class="mb-3">
                <label for="">验证码</label>
                <div class="input-group mb-3">
                    <input type="text" class="form-control" placeholder="验证码" aria-label="Recipient’s username" aria-describedby="button-addon2">
                    <button class="btn btn-outline-secondary" type="button" >获取验证码</button>
                </div>
            </div>
            <div class="mb-3">
                <label for="">密码</label>
                <input type="password" name="password" class="form-control" placeholder="密码">
            </div>
            
            <div class="mb-3">
                <button class="btn btn-primary w-100">立即注册</button>
            </div>
        </form>
    </div>
    
</main>
</body>
</html>

7.将html 文件改造成django 模版

使用django 中的模版继承, 把模版中相同的部分写到 base.html 中,不同的部分使用 block 占位

base.html

<!DOCTYPE html>
{#{% load static %}#}
<html lang="en">
<head>
    <meta charset="UTF-8">

    <title>
    {% block title %}
        
    {% endblock %}
    </title>

    <link rel="stylesheet" href="{% static 'bootstrap-5.3.6-dist/css/bootstrap.css' %}">
    <script src="{% static 'bootstrap-5.3.6-dist/js/bootstrap.js' %}"></script>
    <link rel="stylesheet" href="{% static "css/base.css" %}">

    {% block head %}
    	
    {% endblock %}

</head>
<body>
<header class="p-3 text-bg-light border-bottom mb-3">
    <div class="container">
        <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
            <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
                <img src="{% static  'img/blog.png' %}" alt="" height="40">
            </a>
            <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                <li><a href="#" class="nav-link px-2 text-secondary">首页</a></li>
                <li><a href="#" class="nav-link px-2 text-secondary">发布博客</a></li>
            </ul>
            <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search"><input type="search"
                                                                                       class="form-control"
                                                                                       placeholder="搜索"
                                                                                       aria-label="Search"></form>
            <div class="text-end">
                <button type="button" class="btn btn-outline-primary me-2">登录</button>
                <button type="button" class="btn btn-primary">注册</button>
            </div>
        </div>
    </div>
</header>
<main class="container bg-white p-3 rounded">
   {% block main %}
   	
   {% endblock %}
</main>
</body>
</html>

首页 index.html

{% extends 'base.html' %}

{% block title %}
	博客首页
{% endblock %}

{% block main %}
    <h1>博客列表</h1>
    <div class="row row-cols-2 row-gap-4">
        <div class="col">
            <div class="card">
                <div class="card-header">
                    <a href="#"> django 基础</a>
                </div>
                <div class="card-body" style="height: 100px">
                    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
                </div>
                <div class="card-footer text-body-secondary d-flex justify-content-between">
                    <div>
                        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="30" height="30" alt="">
                        小明
                    </div>
                    <div>
                        2025/6/2 21:36
                    </div>

                </div>
            </div>
        </div>
        <div class="col">
            <div class="card text-center">
                <div class="card-header">
                    Featured
                </div>
                <div class="card-body">
                    <h5 class="card-title">Special title treatment</h5>
                    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
                    <a href="#" class="btn btn-primary">Go somewhere</a>
                </div>
                <div class="card-footer text-body-secondary">
                    2 days ago
                </div>
            </div>
        </div>
        <div class="col">
            <div class="card text-center">
                <div class="card-header">
                    Featured
                </div>
                <div class="card-body">
                    <h5 class="card-title">Special title treatment</h5>
                    <p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
                    <a href="#" class="btn btn-primary">Go somewhere</a>
                </div>
                <div class="card-footer text-body-secondary">
                    2 days ago
                </div>
            </div>
        </div>
    </div>
{% endblock %}

登录页面,login.html

{% extends "base.html" %}

{% block title %}
	登录
{% endblock %}


{% block main %}
	<div style="max-width: 330px;" class="m-auto">
        <h1>登陆</h1>
        <form action="" method="POST">
            <div class="mb-3">
                <label for="">邮箱</label>
                <input type="email" name="email" class="form-control" placeholder="邮箱">
            </div>
            <div class="mb-3">
                <label for="">密码</label>
                <input type="password" name="password" class="form-control" placeholder="密码">
            </div>
            <div class="form-check mb-3">
              <input class="form-check-input" type="checkbox" name="remember" value="1" id="checkDefault">
              <label class="form-check-label" for="checkDefault">
                记住我
              </label>
            </div>
            <div class="mb-3">
                <button class="btn btn-primary w-100">立即登录</button>
            </div>
        </form>
    </div>
{% endblock %}

注册页面 register.html

{% extends "base.html" %}

{% block title %}
	注册
{% endblock %}

{% block main %}
    <div style="max-width: 330px;" class="m-auto">
        <h1>注册</h1>
        <form action="" method="POST">
            <div class="mb-3">
                <label for="">用户名</label>
                <input type="text" name="username" class="form-control" placeholder="用户名">
            </div>
            <div class="mb-3">
                <label for="">邮箱</label>
                <input type="email" name="email" class="form-control" placeholder="邮箱">
            </div>
            <div class="mb-3">
                <label for="">验证码</label>
                <div class="input-group mb-3">
                    <input type="text" class="form-control" placeholder="验证码" aria-label="Recipient’s username" aria-describedby="button-addon2">
                    <button class="btn btn-outline-secondary" type="button" >获取验证码</button>
                </div>
            </div>
            <div class="mb-3">
                <label for="">密码</label>
                <input type="password" name="password" class="form-control" placeholder="密码">
            </div>
            
            <div class="mb-3">
                <button class="btn btn-primary w-100">立即注册</button>
            </div>
        </form>
    </div>
{% endblock %}

发布博客页面 pub_blog.html

{% extends "base.html" %}

{% block title %}
	发布博客
{% endblock %}

{% block head %}
    <link rel="stylesheet" href="{% static "wangeditor/css/style.css" %}">
    <script src="{% static 'wangeditor/index.js' %}"></script>
    <script src="{% static 'js/pub_blog.js' %}"></script>
    <style>
      #editor—wrapper {
        border: 1px solid #ccc;
        z-index: 100; /* 按需定义 */
      }
      #toolbar-container {
        border-bottom: 1px solid #ccc;
      }
      #editor-container {
        height: 500px;
      }
    </style>
{% endblock %}

{% block main %}
    <h1>发布博客</h1>
    <div class="mt-3">
        <form action="" method="POST">
            <div class="mb-3">
                <label class="form-label" for="">标题</label>
                <input type="text" name="title" class="form-control">
            </div>
            
            <div class="mb-3">
                <label class="form-label" for="">分类</label>
                <select name="category" class="form-select">
                    <option value="1">python</option>
                    <option value="2">前端</option>
                </select>
            </div>
            <div class="mb-3">
                <label class='form-label' for="">内容</label>
                    <div id="editor—wrapper">
                      <div id="toolbar-container"><!-- 工具栏 --></div>
                      <div id="editor-container"><!-- 编辑器 --></div>
                    </div>
            </div>
            <div class="mb-3 text-end">
                <button class="btn btn-primary">发布</button>
            </div>
        </form>
    </div>
{% endblock %}


博客详情页面 blog_detail.html

{% extends "base.html" %}


{% block main %}
    <h1>django 基础</h1>
    <hr>
    <div class="mt-2">
        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="30" height="30" alt="">
        <span class="ms-2">小明</span>
        <span class="ms-2">于</span>
        <span class="ms-2">2025/6/3 8:21</span>
    </div>
    <hr>
    <div class="py-2">
        博客详情
    </div>
    <hr>
    <div class="mt-2">
        <h3>评论:(10)</h3>
        <form action="" mehtod="POST">
            <div class="mt-2">
                <input type="text" class="form-control" placeholder="请输入评论">
            </div>
            <div class="text-end mt-2">
                <button type="button" class="btn btn-primary">评论</button>
            </div>
        </form>
    </div>
    
    <div class="mt-2">
        <ul class="list-group list-group-flush">
            <li class="list-group-item mt-3">
                <div class="d-flex justify-content-between text-body-secondary">
                    <div class="user-info">
                        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="40" height="40" alt="">
                        <span class="ms-2">小明</span>
                    </div>
                    <div class="create-time" style="line-height: 40px">2025/6/3 8:46</div>
                </div>
                <div class="mt-2">评论内容</div>
            </li>
            <li class="list-group-item mt-3">
                <div class="d-flex justify-content-between text-body-secondary">
                    <div class="user-info">
                        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="40" height="40" alt="">
                        <span class="ms-2">小明</span>
                    </div>
                    <div class="create-time" style="line-height: 40px">2025/6/3 8:46</div>
                </div>
                <div class="mt-2">评论内容</div>
            </li>
        </ul>
    </div>
{% endblock %}

8.邮件发送功能实现

通过企业邮箱或者个人邮箱 给注册用户发送邮件, 需要开启 邮箱的SMTP服务

qq 邮箱的SMTP 服务在如下图所示位置

image-20250603174922611

设置发送邮箱的配置(在 settings.py 中书写)

# 发送邮件相关配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
# qq 邮箱  如果是 网易邮箱  'smtp.163.com'
EMAIL_HOST = 'smtp.qq.com'
EMAIL_PORT = 587

# 开启 SMTP 服务的邮箱账号, 用来发送邮件
EMAIL_HOST_USER = 'xxxxxxx@qq.com'

# 开启 SMTP 服务时生成的授权码
EMAIL_HOST_PASSWORD = "xxxxxxxx"

# 开启 SMTP 服务的邮箱账号, 用来发送邮件
DEFAULT_FROM_EMAIL = 'xxxxxx@qq.com'

发送邮件的视图函数

# auth1(app)\views.py

from django.shortcuts import render
from django.http.response import JsonResponse
import string
import random
from django.core.mail import send_mail


def send_email_captcha(request):
    # ?email=xxx  获取给哪个邮箱发送验证码
    email = request.GET.get('email')
    if not email:
        return JsonResponse({"code": 400, "message": "必须传递邮箱!"})
    # 生成验证码(取随机四位 阿拉伯数字)
    captcha = "".join(random.sample(string.digits, 4))
    send_mail("博客注册验证码", message=f"您的注册验证码是:{captcha}", recipient_list=[email], from_email=None)
    return JsonResponse({"code":200, "message": "邮箱验证码发送成功!"})

9.数据库配置和验证码存储

把数据库的配置写在一个 配置文件中, 比如: my_database.cnf, 然后在 settings.py 中引入 配置文件。

# settings.py 中的 数据库配置

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': 'my_database.cnf',
        }
    }
}
my_database.cnf

[client]
database=blog
user = root
password = root
default-character-set=utf8mb4

确认相关的app 在 settings.py 中注册, 如果app没有在settings.py 注册,那么执行数据库迁移命令的时候,无法把相关的 app models.py 中的模型映射到数据库。

image-20250603184520646

创建存储 验证码和邮箱的模型类,在 auth1/models.py 中书写

from django.db import models

# Create your models here.


class CaptchaModel(models.Model):
    email = models.EmailField(unique=True)
    captcha = models.CharField(max_length=4)
    create_time = models.DateTimeField(auto_now_add=True)
    
# 执行数据库迁移命令, 将models.py 中的模型类映射到数据库
# python manage.py makemigrations
# python manage.py migrate

修改 获取验证码的视图函数,将验证码存储到数据库中

def send_email_captcha(request):
    # ?email=xxx  获取给那个邮箱发送验证码
    email = request.GET.get('email')
    if not email:
        return JsonResponse({"code": 400, "message": "必须传递邮箱!"})
    # 生成验证码(取随机四位 阿拉伯数字)
    captcha = "".join(random.sample(string.digits, 4))
    # 将邮箱和验证码存储到数据库中 ,如果找到了邮箱, 更新验证码, 如果没找到,创建并添加数据。
    CaptchaModel.objects.update_or_create(email=email, defaults={'captcha': captcha})
    send_mail("博客注册验证码", message=f"您的注册验证码是:{captcha}", recipient_list=[email], from_email=None)
    return JsonResponse({"code":200, "message": "邮箱验证码发送成功!"})

10.获取验证码按钮倒计时功能实现

使用jQuey 给 发送验证码按钮绑定一个事件, 点击按钮 向获取验证码视图函数发送请求, 使用 ajax 请求

在 register.html 中导入 jquery 库, 使用自己写的 javascript脚步(register.js) 给 获取验证码按钮绑定事件。

{% block head %}
    <script src="{% static 'jquery/jquery.3.6.min.js' %}"></script>
	<script src="{% static 'js/register.js' %}"></script>
{% endblock %}
$(function(){
    function bindCaptchaBtnClick(){
        $("#captcha-btn").click(function(event){
            // 将 按钮对象 转化成  jquery对象
            let $this = $(this);

            let email = $("input[name='email']").val();
            if(!email){
                alert("请先输入邮箱!")
                return;
            }
            // 取消按钮的点击事件,
            $this.off('click');
            // 倒计时
            let countdown = 6;
            let timer = setInterval(function(){
                if(countdown <= 0){
                    $this.text("获取验证码");
                    // 清除定时器
                    clearInterval(timer);

                    // 重新绑定点击事件
                    bindCaptchaBtnClick();
                }else{
                    countdown--;
                    $this.text(countdown + "s");
                }
            }, 1000);

        })
    }
    bindCaptchaBtnClick();
});

11.验证码发送功能完成

给按钮绑定发送 ajax 请求, register.js

$(function(){
    function bindCaptchaBtnClick(){
        $("#captcha-btn").click(function(event){
            // 将 按钮对象 转化成  jquery对象
            let $this = $(this);

            let email = $("input[name='email']").val();
            if(!email){
                alert("请先输入邮箱!")
                return;
            }
            // 取消按钮的点击事件,
            $this.off('click');
            // 发送 ajax 请求
            $.ajax('/auth1/captcha?email=' + email, {
                method:'GET',
                success: function(result){
                    if(result['code'] == 200){
                        alert("验证码发送成功!");
                    }else{
                        alert(result['message']);
                    }

                },
                fail: function(error){
                    console.log(error);
                }
            })

            // 倒计时
            let countdown = 6;
            let timer = setInterval(function(){
                if(countdown <= 0){
                    $this.text("获取验证码");
                    // 清除定时器
                    clearInterval(timer);

                    // 重新绑定点击事件
                    bindCaptchaBtnClick();
                }else{
                    countdown--;
                    $this.text(countdown + "s");
                }
            }, 1000);

        })
    }
    bindCaptchaBtnClick();
});

12.注册功能实现

完善注册功能视图函数

# auth1/views.py
from django.shortcuts import render, redirect, reverse
from django.http.response import JsonResponse
import string
import random
from django.core.mail import send_mail
from .models import CaptchaModel
from django.views.decorators.http import require_http_methods
from .forms import RegisterForm
from django.contrib.auth import get_user_model
User = get_user_model()


@require_http_methods(['GET', 'POST'])
def register(request):
    if request.method == "GET":
        return render(request, "register.html")
    else:
        form = RegisterForm(request.POST)
        if form.is_valid():
            email = form.cleaned_data.get('email')
            username = form.cleaned_data.get("username")
            password = form.cleaned_data.get("password")
            # 使用 django 内置模型的create_user 方法,可以将 password 加密后存储
            User.objects.create_user(email=email, username=username, password=password)
            return redirect(reverse('auth1:login'))
        else:
            print(form.errors)
            return redirect(reverse("auth1:register"))
            # return render(request, "register.html", context={"form": form})

注册表单的数据验证

# auth1/forms.py
from django import forms
from django.contrib.auth import get_user_model
from .models import CaptchaModel

# 这里使用 django 内部定义的user 模型
User = get_user_model()


class RegisterForm(forms.Form):
    username = forms.CharField(max_length=20, min_length=2, error_message={
        'required': "请传入用户名!",
        'max_length': '用户名长度在2~20之间',
        'min_length': '用户名长度在2~20之间',
    })
    email = forms.EmailField(error_messages={"required": "请输入邮箱", 'invalid': "请输入一个正确的邮箱!"})
    captcha = forms.CharField(max_length=4, min_length=4)
    password = forms.CharField(max_length=20, min_length=6)

    # 对某个字段进行自定义验证 用 clean_字段名
    def clean_email(self):
        email = self.cleaned_data.get("email")
        exists = User.objects.filter(email=email).exists()
        if exists:
            raise forms.ValidationError("邮箱已经被注册!")
        return email

    def clean_captcha(self):
        captcha = self.cleaned_data.get('captcha')
        email = self.cleaned_data.get('email')

        captcha_model = CaptchaModel.objects.filter(email=email, captcha=captcha).first()
        if not captcha_model:
            raise forms.ValidationError("验证码和邮箱不匹配!")
        captcha_model.delete()
        return captcha


13.登录功能实现

登录表单验证

# auth1/forms.py

class LoginForm(forms.Form):
    email = forms.EmailField(error_messages={"required": "请传入邮箱!", "invalid": "请传入一个正确的邮箱!"})
    password = forms.CharField(max_length=20, min_length=6)
    remember = forms.IntegerField(required=False)

登录视图函数

# auth1/views.py
from django.shortcuts import render, redirect, reverse
from django.http.response import JsonResponse
import string
import random
from django.core.mail import send_mail
from .models import CaptchaModel
from django.views.decorators.http import require_http_methods
from .forms import RegisterForm, LoginForm
from django.contrib.auth import get_user_model, login
User = get_user_model()


@require_http_methods(["GET", "POST"])
def login1(request):
    if request.method == "GET":
        return render(request, "login.html")
    else:
        form = LoginForm(request.POST)
        if form.is_valid():
            email = form.cleaned_data.get("email")
            password = form.cleaned_data.get("password")
            remember = form.cleaned_data.get("remember")
            user = User.objects.filter(email=email).first()
            # django 自定义的模型 check_password 会自动将加密密码和 密码对比
            if user and user.check_password(password):
                
                # 登录 这里有设置session 保持登录状态
                login(request, user)
                
                
                if not remember:
                    # 如果没有点击记住我,那么就要设置过期时间为0, 即浏览器关闭后就会过期
                    request.session.set_expiry(0)
                # 如果点击了, 那么就什么也不做,使用默认的2周的过期时间
                return redirect('/')
            else:
                print("邮箱或密码错误!")
                # form.add_error("email", "邮箱或者验证码错误!")
                # return render(request, 'login.html', context={'form': form})
                return redirect(reverse("auth1:login"))

14.登录状态和非登录状态切换

导航条的代码是放在 base.html 中的,修改base.html 使点击登录按钮,跳转到登录页面, 点击 注册按钮,跳转到注册页面。

<!-- base.html-->
<!-- 基于 a 标签的 href 跳转-->
<div class="text-end">
    <a href="{% url "auth1:login" %}" type="button" class="btn btn-outline-primary me-2">登录</a>
    <a href="{% url "auth1:register" %}" type="button" class="btn btn-primary">注册</a>
</div>

如何判断用户是否登录,如果使用 django 内部提供的 user 模型创建的User 对象,可以使用 user.is_authenticated 来判断

如何在模版中使用user 对象, 在上下文处理器中的变量,可以直接在 模版中使用, django 在settings.py templates 的 context_processors 定义了一些变量,其中 user 对象被存储在 'django.contrib.auth.context_processors.auth' 中

image-20250604154849871

image-20250604154919876

在 base.html 中 根据用户是否登录进行判断

            {% if user.is_authenticated %}
                <div class="dropdown text-end">
                    <a href="#" class="d-block link-body-emphasis text-decoration-none dropdown-toggle" 
                       data-bs-toggle="dropdown" aria-expanded="false"> 
                    <img src="{% static "img/avator.png" %}" alt="mdo" width="32" height="32" class="rounded-circle"> 
                    </a>
                    <ul class="dropdown-menu text-small" style="">
                        <li><a class="dropdown-item" href="#">Sign out</a></li>
                    </ul>
                </div>
    
            {% else %}
                <div class="text-end">
                    <a href="{% url "auth1:login" %}" type="button" class="btn btn-outline-primary me-2">登录</a>
                    <a href="{% url "auth1:register" %}" type="button" class="btn btn-primary">注册</a>
                </div>
			{% endif %}

15.退出登录功能完成

写一个退出登录的视图函数

from django.contrib.auth import get_user_model, login, logout

def logout1(request):
    logout(request)
    return redirect("/")

base.html 点击退出登录,调用 logout 视图函数

<li><a class="dropdown-item" href="{% url "auth1:logout" %}">退出登录</a></li>

16.发布博客访问限制

点击 首页 和发布博客跳转到相关的页面, 修改 base.html

<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
    <li><a href="/" class="nav-link px-2 text-secondary">首页</a></li>
    <li><a href="{% url 'blog:blog_pub' %}" class="nav-link px-2 text-secondary">发布博客</a></li>
</ul>

没有登录的情况下,不能够跳转到发布博客页面 使用 django 中 登录装饰器login_requried, 没有登录 跳转到登录页面

# blog(app)/views.py
from django.contrib.auth.decorators import login_required
# 使用 reverse_lazy 防止视图函数未加载,进行反转
from django.urls.base import reverse_lazy

# 1. @login_required(login_url=reverse_lazy("auth1:login"))


# 或者直接写 login视图函数对应的 url 
# 2.@login_required(login_rul='/auth1/login')

# 直接写 login_required(), 在 settings.py 中写  LOGIN_URL = '/auth1/login'
# 3. login_required()

@login_required()
def pub_blog(request):
    return render(request, 'pub_blog.html')

17.博客相关模型和表创建

创建 发布博客 类型的数据模型, 博客的数据模型, 评论的数据模型

# blog(app)/models.py

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()
# Create your models here.


class BlogCategory(models.Model):
    name = models.CharField(max_length=200)


class Blog(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    pub_time = models.DateTimeField(auto_now_add=True)
    category = models.ForeignKey(BlogCategory, on_delete=models.CASCADE)
    author = models.ForeignKey(User, on_delete=models.CASCADE)


class BlogComment(models.Model):
    content = models.TextField()
    pub_time = models.DateTimeField(auto_now_add=True)
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    
# 将创建的 数据库模型 映射到 数据库中
#  确认 相关的app 在settings.py 中注册 , 然后使用数据库迁移命令
# python manage.py makemigrations
# python manage.py migrate

18.Admin系统使用讲解

django 初始写好了一个admin视图函数,可以进行后台管理

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    # 包含 blog app 中的 url
    path("", include("blog.urls")),
    path("auth1/", include("auth1.urls"))
]

可以通过修改数据库中相关的字段,来提高用户权限,对admin 后台进行登录

image-20250604204237611

更改 语言为 中文, settings.py 中 LANGUAGE_CODE = 'zh-hans'

image-20250604204817333

image-20250604204933343

想要使用 django admin 管理 app 下的 model 类, 需要在 相关的 app 中 的admin 中 写一些 admin 类来管理 相关的模型

# auth1(app)/ admin.py

from django.contrib import admin
from .models import BlogCategory, Blog, BlogComment


class BlogCategoryAdmin(admin.ModelAdmin):
    list_display = ['name']


class BlogAdmin(admin.ModelAdmin):
    list_display = ['title', 'content', 'pub_time', 'category', 'author']


class BlogCommentAdmin(admin.ModelAdmin):
    list_display = ['content', 'pub_time', 'author', 'blog']


admin.site.register(BlogCategory, BlogCategoryAdmin)
admin.site.register(Blog, BlogAdmin)
admin.site.register(BlogComment, BlogCommentAdmin)

image-20250604210001697

管理的模型类名字也可以设置, 在相关的 模型类中指定名字

# auth1(app) / models.py

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()
# Create your models here.


class BlogCategory(models.Model):
    name = models.CharField(max_length=200)

    class Meta:
        verbose_name = "分类"
        # 复数 形式
        verbose_name_plural = verbose_name


class Blog(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    pub_time = models.DateTimeField(auto_now_add=True)
    category = models.ForeignKey(BlogCategory, on_delete=models.CASCADE)
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    class Meta:
        verbose_name = "博客"
        verbose_name_plural = verbose_name


class BlogComment(models.Model):
    content = models.TextField()
    pub_time = models.DateTimeField(auto_now_add=True)
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    class Meta:
        verbose_name = "博客评论"
        verbose_name_plural = verbose_name


image-20250604210532285

修改每个模型里每个字段名为中文, 使用 verbose_name 起名

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()
# Create your models here.


class BlogCategory(models.Model):
    name = models.CharField(max_length=200, verbose_name="分类名称")

    # admin 管理界面展示 name
    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "分类"
        # 复数 形式
        verbose_name_plural = verbose_name


class Blog(models.Model):
    title = models.CharField(max_length=200, verbose_name="标题")
    content = models.TextField(verbose_name="内容")
    pub_time = models.DateTimeField(auto_now_add=True, verbose_name="发布时间")
    category = models.ForeignKey(BlogCategory, on_delete=models.CASCADE, verbose_name="分类")
    author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="作者")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "博客"
        verbose_name_plural = verbose_name


class BlogComment(models.Model):
    content = models.TextField(verbose_name="内容")
    pub_time = models.DateTimeField(auto_now_add=True, verbose_name="发布时间")
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE, verbose_name="所属博客")
    author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="作者")

    def __str__(self):
        return self.content

    class Meta:
        verbose_name = "博客评论"
        verbose_name_plural = verbose_name

image-20250604211648809

可以在 admin 页面添加一些原始的数据

19.发布博客后端功能实现

完善 pub_blog 视图函数

from django.shortcuts import render
# from django.urls.base import reverse_lazy
from django.http.response import JsonResponse
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from .models import BlogCategory, Blog, BlogComment
from .forms import PubBlogForm


@require_http_methods(["GET", "POST"])
@login_required
def pub_blog(request):
    if request.method == "GET":
        categories = BlogCategory.objects.all()
        return render(request, 'pub_blog.html', context={"categories": categories})
    else:
        form = PubBlogForm(request.POST)
        if form.is_valid():
            title = form.cleaned_data.get("title")
            content = form.cleaned_data.get("content")
            category_id = form.cleaned_data.get("category")
            # 将发布博客存储到 数据库中
            Blog.objects.create(title=title, content=content, category_id=category_id, author=request.user)
            return JsonResponse({"code": 200, "message": "博客发布成功!"})
        else:
            print(form.errors)
            return JsonResponse({"code": 400, "message": "参数错误!"})

定义发布博客的表单验证

# blog(app)/ forms.py

from django import forms


class PubBlogForm(forms.Form):
    title = forms.CharField(max_length=200, min_length=2)
    content = forms.CharField(min_length=2)
    category = forms.IntegerField()

修改 pub_blog.html 显示分类信息

<select name="category" class="form-select">
    {% for category in categories %}
    <option value="{{ category.id }}">{{ category.name }}</option>
    {% endfor %}
</select>

image-20250604214154189

20.发布博客前端功能完成

在 pub_blog.js 中 使用 ajax 向 发布博客视图函数发送 ajax 请求

window.onload = function(){

    const { createEditor, createToolbar } = window.wangEditor

    const editorConfig = {
    placeholder: 'Type here...',
    onChange(editor) {
      const html = editor.getHtml()
      console.log('editor content', html)
      // 也可以同步到 <textarea>
    },
    }

    const editor = createEditor({
    selector: '#editor-container',
    html: '<p><br></p>',
    config: editorConfig,
    mode: 'default', // or 'simple'
    })

    const toolbarConfig = {}

    const toolbar = createToolbar({
    editor,
    selector: '#toolbar-container',
    config: toolbarConfig,
    mode: 'default', // or 'simple'
    })

    // 富文本内容提交
    $("#submit-btn").click(function(event){
        // 阻止按钮的默认行为
        event.preventDefault();

        let title = $("input[name='title']").val();
        let category = $("#category-select").val();
        
        // 主要是这里 需要用 复文本编辑器的 方法获取内容
        let content = editor.getHtml();
        let csrfmiddlewaretoken = $("input[name='csrfmiddlewaretoken']").val()
        $.ajax('/blog/pub', {
            method: "POST",
            data:{title, category, content, csrfmiddlewaretoken},
            success: function(result){
                if(result['code'] == 200){
                    // 获取博客id
                    let blog_id = result['data']['blog_id'];
                    // 跳转到博客详情页面
                    window.location = '/blog/detail/' + blog_id;
                }else{
                    alert(result['mesage']);
                }
            }
        })
    });
}

image-20250604225837787

发布博客视图函数

# blog(app)/ views.py

from django.shortcuts import render
# from django.urls.base import reverse_lazy
from django.http.response import JsonResponse
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from .models import BlogCategory, Blog, BlogComment
from .forms import PubBlogForm


@require_http_methods(["GET", "POST"])
@login_required
def pub_blog(request):
    if request.method == "GET":
        categories = BlogCategory.objects.all()
        return render(request, 'pub_blog.html', context={"categories": categories})
    else:
        form = PubBlogForm(request.POST)
        if form.is_valid():
            title = form.cleaned_data.get("title")
            content = form.cleaned_data.get("content")
            category_id = form.cleaned_data.get("category")
            # 将发布博客存储到 数据库中
            blog = Blog.objects.create(title=title, content=content, category_id=category_id, author=request.user)
            return JsonResponse({"code": 200, "message": "博客发布成功!", 'data': {"blog_id": blog.id}})
        else:
            print(form.errors)
            return JsonResponse({"code": 400, "message": "参数错误!"})

21.博客详情页数据动态展示

完善 博客详情页的视图函数

# blog(app)/ views.py

from django.shortcuts import render
from .models import BlogCategory, Blog, BlogComment

def blog_detail(request, blog_id):
    try:
        blog = Blog.objects.get(pk=blog_id)
    except Exception as e:
        blog = None

    return render(request, "blog_detail.html", context={"blog": blog})

修改 blog_detail.html 中的内容

{% extends "base.html" %}

{% block title %}
	
{% endblock %}

{% block main %}
    <h1>{{ blog.title }}</h1>
    <hr>
    <div class="mt-2">
        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="30" height="30" alt="">
        <span class="ms-2">{{ blog.author.username }}</span>
        <span class="ms-2">于</span>
        <span class="ms-2">{{ blog.pub_time|date:"Y年m月d日 h时i分" }}</span>
    </div>
    <hr>
    <div class="py-2">
        {{ blog.content|safe }}
    </div>
    <hr>
    <div class="mt-2">
        <h3>评论:(10)</h3>
        <form action="" mehtod="POST">
            <div class="mt-2">
                <input type="text" class="form-control" placeholder="请输入评论">
            </div>
            <div class="text-end mt-2">
                <button type="button" class="btn btn-primary">评论</button>
            </div>
        </form>
    </div>
    
    <div class="mt-2">
        <ul class="list-group list-group-flush">
            <li class="list-group-item mt-3">
                <div class="d-flex justify-content-between text-body-secondary">
                    <div class="user-info">
                        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="40" height="40" alt="">
                        <span class="ms-2">小明</span>
                    </div>
                    <div class="create-time" style="line-height: 40px">2025/6/3 8:46</div>
                </div>
                <div class="mt-2">评论内容</div>
            </li>
            <li class="list-group-item mt-3">
                <div class="d-flex justify-content-between text-body-secondary">
                    <div class="user-info">
                        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="40" height="40" alt="">
                        <span class="ms-2">小明</span>
                    </div>
                    <div class="create-time" style="line-height: 40px">2025/6/3 8:46</div>
                </div>
                <div class="mt-2">评论内容</div>
            </li>
        </ul>
    </div>
{% endblock %}

22.博客评论功能完成

完善后端视图函数

# blog(app)/ views.py
from django.views.decorators.http import require_http_methods, require_POST
from django.shortcuts import render, redirect, reverse

@require_POST
@login_required()
def pub_comment(request):
    blog_id = request.POST.get("blog_id")
    content = request.POST.get("content")
    BlogComment.objects.create(content=content, blog_id=blog_id, author=request.user)
    # 重新加载博客详情页
    return redirect(reverse("blog:blog_detail", kwargs={'blog_id': blog_id}))

# blog(app)/ models.py 

class BlogComment(models.Model):
    content = models.TextField(verbose_name="内容")
    pub_time = models.DateTimeField(auto_now_add=True, verbose_name="发布时间")
    # 添加 related_name 可以通过 blog.comments 拿到一个 博客下的所有评论
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="comments", verbose_name="所属博客")
    author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="作者")

修改blog_detail.html

{% extends "base.html" %}


{% block main %}
    <h1>{{ blog.title }}</h1>
    <hr>
    <div class="mt-2">
        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="30" height="30" alt="">
        <span class="ms-2">{{ blog.author.username }}</span>
        <span class="ms-2">于</span>
        <span class="ms-2">{{ blog.pub_time|date:"Y年m月d日 h时i分" }}</span>
    </div>
    <hr>
    <div class="py-2">
        {{ blog.content|safe }}
    </div>
    <hr>
    <div class="mt-2">
        <h3>评论:({{ blog.comments.all | length }})</h3>
        <form action="{% url 'blog:pub_comment' %}" method="POST">
            {% csrf_token %}
            <input type="hidden" name="blog_id" value="{{ blog.id }}">
            <div class="mt-2">
                <input type="text" class="form-control" placeholder="请输入评论" name="content">
            </div>
            <div class="text-end mt-2">
                <button type="submit" class="btn btn-primary">评论</button>
            </div>
        </form>
    </div>
    
    <div class="mt-2">
        <ul class="list-group list-group-flush">
            {% for comment in blog.comments.all %}
            	<li class="list-group-item mt-3">
                <div class="d-flex justify-content-between text-body-secondary">
                    <div class="user-info">
                        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="40" height="40" alt="">
                        <span class="ms-2">{{ comment.author.username }}</span>
                    </div>
                    <div class="create-time" style="line-height: 40px">{{ comment.pub_time | date:"Y年m月d日 h时i分" }}</div>
                </div>
                <div class="mt-2">{{ comment.content }}</div>
            </li>
            {% endfor %}
        </ul>
    </div>
{% endblock %}

23.代码高亮功能

使用 这个网站 https://highlightjs.org/ 里面的 js 代码, 选择需要的编程的高亮显示,下载

image-20250605104140226

image-20250605104906401

{% block head %}
    <!-- 更换不同的高亮主题,可以更改 下面一行style 后的 css 文件-->
    <link rel="stylesheet" href="{% static 'highlight/styles/default.min.css' %}">
	<link rel="stylesheet" href="{% static 'highlight/styles/github-dark-dimmed.min.css' %}">


    <script src="{% static 'highlight/highlight.min.js' %}"></script>
{% endblock %}

<!-- 如下代码放在html 代码的末尾, 是为了让所有代码加载完成后, 再执行 javascript 代码-->
<script>
    hljs.highlightAll()
</script>
{% extends "base.html" %}

{% block head %}
    <link rel="stylesheet" href="{% static 'highlight/styles/default.min.css' %}">
    <script src="{% static 'highlight/highlight.min.js' %}"></script>
{% endblock %}

{% block main %}
    <h1>{{ blog.title }}</h1>
    <hr>
    <div class="mt-2">
        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="30" height="30" alt="">
        <span class="ms-2">{{ blog.author.username }}</span>
        <span class="ms-2">于</span>
        <span class="ms-2">{{ blog.pub_time|date:"Y年m月d日 h时i分" }}</span>
    </div>
    <hr>
    <div class="py-2">
        {{ blog.content|safe }}
    </div>
    <hr>
    <div class="mt-2">
        <h3>评论:({{ blog.comments.all | length }})</h3>
        <form action="{% url 'blog:pub_comment' %}" method="POST">
            {% csrf_token %}
            <input type="hidden" name="blog_id" value="{{ blog.id }}">
            <div class="mt-2">
                <input type="text" class="form-control" placeholder="请输入评论" name="content">
            </div>
            <div class="text-end mt-2">
                <button type="submit" class="btn btn-primary">评论</button>
            </div>
        </form>
    </div>
    
    <div class="mt-2">
        <ul class="list-group list-group-flush">
            {% for comment in blog.comments.all %}
            	<li class="list-group-item mt-3">
                <div class="d-flex justify-content-between text-body-secondary">
                    <div class="user-info">
                        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="40" height="40" alt="">
                        <span class="ms-2">{{ comment.author.username }}</span>
                    </div>
                    <div class="create-time" style="line-height: 40px">{{ comment.pub_time | date:"Y年m月d日 h时i分" }}</div>
                </div>
                <div class="mt-2">{{ comment.content }}</div>
            </li>
            {% endfor %}
        </ul>
    </div>
    <script>
    hljs.highlightAll()
    </script>
{% endblock %}

image-20250605105108591

image-20250605105432124

24.搜索功能和首页视图函数书写

在 模型类中指定 查找的时候 倒序排序

# blog(app)/ models.py
class Blog(models.Model):
    title = models.CharField(max_length=200, verbose_name="标题")
    content = models.TextField(verbose_name="内容")
    pub_time = models.DateTimeField(auto_now_add=True, verbose_name="发布时间")
    category = models.ForeignKey(BlogCategory, on_delete=models.CASCADE, verbose_name="分类")
    author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="作者")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "博客"
        verbose_name_plural = verbose_name
        # 查找博客时, 根据发布时间倒序排序
        ordering = ['-pub_time']
        
        

class BlogComment(models.Model):
    content = models.TextField(verbose_name="内容")
    pub_time = models.DateTimeField(auto_now_add=True, verbose_name="发布时间")
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="comments", verbose_name="所属博客")
    author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="作者")

    def __str__(self):
        return self.content

    class Meta:
        verbose_name = "博客评论"
        verbose_name_plural = verbose_name
        # 评论根据评论时间倒序排序
        ordering = ['-pub_time']

search 视图函数

# blog(app)/ vies.py

@require_GET
def search(request):
    # /search?q=xxx
    q = request.GET.get("q")
    # 从博客的标题和内容中查找含有q 关键字的博客
    if q:
        blogs = Blog.objects.filter(Q(title__icontains=q) | Q(content__icontains=q)).all()
        return render(request, 'index.html', context={"blogs": blogs})
    else:
        return redirect(reverse('blog:index'))

视图函数和url 的映射

from django.urls import path
from .import views

# 命名空间
app_name = "blog"


urlpatterns = [
    path("", views.index, name="index"),
    path('blog/detail/<int:blog_id>', views.blog_detail, name="blog_detail"),
    path('blog/pub', views.pub_blog, name='blog_pub'),
    path("blog/comment/pub", views.pub_comment, name="pub_comment"),
    path("blog/search", views.search, name="search")
]

修改 base.html 中的search 按钮 action 提交的视图函数

<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search" action="{% url 'blog:search' %}" method="GET">
    <input type="search" class="form-control" name='q' placeholder="搜索" aria-label="Search">
</form>

首页视图函数完善

# blog(app)/ views.py

def index(request):
    blogs = Blog.objects.all()
    return render(request, "index.html", context={"blogs": blogs})

index.html 页面

{% extends 'base.html' %}

{% block title %}
	博客首页
{% endblock %}

{% block main %}
    <h1>博客列表</h1>
    <div class="row row-cols-2 row-gap-4">
        {% for blog in blogs %}
            <div class="col">
            <div class="card">
                <div class="card-header">
                    <a href="{% url 'blog:blog_detail' blog_id =blog.id %}"> {{ blog.title }}</a>
                </div>
                <div class="card-body" style="height: 100px">
                    <p class="card-text">{{ blog.content | striptags | truncatechars:200 }}</p>
                </div>
                <div class="card-footer text-body-secondary d-flex justify-content-between">
                    <div>
                        <img src="{% static "img/avator.png" %}" class="rounded-circle" width="30" height="30" alt="">
                        {{ blog.author.username }}
                    </div>
                    <div>
                        {{ blog.pub_time | date: "Y年m月d日 h时i分" }}
                    </div>

                </div>
            </div>
        </div>
        {% endfor %}
    </div>
{% endblock %}

image-20250605113416804

项目代码地址: https://github.com/Red-brief/blog_project_django

posted @ 2025-06-05 13:44  Ref-brief  阅读(21)  评论(4)    收藏  举报