《Django By Example》第十章 下 中文 翻译 (
全文链接
第一章 创建一个blog应用
第二章 使用高级特性来增强你的blog
第三章 扩展你的blog应用
第四章上 创建一个社交网站
第四章下 创建一个社交网站
第五章 在你的网站中分享内容
第六章 跟踪用户动作
第七章 建立一个在线商店
第八章 管理付款和订单
第九章上 扩展你的商店
第九章下 扩展你的商店
第十章上 创建一个在线学习平台
第十章下 创建一个在线学习平台
第十一章 缓存内容
第十二章 构建一个API
书籍出处:https://www.packtpub.com/web-development/django-example
原作者:Antonio Melé
(译者注:翻译本章过程中几次想放弃,但是既然都到第十章了,怎么能放弃!)
(以下是第十章下半章)
使用组和权限
我们已经创建了基础的视图来管理课程。但是目前所有的用户都可以使用这些视图。我们想要限制这些视图从而只有教师有权限去创建和管理课程。Django认证框架包含一个权限系统允许你去分配权限给用户和组。我们将要创建一个组给教师用户并且分配权限给他们可以创建,更新以及删除课程。
使用命令python manage.py runserver
命令运行开发服务器并且在你的浏览器中打开 http://127.0.0.1:8000/admin/auth/group/add/ 来创建一个新的Group
对象。添加的组名为Instructors,然后选择courses应用中的除了Subject模型的所有权限,如下所示:
如你所见,有三种不同的权限给每个模型:Can add,can change以及Can delete。选择好给这个组的权限之后,点击Save按钮。
Django会自动给模型创建权限,但是你也可以创建定制的权限。你可以找到更多关于添加定制权限的文档,通过访问 https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#custom-permissions 。
打开 http://127.0.0.1:8000/admin/auth/user/add/ 然后创建一个新用户。编辑这个用户然后添加Instructors组给这个用户如下所示:
django-10-4用户会继承他们所在组的权限,但是你也可以在管理平台上添加单独的权限给一个指定的用户。当用户的is_superuser设置为True的时候会自动拥有所有的权限。
限制使用基于类的视图
我们将要限制使用基于类的视图从而只有那些拥有合适权限的用户才能添加,修改,或者删除Course
对象认证框架包含一个permission_required
装饰器来限制对视图的使用。Django 1.9将会包含权限mixins给基于类的视图(译者注:到目前为止,Django版本已经是1.10.6)。然而,Django1.8还没有包含它们。因此,我们将要第三方模块提供的权限mixins,该第三方模块名为 django-braces(译者注:。。。。。。下面我是不是可以不用翻译了。。。。。。)。
使用django-braces的mixins
Django-braces是一个第三方的模块,它包含一个通用mixins的采集给Django使用。这些mixins提供额外的特性给基于类的视图。你可以看到django-braces提供的所有mixins列表,通过访问 http://django-braces.readthedocs.org/en/latest/。
使用pip命令安装django-braces:
pip install django-braces==1.8.1
我们将要使用以下两个django-braces提供的mixinx来限制视图的使用:
-
LoginRequiredMixin:复制
login_required
装饰器的功能。 - PermissionRequiredMixin:准许拥有指定权限的用户使用该视图。请记住,超级用户自动拥有所有权限。
编辑courses应用的views.py文件,添加如下导入:
from braces.views import LoginRequiredMixin,
PermissionRequiredMixin
像下面一样让OwnerCourseEditMixin
继承LoginRequiredMixin
:
class OwnerCourseEditMixin(OwnerMixin, LoginRequiredMixin):
model = Course
fields = ['subject', 'title', 'slug', 'overview']
success_url = reverse_lazy('manage_course_list')
之后,添加一个permission_required
属性给创建,跟新,以及删除视图,如下所示:
class CourseCreateView(PermissionRequiredMixin,
OwnerCourseEditMixin,
CreateView):
permission_required = 'courses.add_course'
class CourseUpdateView(PermissionRequiredMixin,
OwnerCourseEditMixin,
UpdateView):
template_name = 'courses/manage/course/form.html'
permission_required = 'courses.change_course'
class CourseDeleteView(PermissionRequiredMixin,
OwnerCourseMixin,
DeleteView):
template_name = 'courses/manage/course/delete.html'
success_url = reverse_lazy('manage_course_list')
permission_required = 'courses.delete_course'
PermissionRequiredMixin
会在用户使用视图的时候检查该用户是否有指定在permission_required
属性中的权限。我们的视图现在只准许有适当权限的用户使用。
让我们给以上视图创建URLs。在courses应用目录中创建新的文件命名为urls.py。添加以下代码:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^mine/$',
views.ManageCourseListView.as_view(),
name='manage_course_list'),
url(r'^create/$',
views.CourseCreateView.as_view(),
name='course_create'),
url(r'^(?P<pk>\d+)/edit/$',
views.CourseUpdateView.as_view(),
name='course_edit'),
url(r'^(?P<pk>\d+)/delete/$',
views.CourseDeleteView.as_view(),
name='course_delete'),
]
以上的URL模式是给列表,创建,编辑以及删除课程试图使用的。编辑educa项目的主urls.py文件然后包含courses应用的URL模式,如下所示:
urlpatterns = [
url(r'^accounts/login/$', auth_views.login, name='login'),
url(r'^accounts/logout/$', auth_views.logout, name='logout'),
url(r'^admin/', include(admin.site.urls)),
url(r'^course/', include('courses.urls')),
]
我们需要给这些视图创建模块。在courses应用中创建以下目录以及文件:
courses/
manage/
course/
list.html
form.html
delete.html
编辑 courses/manage/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 "course_edit" course.id %}">Edit</a>
<a href="{% url "course_delete" course.id %}">Delete</a>
</p>
</div>
{% empty %}
<p>You haven't created any courses yet.</p>
{% endfor %}
<p>
<a href="{% url "course_create" %}" class="button">Create new course</a>
</p>
</div>
{% endblock %}
这是ManageCourseListView
视图的模板。在这个模板中,我们通过当前用户来排列课程。我们给每个课程都包含了编辑或者删除链接,以及一个创建新课程的链接。
使用命令python manage.py runserver
命令运行开发服务器。在你的浏览器中打开 http://127.0.0.1:8000/accounts/login/?next=/course/mine/ 然后使用Instrctors组中的一个用户进行登录。登录完成后,你会被重定向到 http://127.0.0.1:8000/course/mine/ 并且你会看到如下页面:
这个页面将会展示所有当前用户创建的课程。
让我们创建一个给创建和更新课程视图使用的模板,该模板用来展示表单。编辑courses/manage/course/form.html模板并且输入以下代码:
{% extends "base.html" %}
{% block title %}
{% if object %}
Edit course "{{ object.title }}"
{% else %}
Create a new course
{% endif %}
{% endblock %}
{% block content %}
<h1>
{% if object %}
Edit course "{{ object.title }}"
{% else %}
Create a new course
{% endif %}
</h1>
<div class="module">
<h2>Course info</h2>
<form action="." method="post">
{{ form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Save course"></p>
</form>
</div>
{% endblock %}
这个form.html模板被CoursecREATEvIEW
和courseUpdateView
视图使用。在这个模板中,我们检查是否有个object变量在上下文环境中。如果object存在上下文环境中,我们就知道我们正在更新一个存在的课程,并且我们在页面标题中使用它。如果不存在,我们就要创建一个新的Course对象。
在你的浏览器中打开 http://127.0.0.1:8000/course/mine/ 然后点击Create new course按钮。你会看到如下页面:
django-10-6填写好表单内容然后点击Save course按钮。这个课程将会被保存并且你将会被重定向到课程列表页面。它看上去如下所示:
django-10-7之后,点击你刚才创建的课程的Edit链接。你将会再次看到表单,但是这一次你将编辑一个已经存在的Course对象而不是创建新课程。
最后,编辑courses/manage/course/delete.html模板然后添加以下代码:
{% extends "base.html" %}
{% block title %}Delete course{% endblock %}
{% block content %}
<h1>Delete course "{{ object.title }}"</h1>
<div class="module">
<form action="" method="post">
{% csrf_token %}
<p>Are you sure you want to delete "{{ object }}"?</p>
<input type="submit" class"button" value="Confirm">
</form>
</div>
{% endblock %}
这个模板是给CourseDeleteView
视图使用的。这个视图从Django提供的DeleteView
视图继承而来,DeleteView
视图期望用户确认删除一个对象。
打开你的浏览器,点击你的课程的Delete链接。你会看到如下确认页面:
django-10-8点击CONFIRM按钮。这个课程将会被删除并且你再次会被重定向到课程列表页面。
教师们现在可以创建,编辑,以及删除课程。下一步,我们需要提供他们一个内容管理系统来给课程添加模块以及内容。我们将从管理课程模块开始。
使用formsets
Django自带一个抽象层用于在同一个页面中使用多个表单。这些表单的组合成为formsets。formsets能管理多个Form或者ModelForm表单实例。所有的表单都可以一次性提交并且formset会照顾到一些事情,例如,表单的初始化数据展示,限制表单能够提交的最大数字,以及所有表单的验证。
formsets包含一个is_valid()
方法来一次性验证所有表单。你还可以提供初始数据给表单以及指定展示任意多的额外的空的表单。
你可以学习到更多关于formsets,通过访问 https://docs.djangoproject.com/en/1.8/topics/forms/modelforms/#model-formsets 。
管理课程模块
由于课程会被分为可变数量的模块,因此在这里使用formets是有意义的。在courses应用目录下创建一个forms.py文件,然后添加以下代码:
from django import forms
from django.forms.models import inlineformset_factory
from .models import Course, Module
ModuleFormSet = inlineformset_factory(Course,
Module,
fields=['title',
'description'],
extra=2,
can_delete=True)
以上就是ModuleFormSet
formset。我们使用Django提供的inlineformset_factory()
函数来构建它。内联formsets是在formsets之上的一个小抽象,用于方便被关联对象的操作。这个函数允许我们去给关联到一个Course对象的Module对象动态的构建一个模型formset。
我们使用以下参数去构建formset:
- fields:这个字段将会被formset中的每个表单包含。
- extra:允许我们设置在formset中显示的空的额外的表单数。
- can_delete:如果你将这个参数设置为True,Django将会包含一个布尔字段给所有的表单,该布尔字段将会渲染成一个复选框。允许你确定这个对象你真的要进行删除。
编辑courses应用的views.py文件并且添加如下代码:
from django.shortcuts import redirect, get_object_or_404
from django.views.generic.base import TemplateResponseMixin, View
from .forms import ModuleFormSet
class CourseModuleUpdateView(TemplateResponseMixin, View):
template_name = 'courses/manage/module/formset.html'
course = None
def get_formset(self, data=None):
return ModuleFormSet(instance=self.course,data=data)
def dispatch(self, request, pk):
self.course = get_object_or_404(Course,
id=pk,
owner=request.user)
return super(CourseModuleUpdateView,
self).dispatch(request, pk)
def get(self, request, *args, **kwargs):
formset = self.get_formset()
return self.render_to_response({'course': self.course,
'formset': formset})
def post(self, request, *args, **kwargs):
formset = self.get_formset(data=request.POST)
if formset.is_valid():
formset.save()
return redirect('manage_course_list')
return self.render_to_response({'course': self.course,
'formset': formset})
CourseModuleUpdateView
视图控制formset给一个指定的课程添加,更新,以及删除模块。这个视图继承自以下的mixins和视图:
-
TemplateResponseMixin:这个mixins负责渲染模板以及返回一个HTTP响应。它需要一个template_name属性,该属性指明要被渲染的模板,并提供
render_to_ response()
方法来传递上下文并渲染模板。 -
View:Django提供的基础的基于类的视图。
在这个视图中,我们导入以下方法:
-
get_formset():我们定义这个方法去避免重复构建formset的代码。我们使用可选数据为给予的
Course
对象创建一个ModuleFormSet
对象。 -
dispatch():这个方法由
View
类提供。它需要一个HTTP请求及其参数并尝试委托一个与使用的HTTP方法匹配的小写方法:GET请求被委派给get()
方法和一个POST请求到post()
。在这种方法中,我们使用get_object_or_404()
快捷方式函数获取属于当前用户的给予id参数的Course对象。我们将这串代码包含在dispatch()
方法中是因为我们需要检索所有GET和POST请求的课程。我们保存该对象到这个视图的course
属性给使它能被别的方法使用。 -
get():给GET请求执行。我们构建一个空的
ModuleFormSet
formset并且使用TemplateResponseMixin
提供的render_to_response()
方法将它与当前的Course
对象一起渲染到模板中。 -
post():给POST请求执行。在这个方法中,我们执行以下操作:
- 1 我们使用提交的数据构建一个
ModuleFormSet
实例。 - 2 我们执行formset的
is_valid()
方法来验证其中的所有表单。 - 3 如果这个formset验证通过,我们通过调用
save()
方法来保存它。在这点上,任何的修改操作,例如增加,更新或者标记模块用来删除,都会应用到数据库中。之后,我们重定向用户到manage_course_list
URL。如果这个formset没有通过验证,我们渲染模板展示所有内置的错误信息。
- 1 我们使用提交的数据构建一个
编辑courses应用的urls.py文件,添加以下URL模式:
url(r'^(?P<pk>\d+)/module/$',
views.CourseModuleUpdateView.as_view(),
name='course_module_update'),
在courses/manage/模板目录中创建一个新的目录命名为module。创建一个courses/manage/module/formset.html模板并且添加以下代码:
{% extends "base.html" %}
{% block title %}
Edit "{{ course.title }}"
{% endblock %}
{% block content %}
<h1>Edit "{{ course.title }}"</h1>
<div class="module">
<h2>Course modules</h2>
<form action="" method="post">
{{ formset }}
{{ formset.management_form }}
{% csrf_token %}
<input type="submit" class="button" value="Save modules">
</form>
</div>
{% endblock %}
在这个模板中,我们创建一个<form>
HTML元素,在其中我们包含我们的formset
。我们还通过变量{{ formset.management_form }}
包含给formset使用的管理表单。这个管理表单包含隐藏的字段去控制保单的初始化,总数,最小值和最大值。如你所见,创建一个formset非常容易。
编辑courses/manage/course/list.html模板并且在课程编辑和删除链接下方添加以下链接给course_module_update
使用:
<a href="{% url "course_edit" course.id %}">Edit</a>
<a href="{% url "course_delete" course.id %}">Delete</a>
<a href="{% url "course_module_update" course.id %}">Edit modules</a>
我们已经包含了用来编辑课程模板的链接。在你浏览器中打开 http://127.0.0.1:8000/course/mine/ 然后选择一个课程点击对应的Edit modules链接。你会看到一个如下的formset:
django-10-9这个formset包含所有在这个课程中存在的Module对象的表单。在这些表单之后,有两个空的额外的表单会被展示因为我们给ModuleFormSet
设置extra=2
。当你保存这个formset的时候,Django将会包含另外两个额外的字段来添加新的模块。
添加内容给课程模块
现在,我们需要一个方法来添加内容给课程模块。我们有四种不同的内容类型:文本,视频,图片以及文件。我们可以考虑创建四个不同的视图去保存内容,给每个模型都对应上一个视图。然而,我们将采取更通用的方法,并创建一个处理创建或更新任何内容模型的对象的视图。
编辑courses应用的views.py文件并且添加如下代码:
from django.forms.models import modelform_factory
from django.apps import apps
from .models import Module, Content
class ContentCreateUpdateView(TemplateResponseMixin, View):
module = None
model = None
obj = None
template_name = 'courses/manage/content/form.html'
def get_model(self, model_name):
if model_name in ['text', 'video', 'image', 'file']:
return apps.get_model(app_label='courses',
model_name=model_name)
return None
def get_form(self, model, *args, **kwargs):
Form = modelform_factory(model, exclude=['owner',
'order',
'created',
'updated'])
return Form(*args, **kwargs)
def dispatch(self, request, module_id, model_name, id=None):
self.module = get_object_or_404(Module,
id=module_id,
course__owner=request.user)
self.model = self.get_model(model_name)
if id:
self.obj = get_object_or_404(self.model,
id=id,
owner=request.user)
return super(ContentCreateUpdateView,
self).dispatch(request, module_id, model_name, id)
以上是ContentCreateUpdateView
视图的第一部分。这个视图允许我们去创建和更新不同模块的内容。这个视图定义了以下方法:
-
get_model():在这儿,我们会对被给予的模型名是否四种内容模型中的一种:text,video,image以及file.之后我们使用Django的
apps
模块去通过给予的模型名来获取实际的类。如果给予的模型名不是其中的一种,我们返回None
。 -
get_form():我们使用表单框架的
modelform_factory()
函数来构建一个动态的表单。由于我们将要给Text,Video,Image以及File模型构建一个表单,我们使用exclude
参数去指定要从表单中排除的公共字段,并允许自动包含所有其他属性。通过做到这点,我们不必去知道依赖的模型中锁包含的字段。 -
dispatch():它检索以下URL参数并且存储相符的模块,模型以及内容对象作为类的属性:
- module_id:The id for the module that the content is/will be associated with(译者注:求比较好的翻译)。
- model_name:要创建或更新的内容的模型名。
- id:这是将要更新的对象的id。在创建新对象的时候它会是
None
。
添加以下get()
和post()
方法给ContentCreateUpdateView
:
def get(self, request, module_id, model_name, id=None):
form = self.get_form(self.model, instance=self.obj)
return self.render_to_response({'form': form,
'object': self.obj})
def post(self, request, module_id, model_name, id=None):
form = self.get_form(self.model,
instance=self.obj,
data=request.POST,
files=request.FILES)
if form.is_valid():
obj = form.save(commit=False)
obj.owner = request.user
obj.save()
if not id:
# new content
Content.objects.create(module=self.module,
return redirect('module_content_list', self.module.id)
return self.render_to_response({'form': form,
'object': self.obj})
以上方法如下所示:
- get():当收到一个GET请求的时候会被执行。我们构建模型表单给
Text
,Video
,Image
,以及File
实例使用当它们被保存的时候。除此以外,我们不会传递实例给创建新的对象,因为self.obj
在没有id提供的时候是None
。 - post():当收到一个POST请求的时候会被执行。我们构建模型表单会传递所有提交的数据和文件给该表单。之后我们验证该表单。如果这个表单验证通过,我们创建一个新的对象并且在保存该对象到数据库之前分配
request.user
作为该对象的拥有者。我们会检查id
参数,如果没有提供id
,我们就知道当前用户正在创建一个新的对象而不是更新一个已经存在的对象。如果这是一个新的对象,我们创建一个Content
对象给给予的模块并且关联新的内容给该模块。
编辑courses应用的urls.py文件禀帖添加以下URL模式:
url(r'^module/(?P<module_id>\d+)/content/(?P<model_name>\w+)/create/$',
views.ContentCreateUpdateView.as_view(),
name='module_content_create'),
url(r'^module/(?P<module_id>\d+)/content/(?P<model_name>\w+)/(?P<id>\d+)/$',
views.ContentCreateUpdateView.as_view(),
name='module_content_update'),
以上新的URL模式如下:
- module_content_create:用来创建新的文本,视频,图片或者文件对象并且给一个模块添加这些对象。它包含
module_id
和model_name
参数。前者允许连接新的内容对象给给予的模块。后者指定构建表单使用的内容模型。 - module_content_update:用来更新一个已有的文本,视频,图片或者文件对象。它包含
module_id
和model_name
参数,以及一个id
参数来辨明那个需要被更新的内容。
在courses/manage/模板目录下创建新的目录命名为content。创建模板courses/manage/content/form.html并且添加以下代码:
{% extends "base.html" %}
{% block title %}
{% if object %}
Edit content "{{ object.title }}"
{% else %}
Add a new content
{% endif %}
{% endblock %}
{% block content %}
<h1>
{% if object %}
Edit content "{{ object.title }}"
{% else %}
Add a new content
{% endif %}
</h1>
<div class="module">
<h2>Course info</h2>
<form action="" method="post" enctype="multipart/form-data">
{{ form.as_p }}
{% csrf_token %}
<p><input type="submit" value="Save content"></p>
</form>
</div>
{% endblock %}
这个模板是给ContentCreateUpdateView
视图使用的。在这个模板中,我们会检查是否有一个object
变量在上下文环境中。如果object
存在上下文环境中,我们知道我们正在更新一个已经存在的对象。如果没有,我们在创建一个新的对象。
我们在<form>
HTML元素中包含enctype="multipart/form-data"
,因为这个表单包含一个文件上传用来给Field
和Image
内容模型使用。
运行开发服务器。给存在的课程创建一个模块并且在你的浏览器中打开 http://127.0.0.1:8000/course/module/6/content/image/create/ 。如果有必要,在ULR中修改模块id。你将会看到以下表单用来创建新的Image
对象:
先不要提交表单。如果你想要尝试,它将会是失败的,因为我们还没有定义module_content_list
的URL。我们一会儿就要去创建它。
我们还需要一个视图去删除内容。编辑courses应用的views.py文件,添加以下代码:
class ContentDeleteView(View):
def post(self, request, id):
content = get_object_or_404(Content,
id=id,
module__course__owner=request.user)
module = content.module
content.item.delete()
content.delete()
return redirect('module_content_list', module.id)
ContentDeleteView
通过给予的id检索content
对象,它删除关联的Text,Video,Image以及File对象,并且在最后,它会删除Content
对象并且重定向用户到module_content_list
URL去排列其他模块的内容。
编辑courses应用的urls.py文件并且添加以下URL模式:
url(r'^content/(?P<id>\d+)/delete/$',
views.ContentDeleteView.as_view(),
name='module_content_delete'),
现在,教师们可以方便的创建,更新以及删除内容。
管理模块和内容
我们已经构建了用来创建,编辑以及删除课程模块和内容的视图。现在,我们需要一个视图去给一个课程显示所有的模块并且给一个指定的模块排列所有的内容。
编辑courses应用的views.py文件并且添加以下代码:
class ModuleContentListView(TemplateResponseMixin, View):
template_name = 'courses/manage/module/content_list.html'
def get(self, request, module_id):
module = get_object_or_404(Module,
id=module_id,
course__owner=request.user)
return self.render_to_response({'module': module})
以上就是ModuleContentListView
视图。这个视图通过给予的id拿到Module
对象该对象是属于当前的用户并且通过给予的模块渲染一个模板。
编辑courses应用的urls.py文件,添加以下URL模式:
url(r'^module/(?P<module_id>\d+)/$',
views.ModuleContentListView.as_view(),
name='module_content_list'),
在templates/courses/manage/module/目录下创建新的模板命名为content_list.html,添加以下代码:
{% extends "base.html" %}
{% block title %}
Module {{ module.order|add:1 }}: {{ module.title }}
{% endblock %}
{% block content %}
{% with course=module.course %}
<h1>Course "{{ course.title }}"</h1>
<div class="contents">
<h3>Modules</h3>
<ul id="modules">
{% for m in course.modules.all %}
<li data-id="{{ m.id }}" {% if m == module %}
class="selected"{% endif %}>
<a href="{% url "module_content_list" 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>
<p><a href="{% url "course_module_update" course.id %}">Edit modules</a>
</p>
</div>
<div class="module">
<h2>Module {{ module.order|add:1 }}: {{ module.title }}</h2>
<h3>Module contents:</h3>
<div id="module-contents">
{% for content in module.contents.all %}
<div data-id="{{ content.id }}">
{% with item=content.item %}
<p>{{ item }}</p>
<a href="#">Edit</a>
<form action="{% url "module_content_delete" content.id %}" method="post">
<input type="submit" value="Delete">
{% csrf_token %}
</form>
{% endwith %}
</div>
{% empty %}
<p>This module has no contents yet.</p>
{% endfor %}
</div>
<hr>
<h3>Add new content:</h3>
<ul class="content-types">
<li><a href="{% url "module_content_create" module.id "text" %}">Text</a></li>
<li><a href="{% url "module_content_create" module.id "image" %}">Image</a></li>
<li><a href="{% url "module_content_create" module.id "video" %}">Video</a></li>
<li><a href="{% url "module_content_create" module.id "file" %}">File</a></li>
</ul>
</div>
{% endwith %}
{% endblock %}
这个模板展示一个课程所有的模块以及被选中的模块的内容。我们迭代课程模块并将它们展示在侧边栏。我们还迭代模块的内容并且通过content.item
去获取关联的Text,Video,Image以及File对象。我们还包含可以创建新的文本,视频,图片以及文件内容的链接。
我们想要知道每个item对象是哪种类型(文本,视频,图片或者文件)的对象。我们需要模型名用来构建URL去编辑对象。除此以外,我们还在模板中展示各式各样的不同的item,基于item的内容类型。我们可以通过访问对象的_meta
属性来从模型的Meta
类中获取一个对象的模型。尽管如此,Django不允许访问开头是下划线的变量或者属性在模板中为了编辑检索私有属性或者调用到私有方法。我们可以通过编写一个定制模板过滤器来解决这个问题。
在courses应用目录下创建以下文件结构:
templatetags/
__init__.py
course.py
编辑course.py模块,添加以下代码:
from django import template
register = template.Library()
@register.filter
def model_name(obj):
try:
return obj._meta.model_name
except AttributeError:
return None
以上就是model_name
模板过滤器。我们可以在模板中通过object|model_name
应用它来给一个对象获取模型的名字。
编辑templates/courses/manage/module/content_list.html模板,在{% extends %}
模板标签下添加以下内容:
{% load course %}
这样将会加载course模板标签。之后,将以下内容:
<p>{{ item }}</p>
<a href="#">Edit</a>
替换成:
<p>{{ item }} ({{ item|model_name }})</p>
<a href="{% url "module_content_update" module.id item|model_name item.id %}">Edit</a>
现在,我们在模板中展示item模型并且使用模型名曲构建编辑对象的链接。编辑courses/manage/course/list.html模板,添加一个链接给module_content_list
URL,如下所示:
<a href="{% url "course_module_update" course.id %}">Edit modules</a>
{% if course.modules.count > 0 %}
<a href="{% url "module_content_list" course.modules.first.id %}">Manage contents</a>
{% endif %}
这个新链接允许用户去访问课程的第一个模块的内容,如果有好多内容的话。
打开 http://127.0.0.1:8000/course/mine/ 然后点击一个包含最新模块的课程的Manage contents链接。你会看到如下页面:
django-10-11当你点击左方侧边栏的一个模块上,它的内容会在主区域显示。这个模板还包含用来给展示的模块添加一个新的文本,视频,图片或者文件内容。给这个模块添加一堆不同的内容然后看下结果。这个内容将会出现在Module contents之后,如下所示:
django-10-12重新整理模块和内容
我们需要提供一个简单的放来去重新排序课程模板和它们的内容。我们将要使用一个JavaScript drag-n-drop 控件去让我们的用户通过拖拽课程的模块来对课程模块进行重新排序。当用户完成拖拽一个模块,我们将会执行一个异步请求(AJAX)去存储新的模块顺序。
我们需要一个视图,该视图通过编译在JSON中的模块的id来检索新的对象。编辑courses应用的views.py文件,添加以下代码:
from braces.views import CsrfExemptMixin, JsonRequestResponseMixin
class ModuleOrderView(CsrfExemptMixin,
JsonRequestResponseMixin,
View):
def post(self, request):
for id, order in self.request_json.items():
Module.objects.filter(id=id,
course__owner=request.user).update(order=order)
return self.render_json_response({'saved': 'OK'})
以上是ModuleOrderView
。我们使用以下django-braces的mixins:
- csrfExemptMixin:用来避免在POST请求中检查一个CSRF token。
- JsonRequestResponseMixin:将请求的数据分析为JSON并且将相应也序列化成JSON并且返回一个
application/json
内容类型的HTTP响应。
我们可以构建一个类似的视图去排序一个模块的内容。添加以下代码到views.py中:
class ContentOrderView(CsrfExemptMixin,
JsonRequestResponseMixin,
View):
def post(self, request):
for id, order in self.request_json.items():
Content.objects.filter(id=id,
module__course__owner=request.user) \
.update(order=order)
return self.render_json_response({'saved': 'OK'})
现在,编辑courses应用的urls.py文件,添加以下URL模式:
url(r'^module/order/$',
views.ModuleOrderView.as_view(),
name='module_order'),
url(r'^content/order/$',
views.ContentOrderView.as_view(),
name='content_order'),
最后,我们在模板中导入drag-n-drop功能。我们将要使用jQuery UI库来使用这个功能。jQuery UI基于jQuery构建并且它提供了一组界面交互,效果和小部件。我们将要使用它的sortable元素。首先,我们需要在基础模板中加载jQuery UI。打开courses应用下的templates/目录下的base.html文件,在加载jQuery的下方添加jQuery UI脚本,如下所示:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
(译者注:要用以上地址,记得翻墙。。。。。或者自己直接下载)
我们在jQuery框架下加载jQuery UI。现在,编辑courses/manage/module/content_list.html模板添加以下代码在模板的底部:
{% block domready %}
$('#modules').sortable({
stop: function(event, ui) {
modules_order = {};
$('#modules').children().each(function(){
// update the order field
$(this).find('.order').text($(this).index() + 1);
// associate the module's id with its order
modules_order[$(this).data('id')] = $(this).index();
});
$.ajax({
type: 'POST',
url: '{% url "module_order" %}',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify(modules_order)
});
}
});
$('#module-contents').sortable({
stop: function(event, ui) {
contents_order = {};
$('#module-contents').children().each(function(){
// associate the module's id with its order
contents_order[$(this).data('id')] = $(this).index();
});
$.ajax({
type: 'POST',
url: '{% url "content_order" %}',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
data: JSON.stringify(contents_order),
});
}
});
{% endblock %}
这个JavaScripy代码在{% block domready %}
区块中,因此它会被包含在我们之前定义在base.html模板中的jQuery的$(document).ready()
事件中。这将保证我们的JavaScripy代码会在页面每次加载的时候都会被执行一次。我们给列在侧边栏的模块定义了一个sortable元素并且给模块内容列也定义了一个不同的。这两者有着相似的方式。在以上代码中,我们执行以下任务:
- 1 首先,我们给modules HTML元素定义了一个sortable元素。请记住,我们使用
#moudles
,因为jQuery给选择器使用CSS符号。 - 2 我们给stop事件指定一个函数。这个时间会在用户每次储存一个元素的时候被触发。
- 3 我们创建一个空的modules_orders目录。给这个目录的键将会是模块的id,并且给每个模块的值都会被分配次序。
- 4 我们迭代
#module
子元素。我们给每个模块重新计算展示次序并且拿到每个模块的data-id属性,该属性包含了模块的id。我们给modules_order目录添加id作为一个键并且模型的新的索引作为值。 - 5 我们运行一个AJAX POST请求给content_order URL,在请求中包含modules_orders的序列化的JSON数据。相应的
ModuleOrderView
会负责更新模块的顺序。
sortable元素排列内容非常类似与上者的方法。回到你的浏览器然后重载页面。现在你将可以点击并且拖动模块和内容,去重新排序它们如下所示:
django-10-13很好!现在你可以重新排序课程模块和模块内容了。
总结
在这章中,你学习了如何创建一个多功能的内容管理系统。你使用了模型继承以及创建了一个定制模型字段。你还通过基于类的视图和mixins工作。你创建了formsets以及一个系统去管理不同类型的内容。
在下一章,你将会创建一个学生注册系统。你还将熏染不同类型的内容,并且你还会学习如何使用Django的缓存框架。
译者总结
四个字:本章真长。