第二章 數據庫結構
***************
本章節覆蓋以下議題:
* 使用模型mixin
* 使用相對URL方法創建一個模型mixin
* 創建一個模型mixin以處理日期的創建和修改
* 創建一個模型mixin以處理meta標簽
* 創建一個模型mixin以處理通用關系
* 處理多語言字段
* 使用South遷移 (譯者注:Django1.7中已經有了自己遷移模塊,故內容將略去)
* 使用South將一個外鍵改變為多対多字段
## 引言
當你新建新的app時,要做的第一件事就是創建表現數據庫結構的模型。我們假設你之前已經創建了Django的app,要是沒有話馬上創建一個,而且你也閱讀并了解Django的官方教程。本章,我會向你演示一些讓數據庫結構在項目的不同應用中保持一致的有趣的技術。然后我將向你演示創建模型字段以處理數據庫中的數據的國際化。本章的最后,我會向你演示在開發的過程中如何使用遷移來改變數據庫結構。
## 使用模型mixin
在Python這樣的面向對象語言中,mixin類可以被視為一個實現功能的接口。當一個模型擴展了一個mixin,它就實現了接口,并包括了mixin的所有字段,特性,和方法。當你想要在不同的模型中重復地使用通用功能時,可以使用Django模型的mixin。
## 預熱
要開始的話,你需要創建一些可重復使用的mixin。mixin的某些典型例子會在后面章節展示。一個保存模型mixin的好地方就是`utils`模塊。
>##### 提示
>如果你要創建一個與他人共享的重復使用app,那就要把模型mixin放在app里,比如放在應用的`base.py`文件中。
## 具體做法
在任何想要使用的mixin的Django應用中,創建`models.py`文件,并輸入下面的代碼:
```python
#demo_app/models.py
# -*- coding: UTF-8 -*-
from django.db import models
from django.utils.translation import ugettext_lazy as _
from utils.models import UrlMixin
from utils.models import CreationModificationMixin
from utils.models import MetaTagsMixin
class Idea(UrlMixin, CreationModificationMixin, MetaTagsMixin):
title = models.CharField(_("Title"), max_length=200)
content = models.TextField(_("Content"))
class Meta:
verbose_name = _("Idea")
verbose_name_plural = _("Ideas")
def __unicode__(self):
return self.title
```
## 工作原理
Django的模型繼承支持三種類型的繼承:抽象基類,多重繼承,以及代理模型。模型mixin是擁有特定字段,屬性,和方法的抽象模型類。當你創建前面的例子所示`Idea`這樣的模型時,它從`UrlMixin`
,`CreationModificationMixin`和`MetaTagsMixin`繼承了所有功能。所有的抽象類字段都作為所擴展模型的字段被保存在相同的數據庫表中。
## 還有更多呢
為了學習更多不同類型的模型繼承,參考Django官方文檔https://docs.djangoproject.com/en/dev/topics/db/models/#model-inheritance。
## 參見
* 使用相對URL方法創建一個模型mixin技巧
* 創建模型mixin以處理日期的創建和修改
* 創建模型mixin以處理meta標簽
## 使用相對URL方法創建一個模型mixin
每個模型都有自己的頁面,定義`get_absolute_url()`方法是很好的做法。這個方法可以用在模板中,它也可以用在Django admin站點中以預覽所保存的項目。然而,`get_absolute_url`很不明確,因為它實際上返回的是URL路徑而不是完整的URL。在這個做法,我會向你演示如何創建一個模型mixin,默認它允許你定義URL路徑或者完整的URL,以生成一個開箱即用的URL,并處理`get_absolute_url`方法的設置事宜。
## 預備!
如果你還沒有完成創保存mixin的`utils`包。然后,在`utils`包內(可選的是,如果創建了一個重復使用的應用,那么你需要把`base.py`放在應用中)創建`models.py`文件。
## 具體做法
按步驟地執行以下命令:
1. 在`utils`包的`models.py`文件中添加以下內容:
```python
#utils/models.py
# -*- coding: UTF-8 -*-
import urlparse
from django.db import models
from django.contrib.sites.models import Site
from django.conf import settings
class UrlMixin(models.Model):
"""
替換get_absolute_url()。模型擴展該mixin便可以執行get_url或者get_url_path。
"""
class Meta:
abstract = True
def get_url(self):
if hasattr(self.get_url_path, "dont_recurse"):
raise NotImplementedError
try:
path = self.get_url_path()
except NotImplementedError:
raise
website_url = getattr(settings, "DEFAULT_WEBSITE_URL", "http://127.0.0.1:8000")
return website_url + path
get_url.dont_recurse = True
def get_url_path(self):
if hasattr(self.get_url, "dont_recurse"):
raise NotImplementedError
try:
url = self.get_url()
except NotImplementedError:
raise
bits = urlparse.urlparse(url)
return urlparse.urlunparse(("", "") + bits[2:])
get_url_path.dont_recurse = True
def get_absolute_url(self):
return self.get_url_path()
```
2. 為了在應用中使用mixin,需要把它從`utils`包導入,然后在模型類中繼承mixin,并定義`get_absolute_url()`方法如下:
```python
# demo_app/models.py
# -*- coding: UTF-8 -*-
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from utils.models import UrlMixin
class Idea(UrlMixin):
title = models.CharField(_("Title"), max_length=200)
#...
def get_url_path(self):
return reverse("idea_details", kwargs={
"idea_id": str(self.pk)
})
```
3. 如果你在臨時或者生產環境中檢查該代碼,抑或你在本地運行不同的IP或者端口的服務器,那么你需要在本地設置的`DEFAULT_WEBSITE_URL`中設置如下內容:
```python
#settings.py
# ...
DEFAULT_WEBSITE_URL = "http://www.example.com"
```
## 工作原理
`UrlMixin`是一個擁有三種方法的抽象模型:`get_url()`, get_url_path()`,`get_absolute_url`。`get_url()`或者`get_url_path()`方法期待在所擴展的模型類中被重寫,比如,`Idea`類。你可定義`get_url`,它是一個到對象的完整URL,`get_url_path`會把它剝離到路徑。你也可以定義`get_url_path`,它是到對象的絕對路徑,然后`get_url`會添加網站的URL到路徑的開始。`get_absolute_url`方法會模仿`get_url_path`。
>## 提示
>通常的經驗總是重寫`get_url_path()`方法。
>當你在同一個網站中需要到一個對象的鏈接,可以在模板中,使用`<a href="{{ idea.get_url_path }}">{{ idea.title }}</a>`。對于電子郵件,RSS訂閱或者API可以使用,`<a href="{{ idea.get_url }}>"{{ idea.title }}</a>`。
## 參閱
```
使用模型mixin
創建處理數據生成和修改的模型mixin
創建模型mixin以處理元標簽
創建模型mixin處理通用關系
```
## 創建處理數據生成和修改的模型mixin
在模型中對于模型實例的創建和修改來說,一種常見的行為就是擁有時間戳。該方法中,我會向你演示如何給創建保存、修改模型的日期和時間。使用這樣的mixin,可以保證所有的模型對于時間戳都使用相同的字段,以及擁有同樣的行為。
## 準備開始
如果你還沒有完成創保存mixin的`utils`包。然后,在`utils`包內(可選的是,如果創建了一個重復使用的應用,那么你需要把`base.py`放在應用中)創建`models.py`文件。
## 如何做
打開`utils`包中的`models.py`文件,并輸入以下的代碼:
```python
#utils/models.py
# -*- coding: UTF-8 -*-
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.timezone import now as timezone_now
class CreationModificationDateMixin(models.Model):
"""
可以創建和修改日期和時間的抽象基類。
"""
created = models.DateTimeField(
_("creation date and time"),
editable=False,
)
modified = models.DateTimeField(
_("modification date and time"),
null=True,
editable=False,
)
def save(self, *args, **kwargs):
if not self.pk:
self.created = timezone_now()
else:
#為了保證我們一直擁有創建數據,添加下面的條件語句
if not self.created:
self.created = timezone_now()
self.modified = timezone_now()
super(CreationModificationDateMixin, self).save(*args, **kwargs)
save.alters_data = True
class Meta:
abstract = True
```
## 工作原理
`CreationModificationDateMixin`類是一個抽象模型,這意味著它所擴展的模型類會在同一個數據表中創建所有字段,即,沒有一對一關系讓表變得難以處理。該mixin擁有兩個日期-時間字段,以及一個在保存擴展模型會調用的`save()`方法。`save()`方法檢查模型是否擁有主鍵,這是一種新建但還未保存的實例的情況。否則,如果主鍵存在,修改的日期就會被設置為當前的日期和時間。
作為選擇,你可以不使用`save()`方法,而使用`auto_now_add`和`auto_now`屬性來`created`和`修改`字段以自動地創建和修改時間戳。
## 參閱
使用模型mixin
創建模型mixin以處理meta標簽
創建模型mixin以處理通用關系
## 創建模型mixin以處理meta標簽
如果你想要為搜索引擎而優化網站,那么你不僅需要個每個頁面都設置語義裝飾,而且也需要合適的元標簽。為了最大的靈活性,你需要有一種對在網站中有自己頁面的所有對象都定義指定的元標簽的方法。于此技法中,我們會向你演示如何對字段和方法創建一個mixin以關聯到元標簽。
## 準備開始嘍!
和前面的做法一樣,確保你為mixin備好了`utils`包。用你最喜歡的編輯器打開`models.py`文件。
## 具體做法
于`models.py`文件寫入以下內容:
```python
#utils/models.py
# -*- coding: UTF-8 -*-
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.template.defaultfilters import escape
from django.utils.safestring import mark_safe
class MetaTagsMixin(models.Model):
"""
用于<head>元素中由抽象基類所構成的元標簽
"""
meta_keywords = models.CharField(
_("Keywords"),
max_length=255,
blank=True,
help_text=_("Separate keywords by comma."),
)
meta_description = models.CharField(
_("Description"),
max_length=255,
blank=True,
)
meta_author = models.CharField(
_("Author"),
max_length=255,
blank=True,
)
meta_copyright = models.CharField(
_("Copyright"),
max_length=255,
blank=True,
)
class Meta:
abstract = True
def get_meta_keyword(self):
tag = u" "
if self.meta_keywords:
tag = u'<meta name="keywords" content="{}".format(escape(self.meta_author)) />
return mark_safe(tag)
def get_meta_description(self):
tag = u" "
if self.meta_description:
tag = u'<meta name="description" content="{1}".format(escape(self.meta_author)) />
return mark_safe(tag)
def get_meta_author(self):
tag = u" "
if self.get_meta_author:
tag = u'<meta name="author" content="{1}".format(escape(self.meta_author)) />'
def get_meta_copyright(self):
tag = u" "
if self.meta_copyright:
tag = u'<meta name="copyright" content="{}".format(escape(self.meta_copyright)) />'
return mark_safe(tag)
def get_meta_tags(self):
return mark_safe(u" ".join(
self.get_meta_keyword(),
self.get_meta_description(),
self.get_meta_author(),
self.get_meta_copyright(),
))
```
## 工作原理
該mixin添加了四個字段到模型擴展:`meta_keywords, meta_description, meta_author`和`meta_copyright`。在HTML中渲染元標簽的方法也添加了。
如果你在`Idea`這樣的模型中使用mixin,它出現在本章的第一個方法,那么你可以在目的是渲染所有元標簽的詳細頁面模板的`HEAD`部分中寫入以下內容:
```python
{{ ieda.get_meta_tags }}
```
你也可以利用下面的行來渲染一個特定的元標簽:
```python
{{ idea.get_meta_description }}
```
或許你也注意到了代碼片段,渲染的元標簽被標記為安全,即,它們沒有被轉義而且我們也不要使用`safe`模板過濾器。只有來自數據庫中的值才被轉義,以保證最終的HTML成型良好。
## 參見
使用模型mixin
創建一個模型mixin以處理日期的創建和修改
創建一個模型mixin以處理通用關系
## 創建模型mixin以處理通用關系
除了外鍵關系或者對對關系這樣的正常的數據庫關系之外,Django還提供了一種關聯一個模型到任意模型的實例。此概念稱為通用關系。每個通用關系都有一個關聯模型的內容類型,而且這個內容類型保存為該模型實例的ID。
該方法中,我們會向你演示如何將通用關系的創建歸納為模型mixin。
## 準備開始
要讓該方法正常運行,你需要安裝`contenttypes`應用。默認它應該位于`INSTALLED_APPS`目錄中:
```pyhton
INSTALLED_APPS = (
# ...
"django.contrib.contenttypes",
)
```
再者,要確保你已經創建了放置模型mixin的`utils`包。
## 工作原理
在文本編輯器中打開`utils`包中的`models.py`文件,并輸入以下的內容:
```python
“
#utils/models.py
# -*- coding: UTF-8 -*-
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.core.exceptions import FieldError
def object_relation_mixin_factory(
prefix=None,
prefix_verbose=None,
add_related_name=False,
limit_content_type_choices_to={},
limit_object_choices_to={},
is_required=False,
)
""" 返回一個使用含有動態字段名稱的“Content type - object Id”的mixin類的通用外鍵。
該函數只是一個類生成器。
參數:
prefix:前綴用來添加到字段的前面
prefix_verbose:前綴的冗余名稱,用來生成Admin中內容對象的字段列的title
add_related_name:表示一個布爾值,
"""
```
## 工作原理
## 參見
The Creating a model mixin with URL-related methods recipe
The Creating a model mixin to handle creation and modification dates recipe
The Creating a model mixin to take care of meta tags recipe
## 處理多語言字段
## 預熱
## 具體做法
## 工作原理
## 使用South遷移
略。Django1.7已經自帶遷移模塊。
## 使用South將一個外鍵改變為多対多字段
略。