《Django By Example》第六章 中文翻譯
譯者夜夜月,已獲作者授權轉載。
原文鏈接:http://www.jianshu.com/p/7ad5db9a1b69為了獲得更好閱讀體驗,可點擊原文鏈接跳轉到簡書閱讀。鼓勵對譯者的辛勤付出進行打賞。
書籍出處:Django By Example
原作者:Antonio Melé(譯者註:無他,祝大家年會都中獎!)
第六章
跟蹤用戶動作
在上一章中,你在你的項目中實現了AJAX視圖(views),通過使用jQuery並創建了一個JavaScript書籤在你的平台中分享別的網站的內容。
在本章中,你會學習如何創建一個粉絲系統以及創建一個用戶活動流(activity stream)。你會學習到Django信號(signals)的工作方式以及在你的項目中集成Redis快速 I/O 倉庫用來存儲視圖(views)項。
本章將會覆蓋以下幾點:
- 通過一個中介模型(intermediate model)創建多對對的關係
- 創建 AJAX 視圖(views)
- 創建一個活動流(activity stream)應用
- 給模型(modes)添加通用關係
- 取回對象的最優查詢集(QuerySets)
- 使用信號(signals)給非規範化的計數
- 存儲視圖(views)項到 Redis 中
創建一個粉絲系統
我們將要在我們的項目中創建一個粉絲系統。我們的用戶在平台中能夠彼此關注並且跟蹤其他用戶的分享。這個關係在用戶中的是多對多的關係,一個用戶能夠關注多個用戶並且能被多個用戶關注。
通過一個中介模型(intermediate model)(intermediary model)創建多對對的關係
在上一章中,你創建了多對對關係通過在其中一個有關聯的模型(model)上添加了一個ManyToManyField然後讓Django為這個關係創建了資料庫表。這種方式支持大部分的場景,但是有時候你需要為這種關係創建一個中介模型(intermediate model)。創建一個中介模型(intermediate model)是非常有必要的當你想要為當前關係存儲額外的信息,例如當前關係創建的時間點或者一個描述當前關係類型的欄位。
我們會創建一個中介模型(intermediate model)用來在用戶之間構建關係。有兩個原因可以解釋為什麼我們要用一個中介模型(intermediate model):
- 我們使用Django提供的user模型(model)並且我們想要避免修改它。
- 我們想要存儲關係建立的時間
編輯你的account應用中的models.py文件添加如下代碼:
from django.contrib.auth.models import Usern class Contact(models.Model):n user_from = models.ForeignKey(User,n related_name=rel_from_set)n user_to = models.ForeignKey(User,n related_name=rel_to_set)n created = models.DateTimeField(auto_now_add=True,n db_index=True)n class Meta:n ordering = (-created,)n def __str__(self):n return {} follows {}.format(self.user_from,nself.user_to)n
這個Contact模型我們將會給用戶關係使用。它包含以下欄位:
- user_form:一個ForeignKey指向創建關係的用戶
- user_to:一個ForeignKey指向被關注的用戶
- created:一個auto_now_add=True的DateTimeField欄位用來存儲關係創建時的時間
在ForeignKey欄位上會自動生成一個資料庫索引。我們使用db_index=True來創建一個資料庫索引給created欄位。這會提升查詢執行的效率當通過這個欄位對查詢集(QuerySets)進行排序的時候。
使用 ORM ,我們可以創建一個關係給一個用戶 user1 關注另一個用戶 user2,如下所示:
user1 = User.objects.get(id=1)nuser2 = User.objects.get(id=2)nContact.objects.create(user_from=user1, user_to=user2)n
關係管理器 rel_form_set 和 rel_to_set 會返回一個查詢集(QuerySets)給Contace模型(model)。為了
從User模型(model)中存取最終的關係側,Contace模型(model)會期望User包含一個ManyToManyField,如下所示(譯者註:以下代碼是作者假設的,實際上User不會包含以下代碼):
following = models.ManyToManyField(self,n through=Contact,n related_name=followers,n symmetrical=False)n
在這個例子中,我們告訴Django去使用我們定製的中介模型(intermediate model)來創建關係通過給ManyToManyField添加through=Contact。這是一個從User模型到本身的多對對關係:我們在ManyToMnyfIELD欄位中引用 self來創建一個關係給相同的模型(model)。
當你在多對多關係中需要額外的欄位,創建一個定製的模型(model),一個關係側就是一個ForeignKey。添加一個 ManyToManyField 在其中一個有關聯的模型(models)中然後通過在through參數中包含該中介模型(intermediate model)指示Django去使用你的定製中介模型(intermediate model)。
如果User模型(model)是我們應用的一部分,我們可以添加以上的欄位給模型(model)(譯者註:所以說,上面的代碼是作者假設存在)。但實際上,我們無法直接修改User類,因為它是屬於django.contrib.auth應用的。我們將要做些輕微的改動,給User模型動態的添加這個欄位。編輯account應用中的model.py文件,添加如下代碼:
# Add following field to User dynamicallynUser.add_to_class(following,n models.ManyToManyField(self,n through=Contact,n related_name=followers,n symmetrical=False))n
在以上代碼中,我們使用Django模型(models)的add_to_class()方法給User模型(model)添加monkey-patch(譯者註:猴子補丁 Monkey patch 就是在運行時對已有的代碼進行修改,而不需要修改原始代碼)。你需要意識到,我們不推薦使用add_to_class()為模型(models)添加欄位。我們在這個場景中利用這種方法是因為以下的原因:
- 我們可以非常簡單的取回關係對象使用Django ORM的user.followers.all()以及user.following.all()。我們使用中介(intermediary) Contact 模型(model)可以避免複雜的查詢例如使用到額外的資料庫操作joins,如果在我們的定製Profile模型(model)中定義過了關係。
- 這個多對多關係的表將會被創建通過使用Contact模型(model)。因此,動態的添加ManyToManyField將不會對Django User 模型(model)的資料庫進行任意改變。
- 我們避免了創建一個定義的用戶模型(model),保持了所有Django內置User的特性。
請記住,在大部分的場景中,在我們之前創建的Profile模型(model)添加欄位是更好的方法,可以替代在User模型(model)上打上monkey-patch。Django還允許你使用定製的用戶模型(models)。如果你想要使用你的定製用戶模型(model),可以訪問 Customizing authentication in Django 獲得更多信息。
你能看到上述代碼中的關係包含了symmetrical=Flase來定義一個非對稱(non-symmetric)關係。這表示如果我關注了你,你不會自動的關注我。
當你使用了一個中介模型(intermediate model)給多對多關係,一些關係管理器的方法將不可用,例如:add(),create()以及remove()。你需要創建或刪除中介模型(intermediate model)的實例來代替。
運行如下命令來生成account應用的初始遷移:
python manage.py makemigrations accountn
你會看到如下輸出:
Migrations for account:n 0002_contact.py:n - Create model Contactn
現在繼續運行以下命令來同步應用到資料庫中:
python manage.py migrate accountn
你會看到如下內容包含在輸出中:
Applying account.0002_contact... OKn
Contact模型(model)現在已經被同步進了資料庫,我們可以在用戶之間創建關係。但是,我們的網站還沒有提供一個方法來瀏覽用戶或查看詳細的用戶profile。讓我們為User模型構建列表和詳情視圖(views)。
為用戶profiles創建列表和詳情視圖(views)
打開account應用中的views.py文件添加如下代碼:
from django.shortcuts import get_object_or_404nfrom django.contrib.auth.models import Usern @login_requiredn def user_list(request):n users = User.objects.filter(is_active=True)n return render(request,n account/user/list.html,n {section: people,n users: users})n @login_requiredn def user_detail(request, username):n user = get_object_or_404(User,n username=username,n is_active=True)n return render(request,n account/user/detail.html,n {section: people,n user: user})n
以上是User對象的簡單列表和詳情視圖(views)。user_list視圖(view)獲得了所有的可用用戶。Django User 模型(model)包含了一個標誌(flag)is_active來指示用戶賬戶是否可用。我們通過is_active=True來過濾查詢只返回可用的用戶。這個視圖(vies)返回了所有結果,但是你可以改善它通過添加頁碼,這個方法我們在image_list視圖(view)中使用過。
user_detail視圖(view)使用get_object_or_404()快捷方法來返回所有可用的用戶通過傳入的用戶名。當使用傳入的用戶名無法找到可用的用戶這個視圖(view)會返回一個HTTP 404響應。
編輯account應用的urls.py文件,為以上兩個視圖(views)添加URL模式,如下所示:
urlpatterns = [n # ...n url(r^users/$, views.user_list, name=user_list),n url(r^users/(?P<username>[-w]+)/$,n views.user_detail,n name=user_detail),n]n
我們會使用 user_detail URL模式來給用戶生成規範的URL。你之前就在模型(model)中定義了一個get_absolute_url()方法來為每個對象返回規範的URL。另外一種方式為一個模型(model)指定一個URL是為你的項目添加ABSOLUTE_URL_OVERRIDES設置。
編輯項目中的setting.py文件,添加如下代碼:
ABSOLUTE_URL_OVERRIDES = {n auth.user: lambda u: reverse_lazy(user_detail,n args=[u.username])n}n
Django會為所有出現在ABSOLUTE_URL_OVERRIDES設置中的模型(models)動態添加一個get_absolute_url()方法。這個方法會給設置中指定的模型返回規範的URL。我們給傳入的用戶返回user_detail URL。現在你可以在一個User實例上使用get_absolute_url()來取回他自身的規範URL。打開Python shell輸入命令python manage.py shell運行以下代碼來進行測試:
>>> from django.contrib.auth.models import Usern>>> user = User.objects.latest(id)n>>> str(user.get_absolute_url())n/account/users/ellington/n
返回的URL如同期望的一樣。我們需要為我們剛才創建的視圖(views)創建模板(templates)。在account應用下的*templates/account/目錄下添加以下目錄和文件:
/user/n detail.htmln list.htmln
編輯account/user/list.html模板(template)給它添加如下代碼:
{% extends "base.html" %}n{% load thumbnail %}n{% block title %}People{% endblock %}n{% block content %}n <h1>People</h1>n <div id="people-list">n {% for user in users %}n <div class="user">n <a href="{{ user.get_absolute_url }}">n {% thumbnail user.profile.photo "180x180" crop="100%" as im %}n ![]({{ im.url }})n {% endthumbnail %}n </a>n <div class="info">n <a href="{{ user.get_absolute_url }}" class="title">n {{ user.get_full_name }}n </a> n </div>n </div>n {% endfor %}n </div>n{% endblock %}n
這個模板(template)允許我們在網站中排列所有可用的用戶。我們對給予的用戶進行迭代並且使用`{% thumbnail %}模板(template)標籤(tag)來生成profile圖片縮微圖。
打開項目中的base.html模板(template),在以下菜單項的href屬性中包含user_listURL:
<li {% if section == "people" %}class="selected"{% endif %}>n <a href="{% url "user_list" %}">People</a>n</li>n
通過命令python manage.py runserver啟動開發伺服器然後在瀏覽器打開 http://127.0.0.1:8000/account/users/ 。你會看到如下所示的用戶列:
django-6-1(譯者註:圖靈,特斯拉,愛因斯坦,都是大牛啊)
編輯account應用下的account/user/detail.html模板,添加如下代碼:
{% extends "base.html" %}n{% load thumbnail %}n{% block title %}{{ user.get_full_name }}{% endblock %}n{% block content %}n <h1>{{ user.get_full_name }}</h1>n <div class="profile-info">n {% thumbnail user.profile.photo "180x180" crop="100%" as im %}n ![]({{ im.url }})n {% endthumbnail %}n </div>n {% with total_followers=user.followers.count %}n <span class="count">n <span class="total">{{ total_followers }}</span>n follower{{ total_followers|pluralize }}n </span>n <a href="#" data-id="{{ user.id }}" data-action="{% if request.user in user.followers.all %}un{% endif %}follow" class="followbutton">n {% if request.user not in user.followers.all %}n Follown {% else %}n Unfollown {% endif %}n </a>n <div id="image-list" class="image-container">n {% include "images/image/list_ajax.html" with images = user.images_create.all %}n </div>n {% endwith %}n{% endblock %}n
在詳情模板(template)中我們展示用戶profile並且我們使用{% thumbnail %}模板(template)標籤(tag)來顯示profile圖片。我們顯示粉絲的總數以及一個鏈接可以 follow/unfollow 該用戶。我們會隱藏關注鏈接當用戶在查看他們自己的profile,防止用戶自己關注自己。我們會執行一個AJAX請求來 follow/unfollow 一個指定用戶。我們給 <a>HTML元素添加data-id和data-action屬性包含用戶ID以及當該鏈接被點擊的時候會執行的初始操作,follow/unfollow ,這個操作依賴當前頁面的展示的用戶是否已經被正在瀏覽的用戶所關注。我們展示當前頁面用戶的圖片書籤通過list_ajax.html模板。
再次打開你的瀏覽器,點擊一個擁有圖片書籤的用戶鏈接,你會看到一個profile詳情如下所示:
django-6-2創建一個AJAX視圖(view)來關注用戶
我們將會創建一個簡單的視圖(view)使用AJAX來 follow/unfollow 用戶。編輯account應用中的views.py文件添加如下代碼:
from django.http import JsonResponsenfrom django.views.decorators.http import require_POSTnfrom common.decorators import ajax_requirednfrom .models import Contactn@ajax_requiredn@require_POSTn@login_requiredndef user_follow(request):n user_id = request.POST.get(id)n action = request.POST.get(action)n if user_id and action:n try:n user = User.objects.get(id=user_id)n if action == follow:n Contact.objects.get_or_create(n user_from=request.user,n user_to=user)n else:n Contact.objects.filter(user_from=request.user,n user_to=user).delete()n return JsonResponse({status:ok})n except User.DoesNotExist:n return JsonResponse({status:ko})n return JsonResponse({status:ko})n
user_follow視圖(view)有點類似與我們之前創建的image_like視圖(view)。因為我們使用了一個定製中介模型(intermediate model)給用戶的多對多關係,所以ManyToManyField管理器默認的add()和remove()方法將不可用。我們使用中介Contact模型(model)來創建或刪除用戶關係。
在account應用中的urls.py文件中導入你剛才創建的視圖(view)然後為它添加URL模式:
url(r^users/follow/$, views.user_follow, name=user_follow),n
請確保你放置的這個URL模式的位置在user_detailURL模式之前。否則,任何對 /users/follow/ 的請求都會被user_detail模式給正則匹配然後執行。請記住,每一次的HTTP請求Django都會對每一條存在的URL模式進行匹配直到第一條匹配成功才會停止繼續匹配。
編輯account應用下的user/detail.html模板添加如下代碼:
{% block domready %}n $(a.follow).click(function(e){n e.preventDefault();n $.post({% url "user_follow" %},n {n id: $(this).data(id),n action: $(this).data(action)n },n function(data){n if (data[status] == ok) {n var previous_action = $(a.follow).data(action);nn // toggle data-actionn $(a.follow).data(action,n previous_action == follow ? unfollow : follow);n // toggle link textn $(a.follow).text(n previous_action == follow ? Unfollow : Follow);nn // update total followersn var previous_followers = parseInt(n $(span.count .total).text());n $(span.count .total).text(previous_action == follow ? previous_followers + 1 : previous_followers - 1);n }n }n });n });n{% endblock %}n
這段JavaScript代碼執行AJAX請求來關注或不關注一個指定用戶並且觸發 follow/unfollow 鏈接。我們使用jQuery去執行AJAX請求的同時會設置 follow/unfollow 兩種鏈接的data-aciton屬性以及HTML<a>元素的文本基於它上一次的值。當AJAX操作執行完成,我們還會對顯示在頁面中的粉絲總數進行更新。打開一個存在的用戶的詳情頁面,然後點擊Follow鏈接嘗試下我們剛才構建的功能是否正常。
創建一個通用的活動流(activity stream)應用
許多社交網站會給他們的用戶顯示一個活動流(activity stream),這樣他們可以跟蹤其他用戶在平台中的操作。一個活動流(activity stream)是一個用戶或一個用戶組最近活動的列表。舉個例子,Facebook的News Feed就是一個活動流(activity stream)。用戶X給Y圖片打上了書籤或者用戶X關注了用戶Y也是例子操作。我們將會構建一個活動流(activity stream)應用這樣每個用戶都能看到他關注的用戶最近進行的交互。為了做到上述功能,我們需要一個模型(modes)來保存用戶在網站上的操作執行,還需要一個簡單的方法來添加操作給feed。
運行以下命令在你的項目中創建一個新的應用命名為actions:
django-admin startapp actionsn
在你的項目中的settings.py文件中的INSTALLED_APPS設置中添加actions,這樣可以讓Django知道這個新的應用是可用狀態:
INSTALLED_APPS = (n # ...n actions, n)n
編輯actions應用下的models.py文件添加如下代碼:
from django.db import modelsnfrom django.contrib.auth.models import Usernnclass Action(models.Model):n user = models.ForeignKey(User,n related_name=actions,n db_index=True)n verb = models.CharField(max_length=255)n created = models.DateTimeField(auto_now_add=True,n db_index=True)nn class Meta:n ordering = (-created,)n
這個Action模型(model)將會用來記錄用戶的活動。模型(model)中的欄位解釋如下:
- user:執行該操作的用戶。這個一個指向Django User模型(model)的 ForeignKey。
- verb:這是用戶執行操作的動作描述。
- created:這個時間日期會在動作執行的時候創建。我們使用auto_now_add=True來動態設置它為當前的時間當這個對象第一次被保存在資料庫中。
通過這個基礎模型(model),我們只能夠存儲操作例如用戶X做了哪些事情。我們需要一個額外的ForeignKey欄位為了保存操作會涉及到的一個target(目標)對象,例如用戶X給圖片Y打上了暑期那或者用戶X現在關注了用戶Y。就像你之前知道的,一個普通的ForeignKey只能指向一個其他的模型(model)。但是,我們需要一個方法,可以讓操作的target(目標)對象是任何一個已經存在的模型(model)的實例。這個場景就由Django內容類型框架來上演。
使用內容類型框架
Django包含了一個內容類型框架位於django.contrib.contenttypes。這個應用可以跟蹤你的項目中所有的模型(models)以及提供一個通用介面來與你的模型(models)進行交互。
當你使用startproject命令創建一個新的項目的時候這個contenttypes應用就被默認包含在INSTALLED_APPS設置中。它被其他的contrib包使用,例如認證(authentication)框架以及admin應用。
contenttypes應用包含一個ContentType模型(model)。這個模型(model)的實例代表了你的應用中真實存在的模型(models),並且新的ContentTYpe實例會動態的創建當新的模型(models)安裝在你的項目中。ContentType模型(model)有以下欄位:
- app_label:模型(model)屬於的應用名,它會自動從模型(model)Meta選項中的app_label屬性獲取到。舉個例子:我們的Image模型(model)屬於images應用
- model:模型(model)類的名字
- name:模型的可讀名,它會自動從模型(model)Meta選項中的verbose_name獲取到。
讓我們看一下我們如何實例化ContentType對象。打開Python終端使用python manage.py shell命令。你可以獲取一個指定模型(model)對應的ContentType對象通過執行一個帶有app_label和model屬性的查詢,例如:
>>> from django.contrib.contenttypes.models import ContentTypen>>> image_type = ContentType.objects.get(app_label=images,model=image)n>>> image_typen<ContentType: image>n
你還能反過來獲取到模型(model)類從一個ContentType對象中通過調用它的model_class()方法:
>>> from images.models import Imagen>>> ContentType.objects.get_for_model(Image)n<ContentType: image>n
以上就是內容類型的一些例子。Django提供了更多的方法來使用他們進行工作。你可以訪問 The contenttypes framework 找到關於內容類型框架的官方文檔。
添加通用的關係給你的模型(models)
在通用關係中ContentType對象扮演指向模型(model)的角色被關聯所使用。你需要3個欄位在模型(model)中組織一個通用關係:
- 一個ForeignKey欄位ContentType。這個欄位會告訴我們給這個關聯的模型(model)。
- 一個欄位用來存儲被關聯對象的primary key。這個欄位通常是一個PositiveIntegerField用來匹配Django自動的primary key欄位。
- 一個欄位用來定義和管理通用關係通過使用前面的兩個欄位。內容類型框架提供一個GenericForeignKey欄位來完成這個目標。
編輯actions應用的models.py文件,添加如下代碼:
from django.db import modelsnfrom django.contrib.auth.models import Usernfrom django.contrib.contenttypes.models import ContentTypenfrom django.contrib.contenttypes.fields import GenericForeignKeynclass Action(models.Model):n user = models.ForeignKey(User,n related_name=actions,n db_index=True)n verb = models.CharField(max_length=255)n target_ct = models.ForeignKey(ContentType,n blank=True,n null=True,n related_name=target_obj)n target_id = models.PositiveIntegerField(null=True,n blank=True,n db_index=True)n target = GenericForeignKey(target_ct, target_id)n created = models.DateTimeField(auto_now_add=True,n db_index=True)n class Meta:n ordering = (-created,)n
我們給Action模型添加了以下欄位:
- target_ct:一個ForeignKey欄位指向ContentType模型(model)。
- target_id:一個PositiveIntegerField用來存儲被關聯對象的primary key。
- target:一個GenericForeignKey欄位指向被關聯的對象基於前面兩個欄位的組合之上。
Django沒有創建任何欄位在資料庫中給GenericForeignKey欄位。只有target_ct和target_id兩個欄位被映射到資料庫欄位。兩個欄位都有blank=True和null=True屬性所以一個target(目標)對象不是必須的當保存Action對象的時候。
你可以讓你的應用更加靈活通過使用通用關係替代外鍵當它對擁有一個通用關係有意義。
運行以下命令來創建初始遷移為這個應用:
python manage.py makemigrations actionsn
你會看到如下輸出:
Migrations for actions:n 0001_initial.py:n - Create model Actionn
接著,運行下一條命令來同步應用到資料庫中:
python manage.py migraten
這條命令的輸出表明新的遷移已經被應用:
Applying actions.0001_initial... OKn
讓我們在管理站點中添加Action模型(model)。編輯actions應用的admin.py文件,添加如下代碼:
from django.contrib import adminnfrom .models import Actionnnclass ActionAdmin(admin.ModelAdmin):n list_display = (user, verb, target, created)n list_filter = (created,)n search_fields = (verb,)nnadmin.site.register(Action, ActionAdmin)n
你已經將Action模型(model)註冊到了管理站點中。運行命令python manage.py runserver來初始化開發伺服器然後在瀏覽器中打開 http://127.0.0.1:8000/admin/actions/action/add/ 。你會看到如下頁面可以創建一個新的Action對象:
django-6-3如你所見,只有target_ct和target_id兩個欄位是映射為真實的資料庫欄位顯示,並且GenericForeignKey欄位不在這兒出現。target_ct允許你選擇任何一個在你的Django項目中註冊的模型(models)。你可以限制內容類型從一個限制的模型(models)集合中選擇通過在target-ct欄位中使用limit_choices_to屬性:limit_choices_to屬性允許你限制ForeignKey欄位的內容通過給予一個特定值的集合。
在actions應用目錄下創建一個新的文件命名為utils.py。我們會定義一個快捷函數,該函數允許我們使用一種簡單的方式創建新的Action對象。編輯這個新的文件添加如下代碼給它:
from django.contrib.contenttypes.models import ContentTypenfrom .models import Actionndef create_action(user, verb, target=None):n action = Action(user=user, verb=verb, target=target)n action.save()n
create_action()函數允許我們創建actions,該actions可以包含一個target對象或不包含。我們可以使用這個函數在我們代碼的任何地方添加新的actions給活動流(activity stream)。
在活動流(activity stream)中避免重複的操作
有時候你的用戶可能多次執行同個動作。他們可能在短時間內多次點擊 like/unlike 按鈕或者多次執行同樣的動作。這會導致你停止存儲和顯示重複的動作。為了避免這種情況我們需要改善create_action()函數來避免大部分的重複動作。
編輯actions應用中的utils.py文件使它看上去如下所示:
import datetimenfrom django.utils import timezonenfrom django.contrib.contenttypes.models import ContentTypenfrom .models import Actionnndef create_action(user, verb, target=None):n # check for any similar action made in the last minuten now = timezone.now()n last_minute = now - datetime.timedelta(seconds=60)n similar_actions = Action.objects.filter(user_id=user.id,n verb= verb,n timestamp__gte=last_minute)n if target:n target_ct = ContentType.objects.get_for_model(target)n similar_actions = similar_actions.filter(n target_ct=target_ct,n target_id=target.id)n if not similar_actions:n # no existing actions foundn action = Action(user=user, verb=verb, target=target)n action.save()n return Truen return Falsen
我們通過修改create_action()函數來避免保存重複的動作並且返回一個布爾值來告訴該動作是否保存。下面來解釋我們是如何避免重複動作的:
- 首先,我們通過Django提供的timezone.now()方法來獲取當前時間。這個方法同datetime.datetime.now()相同,但是返回的是一個*timezone-aware*對象。Django提供一個設置叫做*USE_TZ*用來啟用或關閉時區的支持。通過使用*startproject*命令創建的默認*settings.py*包含USE_TZ=True`。
- 我們使用last_minute變數來保存一分鐘前的時間,然後我們取回用戶從那以後執行的任意一個相同操作。
- 我們會創建一個Action對象如果在最後的一分鐘內沒有存在同樣的動作。我們會返回True如果一個Action對象被創建,否則返回False。
添加用戶動作給活動流(activity stream)
是時候添加一些動作給我們的視圖(views)來給我的用戶構建活動流(activity stream)了。我們將要存儲一個動作為以下的每一個實例:
- 一個用戶給某張圖片打上書籤
- 一個用戶喜歡或不喜歡某張圖片
- 一個用戶創建一個賬戶
- 一個用戶關注或不關注某個用戶
編輯images應用下的views.py文件添加以下導入:
from actions.utils import create_actionn
在image_create視圖(view)中,在保存圖片之後添加create-action(),如下所示:
new_item.save()ncreate_action(request.user, bookmarked image, new_item)n
在image_like視圖(view)中,在添加用戶給users_like關係之後添加create_action(),如下所示:
image.users_like.add(request.user)ncreate_action(request.user, likes, image)n
現在編輯account應用中的view.py文件添加以下導入:
from actions.utils import create_actionn
在register視圖(view)中,在創建Profile對象之後添加create-action(),如下所示:
new_user.save()nprofile = Profile.objects.create(user=new_user)ncreate_action(new_user, has created an account)n
在user_follow視圖(view)中添加create_action(),如下所示:
Contact.objects.get_or_create(user_from=request.user,user_to=user)ncreate_action(request.user, is following, user)n
就像你所看到的,感謝我們的Action模型(model)和我們的幫助函數,現在保存新的動作給活動流(activity stream)是非常簡單的。
顯示活動流(activity stream)
最後,我們需要一種方法來給每個用戶顯示活動流(activity stream)。我們將會在用戶的dashboard中包含活動流(activity stream)。編輯account應用的views.py文件。導入Action模型然後修改dashboard視圖(view)如下所示:
from actions.models import Actionnn@login_requiredndef dashboard(request):n # Display all actions by defaultn actions = Action.objects.exclude(user=request.user)n following_ids = request.user.following.values_list(id,flat=True)n if following_ids:n # If user is following others, retrieve only their actionsn actions = actions.filter(user_id__in=following_ids)n actions = actions[:10]nn return render(request,n account/dashboard.html,n {section: dashboard,n actions: actions})n
在這個視圖(view),我們從資料庫取回所有的動作(actions),不包含當前用戶執行的動作。如果當前用戶還沒有關注過任何人,我們展示在平台中的其他用戶的最新動作執行。這是一個默認的行為噹噹前用戶還沒有關注過任何其他的用戶。如果當前用戶已經關注了其他用戶,我們就限制查詢只顯示當前用戶關注的用戶的動作執行。最後,我們限制結果只返回最前面的10個動作。我們在這兒並不使用order_by(),因為我們依賴之前已經在Action模型(model)的Meta的排序選項。最新的動作會首先返回,因為我們在Action模型(model)中設置過ordering = (-created,)。
優化涉及被關聯的對想的查詢集(QuerySets)
每次你取回一個Aciton對象,你都可能存取它的有關聯的User對象,
並且可能這個用戶也關聯它的Profile對象。Django ORM提供了一個簡單的方式一次性取回有關聯的對象,避免對資料庫進行額外的查詢。使用select_related
Django提供了一個叫做select_related()的查詢集(QuerySets)方法允許你取回關係為一對多的關聯對象。該方法將會轉化成一個單獨的,更加複雜的查詢集(QuerySets),但是你可以避免額外的查詢當存取這些關聯對象。select_relate方法是給ForeignKey和OneToOne欄位使用的。它通過執行一個 SQL JOIN並且包含關聯對象的欄位在SELECT聲明中。
為了利用select_related(),編輯之前代碼中的以下行(譯者註:請注意雙下劃線):
actions = actions.filter(user_id__in=following_ids)n
添加select_related在你將要使用的欄位上:
actions = actions.filter(user_id__in=following_ids)n .select_related(user, user__profile)n
我們使用user__profile(譯者註:請注意是雙下劃線)來連接profile表在一個單獨的SQL查詢中。如果你調用select_related()而不傳入任何參數,它會取回所有ForeignKey關係的對象。給select_related()限制的關係將會在隨後一直訪問。
小心的使用select_related()將會極大的提高執行時間
使用prefetch_related
如你所見,select_related()將會幫助你提高取回一對多關係的關聯對象的執行效率。但是,select_related()無法給多對多或者多對一關係(ManyToMany或者倒轉ForeignKey欄位)工作。Django提供了一個不同的查詢集(QuerySets)方法叫做prefetch_realted,該方法在select_related()方法支持的關係上增加了多對多和多對一的關係。prefetch_related()方法為每一種關係執行單獨的查找然後對各個結果進行連接通過使用Python。這個方法還支持GeneriRelation和GenericForeignKey的預先讀取。
完成你的查詢通過為它添加prefetch_related()給目標GenericForeignKey欄位,如下所示:
actions = actions.filter(user_id__in=following_ids)n .select_related(user, user__profile)n .prefetch_related(target)n
這個查詢現在已經被充分利用用來取回包含關聯對象的用戶動作(actions)。
為actions創建模板(templates)
我們要創建一個模板(template)用來顯示一個獨特的Action對象。在actions應用中創建一個新的目錄命名為templates。添加如下文件結構:
actions/n action/n detail.htmln
編輯actions/action/detail.html模板(template)文件添加如下代碼:
明天添加n
這個模板用來顯示一個Action對象。首先,我們使用{% with %}模板標籤(template tag)來獲取用戶操作的動作(action)和他們的profile。然後,我們顯示目標對象的圖片如果Action對象有一個關聯的目標對象。最後,如果有執行過的動作(action),包括動作和目標對象,我們就顯示鏈接給用戶。
現在,編輯account/dashboard.html模板(template)添加如下代碼到content區塊下方:
<h2>Whats happening</h2>n<div id="action-list">n {% for action in actions %}n {% include "actions/action/detail.html" %}n {% endfor %}n</div>n
在瀏覽器中打開 http://127.0.0.1:8000/account/ 。登錄一個存在的用戶並且該用戶執行過一些操作已經被存儲在資料庫中。然後,登錄其他用戶,關注之前登錄的用戶,在dashboard頁面可以看到生成的動作流。如下所示:
django-6-4我們剛剛創建了一個完整的活動流(activity stream)給我們的用戶並且我們還能非常容易的添加新的用戶動作給它。你還可以添加無限的滾動功能給活動流(activity stream)通過集成AJAX分頁處理,和我們之前在image_list視圖(view)使用過的一樣。
給非規範化(denormalizing)計數使用信號
有一些場景,你想要使你的數據非規範化。非規劃化使指在一定的程度上製造一些數據冗餘用來優化讀取的性能。你必須十分小心的使用非規劃化並且只有在你真的非常需要它的時候才能使用。你會發現非規劃化的最大問題就是保持你的非規範化數據更新是非常困難的。
我們將會看到一個例子關於如何改善(improve)我們的查詢通過使用非規範化計數。缺點就是我們不得不保持冗餘數據的更新。我們將要從我們的Image模型(model)中使數據非規範化然後使用Django信號來保持數據的更新。
使用信號進行工作
Django自帶一個信號調度程序允許receiver函數在某個動作出現的時候去獲取通知。信號非常有用,當你需要你的代碼去執行某些事件的時候同時正在發生其他事件。你還能夠創建你自己的信號這樣一來其他人可以在某個事件發生的時候獲得通知。
Django模型(models)提供了幾個信號,它們位於django.db.models.signales。舉幾個例子:
- pre_save 和 post_save:前者會在調用模型(model)的save()方法前發送信號,後者反之。
- pre_delete 和 post_delete:前者會在調用模型(model)或查詢集(QuerySets)的delete()方法之前發送信號,後者反之。
- m2m_changed:當在一個模型(model)上的ManayToManayField被改變的時候發送信號。
以上只是Django提供的一小部分信號。你可以通過訪問 Signals | Django documentation | Django 獲得更多信號資料。
打個比方,你想要獲取熱門圖片。你可以使用Django的聚合函數來獲取圖片,通過圖片獲取的用戶喜歡數量來進行排序。要記住你已經使用過Django聚合函數在第三章 擴展你的blog應用。以下代碼將會獲取圖片並進行排序通過它們被用戶喜歡的數量:
from django.db.models import Countnfrom images.models import Imagenimages_by_popularity = Image.objects.annotate(n total_likes=Count(users_like)).order_by(-total_likes)n
但是,通過統計圖片的總喜歡數量進行排序比直接使用一個已經存儲總統計數的欄位進行排序要消耗更多的性能。你可以添加一個欄位給Image模型(model)用來非規範化喜歡的數量用來提升涉及該欄位的查詢的性能。那麼,問題來了,我們該如何保持這個欄位是最新更新過的。
編輯images應用下的models.py文件,給Image模型(model)添加以下欄位:
total_likes = models.PositiveIntegerField(db_index=True,n default=0)n
total_likes欄位允許我們給每張圖片存儲被用戶喜歡的總數。非規範化數據非常有用當你想要使用他們來過濾或排序查詢集(QuerySets)。
在你使用非規範化欄位之前你必須考慮下其他幾種提高性能的方法。考慮下資料庫索引,最佳化查詢以及緩存在開始規範化你的數據之前。
運行以下命令將新添加的欄位遷移到資料庫中:
python manage.py makemigrations imagesn
你會看到如下輸出:
Migrations for images:n 0002_image_total_likes.py:n - Add field total_likes to imagen
接著繼續運行以下命令來應用遷移:
python manage.py migrate imagesn
輸出中會包含以下內容:
Applying images.0002_image_total_likes... OKn
我們要給m2m_changed信號附加一個receiver函數。在images應用目錄下創建一個新的文件命名為signals.py。給該文件添加如下代碼:
from django.db.models.signals import m2m_changednfrom django.dispatch import receivernfrom .models import Imagen@receiver(m2m_changed, sender=Image.users_like.through)ndef users_like_changed(sender, instance, **kwargs):n instance.total_likes = instance.users_like.count()n instance.save()n
首先,我們使用receiver()裝飾器將users_like_changed函數註冊成一個receiver函數,然後我們將該函數附加給m2m_changed信號。我們將這個函數與Image.users_like.through連接,這樣這個函數只有當m2m_changed信號被Image.users_like.through執行的時候才被調用。還有一個可以替代的方式來註冊一個receiver函數,由使用Signal對象的connect()方法組成。
Django信號是同步阻塞的。不要使用非同步任務導致信號混亂。但是,你可以聯合兩者來執行非同步任務當你的代碼只接受一個信號的通知。
你必須連接你的receiver函數給一個信號,只有這樣它才會被調用當連接的信號發送的時候。有一個推薦的方法用來註冊你的信號是在你的應用配置類中導入它們到ready()方法中。Django提供一個應用註冊允許你對你的應用進行配置和內省。
典型的應用配置類
django允許你指定配置類給你的應用們。為了提供一個自定義的配置給你的應用,創建一個繼承django.apps的Appconfig類的自定義類。這個應用配置類允許你為應用存儲元數據和配置並且提供
內省。你可以通過訪問 https://docs. djangoproject.com/en/1.8/ref/applications/ 獲取更多關於應用配置的信息。
為了註冊你的信號receiver函數,當你使用receiver()裝飾器的時候,你只需要導入信號模塊,這些信號模塊被包含在你的應用的AppConfig類中的ready()方法中。這個方法在應用註冊被完整填充的時候就調用。其他給你應用的初始化都可以被包含在這個方法中。
在images應用目錄下創建一個新的文件命名為apps.py。為該文件添加如下代碼:
from django.apps import AppConfignclass ImagesConfig(AppConfig):n name = imagesn verbose_name = Image bookmarksn def ready(self):n # import signal handlersn import images.signalsn
name屬性定義該應用完整的Python路徑。verbose_name屬性設置了這個應用可讀的名字。它會在管理站點中顯示。ready()方法就是我們為這個應用導入信號的地方。
現在我們需要告訴Django我們的應用配置位於哪裡。編輯位於images應用目錄下的init.py文件添加如下內容:
default_app_config = images.apps.ImagesConfign
打開你的瀏覽器瀏覽一個圖片的詳細頁面然後點擊like按鈕。再進入管理頁面看下該圖片的total_like屬性。你會看到total_likes屬性已經更新了最新的like數如下所示:
django-6-5現在,你可以使用totla_likes屬性來進行熱門圖片的排序或者在任何地方顯示這個值,從而避免了複雜的查詢操作。以下獲取圖片的查詢通過圖片的喜歡數量進行排序:
images_by_popularity = Image.objects.annotate(n likes=Count(users_like)).order_by(-likes)n
現在我們可以用新的查詢來代替上面的查詢:
images_by_popularity = Image.objects.order_by(-total_likes)n
以上查詢的返回結果只需要很少的SQL查詢性能。以上就是一個例子關於如何使用Django信號。
小心使用信號,因為它們會給理解控制流製造困難。在很多場景下你可以避免使用信號如果你知道哪個接收器需要被通知。
使用Redis來存儲視圖(views)項
Redis是一個高級的key-value(鍵值)資料庫允許你保存不同類型的數據並且在I/O(輸入/輸出)操作上非常非常的快速。Redis可以在內存中存儲任何東西,但是這些數據能夠持續通過偶爾存儲數據集到磁碟中或者添加每一條命令到日誌中。Redis是非常出彩的通過與其他的鍵值存儲對比:它提供了一個強大的設置命令,並且支持多種數據結構,例如string,hashes,lists,sets,ordered sets,甚至bitmaps和HyperLogLogs。
SQL最適合用於模式定義的持續數據存儲,而Redis提供了許多優勢當需要處理快速變化的數據,易失性存儲,或者需要一個快速緩存的時候。讓我們看下Redis是如何被使用的,當構建新的功能到我們的項目中。
安裝Redis
從 Redis 下載最新的Redis版本。解壓tar.gz文件,進入redis目錄然後編譯Redis通過使用以下make命令:
cd redis-3.0.4(譯者註:版本根據自己下載的修改)nmake (譯者註:這裡是假設你使用的是linux或者mac系統才有make命令,windows如何操作請看下官方文檔)n
在Redis安裝完成後允許以下shell命令來初始化Redis服務:
src/redis-servern
你會看到輸出的結尾如下所示:
# Server started, Redis version 3.0.4n* DB loaded from disk: 0.001 secondsn* The server is now ready to accept connections on port 6379n
默認的,Redis運行會佔用6379埠,但是你也可以指定一個自定義的埠通過使用--port標誌,例如:redis-server --port 6655。當你的服務啟動完畢,你可以在其他的終端中打開Redis客戶端通過使用如下命令:
src/redis-clin
你會看到Redis客戶端shell如下所示:
127.0.0.1:6379>n
Redis客戶端允許你在當前shell中立即執行Rdis命令。來我們來嘗試一些命令。鍵入SET命令在Redis客戶端中存儲一個值到一個鍵中:
127.0.0.1:6379> SET name "Peter"nokn
以上的命令創建了一個帶有字元串「Peter」值的name鍵到Redis資料庫中。OK輸出表明該鍵已經被成功保存。然後,使用GET命令獲取之前的值,如下所示:
127.0.0.1:6379> GET namen"Peter"n
你還可以檢查一個鍵是否存在通過使用EXISTS命令。如果檢查的鍵存在會返回1,反之返回0:
127.0.0.1:6379> EXISTS namen(integer) 1n
你可以給一個鍵設置到期時間通過使用EXPIRE命令,該命令允許你設置該鍵能在幾秒內存在。另一個選項使用EXPIREAT命令來期望一個Unix時間戳。鍵的到期消失是非常有用的當將Redis當做緩存使用或者存儲易失性的數據:
127.0.0.1:6379> GET namen"Peter"n127.0.0.1:6379> EXPIRE name 2n(integer) 1nWait for 2 seconds and try to get the same key again:n127.0.0.1:6379> GET namen(nil)n
(nil)響應是一個空的響應說明沒有找到鍵。你還可以通過使用DEL命令刪除任意鍵,如下所示:
127.0.0.1:6379> SET total 1nOKn127.0.0.1:6379> DEL totaln(integer) 1n127.0.0.1:6379> GET totaln(nil)n
以上只是一些鍵選項的基本命令。Redis包含了龐大的命令設置給一些數據類型,例如strings,hashes,sets,ordered sets等等。你可以通過訪問 http://redis.io/commands看到所有Reids命令以及通過訪問 http://redis.io/topics/data-types 看到所有Redis支持的數據類型。
通過Python使用Redis
我們需要綁定Python和Redis。通過pip渠道安裝redis-py命令如下:
pip install redis==2.10.3(譯者註:版本可能有更新,如果需要最新版本,可以不帶上==2.10.3後綴)n
你可以訪問 Welcome to redis-py』s documentation! 得到redis-py文檔。
redis-py提供兩個類用來與Redis交互:StrictRedis和Redis。兩者提供了相同的功能。StrictRedis類嘗試遵守官方的Redis命令語法。Redis類型繼承Strictredis重寫了部分方法來提供向後的兼容性。我們將會使用StrictRedis類,因為它遵守Redis命令語法。打開Python shell執行以下命令:
>>> import redisn>>> r = redis.StrictRedis(host=localhost, port=6379, db=0)n
上面的代碼創建了一個與Redis資料庫的連接。在Redis中,資料庫通過一個整形索引替代資料庫名字來辨識。默認的,一個客戶端被連接到資料庫 0 。Reids資料庫可用的數字設置到16,但是你可以在redis.conf文件中修改這個值。
現在使用Python shell設置一個鍵:
>>> r.set(foo, bar)nTruen
以上命令返回Ture表明這個鍵已經創建成功。現在你可以使用get()命令取回該鍵:
>>> r.get(foo)nbarn
如你所見,StrictRedis方法遵守Redis命令語法。
讓我們集成Rdies到我們的項目中。編輯bookmarks項目的settings.py文件添加如下設置:
REDIS_HOST = localhostnREDIS_PORT = 6379nREDIS_DB = 0n
以上設置了Redis伺服器和我們將要在項目中使用到的資料庫。
存儲視圖(vies)項到Redis中
讓我們存儲一張圖片被查看的總次數。如果我們通過Django ORM來完成這個操作,它會在每次該圖片顯示的時候執行一次SQL UPDATE聲明。使用Redis,我們只需要對一個計數器進行增量存儲在內存中,從而帶來更好的性能。
編輯images應用下的views.py文件,添加如下代碼:
import redisnfrom django.conf import settingsn# connect to redisnr = redis.StrictRedis(host=settings.REDIS_HOST,n port=settings.REDIS_PORT,n db=settings.REDIS_DB)n
在這兒我們建立了Redis的連接為了能在我們的視圖(views)中使用它。編輯images_detail視圖(view)使它看上去如下所示:
def image_detail(request, id, slug):nimage = get_object_or_404(Image, id=id, slug=slug)n# increment total image views by 1ntotal_views = r.incr(image:{}:views.format(image.id)) nreturn render(request,n images/image/detail.html,n {section: images,n image: image,n total_views: total_views})n
在這個視圖(view)中,我們使用INCR命令,它會從1開始增量一個鍵的值,在執行這個操作之前如果鍵不存在,它會將值設定為0.incr()方法在執行操作後會返回鍵的值,然後我們可以存儲該值到total_views變數中。我們構建Rddis鍵使用一個符號,比如 object-type:id:field (for example image:33:id) 。
對Redis的鍵進行命名有一個慣例是使用冒號進行分割來創建鍵的命名空間。做到這點,鍵的名字會特別冗長,有關聯的鍵會分享部分相同的模式在它們的名字中。
編輯image/detail.html模板(template)在已有的<span class="count">元素之後添加如下代碼:
<span class="count">n <span class="total">{{ total_views }}</span>n view{{ total_views|pluralize }}n</span>n
現在在瀏覽器中打開一張圖片的詳細頁面然後多次載入該頁面。你會看到每次該視圖(view)被執行的時候,總的觀看次數會增加 1 。如下所示:
django-6-6你已經成功的集成Redis到你的項目中來存儲項統計。
存儲一個排名到Reids中
讓我們使用Reids構建更多的功能。我們要在我們的平台中創建一個最多瀏覽次數的圖片排行。為了構建這個排行我們將要使用Redis分類集合。一個分類集合是一個非重複的字元串採集,其中每個成員和一個分數關聯。其中的項根據它們的分數進行排序。
編輯images引用下的views.py文件,使image_detail視圖(view)看上去如下所示:
def image_detail(request, id, slug):nimage = get_object_or_404(Image, id=id, slug=slug)n# increment total image views by 1ntotal_views = r.incr(image:{}:views.format(image.id)) # increment image ranking by 1 nr.zincrby(image_ranking, image.id, 1)nreturn render(request,n images/image/detail.html,n {section: images,n image: image,n total_views: total_views})n
我們使用zincrby()命令存儲圖片視圖(views)到一個分類集合中通過鍵image:ranking。我們存儲圖片id,和一個分數1,它們將會被加到分類集合中這個元素的總分上。這將允許我們在全局上持續跟蹤所有的圖片視圖(views),並且有一個分類集合,該分類集合通過圖片的瀏覽次數進行排序。
現在創建一個新的視圖(view)用來展示最多瀏覽次數圖片的排行。在views.py文件中添加如下代碼:
@login_requiredndef image_ranking(request):n # get image ranking dictionaryn image_ranking = r.zrange(image_ranking, 0, -1,n desc=True)[:10]n image_ranking_ids = [int(id) for id in image_ranking]n # get most viewed imagesn most_viewed = list(Image.objects.filter(n id__in=image_ranking_ids))n most_viewed.sort(key=lambda x: image_ranking_ids.index(x.id))n return render(request,n images/image/ranking.html,n {section: images,n most_viewed: most_viewed})n
以上就是image_ranking視圖。我們使用zrange()命令獲得分類集合中的元素。這個命令期望一個自定義的範圍,最低分和最高分。通過將 0 定為最低分, -1 為最高分,我們告訴Redis返回分類集合中的所有元素。最終,我們使用[:10]對結果進行切片獲取最前面十個最高分的元素。我們構建一個返回的圖片IDs的列,然後我們將該列存儲在image_ranking_ids變數中,這是一個整數列。我們通過這些IDs取回對應的Image對象,並將它們強制轉化為列通過使用list()函數。強制轉化查詢集(QuerySets)的執行是非常重要的,因為接下來我們要在該列上使用列的sort()方法(就是因為這點所以我們需要的是一個對象列而不是一個查詢集(QuerySets))。我們排序這些Image對象通過它們在圖片排行中的索引。現在我們可以在我們的模板(template)中使用most_viewed列來顯示10個最多瀏覽次數的圖片。
創建一個新的image/ranking.html模板(template)文件,添加如下代碼:
{% extends "base.html" %}nn{% block title %}Images ranking{% endblock %}nn{% block content %}n <h1>Images ranking</h1>n <ol>n {% for image in most_viewed %}n <li>n <a href="{{ image.get_absolute_url }}">n {{ image.title }}n </a> n </li>n {% endfor %}n </ol>n{% endblock %}n
這個模板(template)非常簡單明了,我們只是對包含在most_viewed中的Image對象進行迭代。
最後為新的視圖(view)創建一個URL模式。編輯images應用下的urls.py文件,添加如下內容:
url(r^ranking/$, views.image_ranking, name=create),n
在瀏覽器中打開 http://127.0.0.1:8000/images/ranking/ 。你會看到如下圖片排行:
django-6-7Redis的下一步
Redis並不能替代你的SQL資料庫,但是它是一個內存中的快速存儲,更適合某些特定任務。將它添加到你的棧中使用當你真的感覺它很需要。以下是一些適合Redis的場景:
- Counting:如你之前看到的,通過Redis管理計數器非常容易。你可以使用incr()和`incrby()。
- Storing latest items:你可以添加項到一個列的開頭和結尾通過使用lpush()和rpush()。移除和返回開頭和結尾的元素通過使用lpop()以及rpop()。你可以削減列的長度通過使用ltrim()來維持它的長度。
- Queues:除了push和pop命令,Redis還提供堵塞的隊列命令。
- Caching:使用expire()和expireat()允許你將Redis當成緩存使用。你還可以找到第三方的Reids緩存後台給Django使用。
- Pub/Sub:Redis提供命令給訂閱或不訂閱,並且給渠道發送消息。
- Rankings and leaderboards:Redis使用分數的分類集合使創建排行榜非常的簡單。
- Real-time tracking:Redis快速的I/O(輸入/輸出)使它能完美支持實時場景。
總結
在本章中,你構建了一個粉絲系統和一個用戶活動流(activity stream)。你學習了Django信號是如何進行工作並且在你的項目中集成了Redis。
在下一章中,你會學習到如何構建一個在線商店。你會創建一個產品目錄並且通過會話(sessions)創建一個購物車。你還會學習如何通過Celery執行非同步任務。
譯者總結:
這一章好長啊!最後部分的Redis感覺最實用。準備全書翻譯好後再抽時間把翻譯好的所有章節全部重新校對下!那麼大家下章再見!祈禱我年終中大獎!
推薦閱讀: