全文鏈接
第一章 創建一個blog應用
第二章 使用高級特性來增強你的blog
第三章 擴展你的blog應用
第四章上 創建一個社交網站
第四章下 創建一個社交網站
第五章 在你的網站中分享內容
第六章 跟蹤用戶動作
第七章 建立一個在線商店
第八章 管理付款和訂單
第九章上 擴展你的商店
第九章下 擴展你的商店
第十章上 創建一個在線學習平臺
第十章下 創建一個在線學習平臺
第十一章 緩存內容
第十二章 構建一個API
書籍出處:https://www.packtpub.com/web-development/django-example
原作者:Antonio Melé
(譯者注:翻譯本章過程中幾次想放棄,但是既然都到第十章了,怎么能放棄!)
第十章
創建一個在線學習平臺(e-Learning Platform)
在上一章,你添加國際化到你的在線商店項目中。你還構建了一個優惠券系統和一個商品推薦引擎。在這章中,你會創建一個新項目。你將構建一個在線學習平臺創建一個定制內容管理系統。
在這章中,你會學習以下操作:
- 創建fixtures給你的模型
- 使用模型繼承
- 創建定制模型字段
- 使用基于類的視圖和mixins
- 構建formsets
- 管理組合權限
- 創建一個內容管理系統
創建一個在線學習平臺
我們最實際的項目將會是一個在線學習平臺。在本章中,我們將要構建一個靈活的內容管理系統(CMS)用來允許教師來創建課程和管理它們的內容。
首先,創建一個虛擬環境給你的新項目并且激活它通過以下命令:
mkdir env
virtualenv env/educa
source env/educa/bin/activate
安裝Django到你的虛擬環境中通過以下命令:
pip install Django==1.8.6
我們將要管理圖片上傳在我們的項目中,所以我們還需要安裝Pillow通過以下命令:
pip install Pillow==2.9.0
創建一個新項目使用以下命令:
django-admin startproject educa
進入這個新的educa目錄并且創建一個新應用使用以下命令:
cd educa
django-admin startapp courses
編輯educa項目的settings.py文件并且添加courses到INSTALLED_APPS
設置中如下所示:
INSTALLED_APPS = (
'courses',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
courses應用現在已經在這個項目中激活。讓我們定義模型給課程以及課程內容。
構建課程模型
我們的在線學習平臺將會提供課程在許多主題中。每一個課程都將會劃分為一個可配置的模塊編號,并且每個模塊將會包含一個可配置的內容編號。將會有許多類型的內容:文本,文件,圖片,或者視頻。下面的例子展示了我們的課程目錄的數據結構:
Subject 1
Course 1
Module 1
Content 1 (images)
Content 3 (text)
Module 2
Content 4 (text)
Content 5 (file)
Content 6 (video)
...
讓我們來構建課程模型。編輯courses應用的models.py文件并且添加如下代碼:
from django.db import models
from django.contrib.auth.models import User
class Subject(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
class Meta:
ordering = ('title',)
def __str__(self):
return self.title
class Course(models.Model):
owner = models.ForeignKey(User,
related_name='courses_created')
subject = models.ForeignKey(Subject,
related_name='courses')
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
overview = models.TextField()
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-created',)
def __str__(self):
return self.title
class Module(models.Model):
course = models.ForeignKey(Course, related_name='modules')
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
def __str__(self):
return self.title
這些是最初的Subject,Course,以及Module模型。Course模型字段如下所示:
- owner:創建這個課程的教師。
- subject:這個課程屬于的主題。這是一個ForeingnKey字段指向Subject模型。
- title:課程標題。
- slug:課程的slug。之后它將會被用在URLs中。
- overview:這是一個TextFied列用來包含一個關于課程的概述。
- created:課程被創建的日期和時間。它將會被Django自動設置當創建一個新的對象,因為
auto_now_add=True
。
每一個課程都被劃分為多個模塊。因此,Module模型包含一個ForeignKey字段用來指向Course模型。
打開shell并且運行一下命令來給這個應用創建最初的遷移:
python manange.py makemigrations
你將會看到以下輸出:
Migrations for 'courses':
0001_initial.py:
- Create model Course
- Create model Module
- Create model Subject
- Add field subject to course
之后,運行一下命令來應用所有的遷移到數據庫中:
python manage.py migrate
你將會看到一個輸出包含所有應用的遷移,包括Django的那些。這個輸出將會包含以下行:
Applying courses.0001_initial... OK
這告訴我們那個我們的courses引用模型已經同步到了數據庫中。
注冊模型到管理平臺中
我們將要添加課程模型到管理平臺中。編輯courses應用目錄下的admin.py文件并且添加以下代碼:
from django.contrib import admin
from .models import Subject, Course, Module
@admin.register(Subject)
class SubjectAdmin(admin.ModelAdmin):
list_display = ['title', 'slug']
prepopulated_fields = {'slug': ('title',)}
class ModuleInline(admin.StackedInline):
model = Module
@admin.register(Course)
class CourseAdmin(admin.ModelAdmin):
list_display = ['title', 'subject', 'created']
list_filter = ['created', 'subject']
search_fields = ['title', 'overview']
prepopulated_fields = {'slug': ('title',)}
inlines = [ModuleInline]
課程應用的模型現在已經在管理平臺中注冊。我們使用@admin.register()
裝飾器替代了admin.site.register()
方法。它們都提供了相同的功能。
提供最初數據給模型
有時候你可能想要預裝你的數據庫通過使用硬編碼數據。這是很有用的,當自動包含最初數據在項目設置中用來替代手工去添加數據。Django自帶一個簡單的方法來加載以及轉儲數據庫中的數據到字段中,這被稱為fixtures。
Django支持fixtures在JSON,XML,或者YAML格式中。我們將要創建一個fixture用來包含一些最初的Subject對象給我們的項目。
首先,創建一個超級用戶使用如下命令:
python manage.py createsuperuser
之后,運行開發服務器使用以下命令:
python manage.py runserver
現在,打開 http://127.0.0.1:8000/admin/courses/subject/ 在你的瀏覽器中。創建一些主題通過使用管理平臺。列頁面看上去如下所示:
運行一下命令在shell中:
python manage.py dumpdata courese --indent=2
你會看到類似以下的輸出:
[
{
"fileld:": {
"title": "Programming",
"slug": "programming"
},
"model": "courses.subject",
"pk": 1
},
{
"fields": {
"title": "Mathematics",
"slug": "mathematics"
},
"model": "courses.subject",
"pk": 2
},
{
"fields": {
"title": "Physics",
"slug": "physics"
},
"model": "courses.subject",
"pk": 3
}, {
"fields": {
"title": "Music",
"slug": "music"
},
"model": "courses.subject",
"pk": 4
}
]
dumpdata命令從數據庫中轉儲數據到標準輸出中,默認序列化為JSON。這串數據結構包含的信息關于這個模型以及它的字段將會被Django用來加載它到數據庫中。
你可以提供應用名給這命令或者指定模型給輸出數據使用app.Model
格式。你還可以指定格式通過使用--format
標記。默認的,dumpdata輸出序列化數據給標準輸出。當然,你可以表明一個輸出文件通過使用--output
標記。--indent
標記允許你指定縮進。更多信息關于udmpdata參數,運行python manage.py dumpdata --help
。
保存這個轉儲為一個fixtures文件到orders應用的fixtures/目錄中,通過使用如下命令:
mkdir courses/fixtures
python manage.py dumpdata courses --indent=2 --output=courses/fixtures/
subjects.json
使用管理平臺去移除你之前創建的主題。之后加載fixture到數據庫中通過使用以下命令:
python manage.py loaddata subjects.json
所有包含在fixture中的subject
對象都會加載到數據庫中。
默認的,Django會尋找每一個應用的fixtures/目錄下的文件,但是你可以指定fixture文件的完整路徑給loaddata命令。你還可以使用FIXTURE_DIRS
設置來告訴Django去額外的目錄尋找fixtures。
Fixtures并不只對初始化數據有用,還可以提供簡單的數據給你的應用或者數據請求給你的測試用例。
你可以找到更多關于如何使用fixtures在測試中,通過訪問 https://docs.djangoproject.com/en/1.8/topics/testing/tools/#topics-testing-fixtures 。
如果你想要加載fixturres在模型遷移中,去看下Django的文檔關于數據遷移。請記住,我們創建了一個定制遷移在第九章,擴展你的商店來遷移存在的數據在修改給翻譯的模型之后。你可以找到遷移數據的文檔,通過訪問 https://docs.djangoproject.com/en/1.8/topics/migrations/#data-migrations 。
給不同的內容創建模型
我們打算添加各種不同的內容類型給課程模塊,例如文本,圖片,文件以及視屏。我們需要一個通用的數據模型可以允許我們去存儲不同的內容。在第六章,跟蹤用戶行為中,你已經學習過有關使用通用關系方便的創建外鍵能夠指向任何模型的對象。我們將要創建一個content模型相當于模塊內容以及定義一個通用關系來連接任意種類的內容。
編輯courses應用下的models.py文件并且添加如下導入:
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
之后添加如下代碼到文件后面:
class Content(models.Model):
module = models.ForeignKey(Module, related_name='contents')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
item = GenericForeignKey('content_type', 'object_id')
這就是一個Content模型。一個模塊包含多種內容,所有我們定義了一個ForeignKey
字段給module模型。我們還設置了一個通用關系來連接對象從不同的模型中相當于不同的內容類型。請記住,我們需要三種不同的字段來設置一個通用關系。在我們的Content模型中,它們是:
- content_type:一個ForeignKey字段指向ContentType模型
- object_id:這是PositiveIntegerField用來存儲有關聯對象的關鍵字
- item:一個GenericForeignKey字段指向被關聯的對象通過結合前兩個字段
只有content_type
和object_id
字段有一個對應列在這個模型的數據庫表中。item
字段允許你去檢索或者直接設置關聯對象,并且它的功能是簡歷在其他兩個字段之上。
我們將要使用一個不同的模型給每一種內容。我們的內容模型將會有很多共有的字段,但是它們將會有不同之處在它們存儲的真實內容中。
使用模型繼承
Django支持模型繼承。類似與Python中的標準類繼承。Django提供以下三種方式來使用模型繼承:
- Abstract models:非常有用當你想要安置一些公用信息到多個模型中。沒有數據庫表會被創建給抽象模型。
- Multi-table model inheritance:可適當的利用當每個模型經過慎重考慮都是一個它自身的完整的模型。每個模型都會創建一個數據庫表。
- Proxy models:非常有用當你需要改變一個模型行為,比如說,包含額外的方法,修改默認管理器,或者使用不同的元選項。沒有數據表會被創建給代理模型。
讓我們對以上三者都來一次近距離的實踐。
抽象模型
一個抽象模型就是一個基礎類,你定義在其中的字段就是你想要包含到所有子模型中的字段。Djnago不會創建任何數據庫表給抽象模型。每個子模型都會創建一張數據庫表,包含有繼承自抽象類的字段以及在子模型中自己定義的字段。
為了抽象一個模型,你需要在Meta
類中包含abstract=True
。Django將會認出這個模型是一個抽象模型并且不會給它創建數據庫表。為了創建子模型,你只需要基于這個抽象模型。下面就是一個例子關于一個抽象的Content模型和一個子的Text
模型:
from django.db import models
class BaseContent(models.Model):
title = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class Text(BaseContent):
body = models.TextField()
在這個例子中,Django將只會給Text
模型創建表,包含title
,created
以及body
字段。
多表模型繼承
在多表模型繼承中,每個模型對應一個張數據庫表。Django創建一個OneToOneField字段給子模型創建關系指向它的父模型。
為了使用多表繼承,你必須基于一個存在的模型。djnago將會創建一張數據表給每個源頭模型以及子模型。以下例子展示多表繼承:
from django.db import models
class BaseContent(models.Model):
title = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
class Text(BaseContent):
body = models.TextField()
Django將會包含一個自動生成的OneToOneField字段在Text
模型中并且給每個模型創建一張數據庫表。
代理模型
代理模型被用于改變一個模型的行為,舉個例子,包含額外的方法或者不同的元選項。每個模型對源頭模型的數據庫表起作用。為了創建一個代理模型,在這個模型的Meta
類中添加proxy=True
。
以下例子說明如何創建一個代理模型:
from django.db import models
from django.utils import timezone
class BaseContent(models.Model):
title = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
class OrderedContent(BaseContent):
class Meta:
proxy = True
ordering = ['created']
def created_delta(self):
return timezone.now() - self.created
這里,我們定義了一個OrderedContent模型這是一個代理模型給Content模型使用。這個模型提供了一個默認的排序給查詢集并且一個額外的create_delta()
方法。這兩個模型,Content
和OrderedContent
,對同一個數據庫表起作用,并且通過任一一個模型都能通過ORM渠道連接到對象。
創建內容模型
我們的courses應用的Content模型包含一個通用關系來連接不同類型的內容給該應用。我們將要創建一個不同的模型給每種類型的內容。所有內容模型將會有一些公用的字段,以及額外的字段去存儲定制數據。我們將會創建一個抽象模型來提供公用字段給所有內容模型。
編輯courses應用的models.py文件,并且添加以下代碼:
class ItemBase(models.Model):
owner = models.ForeignKey(User,
related_name='%(class)s_related')
title = models.CharField(max_length=250)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
def __str__(self):
return self.title
class Text(ItemBase):
content = models.TextField()
class File(ItemBase):
file = models.FileField(upload_to='files')
class Image(ItemBase):
file = models.FileField(upload_to='images')
class Video(ItemBase):
url = models.URLField()
在這串代碼中,我們定義了一個抽象模型命名為ItemBase
。除此以外,我們在Meta
類中設置abstract=True
。在這個模型中,我們定義owner,title,created
,以及updated
字段。這些公用字段將會被所有的內容類型使用到。owner
字段允許我們去存儲哪個用戶創建了這個內容。因為和這個字段是被定義在一個抽象類中,我們需要不同的related_name
給每個子模型。Django允許我們去指定一個占位符給model
類名在related_name
屬性類似%(class)s
。為了做到這些,related_name
對每個子模型都會自動生成。因為我們使用%(class)s_related
作為related_name
,給子模型的相對關系將各自是text_related,file_related,image_related,
以及vide0_related
。
我們已經定義了四種不同的內容模型,它們都繼承自ItemBase
抽象模型,它們是:
- Text:用來存儲文本內容。
- File:用來存儲文件,例如PDF。
- Image:用來存儲圖片文件。
- Video:用來存儲視頻。我們使用一個
URLField
字段來提供一個視頻URL為了嵌入該視頻。
每個子模型包含定義在ItemBase
類中的字段以及它自己的字段。text_related,file_related,image_related,
以及vide0_related
都會各自創建一張數據庫表。不會有數據庫表連接到ItemBase
模型,因為它是一個抽象模型。
編輯你之前創建的Content模型,修改它的content_type
字段如下所示:
content_type = models.ForeignKey(ContentType,
limit_choices_to={'model__in':('text',
'video',
'image',
'file')})
我們添加一個limit_choices_to
參數來限制ContentType
對象可以被通用關系使用。我們使用model__in
字段查找過濾這個查詢給ContentType
對象通過一個model
屬性就像'text','video','image',或者'file'。
讓我們創建一個遷移來包含這些新的模型我們之前添加的。運行以下命令:
python manage.py makemigrations
你會看到以下輸出:
Migrations for 'courses':
0002_content_file_image_text_video.py:
- Create model Content
- Create model File
- Create model Image
- Create model Text
- Create model Video
之后,運行一下命令來應用新的遷移:
python manage.py migrate
你會在輸出結果看到以下內容:
Running migrations:
Rendering model states... DONE
Applying courses.0002_content_file_image_text_video... OK
我們之前創建的模型對于添加不同的內容給課程模塊是很合適的。但是,仍然有一些東西是被遺漏的在我們的模型中。課程模塊和內容應當跟隨一個特定的順序。我們需要一個字段,這個字段允許我們簡單的排序它們。
創建定制模型字段
Django自帶一個完整的模型字段采集能讓你用來構建你的模型。當然,你也可以創建你自己的模型字段來存儲定制數據或者改變現有字段的行為。
我們需要一個字段允許我們給對象們定義次序。如果你想通過Djanog提供的一個字段來方便的處理這點,你大概會想到添加一個PositiveIntegerField
給你的模型。這是一個好的起點。我們可以創建一個定制字段,該字段繼承自PositiveIntegerField
并且提供額外的行為。
有兩種相關的功能我們將構建到我們的次序字段中:
- 自動分配一個次序值當沒有指定的次序被提供的時候。當沒有次數被提供的時候存儲一個對象,我們的字段將自動分配下一個次序,該次序基于最后存在次序的對象。如果有兩個對象,分別是次序1和次序2,當保存第三個對象的時候,我們會自動分配次序3給第三個對象如果沒有給予指定的次序。
- 次序對象關于其他的字段。課程模塊將按照它們所屬的課程和相關模塊的內容進行排序。
創建一個新的fields.py文件到courses應用目錄下,然后添加以下代碼:
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
class OrderField(models.PositiveIntegerField):
def __init__(self, for_fields=None, *args, **kwargs):
self.for_fields = for_fields
super(OrderField, self).__init__(*args, **kwargs)
def pre_save(self, model_instance, add):
if getattr(model_instance, self.attname) is None:
# no current value
try:
qs = self.model.objects.all()
if self.for_fields:
# filter by objects with the same field values
# for the fields in "for_fields"
query = {field: getattr(model_instance, field) for field in self.for_fields}
qs = qs.filter(**query)
# get the order of the last item
last_item = qs.latest(self.attname)
value = last_item.order + 1
except ObjectDoesNotExist:
value = 0
setattr(model_instance, self.attname, value)
return value
else:
return super(OrderField,
self).pre_save(model_instance, add)
這就是我們的定制OrderField
.它繼承自Django提供的PositiveIntegerField
字段。我們的OrderField
字段需要一個可選的for_fields
參數,這個參數允許我們表明次序根據這些字段進行計算。
我們的字段覆蓋PositiveIntegerField
字段的pre_save()
方法,這字段會在保存這個字段到數據庫之前進行執行。在這個方法中,我們做了以下操作:
-
1 我們檢查在模型實例中的字段是否已有一個值。我們是
self.attname
,它是在這個模型中給予這個字段的屬性名。如果在這個屬性的值不同于None
,我們就會進行如下操作來計算出一個次序給它:- 1 我們構建一個查詢集去檢索所有對象給這個字段的模型。我們通過訪問
self.model
來檢索該字段所屬的模型類。 - 2 我們通過模型字段中的那些被定義在
for_fields
參數中的字段的當前值來過濾這個查詢集(如果有的話)。為了做到這點,我們通過給予的字段來計算次序。 - 3 我們從數據庫中使用最高的次序來檢索對象通過是用
last_item = qs.latest(self.attname)
。如果沒有找到對象,我們假定這個對象是第一個并且分配次序0給它。 - 4 如果找到一個對象,我們給找到的最高次序增加1。
- 5 我們分配計算過的次序給在模型實例中的字段的值通過使用
setattr()
并且返回它。
- 1 我們構建一個查詢集去檢索所有對象給這個字段的模型。我們通過訪問
2 如果這個模型實例有一個值給當前的字段,我們不需要做任何事情。
當你創建定制模型字段,使它們通過。避免硬編碼數據被依賴一個指定模型或者字段。你的字段才能在任意模型中起效。
你可以找到更多的信息關于編寫定制模型字段,通過訪問 https://docs.djangoproject.com/en/1.8/howto/custom-model-fields/ 。
讓我們添加新的字段給我們的模型。編輯courses應用的models.py文件,導入新的字段如下所示:
from .fields import OrderField
之后,添加以下OrderField
字段給Module
模型:
order = OrderField(blank=True, for_fields=['course'])
我們命名新的字段為order
,并且我們指定該字段的次序根據課程計算通過設置for_fields=['course']
。這意味著新的模塊的次序將會是最后的同樣的Course對象模塊的次序增加1。現在你可以編輯Module
模型的__str__()
方法來包含它的次序如下所示:
def __str__(self):
return '{}. {}'.format(self.order, self.title)
模塊內容也需要跟隨一個特定的次序。添加一個OrderField
字段給Content
模型如下所示:
order = OrderField(blank=True, for_fields=['module'])
這一次,我們指定這個次序根據moduel
字段進行計算。最后,讓我們給這兩個模型都添加一個默認的序列。添加如下Meta
類給Module
和Content
模型:
class Meta:
ordering = ['order']
Module
和Content
模型現在看上去如下所示:
class Module(models.Model):
course = models.ForeignKey(Course,related_name='modules')
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
order = OrderField(blank=True, for_fields=['course'])
class Meta:
ordering = ['order']
def __str__(self):
return '{}. {}'.format(self.order, self.title)
class Content(models.Model):
module = models.ForeignKey(Module, related_name='contents')
content_type = models.ForeignKey(ContentType,
limit_choices_to={'model__in':('text',
'video',
'file')})
item = GenericForeignKey('content_type', 'object_id')
order = OrderField(blank=True, for_fields=['module'])
class Meta:
ordering = ['order']
讓我們創建一個新模型遷移來體現新的次序字段。打開shell并且運行如下命令:
python manage.py makemigrations courses
你會看到如下輸出:
You are trying to add a non-nullable field 'order' to content without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows)
2) Quit, and let me add a default in models.py
Select an option:
Django正在告訴我們由于我們添加了一個新的字段給已經存在的模型,我們必須提供一個默認值給數據庫中已經存在的各行記錄。如果這個字段有null=True
,它將會采用空值并且Django將會創建這個遷移而不會找我們要一個默認值。我們可以指定一個默認值或者取消這次遷移然后在創建這個遷移之前去models.py
文件中給order
字段添加一個default
屬性。
輸入 1 然后按下回車來提供一個默認值給已經存在的記錄。你將會看到如下輸出:
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
>>>
輸入 0 作為給已經存在的記錄的默認值然后按下回車。Djanog將會詢問你還需要一個默認值給Module
模型。選擇第一個選項然后再次輸入 0 作為默認值。最后,你將會看到如下類似的輸入:
Migrations for 'courses':
0003_auto_20150701_1851.py:
- Change Meta options on content
- Change Meta options on module
- Add field order to content
- Add field order to module
之后,應用新的遷移通過以下命令:
python manage.py migrate
這個命令的輸出將會通知你這次遷移成功的應用,如下所示:
Applying courses.0003_auto_20150701_1851... OK
讓我們測試我們新的字段。打開shell使用python manage.py shell
然后創建一個新的課程如下所示:
>>> from django.contrib.auth.models import User
>>> from courses.models import Subject, Course, Module
>>> user = User.objects.latest('id')
>>> subject = Subject.objects.latest('id')
>>> c1 = Course.objects.create(subject=subject, owner=user,
title='Course 1', slug='course1')
我們已經在數據庫中創建了一個課程。現在讓我們給課程添加模塊然后看下模塊的次序是如何自動計算的。我們創建一個初始模板然后檢查它的次序:
>>> m1 = Module.objects.create(course=c1, title='Module 1')
>>> m1.order
0
OrderField
設置這個模塊的值為 0,因為這個模塊是這個課程的第一個Module
對象。現在我們創建第二個對象給這個課程:
>>> m2 = Module.objects.create(course=c1, title='Module 2')
>>> m2.order
1
OrderField
計算出下一個次序值是已經存在的對象中最高的次序值加上 1。讓我們創建第三個模塊強制指定一個次序:
>>> m3 = Module.objects.create(course=c1, title='Module 3', order=5)
>>> m3.order
5
如果我們指定了一個定制次序,OrderField
字段將不會進行干涉,然后order
的值將會使用指定的次序。
讓我們添加第四個模塊:
>>> m4 = Module.objects.create(course=c1, title='Module 4')
>>> m4.order
6
這第四個模塊的次序會被自動設置。我們的OrderField
字段不會保證所有的次序值是連續的。無論如何,它會根據已經存在的次序值并且分配下一個次序基于已經存在的最高次序。
讓我們創建第二個課程并且添加一個模塊給它:
>>> c2 = Course.objects.create(subject=subject, title='Course 2', slug='course2', owner=user)
>>> m5 = Module.objects.create(course=c2, title='Module 1')
>>> m5.order
0
為了計算這個新模塊的次序,該字段只需要考慮基于同一課程的已經存在的模塊。由于這是第二個課程的第一個模塊,次序的結果值就是 0 。這是因為我們指定for_fields=['course']
在Module
模型的order
字段中。
恭喜你!你已經成功的創建了你的第一個定制模型字段。
創建一個內容管理系統
到現在我們已經創建了一個多功能數據模型,我們將要構建一個內容管理系統(CMS)。這個CMS將允許教師去創建課程以及管理課程的內容。我們需要提供以下功能:
- 登錄CMS。
- 排列教師創建的課程。
- 創建,編輯以及刪除課程。
- 添加模塊到一個課程中并且重新排序它們。
- 添加不同類型的內容給每個模塊并且重新排序內容。
添加認證系統
我們將要使用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/', include(admin.site.urls)),
]
創建認證模板
在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>
<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 %}
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
$(document).ready(function() {
{% block domready %}
{% endblock %}
});
</script>
</body>
</html>
這個基礎模板將會被其他的模板擴展。在這個模板中,我們定義了以下區塊:
- title:這個區塊是給別的模板用來給每個頁面添加定制的標題。
- content:這個是內容的主區塊。所有擴展基礎模板的模板都可以添加各自的內容到這個區塊。
- domready:位于jQuery的
$document.ready()
方法里面。它允許我們執行代碼當DOM完成加載的時候。
這個模板使用的CSS樣式位于本章實例代碼的courses應用下的static/目錄下。你可以拷貝static/目錄到你的項目的相同目錄下來使用它們。
編輯registration/login.html模板并且添加以下代碼:
{% extends "base.html" %}
{% block title %}Log-in{% endblock %}
{% 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, use 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 %}
這是一個給Django的login
視圖用的標準登錄模板。編輯registration/logged_out.html模板然后添加以下代碼:
{% extends "base.html" %}
{% block title %}Logged out{% endblock %}
{% 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 %}
這個模板將會在用戶登出后展示。通過命令python manage.py runserver
命令運行開發服務器然后在你的瀏覽器中打開 http://127.0.0.1:8000/accounts/login/ 。你會看到如下登錄頁面:
創建基于類的視圖
我們將要構建一些視圖用來創建,編輯,以及刪除課程。為了這個目的我們將會使用基于類的視圖。編輯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(ManageCourseListView, self).get_queryset()
return qs.filter(owner=self.request.user)
以上就是ManageCourseListView
視圖。它從Django的通用ListView
繼承而來。我們重寫了這個視圖的get_queryset()
方法來只對當前用戶創建的課程進行檢索。為了阻止用戶對不是由他們創建的課程進行編輯,更新或者刪除操作,我們還需要重寫在創建,更新以及刪除視圖中的get_queryse()
方法。當你需要去提供一個指定行為給多個基于類的視圖,推薦你使用mixins
。
對基于類的視圖使用mixins
mixins是一種特殊的用于一個類的多重繼承。你可以使用它們來提供常見的離散功能,添加到其他的mixins,允許你去定義一個類的行為。有兩種場景要使用mixins:
- 你想要提供多個可選的特性給一個類
- 你想要使用一個特定的特性在多個類上
你可以找到關于如何在基于類的視圖上使用mixins的文檔,通過訪問 https://docs.djangoproject.com/en/1.8/topics/class-based-views/mixins/ 。
Django自帶多個mixins用來提供額外的功能給你的基于類的視圖。你可以找到所有的mixins在 https://docs.djangoproject.com/en/1.8/ref/class-based-views/mixins/ 。
我們將要創建一個mixin類來包含一個公用的行為并且將它給課程的視圖使用。編輯courses應用的views.py文件,把它修改成如下所示:
from django.core.urlresolvers import reverse_lazy
from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, UpdateView, \
DeleteView
from .models import Course
class OwnerMixin(object):
def get_queryset(self):
qs = super(OwnerMixin, self).get_queryset()
return qs.filter(owner=self.request.user)
class OwnerEditMixin(object):
def form_valid(self, form):
form.instance.owner = self.request.user
return super(OwnerEditMixin, self).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')
在上述代碼中,我們創建了OwnerMixin
和OwnerEditMixin
這兩個mixin。我們將要使用這些mixins與Django提供的ListView
,CreateView
,UpdateView
以及DeleteView
視圖結合。Ownermixin
導入了以下方法。
-
get_queryset()
:這個方法被視圖用來獲取基礎查詢集。我們的mixin將會重寫這個方法使用owner
屬性對對象進行過濾來檢索屬于當前用戶的對象(request.user)。
OwnerEditMixin
導入了以下方法:
-
form_valid()
:這個方法被視圖用來使用Django的ModelFormMixin
mixin,也就是說,帶有表單的視圖或模型表單的視圖比如Createview
和UpdateView.form_valid()
當提交的表單是有效的時候就會被執行。這個方法默認的行為是保存實例(對于模型表單)以及重定向用戶到success_url
。我們重寫了這個方法來自動設置當前的用戶到本次會被保存的對象的owner
屬性中。為了做到前面所說的,我們設置自動分配一個擁有者給該對象,當該對象被保存的時候。
我們的OwnerMixin
類能夠被視圖用來和任意模型進行交互使模型包含一個owner
屬性。
我們還定義了一個OwnercourseMixin
類,該類繼承OwnerMixin
并且提供以下屬性給子視圖:
-
model
:這個模型給查詢集使用。被所有視圖使用。
我們定義一個OwnerCourseEditMixin
mixin通過以下屬性:
-
fields
:這些模型字段用來從CreateView
和UpdateView
視圖中構建模型。 -
success_url
:被CreateView
和UpdateView
使用來在表單成功提交之后重定向用戶。我們之后將會創建一個名為manage_course_list
的URL來使用。
最后,我們創建以下視圖,這些視圖都是基于OwnerCourseMixin
的子類:
-
MangeCourselISTvIEW
:排序用戶創建的課程。它從OwnerCourseMixin
和ListView
繼承而來。 -
CoursecreateView
:使用模型表單來創建一個新的Course
對象。它使用定義在OwnerCourseEditMixin
中的字段來構建一個表單模型并且也是CreateView
的子類。 -
CourseUpdateView
:允許編輯一個現有的Course
對象。它從OwnerCourseMixin
和UpdateView
繼承而來。 -
CourseDeleteView
:從OwnerCourseMixin
和通用的DeleteView
繼承而來。定義success_url
在對象被刪除的時候重定向用戶。
(譯者注:上半章結束)