Django异步请求和后台管理实战

项目概述

  1. 项目实现Ajax异步请求局部刷新
  2. 使用XAdmin后台模板
  3. 提供图片上传接口
  4. 在明细页应用了富文本编辑器
  5. 在加载图书信息的时候使用LazyLoad(图片懒加载)
python 复制代码
# 环境
asgiref==3.7.2
crispy-bootstrap3==2024.1
defusedxml==0.7.1
diff-match-patch==20230430
Django==3.2.25
django-ckeditor==5.9.0
django-crispy-forms==2.0
django-formtools==2.1
django-import-export==3.2.0
django-js-asset==2.0.0
django-reversion==5.0.12
django-stdimage==3.2.0
et-xmlfile==1.1.0
future==0.15.2
httplib2==0.9.2
MarkupPy==1.14
odfpy==1.4.1
openpyxl==3.1.3
Pillow==6.2.0
progressbar2==4.2.0
PyMySQL==1.1.1
python-utils==3.5.2
pytz==2024.1
PyYAML==6.0.1
six==1.10.0
sqlparse==0.4.4
tablib==3.4.0
typing_extensions==4.7.1
xlrd==2.0.1
xlwt==1.3.0

项目初始化并添加xadmin

  • 创建项目 python manage.py startapp bookshop,并在配置文件中完成注册
  • 根目录新建apps文件夹存放刚创建出来的项目
  • 根目录新建extra_apps文件夹存放xadmin源码
python 复制代码
# 添加修改settings.py 

import os,sys

sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
sys.path.insert(0, os.path.join(BASE_DIR, 'extra_apps'))


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'bookshop',
    'xadmin',
    'crispy_forms',
    'crispy_bootstrap3',
]
  • 配置文件中设置项目根目录并将文件夹设置为Sources Root格式
  • 新建数据库连接并完成连接配置
python 复制代码
# settings.py 添加mysql连接信息

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django_bookshops',
        'USER': '用户名',
        'PASSWORD': '密码',
        'HOST': 'ip',
        'PORT': '端口'
    }
}

# 根目录下与settings.py同文件夹下__init__.py 添加如下信息
import pymysql
pymysql.install_as_MySQLdb()
  • 前端文件添加

apps文件夹下新建Python Package文件命名static,在static文件夹下新建css、images、js文件夹

python 复制代码
# settings.py 下添加访问静态文件路径

STATIC_URL = '/static/'

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

将准备好的前端文件放在指定文件夹中,目录结构如下

python 复制代码
# 修改 HTML 文件
# 顶部添加
{% load static %}
# 引入静态文件
<link href="{%  static 'css/book.css' %}" rel="stylesheet">
  • url路由配置

apps文件夹下的bookshop项目中新建urls.py 文件,并在根目录下与settings.py文件同目录下的urls.py文件中添加引入外部 url配置

python 复制代码
# settings.py 同目录下 urls.py配置

from django.contrib import admin
from django.urls import path
# 调用其他app下定义的 url
from django.urls import include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('home/', include('bookshop.urls'))
]


# ==============apps目录下urls.py================

from django.urls import path
from bookshop import views as bookshop_views

urlpatterns = [
    # =========前端 url请求:返回页面============
    path('', bookshop_views.book, name='book'),
]
  • 准备数据库models类并插入数据
python 复制代码
# models.py 
from django.db import models
# 引入stdimage的模块
from stdimage.models import StdImageField  # 字段类型
from stdimage.utils import UploadToUUID   # 生成文件名
from ckeditor_uploader.fields import RichTextUploadingField


# Create your models here.

class BookType(models.Model):  # 图书类别 -- 计算机,法律,外语,。。。。
    name = models.CharField(max_length=100, null=False, verbose_name='名称')  # 类别编码

    class Meta:
        verbose_name = "图书类别"
        verbose_name_plural = "图书类别"

    def __str__(self):
        return "%s" % self.name


class Press(models.Model):  # 出版社
    id = models.CharField(primary_key=True, max_length=100, null=False, verbose_name='编号')  # 编号
    name = models.CharField(max_length=100, null=False, verbose_name='名称')  # 名称
    city = models.CharField(max_length=100,  null=False, verbose_name='城市')  # 城市
    tel = models.CharField(max_length=100, null=False, verbose_name='电话')  # 电话
    email = models.CharField(max_length=100,  null=False, verbose_name='邮箱')  # 邮箱
    address = models.CharField(max_length=100,  null=False, verbose_name='地址')  # 地址

    class Meta:
        verbose_name = "出版社"
        verbose_name_plural = "出版社"

    def __str__(self):
        return ("出版社编号:%s\t出版社名称:%s\n" % (self.id,self.name))
        # return "%s" % self.name


class Author(models.Model):  # 作者
    gender_choices = (("男", '男'), ('女', '女'))
    id = models.CharField(primary_key=True, max_length=100,  null=False, verbose_name='编号')  # 编号
    name = models.CharField(max_length=100,  null=False, verbose_name='姓名')  # 姓名
    gender = models.CharField(max_length=100, choices=gender_choices, verbose_name='性别')    # 性别
    city = models.CharField(max_length=100, null=False, verbose_name='城市')  # 城市
    tel = models.CharField(max_length=100,  null=False, verbose_name='电话')  # 电话
    email = models.CharField(max_length=100,  null=False, verbose_name='邮箱')  # 邮箱
    address = models.CharField(max_length=100,  null=False, verbose_name='地址')  # 地址

    class Meta:
        verbose_name = "作者"
        verbose_name_plural = "作者"

    def __str__(self):
        return "%s\n" % self.name


class Book(models.Model):   # 图书
    id = models.CharField(primary_key=True, max_length=100, null=False, verbose_name='编号')  # 编号,主键
    name = models.CharField(max_length=100,  null=False, verbose_name='名称')  # 名称
    barcode = models.CharField(max_length=100,  null=False, verbose_name='条形码')  # 条形码
    type = models.ForeignKey("BookType", on_delete=models.CASCADE, verbose_name='类别')  # 类别
    press = models.ForeignKey("Press", on_delete=models.CASCADE, verbose_name='出版社')  # 出版社
    author = models.ForeignKey("Author", on_delete=models.CASCADE, verbose_name='作者')  # 作者
    price = models.FloatField(null=False, verbose_name='单价')  # 价格
    difficulty = models.CharField(max_length=100,null=False, verbose_name='难易程度')  # 难易程度: 0--入门  1--中级  2--高级
    publish_date = models.DateField(null=False, verbose_name='发行时间')  # 发行时间
    storage_in_num = models.IntegerField(null=False, verbose_name='入库量')  # 入库量
    storage_in_date = models.DateField(null=False, verbose_name='入库时间') # 入库时间
    inventory_num = models.IntegerField(null=False, verbose_name='库存量')  # 库存量
    class Meta:
        verbose_name = "图书"
        verbose_name_plural = "图书"

    def __str__(self):
        return "%s" % self.name
python 复制代码
# 同步数据库信息
python manage.py makemigrations
python manage.py migrate

Ajax 异步请求

Ajax 即"Asynchronous JavaScript And XML"(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。

AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容

  • 准备图书类别的JSON数据
python 复制代码
# ************定义views视图********
from django.shortcuts import render
from django.http import HttpResponse,JsonResponse
from apps.bookshop.models import *

# Create your views here.
# =============前端代码:返回页面===========
def book(request):
    return render(request,'book.html')

# =============后端代码:从数据库中提取数据封装成json返回给前端===========

# 获取所有的图书类别
def get_booktype(request):
    obj_booktypes = BookType.objects.all().values()
    # Queryset无法直接转换成 Json,只有python基础的数据类型才可以,最外层的集合转换成列表
    booktypes = list(obj_booktypes)
    # 插入全部选项
    booktypes.insert(0,{"id":0,"name":"全部"})
    # 返回前端 json格式
    return JsonResponse({'type':booktypes})

# **********定义url路由************

from django.urls import path
from bookshop import views as bookshop_views


urlpatterns = [
    # =========前端 url请求:返回页面============
    path('', bookshop_views.book, name='book'),

    # =========后端 url请求:返回json数据========
    path('booktype/get/', bookshop_views.get_booktype, name='booktype'),

]
  • 将后端返回的数据展示在页面上
html 复制代码
# book.html
{% load static %}
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>图书页面</title>
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <!--index.css-->
    <link href="{%  static 'css/book.css' %}" rel="stylesheet">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <!-- jQuery中的Transtion  -->
    <script src="https://cdn.bootcss.com/jquery.transit/0.9.12/jquery.transit.min.js"></script>
    <!--引入LazyLoad-->
    <script src="{% static 'js/jquery.lazyload.min.js' %}"></script>
</head>

<body>
    <div class="container-fiuled header">
        <div class="container">
            <img src="{% static 'images/book_logo.png' %}">
        </div>
    </div>

    <div class="container typefilter">
        <div class="row">
            <div class="col-lg-1">类别:</div>
            <div class="col-lg-11 booktypes">
                <!-- Ajax动态加载 -->
            </div>
        </div>
    </div>

    <div class="container otherfilter">
        <form class="form-inline">
            <div class="col-lg-2 col-lg-offset-1">
                <div class="form-group-sm">
                    <label>价格: &nbsp;&nbsp;</label>
                    <select class="form-control price">
                        <option value="0" selected>所有</option>
                        <option value="1">0-20元</option>
                        <option value="2">20-30元</option>
                        <option value="3">30-40元</option>
                        <option value="4">40-50元</option>
                        <option value="5">50-60元</option>
                        <option value="6">60-70元</option>
                        <option value="7">70元以上</option>
                    </select>
                </div>
            </div>
            <div class="col-lg-2">
                <div class="form-group-sm">
                    <label>难易程度: &nbsp;&nbsp;</label>
                    <select class="form-control difficulty">
                        <option value="0" selected>所有</option>
                        <option value="1">入门</option>
                        <option value="2">中级</option>
                        <option value="3">高级</option>
                    </select>
                </div>
            </div>
            <div class="col-lg-2">
                <div class="form-group-sm">
                    <span style="font-weight: bold">价格排序: </span>
                    <span class="glyphicon glyphicon-sort-by-attributes asc" style="margin-left: 10px;"></span>
                    <span class="glyphicon glyphicon-sort-by-attributes-alt desc" style="margin-left: 10px;"></span>
                </div>
            </div>
            <div class="col-lg-5" style="text-align: right">
                <div class="form-group-sm">
                    <input type="text" class="form-control querystr" placeholder="输入图书关键字" style="width:360px;">
                    <button class="btn btn-primary btn-sm btnquery" type="button">查找</button>
                </div>
            </div>
        </form>

    </div>

    <div class="container books" style="width:1220px;height: 400px;margin:20px auto;">
          <!-- Ajax动态加载 -->
    </div>
</body>
</html>
javascript 复制代码
#  JavaScript代码
<script>
    // 入口函数
    $(document).ready(function (){
        // 代码自动执行(页面加载时自动执行)
        $.ajax({
            url: 'booktype/get/',
            method: 'GET',
            success: function (res){
                console.log(res)
                // 调用函数
                addTypes(res.type)
            }
        });

});

    // DOM 操作
    // 将图书类别加载到指定区域
    function addTypes(types){
        // 清空指定区域内容
        $('.typefilter .booktypes').html("");
        // 循环加载图书类别
        types.forEach(function (element,index,array){
            // 新建一个 span
            let oneType = $('<span>').html(element.name).attr('value',element.id).css('font-size',"14px").css('font-weight','normal').css('margin',"10px");;
            //添加一个Hover操作
            oneType.on('mouseenter',()=>{
                oneType.css('font-weight','bold');
            }).on('mouseleave',()=>{
                 oneType.css('font-weight','normal');
            });
            // 判断是不是全部
            if (index===0){
                oneType.css({'color':'red'});
            }
            // 添加到页面类别中
            oneType.appendTo($('.typefilter .booktypes'));
        });
    }       

</script>
  • 准备图书列表数据
python 复制代码
# ************定义views视图********
from django.shortcuts import render
from django.http import HttpResponse,JsonResponse
from apps.bookshop.models import *

# Create your views here.
# =============前端代码:返回页面===========
def book(request):
    return render(request,'book.html')

# =============后端代码:从数据库中提取数据封装成json返回给前端===========

# 获取所有的图书类别
def get_booktype(request):
    obj_booktypes = BookType.objects.all().values()
    # Queryset无法直接转换成 Json,只有python基础的数据类型才可以,最外层的集合转换成列表
    booktypes = list(obj_booktypes)
    # 插入全部选项
    booktypes.insert(0,{"id":0,"name":"全部"})
    # 返回前端 json格式
    return JsonResponse({'type':booktypes})

# 获取所有的图书列表
def get_books(request):
    obj_books = Book.objects.all().values()
    # Queryset无法直接转换成 Json,只有python基础的数据类型才可以,最外层的集合转换成列表
    books = list(obj_books)
    # 遍历图书 获取类别名称
    for index,item in enumerate(books):
        # 根据类别编号获取图书类别的对象
        obj_type = BookType.objects.get(id=item['type_id'])
        # 附加typename到当前成员
        books[index]['typename'] = obj_type.name

    # 返回前端 json格式
    # return JsonResponse({'book': books})
    # 'ensure_ascii': False 返回的数据显示中文
    return JsonResponse({'book': books},json_dumps_params={'ensure_ascii': False})

# **********定义url路由************

from django.urls import path
from bookshop import views as bookshop_views


urlpatterns = [
    # =========前端 url请求:返回页面============
    path('', bookshop_views.book, name='book'),

    # =========后端 url请求:返回json数据========
    path('booktype/get/', bookshop_views.get_booktype, name='booktype'),
    path('books/get/', bookshop_views.get_books, name='books'),

]
  • 将后端返回的图书展示在页面上
javascript 复制代码
#  JavaScript代码
<script>
    // 入口函数
    $(document).ready(function (){
        // 代码自动执行(页面加载时自动执行)
        $.ajax({
            url: 'booktype/get/',
            method: 'GET',
            success: function (res){
                console.log(res)
                // 调用函数
                addTypes(res.type)
            }
        });

   // *******自动加载图书列表(页面加载时自动执行)*******
        $.ajax({
            url: 'books/get/',
            method: 'GET',
            success: function (res){
                console.log(res)
                // 调用函数
                addBooks(res.book)
            }
        });

});

    // DOM 操作
    // 将图书类别加载到指定区域
    function addTypes(types){
        // 清空指定区域内容
        $('.typefilter .booktypes').html("");
        // 循环加载图书类别
        types.forEach(function (element,index,array){
            // 新建一个 span
            let oneType = $('<span>').html(element.name).attr('value',element.id).css('font-size',"14px").css('font-weight','normal').css('margin',"10px");;
            //添加一个Hover操作
            oneType.on('mouseenter',()=>{
                oneType.css('font-weight','bold');
            }).on('mouseleave',()=>{
                 oneType.css('font-weight','normal');
            });
            // 判断是不是全部
            if (index===0){
                oneType.css({'color':'red'});
            }
            // 添加到页面类别中
            oneType.appendTo($('.typefilter .booktypes'));
        });
    } 

    //**********把图书列表加载到制定区域*********
    function addBooks(books) {
        //清空区域内容
        $('.books').html("");
        //遍历
        books.forEach(function (element, index, array) {
            //新建一个div-最外层的容器
            let outer = $('<div>').attr( "class", "col-lg-3");
            //新建一个div,设置类为thumbnail
            let thumbnail = $('<div>').attr( "class", "thumbnail").css('overflow','hidden').appendTo(outer);
            //新建一个img标签,放置图片
            let img = $('<img>')
                .attr('scr',baseURL+element.image) //******
                .appendTo(thumbnail)
            //设置图的hover过渡特效 --- 放大1.4倍
            img.on('mouseenter',function () {
               img.transition({ scale: 1.4 }, 300);
            }).on('mouseleave',function () {
               img.transition({ scale: 1 }, 300);
            });
            //新建一个div--放置价格
            let price = $('<div>').html("价格:" + element.price.toFixed(2) + "元").css('text-align', 'left').css('color','red').css('margin-top','20px').css('font-size','16px')
                .css('line-height', '30px').appendTo(thumbnail);
             {#$("<span>").html("类别:" + element.typename).css('text-align', 'left').css('font-size','16px')#}
             {#   .css('line-height', '30px').appendTo(thumbnail);#}


            //新建一个div--放置图书名称
            let bookname =$('<div>').html(element.name).css('text-align', 'left').css('color','navy').css('font-size','16px').appendTo(thumbnail);
            //设置图书名称的 Hover
            bookname.on('mouseenter',function () {
                bookname.css('font-weight','bold');
            }).on('mouseleave',function () {
                bookname.css('font-weight','normal');
            });
            //把当前容器放置在页面上
            outer.appendTo($('.books'))
        });
    
    }      

</script>
  • 效果如下

xadmin 部署及配置

  • 引入源码到项目中
javascript 复制代码
# 安装xadmin的依赖包进入源码的目录
pip install -r requirements.txt
  • 安装核心的包
javascript 复制代码
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'bookshop',
    'xadmin',
    'crispy_forms',
    'crispy_bootstrap3',
]
  • 修改URL
python 复制代码
from django.contrib import admin
from django.urls import path
# 调用其他app下定义的 url
from django.urls import include
# 引入 xadmin
import xadmin


urlpatterns = [
    # path('admin/', admin.site.urls),
    path('xadmin/', xadmin.site.urls),
    path('home/', include('bookshop.urls'))
]
  • 同步数据库
python 复制代码
python manage.py makemigrations
python manage.py migrate
  • 设置管理员账号密码
python 复制代码
python manage.py createsuperuser
  • 报错问题解决
python 复制代码
解决报错 render() missing 1 required positional argument: 'form_style' 
1.utils.py 29行render_field()中添加form_style=None,
2.删除 detail.py 34、35行form_style

解决报错 render() missing 1 required positional argument: context
1.utils.py  66行context变为context=context

解决报错 OperationalError: (1271, "Illegal mix of collations for operation 'like'")
1.找到django包下面的\site-packages\django\db\backends\mysql\base.py文件,编辑
将icontains的值 'icontains': 'LIKE %s',改为'icontains': 'LIKE BINARY %s',

解决报错 django.template.exceptions.TemplateDoesNotExist: bootstrap3/errors.html
1.安装 pip install crispy-bootstrap3 -i https://mirrors.aliyun.com/pypi/simple/
2.注册 'crispy_bootstrap3'
  • Models类注册到xadmin
python 复制代码
# 1.后台界面中文显示,更改 settings.py

# 语言
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-Hans'

# 时区
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'

# 2.bookshop目录下新建 adminx.py 文件

from apps.bookshop import models
import xadmin


# 定义相应对象的xadmin类
class PressAdmin(object):
    # 配置表格显示字段
    list_display = ['id','name','city','tel','email','address']
    # 分页
    list_per_page = 10

# 将类注册到xadmin
xadmin.site.register(models.Press,PressAdmin)

# 定义相应对象的xadmin类
class BookTypeAdmin(object):
    # 配置表格显示字段
    list_display = ['name']
    # 分页
    list_per_page = 10

# 将类注册到xadmin
xadmin.site.register(models.BookType,BookTypeAdmin)


# 定义相应对象的xadmin类
class AuthorAdmin(object):
    # 配置表格显示字段
    list_display = ['id','name','city','gender','tel','email','address']
    # 分页
    list_per_page = 10

# 将类注册到xadmin
xadmin.site.register(models.Author,AuthorAdmin)

# 定义相应对象的xadmin类
class BookAdmin(object):
    # 配置表格显示字段
    list_display = ['id','name','barcode','type','press','author','price','difficulty','publish_date','storage_in_num','storage_in_date','inventory_num']
    # 分页
    list_per_page = 10
    # 排序
    ordering = ['id']
    # 查看详情
    show_detail_fields = ['id']
    # 图标  https://v3.bootcss.com/components/
    # https://fontawesome.com/v4/icons/
    model_icon = "fa fa-signal"
    # 搜索字段
    search_fields = ['id','name','barcode']
    # 筛选字段
    list_filter = ['id','name','barcode','type','press','author','price']
    # 配置自动刷新的时间
    refresh_times = (3,5)


# 将类注册到xadmin
xadmin.site.register(models.Book,BookAdmin)
  • xadmin中全局配置
python 复制代码
from apps.bookshop import models
import xadmin

# 定义相应对象的xadmin类
class PressAdmin(object):
    # 配置表格显示字段
    list_display = ['id','name','city','tel','email','address']
    # 分页
    list_per_page = 10

# 将类注册到xadmin
xadmin.site.register(models.Press,PressAdmin)

# 定义相应对象的xadmin类
class BookTypeAdmin(object):
    # 配置表格显示字段
    list_display = ['name']
    # 分页
    list_per_page = 10

# 将类注册到xadmin
xadmin.site.register(models.BookType,BookTypeAdmin)


# 定义相应对象的xadmin类
class AuthorAdmin(object):
    # 配置表格显示字段
    list_display = ['id','name','city','gender','tel','email','address']
    # 分页
    list_per_page = 10

# 将类注册到xadmin
xadmin.site.register(models.Author,AuthorAdmin)

# 定义相应对象的xadmin类
class BookAdmin(object):
    # 配置表格显示字段
    list_display = ['id','name','barcode','type','press','author','price','difficulty','publish_date','storage_in_num','storage_in_date','inventory_num']
    # 分页
    list_per_page = 10
    # 排序
    ordering = ['id']
    # 查看详情
    show_detail_fields = ['id']
    # 图标  https://v3.bootcss.com/components/
    # https://fontawesome.com/v4/icons/
    model_icon = "fa fa-signal"
    # 搜索字段
    search_fields = ['id','name','barcode']
    # 筛选字段
    list_filter = ['id','name','barcode','type','press','author','price']
    # 配置自动刷新的时间
    refresh_times = (3,5)


# 将类注册到xadmin
xadmin.site.register(models.Book,BookAdmin)


"""========修改全局基础配置=========="""
from xadmin import views
class BaseSetting(object):
    # 是否启用主题
    enable_themes = True
    use_bootswatch = True

    def has_add_permission(self,request):
        return False

class GlobalSetting(object):
    site_title = '后台管理系统'
    site_footer = '版权所有 @lv'
    # 侧边栏收起
    menu_style = 'accordion'

# *****将全局配置配置到xadmin中*****
xadmin.site.register(views.BaseAdminView,BaseSetting)
xadmin.site.register(views.CommAdminView,GlobalSetting)

图片上传功能实现

  • 准备存储图片的文件夹media
  • 设置MEDIA ROOT 和 MEDIA URL
python 复制代码
# MEDIA ROOT:本地处理的路径,MEDIA URL: 互联网访问的路径
# settings.py 
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
MEDIA_URL = ''
  • 配置URL
python 复制代码
from django.urls import path
# 调用其他app下定义的 url
from django.urls import include
# 引入 xadmin
import xadmin
from django.conf import settings
# 匹配静态文件
from django.conf.urls.static import static

urlpatterns = [
    # path('admin/', admin.site.urls),
    path('xadmin/', xadmin.site.urls),
    path('home/', include('bookshop.urls'))
]

# 允许所有的图片被访问
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
  • 安装模块
python 复制代码
pip install django-stdimage==3.2.0 -i https://mirrors.aliyun.com/pypi/simple/
  • 配置models.py模版文件
python 复制代码
# 引入stdimage的模块
from stdimage.models import StdImageField
# 生成唯一文件名
from stdimage.utils import UploadToUUID
# 添加图书字段
# path="" 默认存储到media下 variations缩略图配置
image = StdImageField(max_length=200, upload_to=UploadToUUID(path=""),
verbose_name='图片', variations={'thumbnail': {'width': 70, 'height': 100}})
  • 更新连接表
python 复制代码
python manage.py makemigrations
  • 实现上传的图片在前台显示
javascript 复制代码
#  JavaScript代码
<script>
    // 入口函数
    $(document).ready(function (){
        // 代码自动执行(页面加载时自动执行)
        $.ajax({
            url: 'booktype/get/',
            method: 'GET',
            success: function (res){
                console.log(res)
                // 调用函数
                addTypes(res.type)
            }
        });

   // *******自动加载图书列表(页面加载时自动执行)*******
        $.ajax({
            url: 'books/get/',
            method: 'GET',
            success: function (res){
                console.log(res)
                // 调用函数
                addBooks(res.book)
            }
        });

});

    // DOM 操作
    // 将图书类别加载到指定区域
    function addTypes(types){
        // 清空指定区域内容
        $('.typefilter .booktypes').html("");
        // 循环加载图书类别
        types.forEach(function (element,index,array){
            // 新建一个 span
            let oneType = $('<span>').html(element.name).attr('value',element.id).css('font-size',"14px").css('font-weight','normal').css('margin',"10px");;
            //添加一个Hover操作
            oneType.on('mouseenter',()=>{
                oneType.css('font-weight','bold');
            }).on('mouseleave',()=>{
                 oneType.css('font-weight','normal');
            });
            // 判断是不是全部
            if (index===0){
                oneType.css({'color':'red'});
            }
            // 添加到页面类别中
            oneType.appendTo($('.typefilter .booktypes'));
        });
    } 

    //**********把图书列表加载到制定区域*********
    function addBooks(books) {
        //清空区域内容
        $('.books').html("");
        //*********定义一个基础的URL********
        let baseURL = "http://127.0.0.1:8000/";
        //遍历
        books.forEach(function (element, index, array) {
            //新建一个div-最外层的容器
            let outer = $('<div>').attr( "class", "col-lg-3");
            //新建一个div,设置类为thumbnail
            let thumbnail = $('<div>').attr( "class", "thumbnail").css('overflow','hidden').appendTo(outer);
            //新建一个img标签,放置图片
            let img = $('<img>')
                .attr('scr',baseURL+element.image) //******
                .appendTo(thumbnail)
            //设置图的hover过渡特效 --- 放大1.4倍
            img.on('mouseenter',function () {
               img.transition({ scale: 1.4 }, 300);
            }).on('mouseleave',function () {
               img.transition({ scale: 1 }, 300);
            });
            //新建一个div--放置价格
            let price = $('<div>').html("价格:" + element.price.toFixed(2) + "元").css('text-align', 'left').css('color','red').css('margin-top','20px').css('font-size','16px')
                .css('line-height', '30px').appendTo(thumbnail);
             {#$("<span>").html("类别:" + element.typename).css('text-align', 'left').css('font-size','16px')#}
             {#   .css('line-height', '30px').appendTo(thumbnail);#}


            //新建一个div--放置图书名称
            let bookname =$('<div>').html(element.name).css('text-align', 'left').css('color','navy').css('font-size','16px').appendTo(thumbnail);
            //设置图书名称的 Hover
            bookname.on('mouseenter',function () {
                bookname.css('font-weight','bold');
            }).on('mouseleave',function () {
                bookname.css('font-weight','normal');
            });
            //把当前容器放置在页面上
            outer.appendTo($('.books'))
        });
    
    }      

</script>

图书筛选功能实现

  • 图书类别筛选
javascript 复制代码
#  前端JavaScript代码
<script>
    // *******
    queryItems = {
        type: 0,  //类别
        pricemin: 0, //价格区间的最小值
        pricemax: 10000,//价格区间的最大值
        difficulty: "0", //难易程度
        order: 0,  //排序: 0-不排序  1-升序  2-降序
        inputstr: "", //输入的查询字符串
    };


    // 入口函数
    $(document).ready(function (){
        // 代码自动执行(页面加载时自动执行)
        $.ajax({
            url: 'booktype/get/',
            method: 'GET',
            success: function (res){
                console.log(res)
                // 调用函数展示筛选后的图书
                addTypes(res.type)
            }
        });

   // *******自动加载图书列表(页面加载时自动执行)*******
        $.ajax({
            url: 'books/get/',
            method: 'GET',
            success: function (res){
                console.log(res)
                // 调用函数
                addBooks(res.book)
            }
        });

});

    // DOM 操作

    // 提交图书查询的ajax请求
    function queryBooks(){
        // 进行ajax请求
        $.ajax({
            url: "books/query/",
            method: "post",
            data: queryItems,
            success:function (res){
                console.log(res);
                // 展示筛选后的图书
                addBooks(res.book)
            }
        })
    }

    // 将图书类别加载到指定区域
    function addTypes(types){
        // 清空指定区域内容
        $('.typefilter .booktypes').html("");
        // 循环加载图书类别
        types.forEach(function (element,index,array){
            // 新建一个 span
            let oneType = $('<span>').html(element.name).attr('value',element.id).css('font-size',"14px").css('font-weight','normal').css('margin',"10px");;
            //添加一个Hover操作
            oneType.on('mouseenter',()=>{
                oneType.css('font-weight','bold');
            }).on('mouseleave',()=>{
                 oneType.css('font-weight','normal');
            });
            // 判断是不是全部
            if (index===0){
                oneType.css({'color':'red'});
            }

            // *****响应事件*****
            // 类别点击事件
            oneType.click(function (){
                //清楚所有的选择
                $(".booktypes span").css('color','#000');
                //把点击的选择
                $(this).css('color','red');
                // 把typeid传递给 queryitme.type
                queryItems.type = $(this).attr('value');
                {#console.log(queryItems);#}
                //提交到后台筛选
                queryBooks()

            // 添加到页面类别中
            oneType.appendTo($('.typefilter .booktypes'));
        });
    } 

    //**********把图书列表加载到制定区域*********
    function addBooks(books) {
        //清空区域内容
        $('.books').html("");
        //*********定义一个基础的URL********
        let baseURL = "http://127.0.0.1:8000/";
        //遍历
        books.forEach(function (element, index, array) {
            //新建一个div-最外层的容器
            let outer = $('<div>').attr( "class", "col-lg-3");
            //新建一个div,设置类为thumbnail
            let thumbnail = $('<div>').attr( "class", "thumbnail").css('overflow','hidden').appendTo(outer);
            //新建一个img标签,放置图片
            let img = $('<img>')
                .attr('scr',baseURL+element.image) //******
                .appendTo(thumbnail)
            //设置图的hover过渡特效 --- 放大1.4倍
            img.on('mouseenter',function () {
               img.transition({ scale: 1.4 }, 300);
            }).on('mouseleave',function () {
               img.transition({ scale: 1 }, 300);
            });
            //新建一个div--放置价格
            let price = $('<div>').html("价格:" + element.price.toFixed(2) + "元").css('text-align', 'left').css('color','red').css('margin-top','20px').css('font-size','16px')
                .css('line-height', '30px').appendTo(thumbnail);
             {#$("<span>").html("类别:" + element.typename).css('text-align', 'left').css('font-size','16px')#}
             {#   .css('line-height', '30px').appendTo(thumbnail);#}


            //新建一个div--放置图书名称
            let bookname =$('<div>').html(element.name).css('text-align', 'left').css('color','navy').css('font-size','16px').appendTo(thumbnail);
            //设置图书名称的 Hover
            bookname.on('mouseenter',function () {
                bookname.css('font-weight','bold');
            }).on('mouseleave',function () {
                bookname.css('font-weight','normal');
            });
            //把当前容器放置在页面上
            outer.appendTo($('.books'))
        });
    
    }      

</script>
python 复制代码
# 后端代码 view.py
from django.shortcuts import render
from django.http import HttpResponse,JsonResponse
from apps.bookshop.models import *

# 实现图书查询
def query_books(request):
    # 获取传递来的条件
    type = request.POST.get('type')
    if type == '0':
        obj_books = Book.objects.all().values()
    else:
        # 通过编号筛选出图书
        obj_books = Book.objects.filter(type_id=type).values()
    books = list(obj_books.values())
    # 遍历图书 获取类别名称
    for index, item in enumerate(books):
        # 根据类别编号获取图书类别的对象
        obj_type = BookType.objects.get(id=item['type_id'])
        # 附加typename到当前成员
        books[index]['typename'] = obj_type.name

    return JsonResponse({'book': books}, json_dumps_params={'ensure_ascii': False})


# 设置路由 urls.py
from django.urls import path
from bookshop import views as bookshop_views

urlpatterns = [
    # =========前端 url请求:返回页面============
    path('', bookshop_views.book, name='book'),

    # =========后端 url请求:返回json数据========
    path('booktype/get/', bookshop_views.get_booktype, name='booktype'),
    path('books/get/', bookshop_views.get_books, name='books'),
    path('books/query/', bookshop_views.query_books, name='books_query'),
]
  • 图书价格区间筛选
javascript 复制代码
#  前端JavaScript代码
<script>
    // *******
    PRICE_REGION_VALUE = [[0,10000],[0,20],[20,30],[30,40],[40,50],[50,60],[60,70],[70,10000]];
    DIFF_VALUE =['0', '入门', '中级', '高级'];

    queryItems = {
        type: 0,  //类别
        pricemin: 0, //价格区间的最小值
        pricemax: 10000,//价格区间的最大值
        difficulty: "0", //难易程度
        order: 0,  //排序: 0-不排序  1-升序  2-降序
        inputstr: "", //输入的查询字符串
    };


    // 入口函数
    $(document).ready(function (){
        // 代码自动执行(页面加载时自动执行)
        $.ajax({
            url: 'booktype/get/',
            method: 'GET',
            success: function (res){
                console.log(res)
                // 调用函数展示筛选后的图书
                addTypes(res.type)
            }
        });

   // *******自动加载图书列表(页面加载时自动执行)*******
        $.ajax({
            url: 'books/get/',
            method: 'GET',
            success: function (res){
                console.log(res)
                // 调用函数
                addBooks(res.book)
            }
        });

    // *******价格的下拉框改变事件*******
        $('.price').change(function (){
            let priceArr = PRICE_REGION_VALUE[parseInt($('.price').val())];
            // 范围值赋值给pricemin、pricemax
            queryItems.pricemin = priceArr[0];
            queryItems.pricemax = priceArr[1];
            // 传递到后端
            queryBooks()
        });

});

    // DOM 操作

    // 提交图书查询的ajax请求
    function queryBooks(){
        // 进行ajax请求
        $.ajax({
            url: "books/query/",
            method: "post",
            data: queryItems,
            success:function (res){
                console.log(res);
                // 展示筛选后的图书
                addBooks(res.book)
            }
        })
    }

    // 将图书类别加载到指定区域
    function addTypes(types){
        // 清空指定区域内容
        $('.typefilter .booktypes').html("");
        // 循环加载图书类别
        types.forEach(function (element,index,array){
            // 新建一个 span
            let oneType = $('<span>').html(element.name).attr('value',element.id).css('font-size',"14px").css('font-weight','normal').css('margin',"10px");;
            //添加一个Hover操作
            oneType.on('mouseenter',()=>{
                oneType.css('font-weight','bold');
            }).on('mouseleave',()=>{
                 oneType.css('font-weight','normal');
            });
            // 判断是不是全部
            if (index===0){
                oneType.css({'color':'red'});
            }

            // *****响应事件*****
            // 类别点击事件
            oneType.click(function (){
                //清楚所有的选择
                $(".booktypes span").css('color','#000');
                //把点击的选择
                $(this).css('color','red');
                // 把typeid传递给 queryitme.type
                queryItems.type = $(this).attr('value');
                {#console.log(queryItems);#}
                //提交到后台筛选
                queryBooks()

            // 添加到页面类别中
            oneType.appendTo($('.typefilter .booktypes'));
        });
    } 

    //**********把图书列表加载到制定区域*********
    function addBooks(books) {
        //清空区域内容
        $('.books').html("");
        //*********定义一个基础的URL********
        let baseURL = "http://127.0.0.1:8000/";
        //遍历
        books.forEach(function (element, index, array) {
            //新建一个div-最外层的容器
            let outer = $('<div>').attr( "class", "col-lg-3");
            //新建一个div,设置类为thumbnail
            let thumbnail = $('<div>').attr( "class", "thumbnail").css('overflow','hidden').appendTo(outer);
            //新建一个img标签,放置图片
            let img = $('<img>')
                .attr('scr',baseURL+element.image) //******
                .appendTo(thumbnail)
            //设置图的hover过渡特效 --- 放大1.4倍
            img.on('mouseenter',function () {
               img.transition({ scale: 1.4 }, 300);
            }).on('mouseleave',function () {
               img.transition({ scale: 1 }, 300);
            });
            //新建一个div--放置价格
            let price = $('<div>').html("价格:" + element.price.toFixed(2) + "元").css('text-align', 'left').css('color','red').css('margin-top','20px').css('font-size','16px')
                .css('line-height', '30px').appendTo(thumbnail);
             {#$("<span>").html("类别:" + element.typename).css('text-align', 'left').css('font-size','16px')#}
             {#   .css('line-height', '30px').appendTo(thumbnail);#}


            //新建一个div--放置图书名称
            let bookname =$('<div>').html(element.name).css('text-align', 'left').css('color','navy').css('font-size','16px').appendTo(thumbnail);
            //设置图书名称的 Hover
            bookname.on('mouseenter',function () {
                bookname.css('font-weight','bold');
            }).on('mouseleave',function () {
                bookname.css('font-weight','normal');
            });
            //把当前容器放置在页面上
            outer.appendTo($('.books'))
        });
    
    }      

</script>
python 复制代码
# 后端代码 view.py
from django.shortcuts import render
from django.http import HttpResponse,JsonResponse
from apps.bookshop.models import *

# 实现图书查询
def query_books(request):
    # 获取传递来的条件
    type = request.POST.get('type')
    # 价格
    price_min = request.POST.get('pricemin')
    price_max = request.POST.get('pricemax')
    if type == '0':
        obj_books = Book.objects.all().values()
    else:
        # 通过编号筛选出图书
        obj_books = Book.objects.filter(type_id=type).values()

    # 筛选价格
    obj_books = obj_books.filter(price__gte=price_min,price__lte=price_max)
    books = list(obj_books.values())
    # 遍历图书 获取类别名称
    for index, item in enumerate(books):
        # 根据类别编号获取图书类别的对象
        obj_type = BookType.objects.get(id=item['type_id'])
        # 附加typename到当前成员
        books[index]['typename'] = obj_type.name

    return JsonResponse({'book': books}, json_dumps_params={'ensure_ascii': False})


# 设置路由 urls.py
from django.urls import path
from bookshop import views as bookshop_views

urlpatterns = [
    # =========前端 url请求:返回页面============
    path('', bookshop_views.book, name='book'),

    # =========后端 url请求:返回json数据========
    path('booktype/get/', bookshop_views.get_booktype, name='booktype'),
    path('books/get/', bookshop_views.get_books, name='books'),
    path('books/query/', bookshop_views.query_books, name='books_query'),
]
  • 图书难易程度筛选
python 复制代码
# 前端 JavaScript代码
//  入口函数添加 难度筛选事件
 $('.difficulty').change(function (){
     queryItems.difficulty = DIFF_VALUE[parseInt($('.difficulty').val())]
     // 实现查询
     queryBooks();
});


# 后端图书查询函数添加
# 实现图书查询
def query_books(request):
    # 获取传递来的条件
    type = request.POST.get('type')
    # 价格
    price_min = request.POST.get('pricemin')
    price_max = request.POST.get('pricemax')
    # 难度
    difficulty = request.POST.get('difficulty')
    if type == '0':
        obj_books = Book.objects.all().values()
    else:
        # 通过编号筛选出图书
        obj_books = Book.objects.filter(type_id=type).values()

    # 筛选价格
    obj_books = obj_books.filter(price__gte=price_min,price__lte=price_max)
    # 筛选难度
    if '0' not in difficulty:
        obj_books = obj_books.filter(difficulty=difficulty)
    books = list(obj_books.values())
    # 遍历图书 获取类别名称
    for index, item in enumerate(books):
        # 根据类别编号获取图书类别的对象
        obj_type = BookType.objects.get(id=item['type_id'])
        # 附加typename到当前成员
        books[index]['typename'] = obj_type.name

    return JsonResponse({'book': books}, json_dumps_params={'ensure_ascii': False})
  • 图书关键字筛选
python 复制代码
# 前端 JavaScript代码
//  入口函数添加 难度筛选事件
// 指定关键字查询
$('.btnquery').click(function (){
    // 获取输入字符串
    queryItems.inputstr = $('.querystr').val();
    // 实现查询
    queryBooks();
});


# 后端图书查询函数添加
# 实现图书查询
def query_books(request):
    # 获取传递来的条件
    type = request.POST.get('type')
    # 价格
    price_min = request.POST.get('pricemin')
    price_max = request.POST.get('pricemax')
    # 难度
    difficulty = request.POST.get('difficulty')
    # 输入的值
    input_str = request.POST.get('inputstr')
    if type == '0':
        obj_books = Book.objects.all().values()
    else:
        # 通过编号筛选出图书
        obj_books = Book.objects.filter(type_id=type).values()

    # 筛选价格
    obj_books = obj_books.filter(price__gte=price_min,price__lte=price_max)
    # 筛选难度
    if '0' not in difficulty:
        obj_books = obj_books.filter(difficulty=difficulty)
    # 输入筛选
    obj_books = obj_books.filter(name__icontains=input_str)
    books = list(obj_books.values())
    # 遍历图书 获取类别名称
    for index, item in enumerate(books):
        # 根据类别编号获取图书类别的对象
        obj_type = BookType.objects.get(id=item['type_id'])
        # 附加typename到当前成员
        books[index]['typename'] = obj_type.name

    return JsonResponse({'book': books}, json_dumps_params={'ensure_ascii': False})
  • 图书价格升降序排序
python 复制代码
# 前端 JavaScript代码
//  入口函数添加 难度筛选事件
// 指定关键字查询
// 升序
        $('.asc').click(function (){
            // 把所有排序的图标不选择
            $('.asc').css('color','#fff');
            $('.desc').css('color','#fff');
            // 选中升序
            $('.asc').css('color','red');
            // 修改order
            queryItems.order = 1;
            // 提交给后端
            queryBooks();
        });
        // 降序
        $('.desc').click(function (){
            // 把所有排序的图标不选择
            $('.asc').css('color','#fff');
            $('.desc').css('color','#fff');
            // 选中升序
            $('.desc').css('color','red');
            // 修改order
            queryItems.order = 2;
            // 提交给后端
            queryBooks();
        });


# 后端图书查询函数添加
# 实现图书查询
def query_books(request):
    # 获取传递来的条件
    type = request.POST.get('type')
    # 价格
    price_min = request.POST.get('pricemin')
    price_max = request.POST.get('pricemax')
    # 难度
    difficulty = request.POST.get('difficulty')
    # 输入的值
    input_str = request.POST.get('inputstr')
    # 升降序
    order = request.POST.get('order')
    if type == '0':
        obj_books = Book.objects.all().values()
    else:
        # 通过编号筛选出图书
        obj_books = Book.objects.filter(type_id=type).values()

    # 筛选价格
    obj_books = obj_books.filter(price__gte=price_min,price__lte=price_max)
    # 筛选难度
    if '0' not in difficulty:
        obj_books = obj_books.filter(difficulty=difficulty)
    # 输入筛选
    obj_books = obj_books.filter(name__icontains=input_str)
    # 排序
    if order == '1':
        obj_books = obj_books.order_by('price').all()
    elif order == '2':
        obj_books = obj_books.order_by('-price').all()
    books = list(obj_books.values())
    # 遍历图书 获取类别名称
    for index, item in enumerate(books):
        # 根据类别编号获取图书类别的对象
        obj_type = BookType.objects.get(id=item['type_id'])
        # 附加typename到当前成员
        books[index]['typename'] = obj_type.name

    return JsonResponse({'book': books}, json_dumps_params={'ensure_ascii': False})

图片懒加载

为了提高用户体验度,在长页面的时候,优先加载用户屏幕区域的内容,下拉的内容暂时不加载,等滚动条拖动的时候逐步加载。

python 复制代码
<!--引入图片懒加载js包LazyLoad-->
<script src="{% static 'js/jquery.lazyload.min.js' %}"></script>
// 修改addBooks
function addBooks(books) {
        //清空区域内容
        $('.books').html("");
        //定义一个基础的URL
        let baseURL = "http://127.0.0.1:8000/";
        //遍历
        books.forEach(function (element, index, array) {
            //新建一个div-最外层的容器
            let outer = $('<div>').attr( "class", "col-lg-3");
            //新建一个div,设置类为thumbnail
            let thumbnail = $('<div>').attr( "class", "thumbnail").css('overflow','hidden').appendTo(outer);
            //******新建一个img标签,放置图片****
            let img = $('<img>')
                .attr('class','lazy')
                .attr('width','200px')
                .attr('height','200px')
                .attr('src', "{% static 'images/lazy.png' %}")
                .attr('data-original',baseURL+element.image)
                .appendTo(thumbnail)

            //img.lazyload();
            //设置图的hover过渡特效 --- 放大1.4倍
            img.on('mouseenter',function () {
               img.transition({ scale: 1.4 }, 300);
            }).on('mouseleave',function () {
               img.transition({ scale: 1 }, 300);
            });
            //新建一个div--放置价格
            let price = $('<div>').html("价格:" + element.price.toFixed(2) + "元").css('text-align', 'left').css('color','red').css('margin-top','20px').css('font-size','16px')
                .css('line-height', '30px').appendTo(thumbnail);
             {#$("<span>").html("类别:" + element.typename).css('text-align', 'left').css('font-size','16px')#}
             {#   .css('line-height', '30px').appendTo(thumbnail);#}


            //新建一个div--放置图书名称
            let bookname =$('<div>').html(element.name).css('text-align', 'left').css('color','navy').css('font-size','16px').appendTo(thumbnail);
            //设置图书名称的 Hover
            bookname.on('mouseenter',function () {
                bookname.css('font-weight','bold');
            }).on('mouseleave',function () {
                bookname.css('font-weight','normal');
            });
            //展示类别
            let typeAndDiff = $("<div>");
            $("<span>").html("类别:" + element.typename).css('text-align', 'left').css('font-size','16px')
                .css('line-height', '30px').appendTo(typeAndDiff);
            $("<span>").html("&nbsp&nbsp&nbsp&nbsp难易程度:" + element.difficulty).css('text-align', 'left').css('font-size','16px')
                .css('line-height', '30px').appendTo(typeAndDiff);
            typeAndDiff.appendTo(thumbnail);
            //点击某一本触发的事件
            outer.click(function (){
                {#location.href = "/book/detail/" + element.id + "/";#}
                {#location.href = "/home/book/detail/?bookid=" + element.id;#}
                let temp = window.open('_blank');
                temp.location.href = "/home/book/detail/?bookid=" + element.id;
              });
            //把当前容器放置在页面上
            outer.appendTo($('.books'))
        });
        //设置图片懒加载
        $('img.lazy').lazyload({
            threshold: 200,
            effect: 'fadeIn'
        });
    }

添加图片详情功能

在图书列表页面点击某一本图书获取这本图书的图书编号,在跳转的时候,携带图书编号跳转到详情页,在详情页中通过携带过来的图书编号在数据库査询更多的信息在页面展示。

html 复制代码
# 详情页 html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>图书详情</title>
    <!-- 新 Bootstrap 核心 CSS 文件 -->
    <link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <link type="text/css" rel="stylesheet" href="{% static 'css/bookdetail.base.css' %}">
    <!-- jQuery文件。务必在bootstrap.min.js 之前引入 -->
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
    <div class="container-fiuled header">
        <div class="container">
            <img src="{% static 'images/book_logo.png' %}">
        </div>
    </div>

    <div class="container title">图书详情</div>

    <div class="container basic">
         <div class="left" >

         </div>
         <div class="right" >

         </div>
     </div>

    <div class="container detail" style="padding: 25px; box-sizing: border-box">

    </div>

</body>
</html>
python 复制代码
# 设置 views.py视图
def book_detail(request,bookid):
    print(bookid)
    """图书详情页"""
    return render(request, 'bookdetail.html',context={'bookid':bookid})

# 获取图书详情
def get_book_by_id(request):
    # 接收传递过来的 id
    bookid = request.POST.get('bookid')
    # bookid = '39001'
    # 获取book表信息
    obj_book = Book.objects.filter(id=bookid).values()[0]
    # 通过type_id 获取列表的对象
    obj_booktype = BookType.objects.get(id=obj_book['type_id'])
    # 通过Press_id 获取出版社的对象
    obj_press = Press.objects.get(id=obj_book['press_id'])
    # 通过author_id 获取作者的对象
    obj_author = Author.objects.get(id=obj_book['author_id'])
    # 组合拼接到obj_book
    obj_book['typename'] = obj_booktype.name
    obj_book['pressname'] = obj_press.name
    obj_book['authorname'] = obj_author.name
    # 返回
    return JsonResponse(obj_book)


# 定义前端访问路由
from django.urls import path
from bookshop import views as bookshop_views

urlpatterns = [
    # =========前端 url请求:返回页面============
    path('', bookshop_views.book, name='book'),
    path('book/detail/<bookid>/', bookshop_views.book_detail, name='bookdetail'),#图书详情页

    # =========后端 url请求:返回json数据========
    path('booktype/get/', bookshop_views.get_booktype, name='booktype'),
    path('books/get/', bookshop_views.get_books, name='books'),
    path('books/query/', bookshop_views.query_books, name='books_query'),
    path('books/get_detail/', bookshop_views.get_book_by_id, name='books_getdetail'),#获取某一图书详情
]
javascript 复制代码
// 前端 JavaScript
<script>
    // 入口函数
    $(document).ready(function (){
        // 入口函数代码自动执行
        $.ajax({
            url: '/home/books/get_detail/',
            method: 'post',
            data: {
                'bookid': {{ bookid }}
            },
            success: function (res){
                {#console.log(res)#}
                addBookDetail(res)
            }
        });
        // 展示图书信息
    function addBookDetail(book) {
        //清除图片区域
        let leftArea = $('.basic .left');
        leftArea.html("");
        //定义基础的URL
        BASE_URL = "http://127.0.0.1:8000/";
        //加载图书
        $('<img>').attr("width",'300px').attr('height',"300px").css('margin','50px')
            .attr('src', BASE_URL + book.image).appendTo(leftArea);

        //右边区域
        let rightArea = $('.basic .right');
        rightArea.html("");
        //编号
        let arr = ["id", "name",'authorname','price','typename','difficulty','pressname','publish_date','inventory_num'];
        let arr_name = ['编号','名称','作者','价格','类别','等级','出版社','出版时间','库存量'];
        let obj_ul = $('<ul>').css('font-size','16px');
        //循环
        for(let index in arr){
            let temp_li = $("<li>");
            $("<span>").css('color','red').html(arr_name[index]+":").appendTo(temp_li);
            $("<span>").css('color','#000').html("&nbsp;&nbsp;&nbsp;"+ book[arr[index]]).appendTo(temp_li);
            temp_li.appendTo(obj_ul);
        }
        obj_ul.appendTo(rightArea);

        //把图书的详情展示在页面
        $('.detail').html(book.detail);
    }

    })
</script>

后台部署富文本编辑器

  • 安装模块
javascript 复制代码
pip install django-ckeditor==5.9.0
pip install Pillow==6.2.0
  • 注册并添加配置
python 复制代码
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'bookshop',
    'xadmin',
    'crispy_forms',
    'crispy_bootstrap3',
    'ckeditor',
    'ckeditor_uploader'
]


# ====添加CKEditor配置====
# 使用默认的主题名称
CKEDITOR_CONFIGS = {
'default':
    {
        'toolbar': 'full',
        'height': 500,
        'width': 900,
    },

}
# 上传文件存储在哪个目录 /media/uploads/
CKEDITOR_UPLOAD_PATH = "uploads/"
  • 配置url路由
python 复制代码
from django.urls import path
# 调用其他app下定义的 url
from django.urls import include
# 引入 xadmin
import xadmin
from django.conf import settings
# 匹配静态文件
from django.conf.urls.static import static

urlpatterns = [
    # path('admin/', admin.site.urls),
    path('xadmin/', xadmin.site.urls),
    # 富文本编辑器
    path('ckeditor/',include('ckeditor_uploader.urls')),
    path('home/', include('bookshop.urls'))
]

# 允许所有的图片被访问
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
  • 模版models.py添加Book新字段
python 复制代码
from ckeditor_uploader.fields import RichTextUploadingField
class Book(models.Model):   # 图书
    id = models.CharField(primary_key=True, max_length=100, null=False, verbose_name='编号')  # 编号,主键
    name = models.CharField(max_length=100,  null=False, verbose_name='名称')  # 名称
    barcode = models.CharField(max_length=100,  null=False, verbose_name='条形码')  # 条形码
    type = models.ForeignKey("BookType", on_delete=models.CASCADE, verbose_name='类别')  # 类别
    press = models.ForeignKey("Press", on_delete=models.CASCADE, verbose_name='出版社')  # 出版社
    author = models.ForeignKey("Author", on_delete=models.CASCADE, verbose_name='作者')  # 作者
    price = models.FloatField(null=False, verbose_name='单价')  # 价格
    difficulty = models.CharField(max_length=100,null=False, verbose_name='难易程度')  # 难易程度: 0--入门  1--中级  2--高级
    # path="" 默认存储到media下 variations缩略图配置
    image = StdImageField(max_length=200, upload_to=UploadToUUID(path=""),
                          verbose_name='图片', variations={'thumbnail': {'width': 70, 'height': 100}}
                          )
    publish_date = models.DateField(null=False, verbose_name='发行时间')  # 发行时间
    storage_in_num = models.IntegerField(null=False, verbose_name='入库量')  # 入库量
    storage_in_date = models.DateField(null=False, verbose_name='入库时间') # 入库时间
    inventory_num = models.IntegerField(null=False, verbose_name='库存量')  # 库存量
    # 添加富文本编辑器字段
    detail = RichTextUploadingField(verbose_name="图书详情",default="")
    class Meta:
        verbose_name = "图书"
        verbose_name_plural = "图书"

    def __str__(self):
        return "%s" % self.name
  • 同步数据
python 复制代码
python manage.py makemigrations
python manage.py migrate
  • 将富文本内容展示在前端页面
python 复制代码
//把图书的详情展示在页面
$('.detail').html(book.detail);
相关推荐
小小小小关同学1 小时前
Spring Cloud LoadBalancer
后端·spring·spring cloud
学地理的小胖砸1 小时前
【GEE的Python API】
大数据·开发语言·前端·python·遥感·地图学·地理信息科学
尘心cx2 小时前
抽象类介绍
python
刘好念2 小时前
[Python]使用python统计docx文档字符、单词数
python·office
robot_大菜鸟3 小时前
python_openCV_计算图片中的区域的黑色比例
开发语言·python·opencv
AI让世界更懂你3 小时前
漫谈设计模式 [18]:策略模式
python·设计模式·策略模式
Pandaconda3 小时前
【C++ 面试 - 新特性】每日 3 题(六)
开发语言·c++·经验分享·笔记·后端·面试·职场和发展
这不巧了3 小时前
Faker在pytest中的应用
python·自动化·pytest
chanTwo_003 小时前
go--知识点
开发语言·后端·golang
悟空丶1233 小时前
go基础知识归纳总结
开发语言·后端·golang