《Django By Example》第十一章 中文 翻譯 (我的學習,渣翻)

第十一章

緩存內容

(譯者 @ucag 注:這是倒數第二章,最後一個項目即將完成。 @夜夜月 將會接過翻譯的最後一棒。這本書的翻譯即將完成。這也是我翻譯的最後一章,做爲英語專業的學生,我對於翻譯有了更多的思考。一樣,校對是 @夜夜月)html

(譯者 @夜夜月注:贊,倒數第二章了,接下來就是最後一章了!)python

在上一章中,你使用模型繼承和通常關係建立了一個靈活的課程內容模型。你也使用基於類的視圖,表單集,以及 內容的 AJAX 排序,建立了一個課程管理系統。在這一章中,你將會:git

  • 建立展現課程信息的公共視圖
  • 建立一個學生註冊系統
  • courses中管理學生註冊
  • 建立多樣化的課程內容
  • 使用緩存框架緩存內容

咱們將會從建立一個課程目錄開始,好讓學生可以瀏覽當前的課程以及註冊這些課程。github

展現課程

對於課程目錄,咱們須要建立如下的功能:shell

  • 列出全部的可用課程,能夠經過可選科目過濾
  • 展現一個單獨的課程概覽

編輯 courses 應用的 views.py ,添加如下代碼:數據庫

from django.db.models import Count
from .models import Subject

class CourseListView(TemplateResponseMixin, View):
    model = Course
    template_name = 'courses/course/list.html'
    def get(self, request, subject=None):
        subjects = Subject.objects.annotate(
                      total_courses=Count('courses'))
        courses = Course.objects.annotate(
                      total_modules=Count('modules'))
        if subject:
            subject = get_object_or_404(Subject, slug=subject)
            courses = courses.filter(subject=subject)
       return self.render_to_response({'subjects':subjects,
                                               'subject': subject,
                                               'courses': courses})

這是 CourseListView 。它繼承了 TemplateResponseMixinView 。在這個視圖中,咱們實現了下面的功能:django

  • 1 咱們檢索全部的課程,包括它們當中的每一個課程總數。咱們使用 ORM 的 annotate() 方法和 Count() 聚合方法來實現這一功能。
  • 2 咱們檢索全部的可用課程,包括在每一個課程中包含的模塊總數。
  • 3 若是給了科目的 slug URL 參數,咱們就檢索對應的課程對象,而後咱們將會把查詢限制在所給的科目以內。
  • 4 咱們使用 TemplateResponseMixin 提供的 render_to_response() 方法來把對象渲染到模板中,而後返回一個 HTTP 響應。

讓咱們建立一個詳情視圖來展現單一課程的概覽。在 views.py 中添加如下代碼:後端

from django.views.generic.detail import DetailView

class CourseDetailView(DetailView):
    model = Course
    template_name = 'courses/course/detail.html'

這個視圖繼承了 Django 提供的通用視圖 DetailView 。咱們定義了 modeltemplate_name 屬性。Django 的 DetailView 指望一個 主鍵(pk) 或者 slug URL 參數來檢索對應模型的一個單一對象。而後它就會渲染 template_name 中的模板,一樣也會把上下文中的對象渲染進去。api

編輯 educa 項目中的主 urls.py 文件,添加如下 URL 模式:緩存

from courses.views import CourseListView

urlpatterns = [
    # ...
    url(r'^$', CourseListView.as_view(), name='course_list'),
]

咱們把 course_list 的 URL 模式添加進了項目的主 urls.py 中,由於咱們想要把課程列表展現在 http://127.0.0.1:8000/ 中,而後 courses 應用的全部的其餘 URL 都有 /course/ 前綴。

編輯 courses 應用的 urls.py ,添加如下 URL 模式:

url(r'^subject/(?P<subject>[\w-]+)/$',
    views.CourseListView.as_view(),
    name='course_list_subject'),

url(r'^(?P<slug>[\w-]+)/$',
    views.CourseDetailView.as_view(),
    name='course_detail'),

咱們定義瞭如下 URL 模式:

  • course_list_subject:用於展現一個科目的全部課程
  • course_detail:用於展現一個課程的概覽

讓咱們爲 CourseListViewCourseDetailView 建立模板。在 courses 應用的 templates/courses/ 路徑下建立如下路徑:

  • course/
  • list.html
  • detail.html

編輯 courses/course/list.html 模板,寫入如下代碼:

{% extends "base.html" %}

{% block title %}
    {% if subject %}
        {{ subject.title }} courses
    {% else %}
        All courses
    {% endif %}
{% endblock %}

{% block content %}
<h1>
    {% if subject %}
        {{ subject.title }} courses
    {% else %}
        All courses
    {% endif %}
</h1>
<div class="contents">
    <h3>Subjects</h3>
    <ul id="modules">
        <li {% if not subject %}class="selected"{% endif %}>
            <a href="{% url "course_list" %}">All</a>
        </li>
        {% for s in subjects %}
            <li {% if subject == s %}class="selected"{% endif %}>
                <a href="{% url "course_list_subject" s.slug %}">
                    {{ s.title }}
                    <br><span>{{ s.total_courses }} courses</span>
                </a>
            </li>
        {% endfor %}
    </ul>
</div>
<div class="module">
    {% for course in courses %}
        {% with subject=course.subject %}
            <h3><a href="{% url "course_detail" course.slug %}">{{ course.title }}</a></h3>
            <p>
                <a href="{% url "course_list_subject" subject.slug %}">{{ subject }}</a>.
                {{ course.total_modules }} modules.
                Instructor: {{ course.owner.get_full_name }}
            </p>
        {% endwith %}
    {% endfor %}
</div>
{% endblock %}

這個模板用於展現可用課程列表。咱們建立了一個 HTML 列表來展現全部的 Subject 對象,而後爲它們每個都建立了一個連接,這個連接連接到 course_list_subject 的 URL 。 若是存在當前科目,咱們就把 selected HTML 類添加到當前科目中高亮顯示該科目。咱們迭代每一個 Course 對象,展現模塊的總數和教師的名字。

使用 python manage.py runserver 打開代發服務器,訪問 http://127.0.0.1:8000/ 。你看到的應該是像下面這個樣子:

django-11-1

左邊的側邊欄包含了全部的科目,包括每一個科目的課程總數。你能夠點擊任意一個科目來篩選展現的課程。

編輯 courses/course/detail.html 模板,添加如下代碼:

{% extends "base.html" %}

{% block title %}
    {{ object.title }}
{% endblock %}

{% block content %}
    {% with subject=course.subject %}
        <h1>
            {{ object.title }}
        </h1>
        <div class="module">
            <h2>Overview</h2>
            <p>
                <a href="{% url "course_list_subject" subject.slug %}">{{ subject.title }}</a>.
                {{ course.modules.count }} modules.
                Instructor: {{ course.owner.get_full_name }}
            </p>
            {{ object.overview|linebreaks }}
        </div>
    {% endwith %}
{% endblock %}

在這個模板中,咱們展現了每一個單一課程的概覽和詳情。訪問 http://127.0.0.1:8000/ ,點擊任意一個課程。你就應該看到有下面結構的頁面了:

django-11-2

咱們已經建立了一個展現課程的公共區域了。下面,咱們須要讓用戶能夠註冊爲學生以及註冊他們的課程。

添加學生註冊

使用下面的命令創鍵一個新的應用:

python manage.py startapp students

編輯 educa 項目的 settings.py ,把 students 添加進 INSTALLED_APPS 設置中:

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

建立一個學生註冊視圖

編輯 students 應用的 views.py ,寫入下下面的代碼:

from django.core.urlresolvers import reverse_lazy
from django.views.generic.edit import CreateView
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login

class StudentRegistrationView(CreateView):
    template_name = 'students/student/registration.html'
    form_class = UserCreationForm
    success_url = reverse_lazy('student_course_list')
    
    def form_valid(self, form):
        result = super(StudentRegistrationView,
                        self).form_valid(form)
        cd = form.cleaned_data
        user = authenticate(username=cd['username'],
                            password=cd['password1'])
        login(self.request, user)
        return result

這個視圖讓學生能夠註冊進咱們的網站裏。咱們使用了能夠提供建立模型對象功能的通用視圖 CreateView 。這個視圖要求如下屬性:

  • template_name:渲染這個視圖的模板路徑。
  • form_class:用於建立對象的表單,咱們使用 Django 的 UserCreationForm 做爲註冊表單來建立 User 對象。
  • success_url:當表單成功提交時要將用戶重定向到的 URL 。咱們逆序了 student_course_list URL,咱們稍候將會將建它來展現學生已報名的課程。

當合法的表單數據被提交時 form_valid() 方法就會執行。它必須返回一個 HTTP 響應。咱們覆寫了這個方法來讓用戶在成功註冊以後登陸。

在 students 應用路徑下建立一個新的文件,命名爲 urls.py ,添加如下代碼:

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^register/$',
           views.StudentRegistrationView.as_view(),
           name='student_registration'),
]

編輯 educa 的主 urls.py ,而後把 students 應用的 URLs 引入進去:

url(r'^students/', include('students.urls')),

students 應用內建立以下的文件結構:

templates/
    students/
        student/
            registration.html

編輯 students/student/registration.html 模板,而後添加如下代碼:

{% extends "base.html" %}

{% block title %}
    Sign up
{% endblock %}

{% block content %}
    <h1>
        Sign up
    </h1>
    <div class="module">
        <p>Enter your details to create an account:</p>
        <form action="" method="post">
            {{ form.as_p }}
            {% csrf_token %}
            <p><input type="submit" value="Create my account"></p>
        </form>
    </div>
{% endblock %}

最後編輯 educa 的設置文件,添加如下代碼:

from django.core.urlresolvers import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('student_course_list')

這個是由 auth 模型用來給用戶在成功的登陸以後重定向的設置,若是請求中沒有 next 參數的話。

打開開發服務器,訪問 http://127.0.0.1:8000/students/register/ ,你能夠看到像這樣的註冊表單:

django-11-3

報名

在用戶建立一個帳號以後,他們應該就能夠在 courses 中報名了。爲了保存報名表,咱們須要在 CourseUser 模型之間建立一個多對多關係。編輯 courses 應用的 models.py 而後把下面的字段添加進 Course 模型中:

students = models.ManyToManyField(User,
                        related_name='courses_joined',
                        blank=True)

在 shell 中執行下面的命令來建立遷移:

python manage.py makemigrations

你能夠看到相似下面的輸出:

Migrations for 'courses':
    0004_course_students.py:
       - Add field students to course

接下來執行下面的命令來應用遷移:

python manage.py migrate

你能夠看到如下輸出:

Operations to perform:
    Apply all migrations: courses
Running migrations:
    Rendering model states... DONE
    Applying courses.0004_course_students... OK

咱們如今就能夠把學生和他們報名的課程相關聯起來了。
讓咱們建立學生報名課程的功能吧。

students 應用內建立一個新的文件,命名爲 forms.py ,添加如下代碼:

from django import forms
from courses.models import Course

class CourseEnrollForm(forms.Form):
    course = forms.ModelChoiceField(queryset=Course.objects.all(),
                                    widget=forms.HiddenInput)

咱們將會把這張表用於學生報名。course 字段是學生報名的課程。因此,它是一個 ModelChoiceField 。咱們使用 HiddenInput 控件,由於咱們不打算把這個字段展現給用戶。咱們將會在 CourseDetailView 視圖中使用這個表單來展現一個報名按鈕。

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

from django.views.generic.edit import FormView
from braces.views import LoginRequiredMixin
from .forms import CourseEnrollForm

class StudentEnrollCourseView(LoginRequiredMixin, FormView):
    course = None
    form_class = CourseEnrollForm
    
    def form_valid(self, form):
        self.course = form.cleaned_data['course']
        self.course.students.add(self.request.user)
        return super(StudentEnrollCourseView,
                        self).form_valid(form)

    def get_success_url(self):
        return reverse_lazy('student_course_detail',
                                args=[self.course.id])

這就是 StudentEnrollCourseView 。它負責學生在 courses 中報名。新的視圖繼承了 LoginRequiredMixin ,因此只有登陸了的用戶才能夠訪問到這個視圖。咱們把 CourseEnrollForm表單用在了 form_class 屬性上,同時咱們也定義了一個 course 屬性來儲存所給的 Course 對象。當表單合法時,咱們把當前用戶添加到課程中已報名學生中去。

若是表單提交成功,get_success_url 方法就會返回用戶將會被重定向到的 URL 。這個方法至關於 success_url 屬性。咱們反序 student_course_detail URL ,咱們稍候將會建立它來展現課程中的學生。

編輯 students 應用的 urls.py ,添加如下 URL 模式:

url(r'^enroll-course/$',
    views.StudentEnrollCourseView.as_view(),
    name='student_enroll_course'),

讓咱們把報名按鈕表添加進課程概覽頁。編輯 course 應用的 views.py ,而後修改 CourseDetailView 讓它看起來像這樣:

from students.forms import CourseEnrollForm

class CourseDetailView(DetailView):
    model = Course
    template_name = 'courses/course/detail.html'
    
    def get_context_data(self, **kwargs):
        context = super(CourseDetailView,
                        self).get_context_data(**kwargs)
        context['enroll_form'] = CourseEnrollForm(
                        initial={'course':self.object})
        return context

咱們使用 get_context_data() 方法來在渲染進模板中的上下文裏引入報名表。咱們初始化帶有當前 Course 對象的表單的隱藏 course 字段,這樣它就能夠被直接提交了。

編輯 courses/course/detail.html 模板,而後找到下面的這一行:

{{ object.overview|linebreaks }}

起始行應該被替換爲下面的這幾行:

{{ object.overview|linebreaks }}
{% if request.user.is_authenticated %}
    <form action="{% url "student_enroll_course" %}" method="post">
        {{ enroll_form }}
        {% csrf_token %}
        <input type="submit" class="button" value="Enroll now">
    </form>
{% else %}
    <a href="{% url "student_registration" %}" class="button">
        Register to enroll
    </a>
{% endif %}

這個按鈕就是用於報名的。若是用戶是被認證過的,咱們就展現包含了隱藏表單字段的報名按鈕,這個表單指向了 student_enroll_course URL。若是用戶沒有被認證,咱們將會展現一個註冊連接。

確保已經打開了開發服務器,訪問 http://127.0.0.1:8000/ ,而後點擊一個課程。若是你登陸了,你就能夠在底部看到 ENROLL NOW 按鈕,就像這樣:

django-11-4

若是你沒有登陸,你就會看到一個Register to enroll 的按鈕。

獲取課程內容

咱們須要一個視圖來展現學生已經報名的課程,和一個獲取當前課程內容的視圖。編輯 students 應用的 views.py ,添加如下代碼:

from django.views.generic.list import ListView
from courses.models import Course

class StudentCourseListView(LoginRequiredMixin, ListView):
    model = Course
    template_name = 'students/course/list.html'
    
    def get_queryset(self):
        qs = super(StudentCourseListView, self).get_queryset()
        return qs.filter(students__in=[self.request.user])

這個是用於列出學生已經報名課程的視圖。它繼承 LoginRequiredMixin 來確保只有登陸的用戶才能夠鏈接到這個視圖。同時它也繼承了通用視圖 ListView 來展現 Course 對象列表。咱們覆寫了 get_queryset() 方法來檢索用戶已經報名的課程。咱們經過學生的 ManyToManyField 字段來篩選查詢集以達到這個目的。

把下面的代碼添加進 views.py 文件中:

from django.views.generic.detail import DetailView

class StudentCourseDetailView(DetailView):
    model = Course
    template_name = 'students/course/detail.html'
    
    def get_queryset(self):
        qs = super(StudentCourseDetailView, self).get_queryset()
        return qs.filter(students__in=[self.request.user])
    
    def get_context_data(self, **kwargs):
        context = super(StudentCourseDetailView,
                        self).get_context_data(**kwargs)
        # get course object
        course = self.get_object()
        if 'module_id' in self.kwargs:
            # get current module
            context['module'] = course.modules.get(
                            id=self.kwargs['module_id'])
        else:
            # get first module
            context['module'] = course.modules.all()[0]
        return context

這是 StudentCourseDetailView 。咱們覆寫了 get_queryset 方法把查詢集限制在用戶報名的課程以內。咱們一樣也覆寫了 get_context_data() 方法來把課程的一個模塊賦值在上下文內,若是給了 model_id URL 參數的話。不然,咱們就賦值課程的第一個模塊。這樣,學生就能夠在課程以內瀏覽各個模塊了。

編輯 students 應用的 urls.py ,添加如下 URL 模式:

url(r'^courses/$',
    views.StudentCourseListView.as_view(),
    name='student_course_list'),

url(r'^course/(?P<pk>\d+)/$',
    views.StudentCourseDetailView.as_view(),
    name='student_course_detail'),

url(r'^course/(?P<pk>\d+)/(?P<module_id>\d+)/$',
    views.StudentCourseDetailView.as_view(),
    name='student_course_detail_module'),

students 應用的 templates/students/ 路徑下建立如下文件結構:

course/
    detail.html
    list.html

編輯 students/course/list.html 模板,而後添加下列代碼:

{% extends "base.html" %}

{% block title %}My courses{% endblock %}

{% block content %}
    <h1>My courses</h1>

    <div class="module">
        {% for course in object_list %}
            <div class="course-info">
                <h3>{{ course.title }}</h3>
                <p><a href="{% url "student_course_detail" course.id %}">Access contents</a></p>
            </div>
        {% empty %}
            <p>
                You are not enrolled in any courses yet.
                <a href="{% url "course_list" %}">Browse courses</a>to enroll in a course.
            </p>
        {% endfor %}
    </div>
{% endblock %}

這個模板展現了用戶報名的課程。編輯 students/course/detail.html 模板,添加如下代碼:

{% extends "base.html" %}

{% block title %}
    {{ object.title }}
{% endblock %}

{% block content %}
    <h1>
        {{ module.title }}
    </h1>
    <div class="contents">
        <h3>Modules</h3>
        <ul id="modules">
        {% for m in object.modules.all %}
            <li data-id="{{ m.id }}" {% if m == module %}
class="selected"{% endif %}>
                <a href="{% url "student_course_detail_module" object.id m.id %}">
                    <span>
                        Module <span class="order">{{ m.order|add:1 }}</span>
                    </span>
                    <br>
                    {{ m.title }}
                </a>
            </li>
        {% empty %}
            <li>No modules yet.</li>
        {% endfor %}
        </ul>
    </div>
    <div class="module">
        {% for content in module.contents.all %}
            {% with item=content.item %}
                <h2>{{ item.title }}</h2>
                {{ item.render }}
            {% endwith %}
        {% endfor %}
    </div>
{% endblock %}

這個模板用於報名了的學生鏈接到課程內容。首先咱們建立了一個包含全部課程模塊的 HTML 列表且高亮當前模塊。而後咱們迭代當前的模塊內容,以後使用 {{ item.render }} 來鏈接展現內容。接下來咱們將會在內容模型中添加 render() 方法。這個方法將會負責精準的展現內容。

渲染不一樣類型的內容

咱們須要提供一個方法來渲染不一樣類型的內容。編輯 course 應用的 models.py ,把 render() 方法添加進 ItemBase 模型中:

from django.template.loader import render_to_string
from django.utils.safestring import mark_safe

class ItemBase(models.Model):
    # ...
    def render(self):
        return render_to_string('courses/content/{}.html'.format(
                            self._meta.model_name), {'item': self})

這個方法使用了 render_to_string() 方法來渲染模板以及返回一個做爲字符串的渲染內容。每種內容都使用之內容模型命名的模板渲染。咱們使用 self._meta.model_name 來爲 la 建立合適的模板名。 render() 方法提供了一個渲染不一樣頁面的通用接口。

courses 應用的 templates/courses/ 路徑下建立以下文件結構:

content/
    text.html
    file.html
    image.html
    video.html

編輯 courses/content/text.html 模板,寫入如下代碼:

{{ item.content|linebreaks|safe }}

編輯 courses/content/file.html 模板,寫入如下代碼:

<p><a href="{{ item.file.url }}" class="button">Download file</a></p>

編輯 courses/content/image.html 模板,寫入如下代碼:

<p><img src="{{ item.file.url }}"></p>

爲了使上傳帶有 ImageFieldFielField 的文件工做,咱們須要配置咱們的項目以使用開發服務器提供媒體文件服務。編輯你的項目中的 settings.py ,添加如下代碼:

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

記住 MEDIA_URL 是服務上傳文件的基本 URL 路徑, MEDIA_ROOT 是放置文件的本地路徑。

編輯你的項目的主 urls.py ,添加如下 imports:

from django.conf import settings
from django.conf.urls.static import static

而後,把下面這幾行寫入文件的結尾:

urlpatterns += static(settings.MEDIA_URL,
                document_root=settings.MEDIA_ROOT)

你的項目如今已經準備好使用開發服務器上傳和服務文件了。記住開發服務器不能用於生產環境中。咱們將會在下一章中學習如何配置生產環境。

咱們也須要建立一個模板來渲染 Video 對象。咱們將會使用 django-embed-video 來嵌入視頻內容。 Django-embed-video 是一個第三方 Django 應用,它使你能夠經過提供一個視頻的公共 URL 來在模板中嵌入視頻,相似來自 YouTube 或者 Vimeo 的資源。

使用下面的命令來安裝這個包:

pip isntall django-embed-video==1.0.0

而後編輯項目的 settings.py 而後添加 embed_videoINSTALLED_APPS設置 中。你能夠在這裏找到 django-embed-video 的文檔:
http://django-embed-video.readthedocs.org/en/v1.0.0/ 。

編輯 courses/content/video.html 模板,寫入如下代碼:

{% load embed_video_tags %}
{% video item.url 'small' %}

如今運行開發服務器,訪問 http://127.0.0.1:8000/course/mine/ 。用屬於教師組或者超級管理員的用戶訪問站點,而後添加一些內容到一個課程中。爲了引入視頻內容,你也能夠複製任何一個 YouTube 視頻 URL ,好比 :https://www.youtube.com/watch?n=bgV39DlmZ2U ,而後把它引入到表單的 url 字段中。在添加內容到課程中以後,訪問 http://127.0.0.1:8000/ ,點擊課程而後點擊ENROLL NOW按鈕。你就能夠在課程中報名了,而後被重定向到 student_course_detail URL 。下面這張圖片展現了一個課程內容樣本:

django-11-5

真棒!你已經建立了一個渲染課程的通用接口了,它們中的每個都會被用特定的方式渲染。

使用緩存框架

你的應用的 HTTP 請求一般是數據庫連接,數據處理,和模板渲染的。就處理數據而言,它的開銷可比服務一個靜態網站大多了。

請求開銷在你的網站有愈來愈多的流量時是有意義的。這也使得緩存變得頗有必要。經過緩存 HTTP 請求中 的查詢結果,計算結果,或者是渲染上下文,你將會避免在接下來的請求中巨大的開銷。這使得服務端的響應時間和處理時間變短。

Django 配備有一個健碩的緩存系統,這使得你可使用不一樣級別的顆粒度來緩存數據。你能夠緩存單一的查詢,一個特定的輸出視圖,部分渲染的模板上下文,或者整個網站。緩存系統中的內容會在默認時間內被儲存。你能夠指定緩存數據過時的時間。

這是當你的站點收到一個 HTTP 請求時將會一般使用的緩存框架的方法:

1. 試着在緩存中尋找緩存數據
2. 若是找到了,就返回緩存數據
3. 若是沒有找到,就執行下面的步驟:
    1. 執行查詢或者處理請求來得到數據
    2. 在緩存中保存生成的數據
    3. 返回數據

你能夠在這裏找到更多關於 Django 緩存系統的細節信息:https://docs.djangoproject.com/en/1.8/topics/cache/ 。

激活緩存後端

Django 配備有幾個緩存後端,他們是:

  • backends.memcached.MemcachedCachebackends.memcached.PyLibMCCache:一個內存緩存後端。內存緩存是一個快速、高效的基於內存的緩存服務器。後端的使用取決於你選擇的 Python 綁定(bindings)。
  • backends.db.DatabaseCache: 使用數據庫做爲緩存系統。
  • backends.filebased.FileBasedCache:使用文件儲存系統。把每一個緩存值序列化和儲存爲單一的文件。
  • backends.locmem.LocMemCache:本地內存緩存後端。這是默認的緩存後端
  • backends.dummy.DummyCache:一個用於開發的虛擬緩存後端。它實現了緩存交互界面而不用真正的緩存任何東西。緩存是獨立進程且是線程安全的

對於可選的實現,使用內存的緩存後端吧,好比 Memcached 後端。

安裝 Memcached

咱們將會使用 Memcached 緩存後端。內存緩存運行在內存中,它在 RAM 中分配了指定的數量。當分配的 RAM 滿了時,Memcahed 就會移除最老的數據來保存新的數據。

在這個網址下載 Memcached: http://memcached.org/downloads 。若是你使用 Linux, 你可使用下面的命令安裝:

./configure && make && make test && sudo make install

若是你在使用 Mac OS X, 你可使用命令 brew install Memcached 經過 Homebrew 包管理器來安裝 Memcached 。你能夠在這裏下載 Homebrew http://brew.sh

若是你正在使用 Windwos ,你能夠在這裏找到一個 Windows 的 Memcached 二進制版本:http://code.jellycan.com/memcached/ 。

在安裝 Memcached 以後,打開 shell ,使用下面的命令運行它:

memcached -l 127.0.0.1:11211

Memcached 將會默認地在 11211 運行。固然,你也能夠經過 -l 選項指定一個特定的主機和端口。你能夠在這裏找到更多關於 Memcached 的信息:http://memcached.org 。

在安裝 Memcached 以後,你須要安裝它的 Python 綁定(bindings)。使用下面的命令安裝:】

python install python3-memcached==1.51

緩存設置

Django 提供了以下的緩存設置:

  • CACHES:一個包含全部可用的項目緩存。
  • CACHE_MIDDLEWARE_ALIAS:用於儲存的緩存別名。
  • CACHE_MIDDLEWARE_KEY_PREFIX:緩存鍵的前綴。設置一個緩存前綴來避免鍵的衝突,若是你在幾個站點中分享相同的緩存的話。
  • CACHE_MIDDLEWARE_SECONDS :默認的緩存頁面秒數

項目的緩存系統可使用 CACHES 設置來配置。這個設置是一個字典,讓你能夠指定多個緩存的配置。每一個 CACHES 字典中的緩存能夠指定下列數據:

  • BACKEND:使用的緩存後端。
  • KEY_FUNCTION:包含一個指向回調函數的點路徑的字符,這個函數以prefix(前綴)、verision(版本)、和 key (鍵) 做爲參數並返回最終緩存鍵(cache key)。
  • KEY_PREFIX:一個用於全部緩存鍵的字符串,避免衝突。
  • LOCATION:緩存的位置。基於你的緩存後端,這多是一個路徑、一個主機和端口,或者是內存中後端的名字。
  • OPTIONS:任何額外的傳遞向緩存後端的參數。
  • TIMEOUT:默認的超時時間,以秒爲單位,用於儲存緩存鍵。默認設置是 300 秒,也就是五分鐘。若是把它設置爲 None ,緩存鍵將不會過時。
  • VERSION:默認的緩存鍵的版本。對於緩存版本是頗有用的。

把 memcached 添加進你的項目

讓咱們爲咱們的項目配置緩存。編輯 educa 項目的 settings.py 文件,添加如下代碼:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.
MemcachedCache',
    'LOCATION': '127.0.0.1:11211',
    }
}

咱們正在使用 MemcachedCache 後端。咱們使用 address:port 標記指定了它的位置。若是你有多個 memcached 實例,你能夠在 LOCATION 中使用列表。

監控緩存

這裏有一個第三方包叫作 django-memcached-status ,它能夠在管理站點展現你的項目的 memcached 實例的統計數據。爲了兼容 Python3(譯者夜夜月注:python3大法好。) ,從下面的分支中安裝它:

pip install git+git://github.com/zenx/django-memcached-status.git

編輯 settings.py ,而後把 memcached_status 添加進 INSTALLED_APPS 設置中。確保 memcached 正在運行,在另一個 shell 中打開開發服務器,而後訪問 http://127.0.0.1:8000/adim/ ,使用超級用戶登陸進管理站點,你就能夠看到以下的區域:

django-11-6

這張圖片展現了緩存使用。綠色表明了空閒的緩存,紅色的表示使用了的空間。若是你點擊方框的標題,它展現了你的 memcached 實例的統計詳情。

咱們已經爲項目安裝好了 memcached 而且能夠監控它。讓咱們開始緩存數據吧!

緩存級別

Django 提供瞭如下幾個級別按照顆粒度上升的緩存排列:

  • Low-level cache API:提供了最高顆粒度。容許你緩存具體的查詢或計算結果。
  • Per-view cache:提供單一視圖的緩存。
  • Template cache:容許你緩存模板片斷。
  • Per-site cache:最高級的緩存。它緩存你的整個網站。

在你執行緩存之請仔細考慮下緩存策略。首先要考慮那些不是單一用戶爲基礎的查詢和計算開銷

使用 low-level cache API (低級緩存API)

低級緩存 API 讓你能夠緩存任意顆粒度的對象。它位於 django.core.cache 。你能夠像這樣導入它:

from django.core.cache import cache

這使用的是默認的緩存。它至關於 caches['default'] 。經過它的別名來鏈接一個特定的緩存也是可能的:

from django.core.cache import caches
my_cache = caches['alias']

讓咱們看看緩存 API 是如何工做的。使用命令 python manage.py shell 打開 shell 而後執行下面的代碼:

>>> from django.core.cache import cache
>>> cache.set('musician', 'Django Reinhardt', 20)

咱們鏈接的是默認的緩存後端,使用 set{key,value, timeout} 來保存一個名爲 musician 的鍵和它的爲字符串 Django Reinhardt 的值 20 秒鐘。若是咱們不指定過時時間,Django 會使在 CACHES 設置中緩存後端的默認過時時間。如今執行下面的代碼:

>>> cache.get('musician')
'Django Reinhardt'

咱們在緩存中檢索鍵。等待 20 秒而後指定相同的代碼:

>>> cache.get('musician')
None

musician 緩存鍵已通過期了,get() 方法返回了 None 由於鍵已經不在緩存中了。

在緩存鍵中要避免儲存 None 值,由於這樣你就沒法區分緩存值和緩存過時了

讓咱們緩存一個查詢集:

>>> from courses.models import Subject
>>> subjects = Subject.objects.all()
>>> cache.set('all_subjects', subjects)

咱們執行了一個在 Subject 模型上的查詢集,而後把返回的對象儲存在 all_subjects 鍵中。讓咱們檢索一下緩存數據:

>>> cache.get('all_subjects')
[<Subject: Mathematics>, <Subject: Music>, <Subject: Physics>,
<Subject: Programming>]

咱們將會在視圖中緩存一些查詢集。編輯 courses 應用的 views.py ,添加如下 導入:

from django.core.cache import cache

CourseListViewget() 方法,把下面這幾行:

subjects = Subject.objects.annotate(
        total_courses=Count('courses'))

替換爲:

subjects = cache.get('all_subjects')
if not subjects:
    subjects = Subject.objects.annotate(
                total_courses=Count('courses'))
    cache.set('all_subjects', subjects)

在這段代碼中,咱們首先嚐試使用 cache.get() 來從緩存中獲得 all_students 鍵。若是所給的鍵沒有找到,返回的是 None 。若是鍵沒有被找到(沒有被緩存,或者緩存了可是過時了),咱們就執行查詢來檢索全部的 Subject 對象和它們課程的數量,咱們使用 cache.set() 來緩存結果。

打開代發服務器,訪問 http://127.0.0.1:8000 。當視圖被執行的時候,緩存鍵沒有被找到的話查詢集就會被執行。訪問 http://127.0.0.1:8000/adim/ 而後打開 memcached 統計。你能夠看到相似於下面的緩存的使用數據:

django-11-7

看一眼 Curr Items 應該是 1 。這表示當前有一個內容被緩存。Get Hits 表示有多少的 get 操做成功了,Get Miss表示有多少的請求丟失了。Miss Ratio 是使用它們倆來計算的。

如今導航回 http://127.0.0.1:8000/ ,重載頁面幾回。若是你如今看緩存統計的話,你就會看到更多的(Get HitsCmd Get被執行了)

基於動態數據的緩存

有不少時候你都會想使用基於動態數據的緩存的。基於這樣的狀況,你必需要建立包含全部要求信息的動態鍵來特別定以緩存數據。編輯 courses 應用的 views.py ,修改 CourseListView ,讓它看起來像這樣:

class CourseListView(TemplateResponseMixin, View):
    model = Course
    template_name = 'courses/course/list.html'
    
    def get(self, request, subject=None):
        subjects = cache.get('all_subjects')
        if not subjects:
            subjects = Subject.objects.annotate(
                            total_courses=Count('courses'))
            cache.set('all_subjects', subjects)
        all_courses = Course.objects.annotate(
            total_modules=Count('modules'))
        if subject:
            subject = get_object_or_404(Subject, slug=subject)
            key = 'subject_{}_courses'.format(subject.id)
            courses = cache.get(key)
            if not courses:
                courses = all_courses.filter(subject=subject)
                cache.set(key, courses)
        else:
            courses = cache.get('all_courses')
            if not courses:
                courses = all_courses
                cache.set('all_courses', courses)
        return self.render_to_response({'subjects': subjects,
                                        'subject': subject,
                                        'courses': courses})

在這個場景中,咱們把課程和根據科目篩選的課程都緩存了。咱們使用 all_courses 緩存鍵來儲存全部的課程,若是沒有給科目的話。若是給了一個科目的話咱們就用 'subject_()_course'.format(subject.id)動態的建立緩存鍵。

注意到咱們不能用一個緩存查詢集來建立另外的查詢集是很重要的,由於咱們已經緩存了當前的查詢結果,因此咱們不能這樣作:

courses = cache.get('all_courses')
courses.filter(subject=subject)

相反,咱們須要建立基本的查詢集 Course.objects.annotate(total_modules=Count('modules')) ,它不會被執行除非你強制執行它,而後用它來更進一步的用 all_courses.filter(subject=subject) 限制查詢集萬一數據沒有在緩存中找到的話。

緩存模板片斷

緩存模板片斷是一個高級別的方法。你須要使用 {% load cache %} 在模板中載入緩存模板標籤。而後你就可使用 {% cache %} 模板標籤來緩存特定的模板片斷了。你一般能夠像下面這樣使用緩存標籤:

{% cache 300 fragment_name %}
...
{% endcache %}

{% cache %} 標籤要求兩個參數:過時時間,以秒爲單位,和一個片斷名稱。若是你須要緩存基於動態數據的內容,你能夠經過傳遞額外的參數給 {% cache %} 模板標籤來特別的指定片斷。

編輯 students 應用的 /students/course/detail.html 。在頂部添加如下代碼,就在 {% extends %} 標籤的後面:

{% load cache %}

而後把下面幾行:

{% for content in module.contents.all %}
    {% with item=content.item %}
        <h2>{{ item.title }}</h2>
        {{ item.render }}
    {% endwith %}
{% endfor %}

替換爲:

{% cache 600 module_contents module %}
    {% for content in module.contents.all %}
        {% with item=content.item %}
            <h2>{{ item.title }}</h2>
            {{ item.render }}
        {% endwith %}
    {% endfor %}
{% endcache %}

咱們使用名字 module_contents 和傳遞當前的 Module 對象來緩存模板片斷。這對於當請求不一樣的模型是避免緩存一個模型的內容和服務錯誤的內容來講是很重要的。

若是 USE_I18N 設置是爲 True,單一站點的中間件緩存將會遵守當前激活的語言。若是你使用了 {% cache %} 模板標籤以及可用翻譯特定的變量中的一個,那麼他們的效果將會是同樣的,好比:{% cache 600 name request.LANGUAGE_CODE %}

緩存視圖

你可使用位於 django.views.decrators.cachecache_page 裝飾器來煥春輸出的單個視圖。裝飾器要求一個過時時間的參數(以秒爲單位)。

讓咱們在咱們的視圖中使用它。編輯 students 應用的 urls.py ,添加如下 導入:

from django.views.decorators.cache import cache_page

而後按照以下在 student_course_detail_module URL 模式上應用 cache_page 裝飾器:

url(r'^course/(?P<pk>\d+)/$',
    cache_page(60 * 15)(views.StudentCourseDetailView.as_view()),
    name='student_course_detail'),

url(r'^course/(?P<pk>\d+)/(?P<module_id>\d+)/$',
    cache_page(60 * 15)(views.StudentCourseDetailView.as_view()),
    name='student_course_detail_module'),

如今 StudentCourseDetailView 的結果就會被緩存 15 分鐘了。

單一的視圖緩存使用 URL 來建立緩存鍵。多個指向同一個視圖的 URLs 將會被分開儲存

使用單一站點緩存

這是最高級的緩存。他讓你能夠緩存你的整個站點。

爲了使用單一站點緩存,你須要編輯項目中的 settings.py ,把 UpdateCacheMiddlewareFetchFromCacheMiddleware 添加進 MIDDLEWARE_CLASSES 設置中:

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    # ...
)

記住在請求的過程當中,中間件是按照所給的順序來執行的,在相應過程當中是逆序執行的。UpdateCacheMiddleware 被放在 CommonMiddleware 以前,由於它在相應時才執行,此時中間件是逆序執行的。FetchFromCacheMiddleware 被放在 CommonMiddleware 以後,是由於它須要鏈接後者的的請求數據集。

而後,把下列設置添加進 settings.py 文件:

CACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 60 * 15 # 15 minutes
CACHE_MIDDLEWARE_KEY_PREFIX = 'educa'

在這些設置中咱們爲中間件使用了默認的緩存,而後咱們把全局緩存過時時間設置爲 15 分鐘。咱們也指定了全部的緩存鍵前綴來避免衝突萬一咱們爲多個項目使用了相同的 memcached 後端。咱們的站點如今將會爲全部的 GET 請求緩存以及返回緩存內容。

咱們已經完成了這個來測試單一站點緩存功能。儘管,以站點的緩存對於咱們來講是不怎麼合適的,由於咱們的課程管理視圖須要展現更新數據來實時的給出任何的修改。咱們項目中的最好的方法是緩存用於展現給學生的課程內容的模板或者視圖數據。

咱們已經大體體驗過了 Django 提供的方法來緩存數據。你應合適的定義你本身的緩存策略,優先考慮開銷最大的查詢集或者計算。

總結

在這一章中,咱們建立了一個用於課程的公共視圖,建立了一個用於學生註冊和報名課程的系統。咱們安裝了 memcached 以及實現了不一樣級別的緩存。

在下一章中,咱們將會爲你的項目建立 RESTful API。

(譯者 @夜夜月注:終於只剩下最後一章了!)

相關文章
相關標籤/搜索