26.9 Django书籍管理练习

1. 搭建环境

1.1 创建数据库

handlebars 复制代码
Django本身不会自动创建数据库服务器或数据库实例, 这一步需要手动完成.
handlebars 复制代码
可以使用Navicat可视化工具或者命令行创建'library'数据库, 编码格式为utf8_mp4.
python 复制代码
# 连接数据库
mysql -h localhost -P 3306 -u root -p123456

# 创建library数据库并设置编码
create database library character set utf8mb4;

# 查看MySQL的编码(utf8mb4)
show variables like 'character_set_database';

# 查看library数据库的编码(utf8mb4)
show create database library;

1.2 创建Django项目

handlebars 复制代码
使用Pychamr工具创建Django项目.
项目名称: MyDjango .
应用名称: library .

1.3 连接MySQL数据库

handlebars 复制代码
编辑Django的settings.py配置文件, 使用MySQL数据库, 配置信息如下:
python 复制代码
# 数据库配置
DATABASES = {
    'default': {
        # 使用mysql数据库
        'ENGINE': 'django.db.backends.mysql',
        # ip地址
        'HOST': '127.0.0.1',
        # 端口号
        'POST': 3306,
        # 登入用户
        'USER': 'root',
        # 登入密码
        'PASSWORD': '123456',
        # 连接的库
        'NAME': 'library',
        # 设置编码
        'CHARSET': 'utf8mb4'
    }
}

1.4 创建表模型

handlebars 复制代码
* 1. 作者表(主表)一对一作者详情表(次表), 外键建立在作者详情表中名为author.
* 2. 出版社(一)一对多书籍表(多), 外键字段建立在多的一方书籍表中名为publish.
* 3. 作者表多对多书籍表, 外键字段建立在查询频率高的一方书籍表中名为author.
1.4.1 作者表
handlebars 复制代码
作者表拥有id, name, age三个字段.
python 复制代码
# library/models.py
from django.db import models


# 作者表模型
class Author(models.Model):
    # id字段(自动添加)
    name = models.CharField(max_length=12, verbose_name='作者名称')
    age = models.IntegerField(verbose_name='作者年龄')

    def __str__(self):
        return f'{self.name}'
    
1.4.2 作者详情表
handlebars 复制代码
作者详情表拥有id, phone, addr, author_id四个字段.
python 复制代码
# 作者详情表模型
class AuthorDetail(models.Model):
    # id字段(自动添加)
    phone = models.CharField(max_length=11, verbose_name='手机号码')
    addr = models.CharField(max_length=32, verbose_name='作者住址')
    # 外键字段(一对一关联作者表实例, 并设置级联删除)
    author = models.OneToOneField(to='Author', on_delete=models.CASCADE,
                                  related_name='author_detail', verbose_name='作者名称')

    def __str__(self):
        return f'{self.id}'
1.4.3 出版社表
handlebars 复制代码
出版社表拥有id, name, email, 三个字段.
python 复制代码
# 出版社表模型
class Publish(models.Model):
    # id字段(自动添加)
    name = models.CharField(max_length=12, verbose_name='出版社名称')
    addr = models.CharField(max_length=32, verbose_name='出版社地址')
    email = models.EmailField(verbose_name='出版社邮箱')

    def __str__(self):
        return f'{self.name}'
1.4.4 书籍表模型
handlebars 复制代码
书籍表表拥有id, title, price, publication_date, publish, author六个字段.
python 复制代码
# 书籍表模型
class Book(models.Model):
    # id字段(自动添加)
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='书籍价格')
    publication_date = models.DateField(auto_now_add=True, verbose_name='书籍发布日期')
    # 外键字段
    # 一对多出版社
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE, verbose_name='出版社名称')
    # 多对多作者表
    author = models.ManyToManyField(to=Author, verbose_name='作者列表')

    def __str__(self):
        return f'{self.title}'
handlebars 复制代码
书籍作者关联表会自动创建有id. book_id, author_id三个字段.
1.4.5 完整代码
python 复制代码
from django.db import models


# 作者表模型
class Author(models.Model):
    # id字段(自动添加)
    name = models.CharField(max_length=12, verbose_name='作者名称')
    age = models.IntegerField(verbose_name='作者年龄')

    def __str__(self):
        return f'{self.name}'


# 作者详情表模型
class AuthorDetail(models.Model):
    # id字段(自动添加)
    phone = models.CharField(max_length=11, verbose_name='手机号码')
    addr = models.CharField(max_length=32, verbose_name='作者住址')
    # 外键字段(一对一关联作者表实例, 并设置级联删除)
    author = models.OneToOneField(to='Author', on_delete=models.CASCADE,
                                  related_name='author_detail', verbose_name='作者名称')

    def __str__(self):
        return f'{self.id}'


# 出版社表模型
class Publish(models.Model):
    # id字段(自动添加)
    name = models.CharField(max_length=12, verbose_name='出版社名称')
    addr = models.CharField(max_length=32, verbose_name='出版社地址')
    email = models.EmailField(verbose_name='出版社邮箱')

    def __str__(self):
        return f'{self.name}'


# 书籍表模型
class Book(models.Model):
    # id字段(自动添加)
    title = models.CharField(max_length=32, verbose_name='书籍名称')
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='书籍价格')
    publication_date = models.DateField(auto_now_add=True, verbose_name='书籍发布日期')
    # 外键字段
    # 一对多出版社
    publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE, verbose_name='出版社名称')
    # 多对多作者表
    author = models.ManyToManyField(to=Author, verbose_name='作者列表')

    def __str__(self):
        return f'{self.title}'
1.4.6 数据库迁移
python 复制代码
# 在终端执行数据库迁移命令
PS D:\MyDjango> python manage.py makemigrations 
Migrations for 'library':
  library\migrations\0001_initial.py
    - Create model Author
    - Create model Publish
    - Create model Book
    - Create model AuthorDetail
    # 不知道为什么这里没有添加外键的提示???
PS D:\MyDjango> python manage.py migrate
...
handlebars 复制代码
使用Navicat工具查询创建的表格.
handlebars 复制代码
逆向数据库到模型.

1.5 创建表记录

handlebars 复制代码
在Django ORM中, 执行数据库操作时, 通常不会直接返回一个'成功'的消息, 而是通过返回的结果或是否抛出异常来判断操作是否成功.
1.5.1 作者表记录
id name age
1 aa 18
2 bb 19
3 cc 20
python 复制代码
import os

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "MyDjango.settings")
    import django

    django.setup()

    from library.models import Author

    # 创建作者列表
    author_list = [
        Author(name='aa', age=18),
        Author(name='bb', age=19),
        Author(name='cc', age=20),
    ]

    # 批量创建作者实例
    res = Author.objects.bulk_create(author_list)
    print(res)
1.5.2 作者详情表记录
id phone addr author_id
1 111 北京 1
2 222 上海 2
3 333 深圳 3
python 复制代码
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')
    import django

    django.setup()

    from library.models import AuthorDetail

    # 创建作者详情列表, 这里外键字段直接使用id值.
    author_detail_list = [
        AuthorDetail(phone='111', addr='北京', author_id=1),
        AuthorDetail(phone='222', addr='上海', author_id=2),
        AuthorDetail(phone='333', addr='深圳', author_id=3),
    ]

    # 批量创建作者详细实例
    res = AuthorDetail.objects.bulk_create(author_detail_list)
    print(res)
handlebars 复制代码
在Django中, bulk_create方法用于批量创建对象, 这样可以减少数据库操作次数, 提高性能.
但是, 使用bulk_create方法创建对象时, 这些对象在Python层面的实例并不会立即获得数据库中的ID.
这是因为bulk_create方法并不会立即对这些对象进行数据库查询以获取它们的ID.
1.5.3 出版社表记录
id name addr email
1 北京出版社 北京 bj@qq.com
2 上海出版社 上海 sh@qq.com
3 深圳出版社 深圳 sz@qq.com
python 复制代码
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')
    import django

    django.setup()

    from library.models import Publish

    # 创建出版社实例列表
    publish_list = [
        Publish(name='北京出版社', addr='北京', email='bj@qq.com'),
        Publish(name='上海出版社', addr='上海', email='sh@qq.com'),
        Publish(name='深圳出版社', addr='深圳', email='sz@qq.com'),
    ]

    # 批量创建出版社实例
    res = Publish.objects.bulk_create(publish_list)
    print(res)
1.5.4 书籍表记录
id title price publication_date publish_id author_id
1 Python 100.01 自动生成 1 [1]
2 MySQL 200.02 自动生成 2 [2]
3 Linux 300.03 自动生成 3 [3]
4 HTML 400.04 自动生成 1 [1 ,2]
python 复制代码
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')
    import django

    django.setup()

    from library.models import Book

    # 创建书籍实例列表
    book_list = [
        Book(title='Python', price=100.01, publish_id=1),
        Book(title='MySQL', price=200.02, publish_id=2),
        Book(title='Linux', price=300.03, publish_id=3),
        Book(title='HTML', price=400.04, publish_id=1)
    ]

    # 批量创建书籍实例
    res = Book.objects.bulk_create(book_list)
    print(res)
handlebars 复制代码
这里的对象不能使用外键, 例如:
book1 = Book(title='Python', price=100.01, publish_id=1)
book1.author.set([1])  # 视图在创建对象后使用外键设置多对多关联表的信息, 然后报错:
ValueError: "<Book: Python>" needs to have a value for field "id" before this many-to-many relationship can be used.
ValueError: "<Book: Python>" 需要先为字段 "id" 提供值, 然后才能使用此多对多关系.
这是因为Django的多对多关系是通过一个额外的表来实现的, 这个表需要存储关联的两个对象的主键.
如果一个对象还没有保存到数据库, 它就没有主键, 因此无法建立多对多关系.
1.5.5 书籍作者关联表
id book_id author_id
1 1 1
2 2 2
3 3 3
4 4 1
5 4 2
handlebars 复制代码
在Django中, 当使用ManyToManyField字段时, Django会自动创建一个中间表来管理多对多关系.
然而, 这个中间表并没有直接暴露为一个模型, 因此不能像操作其他模型那样直接操作它.

如果需要访问或修改中间表的数据, 应该通过ManyToManyField提供的API来进行, 比如使用: add(), remove(), set()和clear()等方法.
这些方法会自动处理中间表的数据, 而不需要直接操作它.
python 复制代码
    # 这种方式需要多次查询数据库
    book_obj = models.Book.objects.filter(pk=1).first()
    book_obj.author.add(1)

    book_obj = models.Book.objects.filter(pk=2).first()
    book_obj.author.add(2)

    book_obj = models.Book.objects.filter(pk=3).first()
    book_obj.author.add(3)

    book_obj = models.Book.objects.filter(pk=4).first()
    book_obj.author.add(1, 2)
handlebars 复制代码
使用in查询来一次性获取多本书, 然后遍历这些书并为它们添加作者.
这样可以减少数据库查询的次数, 从而提高效率.
python 复制代码
import os

if __name__ == "__main__":
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'MyDjango.settings')
    import django

    django.setup()

    from library.models import Book

    # 执行一次查询
    books = Book.objects.filter(pk__in=[1, 2, 3, 4])
    for book in books:
        if book.pk == 1:
            res = book.author.add(1)  # 第一本书添加ID为1的作者
            print(res)
        elif book.pk == 2:
            res = book.author.add(2)  # 第二本书添加ID为2的作者
            print(res)
        elif book.pk == 3:
            res = book.author.add(3)  # 第三本书添加ID为3的作者
            print(res)
        elif book.pk == 4:
            res = book.author.add(1, 2)  # 第四本书添加ID为1, 2的作者
            print(res)
	

1.6 静态文件配置

handlebars 复制代码
项目中需要使用到jQuery(3.7.1)框架与bootstrap(3.3.7)框架.

jQuery文件下载地址: https://code.jquery.com/jquery-3.7.1.min.js , 将代码复制到本地文件.
bootstrap框架下载地址: https://v3.bootcss.com/getting-started/#download .
handlebars 复制代码
静态文件配置步骤:
* 1. 项目目录下创建static目录.
* 2. settings.py配置文件中开放静态文件的路径.
python 复制代码
STATIC_URL = '/static/'
# 静态文件目录
STATICFILES_DIRS = [
    BASE_DIR / 'static'
]
handlebars 复制代码
* 3. static目录下创建js目录, 复制jQuery文件到js目录中.
* 4. 复制bootstrap到static目录下.

2. 主页

handlebars 复制代码
* 1. 在项目配置目录的路由文件中设置路由分发.
* 2. 在应用的目录下创建子路由文件.
* 3. 编写路由与视图函数的对应关系.
* 4. 在视图层中写视图函数处理请求.
* 5. 编写视图函数放回的主页面.

2.1 路由层

handlebars 复制代码
在项目配置目录的路由文件中设置路由分发.
python 复制代码
# MyDjango/MyDjango/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    # 设置子路由
    path('', include(('library.urls', 'library'), namespace='library'))
]
handlebars 复制代码
在应用的目录下创建子路由文件并编写路由与视图函数的对应关系, 主页面使用根目录(/), 也就是: http://127.0.0.1:8000/ . 
python 复制代码
# library/urls.py
from django.urls import path
from library.views import home

urlpatterns = [
    # 主页面
    path('', home, name='library')
]

2.2 视图层

handlebars 复制代码
主页面视图返回一个home主页面.
python 复制代码
# library/view.py
from django.shortcuts import render


# 主页面视图
def home(request):
    return render(request, 'home.html', locals())

2.3 模板层

2.3.1 模板页面
handlebars 复制代码
1. 在templates目录下创建base模板页面.
2. 在页面中导入jQuery与bootstrap文件.
3. 在页面中划分css, html, js三个块, 用于模板继承.
4. 在templates目录下创建home页面并继承base模板页面.
html 复制代码
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>图书管理系统</title>
    <!-- 加载静态文件-->
    {% load static %}
    <script src="{% static 'js/jquery.js' %}"></script>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
    <script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>

    <!-- css局部修改区域 -->
    {% block css %}

    {% endblock %}
</head>
<!-- 使用宽松布局 -->
<body class="container-fluid">
<!-- html局部修改区域 -->
{% block html %}

{% endblock %}

<!-- js局部修改区域 -->
{% block js %}

{% endblock %}
</body>
</html>
html 复制代码
<!-- templates/home.html -->
{% extends "base.html" %}
2.3.2 导航栏
handlebars 复制代码
在base模板页面中添加一个导航条, 代码如下(这里只是为了装饰页面, 大部分功能没有实现):
html 复制代码
<!-- 导航条, bg-info蓝色背景 -->
<nav class="navbar bg-info">
    <!-- 全宽布局, 导航栏的元素排列间隙大 -->
    <div class="container-fluid">
        <!-- 导航栏标题 -->
        <div class="navbar-header">
            <!-- 导航栏默认是折叠的. 点击这个按钮时, 会移除collapsed类, 表示导航栏已展开. -->
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <!-- 这三个span元素通过CSS样式被渲染成三条横线, 用于在小屏幕设备上表示导航栏的展开/收起状态. -->
                <span class="sr-only">切换导航</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">图书馆</a>
        </div>

        <!-- 导航栏内容, 包裹了导航栏中应该在较小屏幕设备上折叠起来的所有内容, 通过带有navbar-toggler类的按钮控制 -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <!-- 下拉列表 -->
            <ul class="nav navbar-nav">
                <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="#">Python</a></li>
                        <li><a href="#">MySQL</a></li>
                        <li><a href="#">Linux</a></li>
                    </ul>
                </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">
                <li>
                    <a href="#">收藏本页面</a>
                </li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
                       aria-haspopup="true" aria-expanded="false">背景<span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">浅绿色</a></li>
                        <li><a href="#">浅粉色</a></li>
                        <li><a href="#">浅黄色</a></li>
                    </ul>
                </li>
            </ul>
        </div>
    </div>
</nav>
handlebars 复制代码
启动项目, 访问: http://127.0.0.1:8000/ , 目前的主页面效果如下:
2.3.3 页面内容
handlebars 复制代码
* 1. 页面内容使用栅格布局3-9分.
* 2. 栅格左边部分存放一个功能选项侧边栏并存放多个功能选项, 还实现点击列表项时使其高亮显示.
* 3. 栅格右边部分放一个功能展示面板, 面板含有面板标题和面板内容两个区域, 将面板内容作为可替换区域.
html 复制代码
<!-- 页面内容使用3-9布局 -->
<div class="row">

    <!-- 功能选项侧边栏 -->
    <div class="col-md-3">
        <div class="list-group">
            <a href="/" class="list-group-item active">首页</a>
            <a href="#" class="list-group-item">书籍列表</a>
            <a href="#" class="list-group-item">出版社列表</a>
            <a href="#" class="list-group-item">作者列表</a>
            <a href="#" class="list-group-item">更多</a>
        </div>
    </div>

    <!-- 功能展示面板 -->
    <div class="col-md-9">
        <div class="panel panel-primary">
            <!-- 标题-->
            <div class="panel-heading">
                <h3 class="panel-title">
                    <!-- 面板标题修改区域 -->
                    {% block panel_title %}

                    {% endblock %}
                </h3>
            </div>
            <!-- 内容-->
            <div class="panel-body">
                <!--  面板主题修改区域 -->
                {% block panel_body %}

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

</div>
handlebars 复制代码
这段代码的主要作用是在网页上创建一个带有响应式布局的侧边栏或导航栏, 用于引导用户访问网站的不同部分或功能.
其中包含了五个链接选项: '首页', '图书列表', '出版社列表', '作者列表'和'更多'.
而内容展示区则通过面板来展示链接选项的内容.
handlebars 复制代码
启动项目, 访问: http://127.0.0.1:8000/ , 目前的主页面效果如下:
handlebars 复制代码
为列表项添加模板标签, 根据请求的URL来决定哪个列表项应该被高亮(active类为激活状态有高亮效果, disabled为禁用状态不可选中).
html 复制代码
<div class="list-group">
    <a href="/"
       class="list-group-item {% if request.path  == '/' %} active {% endif %}">首页</a>
    <a href=""
       class="list-group-item {% if request.path == '/book_list' %} active {% endif %}">书籍列表</a>
    <a href="#"
       class="list-group-item {% if request.path == '/publish_list' %} active {% endif %}">出版社列表</a>
    <a href="#"
       class="list-group-item {% if request.path == '/author_list' %} active {% endif %}">作者列表</a>
    <a href="#" class="list-group-item disabled">更多</a>
</div>
handlebars 复制代码
启动项目, 访问主页: 127.0.0.1:8000 , 查看高亮效果:
handlebars 复制代码
列表项的a标签通过反向解析生成url, 必须先在路由层创建好路由, 模板层中才能使用, 否则会报错.
还没有填写url地址的列表项还不能点击.
2.3.4 主页面板
handlebars 复制代码
主页面面板是访问主页面默认展示的信息.
在home页面中继承base.html页面, 并重新定义css, panel_title, panel_body三个块的内容.
在css块中自定义一个响应式类, 确保图片在不同屏幕尺寸下保持良好的布局和显示.
在panel_title块中定义面板的名称.
在panel_body块中展示一个一个轮转图和三张缩略图.
handlebars 复制代码
轮转图的三张图片连接:
1. https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fs2.loli.net%2F2022%2F03%2F10%2FCNJFHvDlquibBda.jpg&pos_id=img-S9SIts4y-1722648817610)
2. https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fs2.loli.net%2F2022%2F03%2F10%2FAWzJVDgPKlc8OZt.jpg&pos_id=img-ZCuvdZg2-1722648821936)
3. https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fs2.loli.net%2F2022%2F03%2F10%2FIZlAELwBqPFjspU.jpg&pos_id=img-jb56HOte-1722648831450)

缩略图的三张图片连接:
1. https://s2.loli.net/2022/03/10/I4ziahsHbWCAkJU.jpg
2. https://s2.loli.net/2022/03/10/fFm9Lu2hkQRoJNg.jpg
3. https://s2.loli.net/2022/03/10/8ojETnzqRkLyG6h.jpg


handlebars 复制代码
主页的信息就不写进去数据库了直接在html中写(懒).
html 复制代码
<!-- 继承模板页码 -->
{% extends "base.html" %}

<!-- 继承css块 -->
{% block css %}
    <style>
        /* 图片的响应式工具类, 它确保图片能够在不同屏幕尺寸下保持良好的布局和显示 */
        .img-fluid {
            width: 100%;
            height: auto;
        }
    </style>
{% endblock %}

<!-- 继承面板标题块 -->
{% block panel_title %}主页功能展示{% endblock %}


<!-- 继承面板主体块 -->
{% block panel_body %}
    <!-- 轮转图 -->
    <div>
        <div id="carousel-example-generic" class="carousel slide" data-ride="carousel">
            <!-- 指标 -->
            <ol class="carousel-indicators">
                <li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li>
                <li data-target="#carousel-example-generic" data-slide-to="1"></li>
                <li data-target="#carousel-example-generic" data-slide-to="2"></li>
            </ol>

            <!-- 幻灯片装饰器 -->
            <div class="carousel-inner " role="listbox">
                <div class="item active ">
                    <!--style="width: 100%;"是图片铺满转轮图-->
                    <img src="https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fs2.loli.net%2F2022%2F03%2F10%2FCNJFHvDlquibBda.jpg&pos_id=img-S9SIts4y-1722648817610)" alt="..." class="img-fluid">
                </div>
                <div class="item">
                    <img src="https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fs2.loli.net%2F2022%2F03%2F10%2FAWzJVDgPKlc8OZt.jpg&pos_id=img-ZCuvdZg2-1722648821936)" alt="..." class="img-fluid">
                </div>
                <div class="item">
                    <img src="https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fs2.loli.net%2F2022%2F03%2F10%2FIZlAELwBqPFjspU.jpg&pos_id=img-jb56HOte-1722648831450)" alt="..." class="img-fluid">
                </div>
            </div>

            <!-- 控制 -->
            <a class="left carousel-control" href="#carousel-example-generic" role="button"
               data-slide="prev">
                <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
                <!-- class="sr-only 这个类通常用于屏幕阅读器(Screen Readers), 它对于视觉用户是不可见的. -->
                <span class="sr-only">上一页</span>
            </a>
            <a class="right carousel-control" href="#carousel-example-generic" role="button"
               data-slide="next">
                <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
                <span class="sr-only">下一页</span>
            </a>
        </div>
    </div>
    <br>
    <!-- 缩略图 -->
    <div>
        <div class="row">
            <div class="col-sm-6 col-md-4">
                <div class="thumbnail">
                    <img src="https://s2.loli.net/2022/03/10/I4ziahsHbWCAkJU.jpg" alt="...">
                    <div class="caption">
                        <h3>Python</h3>
                        <p>由荷兰数学和计算机科学研究学会的吉多·范罗苏姆于1990年代初设计, 作为一门叫做ABC语言的替代品.
                            Python提供了高效的高级数据结构, 还能简单有效地面向对象编程...</p>
                        <p><a href="#" class="btn btn-primary" role="button">关注</a> <a
                                href="#" class="btn btn-default" role="button">收藏</a></p>
                    </div>
                </div>
            </div>

            <div class="col-sm-6 col-md-4">
                <div class="thumbnail">
                    <img src="https://s2.loli.net/2022/03/10/fFm9Lu2hkQRoJNg.jpg" alt="...">
                    <div class="caption">
                        <h3>MySQL</h3>
                        <p>是最流行的关系型数据库管理系统,
                            在WEB应用方面MySQL是最好的RDBMS(关系数据库管理系统)应用软件之一...</p>
                        <p><a href="#" class="btn btn-primary" role="button">关注</a>
                            <a href="#" class="btn btn-default" role="button">收藏</a></p>
                    </div>
                </div>
            </div>

            <div class="col-sm-6 col-md-4">
                <div class="thumbnail">
                    <img src="https://s2.loli.net/2022/03/10/8ojETnzqRkLyG6h.jpg" alt="...">
                    <div class="caption">
                        <h3>Linux</h3>
                        <p>
                            全称GNU/Linux, 是一套免费使用和自由传播的类UNIX操作系统, 它主要受到Minix和Unix思想的启发,
                            是一个基于POSIX和Unix的多用户, 多任务, 支持多线程和多CPU的操作系统...</p>
                        <p><a href="#" class="btn btn-primary" role="button">Button</a> <a
                                href="#" class="btn btn-default" role="button">Button</a></p>
                    </div>
                </div>
            </div>
        </div>
    </div>
{% endblock %}
handlebars 复制代码
启动项目, 访问: http://127.0.0.1:8000/ , 主页面效果如下:

3. 书籍列表

3.1 路由层

handlebars 复制代码
在子路由中添加一个路由, 当用户访问: 127.0.0.1:8000/book_list时展示书籍信息.
python 复制代码
# library/urls.py
from django.urls import path
from library.views import home, book_list

urlpatterns = [
    # 主页面
    path('', home, name='library'),
    # 书籍列表
    path('book_list', book_list, name='book_list')
]

3.2 视图层

handlebars 复制代码
视图函数中读取数据库中的所有的书籍实例并返回给book_list页面(现在数量量少, 先不考虑分页的问题).
python 复制代码
# library/views.py
from django.shortcuts import render
# 导入模型
from library.models import Book


# 书籍列表
def book_list(request):
    # 读取所有书籍实例
    books = Book.objects.all()
    # 返回页面与书籍实例
    return render(request, 'book_list.html', locals())

3.3 模板层

handlebars 复制代码
书籍列表面板展示所有书籍的信息.
1. 在侧边栏的书籍列表中绑定访问地址为.
2. 在templates目录下创建book_list.html页面.
3. 在书籍列表页面中继承base.html页面, 并重新定义css, panel_title, panel_body三个块的内容.
4. 在css块中自定义一个类, 让表格的每行文字垂直对齐(默认向上对齐不怎么好看).
5. 在panel_title块中定义面板的名称.
6. 在panel_body块中使用表格展示书籍信息, 表单展示的信息如下:
序号 编号 书名 作者 出版社 出版日期 价格 操作按钮
1 书籍id号 唯一 可能有多个 唯一 唯一 唯一 编辑 删除
html 复制代码
<!-- 在base.html中为图书列表选项的绑定url地址 -->
<a href="{% url 'library:book_list' %}"
   class="list-group-item {% if request.path == '/book_list' %} active {% endif %}">书籍列表</a>
html 复制代码
<!-- templates/book_list.html -->
<!-- 继承模板页码 -->
{% extends "base.html" %}

{% block css %}
    <style>
        .table > tbody > tr > td {
            vertical-align: middle;
        <!-- 书籍信息在表格中垂直对齐 -->
        }
    </style>

{% endblock %}
<!-- 继承面板标题块 -->
{% block panel_title %}书籍信息{% endblock %}

<!-- 继承面板主体块 -->
{% block panel_body %}
    <!-- 表单. table-striped表单条纹, table-hover:鼠标悬停效果-->
    <table class="table table-striped table-hover">
        <thead>
        <tr>
            <th>序号</th>
            <th>编号</th>
            <th>书名</th>
            <th>作者</th>
            <th>出版社</th>
            <th>出版时间</th>
            <th>价格</th>
            <th>操作</th>
        </tr>
        </thead>
        <!-- 遍历书籍实例 -->
        <tbody>
        {% for book in  books %}
            <tr>
                <td>{{ forloop.counter }}</td>
                <td>{{ book.pk }}</td>
                <td>{{ book.title }}</td>
                <td>
                    {% for author_obj in book.author.all %}
                        <!--每个遍历循环都有一个forloop对象-->
                        {% if forloop.last %}  <!-- 判断当前的作者对象是否为最后一个遍历的元素 --->
                            {{ author_obj.name }} 
                        {% else %}
                            {{ author_obj.name }},
                        {% endif %}
                    {% endfor %}
                </td>
                <td>{{ book.publish.name }}</td>
                <td>{{ book.publication_date|date:'Y-m-d' }}</td>
                <td>{{ book.price }}</td>
                <td>
                    <a href="" class="btn btn-primary btn-sm">编辑</a>
                    <a href="" class="btn btn-danger btn-sm">删除</a>
                </td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
{% endblock %}
handlebars 复制代码
启动项目, 访问: http://127.0.0.1:8000/book_list , 书籍列表页面效果如下:

4. 添加书籍

4.1 路由层

handlebars 复制代码
在子路由中添加一个路由, 当用户访问: 127.0.0.1:8000/book_add时展示书籍添加页面.
python 复制代码
# library/urls.py
from django.urls import path
from library.views import home, book_list, book_add

urlpatterns = [
    # 主页面
    path('', home, name='library'),
    # 书籍列表
    path('book_list', book_list, name='book_list'),
    # 书籍添加
    path('book_add', book_add, name='book_add')
]

4.2 视图层

handlebars 复制代码
先分析添加书籍表单中需要的信息:
* 1. id字段 , 自增不用管.
* 2. title书名字段, 需要表单提供. 
* 3. price书籍价格字段, 需要表单提供.                     
* 4. publication_date出版时间, 自动添加不用管.  
* 5. 外键字段publish, 使用已经存在的出版社.
* 6. 外键字段author, 使用已经存在的作者.
handlebars 复制代码
视图需要读取数据库中的作者表和出版社表的数据再与添加图书页面一同返回.
python 复制代码
# library/views.py
from django.shortcuts import render
# 导入模型
from library.models import Book, Publish, Author


def book_add(request):
    # 获取所有的出版社实例
    publishing = Publish.objects.all()
    # 获取所有的作者实例
    authors = Author.objects.all()

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

4.3 模板层

handlebars 复制代码
1. 在base.html模板页面的侧边栏中添加一个列表项.
2. 在templates目录下创建book_add.html页面.
3. 在添加书籍页面中继承base.html页面, 并重新定义panel_title, panel_body两个块的内容.
4. 在panel_title块中定义面板的名称.
5. 在panel_body块中添加一个表单用于收集书籍信息, 收集的信息如下:
handlebars 复制代码
* 1. id字段 , 自增不用管.
* 2. title书名字段, 需要表单提供, 使用input输入框, 名称为: title.
* 3. price书籍价格字段, 需要表单提供, 使用input输入框, 名称为: price.               
* 4. publication_date出版时间字段, 自动添加(数据插入的时间).
* 5. 外键字段publish, 使用已经存在的出版社, 使用下拉框, 名称为: publish_id, 并设置单选, 展示的是出版社的名称提交的是出版社id. 
* 6. 外键字段author, 使用已经存在的作者, 使用下拉框, 名称为: publish_id, 并设置多选, 展示的是作者的名称提交的是作者id. 
html 复制代码
<!-- base.html的侧边栏中添加一个列表项 -->
<a href="{% url 'library:book_add' %}"
   class="list-group-item {% if request.path == '/book_add' %} active {% endif %}">添加书籍</a>
html 复制代码
<!-- templates/book_add.html -->

<!-- 继承模板页码 -->
{% extends "base.html" %}

<!-- 继承面板标题块 -->
{% block panel_title %} 书籍添加 {% endblock %}

<!-- 继承面板主体块 -->
{% block panel_body %}

    <!-- 表单 -->
    <form action="" method="post">
        <div class="input-group">
            <span class="input-group-addon">书名:</span>
            <input type="text" class="form-control mt-5" placeholder="书籍名称" name="title">
        </div>
        <br>

        <div class="input-group">
            <span class="input-group-addon">价格:</span>
            <input type="text" class="form-control" placeholder="书籍价格" name="price">
        </div>
        <br>

        <div class="input-group">
            <span class="input-group-addon">出版社:</span>
            <select name="publish_id" class="form-control">
                <!--selected默认选中, disabled不可手动选中, hidden隐藏选项-->
                <option selected disabled hidden>点击选择</option>
                {% for publish in publishing %}
                    <option value="{{ publish.pk }}">{{ publish.name }}</option>
                {% endfor %}

            </select>
        </div>
        <br>

        <div class="input-group">
            <span class="input-group-addon">作者:</span>
            <select name="author_id" multiple="multiple" class="form-control">
                {% for author in authors %}
                    <option value="{{ author.pk }}">{{ author.name }}</option>
                {% endfor %}
            </select>
        </div>
        <br>

        <p>
            <input type="submit" value="添加" class="btn btn-primary form-control">
        </p>
    </form>
    
{% endblock %}
handlebars 复制代码
启动项目, 访问: http://127.0.0.1:8000/book_add , 查看添加书籍页面的效果:

4.4 写入数据

handlebars 复制代码
1. 修改视图函数, 从POST请求提交的表单中获取书籍信息.
2. 需要为两张表写入数据(表单中提交的数据都是字符串, 在写入数据库的时候会自动转换类型.)
   首先, 往书籍表中写数据(需要为title, price, publish_id三个字段提供值).
   最后, 使用书籍对象的author外键字段获取到书籍作者关联表, 再使用set()方法添加多对多关联.
   set()方法允许传递一个可迭代对象(比如列表, 元组等), 它会遍历这个可迭代对象, 并逐个添加其中的元素.
3. 写入成功后重定向到书籍列表页面.
python 复制代码
# library/views.py
from django.shortcuts import render, redirect
# 导入模型
from library.models import Book, Publish, Author


# 添加书籍
def book_add(request):
    # 处理GET请求:
    if request.method == 'GET':
        # 获取所有的出版社实例
        publishing = Publish.objects.all()
        # 获取所有的作者实例
        authors = Author.objects.all()

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

    # 处理POST请求:
    if request.method == 'POST':
        # 获取书籍信息
        title = request.POST.get('title')
        price = request.POST.get('price')
        publish_id = request.POST.get('publish_id')
        # 作者id有多个, 通过getlist方法获取, 值是一个列表
        author_id = request.POST.getlist('author_id')

        # 数据校验跳过
        # print(title, price, publish_id, author_id)

        # 创建书籍实例
        book = Book.objects.create(title=title, price=price, publish_id=publish_id)
        # # 添加多对多关联
        book.author.set(author_id)
        # 重定向到书籍列表页面
        return redirect('/book_list')
handlebars 复制代码
启动项目, 访问: http://127.0.0.1:8000/book_add , 填写信息并提交.
handlebars 复制代码
实例添加成功之后, 会跳转到书籍列表页面, 可以查看新添加的书籍记录.

5. 书籍修改

handlebars 复制代码
编辑书籍信息的思路:
* 1. 给编辑按钮绑定请求地址, 在书籍列表中点击修改时, 提交一个get请求, 并携带书籍的id.
* 2. 路由中使用转换器获取id值并传递给视图函数.
* 3. 后端处理请求, 并通过书籍id获取对应书籍实例数据, 返回一个书籍修改页面和需要修改的书籍实例.
* 4. 书籍修改页面中展示一个表单, 表单中展示可修改的书籍信息.

5.1 路由层

handlebars 复制代码
在子路由中添加一个路由, 当用户访问: 127.0.0.1:8000/book_set/id时展示书籍修改页面.
请求地址携带了书籍id, 可以通过内置的转换器来捕获获取这个值.
python 复制代码
# library/urls.py
from django.urls import path
from library.views import home, book_list, book_add, book_set

urlpatterns = [
    # 主页面
    path('', home, name='library'),
    # 书籍列表
    path('book_list', book_list, name='book_list'),
    # 书籍添加
    path('book_add', book_add, name='book_add'),
    # 书籍修改
    path('book_set/<int:book_id>', book_set, name='book_set')
]

5.2 视图层

handlebars 复制代码
修改数据页面需要获取所有的出版社信息和作者信息和修改的书籍信息, 在视图中获取并返回.
python 复制代码
# library/views.py

# 书籍修改, 需要接收内置转换器传递的参数
def book_set(request, book_id):
    # 处理GET请求:
    if request.method == 'GET':
        # 获取所有的出版社实例
        publishing = Publish.objects.all()
        # 获取所有的作者实例
        authors = Author.objects.all()
        # 获取书籍实例
        book = Book.objects.filter(id=book_id).first()
        return render(request, 'book_set.html', locals())

5.3 模板层

handlebars 复制代码
在book_list.html页面中为编辑按钮绑定跳转地址, 提交的get请求携带书籍实例的id.
html 复制代码
<!-- templates/book_list.html -->
<!-- book.pk前面不需要添加/url模板标签会自动处理URL的生成, 只需要提供正确的参数即可,  -->
<a href="{% url 'library:book_set'  book.pk %}" class="btn btn-primary btn-sm">编辑</a>
handlebars 复制代码
在book_set.html页面中展示书籍实例的信息, 通过{% if %}标签可以对外键字段进行默认选中.
html 复制代码
<!-- templates/book_set.html -->
<!-- 继承模板页码 -->
{% extends "base.html" %}

<!-- 继承面板标题块 -->
{% block panel_title %} 书籍信息修改 {% endblock %}

<!-- 继承面板主体块 -->
{% block panel_body %}
    <!-- 表单  -->
    <form action="" method="post">
        {% csrf_token %}
        <div class="input-group">
            <span class="input-group-addon">书名:</span>
            <input type="text" class="form-control mt-5" placeholder="书籍名称" name="title" value="{{ book.title }}">
        </div>
        <br>

        <div class="input-group">
            <span class="input-group-addon">价格:</span>
            <input type="text" class="form-control" placeholder="书籍价格" name="price" value="{{ book.price }}">
        </div>
        <br>

        <div class="input-group">
            <span class="input-group-addon">出版社:</span>
            <select name="publish_id" class="form-control">
                {% for publish in publishing %}
                    <!-- 如果出版社id是书籍实例的外键则默认选中 -->
                    {% if publish.id == book.publish_id %}
                        <option selected value="{{ publish.pk }}">{{ publish.name }}</option>
                    {% else %}
                        <option value="{{ publish.pk }}">{{ publish.name }}</option>
                    {% endif %}
                {% endfor %}

            </select>
        </div>
        <br>

        <div class="input-group">
            <span class="input-group-addon">作者:</span>
            <select name="author_id" multiple="multiple" class="form-control">
                {% for author in authors %}
                    <!--all()获取的是queryset对象, 判断author实例是否在queryset对象中, 如果中则默认选中-->
                    {% if author in book.author.all %}
                        <option selected value="{{ author.pk }}">{{ author.name }}</option>
                    {% else %}
                        <option value="{{ author.pk }}">{{ author.name }}</option>
                    {% endif %}
                {% endfor %}
            </select>
        </div>
        <br>

        <p>
            <input type="submit" value="提交" class="btn btn-primary form-control">
        </p>
    </form>

{% endblock %}
handlebars 复制代码
启动项目, 访问: 127.0.0.1:8000/book_list , 选中一书籍并点击编辑按钮.
handlebars 复制代码
点击编辑后访问: 127.0.0.1:8000/book_set/4 , 书籍修改页面展示如下:

5.4 修改数据

handlebars 复制代码
1. 修改视图函数, 从POST请求提交的表单中获取书籍信息.
2. 需要为两张表更新数据(表单中提交的数据都是字符串, 在写入数据库的时候会自动转换类型.)
   首先, 获取书籍对象, 使用update更新字段: title, price, publish_id的值. 
   最后, 使用书籍对象的author外键字段获取到书籍作者关联表, 再使用set()方法修改多对多关联.
   set()方法允许传递一个可迭代对象(比如列表, 元组等), 它会遍历这个可迭代对象, 并逐个添加其中的元素.
3. 修改成功后重定向到书籍列表页面.
python 复制代码
# library/views.py

# 书籍修改, 需要接收内置转换器传递的参数
def book_set(request, book_id):
    # 处理GET请求:
    if request.method == 'GET':
        # 获取所有的出版社实例
        publishing = Publish.objects.all()
        # 获取所有的作者实例
        authors = Author.objects.all()
        # 获取书籍实例
        book = Book.objects.filter(id=book_id).first()
        return render(request, 'book_set.html', locals())

    # 处理POST请求:
    if request.method == 'POST':
        # 获取书籍信息
        title = request.POST.get('title')
        price = request.POST.get('price')
        publish_id = request.POST.get('publish_id')
        # 作者id有多个, 通过getlist方法获取, 值是一个列表
        author_id = request.POST.getlist('author_id')

        # 数据校验跳过
        # print(title, price, publish_id, author_id)

        # 获取书籍实例, update是queryset对象方法
        query_set = Book.objects.filter(id=book_id)
        # 更新书籍实例
        query_set.update(title=title, price=price, publish_id=publish_id)
        # 通过first()方法获取书籍实例, 在通过author外键更新多对多关联
        query_set.first().author.set(author_id)
        # 重定向到书籍列表页面
        return redirect('/book_list')
handlebars 复制代码
启动项目, 访问两次: 127.0.0.1:8000/book_list/4 , 获取书籍id为4的修改页面, 一个做参考一个做修改并提交请求.
handlebars 复制代码
修改成功之后, 跳转到书籍列表页面中, 查看修改后的书籍信息.
handlebars 复制代码
访问注点意:
修改页面的路由是: 'book_set/<int:book_id>' .
视图函数的定义为: def book_set(request, book_id) .
点击编辑的时候, 访问book_set页面需要携带一个id值为路径, 视图函数也始终接收被转换器捕获的id值路径.
例如访问: 127.0.0.1:8000/book_list/4 , 返回一个书籍修改页面.
当修改信息完成后, 表单向当前的url地址(127.0.0.1:8000/book_list/4)提交POST请求.
上诉过程中, 请求地址中一直确保着book_id, 否则路由或book_set视图函数会出错.

6. 删除书籍

handlebars 复制代码
删除书籍信息的思路:
* 1. 给删除按钮绑定请求地址, 在书籍列表中点击删除时, 提交一个get请求, 并携带书籍的id.
* 2. 路由中使用转换器获取id值并传递给视图函数.
* 3. 后端处理请求, 并通过书籍id删除对应书籍实例数据.
* 4. 删除数据后跳转到图书列表中.

6.1 路由层

handlebars 复制代码
在子路由中添加一个路由, 当用户访问: 127.0.0.1:8000/book_delete/id时展示书籍修改页面.
请求地址携带了书籍id, 可以通过内置的转换器来捕获获取这个值.
python 复制代码
# library/urls.py

from django.urls import path
from library.views import home, book_list, book_add, book_set, book_delete

urlpatterns = [
    # 主页面
    path('', home, name='library'),
    # 书籍列表
    path('book_list', book_list, name='book_list'),
    # 书籍添加
    path('book_add', book_add, name='book_add'),
    # 书籍修改
    path('book_set/<int:book_id>', book_set, name='book_set'),
    # 书籍删除
    path('book_delete/<int:book_id>', book_delete, name='book_delete'),
]

6.2 视图层

handlebars 复制代码
在视图直接通过书籍id删除实例, 然后重定向到书籍列表页面.
python 复制代码
# library/views.py

# 书籍删除
def book_delete(request, book_id):
    # 通过书籍主键删除实例
    Book.objects.filter(id=book_id).delete()
    # 重定向到书籍列表页面
    return redirect('/book_list')

6.3 模型层

handlebars 复制代码
在book_list.html页面中为删除按钮绑定跳转地址, 提交的get请求携带书籍实例的id.
html 复制代码
<!-- templates/book_list.html -->

<a href="{% url 'library:book_delete'  book.pk %}" class="btn btn-danger btn-sm">删除</a>
handlebars 复制代码
启动项目, 访问: 127.0.0.1:8000/book_list, 选中一个书籍实例并点击删除.
handlebars 复制代码
删除成功之后, 跳转到书籍列表页面中, 查看删除后的书籍信息.

7. 页面背景

handlebars 复制代码
在模板页面中写写一个js替换body的背景颜色.
html 复制代码
<!-- 背景色修改 -->
<script>
    $(document).ready(function () {
        // 监听下拉框中链接的点击事件
        $('.dropdown-menu a').click(function (e) {
            e.preventDefault(); // 阻止链接的默认跳转行为
            // 移除body上已有的背景类(如果有的话)
            var $body = $('body');
            $body.removeClass('bg-success bg-danger bg-warning');
            // 根据点击的链接文本添加相应的背景类
            var text = $(this).text();
            switch (text) {
                case '浅绿色':
                    $body.addClass('bg-success');
                    break;
                case '浅粉色':
                    $body.addClass('bg-danger');
                    break;
                case '浅黄色':
                    $body.addClass('bg-warning');
                    break;
            }
        });
    });
</script>
handlebars 复制代码
启动项目, 访问任意一个页面, 例: 127.0.0.1:8000/book_list , 在导航栏中设置页面的背景颜色, 刷新页面则恢复默认的白色.
handlebars 复制代码
出版社列表与作者表有兴趣自己去完善.
相关推荐
Eastsea.Chen2 分钟前
MTK Android12 user版本MtkLogger
android·framework
幽兰的天空7 分钟前
Python 中的模式匹配:深入了解 match 语句
开发语言·python
网易独家音乐人Mike Zhou3 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
安静读书4 小时前
Python解析视频FPS(帧率)、分辨率信息
python·opencv·音视频
小二·5 小时前
java基础面试题笔记(基础篇)
java·笔记·python
小喵要摸鱼7 小时前
Python 神经网络项目常用语法
python
长亭外的少年7 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
一念之坤8 小时前
零基础学Python之数据结构 -- 01篇
数据结构·python
wxl7812278 小时前
如何使用本地大模型做数据分析
python·数据挖掘·数据分析·代码解释器
NoneCoder8 小时前
Python入门(12)--数据处理
开发语言·python