《Django By Example》第五章 中文 翻譯 (個人學習,渣翻)


(譯者@ucag注:大家好,我是新來的翻譯,希望大家多多交流。題目還是沿用老傳統。有做的不嚴謹的地方還請大家指出來。)

(審校者@夜夜月注:人多力量大,第五章由@ucag兩天內獨立翻譯完畢)

第五章

在你的網站中分享內容

在上一章中,你為你的網站建立了用戶注冊和認證系統。你學習了如何為用戶創建定制化的個人資料模型以及如何將主流的社交網絡的認證添加進你的網站。
在這一章中,你將學習如何通過創建一個 JavaScript 書簽來從其他的站點分享內容到你的網站,你也將通過使用 jQuery 在你的項目中實現一些 AJAX 特性。
這一章涵蓋了以下幾點:

  • 創建一個many-to-many(多對多)關系
  • 定制表單(form)的行為
  • 在 Django 中使用 jQuery
  • 創建一個 jQuery 書簽
  • 通過使用 sorl-thumbnail 來生成縮略圖
  • 實現 AJAX 視圖(views)並且使這些視圖(views)和 jQuery 融合
  • 為視圖(views)創建定制化的裝飾器 (decorators)
  • 創建 AJAX 分頁

建立一個能為圖片打標簽的網站

我們將允許用戶可以在我們網站中分享他們在其他網站發現的圖片,並且他們還可以為這些圖片打上標簽。為了達到這個目的,我們將要做以下幾個任務:

  • 定義一個模型來儲存圖片以及圖片的信息
  • 新建一個表單(form)和視圖(view)來控制圖片的上傳
  • 為用戶創建一個可以上傳他們在其他網站發現的圖片的系統

首先,通過以下命令在你的 bookmarks 項目中新建一個應用:

django-admin startapp images

像如下所示一樣在你的 settings.py 文件中 INSTALED_APPS 設置項下添加 'images' :

INSTALLED_APPS = [
    # ... 
    'images',
]

現在Django知道我們的新應用已經被激活了。

創建圖像模型

編輯 images 應用中的 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.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 對象。這是一個 ForeignKey字段 (譯者注:外鍵,即一對多字段),因為它指定了一個一對多關系: 一個用戶可以 post 多張圖片, 但是每張圖片只能由一個用戶上傳
  • title: 圖片的標題
  • slug: 一個只包含字母、數字、下划線、和連字符的標簽, 用於創建優美的 搜索引擎友好(SEO-friendly)的 URL(譯者注:slug 這個詞在中文沒有很好的對應翻譯,所以就請大家記住“slug 表示的是只有字母、數字、下划線和連字符的標簽”。如果有仔細看過 Django 官方文檔的讀者就會知道: slug 是一個新聞術語, 而 Django 的開發目的也是為了更好的編輯新聞, 所以這里就不難理解為什么 Django 中會出現 slug 字段了)
  • url: 這張圖片的源 URL
  • image: 圖片文件
  • description: 一個可選的圖片描述字段
  • created: 用於表明一個對象在數據庫中創建時的時間和日期。由於我們使用了auto_now_add ,當對象被創建時候時間和日期將會被自動設置,我們使用了 db_index=True ,所以 Django 將會在數據庫中為這個字段創建索引

數據庫索引改善了查詢的執行。考慮為這個字段設置 db_index=True 是因為你將要很頻繁地使用 filter()exclude(),order_by() 來執行查詢。ForeignKey 字段或者帶有unique=True的字段表明了一個索引的創建。你也可以使用Meta.index_together來為多個字段創建索引。

我們將要重寫 Image 模型的 save()方法來自動的生成slug字段。這個 slug字段基於title字段的值。像下面這樣導入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)

在這段代碼中,我們使用了 Django 提供的slugify()函數在沒有提供slug字段時根據給定的圖片標題自動生slug,然后,我們保存了這個對象。我們自動生成slug,這樣的話用戶就不用自己輸入slug字段了。

建立多對多關系

我們將要在 Image 模型中再添加一個字段來保存喜歡這張圖片的用戶。因此,我們需要一個多對多關系。因為一個用戶可能喜歡很多張圖片,一張圖片也可能被很多用戶喜歡。
在 Image 模型中添加以下字段:

user_like = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                   related_name='images_liked',
                                   blank=True)

當你定義一個ManyToMany字段時,Django 會用兩張表主鍵(primary key)創建一個中介聯接表(譯者注:就是新建一張普通的表,只是這張表的內容是由多對多關系雙方的主鍵構成的)。ManyToMany字段可以在任意兩個相關聯的表中創建。
ForeignKey字段一樣,ManyToMany字段的related_name屬性使我們可以命名另模型回溯(或者是反查)到本模型對象的關系。ManyToMany字段提供了一個多對多管理器(manager),這個管理器使我們可以回溯相關聯的對象比如:image.users_like.all()或者從一個user中回溯,比如:user.images_liked.all()
打開命令行,執行下面的命令以創建首次遷移:

python manage.py makemigrations images

你能看見以下輸出:

Migrations for 'images':
    0001_initial.py:
        - Create model Image

現在執行這條命令來應用你的遷移:

python manage.py migrate images

你將會看到包含這一行輸出:

Applying images.0001_initial... OK

現在 Image 模型已經在數據庫中同步了。

注冊 Image 模型到管理站點中

編輯 images 應用的 admin.py 文件,然后像下面這樣將 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)

使用命令python manage.py runserver打開開發服務器,在瀏覽器中打開http://127.0.0.1:8000/admin/,可以看到Image模型已經注冊到了管理站點中:

![Django-5-1][1]

從其他網站上傳內容

我們將使用戶可以給從他們在其他網站發現的圖片打上標簽。用戶將要提供圖片的 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,
        }

如你所見,這是一個通過Image模型創建的ModelForm(模型表單),但是這個表單只包含了 title,url,description字段。我們的用戶不會在表單中直接為圖片添加 URL。相反的,他們將會使用一個 JavaScropt 工具來從其他網站中選擇一張圖片然后我們的表單將會以參數的形式接收這張圖片的 URL。我們覆寫 url 字段的默認控件(widget)為一個HiddenInput控件,這個控件將會被渲染為屬性是 type="hidden"的 HTML 元素。使用這個控件是因為我們不想讓用戶看見這個字段。

清潔表單字段

(譯者注:原文標題是:cleaning form fields,在數據處理中有個術語是“清洗數據”,但是這里的清潔還有“使其整潔”的含義,感覺更加符合clean_url這個方法的定位。)
為了驗證提供的圖片 URL 是否合法,我們將檢查以.jpg.jpeg結尾的文件名,來只允許JPG文件的上傳。Django允許你自定義表單方法來清潔特定的字段,通過使用以clean_<fieldname>形式命名的方法來實現。這個方法會在你為一個表單實例執行is_valid()時執行。在清潔方法中,你可以改變字段的值或者為某個特定的字段拋出錯誤當需要的時候,將下面這個方法添加進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字段,這段代碼的工作流程是:

    1. 我們從表單實例的cleaned_data字典中獲取了url字段的值
    1. 我們分離了 URL 來獲取文件擴展名,然后檢查它是否為合法擴展名之一。如果它不是一個合法的擴展名,我們就會拋出ValidationError,並且表單也不會被認證。我們執行的是一個非常簡單的認證。你可以使用更好的方法來驗證所給的 URL 是否是一個合法的圖片。
      除了驗證所給的 URL, 我們還需要下載並保存圖片文件。比如,我們可以使用操作表單的視圖來下載圖片。不過,我們將采用一個更加通用的方法 ———— 通過覆寫我們模型表單中save()方法來完成這個任務。

覆寫模型表單中的save()方法

如你所知,ModelForm提供了一個save()方法來保存目前的模型實例到數據庫中,並且返回一個對象。這個方法接受一個布爾參數commit,這個參數允許你指定這個對象是否要被儲存到數據庫中。如果commitFalsesave()方法將會返回一個模型實例但是並不會把這個對象保存到數據庫中。我們將覆寫表單中的save()方法,來下載圖片然后保存它。
將以下的包在foroms.py中的頂部導入:

from urllib import request
from django.core.files.base import ContentFile
from django.utils.text import slugify

save()方法加入ImageCreateForm中:

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())
# 從給定的 URL 中下載圖片
    response = request.urlopen(image_url)
    image.image.save(image_name,
                    ContentFile(response.read()),
                    save=False)
    if commit:
        image.save()
    return image

我們覆寫的save()方法保持了ModelForm中需要的參數、
這段代碼:

  1. 我們通過調用save()方法從表單中新建了一個image對象,並且commit=False
  2. 我們從表單的cleaned_data字典中獲取了 URL
  3. 我們通過結合image的標題 slug 和源文件的擴展名生成了圖片的名字
  4. 我們使用 Python 的 urllib 模塊來下載圖片,然后我們調用save()方法把圖片傳遞給一個ContentFiel對象,這個對象被下載的文件所實例化。這樣,我們就可以將我們的文件保存到項目中的 media 路徑下。我們傳遞了參數comiit=False來避免對象被保存到數據庫中。
  5. 為了保持和我們覆寫的save()方法一樣的行為,我們將在commit參數為Ture時保存表單到數據庫中。

現在我們需要一個新的視圖來控制我們的表單。編輯 iamges 應用的views.py文件,然后將以下代碼添加進去:

from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .forms import ImageCreateForm

@login_required
def image_create(request):
    """
    View for creating an Image using the JavaScript Bookmarklet.
    """
    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裝飾器,來阻止未認證的用戶的連接。這段代碼完成下面的工作:

  1. 我們先從 GET 中獲取初始數據來創建一個表單實例。這個數據由來自外部網站圖片的urltitle屬性構成,並且將由我們等會兒要創建的 JavaScript 工具提供。現在我們只是假設這里有初始數據。
  2. 如果表單被提交我們將檢查它是否合法。如果這個表單是合法的,我們將新建一個Image實例,但是我們通過傳遞commit=False來保證這個對象將不會保存到數據庫中。
  3. 我們將綁定當前用戶(user)到一個新的iamge對象。這樣我們就可以知道是誰上傳了每一張圖片。
  4. 我們把 iamge 對象保存到了數據庫中
  5. 最后,我們使用 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/', include(admin.site.urls)),
    url(r'^account/', include('account.urls')),
    url(r'^images/', include('images.urls', namespace='images')),
]

最后,你需要創建一個模板來渲染你的表單。在你的 images 應用路徑下創建如下路徑結構:

templates/
   images/
       image/
          create.html

編輯新的 create.html 模板然后添加以下代碼進去:

{% extends "base.html" %}

{% block title %}Bookmark an image{% endblock %}

{% block content %}
    <h1>Bookmark an image</h1>
    <img src="{{ request.GET.url }}" class="image-preview">
    <form action="." method="post">
        {{ form.as_p }}
        {% csrf_token %}
        <input type="submit" value="Bookmark it!">
    </form>
{% endblock %}

現在在你的瀏覽器中打開http://127.0.0.1:8000/images/create/?title=...&url=...,記得在 后面傳遞 GET 參數titleurl來提供一個已存在的JPG圖像的 URL 。
舉個例子,你可以使用像下面這樣的 URL:

http://127.0.0.1:8000/images/create/?title=%20Django%20and%20Duke&url=http://upload.wikimedia.org/wikipedia/commons/8/85/Django_Reinhardt_and_Duke_Ellington_%28Gottlieb%29.jpg

你可以看到一個帶有圖片預覽的表單,就像下面這樣:
![Django-5-2][2]
添加描述然后點擊 Bookmark it!按鈕。一個新的 Image對象將會被保存在你的數據庫中。你將會得到一個錯誤,這個錯誤指示說Image模型沒有get_absolute_url()方法。現在先不要擔心這個,我們待會兒將添加這個方法、在你的瀏覽器中打開http://127.0.0.1:8000/admin/images/image/,確定新的圖像對象已經被保存了。

用 jQuery 創建一個書簽

書簽是一個保存在瀏覽器中包含 JavaScript 代碼的標簽,用來拓展瀏覽器功能。當你點擊書簽的時候, JavaScript 代碼會在瀏覽器顯示的網站中被執行。這是一個在和其它網站交互時非常有用的工具。

一些在線服務,比如 Pinterest 實現了他們自己的書簽來讓用戶可以在他們的平台中分享來自其他網站的內容,我們將以同樣的方式創建一個書簽,讓用戶可以在我們的網站中分享來自其他網站的圖片。
我們將使用 jQuery 來創建我們的書簽。 jQuery 是一個流行的 JavaScript 框架, 這個框架允許你快速開發客戶端的功能。你可以在官網中更多的了解 jQuery: http://jquery.com/

你的用戶將會像下面這樣在他們的瀏覽器中添加書簽然后使用它:

  1. 用戶從你的網站中拖拽一個鏈接到他的瀏覽器。這個鏈接在它的href屬性中包含了 JavaScript 代碼。這段代碼將會被儲存到書簽當中。
  2. 用戶訪問任意一個網站,然后點擊這個書簽, 這個書簽的 JavaScript 代碼就被執行了。

由於 JavaScript 代碼將會以書簽的形式被儲存,之后你將不能更新它。這是個很顯著的缺點,但是你可以通過實現一個簡單的激活腳本來解決這個問題,這個腳本從一個 URL 中加載 JavaScript。你的用戶將會以書簽的形式來保存這個激活腳本,這樣你就能在任何時候更新書簽代碼的內容了。我們將會采用這個方法來創建我們的書簽。我們開始吧!
(譯者注:上面這一段似乎有一點難以理解,其實很簡單,就是把 JavaScript 保存在后端,只讓用戶保存一個能獲取這段 JavaScript 的 url,url 是由書簽來獲取的。用戶保存的就是這個含有獲取 url 的 JavaScript 書簽。)

在 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變量是否被定義來檢測書簽是否被加載。這樣,我們就可以避免在用戶重復點擊書簽時重復加載。如果 myBookmarklet 沒有被定義,我們就再加載一個 JavaScript 文件來在文檔中添加一個<script>元素。 這個 script 標簽加載 bookmarklet_launcher.js腳本,將一個隨機數作為參數來防止加載瀏覽器緩存中的文件。

我們當前的 bookmarklet 代碼位於 bookmarklet.js 靜態文件中。這使我們在不要求用戶更新書簽的情況下更新我們代碼。讓我們把書簽添加進 dashboard 頁,我們的用戶就可以將它拷貝到他們的書簽中。

編輯 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 %}

這個 danshboard 展示了用戶所標記的圖片總數。我們使用{% with %}模板標簽來設置一個帶有用戶標記圖片總數的參數。我們也引入了一個帶有href屬性的鏈接,這個鏈接含有我們的書簽激活腳本。我們從 bookmarklet_launcher.js模板中引入 JavaScript 腳本。

在你的瀏覽器中打開http://127.0.0.1:8000/account/,你可以看到如下頁面:
![此處輸入圖片的描述][3]

拖拽Bookmark it!鏈接到你的瀏覽器的書簽工具欄中。

現在創建下面幾個路徑和文件在 images 應用路徑中:

  • static/
  • js/
  • bookmarklet.js

你會在本章示例代碼文件夾中的images 應用路徑下找到 static/css/ 路徑。復制 css/ 路徑到你的代碼文件夾下的static/中。 css/bookmarklet.css文件為我們的 JavaScript 書簽提供了樣式。
編輯 bookmarklet.js靜態文件,然后添加以下 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 加載腳本,當腳本已經加載到當前網站中時,它負責調用 JQuery 或者是從 Google 的 CDN 中加載 jQuery。當 jQuery 被加載,它會執行 bookmarklet()函數,該函數包含我們的bookmarklet代碼。我們還在這個文件頂部設置幾個變量:

  • jquery_version: 加載的 jQuery 版本
  • site_urlstatic_url:我們網站的主URL 和各自靜態文件的主URL
  • min_widthmin_height :我們的書簽在網站中將要尋找的圖像支持的最小寬度和最小高度,

現在讓我們來實現 bookmarklet函數,編輯 bookmarklet(),讓它看起來像這樣:

  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">&times;</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();
	  });
      };

這段代碼運行如下:

  1. 我們加載了bookmarklet.css樣式表,使用一個隨機的數字作為參數來避免瀏覽器的緩存
  2. 我們添加了定制的 HTML 到當前網站的<body>元素中。這個HTML由包含在當前網站尋找到的圖片的<div>元素構成的。
  3. 我們添加了一個事件,當用戶點擊我們的 HTML 塊中的關閉鏈接時,我們將移除我們添加進去的 HTML。我們使用 #bookmarklet``#close選擇器來找到帶有一個 ID 為close的 HTML 元素,這個 HTML 元素的父ID是 bookmarklet。jQuery 選擇器允許你尋找 HTML 元素。jQuery 選擇器返回所有給定的 CSS 選擇器找到的元素,你可以在這個鏈接中找到一組 jQuery 選擇器:http://api.jquery.com/category/selectors/

在加載了 CSS 樣式表和 HTML 后,我們需要在網站中找到圖片。在bookmarklet()函數的底部添加如下代碼:

    // 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="#"><img src="'+ image_url +'" /></a>');
      }
    });

這段代碼使用了img[src$="jpg"]選擇器來找到所有的<img> HTML 元素,並且這些元素的src屬性以jpg結尾。這意味着我們會找到當前網頁中所有的 JPG 圖片。我們通過each()方法來遍歷所有的結果。我們添加了<div class="images"> HTML 容器用以放置圖片,容器的的尺寸剛好比min_width min_width大一點。

這個 HTML 容器現在包含了可以被打上標簽的圖片,我們想要用戶點擊他們需要的圖片然后給他們打上標簽。在bookmarklet()函數中添加以下代碼:

    // 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');
    });

這段代碼按照如下流程運行:

  1. 我們把一個clck()事件綁定到了圖片的鏈接元素上
  2. 當一個用戶點擊一個圖片時我們新建了一個變量selected_image,這個變量包含了被選擇的圖片的 URL。
  3. 我們隱藏了書簽然后在瀏覽器中打開一個新的窗口,這個窗口訪問了我們的網站中為一個新的圖片打標簽的 URL 。我們傳遞了網站的title元素和被選中圖片的 URL 作為 GET 參數。

在你的瀏覽器中隨便選擇一個網址打開,然后點擊你的書簽。你將會看到一個白色的新窗口出現在當前網頁上,它展示了所有尺寸大於 100*100px 的 JPG 圖片,它看起來就像下面的例子一樣:
![django-5-4][4]
因為我們已經開啟了 Django 的開發服務器,使用 HTTP 來提供頁面, 由於安全限制,書簽將不能在 HTTPS 上工作。

如果你點擊一幅圖片,你將會被重定向到創建圖片的頁面,請求地址傳遞了網站的標題和被選中圖片的 URL 作為 GET 參數。

![Django-5-5][5]
恭喜!這是你的第一個 JavaScript 書簽!現在它已經和你的 Django 項目成為一體!

為你的圖片創建一個詳情視圖

我們將創建一個簡單的詳情視圖,用於展示一張已經保存在我們的網站中的圖片。打開 images 應用的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})

這是一個用於展示圖片的簡單視圖。編輯 iamges 應用的 urls.py,添加以下 URL 模式:

url(r'^detail/(?P<id>\d+)/(?P<slug>[-\w]+)/$',
              views.image_detail, name='detail'),

編輯 images 應用的models.py,並且將get_absolute_url()方法添加進 Image 模型:

from django.core.urlresolvers import reverse
class Image(models.Model):
    # ...
    def get_absolute_url(self):
        return reverse('images:detail',args=(self.id,self.slug))

記住,為對象提供精確 URL 的通用模式是在模型中定義get_absolute_url()方法。

最后,在 images 應用的 模版路徑/images/image/中新建一個模板,命名為detail.html,添加以下代碼:

{% extends "base.html" %}

{% block title %}{{ image.title }}{% endblock %}

{% block content %}
    <h1>{{ image.title }}</h1>
    <img src="{{ image.image.url }}" class="image-detail">
    {% 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 class="image-likes">
            {% for user in image.users_like.all %}
                <div>
                    <img src="{{ user.profile.photo.url }}">
                    <p>{{ user.first_name }}</p>
                </div>
            {% empty %}
                Nobody likes this image yet.
            {% endfor %}
        </div>
    {% endwith %}
{% endblock %}

這個模版用來展示一張被打標簽圖片。我們使用{% with %}標簽來保存所有統計user likes查詢集(QuerySet)的結果,並將這個結果保存在一個新的變量total_likes中。這樣我們就可以避免計算兩次查詢集(QuerySet)的結果。我們也引入了圖片的描述,迭代了image.users_like.all來展示所有喜歡這張圖片的用戶。

使用{% with %}模版標簽來防止 Django 做多次查詢是很有用的

現在使用書簽來為一張圖片打上標簽。在你提交圖片之后你將會被重定向圖片詳情頁面。這張圖片將會包含一條提交成功的消息,效果如下:
![Django-5-6][6]

使用 sorl-thumbnail 創建縮略圖

我們在詳情頁展示原圖片,但是不同的圖片的尺寸是不同的。一些圖片源文件或許會非常大,加載他們會耗費很長時間。展示規范圖片的最好方法是生成縮略圖。我們將使用一個 Django 應用,叫做sorl-thumbnail

打開終端,用下面的命令來安裝sorl-thumbnail

pip install sorl-thumbnail==12.3

編輯 bookmarklet 項目文件的settings.py,將sorl-thumbnail添加進INSTALLED_APPS.

運行下面的命令來同步你的數據庫:

python manage.py migrate

你看到的輸出中應該包含下面這一行:

Creating table thumbnail_kvstore

sorl-thumbnail應用提供了不同的方法來定義一張圖片的縮略圖。它提供了{% thumbnail %}模版標簽來在模版中生成縮略圖,同時還有一個定制的ImageField字段,如果你想要在你的模型中定制縮略圖的話。我們將要使用這個模版標簽。編輯 images/image/detail.html模版,刪除這一行:

<img src="{{ image.image.url }}" class="image-detail">

替換成:

{% load thumbnail %}
{% thumbnail image.image "300" as im %}
<a href="{{ image.image.url }}">
<img src="{{ im.url }}" class="image-detail">
</a>
{% endthumbnail %}

這里,我們定義了一個固定寬度為 300px 的縮略圖。當用戶第一次加載這頁面時,縮略圖將會被創建。生成的縮略圖將會在接下來的請求中被使用。運行python manage.py runserver開啟開發服務器,連接到一張已有圖片的詳情頁。縮略圖將會生成並展示在網站中。

sorl-thumbnail應用提供了幾個選擇來定制你的縮略圖,包括裁減算法和能被應用的不同效果。如果你有任何生成縮略圖的疑難點,你可以在你的設置中添加THUMBNAIL_DEBUG = TRUE來獲得 debug 信息。你可以閱讀sorl-thumbnail的完整文檔:http://sorl-thumbnail.readthedocs.org/

用 jQuery 添加 AJAX 動作

現在我們將在你的應用中添加 AJAX 動作。AJAX 源於 Asynchronous JavaScript and XML(異步 JavaScript 和 XML)。這個術語包含一組可以制造異步 HTTP 請求的技術,它包含從服務器異步發送和接收數據,不需要重載整個頁面,雖然它的名字里有 XML, 但是 XML 不是必需的。你可以以其他的格式發送或者接收數據,如 JSON, HTML,或者是純文本。

我們將在圖片詳情頁添加一個供用戶點擊的鏈接,表示他們喜歡這張圖片。我們將會用 AJAX 來避免重載整個頁面。首先,在 views.py 中創建一個可供用戶點擊“喜歡”或“不喜歡”的視圖。編輯 images 應用的views.py,將以下代碼添加進去:

@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 裝飾器阻止未登錄的用戶連接到這個視圖。require_GET 裝飾器返回一個HttpResponseNotAlloed對象(狀態嗎:405)如果 HTTP 請求不是 GET 。這樣就可以只允許 GET 請求來訪問這個視圖。 Django 同樣也提供了require_POST裝飾器來只允許 POST 請求,以及一個可讓你傳遞一組請求方法作為參數的 require_http_methods裝飾器。

在這個視圖中我們使用了兩個 GET 參數:

  1. image_id:用戶操作的 image 對象的 ID
  2. action: 用戶想要執行的動作。我們把它的值設定為like或者'dislike

我們在Image模型的多對多字段users_like上使用 Django 提供的管理器來添加或者刪除對象關系通過調用add()或者remove()方法來執行這些動作。調用add()時傳遞一個存在於關聯模型中的對象集不會重復添加這個對象,同樣,調用remove()時傳遞一個不存在於關聯模型中的對象集什操作也不會執行。另一個有用的多對多管理器是clear(),它將刪除所有的關聯對象集。

最后,我們使用 Django 提供的JsonResponse類來將給你定的對象轉換為一個 JSON 輸出,這個類返回一個帶有application/json內容類型的 HTTP 響應。

編輯 images 應用中的 urls.py,添加以下 URL 模式:

url(r'^like/$', views.image_like, name='like'),

加載 jQuery

我們需要在我們的圖片詳情頁中添加 AJAX 功能。我們首先將在 base.html模版中引入 AJAX。編輯 account 應用的 base.html模版,然后將以下代碼在</body>標簽前添加以下代碼:

<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提供了一個在高速內容分發網絡中的流行 JavaScript 框架。你也可以自己下載 jQuery, 地址:http://jquery.com/ 。然后將下載的文件添加進應用的static路徑下。

我們添加<script>標簽來引入 JavaScript 代碼。$(dovument).ready()是一個 jQuery 函數,這個函數會在 DOM 層加載完畢后執行。 DON 源於 Document Object Model。當一個頁面被載入時,DOM 會由瀏覽器創建, DOM 被創建為一個樹對象。通過在這個函數中包含我們的代碼來確保我們可以與DOM中加載的所有HTML元素都能進行交互操作。我們的代碼僅僅會在 DOM 對象被加載完畢之后執行。

在文檔預處理函數中,我們會在模板中引入一個 Django 模板塊叫做 domready, 在擴展了基礎模版之后將會引入特定的 JavaScript 。

不要將 JavaScript 代碼和 Django 模板標簽搞混了。 Django 模板語言是在服務端被渲染並輸出最終的 HTML 文檔,JavaScript 是在客戶端被執行的。在某些情況下,使用 Django 動態生成 JavaScript 很有用。

在這一章中,我們在 Django 模板中引入(include)了 JavaSript 代碼。更好的引入方法是加載(load) JavaSript. js文件是作為靜態文件被提供的,特別在有大量腳本時尤其如此。

AJAX 請求中的跨站請求攻擊(CSRF)

你已經在第二章了解到了跨站請求攻擊,在CSRF保護激活的情況下, Django 會檢查所有 POST 請求中的 CSRF token。當你提交表但時,你可以使用{% csrf_token %}模板標簽來發送帶有 token 的表單。無論如何,像 POST 請求一樣對 AJAX 請求傳遞CDRF token 有一點點不方便。因此,Django 允許你在你的 AJAX 請求中設置一個定制的 X-CSRFToken token 頭(header)。這允許你安裝一個 jQuery 或者任意 JavaScript 庫來自動設置X-CSRFToken頭在每一次請求中。

為了在所有的請求中加入 token ,你需要:

  1. csrftoken cookie 中檢索 CSRF token,它在CSRF保護激活的情況下會被設置
  2. 使用 X-CSRFToken頭發送 token 到 AJAX 中

你可以找到更多關於 CSRF 保護 和 AJAX 的信息:http://docs.djangoproject.com/en/1.8/ref/csrf/#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 require 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>

上面這段代碼解釋如下:

  1. 我們從一個公共的 CDN 中載入了一個 jQuery Cookie 插件,這樣我們就可以和 cookies 交互。
  2. 讀取 csrftoken cookie
  3. 我們將定義csrfSafeMethod函數來檢查一個 HTTP 方法是否安全。安全方法不要求 CSRF 保護,他們分別是 GET, HEAD, OPTIONS, TRACE。
  4. 我們用$.ajaxSetup()設置了 jQuery AJAX 請求,在每個 AJAX 請求執行前,我們會檢查請求方法是否安全和當前請求是否跨域名。如果請求是不安全的,我們將用從 cookie 中獲得的值來設置 X-CSRFToken頭。這個設置將會應用到所有由 jQuery 執行的 AJAX 請求中

CSRF token將會在所有的不安全 HTTP 方法的 AJAX 請求中引入,比如 POST, PUT

用 JQuery 執行 AJAX請求

編輯 images 應用中的 images/image/detailmhtml模板,刪除這一行:

{% with total_likes=image.users_like.count %}

替換為:

{% with total_likes=image.users_like.count users_like=image.users_like.all %}

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 %}模板標簽中來保存image.uers_like.all查詢接的結果來避免執行兩次查詢。展示喜歡這張圖片用戶的總數,包含一個“like/unlike”鏈接。我們檢查用戶是否在關聯對象``user_likes中,基於當前的用戶和圖片的關系展示 like 或者 unlike。。我們將以下屬性添加進了` HTML 元素中:

  • data-id:被展示圖片的 ID
  • data-action:當用戶點擊這個鏈接時執行這個動作。這個動作可以是 like 或者是 unlike
    我們將會在向 iamge_like視圖的 AJAX 請求中添加這兩個屬性值。當一個用戶點擊like/unlike鏈接時,我們需要在客戶端執行以下幾個動作:
  1. 調用 AJAX 視圖,並把圖片的 ID 和動作參數傳遞進去
  2. 如果 AJAX 請求成功, 更新<a> HTML元素的data-action屬性(like / unlike),根據此來修改它展示的文本
  3. 更新展示的 likes 的總數

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 %}

這段代碼工作流程如下:

  1. 我們使用$.('a.like') jQuery 選擇器來找到所有的 class 屬性是 like 的<a>標簽
  2. 我們為點擊事件定義了一個控制器函數。這個函數會在用戶每次點擊like/unlike時執行
    3.在控制器函數中,我們使用e.preventDefault()來避免<a>標簽的默認行為。這會阻止鏈接把我們帶到其他地方。
  3. 我們使用$.post()向服務器執行一個異步 POST 請求。 jQuery 也會提供一個$.get()方法來執行 GET 請求和一個低級別的 $.ajax()方法。
  4. 我們使用 Django 的{% url %}模板標簽來構建為 AJAX 請求需要的URL
  5. 我們在請求中建立要發送的 POST 參數字典。他們是 Django 視圖中期望的 IDaction參數。我們從<a>元素的data-iddata-action中獲取兩個參數的值。
  6. 我們定義了一個當 HTTP 應答被接收時的回調函數。它接收一個含有應答內容的數據屬性。
  7. 我們獲取接收數據的status屬性然后檢查它的值是否是ok。如果返回的data是期望中的那樣,我們將切換data-action屬性的鏈接和它的文本內容。這可以讓用戶取消這個動作。
  8. 我們基於執行的動作來增加或者減少 likes 的總數

在你的瀏覽器中打開一張你上傳的圖片的詳情頁,你可以看到初始的 like 統計和一個 LIKE 按鈕:
![Django-5-7][7]
點擊LIKE按鈕,你將會看見 likes 的總數上升了,按鈕的文本也變成了UNLIKE
![Django-5-8][8]
當你點擊UNLIKE按鈕時動作被執行,按鈕的文本也會變成LIKE,統計的總數也會據此下降。

在編寫 JavaScript 時,特別是在寫 AJAX 請求時, 我們建議應該使用一個類似於 Firebug 的工具來調試你的 JavaScript 腳本以及監視 CSS 和 HTML 的變化,你可以下載 Firebug : http://getfirebug.com/。一些瀏覽器比如Chrome或者Safari也包含一些調試 JavaScript 的開發者工具。在那些瀏覽器中,你可以在網頁的任何地方右鍵然后點擊Inspect element來使用網頁開發者工具。

為你的視圖創建定制化的裝飾器

我們將會限制我們的 AJAX 視圖只接收由 AJAX 發起的請求。Django Request 對象提供了一個 is_ajax()方法, 這個方法會檢查請求是否帶有XMLHttpRequest,也就是說,會檢查這個請求是否是一個 AJAX 請求。這個值被設置在HTTP_X_REQUESTED_WITH HTTP頭中, 這個頭被大多數的由JavaScript庫發起的 AJAX 請求包含。

我們將在我們的視圖中創建一個裝飾器,來檢測HTTP_X_RQUESTED_WITH頭。裝飾器是一個可以接收一個函數為參數的函數,並且它可以在不改變作為參數的函數的情況下,拓展此函數的功能。如果裝飾器的概念對你來說還很陌生,在繼續閱讀之前你或許可以看看這個:https:www.python.org/dev/peps/pep-0318/。

由於我們的裝飾器將會是通用的,它將被應用到任何視圖中,所以我們在我們的項目中將創建一個 commonPython 包,在 bookmarklet 項目中創建如下路徑:

  • common/
  • _init_.py
  • decorators.py

編輯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裝飾器。它定義一個當請求不是 AJAX 時返回HttpResponseBadRequest(HTTP 400)對象的wrap 函數,否則它將返回一個被裝飾了的對象。

現在你可以編輯 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 加載下一頁的內容,然后將內容加入到頁面的底部。

編輯 images 應用的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 分頁,我們將會把結果渲染給兩個不同的模板:

  1. 對於 AJAX 請求,我們渲染list_ajax.html模板。這個模板將只會包含我們請求頁面的圖片
  2. 對於標准請求: 我們渲染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 %}
    <div class="image">
        <a href="{{ image.get_absolute_url }}">
            {% thumbnail image.image "300x300" crop="100%" as im %}
                <a href="{{ image.get_absolute_url }}">
                    <img src="{{ im.url }}">
                </a>
            {% endthumbnail %}
        </a>
        <div class="info">
            <a href="{{ image.get_absolute_url }}" class="title">{{ image.title }}</a>
        </div>
    </div>
{% endfor %}

這個模板將用於展示一組圖片,它會作為結果返回給 AJAX 請求。之后,在相同路徑下創建'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模板。為了避免重復編碼,我們引入了list_ajax.html模板。這個listmhtml模板將會有一段 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 %}

這段代碼實現了不受限的滾屏功能。我們在 base.html中定義的domready塊中引入了 JavaScript 代碼,這段代碼的工作流程如下:

  1. 我們定義了如下幾個變量:

    • page:保存當前的頁碼
    • empt_page:讓我們知道用戶是否到了最后一頁,然后接收一個空頁面。只要接收到了一個空頁面,我們就會停止發送額外的 AJAX 請求,因為我們確定此時已經沒有結果了。
    • block_requests:當有進程中有 AJAX 請求時,阻止額外的請求。
  2. 我們使用$(window).scroll()來捕獲滾動事件,然后我們為此定義了一個控制器函數。

  3. 我們計算邊框變量來得到文檔高度和窗口高度的差值,因為這個差值是用戶將要滾動的內容的高度。我們從結果當中減去 200,這樣我們就可以在用戶接近底部 200pixels 時加載下一頁的內容。

  4. 我們只在以下兩種條件滿足時發送 AJAX 請求:沒有其他 AJAX 請求被正在被執行時(譯者注:就是同時只有一個 AJAX 請求)(block_request必須是false),用戶也沒有到達頁面底部(empty_page也必須是false)。

  5. 我們將block_request設為True來避免滾動時間觸發額外的 AJAX 請求,然后我們會在請求下一頁時增加一次page計數。

  6. 我們使用$.get()來執行一次 AJAX GET 請求,然后我們在一個叫做data的變量中接收 HTML 響應。這里有兩種情況。

    • 響應沒有內容:我們已經到了結果的末尾,所以這里沒有更多的頁面來供我們加載。我們把empty_page設為True來阻止加載更多的 AJAX 請求。
    • 響應含有數據:我們將數據添加到id為 image-list的 HTML 元素中,當用戶滾動到底部時頁面將直接擴展添加的結果。

在瀏覽器中訪問http://127.0.0.1:8000/images/,你會看到你之前添加的一組圖片,看起來像這樣:
![Django-5-9][9]
[1]: http://ohqrvqrlb.bkt.clouddn.com/django-5-1.png
[2]: http://ohqrvqrlb.bkt.clouddn.com/django-5-2.png
[3]: http://ohqrvqrlb.bkt.clouddn.com/django-5-3.png
[4]: http://ohqrvqrlb.bkt.clouddn.com/django-5-4.png
[5]: http://ohqrvqrlb.bkt.clouddn.com/django-5-5.png
[6]: http://ohqrvqrlb.bkt.clouddn.com/django-5-6.png
[7]: http://ohqrvqrlb.bkt.clouddn.com/django-5-7.png
[8]: http://ohqrvqrlb.bkt.clouddn.com/django-5-8.png
[9]: http://ohqrvqrlb.bkt.clouddn.com/django-5-9.png

滾動到底部將會加載下一頁。確定你已經使用書簽添加了多於 8 張圖片,因為我們每一頁展示的是 8 張圖片。記得使用 Firebug 或者類似的工具來跟蹤 AJAX 請求和調試你的 JavaScript 代碼。

最后,編輯 account 應用中的base.html模板,為主菜單添加圖片項:

<li {% if section == "images" %}class="selected"{% endif %}><a href="{% url "images:list" %}">Images</a></li>

現在你可以從主菜單連接到圖片列表了。

總結

在這一章中,我們創建了一個 JavaScript 書簽來從其他網站分享圖片到我們的網站。你已經用 jQuery 實現了 AJAX 視圖,還添加了 AJAX 分頁。

在下一章中,將會教你如何創建一個粉絲系統和一個活動流。你將和通用關系、信號、與反規范化打交道。你也將學習如何在 Django 中使用 Redis。

(譯者注:這是我翻譯的《Django by Example》的第一篇文章,在之后的日子里我將會與 @夜夜月一同翻譯這本書。翻譯中有不對的地方,還請讀者斧正。我是一名在校大學生,目前大二,出於對編程的喜愛而喜歡上 coding,希望能和各位多多交流。)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM