Alan Hou的个人博客

Django 3 Web开发指南第4版 第4章 模板和JavaScript

完整目录请见:Django 3网页开发指南 – 第4版

本章中我们将学习如下内容:

引言

静态网站适用于静态内容,如传统文档、在线图书或课程;但如今大部分交互式web应用和平台必须拥有动态组件,才能鹤立鸡群、为访客提供最好的用户体验。本章中,我们将学习在Django中如何使用JavaScript和CSS。我们会使用到Bootstrap 4前端框架来进行响应式布局,以及使用jQuery JavaScript框架来实现更具生产力的脚本。

技术要求

和前面一样,学习本章代码,应当安装最新稳定版本的Python、MySQL或PostgreSQL数据库,以及在虚拟环境中安装Django项目。有些小节会需要用到特定的Python依赖。有些会用到额外的JavaScript库。在对应的小节中会进行说明。

本章中的代码请参见 GitHub 仓库的 Chapter04目录。

译者注:本章中使用到了 PostgreSQL,习惯使用 MySQL的小伙伴们请自行安装配置,同时请记得安装psycopg2

pip install psycopg2-binary

编排 base.html模板

在开始使用模板前,首先要做的是创建一个base.html基础模板,项目中的大部分页面模板对其进行扩展。本小节中,我们将演示如何为多语种HTML5网站创建这种模板,同时顾及响应式。

💡响应式(responsive)网站对所有设备提供相同的基础内容,按视窗进行相应的样式展示,不管使用的是桌面浏览器、平板电脑还是手机。这与自适应(adaptive)网站不同,后者根据user agent来自决定设备类型,然后提供不同的内容、标记,甚至会根据user agent的分类不同而使用不同的功能。

准备工作

在项目中创建templates目录,并在配置文件中添加模板目录,如下所示:

如何实现…

按照如下步骤进行操作:

  1. 在模板根目录下,使用如下内容创建一个base.html文件:
  2. 在misc/includes中,创建包含所有版本favicon的模板:

    ℹ️favicon是一个通常会在浏览器标签、最近访问网站的集合或桌面上快捷方式中看到的小图片。可以使用一个在线生成器来使用 logo 生成适用不同用例、浏览器及平台多个版本的favicon。我们常用的favicon生成器有https://favicomatic.com/和https://realfavicongenerator.net/。

  3. 使用网站的头部和底部创建模板misc/includes/header.html和misc/includes/footer.html。现在可以保持文件的内容为空。

实现原理…

基础模板包含HTML文档的<head> 和 <body>两大版块,其中包含网站中其它页面都将重用的内容。根据网页设计的要求,可以对不同布局建立不同的基础模板。例如,我们可以添加一个base_simple.html文件,在其中包含相同的<head>版块以及最简化的<body>版块,将其用于登录页、密码重置或其它简单页面。也可以对其它布局添加单独的基础模板,如单栏、两栏和三栏布局,每个都继承base.html并按需要重写各版块。

我们来看此前所定义的base.html的详情。以下是<head>版块中的详细说明:

以下是<body>版块中的详细说明:

我们所创建的基础模板并非是一个一成不变的模板。可以修改其结构或添加所需的元素,例如针对 body属性的模板区块、Google Analytics代码段、常用JavaScript文件、针对iPhone书签的Apple touch图标、Open Graph元标签、Twitter Card标签、schema.org属性等等。还可以根据项目需求定义其它的版块,甚至是封装整个body内容,以便在子模板中进行重写。

相关内容

使用 Django Sekizai

在Django模板中,通常会使用模板继承来重写父模板中的版块来在HTML文档中添加样式或脚本。这表示每个视图的主模板应知晓其内的所有内容;但有时让所包含模板来决定样式和脚本的加载会更为便捷。通过Django Sekizai可进行实现,本节我们就来学习如何使用。

准备工作

在开始本小节的学习前,按照如下步骤来进行准备:

  1. 在虚拟环境中安装django-classy-tags和django-sekizai(并将它们添加到requirements/_base.txt中):
  2. 然后在配置文件的已安装应用中添加sekizai:
  3. 接着在配置文件的模板配置中添加sekizai上下文处理器:

如何实现…

按照如下步骤来完成本小节的学习:

  1. 在base.html模板的开头加载sekizai_tags库:
  2. 还是在该文件中,在<head>版块的最后,添加模板标签{% render_block “css” %}如下:
  3. 然后在<body> 版块的结束处添加模板标签{% render_block “js” %}如下:
  4. 现在,想在任意模板中添加样式或JavaScript时,像下面这样使用{% addtoblock %}模板标签:

实现原理…

Django Sekizai适用于{% include %} 模板标签所包含的模板、通过模板渲染的自定义模板标签或针对表单组件的模板。模板标签{% addtoblock %}定义了我们想要添加HTML内容的Sekizai版块。

在Sekizai版块中添加内容时,django-sekizai进行仅包含该内容一次的处理。这表示可以有多个同一类型的组件,但它们的CSS和JavaScript都只会加载并执行一次。

相关内容

在JavaScript中暴露配置

Django项目的配置放在settings文件中,如用于开发环境的myproject/settings/dev.py;我们在第1章 Django 3.0入门为开发、测试、预发布和生产环境配置设置一节中进行过讲解。其中有些配置值可用于浏览器的功能,因此也需要在JavaScript中进行设置。我们希望在同一处定义项目设置,因此在本小节中,我们将学习如何将一些Django端的配置值传递到浏览器。

准备工作

确保在TEMPLATES[‘OPTIONS’][‘context_processors’] 配置中有请求上下文处理器,如下:

如未创建的话,还应创建core应用,并将其添加至配置文件的INSTALLED_APPS中:

如何实现…

按照如下步骤来创建并包含JavaScript配置:

  1. 在core应用的views.py文件中,创建一个返回JavaScript内容类型的js_settings()视图,如以下代码所示:
  2. 在URL配置中插入该视图:
  3. 通过在base.html模板的最后添加该内容来在前台中加载JavaScript视图:
  4. 此时可以像下面这样在任意JavaScript文件中访问具体的配置:

实现原理…

在 js_settings视图中,我们构建了一个希望传递给浏览器的设置字典,转化字典为JSON,渲染解析该JSON的JavaScript的模板并将结果赋值给window.settings 变量。通过将字典转化为JSON字符串并在JavaScript文件中解析,我们不必担心最后一个元素后逗号所带来的问题 – 这在Python中允许使用,但在JavaScript中视作无效。

渲染后的JavaScript文件类似下面这样:

相关内容

使用HTML5 data属性

HTML5中引入了data-*属性用解析来自网页服务器中的具体HTML元素为JavaScript和CSS。在本小节中,我们将学习一种有效把Django中数据添加到HTML5数据属性中的方式,然后用实例讲解如何在JavaScript中读取这些数据:我们将渲染带有具体地理位置标记的Google Map;在点击标记时,将会在信息窗口中显示该地址。

准备工作

按照如下步骤来进行准备:

  1. 在本章及接下来的章节中使用带有PostGIS插件的PostgreSQL数据库。要了解如何安装PostGIS插件,请参阅官方文档
  2. 确保在Django项目中使用了postgis数据库后台:
  3. 创建带有Location模型的locations应用。它将包含一个UUID主键、Char 字段名称、街道地址、城市、国家和邮编,PostGIS相关的Geoposition字段以及Description文本字段:
  4. 重写save()方法来在创建地点时生成唯一UUID字段:
  5. 创建方法来以一个字符串获取地点的完整地址:
  6. 创建函数来通过纬度和经度来设置地理位置,在数据中,地理位置保存为一个Point字段。我们可以使用在Django shell、表单、管理命令、数据迁移和其它地方使用这些函数:
  7. 在更新模型后别忘记生成并运行迁移。
  8. 创建模型管理后台来添加及修改地点。这里不使用标准的ModelAdmin,而是使用gis应用中的OSMGeoAdmin。它会使用OpenStreetMap来渲染一个地图来设置地理位置,参见https://www.openstreetmap.org:
  9. 在后台中添加一些位置以供今后使用。

我们还将在后续小节中使用并改进这一locations应用。

译者注:1、django.core.exceptions.ImproperlyConfigured: Could not find the GDAL library…

其它平台参见官方文档

Postgis也可通过 Stack Builder(Spatial Extensions) 来进行安装

2、TemplateDoesNotExist gis/admin/osm.html

在INSTALLED_APPS中添加 ‘django.contrib.gis’

如何实现…

执行如下步骤:

  1. 注册Google Maps API密钥。可以参照Google开发者文档了解如何操作。
  2. 在secrets中添加Google Maps API密钥,然后在设置中进行读取:
  3. 在core应用中,创建上下文处理器来将GOOGLE_MAPS_API_KEY暴露到模板中:
  4. 在模板配置中引用这一上下文处理器:
  5. 对地点创建列表及详情视图:
  6. 对locations应用创建URL配置:
  7. 在项目的URL配置中包含地点的URL:
  8. 是时候创建用于地点列表和详情视图的模板了。地点列表现在尽量保持简化,我们只需要能够浏览地点并获取地点详情视图即可。
  9. 接下来,我们通过继承 base.html并重写content版块来创建一个地点详情的模板:
  10. 同时在同一模板中,重写js版块:
  11.  像模板一样,我们需要能够读取HTML5 data属性的JavaScript文件并使用它们来渲染带有标记的地图:
  12. 要让地图美观的显示,我需要使用一些CSS,如以下代码所示:

实现原理…

如果运行本地开发服务器并浏览地点的详情视图,会浏览到带有地图及标记的页面。而在点击标记时,会弹出地址信息。像下面这样:

移动设备因滚动叠加的原因而无法地图中滚动,我们选择在小屏(宽度小于480 px)上隐藏地图,因而在缩小屏幕时,地图最终会不可见,如下所示:

我们再看一下代码。在前几步中,我们添加了Google Maps API 密钥并对所有模板进行了暴露。然后我们创建了视图来浏览地点并将它们插入到了URL配置中。接着创建了一个列表及详情模板。

ℹ️DetailView的默认template_name为小写版本的模型名,加上detail;因此我们的模板文件名为location_detail.html。如果希望使用其它的模板,可以在视图中指定template_name属性。同理,ListView的默认template_name为小写版本的模型名,加上list,因而名称为location_list.html。

在详情模板中,id=”map”的<div>元素后面接地点标题和描述,以及data-latitude、data-longitude和data-address属性。这些共同组成了content版块的元素。<body>最后面的js版块中添加了两个<script>标签,一个是后面会讲到的location_detail.js,另一个是Google Maps API脚本,对其传递Maps API密钥以及API加载时的回调函数名称。

在JavaScript文件中,我们使用prototype函数创建了一个Location类。该函数有一个静态init()方法,给定为Google Maps API的回调函数。在调用init()时,会调用该构造函数来创建单个Location实例。在构造函数中,采取了一系列步骤来设置地图及其功能:

  1. 首先通过ID找到map的壳(容器)。仅在找到元素时才会继续。
  2. 然后,我们使用data-latitude和data-longitude属性来查找地理坐标,将它们存储为字典来作为地点的坐标。这一对象是Google Maps API所能识别的形式,在稍后使用。
  3. 下面读取data-address,并将其直接存储为地点的地址属性。
  4. 现在我们进行构建,从地图开始。要确保地点可见,我们使用之前从数据属性中拉取的坐标作为中点。
  5. 标记会让地点在地图上更明显,位置使用同样的坐标。
  6. 最后,我们构建信息容器,这是一种使用API直接在地图上显示的气泡弹窗。除此前获取的地址外,我们还根据模板中指定的.map-title类来查找地点标题。它会在窗口中以<h1>标题进行添加,后接为<p>段落的地址。为能显示该窗口,我们对标记添加了一个点击事件监听器来打开该窗口。

相关内容

提供响应式图片

响应式网站已成为常规操作,在对移动设备和台式机提供同样内容时浮现出了很多性能问题。一种在小屏设备上降低负载时间的简易方式是提供更小的图片。这正是响应式图片的核心组件srcset和sizes属性发光发热的地方。

准备工作

我们使用前一小节中的locations应用。

如何实现…

按照如下步骤来添加响应式图片:

  1. 首先在虚拟环境中安装django-imagekit并添加到requirements/_base.txt.中。我们使用它来将原始图片修改为指定尺寸:
  2. 在设置文件的INSTALLED_APPS中添加 imagekit:
  3. 在models.py文件的开头处,导入一些用于图片版本的库、并定义一个用于图片文件目录和文件名的函数:
  4. 此时对定义图片版本相同文件中的Location模型添加picture字段:
  5. 然后对Location模型重写delete()方法来在删除模型实例时删除所生成的版本文件:
  6. 添加并运行迁移来在数据库模式中添加新的图片字段。
  7. 更新地点详情模板来包含图片:
  8. 最后,在后台中对地点添加这些图片。

实现原理…

响应式图片很强大,并且底层在于根据展示图片显示设备特性的media规则来提供不同的图片。我们首先做的是添加django-imageki应用,这让其可以实时按需生成不同的图片。

很明显我们还需要原始图片,因此在Location模型中,我们添加了一个名为picture的图片字段。在 upload_to()函数中, 我们通过年月和地点的UUID以及上传文件的扩展名构建了upload路径和文件名。我们还像下面这样定义了图片版本规格:

在地点的delete() 方法中,我们查看picture字段是否有值,然后在删除地点本身前会去删除该字段以及各图片版本。在磁盘上找不到文件时我们使用contextlib.suppress(FileNotFoundError)来静默地忽略错误。

最有趣的部分在模板中。在地点的图片存在时,我们构建<picture>元素。表面上这基本是一个容器。而事实上其中除去现在模板尾部的<img>外里面没有其它内容,但其用处很大。除默认图片外,我们还生成了其它大小的缩略图 – 480 px 和 768 px,它们用于构建额外的<source>元素。每个<source>元素具有media规则条件,根据条件在srcset属性值中选择图片。本例中,我们仅为每个<source>提供了一张图片。地点详情页面此时会在地图上包含图片,如下图所示:

在浏览器加载标记时,会按照一系列步骤来决定加载哪张图片:

结果是在较小的视窗中会加载较小的图片。例如,这里我们可以看到仅375 px宽的视窗会加载最小的图片:

对于完全无法识别<picture> 和 <source>的浏览器,还是能加载默认图片,那样与普通的<img>并没有什么区别。

扩展知识…

不仅可以使用响应式图片来提供目标图片尺寸,也可以区分像素密度来提供对给定视窗尺寸设计所显式剪切的图片。这被称之为art direction。如果想要了解,请阅读Mozilla开发者网络(MDN)上有关该主题的文章,点击访问

相关内容

实现持续滚动

社交网站常常会有持续滚动的功能,可以通过无限下拉滚动来实现翻页功能。这种方式不是通过链接来单独获取更多的内容集合,而是一个子项长列表,在页面下拉滚动时,加载新项并自动加入到页面底部。本小节中,我们将学习如何通过Django和 jScroll jQuery 插件来实现这一功能。

ℹ️可以通过https://jscroll.com/下载jScroll脚本并阅读有关该插件的文档。

准备工作

我们将复用前面小节中所创建的locations应用。

为了让列表视图中显示的内容更为丰富,我们在Location模型中添加一个rating字段,如下:

get_rating_percentage()方法用于以百分比形式返回评分。

别忘了迁移操作,然后在后台中对地点添加评级。

如何实现…

按照如下步骤来创建持续滚动页面:

  1. 首先在后台中添加足够多的地点。参照使用HTML5 data属性一节,我们会添加一个每页10项的LocationList视图,因此至少需要11个地点来验证持续滚动的效果。
  2. 修改地点列表视图的模板如下:
  3. 在同一个模板文件中,使用如下代码重写css和js代码块:
  4. 模板中的最后一步是用JavaScript重写extra_body版块来添加加载中的图标:
  5. 创建页面导航地址为locations/includes/navigation.html。目前保持为空即可。
  6. 下一步添加JavaScript初始化持续滚动组件:
  7. 最后,我添加一些CSS来让评分显示为对用户更友好的星标,而不是干巴巴的数字:
  8. 在网站主样式文件中,添加加载中的样式:

实现原理…

在浏览器中打开地点列表视图时,会在页面中显示通过在视图中由paginate_by预设的条数(即10条)。在下拉时,会自动加载下一页并将获取到的内容添加至容器中。页面链接使用第5章 自定义模板过滤器和标签创建模板标签来修改请求查询参数一节中所介绍的{% modify_query %}自定义模板标签来即根据当前链接生成相应的URL,指向对应的下一页。如果网速较慢,那么在滚动到页面底部时,会在下一页加载完成并添加至当前列表前看到如下面这样的页面:

进一步进行滚动,第二、第三页及后续页面的内容会添加至底部。直接没有更多页面可供加载,体现为最后一组中没有进行一步可供加载的分页链接。

这里我们使用Cloudflare CDN URL来加载jScroll,但如果你选择下载文件至本地,那么使用{% static %}查询在添加脚本至本地模板。

在初始加载页面时,元素具有item-list CSS类,包含页面内容及分页链接,可通过list.js中的代码转变为jScroll对象。其实这种实现非常通用,它可以按照相似的标记结构来启动用对任意列表显示进行持续滚动。

提供了如下选项为定义其功能:

rating.css插入Unicode星形字符并对中空的星形覆盖上已填充星形来实现评分效果。使用等于评分值最大百分比的值作为宽度(本例为5),填充后的星形在中空星形之上覆盖相应的空间,允许进行小数评分。在标记中,对于使用屏幕阅读器的人们有一个带有评分信息的aria-label属性。

最后, style.css文件中的CSS使用CSS动画来创建一个旋转的加载图标。

扩展知识…

在边栏中有一个用于导航的占位符。注意对于持续滚动,所有内容列表之后的二级导航应位于边栏而不是footer中,因为访客可能永远不会触达页面的底部。

相关内容

在模态对话框中打开对象详情

本小节中,我们将创建一个地点链接列表,在点击时,会打开带有一些地点信息和Learn more…链接的Bootstrap模态框,链接指向地点详情页:

对话框中的内容使用Ajax加载。对于不支持JavaScript的访客,会跳过中间步骤立即打开详情页。

准备工作

我们使用前一小节所创建的locations应用。

确保其中包含了我们前面所定义的视图、URL配置以及地点列表和详情模板。

如何实现…

执行以下步骤来在列表视图和详情视图之间创建一个模态对话框中间页:

  1. 首先,在地点应用的URL配置中,添加一条响应模态对话框的规则:
  2. 为模态对话框创建一个模板:
  3. 在地点列表的模板中,通过添加自定义data属性来更新链接为地点详情:
  4. 同样在该文件中,通过模态对话框的代码重写extra_body内容:
  5. 最后,修改list.js文件,添加脚本来处理打开和关闭模态对话框:

实现原理…

如果我们在浏览器中访问地点列表视图并点击其中一个地点时,会看到类似下面这样的模态对话框:

我们来看这些是如何共同作用的。名为location_detail_modal的URL路径指向同一地点的详情视图,但使用了不同的模板。该模板仅有一张响应式图片和具有Learn more…链接的模态对话框底部,链接指向该地点的常规详情页。在列表视图中,我们修改了列表项的链接来包含 data-modal-title 和 data- modal-url属性,稍后在JavaScript中进行引用。前一个属性表明应将完整地址用作标题。后一个属性表明模态对话框的主体 HTML应该接收的位置。在列表视图的最后是Bootstrap 4模态对话框的标记。该对话框包含具有Close按钮和标题的状况,以及主要详情的内容区。JavaScript应通过js版块进行添加。

在JavaScript文件中,我们使用jQuery框架来利用其更精简的语法和跨浏览器功能。在加载页面时,对.item-list元素添加了一个事件处理器on(‘click’)。在点击任意a.item时,事件被委托给处理器,它读取并存储自定义数据属性为url和title。在成功提取这些内容后,阻止原始点击动作(导航至完整的详情页)执行,然后设置模态框以供显示。设置新标题和隐藏对话框并通过Ajax加载模态对话框的内容到.modal-body元素中。最后使用Bootstrap 4 modal() jQuery插件将模态框显示给用户。

如果JavaScript无法通过自定义属性处理模态对话框的URL,甚至是list.js中的JavaScript完全无法加载或执行,用户可以通过点击地点链接正常进行详情页。我们以渐进式的增强实现了模态框,这样会提升用户的体验,即使出现错误也没问题。

相关内容

实现Like微件

通常在具有社交组件的网站中,会具有Facebook、Twitter和Google+插件来用于喜欢和分享内容。本节中,我们将讲解在Django中实现类似的功能,用于在用户对内容点赞时将信息保存至数据库中。可以根据用户在网站上具体点赞的内容来创建相应的视图。我们会创建一个Like插件,包含两个状态按钮以及显示总点赞数的数标。

以下截图显示的是未点赞状态,用户通过点击按钮来进行点赞:

以下截图显示的是已点赞状态,用户通过点击按钮来取消点赞:

插件状态中的改变交由Ajax调用处理。

准备工作

首先,创建likes应用交添加至INSTALLED_APPS中。然后设置一个Like模型,它有一个对点赞内容用户的外键关联,以及对数据库中任意对象的通用关联。我们将使用第2章 模型和数据库结构创建模型mixin来处理通用关联一节中所写义的object_relation_base_factory。如果不希望使用mixin,可以在如下模型中自己定义一个通用关联:

同时确保在配置文件中设置了request上下文处理器。并且为了让当前已登录用户与request关联需要在request中添加authentication middleware:

别忘了对新的Like模型创建和运行迁移来进行相应的数据库设置。

如何实现…

逐步执行如下步骤:

  1. 在likes应用中,创建一个templatetags目录,共中包含一个空的__init__.py文件让其成为Python模块。然后添加likes_tags.py文件,在其中我们定义{% like_widget %}模板标签如下:
  2. 在同一个文件中添加过滤器来获取某个用户的Like状态以及具体对象的Like总数:
  3. 在URL规则中,我们需要一个针对视图的规则,使用Ajax来处理点赞和取消点赞:
  4. 还应保证URL与项目之间的映射:
  5. 接着需要定义视图,如以下代码所示:
  6. 在任意对象的列表或详情视图中,我们可以添加该微件的模板标签。下面将这一微件添加到前面小节中所创建的地点详情页中:
  7. 然后需要为该微件添加一个模板,如以下代码所示:
  8. 最后,我们创建JavaScript来处理在浏览中点赞和取消点赞的动作,如下:

实现原理…

此时可对网站任何对象使用{% like_widget for object %}模板标签。它生成一个显示Like状态的微件,状态根据当前登录用户是否及如何与对象响应生成。

Like按钮有3个自定义HTML5 data属性:

我们使用django-sekizai在页面中添加了<script src=”{% static ‘likes/js/widget.js’ %}”></script> 。注意如果页面中有一个以上的Like微件时,只需要包含该JavaScript一次即可。而如果在页面上没有Like微件,则无需包含该JavaScript代码。

在这个JavaScript文件中,Like按钮由like-button CSS类进行识别。有一个关联到文档的事件监听器来监听页面上的这类按钮的点击事件,然后对data-href所指定的URL进行Ajax post调用。

指定的视图json_set_like接收两个参数:内容类型ID及所点赞对象的主键。视图检查指定对象是否存在点赞,如果存在则进行删除;反之添加一个Like对象。视图会返回一个JSON响应,包含success状态、Like对象所接收的动作(add或remove)、所有用户对该对象的Like总数。根据所返回的动作,JavaScript将对按钮显示相应的状态。

可以在浏览器的开发者工具中调试Ajax响应,通常位于Network标签下。如果在开发过程中出现服务端错误,在配置中开启DEBUG后可以在响应预览中看到错误追踪信息,否则会看到下图中所示返回的JSON:

相关内容

通过Ajax上传图片

使用默认的文件input框,很快就会发现需要进行改进来提升用户体验:

本节中,我们学习如何改进文件上传功能。

准备工作

我们使用前面小节中所创建的locations应用。

我们的JavaScript文件会依赖于外部库-jQuery File Upload。可以下载https://github.com/blueimp/jQuery-File-Upload/tree/v10.2.0中的文件并放入 site_static/site/vendor/jQuery-File-Upload-10.2.0。这个工具还要求使用jquery.ui.widget.js,放在vendor/子目录中。准备好这些后就可以开始我们的学习了。

如何实现…

通过以下步骤为locations定义一个表单,让其支持Ajax上传:

  1. 我们为locations创建一个模型表单,有非必填的picture字段、隐藏的picture_path字段、地理位置使用的latitude和longitude字段:
  2. 在该表单的__init__()方法中,我们从模型实例中读取位置,然后对表单定义django-crispy-forms布局:
  3. 然后我们要对该表单的picture和picture_path字段添加验证:
  4. 最后,我们对该表单添加保存方法,处理图片和地理位置的保存:
  5. 除前面在locations应用中定义的视图外,还要添加一个add_or_change_location视图,如以下代码所示:
  6. 在URL配置中添加该视图:
  7. 在core应用的视图中,添加通用upload_file函数上传图片,供其它具有picture字段的应用复用:
  8. 为新上传视图设置URL规则如下:
  9. 下面为地点表单创建模板如下:
  10. 我们还需要一些模板。创建一个文件上传自定义模板,其中包含所需的CSS和JavaScript:
  11. 然后,创建一个用于图片预览的模板:
  12. 最后,添加处理图片上传和预览的JavaScript:

实现原理…

如果JavaScript执行失败,表单仍然可用,但在JavaScript正常运行时,会得到一个改进了的表单,文件字段由按钮替换,如下所示:

在通过点击Upload File…按钮选中图片后,浏览器中的结果类似下面的截图:

点击Upload File…按钮会触发文件对话框,要求选择文件,选中后会立即开始Ajax上传处理。然后我们看到添加了图片的预览。预览的图片上传到了一个临时目录,文件名保存在picture_path隐藏字段中。在提交表单时,表单通过临时目录或图片字段保存该图片。图片字段的值在提交表单时没有JavaScript或无法加载JavaScript的情况下会进行提交。如果在页面重新加载后其它字段存在验证错误,那么预览图片根据picture_path进行加载。

我们进一步深挖运行步骤,查看其运行原理。

在针对Location模型的模型表单中,我们令picture字段非必填,但它在模型层面是必填的。此外,还添加了picture_path字段,这两个字段至少有一个要提交至表单。在crispy-forms布局中,我们对picture字段定义了一个自定义模板file_upload_field.html。在里面设置了预览图片、上传进度条、允许上传文件格式以及最小尺寸的自定义帮助文本。还在该模板中添加了 jQuery File Upload库中的CSS和JavaScript文件以及自定义脚本picture_upload.js。CSS文件将上传字段渲染为美观的按钮。JavaScript负责基于Ajax的文件上传。

picture_upload.js将所选中的文件发送给upload_file视图。该视图检查上传文件是否为图片类型并尝试将其保存至项目MEDIA_ROOT中的temporary-uploads/目录下。此视图返回文件成功或不成功上传详情的JSON。

在选中并上传图片及提交表单之后,会调用LocationForm的save()方法。如果存在picture_path字段,会从临时目录中提取文件、复制到Location模型的picture字段中。然后会删除临时目录中的图片并保存Location实例。

扩展知识…

我们在模型表单中排除了geoposition字段,而是将地理位置的数据以纬度和经度字段进行渲染。默认地理位置的PointField以Leaflet.js地图进行渲染,无法自定义。通过这两个纬度和经度字段,我们可以灵活地使用Google Maps API, Bing Maps API或Leaflet.js来在地图中显示,可手动输入或通过输入的地址代码生成。

为方便起见,我们使用了前面在使用HTML5 data属性一节中定义的两个帮助方法get_geoposition() 和 set_geoposition()。

相关内容

退出移动版