1. 程式人生 > >第十章 構建一個線上學習平臺(下)

第十章 構建一個線上學習平臺(下)

10 構建一個線上學習平臺

10.5 建立內容管理系統

現在我們已經建立了一個萬能的資料模型,接下來我們會建立一個內容管理系統(CMS)。CMS允許教師建立課程,並管理它們的內容。我們需要以下功能:

  • 登入到CMS
  • 教師建立的課程列表
  • 建立,編輯和刪除課程
  • 新增單元到課程,並對它們重新排序
  • 新增不同型別的內容到每個單元中,並對它們重新排序

10.5.1 新增認證系統

我們將在平臺中使用Django的認證框架。教師和學生都是Django的User模型的例項。因此,他們可以使用django.contrib.auth的認證檢視登入網站。

編輯educa專案的主urls.py檔案,並引入Django認證框架的login

logout檢視:

from django.conf.urls import include, url
from django.contrib import admin
from django.contrib.auth import views as auth_views

urlpatterns = [
    url(r'^accounts/login/$', auth_views.login, name='login'),
    url(r'^accounts/logout/$', auth_views.logout, name='logout'),
    url(r'^admin/'
, admin.site.urls), ]

10.5.2 建立認證模板

courses應用目錄中建立以下檔案結構:

templates/
    base.html
    registration/
        login.html
        logged_out.html

構建認證模板之前,我們需要為專案準備基礎模板。編輯base.html模板,並新增以下內容:

{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
<title>
{% block title %}Educa{% endblock title %}</title> <link href="{% static "css/base.css" %}" rel="stylesheet"> </head> <body> <div id="header"> <a href="/" class="logo">Educa</a> <ul class="menu"> {% if request.user.is_authenticated %} <li><a href="{% url "logout" %}">Sign out</a></li> {% else %} <li><a href="{% url "login" %}">Sign in</a></li> {% endif %} </ul> </div> <div id="content"> {% block content %}{% endblock content %} </div> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> <script> $(document).ready(function() { {% block domready %}{% endblock domready %} }); </script> </body> </html>

這是基礎模板,其它模板會從它擴充套件。在這個模板中,我們定義了以下塊:

  • title:其它模組用來為每個頁面新增自定義標題的塊。
  • content:主要的內容塊。所有擴充套件基礎模板的模板必須在這個塊中新增內容。
  • domready:位於jQuery的$(document).ready()函式內。允許我們在DOM完成載入時執行程式碼。

這個模板中使用的CSS樣式位於本章例項程式碼的courses應用的static/目錄中。你可以把它拷貝到專案的相同位置。

編輯registration/login.html模板,並新增以下程式碼:

{% extends "base.html" %}{% block title %}Log-in{% endblock title %}{% block content %}
    <h1>Log-in</h1>
    <div class="module">
        {% if form.errors %}
            <p>Your username and password didn't match.Please try again.</p>
        {% else %}
            <p>Please, user the following form to log-in:</p>
        {% endif %}
        <div class="login-form">
            <form action="{% url "login" %}" method="post">
                {{ form.as_p }}{% csrf_token %}
                <input type="hidden" name="next" value="{{ next }}" />
                <p><input type="submit" value="Log-in"></p>
            </form>
        </div>
    </div>
{% endblock content %}

這是Django的login檢視的標準登入模板。編輯registration/logged_out.html模板,並新增以下程式碼:

{% extends "base.html" %}{% block title %}Logged out{% endblock title %}{% block content %}
    <h1>Logged out</h1>
    <div class="module">
        <p>
            You have been successfully logged out. You can 
            <a href="{% url "login" %}">log-in again</a>.
        </p>
    </div>
{% endblock content %}

使用者登出後會顯示這個模板。執行python manage.py runserver命令啟動開發伺服器,然後在瀏覽器中開啟http://127.0.0.1:8000/accounts/login/,你會看到以下登入頁面:

10.5.3 建立基於類的檢視

我們將構建用於建立,編輯和刪除課程的檢視。我們將使用基於類的檢視。編輯courses應用的views.py檔案,並新增以下程式碼:

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

class ManageCourseListView(ListView):
    model = Course
    template_name = 'courses/manage/course/list.html'

    def get_queryset(self):
        qs = super().get_queryset()
        return qs.filter(owner=self.request.user)

這是ManageCourseListView檢視。它從Django的通用ListView繼承。我們覆寫了檢視的get_queryset()方法,只檢索當前使用者建立的課程。要阻止使用者編輯,更新或者刪除不是他們建立的課程,我們還需要在建立,更新和刪除檢視中覆寫get_queryset()方法。當你需要為數個基於類的檢視提供特定行為,推薦方式是使用minxins

10.5.4 為基於類的檢視使用mixins

Mixins是一個類的特殊的多重繼承。你可以用它們提供常見的離散功能,把它們新增到其它mixins中,允許你定義一個類的行為。有兩種主要場景下使用mixins:

  • 你想為一個類提供多個可選的特性
  • 你想在數個類中使用某個特性

你可以在這裡閱讀如何在基於類的檢視中使用mixins的文件。

Django自帶幾個mixins,為基於類的檢視提供額外的功能。你可以在這裡找到所有mixins。

我們將建立包括一個常見功能的mixins類,並把它用於課程的檢視。編輯courses應用的views.py檔案,如下修改:

from django.core.urlresolvers import reverse_lazy
from django.views.generic.list import ListView
from django.views.generic.edit import CreateView
from django.views.generic.edit import UpdateView
from django.views.generic.edit import DeleteView
from .models import Course

class OwnerMixin:
    def get_queryset(self):
        qs = super().get_queryset()
        return qs.filter(owner=self.request.user)

class OwnerEditMixin:
    def form_valid(self, form):
        form.instance.owner = self.request.user
        return super().form_valid(form)

class OwnerCourseMixin(OwnerMixin):
    model = Course

class OwnerCourseEditMixin(OwnerCourseMixin, OwnerEditMixin):
    fields = ['subject', 'title', 'slug', 'overview']
    success_url = reverse_lazy('manage_course_list')
    template_name = 'courses/manage/course/form.html'

class ManageCourseListView(OwnerCourseMixin, ListView):
    template_name = 'courses/manage/course/list.html'

class CourseCreateView(OwnerCourseEditMixin, CreateView):
    pass

class CourseUpdateView(OwnerCourseEditMixin, UpdateView):
    pass

class CourseDeleteView(OwnerCourseMixin, DeleteView):
    template_name = 'courses/manage/course/delete.html'
    success_url = reverse_lazy('manage_course_list')

在這段程式碼中,我們建立了OwnerMixinOwnerEditMixin兩個mixins。我們與Django提供的ListViewCreateViewUpdateViewDeleteView檢視一起使用這些mixins。OwnerMixin實現了以下方法:

  • get_queryset():檢視用這個方法獲得基本的QuerySet。我們的mixin會覆寫這個方法,通過owner屬性過濾物件,來檢索屬於當前使用者的物件(request.user)。

OwnerEditMixin實現以下方法:

  • form_valid():使用Django的ModelFormMixin的檢視會使用這個方法,比如,帶表單或者模型表單的檢視(比如CreateViewUpdateView)。當提交的表單有效時,會執行form_valid()。這個方法的預設行為是儲存例項(對於模型表單),並重定向使用者到success_url。我們覆寫這個方法,在被儲存物件的owner屬性中自動設定當前使用者。這樣,當儲存物件時,我們自動設定了物件的owner

我們的OwnerMixin類可用於與包括owner屬性的任何模型互動的檢視。

我們還定義了一個OwnerCourseMixin,它從OwnerMixin繼承,併為子檢視提供以下屬性:

  • model:用於QuerySet的模型。可以被所有檢視使用。

我們用以下屬性定義了一個OwnerCourseEditMixin

  • fields:模型的這個欄位構建了CreateViewUpdateView檢視的模型表單。
  • success_url:當表單提交成功後,CreateViewUpdateView用它重定向使用者。

最後,我們建立從OwnerCourseMixin繼承的檢視:

  • ManageCourseListView:列出使用者建立的課程。它從OwnerCourseMixinListView繼承。
  • CourseCreateView:用模型表單建立一個新的Course物件。它用在OwnerCourseEditMixin中定義的欄位來構建模型表單,它還從CreateView繼承。
  • CourseUpdateView:允許編輯一個已存在的Course物件。它從OwnerCourseEditMixinUpdateView繼承。
  • CourseDeleteView:從OwnerCourseMixin和通用的DeleteView繼承。定義了success_url,用於刪除物件後重定向使用者。

10.5.5 使用組和許可權

我們已經建立了管理課程的基礎檢視。當前,任何使用者都可以訪問這些檢視。我們想限制這些檢視,只有教師有許可權建立和管理課程。Django的認證框架包括一個許可權系統,允許你給使用者和組分配許可權。我們將為教師使用者建立一個組,並分配建立,更新和刪除課程的許可權。

使用python manage.py runserver命令啟動開發伺服器,並在瀏覽器中開啟http://127.0.0.1:8000/admin/auth/group/add/,然後建立一個新的Group物件。新增組名為Instructors,並選擇courses應用的所有許可權,除了Subject模型的許可權,如下圖所示:

正如你所看到,每個模型有三個不同的許可權:Can addCan changeCan delete。為這個組選擇許可權後,點選Save按鈕。

Django自動為模型建立許可權,但你也可以建立自定義許可權。你可以在這裡閱讀更多關於新增自定義許可權的資訊。

開啟http://127.0.0.1:8000/admin/auth/user/add/,然後新增一個新使用者。編輯使用者,並把它新增Instructors組,如下圖所示:

使用者從它所屬的組中繼承許可權,但你也可以使用管理站點為單個使用者新增獨立許可權。is_superuser設定為True的使用者自動獲得所有許可權。

10.5.5.1 限制訪問基於類的檢視

我們將限制訪問檢視,只有擁有適當許可權的使用者才可以新增,修改或刪除Course物件。認證框架包括一個permission_required裝飾器來限制訪問檢視。Django 1.9將會包括基於類檢視的許可權mixins。但是Django 1.8不包括它們。因此,我們將使用第三方模組django-braces提供的許可權mixins。

譯者注:現在Django的最新版本是1.11.X。

Django-braces是一個第三方模組,其中包括一組通用的Django mixins。這些mixins為基於類的檢視提供了額外的特性。你可以在這裡檢視django-braces提供的所有mixins。

使用pip命令安裝django-braces:

pip install django-braces

我們將使用django-braces的兩個mixins來限制訪問檢視:

  • LoginRequiredMixin:重複login_required裝飾器的功能。
  • PermissionRequiredMixin:允許有特定許可權的使用者訪問檢視。記住,超級使用者自動獲得所有許可權。

編輯courses應用的views.py檔案,新增以下匯入:

from braces.views import LoginRequiredMixin
from braces.views import PermissionRequiredMixin

OwnerCourseMixinLoginRequiredMixin繼承:

class OwnerCourseMixin(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屬性中之指定的許可權。現在只有合適許可權的使用者可以訪問我們的檢視。

讓我們為這些檢視建立URL。在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/', admin.site.urls),
    url(r'^course/', include('courses.urls')),
]

我們需要為這些檢視建立模板。在courses應用的templates/目錄中建立以下目錄和檔案:

courses/
    manage/
        course/
            list.html
            form.html
            delete.html

編輯courses/manage/course/list.html模板,並新增以下程式碼:

{% extends "base.html" %}{% block title %}My courses{% endblock title %}{% 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 content %}

這是ManageCourseListView檢視的模板。在這個模板中,我們列出了當前使用者建立的課程。我們包括了編輯或刪除每個課程的連結,和一個建立新課程的連結。

使用python manage.py runserver命令啟動開發伺服器。在瀏覽器中開啟http://127.0.0.1:8000/accounts/login/?next=/course/mine/,並用屬於Instructors組的使用者登入。登入後,你會重定向到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 title %}{% 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 content %}

form.html模板用於CourseCreateViewCourseUpdateView檢視。在這個模板中,我們檢查上下文是否存在object變數。如果上下文中存在object,我們已經正在更新一個已存在課程,並在頁面標題使用它。否則,我們建立一個新的Course物件。

在瀏覽器中開啟http://127.0.0.1:8000/course/mine/,然後點選Create new course。你會看到以下頁面:

填寫表單,然後點選Save course按鈕。課程會被儲存,並且你會被重定向到課程列表頁面,如下圖所示:

然後點選你剛建立的課程的Edit連結。你會再次看到表單,但這次你在編輯已存在的Course物件,而不是建立一個新的。

最後,編輯courses/manage/course/delete.html模板,並新增以下程式碼:

{% extends "base.html" %}{% block title %}Delete course{% endblock title %}{% 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 content %}

這是CourseDeleteView檢視的模板。這個檢視從Django提供的DeleteView檢視繼承,它希望使用者確認是否刪除一個物件。

開啟你的瀏覽器,並點選課程的Delete連結。你會看到以下確認頁面:

點選CONFIRM按鈕。課程會被刪除,你會再次被重定向到課程列表頁面。

現在教師可以建立,編輯和刪除課程。下一步,我們將給教師提供一個內容管理系統,為課程新增單元和內容。我們從管理課程單元開始。

10.5.6 使用表單集

Django自帶一個抽象層,可以在同一個頁面使用多個表單。這些表單組稱為表單集(formsets)。表單集管理多個確定的FormModelForm例項。所有表單會一次性提交,表單集會負責處理一些事情,比如顯示的初始表單數量,限制最大的提交表單數量,以及驗證所有表單。

表單集包括一個is_valide()方法,可以一次驗證所有表單。你還可以為表單提供初始資料,並指定顯示多少額外的空表單。

你可以在這裡進一步學習表單集,以及在這裡學習模型表單集。

10.5.6.1 管理課程單元

因為一個課程分為多個單元,所以這裡可以使用表單集。在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表單集。我們用Django提供的inlineformset_factory()函式構建它。內聯表單集(inline formsets)是表單集之上的一個小抽象,可以簡化關聯物件的使用。這個函式允許我們動態構建一個模型表單集,把Module物件關聯到一個Course物件。

我們使用以下引數構建表單集:

  • fields:在表單集的每個表單中包括的欄位。
  • extra:允許我們在表單集中設定兩個額外的空表單。
  • 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().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檢視處理表單集來新增,更新和刪除指定課程的單元。這個檢視從以下mixins和檢視繼承:

  • TemplateResponseMixin:這個mixin負責渲染模板,並返回一個HTTP響應。它需要一個template_name屬性,指定被渲染的模板,並提供render_to_response()方法,傳入上下文引數,並渲染模板。
  • View:Django提供的基礎的基於類的檢視。

在這個檢視中,我們實現了以下方法:

  • get_formset():我們定義這個方法,避免構建表單集的重複程式碼。我們用可選的data為給定的Course物件建立ModuleFormSet物件。
  • dispatch():這個方法由View類提供。它接收一個HTTP請求作為引數,並嘗試委託到與使用的HTTP方法匹配的小寫方法:GET請求委託到get()方法,POST請求委託到post()方法。在這個方法中,我們用get_object_or_404()函式獲得屬於當前使用者,並且ID等於id引數的Course物件。因為GET和POST請求都需要檢索課程,所以我們在dispatch()方法中包括這段程式碼。我們把它儲存在檢視的course屬性,讓其它方法也可以訪問。
  • get():GET請求時執行的方法。我們構建一個空的ModuleFormSet表單集,並使用TemplateResponseMixin提供的render_to_response()方法,把當前Course物件和表單集渲染到模板中。
  • post():POST請求時執行的方法。在這個方法中,我們執行以下操作:
    1. 我們用提交的資料構建一個ModuleFormSet例項。
    2. 我們執行表單集的is_valid()方法,驗證表單集的所有表單。
    3. 如果表單集有效,則呼叫save()方法儲存它。此時,新增,更新或者標記刪除的單元等任何修改都會應用到資料庫中。然後我們重定向使用者到manage_course_list URL。如果表單集無效,則渲染顯示錯誤的模板。

編輯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 title %}{% 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 content %}

在這個模板中,我們建立了一個<form>元素,其中包括我們的表單集。我們還用{{ formset.management_form }}變數為表單集包括了管理表單。管理表單儲存隱藏的欄位,用於控制表單的初始數量,總數量,最小數量和最大數量。正如你所看到的,建立表單集很簡單。

編輯courses/manage/course/list.html模板,在課程編輯和刪除連結下面,為course_module_update URL新增以下連結:

<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連結,你會看到如圖所示的表單集:

表單集中包括課程中每個Module物件的表單。在這些表單之後,顯示了兩個額外的空表單,這是因為我們為ModuleFormSet設定了extra=2。當你儲存表單集時,Django會包括另外兩個額外欄位來新增新單元。

10.5.7 新增內容到課程單元

現在我們需要一種新增內容到課程單元的方式。我們有四種不同型別的內容:文字,視訊,圖片和檔案。我們可以考慮建立四個不同的檢視,來為每種模型建立內容。但是我們會用更通用的方法:建立一個可以處理建立或更新任何內容模型物件的檢視。

編輯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().dispatch(request, module_id, model_name, id)

這是ContentCreateUpdateView的第一部分。它允許我們建立和更新不同模型的內容。這個檢視定義了以下方法:

  • get_model():在這裡,我們檢查給定的模型名稱是否為四種內容模型之一:文字,視訊,圖片或檔案。然後我們用Django的apps.get_model()獲得給定模型名的實際類。如果給定的模型名不是四種之一,則返回None
  • get_form():我們用表單框架的modelform_factory()函式動態構建表單。因為我們要為TextVideoImageFile模型構建表單,所以我們使用exclude引數指定要從表單中排出的欄位,而讓剩下的所有欄位自動包括在表單中。這樣我們不用根據模型來包括欄位。
  • dispatch():它接收以下URL引數,並用類屬性儲存相應的單元,模型和內容物件:
    • module_id:內容會關聯的單元的ID。
    • model_name:內容建立或更新的模型名。
    • id:被更新的物件的ID。建立新物件時為None。

ContentCreateUpdateView類中新增以下get()post()方法:

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,
                item=obj
            )
        return redirect('module_content_list', self.module.id)
    return self.render_to_response({
        'form': form,
        'object': self.obj
    })

這些方法分別是:

  • get():收到GET請求時執行。我們為被更新的TextVideoImage或者File例項構建模型表單。否則我們不會傳遞例項來建立新物件,因為如果沒有提供id,則self.obj為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_idmodel_name引數。第一個引數允許我們把新內容物件連結到給定的單元。第二個引數指定了構建表單的內容模型。
  • module_content_update:用於更新已存在的文字,檢視,圖片或者檔案物件。它包括module_idmodel_name引數,以及被更新的內容的id引數。

courses/manage/