Django异步请求和后台管理实战
项目概述
- 项目实现Ajax异步请求局部刷新
- 使用XAdmin后台模板
- 提供图片上传接口
- 在明细页应用了富文本编辑器
- 在加载图书信息的时候使用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,syssys.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 includeurlpatterns = [path('admin/', admin.site.urls),path('home/', include('bookshop.urls'))
]# ==============apps目录下urls.py================from django.urls import path
from bookshop import views as bookshop_viewsurlpatterns = [# =========前端 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.nameclass 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.nameclass 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.nameclass 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_viewsurlpatterns = [# =========前端 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>价格: </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>难易程度: </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){// 新建一个 spanlet 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_viewsurlpatterns = [# =========前端 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){// 新建一个 spanlet 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,设置类为thumbnaillet 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);//设置图书名称的 Hoverbookname.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 xadminurlpatterns = [# 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 = Trueuse_bootswatch = Truedef has_add_permission(self,request):return Falseclass 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 staticurlpatterns = [# 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){// 新建一个 spanlet 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,设置类为thumbnaillet 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);//设置图书名称的 Hoverbookname.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){// 新建一个 spanlet 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.typequeryItems.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,设置类为thumbnaillet 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);//设置图书名称的 Hoverbookname.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.namereturn JsonResponse({'book': books}, json_dumps_params={'ensure_ascii': False})# 设置路由 urls.py
from django.urls import path
from bookshop import views as bookshop_viewsurlpatterns = [# =========前端 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、pricemaxqueryItems.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){// 新建一个 spanlet 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.typequeryItems.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,设置类为thumbnaillet 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);//设置图书名称的 Hoverbookname.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.namereturn JsonResponse({'book': books}, json_dumps_params={'ensure_ascii': False})# 设置路由 urls.py
from django.urls import path
from bookshop import views as bookshop_viewsurlpatterns = [# =========前端 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.namereturn 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.namereturn 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');// 修改orderqueryItems.order = 1;// 提交给后端queryBooks();});// 降序$('.desc').click(function (){// 把所有排序的图标不选择$('.asc').css('color','#fff');$('.desc').css('color','#fff');// 选中升序$('.desc').css('color','red');// 修改orderqueryItems.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.namereturn 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("");//定义一个基础的URLlet baseURL = "http://127.0.0.1:8000/";//遍历books.forEach(function (element, index, array) {//新建一个div-最外层的容器let outer = $('<div>').attr( "class", "col-lg-3");//新建一个div,设置类为thumbnaillet 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);//设置图书名称的 Hoverbookname.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("    难易程度:" + 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):# 接收传递过来的 idbookid = 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_bookobj_book['typename'] = obj_booktype.nameobj_book['pressname'] = obj_press.nameobj_book['authorname'] = obj_author.name# 返回return JsonResponse(obj_book)# 定义前端访问路由
from django.urls import path
from bookshop import views as bookshop_viewsurlpatterns = [# =========前端 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("");//定义基础的URLBASE_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(" "+ 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 staticurlpatterns = [# 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);
相关文章:

Django异步请求和后台管理实战
项目概述 项目实现Ajax异步请求局部刷新使用XAdmin后台模板提供图片上传接口在明细页应用了富文本编辑器在加载图书信息的时候使用LazyLoad(图片懒加载) # 环境 asgiref3.7.2 crispy-bootstrap32024.1 defusedxml0.7.1 diff-match-patch20230430 Djang…...

大奖放送 | AI编程达人秀视频文章征集大赛来啦!
AI Coding,可以有多少种打开玩法?腾讯云AI代码助手是一款辅助编码工具,基于混元大模型,提供技术对话、代码补全、代码诊断和优化等能力,为你生成优质代码,帮你解决技术难题,提升编码效率。 我…...

最新小猫咪PHP加密系统源码V1.4_本地API接口_带后台
小猫咪PHP加密系统历时半年,它再一次迎来更新,更新加密算法(这应该是最后一次更新加密算法了,以后主要更新都在框架功能上面了),适配php56-php74,取消批量加密(一些不可控因素&#…...

a bag of bones
以下是根据你提供的内容制作的5道选择题,包括答案和解析: 1. 短语 "a bag of bones" 通常用来描述什么? - A. 一个恐怖片中的角色 - B. 一个非常瘦弱的人 - C. 一个懒惰的人 - D. 一个穿着比基尼的人 答案:B 解析&#…...

XLT高速线缆自动化测试系统
高速线缆自动化测试系统 随着高速通信的快速发展,对于高速数据通信线缆性能要求日益增高,在其硏发、生产阶段,需要多次测试射频性能。传统人工手动测试存在测试环境搭建复杂、测试效率低、耗时长,特别是多次测试中因为人工测试带…...

微软AI业务最新营收数据情况(2024年7月)
Azure AI 年度经常性收入 (ARR):达到50亿美元客户数量:60,000家平均客户价值 (ACV) 中位数:83,000美元同比增长率:达到了惊人的900% GitHub Copilot 年度经常性收入 (ARR):达到3亿美元客户数量:77,000家…...

canvas绘制表格
canvas绘制表格 最近在为公司产品做技术预研,经理让用canvas做一个表格,于是就有了这篇博客。 我们的数据是后端通过MQTT推送过来的 我在代码中也直接使用了 具体MQTT的实现代码,可见博客 在vue使用MQTT 在这里为了方便实用我直接封装成组件…...

避免溃坝的关键:渗压计在防洪管理中的作用
防洪管理对于保障人民生命财产安全具有重要意义,而溃坝作为防洪管理中的重大风险之一,其防范工作尤为关键。在防洪管理体系中,渗压计作为一种重要的监测工具,发挥着不可替代的作用。本文将深入探讨渗压计在防洪管理中的作用。 实时…...

品牌建设如何助力中小企业突破生存瓶颈?
品牌,不仅仅是一个标志或商标,更是企业的形象、声誉和信誉的体现。品牌的存在是为了使企业区别于其他竞争对手,树立独特的形象,赢得消费者的认可和信任。 品牌的本质是品牌拥有者的产品、服务或其它优于竞争对手的优势能为目标受…...

探索Python FastAPI的Annotated参数设计:提升代码的灵活性与可读性
在现代软件开发中,代码的可读性和灵活性是至关重要的。Python的FastAPI框架以其高性能和易用性而受到开发者的喜爱。FastAPI提供了一种名为Annotated的参数设计方式,它允许开发者以类型注解的形式增强函数参数的定义,从而提升代码的表达力和灵…...

ClickHouse 进阶【建表、查询优化】
1、ClickHouse 进阶 因为上一节部署了集群模式,所以需要启动 Zookeeper 和 ck 集群; 1.1、Explain 基本语法 EXPLAIN [AST | SYNTAX | PLAN | PIPELINE] [setting value, ...] SELECT ... [FORMAT ...] AST:用于查看语法树SYNTAX&#…...

Qt拖拽事件详解及代码实现
Qt拖拽事件详解及代码实现 前言项目描述代码结构简介代码详解 前言 qt拖拽事件是一项非常常用并且非常好用的功能,拖拽实际上是一种信息传递的载体,其目的是将信息从一个对象传递给另一个对象。通过拖拽可以简化文件打开或业务操作流程,qt初…...

云原生的候选应用
提示 该内容摘自电子书《为 Azure 构建云原生 .NET 应用程序》,可在**.NET Docs**上获取,也可以免费下载 PDF并离线阅读。 考虑一下您的组织需要构建哪些应用程序。然后,看看您投资组合中的现有应用程序。其中有多少需要云原生架构ÿ…...

什么是单例模式?
单例模式是一种常见的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个唯一实例。这种模式通常在需要控制某些资源的访问权限或确保对象的唯一性时使用。 单例模式的特点 唯一实例:单例模式确保一个类只有一个实例存在,全局可访问。 延迟实例化:在需…...

F4Pan百度网盘不限速直链解析工具最新可用
最新可用百度网盘不限速直链解析工具,现在很多解析网站和浏览器扩展都失效了,这个是用《F4Pan网盘解析系统开源源码》搭建的,有兴趣可以去研究研究。 下面看一下测试速度超过70MB每秒比开通会员还快非常的恐怖。 使用方法 1.下载F4Pan解析工…...

设计模式实战:智能家居系统的设计与实现
问题描述 设计一个智能家居系统,支持设备的控制(如灯、空调等),提供多种操作策略,并且在设备状态发生变化时通知用户。系统需要确保设备操作的灵活性和可扩展性。 设计分析 命令模式 命令模式用于将请求封装成对象,从而使我们可以用不同的请求、队列或日志来参数化其…...

Unity Rigidbody 踩坑记录
1:两个带有刚体的物体碰撞会一直不停的弹 把被动受力的刚提的 Freeze Position 的勾选 去掉(碰到过一次,有一种受力无法释放又返回给目标的 所以一直弹跳的感觉) 2:子物体 和父物体 都有刚体的情况下 子物体 Freeze R…...

Guitar Pro简谱怎么输入 ?如何把简谱设置到六线谱的下面?
一、Guitar Pro简谱怎么输入 简谱在音乐学习、演奏、创作和传播中都起着非常重要的作用,是音乐领域不可或缺的工具。吉他乐谱的制作可以使简谱,也可以使五线谱、六线谱等多种形式,这几种乐谱都可以使用Guitar Pro来完成。下面来看看Guitar Pr…...

Python 爬虫项目实战(一):爬取某云热歌榜歌曲
前言 网络爬虫(Web Crawler),也称为网页蜘蛛(Web Spider)或网页机器人(Web Bot),是一种按照既定规则自动浏览网络并提取信息的程序。爬虫的主要用途包括数据采集、网络索引、内容抓…...

Mongodb权限
MongoDB 的权限管理用于确保数据库的安全性并限制用户访问敏感数据。MongoDB 使用基于角色的访问控制(RBAC)来管理权限,允许管理员定义用户和角色,并为这些角色分配相应的权限。 Mongodb的内置角色 数据库角色 角色说明权限read…...

力扣第五十三题——最大子数组和
内容介绍 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组 是数组中的一个连续部分。 示例 1: 输入:nums [-2,1,-3,4,-1,2,1,-5,4] 输出&…...

达梦数据库:select报错:不是 GROUP BY 表达式
目录 SQL示例报错信息原因排查解决方法一:达梦支持灵活的处理方式,可以直接在查询中加hint参数方法二:修改dm.ini参数GROUP_OPT_FLAG1,动态,会话级参数,不用重启数据库方法三:配置兼容参数&…...

大模型卷向「下半场」,产业场景成拼杀重地
在19世纪的一个雨声潺潺的夏日,诗人拜伦与雪莱在瑞士的湖畔边闲聊,他们聊到了一个大胆的想法:如果能够把一个生物的各个部分制造出来,再组装到一起,赋予它生命的温暖,那会怎样? 这次对话激发了…...

OD C卷 - 多线段数据压缩
多段 线 数据压缩 (200) 如图中每个方格为一个像素(i,j),线的走向只能水平、垂直、倾斜45度;图中线段表示为(2, 8)、(3,7)、(3, 6)、(…...

密码学基础:搞懂Hash函数SHA1、SHA-2、SHA3(2)
目录 1.引入 2. SHA512-224\256 3.SHA-3 4.MD5 5.SM3 1.引入 上篇密码学基础:搞懂Hash函数SHA1、SHA-2、SHA3(1)-CSDN博客,我们先就将基础的SHA1\2讲解了,接下来我们继续聊SHA-3、SHA2变体SHA512_224\256等 2. SHA512-224\256 SHA512…...

C++ 异步编程:std::async、std::future、std::packaged_task 和 std::promise
C 异步编程:std::async、std::future、std::packaged_task 和 std::promise 在现代 C 编程中,异步编程已经成为一种常见的模式。利用 C11 引入的标准库组件 std::async、std::future、std::packaged_task 和 std::promise,我们可以更方便地处…...

OD C卷 - 石头剪刀布游戏
石头剪刀布游戏 (100) 剪刀石头布游戏,A-石头、B-剪刀、C-布游戏规则: 胜负规则,A>B; B>C; C>A;当本场次中有且仅有一种出拳形状优于其他出拳形状,则该形状的玩家是胜利者,否则认为是…...

关于k8s集群中kubectl的陈述式资源管理
1、k8s集群资源管理方式分类 (1)陈述式资源管理方式:增删查比较方便,但是改非常不方便 使用一条kubectl命令和参数选项来实现资源对象管理操作 (2)声明式资源管理方式:yaml文件管理 使用yam…...

XML 学习笔记
简介: (1)XML:可扩展性标记语言,用于传输和存储数据,而不是展示数据,是W3C 推举的数据传输格式。 XML的标签必须自定义,但是在写标签名的时候一定要有含义。 XML 只能有一个根节点…...

MongoDB未授权访问漏洞
2.MongoDB未授权访问漏洞 mongodb数据库是由C编写,主要是为了提供web应可用扩展的一种高性能数据库。开启MongoDB服务时不添加任何参数时,默认是没有权限验证的,登录的用户可以通过默认端口无需密码对数据库任意操作(增、删、改、查高危动作)而且可以远程访问数据库…...