第十一章
緩存內(nèi)容
(譯者 @ucag 注:這是倒數(shù)第二章,最后一個(gè)項(xiàng)目即將完成。 @夜夜月 將會接過翻譯的最后一棒。這本書的翻譯即將完成。這也是我翻譯的最后一章,作為英語專業(yè)的學(xué)生,我對于翻譯有了更多的思考。同樣,校對是 @夜夜月)
(譯者 @夜夜月注:贊,倒數(shù)第二章了,接下來就是最后一章了!)
在上一章中,你使用模型繼承和一般關(guān)系創(chuàng)建了一個(gè)靈活的課程內(nèi)容模型。你也使用基于類的視圖,表單集,以及 內(nèi)容的 AJAX 排序,創(chuàng)建了一個(gè)課程管理系統(tǒng)。在這一章中,你將會:
創(chuàng)建展示課程信息的公共視圖
創(chuàng)建一個(gè)學(xué)生注冊系統(tǒng)
在courses中管理學(xué)生注冊
創(chuàng)建多樣化的課程內(nèi)容
使用緩存框架緩存內(nèi)容
我們將會從創(chuàng)建一個(gè)課程目錄開始,好讓學(xué)生能夠?yàn)g覽當(dāng)前的課程以及注冊這些課程。
展示課程
對于課程目錄,我們需要?jiǎng)?chuàng)建以下的功能:
列出所有的可用課程,可以通過可選科目過濾
展示一個(gè)單獨(dú)的課程概覽
編輯 courses 應(yīng)用的 views.py
,添加以下代碼:
from django.db.models import Countfrom .models import Subjectclass 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
。它繼承了 TemplateResponseMixin
和 View
。在這個(gè)視圖中,我們實(shí)現(xiàn)了下面的功能:
1 我們檢索所有的課程,包括它們當(dāng)中的每個(gè)課程總數(shù)。我們使用 ORM 的
annotate()
方法和Count()
聚合方法來實(shí)現(xiàn)這一功能。2 我們檢索所有的可用課程,包括在每個(gè)課程中包含的模塊總數(shù)。
3 如果給了科目的 slug URL 參數(shù),我們就檢索對應(yīng)的課程對象,然后我們將會把查詢限制在所給的科目之內(nèi)。
4 我們使用
TemplateResponseMixin
提供的render_to_response()
方法來把對象渲染到模板中,然后返回一個(gè) HTTP 響應(yīng)。
讓我們創(chuàng)建一個(gè)詳情視圖來展示單一課程的概覽。在 views.py
中添加以下代碼:
from django.views.generic.detail import DetailViewclass CourseDetailView(DetailView): model = Course template_name = 'courses/course/detail.html'
這個(gè)視圖繼承了 Django 提供的通用視圖 DetailView
。我們定義了 model
和 template_name
屬性。Django 的 DetailView
期望一個(gè) 主鍵(pk) 或者 slug URL 參數(shù)來檢索對應(yīng)模型的一個(gè)單一對象。然后它就會渲染 template_name
中的模板,同樣也會把上下文中的對象渲染進(jìn)去。
編輯 educa
項(xiàng)目中的主 urls.py
文件,添加以下 URL 模式:
from courses.views import CourseListView urlpatterns = [ # ... url(r'^$', CourseListView.as_view(), name='course_list'), ]
我們把 course_list
的 URL 模式添加進(jìn)了項(xiàng)目的主 urls.py
中,因?yàn)槲覀兿胍颜n程列表展示在 http://127.0.0.1:8000/
中,然后 courses
應(yīng)用的所有的其他 URL 都有 /course/
前綴。
編輯 courses
應(yīng)用的 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
:用于展示一個(gè)科目的所有課程course_detail
:用于展示一個(gè)課程的概覽
讓我們?yōu)?nbsp;CourseListView
和 CourseDetailView
創(chuàng)建模板。在 courses
應(yīng)用的 templates/courses/
路徑下創(chuàng)建以下路徑:
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 %}
這個(gè)模板用于展示可用課程列表。我們創(chuàng)建了一個(gè) HTML 列表來展示所有的 Subject
對象,然后為它們每一個(gè)都創(chuàng)建了一個(gè)鏈接,這個(gè)鏈接鏈接到 course_list_subject
的 URL 。 如果存在當(dāng)前科目,我們就把 selected
HTML 類添加到當(dāng)前科目中高亮顯示該科目。我們迭代每個(gè) Course
對象,展示模塊的總數(shù)和教師的名字。
使用 python manage.py runserver
打開代發(fā)服務(wù)器,訪問 http://127.0.0.1:8000/ 。你看到的應(yīng)該是像下面這個(gè)樣子:
左邊的側(cè)邊欄包含了所有的科目,包括每個(gè)科目的課程總數(shù)。你可以點(diǎn)擊任意一個(gè)科目來篩選展示的課程。
編輯 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 %}
在這個(gè)模板中,我們展示了每個(gè)單一課程的概覽和詳情。訪問 http://127.0.0.1:8000/ ,點(diǎn)擊任意一個(gè)課程。你就應(yīng)該看到有下面結(jié)構(gòu)的頁面了:
我們已經(jīng)創(chuàng)建了一個(gè)展示課程的公共區(qū)域了。下面,我們需要讓用戶可以注冊為學(xué)生以及注冊他們的課程。
添加學(xué)生注冊
使用下面的命令創(chuàng)鍵一個(gè)新的應(yīng)用:
python manage.py startapp students
編輯 educa
項(xiàng)目的 settings.py
,把 students
添加進(jìn) INSTALLED_APPS
設(shè)置中:
INSTALLED_APPS = ( # ... 'students', )
創(chuàng)建一個(gè)學(xué)生注冊視圖
編輯 students
應(yīng)用的 views.py
,寫入下下面的代碼:
from django.core.urlresolvers import reverse_lazyfrom django.views.generic.edit import CreateViewfrom django.contrib.auth.forms import UserCreationFormfrom django.contrib.auth import authenticate, loginclass 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
這個(gè)視圖讓學(xué)生可以注冊進(jìn)我們的網(wǎng)站里。我們使用了可以提供創(chuàng)建模型對象功能的通用視圖 CreateView
。這個(gè)視圖要求以下屬性:
template_name
:渲染這個(gè)視圖的模板路徑。form_class
:用于創(chuàng)建對象的表單,我們使用 Django 的UserCreationForm
作為注冊表單來創(chuàng)建User
對象。success_url
:當(dāng)表單成功提交時(shí)要將用戶重定向到的 URL 。我們逆序了student_course_list
URL,我們稍候?qū)⒔ㄋ鼇碚故緦W(xué)生已報(bào)名的課程。
當(dāng)合法的表單數(shù)據(jù)被提交時(shí) form_valid()
方法就會執(zhí)行。它必須返回一個(gè) HTTP 響應(yīng)。我們覆寫了這個(gè)方法來讓用戶在成功注冊之后登錄。
在 students 應(yīng)用路徑下創(chuàng)建一個(gè)新的文件,命名為 urls.py
,添加以下代碼:
from django.conf.urls import urlfrom . import views urlpatterns = [ url(r'^register/$', views.StudentRegistrationView.as_view(), name='student_registration'), ]
編輯 educa
的主 urls.py
,然后把 students
應(yīng)用的 URLs 引入進(jìn)去:
url(r'^students/', include('students.urls')),
在 students
應(yīng)用內(nèi)創(chuàng)建如下的文件結(jié)構(gòu):
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
的設(shè)置文件,添加以下代碼:
from django.core.urlresolvers import reverse_lazy LOGIN_REDIRECT_URL = reverse_lazy('student_course_list')
這個(gè)是由 auth
模型用來給用戶在成功的登錄之后重定向的設(shè)置,如果請求中沒有 next 參數(shù)的話。
打開開發(fā)服務(wù)器,訪問 http://127.0.0.1:8000/students/register/ ,你可以看到像這樣的注冊表單:
報(bào)名
在用戶創(chuàng)建一個(gè)賬號之后,他們應(yīng)該就可以在 courses
中報(bào)名了。為了保存報(bào)名表,我們需要在 Course
和 User
模型之間創(chuàng)建一個(gè)多對多關(guān)系。編輯 courses
應(yīng)用的 models.py
然后把下面的字段添加進(jìn) Course
模型中:
students = models.ManyToManyField(User, related_name='courses_joined', blank=True)
在 shell 中執(zhí)行下面的命令來創(chuàng)建遷移:
python manage.py makemigrations
你可以看到類似下面的輸出:
Migrations for 'courses': 0004_course_students.py: - Add field students to course
接下來執(zhí)行下面的命令來應(yīng)用遷移:
python manage.py migrate
你可以看到以下輸出:
Operations to perform: Apply all migrations: courses Running migrations: Rendering model states... DONE Applying courses.0004_course_students... OK
我們現(xiàn)在就可以把學(xué)生和他們報(bào)名的課程相關(guān)聯(lián)起來了。
讓我們創(chuàng)建學(xué)生報(bào)名課程的功能吧。
在 students
應(yīng)用內(nèi)創(chuàng)建一個(gè)新的文件,命名為 forms.py
,添加以下代碼:
from django import formsfrom courses.models import Courseclass CourseEnrollForm(forms.Form): course = forms.ModelChoiceField(queryset=Course.objects.all(), widget=forms.HiddenInput)
我們將會把這張表用于學(xué)生報(bào)名。course
字段是學(xué)生報(bào)名的課程。所以,它是一個(gè) ModelChoiceField
。我們使用 HiddenInput
控件,因?yàn)槲覀儾淮蛩惆堰@個(gè)字段展示給用戶。我們將會在 CourseDetailView
視圖中使用這個(gè)表單來展示一個(gè)報(bào)名按鈕。
編輯 students
應(yīng)用的 views.py
,添加以下代碼:
from django.views.generic.edit import FormViewfrom braces.views import LoginRequiredMixinfrom .forms import CourseEnrollFormclass 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
。它負(fù)責(zé)學(xué)生在 courses
中報(bào)名。新的視圖繼承了 LoginRequiredMixin
,所以只有登錄了的用戶才可以訪問到這個(gè)視圖。我們把 CourseEnrollForm
表單用在了 form_class
屬性上,同時(shí)我們也定義了一個(gè) course
屬性來儲存所給的 Course
對象。當(dāng)表單合法時(shí),我們把當(dāng)前用戶添加到課程中已報(bào)名學(xué)生中去。
如果表單提交成功,get_success_url
方法就會返回用戶將會被重定向到的 URL 。這個(gè)方法相當(dāng)于 success_url
屬性。我們反序 student_course_detail
URL ,我們稍候?qū)?chuàng)建它來展示課程中的學(xué)生。
編輯 students
應(yīng)用的 urls.py
,添加以下 URL 模式:
url(r'^enroll-course/$', views.StudentEnrollCourseView.as_view(), name='student_enroll_course'),
讓我們把報(bào)名按鈕表添加進(jìn)課程概覽頁。編輯 course
應(yīng)用的 views.py
,然后修改 CourseDetailView
讓它看起來像這樣:
from students.forms import CourseEnrollFormclass 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()
方法來在渲染進(jìn)模板中的上下文里引入報(bào)名表。我們初始化帶有當(dāng)前 Course
對象的表單的隱藏 course 字段,這樣它就可以被直接提交了。
編輯 courses/course/detail.html
模板,然后找到下面的這一行:
{{ object.overview|linebreaks }}
起始行應(yīng)該被替換為下面的這幾行:
{{ 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 %}
這個(gè)按鈕就是用于報(bào)名的。如果用戶是被認(rèn)證過的,我們就展示包含了隱藏表單字段的報(bào)名按鈕,這個(gè)表單指向了 student_enroll_course
URL。如果用戶沒有被認(rèn)證,我們將會展示一個(gè)注冊鏈接。
確保已經(jīng)打開了開發(fā)服務(wù)器,訪問 http://127.0.0.1:8000/ ,然后點(diǎn)擊一個(gè)課程。如果你登錄了,你就可以在底部看到 ENROLL NOW 按鈕,就像這樣:
如果你沒有登錄,你就會看到一個(gè)Register to enroll 的按鈕。
獲取課程內(nèi)容
我們需要一個(gè)視圖來展示學(xué)生已經(jīng)報(bào)名的課程,和一個(gè)獲取當(dāng)前課程內(nèi)容的視圖。編輯 students
應(yīng)用的 views.py
,添加以下代碼:
from django.views.generic.list import ListViewfrom courses.models import Courseclass 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])
這個(gè)是用于列出學(xué)生已經(jīng)報(bào)名課程的視圖。它繼承 LoginRequiredMixin
來確保只有登錄的用戶才可以連接到這個(gè)視圖。同時(shí)它也繼承了通用視圖 ListView
來展示 Course
對象列表。我們覆寫了 get_queryset()
方法來檢索用戶已經(jīng)報(bào)名的課程。我們通過學(xué)生的 ManyToManyField
字段來篩選查詢集以達(dá)到這個(gè)目的。
把下面的代碼添加進(jìn) views.py
文件中:
from django.views.generic.detail import DetailViewclass 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
方法把查詢集限制在用戶報(bào)名的課程之內(nèi)。我們同樣也覆寫了 get_context_data()
方法來把課程的一個(gè)模塊賦值在上下文內(nèi),如果給了 model_id
URL 參數(shù)的話。否則,我們就賦值課程的第一個(gè)模塊。這樣,學(xué)生就可以在課程之內(nèi)瀏覽各個(gè)模塊了。
編輯 students
應(yīng)用的 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
應(yīng)用的 templates/students/
路徑下創(chuàng)建以下文件結(jié)構(gòu):
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 %}
這個(gè)模板展示了用戶報(bào)名的課程。編輯 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 %}
這個(gè)模板用于報(bào)名了的學(xué)生連接到課程內(nèi)容。首先我們創(chuàng)建了一個(gè)包含所有課程模塊的 HTML 列表且高亮當(dāng)前模塊。然后我們迭代當(dāng)前的模塊內(nèi)容,之后使用 {{ item.render }}
來連接展示內(nèi)容。接下來我們將會在內(nèi)容模型中添加 render()
方法。這個(gè)方法將會負(fù)責(zé)精準(zhǔn)的展示內(nèi)容。
渲染不同類型的內(nèi)容
我們需要提供一個(gè)方法來渲染不同類型的內(nèi)容。編輯 course
應(yīng)用的 models.py
,把 render()
方法添加進(jìn) ItemBase
模型中:
from django.template.loader import render_to_stringfrom django.utils.safestring import mark_safeclass ItemBase(models.Model): # ... def render(self): return render_to_string('courses/content/{}.html'.format( self._meta.model_name), {'item': self})
這個(gè)方法使用了 render_to_string()
方法來渲染模板以及返回一個(gè)作為字符串的渲染內(nèi)容。每種內(nèi)容都使用以內(nèi)容模型命名的模板渲染。我們使用 self._meta.model_name
來為 la
創(chuàng)建合適的模板名。 render()
方法提供了一個(gè)渲染不同頁面的通用接口。
在 courses
應(yīng)用的 templates/courses/
路徑下創(chuàng)建如下文件結(jié)構(gòu):
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>
為了使上傳帶有 ImageField
和 FielField
的文件工作,我們需要配置我們的項(xiàng)目以使用開發(fā)服務(wù)器提供媒體文件服務(wù)。編輯你的項(xiàng)目中的 settings.py
,添加以下代碼:
MEDIA_URL = '/media/'MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
記住 MEDIA_URL
是服務(wù)上傳文件的基本 URL 路徑, MEDIA_ROOT
是放置文件的本地路徑。
編輯你的項(xiàng)目的主 urls.py
,添加以下 imports:
from django.conf import settingsfrom django.conf.urls.static import static
然后,把下面這幾行寫入文件的結(jié)尾:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
你的項(xiàng)目現(xiàn)在已經(jīng)準(zhǔn)備好使用開發(fā)服務(wù)器上傳和服務(wù)文件了。記住開發(fā)服務(wù)器不能用于生產(chǎn)環(huán)境中。我們將會在下一章中學(xué)習(xí)如何配置生產(chǎn)環(huán)境。
我們也需要?jiǎng)?chuàng)建一個(gè)模板來渲染 Video
對象。我們將會使用 django-embed-video 來嵌入視頻內(nèi)容。 Django-embed-video 是一個(gè)第三方 Django 應(yīng)用,它使你可以通過提供一個(gè)視頻的公共 URL 來在模板中嵌入視頻,類似來自 YouTube 或者 Vimeo 的資源。
使用下面的命令來安裝這個(gè)包:
pip isntall django-embed-video==1.0.0
然后編輯項(xiàng)目的 settings.py
然后添加 embed_video
到 INSTALLED_APPS
設(shè)置 中。你可以在這里找到 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' %}
現(xiàn)在運(yùn)行開發(fā)服務(wù)器,訪問 http://127.0.0.1:8000/course/mine/ 。用屬于教師組或者超級管理員的用戶訪問站點(diǎn),然后添加一些內(nèi)容到一個(gè)課程中。為了引入視頻內(nèi)容,你也可以復(fù)制任何一個(gè) YouTube 視頻 URL ,比如 :https://www.youtube.com/watch?n=bgV39DlmZ2U ,然后把它引入到表單的 url
字段中。在添加內(nèi)容到課程中之后,訪問 http://127.0.0.1:8000/ ,點(diǎn)擊課程然后點(diǎn)擊ENROLL NOW按鈕。你就可以在課程中報(bào)名了,然后被重定向到 student_course_detail
URL 。下面這張圖片展示了一個(gè)課程內(nèi)容樣本:
真棒!你已經(jīng)創(chuàng)建了一個(gè)渲染課程的通用接口了,它們中的每一個(gè)都會被用特定的方式渲染。
使用緩存框架
你的應(yīng)用的 HTTP 請求通常是數(shù)據(jù)庫鏈接,數(shù)據(jù)處理,和模板渲染的。就處理數(shù)據(jù)而言,它的開銷可比服務(wù)一個(gè)靜態(tài)網(wǎng)站大多了。
請求開銷在你的網(wǎng)站有越來越多的流量時(shí)是有意義的。這也使得緩存變得很有必要。通過緩存 HTTP 請求中 的查詢結(jié)果,計(jì)算結(jié)果,或者是渲染上下文,你將會避免在接下來的請求中巨大的開銷。這使得服務(wù)端的響應(yīng)時(shí)間和處理時(shí)間變短。
Django 配備有一個(gè)健碩的緩存系統(tǒng),這使得你可以使用不同級別的顆粒度來緩存數(shù)據(jù)。你可以緩存單一的查詢,一個(gè)特定的輸出視圖,部分渲染的模板上下文,或者整個(gè)網(wǎng)站。緩存系統(tǒng)中的內(nèi)容會在默認(rèn)時(shí)間內(nèi)被儲存。你可以指定緩存數(shù)據(jù)過期的時(shí)間。
這是當(dāng)你的站點(diǎn)收到一個(gè) HTTP 請求時(shí)將會通常使用的緩存框架的方法:
1. 試著在緩存中尋找緩存數(shù)據(jù)2. 如果找到了,就返回緩存數(shù)據(jù)3. 如果沒有找到,就執(zhí)行下面的步驟: 1. 執(zhí)行查詢或者處理請求來獲得數(shù)據(jù) 2. 在緩存中保存生成的數(shù)據(jù) 3. 返回?cái)?shù)據(jù)
你可以在這里找到更多關(guān)于 Django 緩存系統(tǒng)的細(xì)節(jié)信息:https://docs.djangoproject.com/en/1.8/topics/cache/ 。
激活緩存后端
Django 配備有幾個(gè)緩存后端,他們是:
backends.memcached.MemcachedCache
或backends.memcached.PyLibMCCache
:一個(gè)內(nèi)存緩存后端。內(nèi)存緩存是一個(gè)快速、高效的基于內(nèi)存的緩存服務(wù)器。后端的使用取決于你選擇的 Python 綁定(bindings)。backends.db.DatabaseCache
: 使用數(shù)據(jù)庫作為緩存系統(tǒng)。backends.filebased.FileBasedCache
:使用文件儲存系統(tǒng)。把每個(gè)緩存值序列化和儲存為單一的文件。backends.locmem.LocMemCache
:本地內(nèi)存緩存后端。這是默認(rèn)的緩存后端backends.dummy.DummyCache
:一個(gè)用于開發(fā)的虛擬緩存后端。它實(shí)現(xiàn)了緩存交互界面而不用真正的緩存任何東西。緩存是獨(dú)立進(jìn)程且是線程安全的
對于可選的實(shí)現(xiàn),使用內(nèi)存的緩存后端吧,比如
Memcached
后端。
安裝 Memcached
我們將會使用 Memcached 緩存后端。內(nèi)存緩存運(yùn)行在內(nèi)存中,它在 RAM 中分配了指定的數(shù)量。當(dāng)分配的 RAM 滿了時(shí),Memcahed 就會移除最老的數(shù)據(jù)來保存新的數(shù)據(jù)。
在這個(gè)網(wǎng)址下載 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 ,你可以在這里找到一個(gè) Windows 的 Memcached 二進(jìn)制版本:http://code.jellycan.com/memcached/ 。
在安裝 Memcached 之后,打開 shell ,使用下面的命令運(yùn)行它:
memcached -l 127.0.0.1:11211
Memcached 將會默認(rèn)地在 11211 運(yùn)行。當(dāng)然,你也可以通過 -l
選項(xiàng)指定一個(gè)特定的主機(jī)和端口。你可以在這里找到更多關(guān)于 Memcached 的信息:http://memcached.org 。
在安裝 Memcached 之后,你需要安裝它的 Python 綁定(bindings)。使用下面的命令安裝:】
python install python3-memcached==1.51
緩存設(shè)置
Django 提供了如下的緩存設(shè)置:
CACHES
:一個(gè)包含所有可用的項(xiàng)目緩存。CACHE_MIDDLEWARE_ALIAS
:用于儲存的緩存別名。CACHE_MIDDLEWARE_KEY_PREFIX
:緩存鍵的前綴。設(shè)置一個(gè)緩存前綴來避免鍵的沖突,如果你在幾個(gè)站點(diǎn)中分享相同的緩存的話。CACHE_MIDDLEWARE_SECONDS
:默認(rèn)的緩存頁面秒數(shù)
項(xiàng)目的緩存系統(tǒng)可以使用 CACHES
設(shè)置來配置。這個(gè)設(shè)置是一個(gè)字典,讓你可以指定多個(gè)緩存的配置。每個(gè) CACHES
字典中的緩存可以指定下列數(shù)據(jù):
BACKEND
:使用的緩存后端。KEY_FUNCTION
:包含一個(gè)指向回調(diào)函數(shù)的點(diǎn)路徑的字符,這個(gè)函數(shù)以prefix(前綴)、verision(版本)、和 key (鍵) 作為參數(shù)并返回最終緩存鍵(cache key)。KEY_PREFIX
:一個(gè)用于所有緩存鍵的字符串,避免沖突。LOCATION
:緩存的位置?;谀愕木彺婧蠖?,這可能是一個(gè)路徑、一個(gè)主機(jī)和端口,或者是內(nèi)存中后端的名字。OPTIONS
:任何額外的傳遞向緩存后端的參數(shù)。TIMEOUT
:默認(rèn)的超時(shí)時(shí)間,以秒為單位,用于儲存緩存鍵。默認(rèn)設(shè)置是 300 秒,也就是五分鐘。如果把它設(shè)置為None
,緩存鍵將不會過期。VERSION
:默認(rèn)的緩存鍵的版本。對于緩存版本是很有用的。
把 memcached 添加進(jìn)你的項(xiàng)目
讓我們?yōu)槲覀兊捻?xiàng)目配置緩存。編輯 educa
項(xiàng)目的 settings.py
文件,添加以下代碼:
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', } }
我們正在使用 MemcachedCache
后端。我們使用 address:port
標(biāo)記指定了它的位置。如果你有多個(gè) memcached
實(shí)例,你可以在 LOCATION
中使用列表。
監(jiān)控緩存
這里有一個(gè)第三方包叫做 django-memcached-status ,它可以在管理站點(diǎn)展示你的項(xiàng)目的 memcached
實(shí)例的統(tǒng)計(jì)數(shù)據(jù)。為了兼容 Python3(譯者夜夜月注:python3大法好。) ,從下面的分支中安裝它:
pip install git+git://github.com/zenx/django-memcached-status.git
編輯 settings.py
,然后把 memcached_status
添加進(jìn) INSTALLED_APPS
設(shè)置中。確保 memcached 正在運(yùn)行,在另外一個(gè) shell 中打開開發(fā)服務(wù)器,然后訪問 http://127.0.0.1:8000/adim/ ,使用超級用戶登錄進(jìn)管理站點(diǎn),你就可以看到如下的區(qū)域:
這張圖片展示了緩存使用。綠色代表了空閑的緩存,紅色的表示使用了的空間。如果你點(diǎn)擊方框的標(biāo)題,它展示了你的 memcached 實(shí)例的統(tǒng)計(jì)詳情。
我們已經(jīng)為項(xiàng)目安裝好了 memcached 并且可以監(jiān)控它。讓我們開始緩存數(shù)據(jù)吧!
緩存級別
Django 提供了以下幾個(gè)級別按照顆粒度上升的緩存排列:
Low-level cache API
:提供了最高顆粒度。允許你緩存具體的查詢或計(jì)算結(jié)果。Per-view cache
:提供單一視圖的緩存。Template cache
:允許你緩存模板片段。Per-site cache
:最高級的緩存。它緩存你的整個(gè)網(wǎng)站。
在你執(zhí)行緩存之請仔細(xì)考慮下緩存策略。首先要考慮那些不是單一用戶為基礎(chǔ)的查詢和計(jì)算開銷
使用 low-level cache API (低級緩存API)
低級緩存 API 讓你可以緩存任意顆粒度的對象。它位于 django.core.cache
。你可以像這樣導(dǎo)入它:
from django.core.cache import cache
這使用的是默認(rèn)的緩存。它相當(dāng)于 caches['default']
。通過它的別名來連接一個(gè)特定的緩存也是可能的:
from django.core.cache import caches my_cache = caches['alias']
讓我們看看緩存 API 是如何工作的。使用命令 python manage.py shell
打開 shell 然后執(zhí)行下面的代碼:
>>> from django.core.cache import cache>>> cache.set('musician', 'Django Reinhardt', 20)
我們連接的是默認(rèn)的緩存后端,使用 set{key,value, timeout}
來保存一個(gè)名為 musician
的鍵和它的為字符串 Django Reinhardt
的值 20 秒鐘。如果我們不指定過期時(shí)間,Django 會使在 CACHES
設(shè)置中緩存后端的默認(rèn)過期時(shí)間。現(xiàn)在執(zhí)行下面的代碼:
>>> cache.get('musician')'Django Reinhardt'
我們在緩存中檢索鍵。等待 20 秒然后指定相同的代碼:
>>> cache.get('musician')None
musician
緩存鍵已經(jīng)過期了,get()
方法返回了 None 因?yàn)殒I已經(jīng)不在緩存中了。
在緩存鍵中要避免儲存 None 值,因?yàn)檫@樣你就無法區(qū)分緩存值和緩存過期了
讓我們緩存一個(gè)查詢集:
>>> from courses.models import Subject>>> subjects = Subject.objects.all()>>> cache.set('all_subjects', subjects)
我們執(zhí)行了一個(gè)在 Subject
模型上的查詢集,然后把返回的對象儲存在 all_subjects
鍵中。讓我們檢索一下緩存數(shù)據(jù):
>>> cache.get('all_subjects')[<Subject: Mathematics>, <Subject: Music>, <Subject: Physics>, <Subject: Programming>]
我們將會在視圖中緩存一些查詢集。編輯 courses
應(yīng)用的 views.py
,添加以下 導(dǎo)入:
from django.core.cache import cache
在 CourseListView
的 get()
方法,把下面這幾行:
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 。如果鍵沒有被找到(沒有被緩存,或者緩存了但是過期了),我們就執(zhí)行查詢來檢索所有的 Subject
對象和它們課程的數(shù)量,我們使用 cache.set()
來緩存結(jié)果。
打開代發(fā)服務(wù)器,訪問 http://127.0.0.1:8000 。當(dāng)視圖被執(zhí)行的時(shí)候,緩存鍵沒有被找到的話查詢集就會被執(zhí)行。訪問http://127.0.0.1:8000/adim/ 然后打開 memcached 統(tǒng)計(jì)。你可以看到類似于下面的緩存的使用數(shù)據(jù):
看一眼 Curr Items 應(yīng)該是 1 。這表示當(dāng)前有一個(gè)內(nèi)容被緩存。Get Hits 表示有多少的 get 操作成功了,Get Miss表示有多少的請求丟失了。Miss Ratio 是使用它們倆來計(jì)算的。
現(xiàn)在導(dǎo)航回 http://127.0.0.1:8000/ ,重載頁面幾次。如果你現(xiàn)在看緩存統(tǒng)計(jì)的話,你就會看到更多的(Get Hits和Cmd Get被執(zhí)行了)
基于動態(tài)數(shù)據(jù)的緩存
有很多時(shí)候你都會想使用基于動態(tài)數(shù)據(jù)的緩存的。基于這樣的情況,你必須要?jiǎng)?chuàng)建包含所有要求信息的動態(tài)鍵來特別定以緩存數(shù)據(jù)。編輯 courses
應(yīng)用的 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})
在這個(gè)場景中,我們把課程和根據(jù)科目篩選的課程都緩存了。我們使用 all_courses
緩存鍵來儲存所有的課程,如果沒有給科目的話。如果給了一個(gè)科目的話我們就用 'subject_()_course'.format(subject.id)
動態(tài)的創(chuàng)建緩存鍵。
注意到我們不能用一個(gè)緩存查詢集來創(chuàng)建另外的查詢集是很重要的,因?yàn)槲覀円呀?jīng)緩存了當(dāng)前的查詢結(jié)果,所以我們不能這樣做:
courses = cache.get('all_courses') courses.filter(subject=subject)
相反,我們需要?jiǎng)?chuàng)建基本的查詢集 Course.objects.annotate(total_modules=Count('modules'))
,它不會被執(zhí)行除非你強(qiáng)制執(zhí)行它,然后用它來更進(jìn)一步的用 all_courses.filter(subject=subject)
限制查詢集萬一數(shù)據(jù)沒有在緩存中找到的話。
緩存模板片段
緩存模板片段是一個(gè)高級別的方法。你需要使用 {% load cache %}
在模板中載入緩存模板標(biāo)簽。然后你就可以使用 {% cache %}
模板標(biāo)簽來緩存特定的模板片段了。你通常可以像下面這樣使用緩存標(biāo)簽:
{% cache 300 fragment_name %} ... {% endcache %}
{% cache %}
標(biāo)簽要求兩個(gè)參數(shù):過期時(shí)間,以秒為單位,和一個(gè)片段名稱。如果你需要緩存基于動態(tài)數(shù)據(jù)的內(nèi)容,你可以通過傳遞額外的參數(shù)給 {% cache %}
模板標(biāo)簽來特別的指定片段。
編輯 students
應(yīng)用的 /students/course/detail.html
。在頂部添加以下代碼,就在 {% extends %}
標(biāo)簽的后面:
{% 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
和傳遞當(dāng)前的 Module
對象來緩存模板片段。這對于當(dāng)請求不同的模型是避免緩存一個(gè)模型的內(nèi)容和服務(wù)錯(cuò)誤的內(nèi)容來說是很重要的。
如果
USE_I18N
設(shè)置是為 True,單一站點(diǎn)的中間件緩存將會遵照當(dāng)前激活的語言。如果你使用了{% cache %}
模板標(biāo)簽以及可用翻譯特定的變量中的一個(gè),那么他們的效果將會是一樣的,比如:{% cache 600 name request.LANGUAGE_CODE %}
緩存視圖
你可以使用位于 django.views.decrators.cache
的 cache_page
裝飾器來煥春輸出的單個(gè)視圖。裝飾器要求一個(gè)過期時(shí)間的參數(shù)(以秒為單位)。
讓我們在我們的視圖中使用它。編輯 students
應(yīng)用的 urls.py
,添加以下 導(dǎo)入:
from django.views.decorators.cache import cache_page
然后按照如下在 student_course_detail_module
URL 模式上應(yīng)用 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'),
現(xiàn)在 StudentCourseDetailView
的結(jié)果就會被緩存 15 分鐘了。
單一的視圖緩存使用 URL 來創(chuàng)建緩存鍵。多個(gè)指向同一個(gè)視圖的 URLs 將會被分開儲存
使用單一站點(diǎn)緩存
這是最高級的緩存。他讓你可以緩存你的整個(gè)站點(diǎn)。
為了使用單一站點(diǎn)緩存,你需要編輯項(xiàng)目中的 settings.py
,把 UpdateCacheMiddleware
和 FetchFromCacheMiddleware
添加進(jìn) MIDDLEWARE_CLASSES
設(shè)置中:
MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.cache.FetchFromCacheMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', # ...)
記住在請求的過程中,中間件是按照所給的順序來執(zhí)行的,在相應(yīng)過程中是逆序執(zhí)行的。UpdateCacheMiddleware
被放在 CommonMiddleware
之前,因?yàn)樗谙鄳?yīng)時(shí)才執(zhí)行,此時(shí)中間件是逆序執(zhí)行的。FetchFromCacheMiddleware
被放在 CommonMiddleware
之后,是因?yàn)樗枰B接后者的的請求數(shù)據(jù)集。
然后,把下列設(shè)置添加進(jìn) settings.py
文件:
CACHE_MIDDLEWARE_ALIAS = 'default'CACHE_MIDDLEWARE_SECONDS = 60 * 15 # 15 minutesCACHE_MIDDLEWARE_KEY_PREFIX = 'educa'
在這些設(shè)置中我們?yōu)橹虚g件使用了默認(rèn)的緩存,然后我們把全局緩存過期時(shí)間設(shè)置為 15 分鐘。我們也指定了所有的緩存鍵前綴來避免沖突萬一我們?yōu)槎鄠€(gè)項(xiàng)目使用了相同的 memcached 后端。我們的站點(diǎn)現(xiàn)在將會為所有的 GET 請求緩存以及返回緩存內(nèi)容。
我們已經(jīng)完成了這個(gè)來測試單一站點(diǎn)緩存功能。盡管,以站點(diǎn)的緩存對于我們來說是不怎么合適的,因?yàn)槲覀兊恼n程管理視圖需要展示更新數(shù)據(jù)來實(shí)時(shí)的給出任何的修改。我們項(xiàng)目中的最好的方法是緩存用于展示給學(xué)生的課程內(nèi)容的模板或者視圖數(shù)據(jù)。
我們已經(jīng)大致體驗(yàn)過了 Django 提供的方法來緩存數(shù)據(jù)。你應(yīng)合適的定義你自己的緩存策略,優(yōu)先考慮開銷最大的查詢集或者計(jì)算。
總結(jié)
在這一章中,我們創(chuàng)建了一個(gè)用于課程的公共視圖,創(chuàng)建了一個(gè)用于學(xué)生注冊和報(bào)名課程的系統(tǒng)。我們安裝了 memcached 以及實(shí)現(xiàn)了不同級別的緩存。
在下一章中,我們將會為你的項(xiàng)目創(chuàng)建 RESTful API。
(譯者 @夜夜月注:終于只剩下最后一章了!)