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

全文鏈接

第一章 創建一個blog應用
第二章 使用高級特性來增強你的blog
第三章 擴展你的blog應用
第四章上 創建一個社交網站
第四章下 創建一個社交網站
第五章 在你的網站中分享內容
第六章 跟蹤用戶動作
第七章 建立一個在線商店
第八章 管理付款和訂單
第九章上 擴展你的商店
第九章下 擴展你的商店
第十章上 創建一個在線學習平臺
第十章下 創建一個在線學習平臺
第十一章 緩存內容
第十二章 構建一個API

書籍出處:https://www.packtpub.com/web-development/django-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 User
   class Contact(models.Model):
       user_from = models.ForeignKey(User,
                                     related_name='rel_from_set')
       user_to = models.ForeignKey(User,
                                   related_name='rel_to_set')
       created = models.DateTimeField(auto_now_add=True,
                                      db_index=True)
       class Meta:
           ordering = ('-created',)
       def __str__(self):
           return '{} follows {}'.format(self.user_from,
self.user_to)

這個Contact模型我們將會給用戶關系使用。它包含以下字段:

  • user_form:一個ForeignKey指向創建關系的用戶
  • user_to:一個ForeignKey指向被關注的用戶
  • created:一個auto_now_add=TrueDateTimeField字段用來存儲關系創建時的時間

ForeignKey字段上會自動生成一個數據庫索引。我們使用db_index=True來創建一個數據庫索引給created字段。這會提升查詢執行的效率當通過這個字段對查詢集(QuerySets)進行排序的時候。

使用 ORM ,我們可以創建一個關系給一個用戶 user1 關注另一個用戶 user2,如下所示:

user1 = User.objects.get(id=1)
user2 = User.objects.get(id=2)
Contact.objects.create(user_from=user1, user_to=user2)

關系管理器 rel_form_setrel_to_set 會返回一個查詢集(QuerySets)給Contace模型(model)。為了
User模型(model)中存取最終的關系側,Contace模型(model)會期望User包含一個ManyToManyField,如下所示(譯者注:以下代碼是作者假設的,實際上User不會包含以下代碼):

following = models.ManyToManyField('self',
                                   through=Contact,
                                   related_name='followers',
                                   symmetrical=False)

在這個例子中,我們告訴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 dynamically
User.add_to_class('following',
                   models.ManyToManyField('self',
                                          through=Contact,
                                          related_name='followers',
                                          symmetrical=False))

在以上代碼中,我們使用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),可以訪問 https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#specifying-a-custom-user-model 獲得更多信息。

你能看到上述代碼中的關系包含了symmetrical=Flase來定義一個非對稱(non-symmetric)關系。這表示如果我關注了你,你不會自動的關注我。

當你使用了一個中介模型(intermediate model)給多對多關系,一些關系管理器的方法將不可用,例如:add()create()以及remove()。你需要創建或刪除中介模型(intermediate model)的實例來代替。

運行如下命令來生成account應用的初始遷移:

python manage.py makemigrations account

你會看到如下輸出:

Migrations for 'account':
     0002_contact.py:
       - Create model Contact

現在繼續運行以下命令來同步應用到數據庫中:

python manage.py migrate account

你會看到如下內容包含在輸出中:

Applying account.0002_contact... OK

Contact模型(model)現在已經被同步進了數據庫,我們可以在用戶之間創建關系。但是,我們的網站還沒有提供一個方法來瀏覽用戶或查看詳細的用戶profile。讓我們為User模型構建列表和詳情視圖(views)。

為用戶profiles創建列表和詳情視圖(views)

打開account應用中的views.py文件添加如下代碼:

from django.shortcuts import get_object_or_404
from django.contrib.auth.models import User
   @login_required
   def user_list(request):
       users = User.objects.filter(is_active=True)
       return render(request,
                     'account/user/list.html',
                     {'section': 'people',
                      'users': users})
   @login_required
   def user_detail(request, username):
       user = get_object_or_404(User,
                                username=username,
                                is_active=True)
       return render(request,
                     'account/user/detail.html',
                     {'section': 'people',
                      'user': user})

以上是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 = [
       # ...
       url(r'^users/$', views.user_list, name='user_list'),
       url(r'^users/(?P<username>[-\w]+)/$',
           views.user_detail,
           name='user_detail'),
]

我們會使用 user_detail URL模式來給用戶生成規范的URL。你之前就在模型(model)中定義了一個get_absolute_url()方法來為每個對象返回規范的URL。另外一種方式為一個模型(model)指定一個URL是為你的項目添加ABSOLUTE_URL_OVERRIDES設置。

編輯項目中的setting.py文件,添加如下代碼:

ABSOLUTE_URL_OVERRIDES = {
    'auth.user': lambda u: reverse_lazy('user_detail',
                                        args=[u.username])
}

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 User
>>> user = User.objects.latest('id')
>>> str(user.get_absolute_url())
'/account/users/ellington/'

返回的URL如同期望的一樣。我們需要為我們剛才創建的視圖(views)創建模板(templates)。在account應用下的*templates/account/目錄下添加以下目錄和文件:

/user/
    detail.html
    list.html

編輯account/user/list.html模板(template)給它添加如下代碼:

{% extends "base.html" %}
{% load thumbnail %}
{% block title %}People{% endblock %}
{% block content %}
    <h1>People</h1>
    <div id="people-list">
       {% for user in users %}
         <div class="user">
            <a href="{{ user.get_absolute_url }}">
             {% thumbnail user.profile.photo "180x180" crop="100%" as im %}
               ![]({{ im.url }})
             {% endthumbnail %}
           </a>
           <div class="info">
             <a href="{{ user.get_absolute_url }}" class="title">
               {{ user.get_full_name }}
             </a> 
           </div>
         </div>
       {% endfor %}
    </div>
{% endblock %}

這個模板(template)允許我們在網站中排列所有可用的用戶。我們對給予的用戶進行迭代并且使用`{% thumbnail %}模板(template)標簽(tag)來生成profile圖片縮微圖。

打開項目中的base.html模板(template),在以下菜單項的href屬性中包含user_listURL:

<li {% if section == "people" %}class="selected"{% endif %}>
    <a href="{% url "user_list" %}">People</a>
</li>

通過命令python manage.py runserver啟動開發服務器然后在瀏覽器打開 http://127.0.0.1:8000/account/users/ 。你會看到如下所示的用戶列:

django-6-1

(譯者注:圖靈,特斯拉,愛因斯坦,都是大牛啊)

編輯account應用下的account/user/detail.html模板,添加如下代碼:


{% extends "base.html" %}
{% load thumbnail %}
{% block title %}{{ user.get_full_name }}{% endblock %}
{% block content %}
    <h1>{{ user.get_full_name }}</h1>
    <div class="profile-info">
    {% thumbnail user.profile.photo "180x180" crop="100%" as im %}
        ![]({{ im.url }})
    {% endthumbnail %}
    </div>
    {% with total_followers=user.followers.count %}
    <span class="count">
        <span class="total">{{ total_followers }}</span>
        follower{{ total_followers|pluralize }}
    </span>
    <a href="#" data-id="{{ user.id }}" data-action="{% if request.user in user.followers.all %}un{% endif %}follow" class="followbutton">
        {% if request.user not in user.followers.all %}
            Follow
        {% else %}
            Unfollow
        {% endif %}
    </a>
    <div id="image-list" class="image-container">
        {% include "images/image/list_ajax.html" with images=user.images_create.all %}
    </div>
    {% endwith %}
{% endblock %}

在詳情模板(template)中我們展示用戶profile并且我們使用{% thumbnail %}模板(template)標簽(tag)來顯示profile圖片。我們顯示粉絲的總數以及一個鏈接可以 follow/unfollow 該用戶。我們會隱藏關注鏈接當用戶在查看他們自己的profile,防止用戶自己關注自己。我們會執行一個AJAX請求來 follow/unfollow 一個指定用戶。我們給 <a> HTML元素添加data-iddata-action屬性包含用戶ID以及當該鏈接被點擊的時候會執行的初始操作,follow/unfollow ,這個操作依賴當前頁面的展示的用戶是否已經被正在瀏覽的用戶所關注。我們展示當前頁面用戶的圖片書簽通過list_ajax.html模板。

再次打開你的瀏覽器,點擊一個擁有圖片書簽的用戶鏈接,你會看到一個profile詳情如下所示:

django-6-2

創建一個AJAX視圖(view)來關注用戶

我們將會創建一個簡單的視圖(view)使用AJAX來 follow/unfollow 用戶。編輯account應用中的views.py文件添加如下代碼:

from django.http import JsonResponse
from django.views.decorators.http import require_POST
from common.decorators import ajax_required
from .models import Contact
@ajax_required
@require_POST
@login_required
def user_follow(request):
    user_id = request.POST.get('id')
    action = request.POST.get('action')
    if user_id and action:
        try:
            user = User.objects.get(id=user_id)
            if action == 'follow':
                Contact.objects.get_or_create(
                    user_from=request.user,
                    user_to=user)
            else:
                Contact.objects.filter(user_from=request.user,
                                        user_to=user).delete()
            return JsonResponse({'status':'ok'})
        except User.DoesNotExist:
            return JsonResponse({'status':'ko'})
    return JsonResponse({'status':'ko'})

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'),

請確保你放置的這個URL模式的位置在user_detailURL模式之前。否則,任何對 /users/follow/ 的請求都會被user_detail模式給正則匹配然后執行。請記住,每一次的HTTP請求Django都會對每一條存在的URL模式進行匹配直到第一條匹配成功才會停止繼續匹配。

編輯account應用下的user/detail.html模板添加如下代碼:

{% block domready %}
     $('a.follow').click(function(e){
       e.preventDefault();
       $.post('{% url "user_follow" %}',
         {
           id: $(this).data('id'),
           action: $(this).data('action')
         },
         function(data){
           if (data['status'] == 'ok') {
             var previous_action = $('a.follow').data('action');
             
             // toggle data-action
             $('a.follow').data('action',
               previous_action == 'follow' ? 'unfollow' : 'follow');
             // toggle link text
             $('a.follow').text(
               previous_action == 'follow' ? 'Unfollow' : 'Follow');
               
             // update total followers
             var previous_followers = parseInt(
               $('span.count .total').text());
             $('span.count .total').text(previous_action == 'follow' ? previous_followers + 1 : previous_followers - 1);
          }
        }
      });
    });
{% endblock %}

這段JavaScript代碼執行AJAX請求來關注或不關注一個指定用戶并且觸發 follow/unfollow 鏈接。我們使用jQuery去執行AJAX請求的同時會設置 follow/unfollow 兩種鏈接的data-aciton屬性以及HTML<a>元素的文本基于它上一次的值。當AJAX操作執行完成,我們還會對顯示在頁面中的粉絲總數進行更新。打開一個存在的用戶的詳情頁面,然后點擊Follow鏈接嘗試下我們剛才構建的功能是否正常。

創建一個通用的活動流(activity stream)應用

許多社交網站會給他們的用戶顯示一個活動流(activity stream),這樣他們可以跟蹤其他用戶在平臺中的操作。一個活動流(activity stream)是一個用戶或一個用戶組最近活動的列表。舉個例子,FacebookNews Feed就是一個活動流(activity stream)。用戶X給Y圖片打上了書簽或者用戶X關注了用戶Y也是例子操作。我們將會構建一個活動流(activity stream)應用這樣每個用戶都能看到他關注的用戶最近進行的交互。為了做到上述功能,我們需要一個模型(modes)來保存用戶在網站上的操作執行,還需要一個簡單的方法來添加操作給feed。

運行以下命令在你的項目中創建一個新的應用命名為actions

django-admin startapp actions

在你的項目中的settings.py文件中的INSTALLED_APPS設置中添加'actions',這樣可以讓Django知道這個新的應用是可用狀態:

INSTALLED_APPS = (
    # ...
    'actions',    
)

編輯actions應用下的models.py文件添加如下代碼:

from django.db import models
from django.contrib.auth.models import User

class Action(models.Model):
    user = models.ForeignKey(User,
                            related_name='actions',
                            db_index=True)
    verb = models.CharField(max_length=255)
    created = models.DateTimeField(auto_now_add=True,
                                    db_index=True)
                                    
    class Meta:
        ordering = ('-created',)

這個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_labelmodel屬性的查詢,例如:

>>> from django.contrib.contenttypes.models import ContentType
>>> image_type = ContentType.objects.get(app_label='images',model='image')
>>> image_type
<ContentType: image>

你還能反過來獲取到模型(model)類從一個ContentType對象中通過調用它的model_class()方法:

>>> from images.models import Image
>>> ContentType.objects.get_for_model(Image)
<ContentType: image>

以上就是內容類型的一些例子。Django提供了更多的方法來使用他們進行工作。你可以訪問 https://docs.djangoproject.com/en/1.8/ref/contrib/contenttypes/ 找到關于內容類型框架的官方文檔。

添加通用的關系給你的模型(models)

在通用關系中ContentType對象扮演指向模型(model)的角色被關聯所使用。你需要3個字段在模型(model)中組織一個通用關系:

  • 一個ForeignKey字段ContentType。這個字段會告訴我們給這個關聯的模型(model)。
  • 一個字段用來存儲被關聯對象的primary key。這個字段通常是一個PositiveIntegerField用來匹配Django自動的primary key字段。
  • 一個字段用來定義和管理通用關系通過使用前面的兩個字段。內容類型框架提供一個GenericForeignKey字段來完成這個目標。

編輯actions應用的models.py文件,添加如下代碼:

from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
class Action(models.Model):
    user = models.ForeignKey(User,
                             related_name='actions',
                             db_index=True)
    verb = models.CharField(max_length=255)
    target_ct = models.ForeignKey(ContentType,
                                  blank=True,
                                  null=True,
                                  related_name='target_obj')
    target_id = models.PositiveIntegerField(null=True,
                                            blank=True,
                                            db_index=True)
    target = GenericForeignKey('target_ct', 'target_id')
    created = models.DateTimeField(auto_now_add=True,
                                   db_index=True)
    class Meta:
        ordering = ('-created',)

我們給Action模型添加了以下字段:

  • target_ct:一個ForeignKey字段指向ContentType模型(model)。
  • target_id:一個PositiveIntegerField用來存儲被關聯對象的primary key。
  • target:一個GenericForeignKey字段指向被關聯的對象基于前面兩個字段的組合之上。

Django沒有創建任何字段在數據庫中給GenericForeignKey字段。只有target_cttarget_id兩個字段被映射到數據庫字段。兩個字段都有blank=Truenull=True屬性所以一個target(目標)對象不是必須的當保存Action對象的時候。

你可以讓你的應用更加靈活通過使用通用關系替代外鍵當它對擁有一個通用關系有意義。

運行以下命令來創建初始遷移為這個應用:

python manage.py makemigrations actions

你會看到如下輸出:

    Migrations for 'actions':
        0001_initial.py:
            - Create model Action

接著,運行下一條命令來同步應用到數據庫中:

python manage.py migrate

這條命令的輸出表明新的遷移已經被應用:

Applying actions.0001_initial... OK

讓我們在管理站點中添加Action模型(model)。編輯actions應用的admin.py文件,添加如下代碼:

from django.contrib import admin
from .models import Action

class ActionAdmin(admin.ModelAdmin):
    list_display = ('user', 'verb', 'target', 'created')
    list_filter = ('created',)
    search_fields = ('verb',)
    
admin.site.register(Action, ActionAdmin)

你已經將Action模型(model)注冊到了管理站點中。運行命令python manage.py runserver來初始化開發服務器然后在瀏覽器中打開 http://127.0.0.1:8000/admin/actions/action/add/ 。你會看到如下頁面可以創建一個新的Action對象:

django-6-3

如你所見,只有target_cttarget_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 ContentType
from .models import Action
def create_action(user, verb, target=None):
    action = Action(user=user, verb=verb, target=target)
    action.save()

create_action()函數允許我們創建actions,該actions可以包含一個target對象或不包含。我們可以使用這個函數在我們代碼的任何地方添加新的actions給活動流(activity stream)。

在活動流(activity stream)中避免重復的操作

有時候你的用戶可能多次執行同個動作。他們可能在短時間內多次點擊 like/unlike 按鈕或者多次執行同樣的動作。這會導致你停止存儲和顯示重復的動作。為了避免這種情況我們需要改善create_action()函數來避免大部分的重復動作。

編輯actions應用中的utils.py文件使它看上去如下所示:

import datetime
from django.utils import timezone
from django.contrib.contenttypes.models import ContentType
from .models import Action

def create_action(user, verb, target=None):
    # check for any similar action made in the last minute
    now = timezone.now()
    last_minute = now - datetime.timedelta(seconds=60)
    similar_actions = Action.objects.filter(user_id=user.id,
                                            verb= verb,
                                        created__gte=last_minute)
    if target:
        target_ct = ContentType.objects.get_for_model(target)
        similar_actions = similar_actions.filter(
                                            target_ct=target_ct,
                                            target_id=target.id)
    if not similar_actions:
        # no existing actions found
        action = Action(user=user, verb=verb, target=target)
        action.save()
        return True
    return False

我們通過修改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_action

image_create視圖(view)中,在保存圖片之后添加create-action(),如下所示:

new_item.save()
create_action(request.user, 'bookmarked image', new_item)

image_like視圖(view)中,在添加用戶給users_like關系之后添加create_action(),如下所示:

image.users_like.add(request.user)
create_action(request.user, 'likes', image)

現在編輯account應用中的view.py文件添加以下導入:

from actions.utils import create_action

register視圖(view)中,在創建Profile對象之后添加create-action(),如下所示:

new_user.save()
profile = Profile.objects.create(user=new_user)
create_action(new_user, 'has created an account')

user_follow視圖(view)中添加create_action(),如下所示:

Contact.objects.get_or_create(user_from=request.user,user_to=user)
create_action(request.user, 'is following', user)

就像你所看到的,感謝我們的Action模型(model)和我們的幫助函數,現在保存新的動作給活動流(activity stream)是非常簡單的。

顯示活動流(activity stream)

最后,我們需要一種方法來給每個用戶顯示活動流(activity stream)。我們將會在用戶的dashboard中包含活動流(activity stream)。編輯account應用的views.py文件。導入Action模型然后修改dashboard視圖(view)如下所示:

from actions.models import Action

@login_required
def dashboard(request):
    # Display all actions by default
    actions = Action.objects.exclude(user=request.user)
    following_ids = request.user.following.values_list('id',flat=True)
    if following_ids:
        # If user is following others, retrieve only their actions
        actions = actions.filter(user_id__in=following_ids)
    actions = actions[:10]
    
    return render(request,
                  'account/dashboard.html',
                  {'section': 'dashboard',
                    'actions': actions})

在這個視圖(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方法是給ForeignKeyOneToOne字段使用的。它通過執行一個 SQL JOIN并且包含關聯對象的字段在SELECT 聲明中。

為了利用select_related(),編輯之前代碼中的以下行(譯者注:請注意雙下劃線):

actions = actions.filter(user_id__in=following_ids)

添加select_related在你將要使用的字段上:

actions = actions.filter(user_id__in=following_ids)\
                    .select_related('user', 'user__profile')

我們使用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。這個方法還支持GeneriRelationGenericForeignKey的預先讀取。

完成你的查詢通過為它添加prefetch_related()給目標GenericForeignKey字段,如下所示:

actions = actions.filter(user_id__in=following_ids)\
                 .select_related('user', 'user__profile')\
                 .prefetch_related('target')

這個查詢現在已經被充分利用用來取回包含關聯對象的用戶動作(actions)。

actions創建模板(templates)

我們要創建一個模板(template)用來顯示一個獨特的Action對象。在actions應用中創建一個新的目錄命名為templates。添加如下文件結構:

actions/
    action/
        detail.html

編輯actions/action/detail.html模板(template)文件添加如下代碼:

明天添加

這個模板用來顯示一個Action對象。首先,我們使用{% with %}模板標簽(template tag)來獲取用戶操作的動作(action)和他們的profile。然后,我們顯示目標對象的圖片如果Action對象有一個關聯的目標對象。最后,如果有執行過的動作(action),包括動作和目標對象,我們就顯示鏈接給用戶。

現在,編輯account/dashboard.html模板(template)添加如下代碼到content區塊下方:

<h2>What's happening</h2>
<div id="action-list">
    {% for action in actions %}
        {% include "actions/action/detail.html" %}
    {% endfor %}
</div>

在瀏覽器中打開 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_savepost_save:前者會在調用模型(model)的save()方法前發送信號,后者反之。
  • pre_deletepost_delete:前者會在調用模型(model)或查詢集(QuerySets)的delete()方法之前發送信號,后者反之。
  • m2m_changed:當在一個模型(model)上的ManayToManayField被改變的時候發送信號。

以上只是Django提供的一小部分信號。你可以通過訪問 https://docs.djangoproject.com/en/1.8/ref/signals/ 獲得更多信號資料。

打個比方,你想要獲取熱門圖片。你可以使用Django的聚合函數來獲取圖片,通過圖片獲取的用戶喜歡數量來進行排序。要記住你已經使用過Django聚合函數在第三章 擴展你的blog應用。以下代碼將會獲取圖片并進行排序通過它們被用戶喜歡的數量:

from django.db.models import Count
from images.models import Image
images_by_popularity = Image.objects.annotate(
    total_likes=Count('users_like')).order_by('-total_likes')

但是,通過統計圖片的總喜歡數量進行排序比直接使用一個已經存儲總統計數的字段進行排序要消耗更多的性能。你可以添加一個字段給Image模型(model)用來非規范化喜歡的數量用來提升涉及該字段的查詢的性能。那么,問題來了,我們該如何保持這個字段是最新更新過的。

編輯images應用下的models.py文件,給Image模型(model)添加以下字段:

total_likes = models.PositiveIntegerField(db_index=True,
                                          default=0)

total_likes字段允許我們給每張圖片存儲被用戶喜歡的總數。非規范化數據非常有用當你想要使用他們來過濾或排序查詢集(QuerySets)。

在你使用非規范化字段之前你必須考慮下其他幾種提高性能的方法。考慮下數據庫索引,最佳化查詢以及緩存在開始規范化你的數據之前。

運行以下命令將新添加的字段遷移到數據庫中:

python manage.py makemigrations images

你會看到如下輸出:

Migrations for 'images':
    0002_image_total_likes.py:
        - Add field total_likes to image

接著繼續運行以下命令來應用遷移:

python manage.py migrate images

輸出中會包含以下內容:

Applying images.0002_image_total_likes... OK

我們要給m2m_changed信號附加一個receiver函數。在images應用目錄下創建一個新的文件命名為signals.py。給該文件添加如下代碼:

from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from .models import Image
@receiver(m2m_changed, sender=Image.users_like.through)
def users_like_changed(sender, instance, **kwargs):
    instance.total_likes = instance.users_like.count()
    instance.save()

首先,我們使用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.appsAppconfig類的自定義類。這個應用配置類允許你為應用存儲元數據和配置并且提供
內省。

你可以通過訪問 https://docs. djangoproject.com/en/1.8/ref/applications/ 獲取更多關于應用配置的信息。

為了注冊你的信號receiver函數,當你使用receiver()裝飾器的時候,你只需要導入信號模塊,這些信號模塊被包含在你的應用的AppConfig類中的ready()方法中。這個方法在應用注冊被完整填充的時候就調用。其他給你應用的初始化都可以被包含在這個方法中。

images應用目錄下創建一個新的文件命名為apps.py。為該文件添加如下代碼:

from django.apps import AppConfig
class ImagesConfig(AppConfig):
    name = 'images'
    verbose_name = 'Image bookmarks'
    def ready(self):
        # import signal handlers
        import images.signals

name屬性定義該應用完整的Python路徑。verbose_name屬性設置了這個應用可讀的名字。它會在管理站點中顯示。ready()方法就是我們為這個應用導入信號的地方。

現在我們需要告訴Django我們的應用配置位于哪里。編輯位于images應用目錄下的init.py文件添加如下內容:

default_app_config = 'images.apps.ImagesConfig'

打開你的瀏覽器瀏覽一個圖片的詳細頁面然后點擊like按鈕。再進入管理頁面看下該圖片的total_like屬性。你會看到total_likes屬性已經更新了最新的like數如下所示:

django-6-5

現在,你可以使用totla_likes屬性來進行熱門圖片的排序或者在任何地方顯示這個值,從而避免了復雜的查詢操作。以下獲取圖片的查詢通過圖片的喜歡數量進行排序:

images_by_popularity = Image.objects.annotate(
    likes=Count('users_like')).order_by('-likes')

現在我們可以用新的查詢來代替上面的查詢:

images_by_popularity = Image.objects.order_by('-total_likes')

以上查詢的返回結果只需要很少的SQL查詢性能。以上就是一個例子關于如何使用Django信號。

小心使用信號,因為它們會給理解控制流制造困難。在很多場景下你可以避免使用信號如果你知道哪個接收器需要被通知。

使用Redis來存儲視圖(views)項

Redis是一個高級的key-value(鍵值)數據庫允許你保存不同類型的數據并且在I/O(輸入/輸出)操作上非常非常的快速。Redis可以在內存中存儲任何東西,但是這些數據能夠持續通過偶爾存儲數據集到磁盤中或者添加每一條命令到日志中。Redis是非常出彩的通過與其他的鍵值存儲對比:它提供了一個強大的設置命令,并且支持多種數據結構,例如string,hashes,lists,sets,ordered sets,甚至bitmaps和HyperLogLogs。

SQL最適合用于模式定義的持續數據存儲,而Redis提供了許多優勢當需要處理快速變化的數據,易失性存儲,或者需要一個快速緩存的時候。讓我們看下Redis是如何被使用的,當構建新的功能到我們的項目中。

安裝Redis

http://redis.io/download 下載最新的Redis版本。解壓tar.gz文件,進入redis目錄然后編譯Redis通過使用以下make命令:

cd redis-3.0.4(譯者注:版本根據自己下載的修改)
make (譯者注:這里是假設你使用的是linux或者mac系統才有make命令,windows如何操作請看下官方文檔)

在Redis安裝完成后允許以下shell命令來初始化Redis服務:

src/redis-server

你會看到輸出的結尾如下所示:

# Server started, Redis version 3.0.4
* DB loaded from disk: 0.001 seconds
* The server is now ready to accept connections on port 6379

默認的,Redis運行會占用6379端口,但是你也可以指定一個自定義的端口通過使用--port標志,例如:redis-server --port 6655。當你的服務啟動完畢,你可以在其他的終端中打開Redis客戶端通過使用如下命令:

src/redis-cli

你會看到Redis客戶端shell如下所示:

127.0.0.1:6379>

Redis客戶端允許你在當前shell中立即執行Rdis命令。來我們來嘗試一些命令。鍵入SET命令在Redis客戶端中存儲一個值到一個鍵中:

127.0.0.1:6379> SET name "Peter"
ok

以上的命令創建了一個帶有字符串“Peter”值的name鍵到Redis數據庫中。OK輸出表明該鍵已經被成功保存。然后,使用GET命令獲取之前的值,如下所示:

127.0.0.1:6379> GET name
"Peter"

你還可以檢查一個鍵是否存在通過使用EXISTS命令。如果檢查的鍵存在會返回1,反之返回0:

127.0.0.1:6379> EXISTS name
(integer) 1

你可以給一個鍵設置到期時間通過使用EXPIRE命令,該命令允許你設置該鍵能在幾秒內存在。另一個選項使用EXPIREAT命令來期望一個Unix時間戳。鍵的到期消失是非常有用的當將Redis當做緩存使用或者存儲易失性的數據:

127.0.0.1:6379> GET name
"Peter"
127.0.0.1:6379> EXPIRE name 2
(integer) 1
Wait for 2 seconds and try to get the same key again:
127.0.0.1:6379> GET name
(nil)

(nil)響應是一個空的響應說明沒有找到鍵。你還可以通過使用DEL命令刪除任意鍵,如下所示:

127.0.0.1:6379> SET total 1
OK
127.0.0.1:6379> DEL total
(integer) 1
127.0.0.1:6379> GET total
(nil)

以上只是一些鍵選項的基本命令。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'后綴)

你可以訪問 http://redis-py.readthedocs.org/ 得到redis-py文檔。

redis-py提供兩個類用來與Redis交互:StrictRedisRedis。兩者提供了相同的功能。StrictRedis類嘗試遵守官方的Redis命令語法。Redis類型繼承Strictredis重寫了部分方法來提供向后的兼容性。我們將會使用StrictRedis類,因為它遵守Redis命令語法。打開Python shell執行以下命令:

>>> import redis
>>> r = redis.StrictRedis(host='localhost', port=6379, db=0)

上面的代碼創建了一個與Redis數據庫的連接。在Redis中,數據庫通過一個整形索引替代數據庫名字來辨識。默認的,一個客戶端被連接到數據庫 0 。Reids數據庫可用的數字設置到16,但是你可以在redis.conf文件中修改這個值。

現在使用Python shell設置一個鍵:

>>> r.set('foo', 'bar')
True

以上命令返回Ture表明這個鍵已經創建成功。現在你可以使用get()命令取回該鍵:

>>> r.get('foo')
'bar'

如你所見,StrictRedis方法遵守Redis命令語法。

讓我們集成Rdies到我們的項目中。編輯bookmarks項目的settings.py文件添加如下設置:

REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_DB = 0

以上設置了Redis服務器和我們將要在項目中使用到的數據庫。

存儲視圖(vies)項到Redis中

讓我們存儲一張圖片被查看的總次數。如果我們通過Django ORM來完成這個操作,它會在每次該圖片顯示的時候執行一次SQL UPDATE聲明。使用Redis,我們只需要對一個計數器進行增量存儲在內存中,從而帶來更好的性能。

編輯images應用下的views.py文件,添加如下代碼:

import redis
from django.conf import settings
# connect to redis
r = redis.StrictRedis(host=settings.REDIS_HOST,
                      port=settings.REDIS_PORT,
                      db=settings.REDIS_DB)

在這兒我們建立了Redis的連接為了能在我們的視圖(views)中使用它。編輯images_detail視圖(view)使它看上去如下所示:

def image_detail(request, id, slug):
image = get_object_or_404(Image, id=id, slug=slug)
# increment total image views by 1
total_views = r.incr('image:{}:views'.format(image.id)) 
return render(request,
              'images/image/detail.html',
              {'section': 'images',
               'image': image,
               'total_views': total_views})

在這個視圖(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">
     <span class="total">{{ total_views }}</span>
     view{{ total_views|pluralize }}
</span>

現在在瀏覽器中打開一張圖片的詳細頁面然后多次加載該頁面。你會看到每次該視圖(view)被執行的時候,總的觀看次數會增加 1 。如下所示:

django-6-6

你已經成功的集成Redis到你的項目中來存儲項統計。

存儲一個排名到Reids中

讓我們使用Reids構建更多的功能。我們要在我們的平臺中創建一個最多瀏覽次數的圖片排行。為了構建這個排行我們將要使用Redis分類集合。一個分類集合是一個非重復的字符串采集,其中每個成員和一個分數關聯。其中的項根據它們的分數進行排序。

編輯images引用下的views.py文件,使image_detail視圖(view)看上去如下所示:

def image_detail(request, id, slug):
image = get_object_or_404(Image, id=id, slug=slug)
# increment total image views by 1
total_views = r.incr('image:{}:views'.format(image.id)) # increment image ranking by 1 
r.zincrby('image_ranking', image.id, 1)
return render(request,
              'images/image/detail.html',
              {'section': 'images',
               'image': image,
               'total_views': total_views})

我們使用zincrby()命令存儲圖片視圖(views)到一個分類集合中通過鍵image:ranking。我們存儲圖片id,和一個分數1,它們將會被加到分類集合中這個元素的總分上。這將允許我們在全局上持續跟蹤所有的圖片視圖(views),并且有一個分類集合,該分類集合通過圖片的瀏覽次數進行排序。

現在創建一個新的視圖(view)用來展示最多瀏覽次數圖片的排行。在views.py文件中添加如下代碼:

@login_required
def image_ranking(request):
    # get image ranking dictionary
    image_ranking = r.zrange('image_ranking', 0, -1,
                             desc=True)[:10]
    image_ranking_ids = [int(id) for id in image_ranking]
    # get most viewed images
    most_viewed = list(Image.objects.filter(
                       id__in=image_ranking_ids))
    most_viewed.sort(key=lambda x: image_ranking_ids.index(x.id))
    return render(request,
                  'images/image/ranking.html',
                  {'section': 'images',
                   'most_viewed': most_viewed})

以上就是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" %}

{% block title %}Images ranking{% endblock %}

{% block content %}
    <h1>Images ranking</h1>
     <ol>
       {% for image in most_viewed %}
         <li>
           <a href="{{ image.get_absolute_url }}">
             {{ image.title }}
           </a> 
         </li>
       {% endfor %}
     </ol>
{% endblock %}

這個模板(template)非常簡單明了,我們只是對包含在most_viewed中的Image對象進行迭代。

最后為新的視圖(view)創建一個URL模式。編輯images應用下的urls.py文件,添加如下內容:

url(r'^ranking/$', views.image_ranking, name='create'),

在瀏覽器中打開 http://127.0.0.1:8000/images/ranking/ 。你會看到如下圖片排行:

django-6-7

Redis的下一步

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感覺最實用。準備全書翻譯好后再抽時間把翻譯好的所有章節全部重新校對下!那么大家下章再見!祈禱我年終中大獎!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容