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

news2024/11/17 4:36:06

项目概述

  1. 项目实现Ajax异步请求局部刷新
  2. 使用XAdmin后台模板
  3. 提供图片上传接口
  4. 在明细页应用了富文本编辑器
  5. 在加载图书信息的时候使用LazyLoad(图片懒加载)
# 环境
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源码
# 添加修改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格式

  • 新建数据库连接并完成连接配置

# 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文件夹

# settings.py 下添加访问静态文件路径

STATIC_URL = '/static/'

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

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

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

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

# 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类并插入数据
# 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 manage.py makemigrations
python manage.py migrate

Ajax 异步请求 

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

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

  • 准备图书类别的JSON数据 
# ************定义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'),

]

  • 将后端返回的数据展示在页面上
# 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代码
<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>
  • 准备图书列表数据
# ************定义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代码
<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 部署及配置

  • 引入源码到项目中
# 安装xadmin的依赖包进入源码的目录
pip install -r requirements.txt
  • 安装核心的包
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
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 manage.py makemigrations
python manage.py migrate
  • 设置管理员账号密码
python manage.py createsuperuser
  • 报错问题解决
解决报错 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
# 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中全局配置
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
# MEDIA ROOT:本地处理的路径,MEDIA URL: 互联网访问的路径
# settings.py 
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
MEDIA_URL = ''
  • 配置URL
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)
  • 安装模块
pip install django-stdimage==3.2.0 -i https://mirrors.aliyun.com/pypi/simple/
  • 配置models.py模版文件
# 引入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 manage.py makemigrations
  • 实现上传的图片在前台显示
#  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代码
<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>
# 后端代码 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代码
<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>
# 后端代码 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'),
]

 

  • 图书难易程度筛选
# 前端 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})
  • 图书关键字筛选
# 前端 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})
  • 图书价格升降序排序
# 前端 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})

图片懒加载

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

<!--引入图片懒加载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
{% 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>
# 设置 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
<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>

后台部署富文本编辑器

  • 安装模块
pip install django-ckeditor==5.9.0
pip install Pillow==6.2.0
  • 注册并添加配置
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路由
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新字段
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 manage.py makemigrations
python manage.py migrate
  • 将富文本内容展示在前端页面
//把图书的详情展示在页面
$('.detail').html(book.detail);

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1981688.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内网穿透原理,免费内网穿透(简单使用),公网主动访问内网。

内网穿透的本质就是流量转发&#xff0c;把一个电脑的网卡上的流量数据&#xff0c;转发到另一个网卡的流量数据。 实现原理 网络协议分层就是&#xff0c;物理层&#xff0c;数据链路层&#xff0c;网络层&#xff0c;传输层&#xff0c;应用层。我简单解释一下这些分层协议…

全球情绪分析软件市场规划预测:未来六年CAGR为13.6%

一、引言 随着人工智能和机器学习技术的进步&#xff0c;情绪分析软件市场正迅速成为科技行业的重要组成部分。本文旨在探索情绪分析软件行业的发展趋势、潜在商机及其未来展望。 二、市场趋势 全球情绪分析软件市场的增长主要受企业对客户反馈分析的需求增加、社交媒体监控的…

河底地形的表达形式及与倾斜模型的融合效果

0序&#xff1a; 无人机航测可以解决地表的现状获取&#xff0c;做流域管理&#xff0c;河道管理&#xff0c;生态管理的经常关注河床的变化。其中淤泥的堆积状态是关注的重中之重。 当前对水下高程的测绘&#xff0c;主要是使用无人测量船。设定个轨迹&#xff0c;就和无人机…

03、DQL(数据查询语句)

目录 1、编写顺序 2、基本查询 3、条件查询 4、聚合函数 5、分组查询 6、排序查询 7、分页查询 8、执行顺序 1、编写顺序 SELECT 字段列表 FROM 表名列表 WHERE 条件列表 GROUP BY 分组字段列表 HAVING 分组后条件列表 ORDER BY 排序字段列表 LIMIT 分页参数2、基本查…

手写chatGPT——fetch解析text/event-stream会话流并逐字回显到页面——js技能提升

直接上效果图&#xff1a; 页面分上下两部分&#xff0c;上面是会话界面&#xff0c;底部是提交框。 直接上代码&#xff1a; 解决步骤1&#xff1a;引入vueelementUi <head><meta charset"UTF-8" /><title>Fetch Stream Example</title>…

【java计算机毕设】社团管理系统MySQL springboot vue maven项目设计源码代码+文档 前后端可分离也可不分离

目录 1项目功能 2项目介绍 3项目地址 1项目功能 【java计算机毕设】社团管理系统MySQL springboot vue maven项目设计源码代码文档 前后端可分离也可不分离 2项目介绍 系统功能&#xff1a; 社团管理系统包括管理员、团长、学生三种角色。 管理员功能包括个人中心模块用于修…

ElasticSearch集成webFlux响应式开发

目录 前言 1.Weflux特点&#xff1a; 2.WebFlux简单集成ElasticSearch 2.1 引入基本依赖模块 3.application.yml文件的配置 4.定义Product实体类 5.定义ElasticSearch的数据访问层接口 6.定义Controller 7.启动SpringBoot程序&#xff0c;用postman进行接口测试 前言 We…

mysql的 undo log、redo log、bin log、buffer pool

文章目录 Buffer Pool为什么需要Buffer PoolBuffer Pool 缓存了什么 Redo log为什么需要 redo log&#xff1f;redo log 什么时候刷盘&#xff1f;redo log 文件写满了怎么办&#xff1f; undo log 本文章内容都来自小林coding博主&#xff0c;基于他的文章内容&#xff0c;加一…

保研408真题练习:2010年全国硕士研究生入学统一考试(单选篇1)

&#x1f9ca;&#x1f9ca;&#x1f9ca;单项选择题&#xff08;共40道&#xff09; &#x1f9ca;数据结构&#xff08;11道&#xff09; &#x1f965;1.2.考察的都是栈和队列的入栈&#xff08;队&#xff09;出栈&#xff08;队&#xff09;问题 这道题目重点是掌握各种…

linux网络编程(2)

什么是多线程服务器&#xff1f; 先认识什么是单线程服务器 就是服务器只处理一个客户端信息。 多线程服务器类似的&#xff0c;就是处理很多个客户端的信息。 多进程服务器的核心理念 使用while循环&#xff0c;让服务器一直处于接收状态&#xff0c;每接收到一个客户端&am…

翰德恩赋能新能源龙头企业硬件敏捷研发

该企业始创于1984年&#xff0c;是全球知名的智慧能源系统解决方案提供商。创立40年来&#xff0c;形成了集“发电、储电、输电、变电、配电、售电、用电”为一体的全产业链优势&#xff0c;业务遍及140多个国家和地区&#xff0c;拥有4大全球研发中心&#xff0c;建立6大国际营…

Ldap未授权访问漏洞

LDAP中文全称为&#xff1a;轻型目录访问协议&#xff08;Lightweight Directory Access Protocol&#xff09;&#xff0c;默认使用389&#xff0c; LDAP 底层一般使用 TCP 或 UDP 作为传输协议。目录服务是一个特殊的数据库&#xff0c;是一种以树状结构的目录数据库为基础。…

药厂子母钟系统,强抗干扰能力,满足复杂生产环境

在制药行业中&#xff0c;精确的时间同步对于确保药品生产的质量和合规性至关重要。药厂子母钟系统作为一种高度可靠的时间同步解决方案&#xff0c;不仅能够提供准确的时间信息&#xff0c;还具有强大的抗干扰能力&#xff0c;非常适合在复杂的生产环境中使用。本文将详细介绍…

登录注册功能开发

本篇文章记录怎么实现一个简单的登陆注册功能。 讲解里的代码是不完全的&#xff0c;具体的代码我会放在文章最后 文章目录 准备为什么要有登录&#xff1a;简述注册功能&#xff1a;简述登录功能完全代码&#xff1a;数据实体部分&#xff1a;Users类&#xff1a;UsersLoginD…

ME31L-创建计划协议

ME31L创建计划协议 一般常用的就是LA和LPA LP不需要审批&#xff0c;LPA需要审批。 计划协议和采购申请实现的功能相同。 计划协议创建界面和创建合同的界面相同。 ME38可以分配交货计划行。 输入计划协议号。回车。 双击行项目后&#xff0c;维护行项目字段。 然后可以在货…

一个线程在sleep的时候set一个信号会起作用吗

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

代码随想录第29天|贪心

134.加油站 代码随想录 代码随想录 索引01234gas12345cost34512 计算每个加油站的剩余油量&#xff0c;累计sum&#xff0c;一旦<0就从下一个重新计数。 我还没理解为什么我们不需要计算环路的sum&#xff0c;而是只需要遍历一次。 因为使用了两个变量&#xff1a;curSu…

Java ExecutorService:你真的了解它吗?

文章目录 一、什么是ExecutorService&#xff1f;二、ExecutorService的核心功能三、如何创建和使用ExecutorService&#xff1f; 时光匆匆&#xff0c;又来到另一个里程碑&#xff0c;感谢粉丝们的陪伴&#xff0c;有你们真好~ 不水文啦&#xff0c;一起加油叭~ 一、什么是Exe…

【Java】Jsoup 解析HTML报告

一、需求背景 有好几种报告文件&#xff0c;目前是人肉找报告信息填到Excel上生成统计信息 跟用户交流了下需求和提供的几个文件&#xff0c;发现都是html文件 其实所谓的报告的文件&#xff0c;就是一些本地可打开的静态资源&#xff0c;里面也有js、img等等 二、方案选型 前…

adb环境变量配置(附详细图解)

adb环境变量配置&#xff08;附详细图解&#xff09; 1、找到ADB工具的位置。通常&#xff0c;如果你Android Studio已经安装了Android SDK&#xff0c;ADB工具位于SDK的platform-tools目录下。我这里的目录是C:\Users\user\AppData\Local\Android\Sdk\platform-tools\adb.exe…