5 分享內(nèi)容到你的網(wǎng)站
上一章中,你在網(wǎng)站中構(gòu)建了用戶注冊和認(rèn)證。你學(xué)會了如何為用戶創(chuàng)建自定義的個人資料模型,并添加了主流社交網(wǎng)站的社交認(rèn)證。
在這一章中,你會學(xué)習(xí)如何創(chuàng)建JavaScript書簽工具,來從其它網(wǎng)站分享內(nèi)容到你的網(wǎng)站,你還會使用jQuery和Django實(shí)現(xiàn)AJAX特性。
本章會覆蓋以下知識點(diǎn):
- 創(chuàng)建多對多的關(guān)系
- 定制表單行為
- 在Django中使用jQuery
- 構(gòu)建jQuery書簽工具
- 使用sorl-thumbnail生成圖片縮略圖
- 實(shí)現(xiàn)AJAX視圖,并與jQuery集成
- 為視圖創(chuàng)建自定義裝飾器
- 構(gòu)建AJAX分頁
5.1 創(chuàng)建圖片標(biāo)記網(wǎng)站
我們將允許用戶在其他網(wǎng)站上標(biāo)記和分享他們發(fā)現(xiàn)的圖片,并將其分享到我們的網(wǎng)站。為了實(shí)現(xiàn)這個功能,我們需要完成以下任務(wù):
- 定義一個存儲圖片和圖片信息的模型。
- 創(chuàng)建處理圖片上傳的表單和視圖。
- 為用戶構(gòu)建一個系統(tǒng),讓用戶可以上傳在其它網(wǎng)站找到的圖片。
首先在bookmarks項(xiàng)目目錄中,使用以下命令創(chuàng)建一個新的應(yīng)用:
django-admin startapp images
在settings.py文件的INSTALLED_APPS設(shè)置中添加images:
INSTALLED_APPS = (
# ...
'images',
)
現(xiàn)在Django知道新應(yīng)用已經(jīng)激活了。
5.1.1 創(chuàng)建圖片模型
編輯images應(yīng)用的models.py文件,添加以下代碼:
from django.db import models
from django.conf import settings
class Image(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='images_created')
title = models.CharField(max_length=200)
slug = models.CharField(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
我們將使用這個模型存儲來自不同網(wǎng)站的被標(biāo)記的圖片。讓我們看看這個模型中的字段:
-
user:標(biāo)記這張圖片的User對象。這是一個ForeignKey字段,它指定了一對多的關(guān)系:一個用戶可以上傳多張圖片,但一張圖片只能由一個用戶上傳。 -
title:圖片的標(biāo)題。 -
slug:只包括字母,數(shù)據(jù),下劃線或連字符的短標(biāo)簽,用于構(gòu)建搜索引擎友好的URL。 -
url:圖片的原始URL。 -
image:圖片文件。 -
description:一個可選的圖片描述。 -
created:在數(shù)據(jù)庫中創(chuàng)建對象的時間。因?yàn)槲覀兪褂昧?code>auto_now_add,所以創(chuàng)建對象時會自動設(shè)置時間。我們使用了db_index=True,所以Django會在數(shù)據(jù)庫中為該字段創(chuàng)建一個索引。
數(shù)據(jù)庫索引會提高查詢效率??紤]為經(jīng)常使用
filter(),exclude()或order_by()查詢的字段設(shè)置db_index=True。ForeignKey字段或帶unique=True的字段隱式的創(chuàng)建了索引。你也可以使用Meta.index_together為多個字段創(chuàng)建索引。
我們會覆寫Image模型的save()方法,根據(jù)title字段的值自動生成slug字段。在Image模型中導(dǎo)入slugify()函數(shù),并添加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().save(*args, **kwargs)
沒有提供別名(slug)時,我們根據(jù)給定的標(biāo)題,使用Django提供的slufigy()函數(shù)自動生成圖片的slug字段。然后保存對象。我們?yōu)閳D片自動生成別名,所以用戶不需要為每張圖片輸入slug字段。
5.1.2 創(chuàng)建多對多的關(guān)系
我們將會在Image模型中添加另一個字段,用于存儲喜歡這張圖片的用戶。這種情況下,我們需要一個多對多的關(guān)系,因?yàn)橐粋€用戶可能喜歡多張圖片,每張圖片也可能被多個用戶喜歡。
添加以下代碼到Image模型中:
users_like = models.ManyToManyField(settings.AUTH_USER_MODEL,
related_name='images_liked',
blank=True)
當(dāng)你定義一個ManyToManyFeild時,Django會使用兩個模型的主鍵創(chuàng)建一張中介連接表。ManyToManyFeild可以在兩個關(guān)聯(lián)模型的任何一個中定義。
與ForeignKey字段一樣,ManyToManyFeild允許我們命名從關(guān)聯(lián)對象到這個對象的逆向關(guān)系。ManyToManyFeild字段提供了一個多對多管理器,允許我們檢索關(guān)聯(lián)的對象,比如:image.users_like.all(),或者從user對象檢索:user.images_liked.all()。
打開命令行,執(zhí)行以下命令創(chuàng)建初始數(shù)據(jù)庫遷移:
python manage.py makemigrations images
你會看到以下輸出:
Migrations for 'images':
images/migrations/0001_initial.py
- Create model Image
現(xiàn)在運(yùn)行這條命令,讓遷移生效:
python manage.py migrate images
你會看到包括這一行的輸出:
Applying images.0001_initial... OK
現(xiàn)在Image模型已經(jīng)同步到數(shù)據(jù)庫中。
5.1.3 在管理站點(diǎn)注冊圖片模型
編輯images應(yīng)用的admin.py文件,在管理站點(diǎn)注冊Image模型,如下所示:
from django.contrib import admin
from .models import Image
class ImageAdmin(admin.ModelAdmin):
list_display = ('title', 'slug', 'image', 'created')
list_filter = ['created']
admin.site.register(Image, ImageAdmin)
執(zhí)行python manage.py runserver命令啟動開發(fā)服務(wù)器。在瀏覽器中打開http://127.0.0.1:8000/amdin/,可以看到Image模型已經(jīng)在管理站點(diǎn)注冊,如下圖所示:

5.2 從其它網(wǎng)站上傳內(nèi)容
我們將允許用戶標(biāo)記從其它網(wǎng)站找到的圖片。用戶將提供圖片的URL,一個標(biāo)題和一個可選的描述。我們的應(yīng)用會下載圖片,并在數(shù)據(jù)庫中創(chuàng)建一個新的Image對象。
我們從構(gòu)建一個提交新圖片的表單開始。在images應(yīng)用目錄中創(chuàng)建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,
}
正如你所看到的,這是一個從Image模型創(chuàng)建的ModelForm表單,只包括title,url和description字段。用戶不會直接在表單中輸入圖片URL。而是使用一個JavaScript工具,從其它網(wǎng)站選擇一張圖片,我們的表單接收這張圖片的URL作為參數(shù)。我們用HiddenInput組件覆蓋了url字段的默認(rèn)組件。這個組件渲染為帶有type="hidden"屬性的HTML輸入元素。使用這個組件是因?yàn)槲覀儾幌胗脩艨匆娺@個字段。
5.2.1 清理表單字段
為了確認(rèn)提供的圖片URL是有效的,我們會檢查文件名是否以.jpg或.jpeg擴(kuò)展名結(jié)尾,只允許JPG文件。Django允許你通過形如clean_<filedname>()的方法,定義表單方法來清理指定字段。如果存在這個方法,它會在調(diào)用表單實(shí)例的is_valid()方法時執(zhí)行。在清理方法中,你可以修改字段的值,或者需要時,為這個字段拋出任何驗(yàn)證錯誤。在ImageCreateForm中添加以下方法:
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獲得文件擴(kuò)展名,并檢查是否為合法的擴(kuò)展名。如果不是,拋出
ValidationError,表單實(shí)例不會通過驗(yàn)證。我們執(zhí)行了一個非常簡單的驗(yàn)證。你可以使用更好的方法驗(yàn)證給定的URL是否提供了有效的圖片。
除了驗(yàn)證給定的URL,我們還需要下載并保存圖片。比如,我們可以用處理這個表單的視圖來下載圖片文件。不過我們會使用更通用的方式:覆寫模型表單的save()方法,在每次保存表單時執(zhí)行這個任務(wù)。
5.2.2 覆寫ModelForm的save()方法
你知道,ModelForm提供了save()方法,用于把當(dāng)前模型的實(shí)例保存到數(shù)據(jù)庫中,并返回該對象。這個方法接收一個commit布爾參數(shù),允許你指定是否把該對象存儲到數(shù)據(jù)庫中。如果commit為False,save()方法會返回模型的實(shí)例,但不會保存到數(shù)據(jù)庫中。我們會覆寫表單的save()方法下載指定的圖片,然后保存。
在forms.py文件頂部添加以下導(dǎo)入:
from urllib 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().save(commit=False)
image_url = self.cleaned_data['url']
image_name = '{}.{}'.format(slugify(image.title), image_url.rsplit('.', 1)[1].lower())
#download image from the given URL
response = request.urlopen(image_url)
image.image.save(image_name, ContentFile(response.read()), save=False)
if commit:
image.save()
return image
我們覆寫了save()方法,保留了ModelForm必需的參數(shù)。這段代碼完成以下操作:
- 我們用
commit=False調(diào)用表單的save()方法,創(chuàng)建了一個新的image實(shí)例。 - 我們從表單的
cleaned_data字典中獲得URL。 - 我們用
image的標(biāo)題別名和原始文件擴(kuò)展名的組合生成圖片名。 - 我們使用
urllib模塊下載圖片,然后調(diào)用image字段的save()方法,并傳遞一個ContentFile對象,這個對象由下載的文件內(nèi)容實(shí)例化。這樣就把文件保存到項(xiàng)目的media目錄中了。我們還傳遞了save=False參數(shù),避免把對象保存到數(shù)據(jù)庫中。 - 為了與被我們覆寫的
save()方法保持一致的行為,只有在commit參數(shù)為True時,才把表單保存到數(shù)據(jù)庫中。
現(xiàn)在我們需要一個處理表單的視圖。編輯images應(yīng)用的views.py文件,添加以下代碼:
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import message
from .forms import ImageCreateForm
@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()
message.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})
為了阻止未認(rèn)證用戶訪問,我們在image_create視圖上添加了login_required裝飾器。這個視圖是這樣工作的:
- 我們期望通過
GET請求獲得創(chuàng)建表單實(shí)例的初始數(shù)據(jù)。數(shù)據(jù)由其它網(wǎng)站的圖片url和title屬性組成,這個數(shù)據(jù)由我們之后會創(chuàng)建的JavaScript工具提供。現(xiàn)在我們假設(shè)初始的時候有數(shù)據(jù)。 - 如果提交了表單,我們檢查表單是否有效。如果有效,我們創(chuàng)建一個新的
Image實(shí)例,但我們通過傳遞commit=False來阻止對象保存到數(shù)據(jù)庫中。 - 我們把當(dāng)前對象賦值給新的
image對象。這樣就知道每張圖片是誰上傳的。 - 我們把圖片對象保存到數(shù)據(jù)庫中。
- 最后,我們用Django消息框架創(chuàng)建一條成功消息,并重定向到新圖片的標(biāo)準(zhǔn)URL。我們還沒有實(shí)現(xiàn)
Image模型的get_absolute_url()方法,我們會馬上完成這個工作。
在images應(yīng)用中創(chuàng)建urls.py文件,添加以下代碼:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^create/$', views.image_create, name='create'),
]
編輯項(xiàng)目的主urls.py文件,引入我們剛創(chuàng)建的images應(yīng)用的模式:
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^account/', include('account.urls')),
url(r'^images/', include('images.urls', namespace='images')),
]
最近,你需要創(chuàng)建模板來渲染表單。在images應(yīng)用目錄中創(chuàng)建以下目錄結(jié)構(gòu):
templates/
images/
image/
create.html
編輯create.hmtl文件,添加以下代碼:
{% extends "base.html" %}
{% block title %}Bookmark an image{% endblock %}
{% block content %}
<h1>Bookmark an image</h1>

<form action="." method=POST>
{{ form.as_p }}
{% csrf_token %}
<input type="submit" value="Bookmark it!">
</form>
{% endblock %}
現(xiàn)在在瀏覽器中打開http://127.0.0.1:8000/images/create/?title=...&url=...,其中包括title和url參數(shù),后者是現(xiàn)有的JPG圖片的URL。
例如,你可以使用以下URL:
http://127.0.0.1:8000/images/create/?title=%20Django%20and%20Duke&url=http%3A%2F%2Fmvimg2.meitudata.com%2F56d7967dd02951453.jpg
你會看到帶一張預(yù)覽圖片的表單,如下所示:

添加描述并點(diǎn)擊Bookmark it!按鈕。一個新的Image對象會保存到數(shù)據(jù)庫中。你會得到一個錯誤,顯示Image對象沒有get_absolute_url()方法?,F(xiàn)在不用擔(dān)心,我們之后會添加這個方法。在瀏覽器打開http://127.0.0.1:8000/admin/images/image/,確認(rèn)新的圖片對象已經(jīng)保存了。
5.2.3 用jQuery構(gòu)建書簽工具
書簽工具是保存在web瀏覽器中的書簽,其中包括JavaScript代碼,可以擴(kuò)展瀏覽器的功能。當(dāng)你點(diǎn)擊書簽,JavaScript代碼會在瀏覽器正在顯示的網(wǎng)頁中執(zhí)行。這對構(gòu)建與其它網(wǎng)站交互的工具非常有用。
某些在線服務(wù)(比如Pinterest)實(shí)現(xiàn)了自己的書簽工具,讓用戶可以在自己的平臺分享其它網(wǎng)站的內(nèi)容。我們會創(chuàng)建一個書簽工具,讓用戶以類似的方式在我們的網(wǎng)站中分享其它網(wǎng)站的圖片。
我們將使用jQuery構(gòu)建書簽工具。jQuery是一個非常流行的JavaScript框架,可以快速開發(fā)客戶端的功能。你可以在官方網(wǎng)站進(jìn)一步了解jQuery。
以下是用戶如何在瀏覽器中添加書簽工具,并使用它:
- 用戶從你的網(wǎng)站中拖拽一個鏈接到瀏覽器的書簽中。該鏈接在
href屬性中包含JavaScript代碼。這段代碼會存儲在書簽中。 - 用戶導(dǎo)航到任意網(wǎng)站,并點(diǎn)擊該書簽。該書簽的JavaScript代碼會執(zhí)行。
因?yàn)镴avaScript代碼會以書簽的形式存儲,所以之后你不能更新它。這是一個顯著的缺點(diǎn),但你可以實(shí)現(xiàn)一個簡單的啟動腳本解決這個問題。該腳本從URL中加載實(shí)際的JavaScript書簽工具。用戶會以書簽的形式保存啟動腳本,這樣你就可以在任何時候更新書簽工具的代碼了。我們將采用這種方式構(gòu)建書簽工具。讓我們開始吧。
在images/templates/中創(chuàng)建一個bookmarklet_launcher.js模板。這是啟動腳本,并添加以下代碼:
(function() {
if (window.myBookmarklet !== underfined) {
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變量,來判斷書簽工具是否加載。這樣我們就避免了用戶重復(fù)點(diǎn)擊書簽時多次加載它。如果沒有定義myBookmarklet,我們通過在文檔中添加<script>元素,來加載另一個JavaScript文件。這個scrip標(biāo)簽加載bookmarklet.js腳本,并用一個隨機(jī)參數(shù)作為變量,防止從瀏覽器緩存中加載文件。
真正的書簽工具代碼位于靜態(tài)文件bookmarklet.js中。這樣就能更新我們的書簽工具代碼,而不用要求用戶更新之前在瀏覽器中添加的書簽。讓我們把書簽啟動器添加到儀表盤頁面,這樣用戶就可以拷貝到他們的書簽中。
編輯account應(yīng)用中的account/dashboard.html模板,如下所示:
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block content %}
<h1>Dashboard</h1>
{% with total_images_created=request.user.images_created.count %}
<p>
Welcome to your dashboard.
You have bookmarked {{ total_images_created }} image{{ total_images_created|pluralize }}.
</p>
{% endwith %}
<p>
Drag the following button to your bookmarks toolbar to bookmark images from other websites
→ <a href="javascript:{% include "bookmarklet_launcher.js" %}" class="button">Bookmark it</a>
</p>
<p>
You can also <a href="{% url "edit" %}">edit your profile</a>
or <a href="{% url "password_change" %}">change your password</a>.
<p>
{% endblock %}
儀表盤顯示用戶現(xiàn)在添加書簽的圖片總數(shù)。我們使用{% with %}模板標(biāo)簽設(shè)置當(dāng)前用戶添加書簽的圖片總數(shù)為一個變量。我們還包括了一個帶href屬性的鏈接,該屬性指向書簽工具啟動腳本。我們從bookmarklet_launcher.js模板中引入這段JavaScript代碼。
在瀏覽器中打開http://127.0.0.1:8000/account/,你會看下如下所示的頁面:

拖拽Bookmark it!鏈接到瀏覽器的書簽工具欄中。
現(xiàn)在,在images應(yīng)用目錄中創(chuàng)建以下目錄和文件:
static/
js/
bookmarklet.js
你在本章示例代碼的images應(yīng)用目錄下可以找到static/css目錄??截?code>css/目錄到你代碼的static/目錄中。css/bookmarklet.css文件為我們的JavaScript書簽工具提供了樣式。
編輯bookmarklet.js靜態(tài)文件,添加以下JavaScript代碼:
(function() {
var jquery_version = '2.1.4';
var site_url = 'http://127.0.0.1:8000/';
var static_url = site_url + 'static/';
var min_width = 100;
var min_height = 100;
function bookmarklet(msg) {
// Here goes our bookmarklet code
};
// Check if jQuery is loaded
if(typeof window.jQuery != 'undefined') {
bookmarklet();
} else {
// Check for conflicts
var conflict = typeof window.$ != 'undefined';
// Create the script and point to Google API
var script = document.createElement('script');
script.setAttribute('src',
'http://ajax.googleapis.com/ajax/libs/jquery/' +
jquery_version + '/jquery.min.js');
// Add the script to the 'head' for processing
document.getElementsByTagName('head')[0].appendChild(script);
// Create a way to wait until script loading
var attempts = 15;
(function(){
// Check again if jQuery is undefined
if (typeof window.jQuery == 'undefined') {
if(--attempts > 0) {
// Calls himself in a few milliseconds
window.setTimeout(arguments.callee, 250);
} else {
// Too much attempts to load, send error
alert('An error ocurred while loading jQuery')
}
} else {
bookmarklet();
}
})();
}
})()
這是主要的jQuery加載腳本。如果當(dāng)前網(wǎng)站已經(jīng)加載了jQuery,那么它會使用jQuery;否則會從Google CDN中加載jQuery。加載jQuery后,它會執(zhí)行包含書簽工具代碼的bookmarklet()函數(shù)。我們還在文件頂部設(shè)置了幾個變量:
-
jquery_version:要加載的jQuery版本 -
site_url和static_url:我們網(wǎng)站的主URL和各個靜態(tài)文件的主URL -
min_width和min_height:我們的書簽工具在網(wǎng)站中查找的圖片的最小寬度和高度(單位是像素)
現(xiàn)在讓我們實(shí)現(xiàn)bookmarklet()函數(shù),如下所示:
function bookmarklet(msg) {
// load CSS
var css = jQuery('<link>');
css.attr({
rel: 'stylesheet',
type: 'text/css',
href: static_url + 'css/bookmarklet.css?r=' + Math.floor(Math.random()*99999999999999999999)
});
jQuery('head').append(css);
// load HTML
box_html = '<div id="bookmarklet"><a href="#" id="close">×</a><h1>Select an image to bookmark:</h1><div class="images"></div></div>';
jQuery('body').append(box_html);
// close event
jQuery('#bookmarklet #close').click(function() {
jQuery('#bookmarklet').remove();
});
};
這段代碼是這樣工作的:
- 為了避免瀏覽器緩存,我們使用一個隨機(jī)數(shù)作為參數(shù),來加載
bookmarklet.css樣式表。 - 我們添加了定制的HTML到當(dāng)前網(wǎng)站的
<body>元素中。它由一個<div>元素組成,里面會包括在當(dāng)前網(wǎng)頁中找到的圖片。 - 我們添加了一個事件。當(dāng)用戶點(diǎn)擊我們的HTML塊中的關(guān)閉鏈接時,我們會從文檔中移除我們的HTML。我們使用
#bookmarklet #close選擇器查找ID為close,父元素ID為bookmarklet的HTML元素。一個jQuery選擇器允許你查找多個HTML元素。一個jQuery選擇器返回指定CSS選擇器找到的所有元素。你可以在這里找到j(luò)Query選擇器列表。
加載CSS樣式和書簽工具需要的HTML代碼后,我們需要找到網(wǎng)站中的圖片。在bookmarklet()函數(shù)底部添加以下JavaScript代碼:
// find images and display them
jQuery.each(jQuery('img[src$="jpg"]'), function(index, image) {
if (jQuery(image).width() >= min_width && jQuery(image).height() >= min_height) {
image_url = jQuery(image).attr('src');
jQuery('#bookmarklet .images').append('<a href="#"></a>');
}
});
這段代碼使用img[src$="jpg"]選擇器查找所有src屬性以jpg結(jié)尾的<img>元素。這意味著我們查找當(dāng)前網(wǎng)頁中顯示的所有JPG圖片。我們使用jQuery的each()方法迭代結(jié)果。我們把尺寸大于min_width和min_height變量的圖片添加到<div class="images">容器中。
現(xiàn)在,HTML容器中包括可以添加標(biāo)簽圖片。我們希望用戶點(diǎn)擊需要的圖片,并為它添加標(biāo)簽。在bookmarklet()函數(shù)底部添加以下代碼:
// when an image is selected open URL with it
jQuery('#bookmarklet .images a').click(function(e) {
selected_image = jQuery(this).children('img').attr('src');
// hide bookmarklet
jQuery('#bookmarklet').hide();
// open new window to submit the image
window.open(site_url + 'images/create/?url='
+ encodeURIComponent(selected_image)
+ '&title='
+ encodeURIComponent(jQuery('title').text()),
'_blank');
});
這段代碼完成以下工作:
- 我們綁定一個
click()事件到圖片的鏈接元素。 - 當(dāng)用戶點(diǎn)擊一張圖片時,我們設(shè)置一個新變量——
selected_image,其中包含了選中圖片的URL。 - 我們隱藏書簽工具,并在我們網(wǎng)站中打開一個新的瀏覽器窗口為新圖片添加標(biāo)簽。傳遞網(wǎng)站的
<title>元素和選中的圖片URL作為GET參數(shù)。
在瀏覽器中隨便打開一個網(wǎng)址(比如http://z.cn),并點(diǎn)擊你的書簽工具。你會看到一個新的白色框出現(xiàn)在網(wǎng)頁上,其中顯示所有尺寸大于100*100px的JPG圖片,如下圖所示:

因?yàn)槲覀兪褂玫氖荄jango開發(fā)服務(wù)器,它通過HTTP提供頁面,所以出于瀏覽器安全限制,書簽工具不能在HTTPS網(wǎng)站上工作。
如果你點(diǎn)擊一張圖片,會重定向到圖片創(chuàng)建頁面,并傳遞網(wǎng)站標(biāo)題和選中圖片的URL作為GET參數(shù):

恭喜你!這是你的第一個JavaScript書簽工具,并且完全集成到你的Django項(xiàng)目中了。
5.3 為圖片創(chuàng)建詳情視圖
我們將創(chuàng)建一個簡單的詳情視圖,用于顯示一張保存在我們網(wǎng)站的圖片。打開images應(yīng)用的views.py文件,添加以下代碼:
from django.shortcuts import get_object_or_404
from .models import Image
def image_detail(request, id, slug):
image = get_object_or_404(Image, id=id, slug=slug)
return render(request, 'images/image/detail.html', {'section': 'images', 'image': image})
這是顯示一張圖片的簡單視圖。編輯images應(yīng)用的urls.py文件,添加以下URL模式:
url(r'^detail/(?P<id>\d+)/(?P<slug>[-\w]+)/$', views.image_detail, name='detail'),
編輯images應(yīng)用的models.py文件,在Image模型中添加get_absolute_url()方法,如下所示:
from django.core.urlresolvers import reverse
class Image(models.Model):
# ...
def get_absolute_url(self):
return reverse('image:detail', args=[self.id, self.slug])
記住,為對象提供標(biāo)準(zhǔn)URL的通用方式是在模型中添加get_absolute_url()方法。
最后,在images應(yīng)用的/images/image/模板目錄中創(chuàng)建detail.html模板,添加以下代碼:
{% extends "base.html" %}
{% block title %}{{ image.title }}{% endblock %}
{% block content %}
<h1>{{ image.title }}</h1>

{% with total_likes=image.users_like.count %}
<div class="image-info">
<div>
<span class="count">
{{ total_likes }} like{{ total_likes|pluralize }}
</span>
</div>
{{ image.description|linebreaks }}
</div>
<div class="image-likes">
{% for user in image.users_like.all %}
<div>

<p>{{ user.first.name }}</p>
</div>
{% empty %}
Nobody likes this image yet.
{% endfor %}
</div>
{% endwith %}
{% endblock %}
這是顯示一張?zhí)砑恿藰?biāo)簽的圖片的詳情模板。我們使用{% with %}標(biāo)簽存儲QuerySet的結(jié)果,這個QuerySet在total_likes變量中統(tǒng)計(jì)所有喜歡這張圖片的用戶。這樣就能避免計(jì)算同一個QuerySet兩次。我們還包括了圖片的描述,并迭代image.users_like.all()來顯示所有喜歡這張圖片的用戶。
使用
{% width %}模板標(biāo)簽可以有效地阻止Django多次計(jì)算QuerySet。
現(xiàn)在用書簽工具標(biāo)記一張新圖片。當(dāng)你上傳圖片后,會重定向到圖片詳情頁面。該頁面會包括一條成功消息,如下圖所示:

5.4 使用sorl-thumbnail創(chuàng)建縮略圖
現(xiàn)在,我們在詳情頁面顯示原圖,但是不同圖片的尺寸各不相同。同時,有些圖片的源文件可能很大,需要很長時間才能加載。用統(tǒng)一的方式顯示優(yōu)化圖像的最好方法是生成縮略圖。因此我們將使用一個名為sorl-thumbnail的Django應(yīng)用。
打開終端,執(zhí)行以下命令安裝sorl-thumbnail:
pip install sorl-thumbnail
編輯bookmarks項(xiàng)目的settings.py文件,把sorl.thumbnail添加到INSTALLED_APPS設(shè)置中:
接著執(zhí)行以下命令同步應(yīng)用和數(shù)據(jù)庫:
python manage.py makemigrations thumbnail
python manage.py migrate
sorl-thumbnail應(yīng)用提供了多種定義圖片縮略圖的方式。它提供了{% thumbnail %}模板標(biāo)簽,可以在模板中生成縮略圖;如果你想在模型中定義縮略圖,還提供自定義的ImageField。我們將使用模板標(biāo)簽的方式。編輯images/image/detail.html模板,把這一行代碼:

替換為:
{% load thumbnail %}
{% thumbnail image.image "300" as im %}
<a href="{{ image.image.url }}">

</a>
{% endthumbnail %}
我們在這里定義了一張固定寬度為300像素的縮略圖。用戶第一次加載這個頁面時,會創(chuàng)建一張縮略圖。之后的請求會使用生成的縮略圖。用python manage.py runserver啟動開發(fā)服務(wù)器后,訪問一張已存在的圖片詳情頁。此時會生成一張縮略圖并顯示。
sorl-thumbmail應(yīng)用提供了一些選項(xiàng)來定制縮略圖,包括裁剪算法和不同的效果。如果你在生成縮略圖時遇到問題,可以在設(shè)置中添加THUMBNAIL_DEBUG=True,就能查看調(diào)試信息。你可以在這里閱讀sorl-thumbnail應(yīng)用的完整文檔。
5.5 使用JQuery添加AJAX操作
現(xiàn)在我們將向應(yīng)用中添加AJAX操作。AJAX是Asynchronous JavaScript and XML的縮寫。這個術(shù)語包括一組異步HTTP請求技術(shù)。它包括從服務(wù)器異步發(fā)送和接收數(shù)據(jù),而不用加載整個頁面。盡管名字中有XML,但它不是必需的。你可以使用其它格式發(fā)送或接收數(shù)據(jù),比如JSON,HTML或者普通文本。
我們將會在圖片詳情頁面添加一個鏈接,用戶點(diǎn)擊鏈接表示喜歡這張圖片。我們會用AJAX執(zhí)行這個操作,避免加載整個頁面。首先,我們需要創(chuàng)建一個視圖,讓用戶喜歡或不喜歡圖片。編輯images應(yīng)用的views.py文件,添加以下代碼:
from django.http import JsonResponse
from django.views.decorators.http import require_POST
@login_required
@require_POST
def image_like(request):
image_id = request.POST.get('id')
action = request.POST.get('action')
if image_id and action:
try:
image = Image.objects.get(id=image_id)
if action == 'like':
image.users_like.add(request.user)
else:
image.users_like.remove(request.user)
return JsonResponse({'status': 'ok'})
except:
pass
return JsonResponse({'status': 'ko'})
我們在這個視圖上使用了兩個裝飾器。login_required裝飾阻止沒有登錄的用戶訪問這個視圖;如果HTTP請求不是通過POST完成,required_ POST裝飾器返回一個HttpResponseNotAllowed對象(狀態(tài)碼為405)。這樣就只允許POST請求訪問這個視圖。Django還提供了required_GET裝飾器,只允許GET請求,以及required_http_methods裝飾器,你可以把允許的方法列表作為參數(shù)傳遞。
我們在這個視圖中使用了兩個POST參數(shù):
-
image_id:用戶執(zhí)行操作的圖片對象的ID。 -
action:用戶希望執(zhí)行的操作,我們假設(shè)為like或unlike字符串。
我們使用Django為Image模型的users_like多對多字段提供的管理器的add()或remove()方法從關(guān)系中添加或移除對象。調(diào)用add()方法時,如果傳遞一個已經(jīng)存在關(guān)聯(lián)對象集中的對象,不會重復(fù)添加這個對象;同樣,調(diào)用remove()方法時,如果傳遞一個不存在關(guān)聯(lián)對象集中的對象,不會執(zhí)行任何操作。另一個多對多管理器方法是clear(),會從關(guān)聯(lián)對象集中移除所有對象。
最后,我們使用Django提供的JsonResponse類返回一個HTTP響應(yīng),其中內(nèi)容類型為application/json,它會把給定對象轉(zhuǎn)換為JSON輸出。
編輯images應(yīng)用的urls.py文件,添加以下URL模式:
url(r'^like/$', views.image_like, name='like'),
5.5.1 加載jQuery
我們需要添加AJAX功能到圖片詳情模板中。為了在模板中使用jQuery,首先在項(xiàng)目的base.html模板中引入它。編輯account應(yīng)用的base.html模板,在</body>標(biāo)簽之前添加以下代碼:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
$(document).ready(function() {
{% block domready %}
{% endblock %}
});
</script>
我們從Google加載jQuery框架,Google在高速內(nèi)容分發(fā)網(wǎng)絡(luò)中托管了流行的JavaScript框架。你也可以從http://jquery.com/下載jQuery,然后把它添加到應(yīng)用的static目錄中。
我們添加一個<script>標(biāo)簽來包括JavaScript代碼。$(document).ready()是一個jQuery函數(shù),參數(shù)是一個處理函數(shù),當(dāng)DOM層次構(gòu)造完成后,會執(zhí)行這個處理函數(shù)。DOM是Document Object Model的縮寫。DOM是網(wǎng)頁加載時瀏覽器創(chuàng)建的一個樹對象。在這個函數(shù)中包括我們的代碼,可以確保我們要交互的HTML元素都已經(jīng)在DOM中加載完成。我們的代碼只有在DOM準(zhǔn)備就緒后才執(zhí)行。
在文檔準(zhǔn)備就緒后的處理函數(shù)中,我們包括了一個名為domready的Django模板塊,在擴(kuò)展了基礎(chǔ)模板的模板中可以包括特定的JavaScript。
不要將JavaScript代碼和Django模板標(biāo)簽搞混了。Django模板語言在服務(wù)端渲染,輸出為最終的HTML文檔;JavaScript在客戶端執(zhí)行。某些情況下,使用Django動態(tài)生成JavaScript非常有用。
本章的示例中,我們在Django模板中引入了JavaScript代碼。引入JavaScript代碼更好的方式是加載作為靜態(tài)文件的
.js文件,尤其當(dāng)它們是代碼量很大的腳本時。
5.5.2 AJAX請求的跨站請求偽造
你已經(jīng)在第二章中學(xué)習(xí)了跨站點(diǎn)請求偽造。在激活了CSRF保護(hù)的情況下,Django會檢查所有POST請求的CSRF令牌。當(dāng)你提交表單時,可以使用{% csrf_token %}模板標(biāo)簽發(fā)送帶令牌的表單。但是,對于每個POST請求,AJAX請求都將CSRF令牌作為POST數(shù)據(jù)傳遞是不方便的。因此,Django允許你在AJAX請求中,用CSRF令牌的值設(shè)置一個自定義的X-CSRFToken頭。這允許你用jQuery或其它任何JavaScript庫,在每次請求中自動設(shè)置X-CSRFToken頭。
要在所有請求中包括令牌,你需要:
- 從
csrftokencookie中檢索CSRF令牌,如果激活了CSRF,它就會被設(shè)置。 - 在AJAX請求中,使用
X-CSRFToken頭發(fā)送令牌。
你可以在這里找到更多關(guān)于CSRF保護(hù)和AJAX的信息。
編輯你在base.html中最后引入的代碼,修改為以下代碼:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="http://cdn.jsdelivr.net/jquery.cookie/1.4.1/jquery.cookie.min.js"></script>
<script>
var csrftoken = $.cookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not required CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
$(document).ready(function() {
{% block domready %}
{% endblock %}
});
</script>
這段代碼完成以下工作:
- 我們從一個公有CDN加載jQuery Cookie插件,因此我們可以與cookies交互。
- 我們讀取
csrftokencookie中的值。 - 我們定義
csrfSafeMethod()函數(shù),檢查HTTP方法是否安全。安全的方法不需要CSRF保護(hù),包括GET,HEAD,OPTIONS和TRACE。 - 我們使用
$.ajaxSetup()設(shè)置jQuery AJAX請求。每個AJAX請求執(zhí)行之前,我們檢查請求方法是否安全,以及當(dāng)前請求是否跨域。如果請求不安全,我們用從cookie中獲取的值設(shè)置X-CSRFToken頭。這個設(shè)置會應(yīng)用到j(luò)Query執(zhí)行的所有AJAX請求。
CSRF令牌會在所有使用不安全的HTTP方法的AJAX請求中引入,比如POST或PUT。
5.5.3 使用jQuery執(zhí)行AJAX請求
編輯images應(yīng)用的images/image/detail.htmlt模板,把這一行代碼:
{% with total_likes=image.users_like.count %}
替換為下面這行:
{% with total_likes=image.users_like.count users_like=image.users_like.all %}
然后修改class為image-info的<div>元素,如下所示:
<div class="image-info">
<div>
<span class="count">
<span class="total">{{ total_likes }}</span>
like{{ total_likes|pluralize }}
</span>
<a href="#" data-id="{{ image.id }}"
data-action="{% if request.user in users_like %}un{% endif %}like" class="like button">
{% if request.user not in users_like %}
Like
{% else %}
Unlike
{% endif %}
</a>
</div>
{{ image.description|linebreaks }}
</div>
首先,我們添加了另一個變量到{% with %}模板標(biāo)簽中,用于存儲image.users_like.all的查詢結(jié)果,避免執(zhí)行兩次查詢。我們顯示喜歡這張圖片的用戶總數(shù),以及一個like/unlike鏈接:我們檢查用戶是否在users_like關(guān)聯(lián)對象集中,根據(jù)當(dāng)前用戶跟這樣圖片的關(guān)系顯示like或unlike。我們在<a>元素中添加了以下屬性:
-
data-id:顯示的圖片的ID。 -
data-action:用戶點(diǎn)擊鏈接時執(zhí)行的操作??赡苁?code>like或unlike。
我們將會在AJAX請求發(fā)送這兩個屬性的值給image_like視圖。當(dāng)用戶點(diǎn)擊like/unlike鏈接時,我們需要在客戶端執(zhí)行以下操作:
- 調(diào)用AJAX視圖,并傳遞圖片ID和action參數(shù)。
- 如果AJAX請求成功,用相反操作(
like/unlike)更新<a>元素的data-action屬性,并相應(yīng)的修改顯示文本。 - 更新顯示的喜歡總數(shù)。
在images/image/detail.html模板底部添加包括以下代碼的domready塊:
{% 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')jQuery選擇器查找HTML文檔中class是like的<a>元素。 - 我們?yōu)?code>click事件定義了一個處理函數(shù)。用戶每次點(diǎn)擊
like/unlike鏈接時,會執(zhí)行這個函數(shù)。 - 在處理函數(shù)內(nèi)部,我們使用
e.preventDefault()阻止<a>元素的默認(rèn)行為。這會阻止鏈接調(diào)轉(zhuǎn)到其它地方。 - 我們使用
$.post()向服務(wù)器執(zhí)行一個異步請求。jQuery還提供了執(zhí)行GET請求的$.get()方法,以及一個底層的$.ajax()方法。 - 我們使用Django的
{% url %}模板標(biāo)簽為AJAX請求構(gòu)建URL。 - 我們構(gòu)建在請求中發(fā)送的
POST參數(shù)字典。我們的Django視圖需要id和action參數(shù)。我們從<a>元素的<data-id>和<data-action>屬性中獲得這兩個值。 - 我們定義了回調(diào)函數(shù),當(dāng)收到HTTP響應(yīng)時,會執(zhí)行這個函數(shù)。它的
data屬性包括響應(yīng)的內(nèi)容。 - 我們訪問收到的
data的status屬性,檢查它是否等于ok。如果返回的data是期望的那樣,我們切換鏈接的data-action屬性和文本。這樣就允許用戶取消這個操作。 - 根據(jù)執(zhí)行的操作,我們在喜歡的總數(shù)上加1或減1。
在瀏覽器中打開之前上傳的圖片詳情頁面。你會看到以下初始的喜歡總數(shù)和LIKE按鈕:

點(diǎn)擊LIKE按鈕。你會看到喜歡總數(shù)加1,并且按鈕的文本變?yōu)?code>UNLIKE:

當(dāng)你點(diǎn)擊UNLIKE按鈕時,會執(zhí)行這個操作,按鈕的文本變回LIKE,總數(shù)也會相應(yīng)的改變。
編寫JavaScript代碼時,尤其是執(zhí)行AJAX請求時,推薦使用Firebug等調(diào)試工具。Firebug是一個Firefox插件,允許你調(diào)試JavaScript代碼,并監(jiān)控CSS和HTML的變化。你可以從這里下載Firebug。其它瀏覽器,比如Chrome或Safari,也有調(diào)試JavaScript的開發(fā)者工具。在這些瀏覽器中,右鍵網(wǎng)頁中的任何一個地方,然后點(diǎn)擊Inspect element訪問開發(fā)者工具。
5.6 為視圖創(chuàng)建自定義裝飾器
我們將限制AJAX視圖只允許由AJAX發(fā)起的請求。Django的Request對象提供一個is_ajax()方法,用于檢查請求是否帶有XMLHttpRequest,也就是說是否是一個AJAX請求。這個值在HTTP頭的HTTP_X_REQUESTED_WITH中設(shè)置,絕大部分JavaScript庫都會在AJAX請求中包括它。
我們將創(chuàng)建一個裝飾器,用于在視圖中檢查HTTP_X_REQUESTED_WITH頭。裝飾器是一個接收另一個函數(shù)為參數(shù)的函數(shù),并且不需要顯式修改作為參數(shù)的函數(shù),就能擴(kuò)展它的行為。如果你忘了裝飾器的概念,你可能需要先閱讀這里。
因?yàn)檫@是一個通用的裝飾器,可以作用于任何視圖,所以我們會在項(xiàng)目中創(chuàng)建一個common包。在bookmarks項(xiàng)目目錄中創(chuàng)建以下目錄和文件:
common/
__init__.py
decrorators.py
編輯decrorators.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函數(shù),如果不是AJAX請求,則返回HttpResponseBadRequest對象(HTTP 400)。否則返回被裝飾的函數(shù)。
現(xiàn)在編輯images應(yīng)用的views.py文件,添加這個裝飾器到image_like視圖中:
from common.decrorators import ajax_required
@ajax_required
@login_required
@require_POST
def image_like(request):
# ...
如果你直接在瀏覽器中訪問http://127.0.0.1:8000/images/like/,你會得到一個HTTP 400的響應(yīng)。
如果你在多個視圖中重復(fù)同樣的驗(yàn)證,則可以為視圖構(gòu)建自定義裝飾器。
5.7 為列表視圖創(chuàng)建AJAX分頁
我們需要在網(wǎng)站中列出所有標(biāo)記過的圖片。我們將使用AJAX分頁構(gòu)建一個無限滾動功能。當(dāng)用戶滾動到頁面底部時,通過自動加載下一頁的結(jié)果實(shí)現(xiàn)無限滾動。
我們將實(shí)現(xiàn)一個圖片列表視圖,同時處理標(biāo)準(zhǔn)瀏覽器請求和包括分頁的AJAX請求。當(dāng)用戶首次加載圖片列表頁面,我們顯示第一頁的圖片。當(dāng)用戶滾動到頁面底部,我們通過AJAX加載下一頁的項(xiàng),并添加到主頁面的底部。
我們用同一個視圖處理標(biāo)準(zhǔn)和AJAX分頁。編輯images應(yīng)用的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 first page
images = paginator.page(1)
except EmptyPage:
if request.is_ajax():
# If the request is AJAX an 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})
我們在這個視圖中創(chuàng)建了一個返回?cái)?shù)據(jù)庫中所有圖片的QuerySet。然后我們構(gòu)造了一個Paginator對象來分頁查詢結(jié)果,每頁顯示八張圖片。如果請求的頁數(shù)超出范圍,則拋出EmptyPage異常。這種情況下,如果是通過AJAX發(fā)送請求,則返回一個空的HttpResponse對象,幫助我們在客戶端停止AJAX分頁。我們把結(jié)果渲染到兩個不同的模板中:
- 對于AJAX請求,我們渲染
list_ajax.html模板。該模板只包括請求頁的圖片。 - 對于標(biāo)準(zhǔn)請求,我們渲染
list.html模板。該模板繼承自base.html模板,并顯示整個頁面,同時還包括list_ajax.html模板,用來引入圖片列表。
編輯images應(yīng)用的urls.py文件,添加以下URL模式:
url(r'^$', views.image_list, name='list'),
最后,我們需要創(chuàng)建上面提到的模板。在images/image/模板目錄中創(chuàng)建list_ajax.html模板,添加以下代碼:
{% load thumbnail %}
{% for image in images %}
<div class="image">
<a href="{{ image.get_absolute_url }}">
{% thumbnail image.image "300*300" crop="100%" as im %}
<a href="{{ image.get_absolute url }}">

</a>
{% endthumbnail %}
</a>
<div class="info">
<a href="{{ image.get_absolute_url }}" class="title">
{{ image.title }}
</a>
</div>
</div>s
{% endfor %}
這個模板顯示圖片的列表。我們將用它返回AJAX請求的結(jié)果。在同一個目錄下創(chuàng)建list.html模板,添加以下代碼:
{% extends "base.html" %}
{% block title %}Images bookmarked{% endblock %}
{% block content %}
<h1>Images bookmarked</h1>
<div id="image-list">
{% include "images/image/list_ajax.html" %}
</div>
{% endblock %}
列表模板繼承自base.html模板。為了避免重復(fù)代碼,我們引入了list_ajax.html模板來顯示圖片。list.html模板會包括JavaScript代碼,當(dāng)用戶滾動到頁面底部時,負(fù)責(zé)加載額外的頁面。
在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 %}
這段代碼提供了無限滾動功能。我們在base.html模板中定義的domready塊中引入了JavaScript代碼。這段代碼是這樣工作的:
- 我們定義了以下變量:
-
page:存儲當(dāng)前頁碼。 -
empty_page:讓我們知道是否到了最后一頁,如果是則接收一個空的頁面。只要我們得到一個空的頁面,就會停止發(fā)送額外的AJAX請求,因?yàn)槲覀兗僭O(shè)此時沒有結(jié)果了。 -
block_request:正在處理AJAX請求時,阻止發(fā)送另一個請求。
- 我們使用
$(window).scroll()捕獲滾動事件,并為它定義一個處理函數(shù)。 - 我們計(jì)算邊距變量來獲得文檔總高度和窗口高度之間的差值,這是用戶滾動的剩余內(nèi)容的高度。我們從結(jié)果中減去200,因此,當(dāng)用戶距離頁面底部小于200像素時,我們會加載下一頁。
- 如果沒有執(zhí)行其它AJAX請求(
block_request必須為false),并且用戶沒有獲得最后一頁的結(jié)果時(empty_page也為false),我們才發(fā)送AJAX請求。 - 我們設(shè)置
block_request為true,避免滾動事件觸發(fā)額外的AJAX請求,同時給page計(jì)算器加1來獲取下一頁。 - 我們使用
$.get()執(zhí)行AJAX GET請求,然后在名為data的變量中接收HTML響應(yīng)。這里有兩種情況:
- 響應(yīng)不包括內(nèi)容:我們已經(jīng)到了結(jié)果的末尾,沒有更多頁面需要加載。我們設(shè)置
empty_page為true阻止額外的AJAX請求。 - 響應(yīng)包括內(nèi)容:我們把數(shù)據(jù)添加到id為
image-list的HTML元素中。當(dāng)用戶接近頁面底部時,頁面內(nèi)容會垂直擴(kuò)展附加的結(jié)果。
在瀏覽器中打開http://127.0.0.1:8000/images/,你會看到目前已經(jīng)標(biāo)記過的圖片列表,如下圖所示:

滾動到頁面底部來加載下一頁。確保你用書簽工具標(biāo)記了八張以上圖片,因?yàn)槲覀兠宽擄@示八張圖片。記住,你可以使用Firebug或類似工具追蹤AJAX請求和調(diào)試JavaScript代碼。
最后,編輯account應(yīng)用的base.html模板,為主菜單的Images項(xiàng)添加URL:
<li {% if section == "images" %}class="selected"{% endif %}>
<a href="{% url "images:list" %}">Images</a>
</li>
現(xiàn)在你可以從主菜單中訪問圖片列表。
5.8 總結(jié)
在本章中,我們構(gòu)建了一個JavaScript書簽工具,可以分享其它網(wǎng)站的圖片到我們的網(wǎng)站中。你用jQuery實(shí)現(xiàn)了AJAX視圖,并添加了AJAX分頁。
下一章會教你如何構(gòu)建關(guān)注系統(tǒng)和活動流。你會使用通用關(guān)系(generic relations),信號(signals)和反規(guī)范化(denormalization)。你還會學(xué)習(xí)如何在Django中使用Redis。