第二章 数据库结构 *************** 本章节覆盖以下议题: * 使用模型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将一个外键改变为多対多字段 略。