测试开发之Django实战示例 第十一章 渲染和缓存课程内容
第十一章 渲染和缓存课程内容
在上一章中,使用了模型继承和通用关系建立弹性的课程、章节和内容的关联数据模型,并且建立了一个CMS系统,在其中使用了CBV,表单集和AJAX管理课程内容。在这一章将要做的事情是:
创建公开对外展示课程的视图
创建学生注册系统
学生选课功能
渲染不同的课程内容
采用缓存框架缓存课程内容
我们就从建立一个课程目录,供学生们浏览和选课来开始本章。
1展示课程
为了展示课程,我们需要实现如下功能:
列出所有可用的课程,可以通过课程主题来进行筛选
显示某个课程的具体内容
由于数据模型已经齐备,编辑courses应用的views.py文件,增加以下代码:
Copyfrom django.db.models import Count
from .models import SubjectclassCourseListView(TemplateResponseMixin, View):model = Coursetemplate_name = 'courses/course/list.html'defget(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视图,执行了如下任务:
取所有的主题,使用了annotate()分组和Count()聚合方法生成一个其中包含课程的数量字段。
获得所有课程,同样进行了分组,增加了一个按照章节分组计数的字段。
如果传入了某个具体的主题slug字段,就取得该slug对应的具体主题,并且将课程设置为该主题对应的课程,而不是全部课程。
使用个TemplateResponseMixin类提供的render_to_response()方法将上边几个结果返回给模板。
再创建一个显示具体课程的视图,在views.py里增加如下内容:
Copyfrom django.views.generic.detail import DetailViewclassCourseDetailView(DetailView):model = Coursetemplate_name = 'courses/course/detail.html'
这个视图继承了Django内置的DetailView视图,为其指定模型model和模板template_name属性,该CBV会在模板上渲染其中该数据类的详情。DetailView需要一个slug或者主键pk来从指定的Course模型中获取具体信息,然后在template_name属性指定的模板中进行渲染。
编辑educa项目的根路由urls.py文件,增加以下代码:
Copyfrom courses.views import CourseListView
urlpatterns = [# ...path('', CourseListView.as_view(), name='course_list'),
]
我们想让访问该站点的人默认就来到列表页,因此将course_list设置为匹配网站的根目录,将这行放在所有URL的最下边,其他课程相关的URL都带有/course/前缀。
然后编辑courses应用的urls.py文件,增加下边两条URL:
Copypath('subject/<slug:subject>', views.CourseListView.as_view(), name='course_list_subject'),
path('<slug:slug>', views.CourseDetailView.as_view(), name='course_detail'),
我们添加了如下两条路由:
course_list_subject:展示所有的或某个主题下的课程
course_detail:展示某个课程的详情
来为这两个视图创建模板,在templates/courses/目录下创建:
Copycourse/list.htmldetail.html
编辑courses/course/list.html模板,添加下列代码:
Copy{% 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><divclass="contents"><h3>Subjects</h3><ulid="modules"><li {% ifnotsubject %}class="selected"{% endif %}><ahref="{% url "course_list" %}">All</a></li>{% for s in subjects %}<li {% ifsubject == s %}class="selected"{% endif %}><ahref="{% url "course_list_subject" s.slug %}">{{ s.title }}<br><span>{{ s.total_courses }} courses</span></a></li>{% endfor %}</ul></div><divclass="module">{% for course in courses %}{% with subject=course.subject %}<h3><ahref="{% url "course_detail" course.slug %}">{{ course.title }}</a></h3><p><ahref="{% url "course_list_subject" subject.slug %}">{{ subject }}</a>.{{ course.total_modules }} modules.Instructor: {{ course.owner.get_full_name }}</p>{% endwith %}{% endfor %}</div>
{% endblock %}
这个模板用来列出所有的课程。模板中创建了一个列表,展示所有的Subject对象和反向解析的链接course_list_subject,使用判断来切换CSS类selected用于显示当前被选中的主题。然后迭代所有的Course对象,展示其中的总章节数目以及讲师的名字。
启动站点,打开http://127.0.0.1:8000/,可以看到如下页面:
左侧边栏包含所有的主题以及主题中的课程数量,右侧在默认情况下,显示所有的主题其中的所有的课程。如果选择任何主题,则只显示该主题对应的课程。
编辑courses/course/detail.html,添加如下代码:
Copy{% extends "base.html" %}
{% block title %}{{ object.title }}
{% endblock %}
{% block content %}{% with subject=course.subject %}<h1>{{ object.title }}</h1><divclass="module"><h2>Overview</h2><p><ahref="{% 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/,点击右侧任意一个课程,可以看到如下页面:
我们已经建立好了公共的(不需要特别权限)的展示课程的页面,下一步,需要允许用户以学生身份注册并且选课。
2增加学生注册功能
建立一个新的应用来管理学生注册功能:
Copypython manage.py startapp students
编辑settings.py激活新应用:
CopyINSTALLED_APPS = [# ...'students.apps.StudentsConfig',
]
2.1创建注册视图
编辑students目录内的views.py文件,增加如下代码:
Copyfrom django.urls import reverse_lazy
from django.views.generic.edit import CreateView
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, loginclassStudentRegistrationView(CreateView):template_name = 'students/student/registration.html'form_class = UserCreationFormsuccess_url = reverse_lazy('student_course_list')defform_valid(self, form):result = super(StudentRegistrationView, self).form_valid(form)cd = form.cleaned_datauser = authenticate(username=cd['username'], password=cd['password1'])login(self.request, user)return result
这是允许学生注册的视图,继承了内置的CreateView视图,该视图提供了创建模型对象的一般方法。这个视图需要如下属性:
template_name:需要渲染的模板位置
form_class:必须是一个ModelForm对象,这里指定为Django内置的建立新用户的UserCreationForm表单。
success_url:成功后跳转的URL,通过反向解析student_course_list获取,看名字就知道是给学生列出课程列表的URL,会在稍后配置该URL。
form_valid()方法表单数据成功验证的时候执行,该方法必须返回一个HTTP响应。重写该方法以使用户在成功注册之后就登录。
在students应用中创建urls.py文件,并在其中设置该视图的URL:
Copyfrom django.urls import path
from . import viewsurlpatterns = [path('register/', views.StudentRegistrationView.as_view(), name='student_registration'),
]
然后编辑educa项目的根urls.py,为students应用配置二级路由:
Copyurlpatterns = [# ...path('students/', include('students.urls')),
]
之后在students应用中创建如下目录和模板文件:
Copytemplates/students/student/registration.html
编辑students/student/registration.html模板,添加下列代码:
Copy{% extends "base.html" %}
{% block title %}Sign up
{% endblock %}
{% block content %}<h1>Sign up</h1><divclass="module"><p>Enter your details to create an account:</p><formaction=""method="post">{{ form.as_p }}{% csrf_token %}<p><inputtype="submit"value="Create my account"></p></form></div>
{% endblock %}
启动站点,到http://127.0.0.1:8000/students/register/,应该可以看到如下的注册表单:
注意此时StudentRegistrationView视图的success_url属性中的student_course_list URL还没有配置,所以还不能提交表单,否则会报错。会在下一节中配置该URL。
2.2选课功能
在用户注册成功之后,应该能够让其选课以便加入到某门课的学习中去。很显然,一个学生可以选择多门课程,一个课程也有多个学生,需要在Course模型和User模型之间添加一个多对多关系。
编辑courses应用的models.py文件,为Course模型添加一个字段:
CopyclassCourse(models.Model):# ......students = models.ManyToManyField(User, related_name='courses_joined', blank=True)
之后立刻执行数据迁移过程,不再赘述
现在我们可以通过多对多关系来设置学生与课程之间的关系了。之后需要编写一个视图用于实现选课功能。
在students应用内创建forms.py:
Copyfrom django import forms
from courses.models import CourseclassCourseEnrollForm(forms.Form):course = forms.ModelChoiceField(queryset=Course.objects.all(), widget=forms.HiddenInput)
这个表单是学生选课时候提交的表单。course字段使用了ModelChoiceField,供学生选择所有的课程,使用了HiddenInput插件不给学生展示该表单。这个表单将在CourseDetailView中使用,在页面上显示一个选课按钮让学生进行选课。
编辑students应用的views.py文件,增加如下代码:
Copyfrom django.views.generic.edit import FormView
from django.contrib.auth.mixins import LoginRequiredMixin
from .forms import CourseEnrollFormclassStudentEnrollCourseView(LoginRequiredMixin, FormView):course = Noneform_class = CourseEnrollFormdefform_valid(self, form):self.course = form.cleaned_data['course']self.course.students.add(self.request.user)returnsuper(StudentEnrollCourseView, self).form_valid(form)defget_success_url(self):return reverse_lazy('student_course_detail', args=[self.course.id])
这是用于处理学生选课的StudentEnrollCourseView视图。该视图继承了内置LoginRequiredMixin类,一定要用户登录才能使用该功能。还继承了内置的FormView,因为我们要处理表单提交。设置form_class属性为CourseEnrollForm类,设置了一个course属性用于保存学生选的课程对象。当表单验证通过的时候,取得当前的用户,设置多对多关系,然后调用父类的方法保存数据。
get_success_url()方法返回了成功之后跳转的URL,这个方法和success_url属性的功能一样。该URL会在下一节中设置。
编辑students应用中的urls.py文件,为该视图配置URL:
Copy path('enroll-course/', views.StudentEnrollCourseView.as_view(), name='student_enroll_course'),
然后在课程详情页面增加一个选课按钮,编辑courses应用中的views.py文件,找到CourseDetailView视图,修改成如下所示:
Copyfrom students.forms import CourseEnrollFormclassCourseDetailView(DetailView):model = Coursetemplate_name = 'courses/course/detail.html'defget_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()方法,把这个表单添加到模板变量中,并且将表单中隐藏的字段内容初始化成了当前的课程对象,所以可以直接通过按钮提交表单,无需填写隐藏字段。
在courses/course/detail.html文件中找到如下一行:
Copy{{ object.overview|linebreaks }}
将其替换成如下代码:
Copy{{ object.overview|linebreaks }}
{% if request.user.is_authenticated %}<formaction="{% url "student_enroll_course" %}" method="post">{{ enroll_form }}{% csrf_token %}<inputtype="submit"class="button"value="Enroll now"></form>
{% else %}<ahref="{% url "student_registration" %}" class="button">Register to enroll
</a>
{% endif %}
这样就给页面添加上了按钮,如果用户已登录,就展示该按钮,包含一个指向student_enroll_course的隐藏表单,如果未登录,展示一个登录链接供用户登录。
启动站点,在浏览器中打开http://127.0.0.1:8000/,然后点击一个具体的课程,如果已经登录,可以看到该按钮,如下所示:
如果未登录,则看到的是一个REGISTER TO ENROLL的按钮。
3访问课程内容
在学生选好课之后,还必须创建一个视图给学生展示课程中的章节和内容,以便让他们访问课程内容来进行具体学习。
编辑students应用的views.py文件,添加下列代码:
Copyfrom django.views.generic.listimport ListView
from courses.models import CourseclassStudentCourseListView(LoginRequiredMixin, ListView):model = Coursetemplate_name = 'students/course/list.html'defget_queryset(self):qs = super(StudentCourseListView,self).get_queryset()return qs.filter(students__in=[self.request.user])
这个视图用来给学生展示所有的课程。该视图继承了需要登录的LoginRequiredMixin。还继承了内置的ListView用于展示一系列的Course对象。重写了get_queryset()方法,通过ManyToManyField过滤出当前学生的已选课程。
继续在views.py文件里添加显示详情的类:
Copyfrom django.views.generic.detail import DetailViewclassStudentCourseDetailView(DetailView):model = Coursetemplate_name = 'students/course/detail.html'defget_queryset(self):qs = super(StudentCourseDetailView, self).get_queryset()return qs.filter(students__in=[self.request.user])defget_context_data(self, kwargs):context = super(StudentCourseDetailView, self).get_context_data(kwargs)course = self.get_object()if'module_id'in self.kwargs:context['module'] = course.modules.get(id=self.kwargs['module_id'])else:context['module'] = course.modules.all()[0]return context
这个视图用于向学生展示他们选的课程和章节。依然重写了get_queryset()方法用于返回与当前学生已选课程。重写了get_context_data()方法,如果给了一个module_id,就将模板变量module设置为这个module_id对应的课程,如果没给出,默认为该课程的第一个章节。这样学生就能浏览整个课程的章节。
然后在students应用中的urls.py中为该视图配置URL:
Copy path('course/',views.StudentCourseListView.as_view(),name='student_course_list'),path('course/<pk>/',views.StudentCourseDetailView.as_view(),name='student_course_detail'),path('course/<pk>/<module_id>/',views.StudentCourseDetailView.as_view(),name='student_course_detail_module'),
在students应用中的templates/students/目录下创建如下文件和目录结构:
Copycourse/detail.htmllist.html
编辑students/course/list.html:
Copy{% extends "base.html" %}
{% block title %}My courses{% endblock %}
{% block content %}<h1>My courses</h1><divclass="module">{% for course in object_list %}<divclass="course-info"><h3>{{ course.title }}</h3><p><ahref="{% url "student_course_detail" course.id %}">Access contents</a></p></div>{% empty %}<p>You are not enrolled in any courses yet.<ahref="{% url "course_list" %}">Browse courses</a>to enroll in a course.</p>{% endfor %}</div>
{% endblock %}
这个模板用于展示用户所有选的课程。注意在上一小节里,学生注册成功之后,会被重定向至student_course_list URL,但是如果在其他页面登录,会被导向内置的验证模块的相关URL,所以修改settings.py:
Copyfrom django.urls import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('student_course_list')
设置成这样之后,所有内置auth模块完成登录操作之后都跳转到该指定地址。现在所有的学生在注册成功之后都会跳转到student_course_list URL,即显示该学生已选课程的页面。
再编辑students/course/detail.html,添加下列代码:
Copy{% extends "base.html" %}
{% block title %}{{ object.title }}
{% endblock %}
{% block content %}<h1>{{ module.title }}</h1><divclass="contents"><h3>Modules</h3><ulid="modules">{% for m in object.modules.all %}<lidata-id="{{ m.id }}" {% ifm == module %}class="selected"{% endif %}><ahref="{% url "student_course_detail_module" object.idm.id %}"><span>Module <spanclass="order">{{ m.order|add:1 }}</span></span><br>{{ m.title }}</a></li>{% empty %}<li>No modules yet.</li>{% endfor %}</ul></div><divclass="module">{% for content in module.contents.all %}{% with item=content.item %}<h2>{{ item.title }}</h2>{{ item.render }}{% endwith %}{% endfor %}</div>
{% endblock %}
这是用于让学生具体学习已选课程内容的页面。首先我们创建了一个列表包含所有章节,并且高亮当前章节,然后迭代所有当前章节中的内容,使用了一个{{ item.render }}来展示具体的内容。
此时render()方法还没有编写,在下一节中就来为每个内容对象编写这个方法来展示不同种类的内容。
3.1渲染各种课程内容
我们想为不同的内容编写一个统一的渲染方式。编辑courses应用的models.py文件,来为这四个类共同继承的基类ItemBase模型编写一个render()方法:
Copyfrom django.template.loader import render_to_string
from django.utils.safestring import mark_safeclassItemBase(models.Model):# ......defrender(self):return render_to_string('courses/content/{}.html'.format(self._meta.model_name), {'item': self})
这个方法利用的内置的render_to_string()方法,传入一个模板名称和上下文,然后模板渲染成为一个HTML字符串。每种类型的内容采用不同名称的模板。使用self._meta.model_name获取当前的模型名字。通过这个render()方法,就得到了渲染内容的通用接口。
在courses应用的templates/courses/下边建立如下目录和文件结构:
Copycontent/text.htmlfile.htmlimage.htmlvideo.html
编辑courses/content/text.html,添加如下代码:
Copy{{ item.content|linebreaks|safe }}
编辑courses/content/file.html,添加如下代码:
Copy<p><ahref="{{ item.file.url }}"class="button">Download file</a></p>
编辑courses/content/image.html,添加如下代码:
Copy<p><imgsrc="{{ item.file.url }}"></p>
由于ImageField和FileField都是文件字段,为了管理这两个字段,必须在settings.py中配置媒体文件的路径:
CopyMEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
回忆一下,这里MEDIA_URL是指上传媒体文件的路径,MEDIA_ROOT是指的查找媒体文件的路径。
编辑项目的根urls.py文件,在开头添加下列导入代码:
Copyfrom django.conf import settings
from django.conf.urls.static import static
然后在末尾追加:
Copyif settings.DEBUG:urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
现在我们的站点就能够接受文件上传和提供文件存储了。在开发的过程中,Django会管理媒体文件。但在正式生产环境中,就不能如此配置了。我们将在第十三章学习生产环境的配置。
关于video.html,有点额外的事情要做。将使用django-embed-video模块来集成视频内容。django-embed-video是一个第三方模块,可以将来自YouTube或者Vimeo等来源的视频内容集成到模板中,只需为其提供视频的URL即可。
安装该模块:
Copypip install django-embed-video==1.1.2
然后在settings.py里激活该应用:
CopyINSTALLED_APPS = [# ...'embed_video',
]
可以在https://django-embed-video.readthedocs.io/en/latest/找到这个模块的文档。
现在来编辑video.html:
Copy{% load embed_video_tags %}
{% video item.url "small" %}
现在启动站点,到http://127.0.0.1:8000/course/mine/,以Instructors组内用户的身份登录,为一个课程添加各种课程内容,视频地址可以拷贝任意的YouTube链接比如 https://www.youtube.com/watch?v=bgV39DlmZ2U。
在添加完内容之后,到http://127.0.0.1:8000/点击刚创建的课程,再点击Enroll Now,之后被重定向到课程列表,应该可以看到类似下面的页面:
这样就完成了展示内容的通用方法。
译者注:这里还有一些不完善的地方,比如选了某个课之后回到列表页面再次进入已经选过的课程,会看到Enroll Now还在,其实应该显示Start to Learn之类的词语。这只要在模板中检测一下当前的课程是否包含在用户已经选择的课程中就可以了。
此外在测试代码的时候还发现,如果一个课程内没有章节,则作者在StudentCourseDetailView中的最后一句:
Copy context['module'] = course.modules.all()[0]
此处硬编码了返回第一个查询结果,就会报错,修改方法是做个判断,如果长度=0,就返回空就可以了,这样页面不会渲染出内容。
4使用缓存框架
HTTP请求对我们的Web应用来说,意味着查询数据和处理业务逻辑和渲染模板等工作,这比返回一个静态的页面开销要大很多。
当站点的流量越来越大的时候,大量访问给后端带领的压力是巨大的。这个时候就是缓存系统大派用场的时刻。把一个HTTP请求导致的数据查询,业务逻辑处理结果,甚至渲染后的内容缓存起来,就可以避免在后续类似的请求中反复执行开销大的操作,会有效地提高网站的响应时间。
Django包含一个健壮的缓存系统,可以缓存不同粒度的数据。可以缓存一个查询,一个视图的返回结果,部分模板的渲染内容,甚至整个站点。在缓存系统中存储的数据有时效性,可以设置其过期的时间。
当应用接到一个HTTP请求的时候,通常按照如下的顺序使用缓存系统:
在缓存系统中寻找HTTP请求需要的数据
如果找到了,返回缓存的数据
如果没有找到,按照如下顺序执行:
进行数据查询或者处理,得到数据
将数据保存在缓存内
返回数据
关于详细的缓存机制,可以官方文档:https://docs.djangoproject.com/en/2.0/topics/cache/。
4.1可用的缓存后端
就像数据库一样,Django的缓存机制可以使用多种缓存服务后端来完成,主要有这些:
backends.memcached.MemcachedCache或backends.memcached.PyLibMCCache:是基于Memcached服务的后端。具体使用哪种后端取决于采用哪种Python支持的Memcached模块。
backends.db.DatabaseCache:使用数据库作为缓存(还记得Redis吗)
backends.filebased.FileBasedCache:使用文件作为缓存,序列化每个缓存数据为一个单独的文件
backends.locmem.LocMemCache:本地内存缓存,这是默认值。
backends.dummy.DummyCache:伪缓存机制,仅用于开发。提供了缓存界面但实际上不缓存任何内容。每个进程的缓存互相独立而且线程安全。
对于优化性能而言,最好选取基于内存缓存的缓存机制比如Memcached后端。
4.2安装Memcached服务
我们将使用Memcached缓存。Memcached在内存中运行,占用一定大小的内存作为缓冲区。当被分配的内存用光的时候,Memcached就会以新数据替代较老的数据。
在https://memcached.org/downloads下载Memcached,如果是Linux系统,可以使用下列命令编译安装:
Copy./configure && make && make test && sudo make install
如果使用MacOS X而且安装了Homebrew,可以直接通过brew install memcached安装,也可以在https://brew.sh/下载。安装了Memcached之后,可以通过一个命令启动服务:
Copymemcached -l 127.0.0.1:11211
此时Memcached就会在默认的11211端口运行。还可以通过-l参数指定其他的主机和端口号。可以在https://memcached.org查看文档。
还需要安装Python的Memcached模块:
Copypip install python-memcached==1.59
4.3缓存设置
Django提供了下列缓存设置:
CACHES:一个字典,包含当前项目所有可用的缓存
CACHE_MIDDLEWARE_ALIAS:缓存的别名
CACHE_MIDDLEWARE_KEY_PREFIX:缓存键名的前缀,如果不同站点都用同一个Memcached服务,设置这个KEY可以避免发生键冲突
CACHE_MIDDLEWARE_SECONDS:缓存页面的时间
通过CACHES可以设置项目的缓存系统。这个设置以字典的形式,可以配置多个缓存后端的设置。每个CACHES中的缓存后端有如下设置:
BACKEND:缓存后端
KEY_FUNCTION:生成键的函数,是一个字符串,包含一个可调用函数的位置,这个函数接受前缀,版本和键名为参数,返回一个最终的缓存键
KEY_PREFIX:缓存键名的前缀
LOCATION:缓存后端的位置,根据不同的后端配置,可能是一个目录,一个主机+端口或者一个内存缓存的名称
OPTIONS:其他的向缓存后端传递的配置参数
TIMEOUT:过期设置,单位是秒。默认是300秒=5分钟,如果设置为None,则缓存键不会过期。
VERSION:缓存键的版本号,用于缓存版本信息
4.4为项目配置Memcached缓存
编辑settings.py,将上述的缓存设置加入到文件中:
CopyCACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache','LOCATION': '127.0.0.1:11211',
}
}
这里指定了后端为Memcached,然后指定了主机IP和端口号。如果有很多Memcached配置在不同主机上,可以给LOCATION传一个列表。
4.4.1监控Memcached服务
为了监控Memcached的服务,可以使用第三方模块django-memcache-status,该模块可以在管理后台中显示Memcached的统计情况。安装该模块:
Copypip install django-memcache-status==1.3
编辑setting.py,激活该应用:
CopyINSTALLED_APPS = [# ...'memcache_status',
]
确保Memcached服务在运行,然后进入站点的管理后台,可以看到如下的内容:
绿色的条代表空闲的缓存容量,红色代表已经使用的内容。如果点击标题,可以展开一个详情页看具体内容。已经为项目配置好了Memcached服务,现在来缓存具体内容。
4.5缓存级别
Django为不同粒度的数据提供了如下的缓存级别:
Low-level cache API:粒度最细,缓存精确的查询或者计算结果
Per-view cache:对单独的视图进行缓存
Template cache:缓存模板片段
Per-site cache:站点缓存,最高级别缓存。
在建立缓存系统之前,必须仔细考虑缓存策略。建议对不基于具体用户身份的,系统开销比较大的数据库查询和密集计算进行缓存
4.6使用Low-level cache API
Low-level缓存API可以存储任意粒度的对象,该功能位于django.core.cache,可以导入进来,:
Copyfrom django.core.cache import cache
这个缓存默认使用会使用配置中的default名称对应的缓存后端,类似于数据库,等于通过caches['default]获取缓存后端。。
可以通过如下命令得到一个使用某个具体配置名称的缓存配置:
Copyfrom django.core.cache import caches
my_cache = caches['alias']
在导入当前Django环境的Python命令行中里进行一些实验:
Copy>>> from django.core.cache import cache
>>> cache.set('musician', 'Django Reinhardt', 20)
通过使用了set(key,value,timeout)方法,向默认的缓存后端存入了一个键名叫'musician',值是'Django Reinhardt',20秒过期。如果不给出具体时间,则Django使用settings.py中的默认设置。然后再输入:
Copy>>> cache.get('musician')
'Django Reinhardt'
可以获取对应的值。等待20秒再执行上述命令:
Copy>>> cache.get('musician')
说明该键已经过期,get()方法返回None。
不要在缓存中存储值为None的键,否则无法区分缓存命中与否。
再实验如下代码:
Copy>>> from courses.models import Subject
>>> subjects = Subject.objects.all()
>>> cache.set('all_subjects', subjects)
这里先执行了一个QuerySet查询,然后将查询的结果缓存到all_subjects键中。来试试从缓存中取数:
Copy>>> cache.get('all_subjects')
<QuerySet [<Subject: Mathematics>, <Subject: Music>, <Subject: Physics>, <Subject: Programming>]>
现在我们知道如何使用缓存了。下一步就是给常用的视图增加缓存机制。打开courses应用的views.py文件,首先导入缓存:
Copyfrom django.core.cache import cache
在CourseListView的get()方法里,找到下面这一行:
Copysubjects = Subject.objects.annotate(total_courses=Count('courses'))
将其修改成:
Copysubjects = cache.get('all_subjects')
ifnot subjects:subjects = Subject.objects.annotate(total_courses=Count('courses'))cache.set('all_subjects', subjects)
在这段代码里,我们先尝试去缓存中获取all_subjects这个键,如果结果为None,说明缓存中没有,执行正常数据查询,然后将结果存入到缓存中。
启动站点,访问http://127.0.0.1:8000/,只要访问这个路径,刚才配置的缓存就会启动,由于第一次执行,之前没有缓存内容,所以视图就会将查询结果放入缓存。此时进入管理后台查看Memcached的统计,可以看到如下内容:
在Memcache的统计数据里找到Curr Item这一项,如果严格按照本文来进行,应该为1,除非之前存储了其他内容。这表示当前缓存中有一个键值对。Get Hits表示有多少次Get操作成功命中缓存数据,Get Miss则表示未命中的次数。最上边的Miss Ration使用这两个值计算得到。
现在打开浏览器,反复刷新http://127.0.0.1:8000/,然后再去看Memcahed的统计页面,看看统计数据的变化。
4.6.1缓存动态数据
但有的时候,想缓存动态生成的数据。这就必须建立动态的键,用于唯一确定对具体的缓存数据。看以下例子:
编辑courses应用的views.py文件,修改CourseListView视图如下所示:
Copydefget(self, request, subject=None):subjects = cache.get('all_subjects')ifnot 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)ifnot courses:courses = all_courses.filter(subject=subject)cache.set(key, courses)else:courses = cache.get('all_courses')ifnot courses:courses = all_coursescache.set('all_courses', courses)return self.render_to_response({'subjects': subjects,'subject': subject,'courses': courses})
在这个视图里,我们还保存了所有的课程和按主题过滤的课程。all_courses键对应的是所有课程的查询结果集,动态生成的键名'subject_{}_courses'.format(subject.id)'对应着具体类别的查询结果集。
要注意的是,不能用从缓存中取出来的查询结果再去建立其他查询结果,也就是说下边的代码是不行的:
Copycourses = cache.get('all_courses')
courses.filter(subject=subject)
缓存只能用于存储最终可供页面直接使用的查询结果,不能在中间步骤缓存。所以这就是为什么要在视图开始的地方建立基础查询all_courses = Course.objects.annotate(total_modules=Count('modules')),然后再用courses = all_courses.filter(subject=subject)生成查询结果的原因。
4.7缓存模板片段
缓存模板片段是比较高级别的缓存,需要在模板中加载缓存标签:{% load cache %},然后使用{% cache %}来标记需要缓存的片段。实际使用像这样:
Copy{% cache 300 fragment_name %}
...
{% endcache %}
如上边例子所示,{% cache %}标签有两个可选的参数,第一个是过期秒数,第二个是为该片段起的名称。如果需要缓存动态生成的模板片段,可以再增加额外的参数用于生成唯一KEY。
编辑/students/course/detail.html,为模板在{% extends %}标签后加上:
Copy{% load cache %}
然后找到下列代码:
Copy{% for content in module.contents.all %}{% with item=content.item %}<h2>{{ item.title }}</h2>{{ item.render }}{% endwith %}
{% endfor %}
替换成下列代码:
Copy{% cache 600 module_contents module %}{% for content in module.contents.all %}{% with item=content.item %}<h2>{{ item.title }}</h2>{{ item.render }}{% endwith %}{% endfor %}
{% endcache %}
这里使用了600秒的过期时间,指定了该片段的别名为module_contents,然后用module变量动态创建键,这样就建立了独特的键以避免重复。
如果启用了国际化设置USE_I18N=True,缓存中间件会考虑语言的影响。如果你在一个页面中使用了{% cache %}标签,下次想从缓存中拿到正确的数据,必须将特定语言的代码和缓存标签一起使用,才能得到正确的结果:例如{% cache 600 name request.LANGUAGE_CODE %}.。
4.8缓存视图
可以通过使用django.views.decorators.cache中的cache_page装饰器来缓存视图的输出结果,需要一个参数timeout,是过期秒数。
在视图中使用该装饰器,编辑students应用的urls.py文件,先导入该装饰器:
Copyfrom django.views.decorators.cache import cache_page
然后把cache_page用于student_course_detail和student_course_detail_module两个URL上,如下:
Copypath('course/<pk>/', cache_page(60 * 15)(views.StudentCourseDetailView.as_view()), name='student_course_detail'),
path('course/<pk>/<module_id>/', cache_page(60 * 15)(views.StudentCourseDetailView.as_view()),name='student_course_detail_module'),
这样配置之后,StudentCourseDetailView的结果就会被缓存15分钟。
注意,缓存使用URL来构建缓存键,对同一个视图函数,来自不同URL路由的结果,会被分别缓存。
4.8.1缓存动态数据
缓存站点是级别最高的缓存,允许缓存整个站点。
要启用站点缓存,需要编辑settings.py,把UpdateCacheMiddleware和FetchFromCacheMiddleware中间件加入MIDDLEWARE设置中:
CopyMIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.cache.UpdateCacheMiddleware','django.middleware.common.CommonMiddleware','django.middleware.cache.FetchFromCacheMiddleware',# ...
]
中间件的顺序至关重要,中间件在HTTP请求进来的时候是按照从上到下的顺序执行,返回响应的时候按照从下到上的顺序执行。UpdateCacheMiddleware必须放在CommonMiddleware的上边,因为UpdateCacheMiddleware只在响应的时候才执行。FetchFromCacheMiddleware被放在CommonMiddleware之后,因为FetchFromCacheMiddleware需要CommonMiddleware处理过的请求数据。
然后还需要把下列设置加入到settings.py中:
CopyCACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 60 * 15# 15 minutes
CACHE_MIDDLEWARE_KEY_PREFIX = 'educa'
在这些设置里,设置了使用default名称的缓存后端,15分钟过期时间,以及设置前缀避免重复。现在站点对所有的GET请求,都缓存和优先返回缓存的结果。
这样我们就设置好了整个站点的缓存,然而站点缓存对于我们这个站来说是不适合的,因为CMS系统里更改了数据之后,必须立刻返回更新后的数据。所以最佳的方法是缓存向学生返回课程内容的视图或者模板。
我们已经学习过了Django内的各种方法用于缓存数据。应该明智的设置缓存策略,优先缓存开销高的查询和计算。
总结
在这一章,我们创建了公开展示所有课程的页面,通过多对多关系建立了学生注册和选课系统,并且为站点安装了Memcached服务并缓存了各个级别的内容。
如有不懂还要咨询下方小卡片,博主也希望和志同道合的测试人员一起学习进步
在适当的年龄,选择适当的岗位,尽量去发挥好自己的优势。
我的自动化测试开发之路,一路走来都离不每个阶段的计划,因为自己喜欢规划和总结,
测试开发视频教程、学习笔记领取传送门!!!
相关文章:
测试开发之Django实战示例 第十一章 渲染和缓存课程内容
第十一章 渲染和缓存课程内容在上一章中,使用了模型继承和通用关系建立弹性的课程、章节和内容的关联数据模型,并且建立了一个CMS系统,在其中使用了CBV,表单集和AJAX管理课程内容。在这一章将要做的事情是:创建公开对外…...
90%企业在探索的敏捷开发怎么做?极狐GitLab总结了这些逻辑与流程
本文来自: 彭亮 极狐(GitLab) 高级产品经理 毛超 极狐(GitLab) 研发工程师 极狐(GitLab) 市场部内容团队 “敏捷” 是指能够驾驭变化,保持组织竞争优势的一种能力。自 2001 年《敏捷宣言》以来,敏捷及敏捷开发理念逐渐席卷全球。中国信通院《…...
LeetCode-257. 二叉树的所有路径
目录题目分析递归法题目来源 257. 二叉树的所有路径 题目分析 前序遍历以及回溯的过程如图: 递归法 1.递归函数参数以及返回值 要传入根节点,记录每一条路径的path,和存放结果集的result,这里递归不需要返回值,代…...
测试用例该怎么设计?—— 日常加更篇(下)
😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。 📡主页地址:【Austin_zhai】 🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。…...
Java基础:接口
1.接口的概念 当不是所有子类, 而是多个子类都包含一个方法时, 为了代码书写规范性, 可以用自定义的接口来统一该方法的书写规范. 所以接口可以看作是一种书写规则. 接口是对行为的抽象 抽象类一般是书写在父类当中, 接口是单独书写的, 不是一种类 2.接口的定义和使用 3.接口…...
vuex基础入门:uniapp实现用户登录授权实战
1.背景 vuex是数据共享方案之一,本文以微信小程序登录授权为例介绍一下vuex常用属性state、getters、mutations、actions. 2.基于uniapp实现微信小程序登录授权流程 1.凡是需要用户登录授权信息的页面创建时created方法中需要判断用户是否登录,需要使用本地缓存的token调用服务…...
Windows系统从权限维持角度进行应急响应
一、基本介绍 红队攻击者在对目标进行渗透利用后通常都会进行权限维持,以达到持续利用的目的。而作为防守方进行应急响应时,应该如何与技术高超(jiaohuajianzha)的攻击者斗智斗勇呢?或许可以通过本文可以找到答案。以…...
什么是DNS解析?如何提升DNS解析安全?
DNS解析是保障网站正常运行的一项重要服务,DNS解析出现故障,就会导致网站无法被访问或者被劫持到其他的站点,对业务正常开展造成很大影响,因此网站管理人员要高度关注DNS解析的安全,才能确保网站的正常运转,…...
电路学习笔记
电源部分 2s锂电池 6.4v-8.4v INA180A2IDBVR 电流检测放大器 OUT ADC1_CH0 to ESP32 可能功能:电源电流监测 稳压/电压监测 OUT ADC1_CH1 to ESP32 降压至2.046v-2.686v并通过电容保持稳定 可能功能:降压模块,电压监测 LDO ASM1117-3.3 低压差线性…...
C# 数据结构
目录 一、介绍 二、数组 三、List(列表) 四、Dictionary(字典) 五、Queue(队列) 六、Stack(栈) 七、Hashtable(哈希表) 结束 一、介绍 数据结构是计…...
powerjob的worker启动,研究完了这块代码之后我发现了,代码就是现实中我们码农的真实写照
这是一篇让你受益匪浅的文章,代码即使人生。 worker启动比server启动要复杂一些,毕竟worker是要实际干活的,工欲善其事必先利其器,所以需要准备的工具还是不能少的,server对于powerjob来说,只是一个调度用的…...
配置Qt Creator
前言 为了使Qt Creator更像您最喜欢的代码编辑器或IDE,您可以更改键盘快捷键、配色方案、通用高亮显示、代码片段和版本控制系统的设置。 检查生成和运行设置 Qt Creator是一个集成开发环境(IDE),可以用来开发Qt应用程序。虽然您可以使用Qt Installer…...
C++-类和对象(下)
C-类和对象(下)一,const成员函数二,再谈构造函数1,初始化列表2,explicit关键字三,static成员四,友元(friend)1,全局函数做友元2,类做友…...
什么是仓库管理?
仓库管理包括仓库日常运营所触及的准绳和流程。从较高的层次上讲,这包括接纳和组织仓库空间、布置劳动力、管理库存和完成订单。放大来看,你会发现有效的仓库管理触及到优化和集成这些过程中的每一个,以确保仓库操作的一切方面协同工作&#…...
对话系统学习概述(仅够参考)
对话系统(仅够参考) 目录对话系统(仅够参考)背景类人对话系统的关键特征1、知识运用2、个性体现3、情感识别与表达数据集评价方式评价的一些指标训练模型需要的资源任务型对话系统预训练最新研究进展参考文献背景 对话系统一般包括…...
免费CRM客户管理系统真的存在吗?不仅有,还有5个!
免费CRM客户管理系统真的存在吗?当然有! 说到CRM客户管理系统,相信很多企业并不陌生,是因为CRM客户管理系统已经成为大多数企业最不可或缺的工具。但是对于很多小微企业和个人用户来说,购买和实施CRM的成本仍然难以承…...
C#开发的OpenRA使用自定义字典的比较函数
C#开发的OpenRA使用自定义字典的比较函数 字典是一个常用的数据结构, 因为它采用键值对的方式来保存数据, 这样非常方便程序里进行数据一对一的映射。 比如通过文件名称查找到文件对象,又者通过socket对象找到缓冲区对象。 由于字典是采用HASH算法,所以它的查找时间是非常快…...
DHCP协议
DHCP协议 文章目录DHCP协议DHCP作用及特点DHCP服务IP分配的三种方式DHCP协议中的报文类型DHCP服务工作流程抓包参考动态主机配置协议 DHCP(Dynamic Host Configuration Protocol),提供了一种 插网即用的技术。DHCP是一个应用层协议。当我们将…...
C语言进阶——自定义类型:枚举、联合
🌇个人主页:_麦麦_ 📚今日名言:如果不去遍历世界,我们就不知道什么是我们精神和情感的寄托,但我们一旦遍历了世界,却发现我们再也无法回到那美好的地方去了。当我们开始寻求,我们就已…...
背景透明(opacity vs background)
最近在做项目的时候,遇到透明度的相关设置。 常用的背景透明设置可分为两种,分别是: 一是给background设置透明度。二是利用opacity属性。 在跳了一些坑之后,本人更推荐给background设置透明度,为什么呢?…...
华为OD机试 - 最小施肥机能效(Python)| 真题+思路+考点+代码+岗位
最小施肥机能效 题目 某农场主管理了一大片果园,fields[i]表示不同果林的面积,单位:( m 2 m^2 m2),现在要为所有的果林施肥且必须在 n 天之内完成,否则影响收成。 小布是果林的工作人员,他每次选择一片果林进行施肥,且一片果林施肥完...
vue2 使用 cesium 篇
vue2 使用 cesium 篇 今天好好写一篇哈,之前写的半死不活的。首先说明:这篇博文是我边做边写的,小白也是,实现效果会同时发布截图,如果没有实现也会说明,仅仅作为技术积累,选择性分享࿰…...
2023预测:PKI将受到企业重点关注
2023年,PKI作为关键业务将继续被主流企业关注,根据Keyfactor发布的报告显示,很多企业正努力实施PKI,而以下因素是影响企业决策的主要原因:1、66% 的企业正在其IT环境中部署更多的密钥和证书,而70%的企业表示…...
linux基本功系列之grep命令
文章目录前言一. grep命令介绍二. 语法格式及常用选项三. 参考案例3.1 搜索文件中以root开头的文件3.2 搜索文件中出现的root3.3 搜索除了匹配行之外的行3.4 匹配的部分使用颜色显示3.5 只输出文件中匹配到的地方3.6 输出包含匹配字符串的行,并显示所在的行数3.7 统…...
硬件设计——DDR
一、DDR简介 (1)DDRDouble Data Rate双倍速率同步动态随机存储器。严格的说DDR应该叫DDR SDRAM,人们习惯称为DDR,其中,SDRAM 是Synchronous Dynamic Random Access Memory的缩写,即同步动态随机存取存储器。…...
最近你提前还贷了吗
最近你有想过提前还贷吗?以前,欠别人的是大爷,借别人钱的是孙子。现在好像反过来了呀,想还钱成了孙子。现在,各种银行以各种方式增加你提前还贷的难度。比如第一步,关闭app线上还款入口第二步,需…...
关于STM32常用的8种GPIO输入输出模式的理解
目录 GPIO共有8中输入输出模式,分别是:上拉输入、下拉输入、浮空输入、模拟输入、开漏输出、推挽输出、开漏复用输出、推挽复用输出 ,下面我们详细介绍以下上面的八种输入输出模式。 一、输入模式 (1)上拉输入&#x…...
vue - vue项目中解决 IOS + H5 滑动边界橡皮筋弹性效果
问题: 最近遇到一个问题,我们在企业微信中的 H5 项目中需要用到table表格(支持懒加载 上划加载数据)。但是他们在锁头、锁列的情况下,依旧会出现边界橡皮筋效果。就会显示的很奇怪。 什么是ios橡皮筋效果: 我们知道元素…...
webpack(高级)--创建自己的loader 同步loader 异步loader loader参数校验
webpack 创建自己的loader loader是用于对模块的源代码进行转换(处理) 我们使用过很多loader 比如css-loader style-loader babel-loader 我么如果想要自己创建一个loader 首先创建webpack环境 pnpm add webpack webpack-cli -D 之后创建loader模块…...
Assignment写作各个部分怎么衔接完美?
Assignment格式很简单,就只有四个部分,按着通用的套路来,发现也没什么难度。不过这4个部分自己需要衔接完美,下面就给大家分享一下写Assignment最简单的方法。 如果没有目录可以放在第一页的开头,用“标题字体”加重显…...
免费做优化的网站建设/app注册接单平台
一. 开发背景 想要成为一名优秀的Android开发,你需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样。 正文 腾讯研发人数将近 2 万人,T4 级别的人数大概也不超过 500 人,这还是在近两年 T3 到 T4 级别人…...
wordpress字典/百度云超级会员试用1天
基于人脸识别的移动支付方式-刷脸支付,随着人脸识别技术的发展和应用逐渐在零售、餐饮等消费场景上线应用。而且刷脸支付可结合自助收银系统实现刷脸支付自助收银应用。那么,刷脸支付自助收银怎么助力零售门店? 刷脸支付自助收银使收银结账…...
wordpress 模版修改/百度推广费
2.3 冯?诺依曼结构和哈佛结构 阿兰?图灵在1937年首次提出了一个通用计算设备的设想。他设想所有的计算都可能在一种特殊的机器上执行,这就是现在所说的图灵机。如果我们把计算机定义成一个数据处理器,计算机就可以被看作是一个接收输入数据、处理数据…...
深圳沙头网站建设/个人免费建站系统
基于HSV颜色模型的直方图均衡化图像去雾技术_百度学术 http://xueshu.baidu.com/s?wdpaperuri%3A(8622e930fa7d1a1a46986dd38a978659)&filtersc_long_sign&tnSE_baiduxueshu_c1gjeupa&ieutf-8&sc_ks_paraq%3D%E5%9F%BA%E4%BA%8EHSV%E9%A2%9C%E8%89%B2%E6%A8%A…...
可以玩游戏的网站/百度西安
摘要:二是进入炉渣,计算机在炉内有出路三条,气逸出一是随煤,进入三是生铁。电弧电压,热点嘴直径等气体流量和喷,焊接速度,直径艺参要有钨极钨极焊工手工数主。主要图的投影规律高平齐与俯三视是…...
国外大神的平面设计网站有哪些/网站怎么优化seo
数据库索引编辑锁定索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。如果想按特定职员的姓来查找他或她,则与在表中搜索所有的行相比,索引有助于更快地获取信息。索引的一个主要目的就是加快检…...