点我查看本文集的说明及目录。
本项目相关内容( github传送 )包括:
实现过程:
CH4 创建社交网站
CH5 在网站中分享内容
CH6 追踪用户动作
项目总结及改进:
网站应用实现微信登录
CH5 在网站中分享内容
上一章,我们为网站创建了用户注册和权限功能,学习了如何为用户创建自定义 profile 模型以及使用主要的社交网站账号登录网站。
在这一章中,我们将学习如何创建 JavaScript bookmarklet 实现分享其它网站内容的功能,以及使用 JQuery 和Django 实现 AJAX 特性。
这一章,我们将学习以下内容:
创建多对多关系
为表单自定义行为
在 Django 中使用 jQuery
创建 jQuery bookmarklet
使用 sore-thumbnail 生成图像缩略图
执行 AJAX 视图并使用 jQuery 集成
为视图创建自定义装饰器
创建 AJAX 分页
创建一个图片标签网站
我们将实现用户为图像添加标签、分享从其它网站上找到的图片、以及在我们的网站上分享图片。为实现这个功能,我们需要完成以下工作:
- 定义保存图像及其信息的模型;
- 创建表单和视图来实现上传图片功能;
- 创建用户可以发布从其它网站上找到的图片的系统。
首先,在 bookmarks 项目中新建一个应用:
django-admin startapp images
在项目的 settings.py 文件的 INSTALLED_APPS 中加入 ‘images’ :
INSTALLED_APPS = ['account',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'social_django',
'images']
现在,新的应用已经激活了。
创建图片模型
编辑image应用的models.py模型并添加以下代码:
from django.conf import settings
from django.db import models
# Create your models here.
class Image(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,
related_name='images_created')
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, blank=True)
url = models.URLField()
image = models.ImageField(upload_to='images/%Y/%m/%d')
description = models.TextField(blank=True)
created = models.DateField(auto_now_add=True, db_index=True)
def __str__(self):
return self.title
这个模型用于保存从不同网站上找到的图片。让我们来看一下这个模型的字段:
user : 标记这个图片的 User 对象。这是一个外键,它指定了一个一对多关系。一个用户可以发布多张图片,但是每个图片只有一个用户。
title :图片的标题。
slug :只包括字母、数字、下划线或者连字符来创建 SEO 友好的 URLs 。
url : 这个图片的原始 URL 。
image:图片文件。
describe:可选的图片描述。
created:对象创建日期。由于我们使用了 auto_now_add ,创建对象时会自动填充这个字段。我们使用db_index=True ,这样 Django 将在数据库中为这个字段创建一个索引。
注意:
数据库索引将改善查询表现。对于频繁使用 filter()、exclude()、order_by() 进行查询的字段要设置db_index=True 。外键字段或者 unique=True 的字段会自动设置索引。还可以使用 Meta.index_together 来为多个字段创建索引。
我们将重写 Image 模型的 save() 方法,从而实现根据 title 字段自动生成 slug 字段的功能。导入 slugify() 函数并为 Image 模型添加 save() 方法:
from django.utils.text import slugify
class Image(models.Model):
...
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title)
super(Image, self).save(*args, **kwargs)
笔者注:
在 blog 项目中,我们在 admin网站中设置 prepopulated_field 实现输入 title 时自动生成 slug 。也可以为 blog 项目的 Posts 模型添加这个 save 方法,从而支持在其它页面写文章。
代码中,如果用户没有提供 slug ,我们将使用 slufigy() 函数根据标题自动生成图像的 slug 。然后保存对象。自动生成 slug 可以避免用户为每张图片填写 slug 字段。
创建多对多关系
我们将在 Image 模型中添加一个字段来存储喜欢这张图片的用户。这种情况需要一个多对多关系,因为一个用户可能喜欢多张图片,而且每张图片可以被多个用户喜欢。
在 Image 模型中添加下面的字段:
users_like = models.ManyToManyField(settings.AUTH_USER_MODEL,
related_name='image_liked', blank=True)
定义一个多对多字段时,Django 使用两个数据库表的主键创建了一个内联表。ManyToManyField 可以在两个相关模型中的任何一个模型中。
与在 ForeignKey 字段中一样,ManyToManyField 的 related_name 属性允许相关对象使用这个名字访问这个对象。ManyToManyField 字段提供一个多对多管理器,这个管理器可以获取相关对象,比如 image.users_like.all()或者从用户端查询 user.image_liked.all() 。
打开命令行并执行以下命令:
python manage.py makemigrations images
我们将看到这样的输出:
Migrations for 'images':
images/migrations/0001_initial.py
- Create model Image
现在运行以下命令实现迁移:
python manage.py migrate images
我们将看到这样的输出:
Operations to perform:
Apply all migrations: images
Running migrations:
Applying images.0001_initial... OK
现在,Image 模型同步到数据库中了。
在 admin网站中注册 image 模型
编辑 images 应用的 admin.py 文件并在 admin网站中注册 image 模型:
from django.contrib import admin
from .models import Image
# Register your models here.
class ImageAdmin(admin.ModelAdmin):
list_display = ['title', 'slug', 'image', 'created']
list_filter = ['created']
admin.site.register(Image, ImageAdmin)
使用 python manage.py runserver 运行开发服务器。在浏览器中打开 http://127.0.0.1:8000/admin ,将在 admin网站中看到 image 模型:
发布其它网站上找到的内容
我们将帮助用户从外部网站标记 image 。用户将提供图像的 URL 、标题并且可以进行描述。我们的应用将下载图片并在数据中创建新的 image 对象。
我们从创建一个提交新图片的表单开始。在 images 应用目录下新建 forms.py 文件,并添加以下代码:
from django import forms
from .models import Image
class ImageCreateForm(forms.ModelForm):
class Meta:
model = Image
fields = ('title', 'url', 'description')
widgets = {'url': forms.HiddenInput, }
这个表单是一个 ModelForm,根据 image 模型创建,只包含 title、url 和 description 字段。用户不需要在表单中填入图片的 URL 。他们使用 JavaScript 工具从外部网站选择一个图片,我们的表单将以参数的形式获得这个图片的 URL 。我们重写了 url 字段的默认组件来使用 HiddenInput 组件。这个组件被渲染为一个具有type=‘hidden’ 属性的 HTML 输入元素。我们使用这个组件是因为不希望用户看到这个字段。
验证表单字段
这里只允许上传 JPG 格式的图片,为了验证提供的图片的 URL 有效,我们将检查文件名是否以 .jpg 或者 .jpeg 结尾。Django 允许用户通过定义表单的 clean_
def clean_url(self):
url = self.cleaned_data['url']
valid_extensions = ['jpg', 'jpeg']
extension = url.rsplit('.', 1)[1].lower()
if extension not in valid_extensions:
raise forms.ValidationError(
'The given URL does not match valid image extensions')
return url
在上面的代码中,我们定义 clean_url() 方法来验证 url 字段。代码这样工作:
- 通过访问表单实例的 cleaned_data 字典获得 url 字段的值;
- 截取 URL 获得文件扩展名并验证是否有效。如果该 URL 使用一个无效的扩展名,将引发 ValidationError ,表单将无法通过验证。我们只是实现了一个非常简单的验证,你可以使用更高级的方法检查给定的 URL 是否提供了有效的图片文件。
除了验证给定的 URL ,我们还需要下载图片文件并保存。我们可以使用处理表单的视图来下载图片文件。这里我们使用更加通用的方法来实现,重写模型表单的 save() 方法在保存表单时实现下载。
重写 ModelForm 的 save() 方法
ModelForm 提供一个 save() 方法将当前模型实例保存到数据库中并返回该模型对象。这个方法接收一个布尔参数commit ,这个参数指定是否需要提交到数据库。如果 commit 为 False ,save() 方法将返回一个模型实例但是并不保存到数据库。我们将重写表单的 save() 方法来获得给定图片并保存。
在 forms.py 文件的开头部分导入以下模块:
from requests import request
from django.core.files.base import ContentFile
from django.utils.text import slugify
然后在ImageCreateForm中添加以下save()方法:
def save(self,force_insert=False,force_update=False,commit=True):
image = super(ImageCreateForm,self).save(commit=False)
image_url = self.cleaned_data['url']
image_name = '{}.{}'.format(slugify(image.title),image_url.rsplit('.',1)[1].lower())
# download image form given URL
response = request('GET',image_url)
image.image.save(image_name,ContentFile(response.content),save=False)
if commit:
image.save()
return image
笔者注:
注意,定义 image_name 时使用 rsplit 方法对 image_url 进行拆分, rsplit 与 split 功能类似,只是它从右侧开始拆分,rsplit 中的参数 1 表示只拆分一次,即取出文件后缀即可。
我们重写 save() 方法来保存 ModelForm 需要的参数,下面是代码如何执行的:
- 通过调用设置 commit=False 的 save() 方法获得 image 实例。
- 从表单的 cleaned_data 字典中读取 URL 。
- image 的名称 slug 结合初始文件扩展名生成图片名称;
- 使用 Python 的 request 库下载文件,然后调用 image 字段的 save() 方法,并传入一个 ContentFile 对象, ContentFile 对象是下载文件内容的实例。这样我们将文件保存到项目文件目录下。我们还传入save=False 避免保存到数据库。
- 为了与重写前的 save() 方法保持一样的行为,只有在 commit 参数设置为 True 时才将表单保存到数据库。
笔者注:
视图可能会多次调用表单的 save() 方法,这将导致多次请求及下载图片,我们可以将代码改为:
def save(self,force_insert=False,force_update=False,commit=True): image = super(ImageCreateForm,self).save(commit=False) if commit: image_url = self.cleaned_data['url'] image_name = '{}.{}'.format(slugify(image.title),image_url.rsplit('.',1)[1].lower()) # download image form given URL response = request('GET',image_url) image.image.save(image_name,ContentFile(response.content),save=False) image.save() return image
这样,只在数据库保存对象实例时才下载图片。
我们也可以另外设置标志位来判断是否下载图片。
现在,我们需要一个视图来处理表单,编辑 images 应用的 views.py 文件,并添加以下代码:
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
from .forms import ImageCreateForm
# Create your views here.
@login_required
def image_create(request):
if request.method == 'POST':
# form is sent
form = ImageCreateForm(data=request.POST)
if form.is_valid():
# form data is valid
cd = form.cleaned_data
new_item = form.save(commit=False)
# assign current user to the item
new_item.user = request.user
new_item.save()
messages.success(request, 'Image added successfully')
# redirect to new created item detail view
return redirect(new_item.get_absolute_url())
else:
# build form with data provided by the bookmarklet via GET
form = ImageCreateForm(data=request.GET)
return render(request, 'images/image/create.html',
{'section': 'images', 'form': form})
我们为 image_create 视图添加了 login_required 装饰器来防止没有权限的用户访问。下面是视图的实现的工作:
- 通过 GET 方法得到初始数据来创建表单实例。实例化时将从外部网站获得图片的 url 和 title 数据,我们之后创建的 JavaScript 工具的GET方法提供这些数据,这里我们只是假设获得了初始化数据。
- 如果提交表单,我们将检查它是否有效。如果表单有效我们将创建一个新的 image 实例,但是设置commit=False 来阻止将对象保存到数据库中。
- 为新的 image 对象设置当前用户。这样我们就可以知道谁上传了这张图片。
- 将 image 对象保存到数据库。
- 最后使用 Django 消息框架创建成功消息并将用户重定向到新图片的 URL 。我们现在还没有实现 image 模型的 get_absolute_url() 方法,后续我们会进行添加。
在 images 应用中新建 urls.py 文件并添加以下代码:
from django.conf.urls import url
from . import views
urlpatterns = [url(r'^create/$', views.image_create, name='create'),]
编辑项目的 urls.py 文件并添加刚刚在 images 应用中创建的 URL模式:
urlpatterns = [url(r'^admin/', admin.site.urls),
url(r'^account/',include('account.urls',namespace='account')),
url(r'^', include('social_django.urls', namespace='social')),
url(r'^images/',include('images.urls',namespace='images')),]
最后,我们需要新建模板来渲染表单。在image应用目录下新建下面的目录:
编辑 create.html 模板并添加以下代码:
{% extends "base.html" %}
{% block title %}Bookmark an image{% endblock %}
{% block content %}
Bookmark an image
{% endblock %}
现在,在浏览器中打开 http://127.0.0.1:8000/images/create/?title=...&url=... ,GET 请求中包含后续提供的 title 和 url 参数。
笔者注:
原文这里有个 URL 及 URL 对应的图片,本章完成后可以实现该功能,因此,这里先完成后面的内容,再进行测试。
使用 jQuery 创建 bookmarklet
bookmarklet 是保存在浏览器中使用 JavaScript 代码扩展浏览器功能的书签。当你点击书签时,将在浏览器的当前网页中执行 JavaScript 代码。这对于创建与其他网站进行交互的工具非常有帮助。
一些在线服务(比如 Pinterest )通过自己的 bookmarklet 帮助用户将其它网站的内容分享到自己的平台上。我们将使用相似的方法创建一个 bookmarklet 帮助用户将在其它网站看到的图片分享到我们的网站上。
我们将使用 jQuery 实现 bookmarklet 。jQuery 是一个用于快速开发客户端功能的 JavaScript 框架。你可以从它的网站上了解更多内容:http://jquery.com/ 。
下面是用户如何在自己的浏览器中添加 bookmarklet 并使用:
用户将我们网站上的链接拖动到自己浏览器的书签中。该链接的 href 属性为 JavaScript 代码。这些代码将被保存到书签中。
用户浏览任何网站并点击书签,书签将执行保存的 JavaScript 代码。
由于 JavaScript 代码以书签的形式保存,保存之后我们无法对其进行更新。这个一个重大缺陷,但是可以通过执行简单的启动脚本从 URL 加载 JavaScript 代码来解决这个问题。你的用户将以书签的形式保存这个启动脚本,这样我们可以在任意时刻更新 bookmarklet 。这是我们创建 bookmarklet 时采用的方法,让我们开始吧!
在 image/templates/ 中新建 bookmarklet_launcher.js 模板 。这是一个启动脚本,在脚本中添加以下JavaScript代码:
(function () {
if (window.myBookmarklet !== undefined) {
myBookmarklet();
}
else {
document.body.appendChild(document.createElement('script')).src = 'http://127.0.0.1:8000/static/js/bookmarklet.js?r=' + Math.floor(Math.random() * 99999999999999999999);
}
})();
这个脚本通过检查是否定义了 myBookmarklet 变量来判断是否加载了 bookmarklet 。这样可以在用户重复点击 bookmarklet 时避免重复加载。如果没有定义 myBookmarklet ,我们加载另外一个向文件添加
我们从 https://www.staticfile.org/ 加载 jQuery 框架,https://www.staticfile.org/ 使用高速可靠的内容交付网络托管流行的 JavaScript 框架。 您也可以从 http://jquery.com/ 下载 jQuery ,并将其添加到应用程序的静态目录中。
我们添加
这些代码是这样工作的:
- 从一个公共 CDN 加载 jQuery cookie 插件,这样我们可以与 cookie 交互。
- 读取 csrftoken cookie 的值;
- 定义 csrfSafeMethod() 函数来检查一个 HTTP 方法是否安全。安全的方法(包括 GET、HEAD、OPTIONS和TRACE)不需要 CSRF 防御。
- 使用 $.ajaxSetup() 设置 jQuery AJAX 请求。我们对每个 AJAX 请求检查请求方法是否安全以及当前请求是否跨域。如果请求不安全,我们将使用从 cookie 中获取的值设置 X-CSRFToken 标头。jQuery 的所有 AJAX 请求都将进行这种设置。
CSRF令牌将用在所有使用不安全的 HTTP 方法(比如 POST、PUT )的 AJAX 请求中。
笔者注:
我们可以直接使用 {{ csrf_token }} 从模板内容中获取 CSRF令牌,这样代码简化为:
关于 CSRF 防御的使用方法见:https://www.jianshu.com/p/235876c75d79。
使用 jQuery 实现 AJAX 请求
编辑 image 应用的 images/image/details.html 模板并将以下行:
{% with total_likes=image.users_like.count %}
替换为:
{% with total_likes=image.users_like.count,users_like=image.users_like.all %}
然后,将 class 为 image-info 的
{{ total_likes }}
like{{ total_likes|pluralize }}
{% if request.user not in users_like %}
Like
{% else %}
Unlike
{% endif %}
{{ image.description|linebreaks }}
首先,我们在 {% with %} 模板标签中添加了另一个变量来保存 image.users_like.all 查询结果以避免重复执行。然后展示喜欢这个图片的用户数和一个包含喜欢/不喜欢这张图片的操作链接:基于检查用户是否在 users_like 相关对象集合中来展示喜欢或者不喜欢选项。在 元素添加下面的属性:
data-id: 展示的图片的 ID ;
data-action : 用户点击链接时执行的动作,可以是 like 或者 unlike 。
笔者注:
这是 HTML 5 的新特定,详细说明见 https://www.jianshu.com/p/bfa872c93d23。
我们将这两个属性的值传入 AJAX 请求中。当用户点击 like/unlike 链接时,需要在用户端实现以下动作:
- 调用 AJAX 视图传输图片的 ID 和动作参数;
- 如果 AJAX 请求成功,使用相反的动作更新 HTML 元素的 data-action 属性,并相应更改展示的文本。
- 更改展示的 like 总数。
在 images/image/detail.html 模板底部添加 domready 块并添加以下 JavaScript 代码:
{% block domready %}
$('a.like').click(function(e){
e.preventDefault();
$.post(
'{% url "images:like" %}',
{
id: $(this).data('id'),
action: $(this).data('action')
},
function(data){
if (data['status'] == 'ok'){
var previous_action = $('a.like').data('action');
// toggle data-action
$('a.like').data('action', previous_action == 'like' ? 'unlike' : 'like');
// toggle link text
$('a.like').text(previous_action == 'like' ? 'Unlike' : 'Like');
// update total likes
var previous_likes = parseInt($('span.count .total').text());
$('span.count .total').text(previous_action == 'like' ? previous_likes + 1 :
previous_likes - 1);
}
});
});
{% endblock %}
这些代码实现的操作是:
使用
$('a.like')
选择器来找到 HTML 文档中 class 为 like 的 元素;为点击事件定义一个处理函数,这个函数将在每次用户点击 like/unlike 链接时触发;
在处理函数内部,我们使用 e.preventDefault() 来避免 元素的默认行为。这样避免链接将我们引导到其它地方。
使用
$.post()
实现异步 POST 服务器请求。jQuery 还提供$.get()
方法来实现 GET 请求,以及小写的$.ajax()
方法。使用 {% url %} 模板语言为 AJAX 请求创建 URL 。
设置 POST 请求发送的参数字典,字典包括 Django 视图需要的 ID 和 action 参数。我们从 元素的 data-id 和 data-action 属性获得相应的值。
定义接收 HTTP 响应的回调函数。它接收响应返回的参数。
获取接收数据中的 status 属性并检查它是否等于 'ok' 。如果返回的数据符合预期,我们反转链接的 data-action 属性和文本。这将允许撤销操作。
根据动作,增加或者减少一个喜欢这幅图片的人的数量。
笔者注:
为了避免与第六章中添加的统计查看人数的
混淆,这里为{{ total_likes }} like{{ total_likes|pluralize }}
中 class 为 total 的 span 添加了 id,并将 jQuery 通过
$('span.count .total')
获取元素改为通过$('#like')
获取。即 detail.html 中的代码变为:
{% extends "base.html" %} {% block title %}{{ image.title }}{% endblock %} {% block content %}
{{ image.title }}
{##} {% load thumbnail %} {% thumbnail image.image "300" as im %}
{% endthumbnail %} {% with total_likes=image.users_like.count users_like=image.users_like.all %}
{{ total_likes }} like{{ total_likes|pluralize }} {% if request.user not in users_like %} Like {% else %} Unlike {% endif %}{{ image.description|linebreaks }}{% for user in image.users_like.all %}{% endwith %} {% endblock %} {% block domready %} $('a.like').click(function(e){ e.preventDefault(); $.post( '{% url "images:like" %}', { id: $(this).data('id'), action: $(this).data('action') }, function(data){ if (data['status'] == 'ok'){ var previous_action = $('a.like').data('action'); // toggle data-action $('a.like').data('action', previous_action == 'like' ? 'unlike' : 'like'); // toggle link text $('a.like').text(previous_action == 'like' ? 'Unlike' : 'Like'); // update total likes var previous_likes = parseInt($('#like').text()); $('#like').text(previous_action == 'like' ? previous_likes + 1 : previous_likes - 1); } }); }); {% endblock %}{% empty %} Nobody likes this image yet. {% endfor %}![]()
{{ user }}
在浏览器中打开已经上传的图片的图片详细页面,应该可以看到下面的初始喜欢数量和 LIKE 按钮:
点击 LIKE 按钮。将看到总的喜欢数量增加了一个而且按钮变成了 UNLIKE :
当点击 UNLIKE 按钮后按钮变回 LIKE ,总的喜欢数量相应发生改变。
使用 JavaScript 编程,尤其是实现 AJAX 请求时,推荐使用 Firebug 之类的工具进行调试。Firebug 是一个可以调试 JavaScript 并且可以监测 CSS 和 HTML 变化的 FireFox 插件。可以从 http://getfirebug.com 下载 Firebug 。其它的浏览器,比如 Chrome 或者 Safari 也提供内置开发工具来调试 JavaScript 。在这些浏览器中,你可以右击浏览器中的任何位置并点击 Inspect element 来访问 web开发工具。
为视图创建自定义装饰器
我们将限制 AJAX 视图只接收 AJAX 请求,Django 请求对象提供一个 is_ajax() 方法来判断请求是否是XMLHttpRequest 生成的(这意味将是一个 AJAX 请求)。大多数 JavaScript 库的 AJAX 请求将这个值设置到 HTTP_X_REQUESTED_WITH HTTP 标头中。
我们创建一个装饰器来检查视图的 HTTP_X_REQUESTED_WITH 标头。装饰器是一个函数,它将输入另一个函数并且在不改变该函数的基础上扩展它的行为。如果你不了解这个概念,你可以先看一个这个链接的内容 https://www.python.org/dev/peps/pep-0318/。
由于装饰器是通用的,它可以用于任意视图上。我们将在项目中新建 common Python库。在bookmarket项目下新建以下文件结构:
编辑 decorators.py 文件并添加以下代码:
from django.http import HttpResponseBadRequest def ajax_required(f): def wrap(request, *args, **kwargs): if not request.is_ajax(): return HttpResponseBadRequest() return f(request, *args, **kwargs) wrap.__doc__ = f.__doc__ wrap.__name__ = f.__name__ return wrap
这是我们自定义的 ajax_required 装饰器。它定义了一个 wrap 方法,如果不是 AJAX 请求则返回一个HttpResponseBadReques t对象(HTTP 400)。否则返回装饰器函数。
现在可以编辑 images 应用的 views.py 文件并在 image_like AJAX 视图上添加这个装饰器:
from common.decorators import ajax_required @ajax_required @login_required @require_POST def image_like(request):
如果在浏览器中尝试访问 http://127.0.0.1:8000/images/like ,将会得到一个HTTP 400响应。
注意:
如果发现在许多视图中重复同一项检查,请为视图创建自定义装饰器。
为列表视图添加 AJAX 分页
如果需要在网站中列出所有标注的图片,我们要使用 AJAX 分页实现无限滚动功能。无限滚动是指当用户滚动到页面底部时自动加载其它结果。
我们将实现一个图片列表视图,该视图既可以处理标准浏览器请求,也可以处理包含分页的 AJAX 请求。用户第一次加载图像列表页面时,我们展示图像的第一页。当页面滚动到最底部时将通过 AJAX 加载后面页面的内容。
同一个视图将处理标准请求和 AJAX 分页请求。编辑 image 应用的 views.py 文件并添加以下代码:
from django.http import HttpResponse from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger @login_required def image_list(request): images = Image.objects.all() paginator = Paginator(images, 8) page = request.GET.get('page') try: images = paginator.page(page) except PageNotAnInteger: # If page is not an integer deliver the first page images = paginator.page(1) except EmptyPage: if request.is_ajax(): # If the request is AJAX and the page is out of range # return an empty page return HttpResponse('') # If page is out of range deliver last page of results images = paginator.page(paginator.num_pages) if request.is_ajax(): return render(request, 'images/image/list_ajax.html', {'section': 'images', 'images': images}) return render(request, 'images/image/list.html', {'section': 'images', 'images': images})
在这个视图中,我们定义了 queryset 来返回数据库中的所有图片。然后实现了一个 Paginator 对象按照每页八幅图片对结果进行分页。如果请求的页数已经超出分页页数则实现 EmptyPage 异常处理。如果请求通过 AJAX 实现则返回一个空的 HttpResponse 来帮助我们在客户端停止 AJAX 分页。我们使用两个不同的模板渲染结果:
- 对于 AJAX 请求,我们只渲染 list_ajax.html 模板,这个模板只包含请求的页面的图片。
- 对于标准请求,我们渲染 list.html 模板,这个模板将扩展 base.html 模板来展示整个页面,并且使用list_ajax.html 模板来包含图片列表。
编辑 images 应用的 urls.py 文件并添加以下 URL模式:
url(r'^$', views.image_list, name='list'),
最后实现上面提到的模板,在 images/image 模板目录下新建一个 list_ajax.html 模板,并添加以下代码:
{% load thumbnail %} {% for image in images %} {% endfor %}
这个模板展示图像列表。我们将用它来返回 AJAX 请求结果。在相同的目录下再新建一个 list.html 模板,添加以下代码:
{% extends "base.html" %} {% block title %}Images bookmarked{% endblock %} {% block content %}
Images bookmarked
{% include "images/image/list_ajax.html" %}{% endblock %}这个模板扩展了 base.html 模板。为避免重复代码,我们使用 list_ajax.html 模板来展示图片。list.html 模板将包含滚轮滚动到底部时加载额外页面的 JavaScript 代码。
在 list.html 模板中添加以下代码:
{% block domready %} var page = 1; var empty_page = false; var block_request = false; $(window).scroll(function() { var margin = $(document).height() - $(window).height() - 200; if ($(window).scrollTop() > margin && empty_page == false && block_request == false) { block_request = true; page += 1; $.get('?page=' + page, function(data) { if(data == '') { empty_page = true; } else { block_request = false; $('#image-list').append(data); } }); }; }); {% endblock %}
这段代码实现了无限滚动功能。我们将 JavaScript代码 放到 base.html 中定义的 domready 块中,代码实现的功能包括:
定义以下变量:
- page :保存当前页码;
- empty_page :判断用户是否在最后一页并获取一个空页面。当我们获得一个空页面时表示没有其它结果了,我们将停止发送额外的 AJAX 请求。
- block_request:处理一个 AJAX 请求时阻止发送额外请求;
使用 $(window).scroll() 来获得滚动时间并为其定义一个处理函数;
计算表示总文档高度和窗口高度的差的 margin 变量,这是用户滚动获得额外内容的高度。我们将结果减去200 以便在用户接近底部 200 像素的位置加载下一页;
只在没有实现其他 AJAX请求( block_request 为 False )并且用户没有到达最后一个页面( empty_page 为 Flase )的情况下发送一个 AJAX请求;
将 block_request 设为 True 来避免滚动事件触发另一个 AJAX 请求,并将页面数增加 1 来获得另外一个页面;
使用 $.get() 实现一个AJAX GET 请求并将 HTML 响应返回到 data 的变量中,这里有两种情况:
- 响应不包含内容:我们已经到了底端没有更多页面需要加载了。设置 empty_page 为 true 阻止更多的 AJAX请求;
- 响应包括数据:我们将数据添加到 id 为 image-list 的 HTML 元素底部。当用户到达页面底端时页面内容垂直扩展。
在浏览器中打开 http://127.0.0.1:8000/images/ 。你将看到标记过的图片列表,看起来是这样的:
滚动到页面的底部来加载剩下的页面。确保你使用 bookmarklet 标记的图片多于 8 张,那是我们一页显示的图片数量。记住,我们可以使用 Firebug 或类似工具来追踪 AJAX请求和调试 JavaScript代码。
最后,编辑 account 应用的 base.html 模板并为主目录的 Image 项添加到图片列表的链接:
Images 现在可以从主目录访问图片列表了。
总结
本章,我们使用 JavaScript bookmarklet 实现从其它网站分享图片到自己的网站,使用 jQuery 实现了 AJAX 视图,并添加了 AJAX 分页。
下一章,我们将学习如何创建一个关注系统和一个活动流。将会用到通用关系、signals 和 demormalization ,还将学习如何在 Django 中使用 Redis 。