静态资源和Ajax请求

加载静态资源

如果要在Django项目中使用静态资源,可以先创建一个用于保存静态资源的目录。在vote项目中,我们将静态资源置于名为static的文件夹中,在该文件夹包含了三个子文件夹:css、js和images,分别用来保存外部CSS文件、外部JavaScript文件和图片资源,如下图所示。

Day43 - 静态资源和Ajax请求 - 图1

为了能够找到保存静态资源的文件夹,我们还需要修改Django项目的配置文件settings.py,如下所示:

  1. STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'), ]
  2. STATIC_URL = '/static/'

配置好静态资源之后,大家可以运行项目,然后看看之前我们写的页面上的图片是否能够正常加载出来。需要说明的是,在项目正式部署到线上环境后,我们通常会把静态资源交给专门的静态资源服务器(如Nginx、Apache)来处理,而不是有运行Python代码的服务器来管理静态资源,所以上面的配置并不适用于生产环境,仅供项目开发阶段测试使用。使用静态资源的正确姿势我们会在后续的章节为大家讲解。

Ajax概述

接下来就可以实现“好评”和“差评”的功能了,很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验,因此我们考虑使用Ajax技术来实现“好评”和“差评”。Ajax是Asynchronous Javascript And XML的缩写 , 简单的说,使用Ajax技术可以在不重新加载整个页面的情况下对页面进行局部刷新。

对于传统的Web应用,每次页面上需要加载新的内容都需要重新请求服务器并刷新整个页面,如果服务器短时间内无法给予响应或者网络状况并不理想,那么可能会造成浏览器长时间的空白并使得用户处于等待状态,在这个期间用户什么都做不了,如下图所示。很显然,这样的Web应用并不能带来很好的用户体验。

Day43 - 静态资源和Ajax请求 - 图2

对于使用Ajax技术的Web应用,浏览器可以向服务器发起异步请求来获取数据。异步请求不会中断用户体验,当服务器返回了新的数据,我们可以通过JavaScript代码进行DOM操作来实现对页面的局部刷新,这样就相当于在不刷新整个页面的情况下更新了页面的内容,如下图所示。

Day43 - 静态资源和Ajax请求 - 图3

在使用Ajax技术时,浏览器跟服务器通常会交换XML或JSON格式的数据,XML是以前使用得非常多的一种数据格式,近年来几乎已经完全被JSON取代,下面是两种数据格式的对比。

XML格式:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <message>
  3. <from>Alice</from>
  4. <to>Bob</to>
  5. <content>Dinner is on me!</content>
  6. </message>

JSON格式:

  1. {
  2. "from": "Alice",
  3. "to": "Bob",
  4. "content": "Dinner is on me!"
  5. }

通过上面的对比,明显JSON格式的数据要紧凑得多,所以传输效率更高,而且JSON本身也是JavaScript中的一种对象表达式语法,在JavaScript代码中处理JSON格式的数据更加方便。

用Ajax实现投票功能

下面,我们使用Ajax技术来实现投票的功能,首先修改项目的urls.py文件,为“好评”和“差评”功能映射对应的URL。

  1. from django.contrib import admin
  2. from django.urls import path
  3. from vote import views
  4. urlpatterns = [
  5. path('', views.show_subjects),
  6. path('teachers/', views.show_teachers),
  7. path('praise/', views.prise_or_criticize),
  8. path('criticize/', views.prise_or_criticize),
  9. path('admin/', admin.site.urls),
  10. ]

设计视图函数praise_or_criticize来支持“好评”和“差评”功能,该视图函数通过Django封装的JsonResponse类将字典序列化成JSON字符串作为返回给浏览器的响应内容。

  1. def praise_or_criticize(request):
  2. """好评"""
  3. try:
  4. tno = int(request.GET.get('tno'))
  5. teacher = Teacher.objects.get(no=tno)
  6. if request.path.startswith('/praise'):
  7. teacher.good_count += 1
  8. count = teacher.good_count
  9. else:
  10. teacher.bad_count += 1
  11. count = teacher.bad_count
  12. teacher.save()
  13. data = {'code': 20000, 'mesg': '操作成功', 'count': count}
  14. except (ValueError, Teacher.DoseNotExist):
  15. data = {'code': 20001, 'mesg': '操作失败'}
  16. return JsonResponse(data)

修改显示老师信息的模板页,引入jQuery库来实现事件处理、Ajax请求和DOM操作。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>老师信息</title>
  6. <style>
  7. #container {
  8. width: 80%;
  9. margin: 10px auto;
  10. }
  11. .teacher {
  12. width: 100%;
  13. margin: 0 auto;
  14. padding: 10px 0;
  15. border-bottom: 1px dashed gray;
  16. overflow: auto;
  17. }
  18. .teacher>div {
  19. float: left;
  20. }
  21. .photo {
  22. height: 140px;
  23. border-radius: 75px;
  24. overflow: hidden;
  25. margin-left: 20px;
  26. }
  27. .info {
  28. width: 75%;
  29. margin-left: 30px;
  30. }
  31. .info div {
  32. clear: both;
  33. margin: 5px 10px;
  34. }
  35. .info span {
  36. margin-right: 25px;
  37. }
  38. .info a {
  39. text-decoration: none;
  40. color: darkcyan;
  41. }
  42. </style>
  43. </head>
  44. <body>
  45. <div id="container">
  46. <h1>{{ subject.name }}学科的老师信息</h1>
  47. <hr>
  48. {% if not teachers %}
  49. <h2>暂无该学科老师信息</h2>
  50. {% endif %}
  51. {% for teacher in teachers %}
  52. <div class="teacher">
  53. <div class="photo">
  54. <img src="/static/images/{{ teacher.photo }}" height="140" alt="">
  55. </div>
  56. <div class="info">
  57. <div>
  58. <span><strong>姓名:{{ teacher.name }}</strong></span>
  59. <span>性别:{{ teacher.sex | yesno:'男,女' }}</span>
  60. <span>出生日期:{{ teacher.birth }}</span>
  61. </div>
  62. <div class="intro">{{ teacher.intro }}</div>
  63. <div class="comment">
  64. <a href="/praise/?tno={{ teacher.no }}">好评</a>&nbsp;&nbsp;
  65. (<strong>{{ teacher.good_count }}</strong>)
  66. &nbsp;&nbsp;&nbsp;&nbsp;
  67. <a href="/criticize/?tno={{ teacher.no }}">差评</a>&nbsp;&nbsp;
  68. (<strong>{{ teacher.bad_count }}</strong>)
  69. </div>
  70. </div>
  71. </div>
  72. {% endfor %}
  73. <a href="/">返回首页</a>
  74. </div>
  75. <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  76. <script>
  77. $(() => {
  78. $('.comment>a').on('click', (evt) => {
  79. evt.preventDefault()
  80. let url = $(evt.target).attr('href')
  81. $.getJSON(url, (json) => {
  82. if (json.code == 20000) {
  83. $(evt.target).next().text(json.count)
  84. } else {
  85. alert(json.mesg)
  86. }
  87. })
  88. })
  89. })
  90. </script>
  91. </body>
  92. </html>

上面的前端代码中,使用了jQuery库封装的getJSON方法向服务器发送异步请求,如果不熟悉前端的jQuery库,可以参考《jQuery API手册》

小结

到此为止,这个投票项目的核心功能已然完成,在下面的章节中我们会要求用户必须登录才能投票,没有账号的用户可以通过注册功能注册一个账号。