From 5e1b1e707d407c9235850db510ceaf2d8c053c57 Mon Sep 17 00:00:00 2001 From: wizardforcel <562826179@qq.com> Date: Tue, 15 Sep 2015 18:03:01 +0800 Subject: [PATCH] 9.15 --- 2_1_1_Model syntax.md | 795 ++++++++++++++++++++----------- 5_1_2_Form API.md | 956 ++++++++++++++++++++++++++++++++++++++ 5_1_4_Built-in widgets.md | 639 +++++++++++++++++++++++++ 6_5_1_Introduction.md | 24 + 4 files changed, 2132 insertions(+), 282 deletions(-) create mode 100644 5_1_2_Form API.md create mode 100644 5_1_4_Built-in widgets.md create mode 100644 6_5_1_Introduction.md diff --git a/2_1_1_Model syntax.md b/2_1_1_Model syntax.md index 9d40690..52a4948 100644 --- a/2_1_1_Model syntax.md +++ b/2_1_1_Model syntax.md @@ -1,20 +1,16 @@ - +# 模型 -# 模型 # - -模型是有关你的数据的,简单、确定的信息源。它包含了你所储存数据的一些必要的字段和行为。通常来说,每个模型都对应数据库中的一张表。 +模型是你的数据的唯一的、权威的信息源。它包含你所储存数据的必要字段和行为。通常,每个模型对应数据库中唯一的一张表。 基础: -+ 每个模型都是django.db.models.Model类的子类。 -+ 模型的每个属性都表示数据库中的一个字段。 -+ Django 会提供一套自动生成的用于数据库访问的API;详见执行查询。 +* 每个模型都是[`django.db.models.Model`](../../ref/models/instances.html#django.db.models.Model "django.db.models.Model") 的一个Python 子类。 +* 模型的每个属性都表示数据库中的一个字段。 +* Django 提供一套自动生成的用于数据库访问的API;详见[_执行查询_](queries.html)。 -## 简短的例子 ## +## 简短的例子 -这个例子定义了一个Person模型,它有 first_name和last_name两个属性 +这个例子定义一个`Person`模型,它有`first_name` 和`last_name` 两个属性: ``` from django.db import models @@ -22,11 +18,12 @@ from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) + ``` -first_name和last_name是模型的两个字段。每个字段都被指定成一个类属性,每个属性 都映射一个数据库的列。 +`first_name`和`last_name`是模型的两个[字段](#fields)。每个字段都被指定成一个类属性,每个属性映射到一个数据库的列。 -上面的Person模型会在数据库中创建这样一张表: +上面的`Person` 模型会在数据库中创建这样一张表: ``` CREATE TABLE myapp_person ( @@ -34,37 +31,41 @@ CREATE TABLE myapp_person ( "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL ); + ``` -一些技术上的注意事项:字段类型 +一些技术上的注意事项: -这个表的名称myapp_person,是根据 模型中的元数据自动生成的,也可以覆写为别的名称,详见Table names。 -id 字段是自动添加的,但这个行为可以被重写。详见Automatic primary key fields。 -这个例子使用 PostgreSQL 语法格式化CREATE TABLESQL 语句,要注意的是 Django 是根据settings file配置中指定的数据库类型来生成相应的 SQL 语句。 +* 这个表的名称`myapp_person`,是根据 模型中的元数据自动生成的,也可以覆写为别的名称,详见[_Table names_](../../ref/models/options.html#table-names)。 +* `id` 字段是自动添加的,但这个行为可以被重写。详见[_自增主键字段_](#automatic-primary-key-fields)。 +* 这个例子中的`CREATE TABLE` SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据[_设置文件_](../settings.html) 中指定的数据库类型来使用相应的SQL 语句。 -## 使用模型 ## +## 使用模型 -一旦你定义了模型,就要通知Django启用这些模型,你要做的就是修改配置文件中的INSTALLED_APPS 设置,在其中添加models.py所在应用的名称。 +定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的[`INSTALLED_APPS`](../../ref/settings.html#std:setting-INSTALLED_APPS) 设置,在其中添加`models.py`所在应用的名称。 -例如,假设你的 model 定义在 mysite.myapp.models 中 ( mysite 这个包是由 manage.py startapp 脚本创建的),那么 INSTALLED_APPS 就应该包含下面这行: +例如,如果你的应用的模型位于`myapp.models`模块([`manage.py startapp`](../../ref/django-admin.html#django-admin-startapp) 脚本为一个应用创建的包结构),[`INSTALLED_APPS`](../../ref/settings.html#std:setting-INSTALLED_APPS)部分看上去应该是: ``` INSTALLED_APPS = ( #... - 'mysite.myapp', + 'myapp', #... ) + ``` -在 INSTALLED_APPS 中添加新应用之后,要运行 manage.py syncdb 同步数据库。 +当你在[`INSTALLED_APPS`](../../ref/settings.html#std:setting-INSTALLED_APPS) 中添加新的应用名时,请确保运行命令[`manage.py migrate`](../../ref/django-admin.html#django-admin-migrate),可以首先使用[`manage.py makemigrations`](../../ref/django-admin.html#django-admin-makemigrations) 来为它们生成迁移脚本。 -## 字段 ## +## 字段 -模型 中不可或缺且最为重要的,就是字段集,它是一组数据库字段的列表。字段被指定为类属性。要注意选择字段名称的时候不要和models API 冲突,比如clean, save, 或者delete。 +模型中不可或缺且最为重要的,就是字段集,它是一组数据库字段的列表。字段被指定为类属性。 要注意选择的字段名称不要和[_模型 API_](../../ref/models/instances.html) 冲突,比如`clean`、`save` 或者`delete`。 例如: ``` +from django.db import models + class Musician(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) @@ -75,82 +76,91 @@ class Album(models.Model): name = models.CharField(max_length=100) release_date = models.DateField() num_stars = models.IntegerField() + ``` -## 字段类型 ## +### 字段类型 + +模型中的每个字段都是 [`Field`](../../ref/models/fields.html#django.db.models.Field "django.db.models.Field") 子类的某个实例。Django 根据字段类的类型确定以下信息: -model 中的每个字段都是 Field 子类的某个实例。Django 根据字段类的类型确定以下信息: +* 数据库当中的列类型 (比如, `INTEGER`, `VARCHAR`)。 +* 渲染表单时使用的默认HTML [_部件_](../../ref/forms/widgets.html)(例如,``, ``)。 +* 最低限度的验证需求,它被用在 Django 管理站点和自动生成的表单中。 -数据库当中的列类型 (比如,INTEGER, VARCHAR)。 -Django 的用户管理界面所使用的部件(widget)。当然,前提是你启用了 Django 的管理后台 (例如, ``, ``)。 -最低限度的验证需求。它被用在 Django 管理后台和自动生成的表单中。 -Django 自带数十种内置的字段类型;详见 model 字段参考(model field reference)。如果内置类型仍不能满足你的要求,你可以自由地编写符合你要求的字段类型;详见 编写自定义 model 字段(Writing custom model fields)。 +Django 自带数十种内置的字段类型;完整字段类型列表可以在[_模型字段参考_](../../ref/models/fields.html#model-field-types) 中找到。如果内置类型仍不能满足你的要求,你可以自由地编写符合你要求的字段类型; 详见[_编写自定义的模型字段_](../../howto/custom-model-fields.html)。 -## 字段选项 ## +### 字段选项 -每个字段都有一些特有的参数,详见 model 字段参考(model field reference)。例如, CharField (还有它的派生类) 都需要 max_length 参数来指定存储数据的 VARCHAR 数据库字段的大小。 +每个字段有一些特有的参数,详见[_模型字段参考_](../../ref/models/fields.html#model-field-types)。例如,[`CharField`](../../ref/models/fields.html#django.db.models.CharField "django.db.models.CharField")(和它的派生类)需要[`max_length`](../../ref/models/fields.html#django.db.models.CharField.max_length "django.db.models.CharField.max_length") 参数来指定`VARCHAR` 数据库字段的大小。 -还有一些适用于所有字段的可选的通用参数,这些参数在 参考(reference) 中有详细定义,这里我们只简单介绍一些最常用的: +还有一些适用于所有字段的通用参数。 这些参数在[_参考_](../../ref/models/fields.html#common-model-field-options)中有详细定义,这里我们只简单介绍一些最常用的: -**null** +[`null`](../../ref/models/fields.html#django.db.models.Field.null "django.db.models.Field.null") -如果为 True, Django 在数据库中会将空值(empty)存储为 NULL 。 默认为 False。 +如果为`True`,Django 将用`NULL` 来在数据库中存储空值。 默认值是 `False`. -**blank** +[`blank`](../../ref/models/fields.html#django.db.models.Field.blank "django.db.models.Field.blank") -如果为 True,该字段允许不填(blank)。默认为 False。 +如果为`True`,该字段允许不填。默认为`False`。 -要注意,这与 null 不同。 null 纯粹是数据库范畴的,而 blank 是数据验证范畴的。如果一个字段的 blank=True,Django 的管理后台在做数据验证时,会允许该字段是空值。如果字段的 blank=False,该字段就是必填的。 +要注意,这与 [`null`](../../ref/models/fields.html#django.db.models.Field.null "django.db.models.Field.null") 不同。[`null`](../../ref/models/fields.html#django.db.models.Field.null "django.db.models.Field.null")纯粹是数据库范畴的,而 [`blank`](../../ref/models/fields.html#django.db.models.Field.blank "django.db.models.Field.blank") 是数据验证范畴的。如果一个字段的[`blank=True`](../../ref/models/fields.html#django.db.models.Field.blank "django.db.models.Field.blank"),表单的验证将允许该字段是空值。如果字段的[`blank=False`](../../ref/models/fields.html#django.db.models.Field.blank "django.db.models.Field.blank"),该字段就是必填的。 -**choices** +[`choices`](../../ref/models/fields.html#django.db.models.Field.choices "django.db.models.Field.choices") -它是一个可迭代的二元组(例如,列表或是元组),用来给字段提供选择项。如果设置了 choices ,Django 的管理后台就会显示选择框,而不是标准的文本框,而且这个选择框的选项就是 choices 中的元组。 +由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,而且这个选择框的选项就是choices 中的选项。 这是一个关于 choices 列表的例子: ``` YEAR_IN_SCHOOL_CHOICES = ( - (u'FR', u'Freshman'), - (u'SO', u'Sophomore'), - (u'JR', u'Junior'), - (u'SR', u'Senior'), - (u'GR', u'Graduate'), + ('FR', 'Freshman'), + ('SO', 'Sophomore'), + ('JR', 'Junior'), + ('SR', 'Senior'), + ('GR', 'Graduate'), ) + ``` -每个元组中的第一个元素,是存储在数据库中的值;第二个元素是在管理界面或 ModelChoiceField 中用作显示的内容。在一个给定的 model 类的实例中,想得到某个 choices 字段的显示值,就调用 get_FOO_display 方法(这里的 FOO 就是 choices 字段的名称 )。例如: +每个元组中的第一个元素,是存储在数据库中的值;第二个元素是在管理界面或 ModelChoiceField 中用作显示的内容。 在一个给定的 model 类的实例中,想得到某个 choices 字段的显示值,就调用 `get_FOO_display` 方法(这里的 FOO 就是 choices 字段的名称 )。例如: ``` from django.db import models class Person(models.Model): - GENDER_CHOICES = ( - (u'M', u'Male'), - (u'F', u'Female'), + SHIRT_SIZES = ( + ('S', 'Small'), + ('M', 'Medium'), + ('L', 'Large'), ) name = models.CharField(max_length=60) - gender = models.CharField(max_length=2, choices=GENDER_CHOICES) ->>> p = Person(name="Fred Flinstone", gender="M") + shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES) + +``` + +``` +>>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() ->>> p.gender -u'M' ->>> p.get_gender_display() -u'Male' +>>> p.shirt_size +'L' +>>> p.get_shirt_size_display() +'Large' + ``` -**default** +[`default`](../../ref/models/fields.html#django.db.models.Field.default "django.db.models.Field.default") -字段的默认值。它可以是一个值,也可以是一个可调用的对象(这里称之为对象C)。若是后者,那么每次创建一个新对象时,对象C都将被调用。 +字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。 -**help_text** +[`help_text`](../../ref/models/fields.html#django.db.models.Field.help_text "django.db.models.Field.help_text") -附加的帮助信息。在管理后台编辑该对象的表单中,它显示在字段下面。即使你的对象无须在后台进行管理,它对于文档化也是很有用的。 +表单部件额外显示的帮助内容。即使字段不在表单中使用,它对生成文档也很有用。 -**primary_key** +[`primary_key`](../../ref/models/fields.html#django.db.models.Field.primary_key "django.db.models.Field.primary_key") -如果为 True,那么这个字段就是 model 的主键。 +如果为`True`,那么这个字段就是模型的主键。 -如果你没有指定任何一个字段的 primary_key=True,Django 就会自动添加一个 IntegerField 字段做为主键。所以除非你想重写默认的主键方法,否则没必要在任何字段上设置 primary_key=True 。详见 自增主键字段(Automatic primary key fields). +如果你没有指定任何一个字段的[`primary_key=True`](../../ref/models/fields.html#django.db.models.Field.primary_key "django.db.models.Field.primary_key"),Django 就会自动添加一个[`IntegerField`](../../ref/models/fields.html#django.db.models.IntegerField "django.db.models.IntegerField") 字段做为主键,所以除非你想覆盖默认的主键行为,否则没必要设置任何一个字段的[`primary_key=True`](../../ref/models/fields.html#django.db.models.Field.primary_key "django.db.models.Field.primary_key")。详见[_自增主键字段_](#automatic-primary-key-fields)。 主键字段是只读的。如果你在一个已存在的对象上面更改主键的值并且保存,一个新的对象将会在原有对象之外创建出来。例如: @@ -160,148 +170,168 @@ from django.db import models class Fruit(models.Model): name = models.CharField(max_length=100, primary_key=True) +``` + +``` >>> fruit = Fruit.objects.create(name='Apple') >>> fruit.name = 'Pear' >>> fruit.save() >>> Fruit.objects.values_list('name', flat=True) ['Apple', 'Pear'] + ``` -**unique** +[`unique`](../../ref/models/fields.html#django.db.models.Field.unique "django.db.models.Field.unique") -如果为 True,那么字段值就必须是全表唯一的。 -再说一次,这些仅仅是常用字段的简短介绍,要了解详细内容,请查看 通用 model 字段选项参考(common model field option reference). +如果该值设置为 `True`, 这个数据字段的值在整张表中必须是唯一的 -## 自增主键字段 ## +再说一次,这些仅仅是常用字段的简短介绍, 要了解详细内容,请查看 通用 model 字段选项参考([_common model field option reference_](../../ref/models/fields.html#common-model-field-options)). -默认情况下,Django 会给每个 model 添加下面这个字段: +### 自增主键字段 + +默认情况下,Django 会给每个模型添加下面这个字段: ``` id = models.AutoField(primary_key=True) + ``` 这是一个自增主键字段。 -如果你想指定一个自定义主键字段,只要在某个字段上指定 primary_key=True 即可。如果 Django 看到你显式地设置了 Field.primary_key,就不会自动添加 id 列。 +如果你想指定一个自定义主键字段,只要在某个字段上指定 [`primary_key=True`](../../ref/models/fields.html#django.db.models.Field.primary_key "django.db.models.Field.primary_key") 即可。如果 Django 看到你显式地设置了 [`Field.primary_key`](../../ref/models/fields.html#django.db.models.Field.primary_key "django.db.models.Field.primary_key"),就不会自动添加 `id` 列。 -每个 model 只要有一个字段指定 primary_key=True 就可以了。(无论是显式声明还是自动添加的。) +每个模型只能有一个字段指定[`primary_key=True`](../../ref/models/fields.html#django.db.models.Field.primary_key "django.db.models.Field.primary_key")(无论是显式声明还是自动添加)。 -## 字段的自述名 ## +### 字段的自述名 -除了 ForeignKey, ManyToManyField 和 OneToOneField 之外,其余每个字段类型都接受一个排在首位的可选的位置参数--这就是字段的自述名。如果没有给定自述名,Django 将根据字段的属性名称自动创建自述名--就是将属性名称的空格替换成下划线。 +除[`ForeignKey`](../../ref/models/fields.html#django.db.models.ForeignKey "django.db.models.ForeignKey")、[`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 和 [`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField") 之外,每个字段类型都接受一个可选的位置参数 —— 字段的自述名。如果没有给定自述名,Django 将根据字段的属性名称自动创建自述名 —— 将属性名称的下划线替换成空格。 -在这个例子中,自述名是 "Person's first name": +在这个例子中,自述名是 `"person's first name":` ``` -first_name = models.CharField("Person's first name", max_length=30) +first_name = models.CharField("person's first name", max_length=30) + ``` -在这个例子中,自述名是 "first name": +在这个例子中,自述名是 `"first name"`: ``` first_name = models.CharField(max_length=30) + ``` -ForeignKey, ManyToManyField 和 OneToOneField 都要求排在首位的参数得是一个 model 类,所以要使用 verbose_name 关键字参数才能指定自述名: +[`ForeignKey`](../../ref/models/fields.html#django.db.models.ForeignKey "django.db.models.ForeignKey")、[`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 和 [`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField") 都要求第一个参数是一个模型类,所以要使用 [`verbose_name`](../../ref/models/fields.html#django.db.models.Field.verbose_name "django.db.models.Field.verbose_name") 关键字参数才能指定自述名: ``` poll = models.ForeignKey(Poll, verbose_name="the related poll") sites = models.ManyToManyField(Site, verbose_name="list of sites") place = models.OneToOneField(Place, verbose_name="related place") + ``` -verbose_name 首字母是不用大写的,这是因为 Django 在必要的时候会自动大写首字母的。 +习惯上,[`verbose_name`](../../ref/models/fields.html#django.db.models.Field.verbose_name "django.db.models.Field.verbose_name") 的首字母不用大写。Django 在必要的时候会自动大写首字母。 -## 关系 ## +### 关系 -显然,关系数据库的威力体现在表之间的相互关联。Django 提供了三种最常见的数据库关系:多对一(many-to-one),多对多(many-to-many),一对一(one-to-one)。 +显然,关系数据库的威力体现在表之间的相互关联。 Django 提供了三种最常见的数据库关系:多对一(many-to-one),多对多(many-to-many),一对一(one-to-one)。 -### 多对一关系 ### +#### 多对一关系 -Django 使用 ForeignKey 定义多对一关系。 和使用其他 字段(Field) 类型一样:在 model 当中把它做为一个类属性包含进来。 +Django 使用 [`django.db.models.ForeignKey`](../../ref/models/fields.html#django.db.models.ForeignKey "django.db.models.ForeignKey") 定义多对一关系。和使用其它[`字段`](../../ref/models/fields.html#django.db.models.Field "django.db.models.Field")类型一样:在模型当中把它做为一个类属性包含进来。 -ForeignKey 需要一个位置参数:与该 model 关联的类。 +[`ForeignKey`](../../ref/models/fields.html#django.db.models.ForeignKey "django.db.models.ForeignKey") 需要一个位置参数:与该模型关联的类。 -比如,如果每个 汽车(Car) model 都有一个 生产商(Manufacturer) model -- 也就是说,一个 Manufacturer 可以生产出很多 Car ;但是每一辆 Car 却只能有一个 Manufacturer -- 使用下面的定义: +比如,一辆`Car`有一个`Manufacturer` —— 但是一个`Manufacturer` 生产很多Car,每一辆`Car` 只能有一个`Manufacturer` —— 使用下面的定义: ``` +from django.db import models + class Manufacturer(models.Model): # ... + pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer) # ... + ``` -你还可以创建 递归的关联关系(recursive relationships) (对象和自己进行多对一关联) 和 关联至尚未定义关系的 model (relationships to models not yet defined); 详见 model 字段参考(the model field reference) 。 +你还可以创建[_递归的关联关系_](../../ref/models/fields.html#recursive-relationships)(对象和自己进行多对一关联)和 [_与尚未定义的模型的关联关系_](../../ref/models/fields.html#lazy-relationships);详见[_模型字段参考_](../../ref/models/fields.html#ref-foreignkey)。 -建议你用被关联 model 的小写名称做为 ForeignKey 字段的命名 (上例中,我们就是以manufacturer 的小写做为命名的)。当然,你也可以起别的名字,例如: +建议你用被关联的模型的小写名称做为[`ForeignKey`](../../ref/models/fields.html#django.db.models.ForeignKey "django.db.models.ForeignKey") 字段的名字(例如,上面`manufacturer`)。当然,你也可以起别的名字。例如: ``` class Car(models.Model): company_that_makes_it = models.ForeignKey(Manufacturer) # ... + ``` -> 另见 -> -> ForeignKey 字段还可以接受别的参数,它们都是可选的,在 model 字段参考(the model field reference) 有详细介绍。这些选项定义了关系是如何工作的。 -> -> 访问反向关联对象的细节,请见Following relationships backward example。 -> -> 示例代码请见多对一关系的模型例子( Many-to-one relationship model example)。 +另见 + +[`ForeignKey`](../../ref/models/fields.html#django.db.models.ForeignKey "django.db.models.ForeignKey") 字段还接受许多别的参数,在[_模型字段参考_](../../ref/models/fields.html#foreign-key-arguments)有详细介绍。这些选项帮助定义关联关系应该如何工作;它们都是可选的参数。 + +访问反向关联对象的细节,请见[_Following relationships backward example_](queries.html#backwards-related-objects)。 -## 多对多关系 ## +示例代码,请见[_多对一关系模型示例_](examples/many_to_one.html))。 -ManyToManyField 用来定义多对多关系,用法和其他 Field 字段类型一样:在 model 中做为一个类属性包含进来。 +#### 多对多关系 -ManyToManyField 需要一个位置参数:和该 model 关联的类。 +[`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 用来定义多对多关系,用法和其他[`Field`](../../ref/models/fields.html#django.db.models.Field "django.db.models.Field") 字段类型一样:在模型中做为一个类属性包含进来。 -例如,一个 匹萨(Pizza) 可以有多种不同口味的 浇头(Topping) -- 也就是说,一种 Topping 可以浇在多个 Pizza 上,而每个 Pizza 也可以浇上多种 topping -- 如下: +[`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 需要一个位置参数:和该模型关联的类。 + +例如,一个`Pizza`可以有多种`Topping` —— 一种`Topping` 可以位于多个Pizza 上,而且每个`Pizza` 可以有多种Topping —— 如下: ``` +from django.db import models + class Topping(models.Model): # ... + pass class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping) + ``` -和使用 ForeignKey 一样,你也可以创建 递归的关联关系(recursive relationships) (对象和自己做多对多关联)和 关联至尚未定义关系的 model (relationships to models not yet defined);详见 the model field reference 。 +和使用[`ForeignKey`](../../ref/models/fields.html#django.db.models.ForeignKey "django.db.models.ForeignKey") 一样,你也可以创建[_递归的关联关系_](../../ref/models/fields.html#recursive-relationships)(对象与自己的多对多关联)和[_与尚未定义关系的模型的关联关系_](../../ref/models/fields.html#lazy-relationships);详见[_模型字段参考_](../../ref/models/fields.html#ref-manytomany)。 + +建议你以被关联模型名称的复数形式做为[`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 的名字(例如上例中的`toppings`)。 -建议你以被关联 model 名称的复数形式做为 ManyToManyField 的命名 (例如上例中的 toppings )。 +在哪个模型中设置 [`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 并不重要,在两个模型中任选一个即可 —— 不要两个模型都设置。 -在哪个 model 中设置 ManyToManyField 并不重要,在两个 model 中任选一个即可。 +通常,[`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 实例应该位于可以编辑的表单中。在上面的例子中,`toppings` 位于`Pizza` 中(而不是在 `Topping` 里面设置`pizzas` 的 [`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 字段),因为设想一个Pizza 有多种Topping 比一个Topping 位于多个Pizza 上要更加自然。按照上面的方式,在`Pizza` 的表单中将允许用户选择不同的Toppings。 -通常来说,如果启用了 Django 管理后台,你就可以在后台将 ManyToManyField 实例添加到关联对象中。在上面的例子中,在 Pizza 里面设置 toppings (而不是在 Topping 里面设置 pizzas ManyToManyField)。 这么设置的原因是因为一个 pizza 有多个 topping 相比于一个 topping 浇在多个 pizza 上要更加自然。这样,在 Pizza 的管理后台中,就会允许用户选择不同的 toppings。 +另见 -> 另见 -> -> 在 多对多关系 model 实例(Many-to-many relationship model example) 有一个完整例子。 +完整的示例参见[_多对多关系模型示例_](examples/many_to_many.html)。 -ManyToManyField 字段还可以接受别的参数,它们都是可选的,在 model 字段参考(the model field reference) 中有详细介绍。这些选项定义了关系是如何工作的。 +[`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 字段还接受别的参数,在[_模型字段参考_](../../ref/models/fields.html#manytomany-arguments)中有详细介绍。这些选项帮助定义关系应该如何工作;它们都是可选的。 -### 多对多关系中的其他字段 ### +#### 多对多关系中的其他字段 -处理类似搭配 pizza 和 topping 这样简单的多对多关系时,使用标准的 ManyToManyField 就可以了。但是有时,我们需要在两个 model 之间关联其他的数据。 +处理类似搭配 pizza 和 topping 这样简单的多对多关系时,使用标准的[`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 就可以了。但是,有时你可能需要关联数据到两个模型之间的关系上。 -例如,有这样一个应用:关注某个音乐小组,它拥有多个音乐家成员。我们可以用一个标准的 ManyToManyField 表示小组和成员之间的多对多关系。但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。 +例如,有这样一个应用,它记录音乐家所属的音乐小组。我们可以用一个[`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 表示小组和成员之间的多对多关系。但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。 -在这种情况下,Django 允许你指定一个 model 来定义多对多关系(我们称之为中介 model )。你可以将其他字段放在中介 model 里面,而主 model 的 ManyToManyField 使用 through 参数来指向中介 model 。对于上面的音乐小组的例子来说,代码如下: +对于这些情况,Django 允许你指定一个模型来定义多对多关系。 你可以将其他字段放在中介模型里面。源模型的[`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 字段将使用[`through`](../../ref/models/fields.html#django.db.models.ManyToManyField.through "django.db.models.ManyToManyField.through") 参数指向中介模型。对于上面的音乐小组的例子,代码如下: ``` +from django.db import models + class Person(models.Model): name = models.CharField(max_length=128) - def __unicode__(self): + def __str__(self): # __unicode__ on Python 2 return self.name class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') - def __unicode__(self): + def __str__(self): # __unicode__ on Python 2 return self.name class Membership(models.Model): @@ -309,18 +339,22 @@ class Membership(models.Model): group = models.ForeignKey(Group) date_joined = models.DateField() invite_reason = models.CharField(max_length=64) + ``` -在设置中介 model 时,要显式地定义一个外键,它与包含多对多关系的 model 相关联。这个显式的声明定义了两个 model 之间中如何关联的。 +在设置中介模型时,要显式指定外键并关联到多对多关系涉及的模型。这个显式声明定义两个模型之间是如何关联的。 -在使用中介 model 时要注意以下限制: +中介模型有一些限制: -+ 有且只有一个外键指向目标 model (例中目标 model 就是 Person );否则就会抛出验证异常。 -+ 有且只有一个外键指向源 model (例中源 model 就是 Group );否则就会抛出验证异常。 -+ 但存在唯一的一种特殊情况:利用中介 model 实现递归的多对多关系。这种情况下,两个外键指向同一个 model 是允许的;但这个 model 会被视为多对多关系中不同的双方进行处理。 -+ 定义递归的多对多关系时,你必须设置 symmetrical=False (详见 model 字段参考(the model field reference))。 +* 中介模型必须_有且只有一个_外键到源模型(上面例子中的`Group`),或者你必须使用[`ManyToManyField.through_fields`](../../ref/models/fields.html#django.db.models.ManyToManyField.through_fields "django.db.models.ManyToManyField.through_fields") 显式指定Django 应该使用的外键。如果你的模型中存在超个一个的外键,并且`through_fields`没有指定,将会触发一个无效的错误。 对目标模型的外键有相同的限制(上面例子中的 `Person`)。 +* 对于通过中介模型与自己进行多对多关联的模型,允许存在到同一个模型的两个外键,但它们将被作为多对多关联关系的两个(不同的)方面。如果有_超过_ 两个外键,同样你必须像上面一样指定`through_fields`,否则将引发一个验证错误。 +* 使用中介模型定义与自身的多对多关系时,你_必须_设置 [`symmetrical=False`](../../ref/models/fields.html#django.db.models.ManyToManyField.symmetrical "django.db.models.ManyToManyField.symmetrical")(详见[_模型字段参考_](../../ref/models/fields.html#manytomany-arguments))。 -现在你已经设置了 ManyToManyField 来使用中介 model (在这个例子中就是 Membership),接下来你要开始创建多对多关系。你要做的就是创建中介 model 的实例: +Changed in Django 1.7: + +在Django 1.6 及之前的版本中,中介模型禁止包含多于一个的外键。 + +既然你已经设置好[`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 来使用中介模型(在这个例子中就是`Membership`),接下来你要开始创建多对多关系。你要做的就是创建中介模型的实例: ``` >>> ringo = Person.objects.create(name="Ringo Starr") @@ -328,7 +362,7 @@ class Membership(models.Model): >>> beatles = Group.objects.create(name="The Beatles") >>> m1 = Membership(person=ringo, group=beatles, ... date_joined=date(1962, 8, 16), -... invite_reason= "Needed a new drummer.") +... invite_reason="Needed a new drummer.") >>> m1.save() >>> beatles.members.all() [] @@ -336,12 +370,13 @@ class Membership(models.Model): [] >>> m2 = Membership.objects.create(person=paul, group=beatles, ... date_joined=date(1960, 8, 1), -... invite_reason= "Wanted to form a band.") +... invite_reason="Wanted to form a band.") >>> beatles.members.all() [, ] + ``` -与普通的多对多字段不同,你不能使用 add, create, 和赋值语句 (比如,beatles.members = [...]) 来创建关系: +与普通的多对多字段不同,你_不能_使用`add`、 `create`和赋值语句(比如,`beatles.members = [...]`)来创建关系: ``` # THIS WILL NOT WORK @@ -350,26 +385,32 @@ class Membership(models.Model): >>> beatles.members.create(name="George Harrison") # AND NEITHER WILL THIS >>> beatles.members = [john, paul, ringo, george] + ``` -为什么不能这样做? 这是因为你不能只创建 Person and a Group 之间的关联关系,你还要指定 Membership model 中所需要的所有信息;而简单的 add, create 和赋值语句是做不到这一点的。所以它们不能在这种情况下使用。此时,唯一的办法就是创建中介 model 的实例。 +为什么不能这样做? 这是因为你不能只创建 `Person`和 `Group`之间的关联关系,你还要指定 `Membership`模型中所需要的所有信息;而简单的`add`、`create` 和赋值语句是做不到这一点的。所以它们不能在使用中介模型的多对多关系中使用。此时,唯一的办法就是创建中介模型的实例。 -remove 方法被禁用也是出于同样的原因。但是 clear() 方法却是可用的。它可以清空某个实例所有的多对多关系: +[`remove()`](../../ref/models/relations.html#django.db.models.fields.related.RelatedManager.remove "django.db.models.fields.related.RelatedManager.remove")方法被禁用也是出于同样的原因。但是[`clear()`](../../ref/models/relations.html#django.db.models.fields.related.RelatedManager.clear "django.db.models.fields.related.RelatedManager.clear") 方法却是可用的。它可以清空某个实例所有的多对多关系: ``` -# Beatles have broken up +>>> # Beatles have broken up >>> beatles.members.clear() +>>> # Note that this deletes the intermediate model instances +>>> Membership.objects.all() +[] + ``` -在创建了中介 model 的实例,完成了对多对多关系的定义之后,你就可以执行查询了。和普通的多对多字段一样,你可以直接使用被关联 model 的属性进行查询: +通过创建中介模型的实例来建立对多对多关系后,你就可以执行查询了。 和普通的多对多字段一样,你可以直接使用被关联模型的属性进行查询: ``` # Find all the groups with a member whose name starts with 'Paul' >>> Group.objects.filter(members__name__startswith='Paul') [] + ``` -如果你使用了中介 model ,你也可以利用中介 model 的其他属性进行查询: +如果你使用了中介模型,你也可以利用中介模型的属性进行查询: ``` # Find all the members of the Beatles that joined after 1 Jan 1961 @@ -377,196 +418,252 @@ remove 方法被禁用也是出于同样的原因。但是 clear() 方法却是 ... group__name='The Beatles', ... membership__date_joined__gt=date(1961,1,1)) [>> ringos_membership = Membership.objects.get(group=beatles, person=ringo) +>>> ringos_membership.date_joined +datetime.date(1962, 8, 16) +>>> ringos_membership.invite_reason +'Needed a new drummer.' + +``` + +另一种获取相同信息的方法是,在`Person`对象上查询[_多对多反转关系_](queries.html#m2m-reverse-relationships): + +``` +>>> ringos_membership = ringo.membership_set.get(group=beatles) +>>> ringos_membership.date_joined +datetime.date(1962, 8, 16) +>>> ringos_membership.invite_reason +'Needed a new drummer.' + ``` -### 一对一关系 ### +#### 一对一关系 -OneToOneField 用来定义一对一关系。用法和其他 Field 字段类型一样:在 model 里面做为类属性包含进来。 +[`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField")用来定义一对一关系。 用法和其他`字段`类型一样:在模型里面做为类属性包含进来。 当某个对象想扩展自另一个对象时,最常用的方式就是在这个对象的主键上添加一对一关系。 -OneToOneField 需要一个位置参数:与 model 关联的类。 +[`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField")要一个位置参数:与模型关联的类。 -例如,你想建一个 "places" 数据库,里面有一些常用的字段,比如 address, phone number, 等等。接下来,如果你想在 Place 数据库的基础上建立一个 饭店(Restaurant) 数据库,而不想将已有的字段复制到 Restaurant model ,那你可以在 Restaurant 添加一个 OneToOneField 字段,这个字段指向 Place (因为饭店(restaurant)本身就是一个地点(place),事实上,在处理这个问题的时候,你已经使用了一个典型的 继承(inheritance),它隐含了一个一对一关系)。 +例如,你想建一个“places” 数据库,里面有一些常用的字段,比如address、 phone number 等等。 接下来,如果你想在Place 数据库的基础上建立一个Restaurant 数据库,而不想将已有的字段复制到`Restaurant`模型,那你可以在 `Restaurant` 添加一个[`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField") 字段,这个字段指向`Place`(因为Restaurant 本身就是一个Place;事实上,在处理这个问题的时候,你应该使用一个典型的 [_继承_](#model-inheritance),它隐含一个一对一关系)。 -和使用 ForeignKey 一样,你可以定义 递归的关联关系(recursive relationship) 和 引用尚未定义关系的 model (references to as-yet undefined models) 。详见 model 字段参考(the model field reference) 。 +和使用 [`ForeignKey`](../../ref/models/fields.html#django.db.models.ForeignKey "django.db.models.ForeignKey")一样,你可以定义[_ 递归的关联关系_](../../ref/models/fields.html#recursive-relationships)和[_引用尚未定义关系的模型_](../../ref/models/fields.html#lazy-relationships)。详见[_模型字段参考_](../../ref/models/fields.html#ref-onetoone)。 -> 参见 -> -> 在 一对一关系的 model 例子(One-to-one relationship model example) 有一套完整的例子。 -> -> 这部分是在 Django 1.0 中新增的: 请查看版本文档 +另见 -OneToOneField 字段还有其他一些参数,它们都是可选的,在 model 字段参考(model field reference) 中有详细介绍。 +在[_一对一关系的模型例子_](examples/one_to_one.html) 中有一套完整的例子。 -在以前的版本中,OneToOneField 字段会自动变成 model 的主键。不过现在已经不这么做了(不过要是你愿意的话,你仍可以传递 primary_key 参数来创建主键字段)。所以一个 model 中可以有多个 OneToOneField 字段。 +[`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField")字段也接受一个特定的可选的`parent_link`参数,在[_模型字段参考_](../../ref/models/fields.html#ref-onetoone) 中有详细介绍。 -## 跨文件访问 model ## +在以前的版本中,[`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField") 字段会自动变成模型 的主键。不过现在已经不这么做了(不过要是你愿意的话,你仍可以传递 [`primary_key`](../../ref/models/fields.html#django.db.models.Field.primary_key "django.db.models.Field.primary_key")参数来创建主键字段)。所以一个 模型 中可以有多个[`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField") 字段。 -访问其他应用的 model 是非常容易的。在使用 model 之前将它导入到当前程序即可。例如: +### 跨文件的模型 + +访问其他应用的模型是非常容易的。 在文件顶部你定义模型的地方,导入相关的模型来实现它。然后,无论在哪里需要的话,都可以引用它。例如: ``` -from mysite.geography.models import ZipCode +from django.db import models +from geography.models import ZipCode class Restaurant(models.Model): # ... zip_code = models.ForeignKey(ZipCode) + ``` -## 字段命名的限制 ## +### 字段命名的限制 Django 对字段的命名只有两个限制: -字段名不可以是 Python 的保留字,否则会导致 Python 语法错误。例如: +1. 字段的名称不能是Python 保留的关键字,因为这将导致一个Python 语法错误。例如: -``` -class Example(models.Model): - pass = models.IntegerField() # 'pass' is a reserved word! -``` + ``` + class Example(models.Model): + pass = models.IntegerField() # 'pass' is a reserved word! -字段名称不可以包含连续多个下划线,因为这与 Django 查询时所用的筛选条件语法相冲突。例如: + ``` -``` -class Example(models.Model): - foo__bar = models.IntegerField() # 'foo__bar' has two underscores! -``` +2. 由于Django 查询语法的工作方式,字段名称中连续的下划线不能超过一个。例如: + + ``` + class Example(models.Model): + foo__bar = models.IntegerField() # 'foo__bar' has two underscores! + + ``` -但是,只要你的字段名称与数据库中的列名不同,就可以绕过这些限制。详见 db_column 选项。 +这些限制有变通的方法,因为没有要求字段名称必须与数据库的列名匹配。参 [`db_column`](../../ref/models/fields.html#django.db.models.Field.db_column "django.db.models.Field.db_column") 选项。 -SQL 保留字,如 join, where 和 select, 可以做为 model 中字段的名称。这是因为 Django 会对每个 SQL 查询的数据库名称和列名称做重编码,至于如何编码视你所用的数据库而定。 +SQL 的保留字例如`join`、`where` 和`select`,可以用作模型的字段名,因为Django 会对底层的SQL 查询语句中的数据库表名和列名进行转义。 它根据你的数据库引擎使用不同的引用语法。 -## 自定义字段类型 ## +### 自定义字段类型 -如果 Django 自带的字段类型不能满足你的应用,或者你希望使用一些不常见的数据库列类型,那你可以创建自定义的字段类型。详见 编写自定义 model 字段(Writing custom model fields)。 +如果已有的模型字段都不合适,或者你想用到一些很少见的数据库列类型的优点,你可以创建你自己的字段类型。创建你自己的字段在[_编写自定义的模型字段_](../../howto/custom-model-fields.html)中有完整讲述。 -## Meta 选项 ## +## 元选项 -通过使用一个内含的 class Meta 来为你的model 添加元数据,例如: +使用内部的`class Meta` 定义模型的元数据,例如: ``` +from django.db import models + class Ox(models.Model): horn_length = models.IntegerField() class Meta: ordering = ["horn_length"] verbose_name_plural = "oxen" + ``` -在 model 里面,除了字段就是元数据,比如排序项(ordering),数据库名称(db_table),和自述名(verbose_name 和 verbose_name_plural)。对于 model 来说,这些都不是必需的,甚至就连 class Meta 本身都不是必需的。 +模型元数据是“任何不是字段的数据”,比如排序选项([`ordering`](../../ref/models/options.html#django.db.models.Options.ordering "django.db.models.Options.ordering")),数据表名([`db_table`](../../ref/models/options.html#django.db.models.Options.db_table "django.db.models.Options.db_table"))或者人类可读的单复数名称([`verbose_name`](../../ref/models/options.html#django.db.models.Options.verbose_name "django.db.models.Options.verbose_name") 和[`verbose_name_plural`](../../ref/models/options.html#django.db.models.Options.verbose_name_plural "django.db.models.Options.verbose_name_plural"))。在模型中添加`class Meta`是完全可选的,所有选项都不是必须的。 + +所有`元`选项的完整列表可以在[_模型选项参考_](../../ref/models/options.html)找到。 + +## 模型的属性 + +`objects` -Meta 选项的完整列表可以在 model 选项参考(model option reference) 中找到。 +The most important attribute of a model is the +[`Manager`](managers.html#django.db.models.Manager "django.db.models.Manager"). It’s the interface through which +database query operations are provided to Django models and is used to +[_retrieve the instances_](queries.html#retrieving-objects) from the database. If no +custom `Manager` is defined, the default name is +[`objects`](../../ref/models/class.html#django.db.models.Model.objects "django.db.models.Model.objects"). Managers are only accessible via +model classes, not the model instances. -## Model 方法 ## +## 模型的方法 -自定义 model 的方法,就是为你的对象添加自定义的行级功能(row-level),而 Manager 方法却喜欢做表级的事情(table-wide)。所以,model 方法应该作用于 model 类的实例(也就是说,在实例对象上使用 model 方法,而不是在类上直接使用)。 +可以在模型上定义自定义的方法来给你的对象添加自定义的“底层”功能。[`Manager`](managers.html#django.db.models.Manager "django.db.models.Manager") 方法用于“表范围”的事务,模型的方法应该着眼于特定的模型实例。 -最好是只在一个地方(就是在 model 中)保存商业逻辑。 +这是一个非常有价值的技术,让业务逻辑位于同一个地方 —— 模型中。 -例如,在下面这个 model 中自定义方法: +例如,下面的模型具有一些自定义的方法: ``` -from django.contrib.localflavor.us.models import USStateField +from django.db import models class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) birth_date = models.DateField() - address = models.CharField(max_length=100) - city = models.CharField(max_length=50) - state = USStateField() # Yes, this is America-centric... def baby_boomer_status(self): "Returns the person's baby-boomer status." import datetime - if datetime.date(1945, 8, 1) <= self.birth_date <= datetime.date(1964, 12, 31): - return "Baby boomer" if self.birth_date < datetime.date(1945, 8, 1): return "Pre-boomer" - return "Post-boomer" - - def is_midwestern(self): - "Returns True if this person is from the Midwest." - return self.state in ('IL', 'WI', 'MI', 'IN', 'OH', 'IA', 'MO') + elif self.birth_date < datetime.date(1965, 1, 1): + return "Baby boomer" + else: + return "Post-boomer" def _get_full_name(self): "Returns the person's full name." return '%s %s' % (self.first_name, self.last_name) full_name = property(_get_full_name) + ``` -本例中最后一个方法是一个 属性(property). 了解属性详见这里。 +这个例子中的最后一个方法是一个[_property_](../../glossary.html#term-property)。 -在 model 实例参考(model instance reference) 中一个完整的方法列表 自动添加到每个 model 中的方法(methods automatically given to each model)。 你可以重写里面的大部分方法 -- 详见下面的 重写已定义的 model 方法(overriding predefined model methods),-- 但是有两个方法是经常要重写的: +[_模型实例参考_](../../ref/models/instances.html) 具有一个完整的[_为模型自动生成的方法_](../../ref/models/instances.html#model-instance-methods) 列表。你可以覆盖它们 —— 参见下文[覆盖模型预定义的方法](#overriding-predefined-model-methods) —— 但是有些方法你会始终想要重新定义: -**__unicode__()** +[`__str__()`](../../ref/models/instances.html#django.db.models.Model.__str__ "django.db.models.Model.__str__") (Python 3) -这是一个 Python 的魔术方法 ("magic method"),它返回对象的 Unicode 表示。当某个对象被要强制转换成字符串,或是要做为字符串显示时,Python 和 Django 就会调用该方法。最典型的,在命令行或管理后台中显示对象,就会用到 __unicode__() 方法。 +Python 3 equivalent of `__unicode__()`. -你应该总是自定义这个方法;该方法默认的实现没有什么用。 +[`__unicode__()`](../../ref/models/instances.html#django.db.models.Model.__unicode__ "django.db.models.Model.__unicode__") (Python 2) -**get_absolute_url()** +一个Python “魔法方法”,返回对象的Unicode “表示形式”。当模型实例需要强制转换并显示为普通的字符串时,Python 和Django 将使用这个方法。最明显是在交互式控制台或者管理站点显示一个对象的时候。 -Django 使用这个方法算出某个对象的网址(URL)。Django 在管理后台和任何需要得到对象网址的地方使用该方法。 +将将永远想要定义这个方法;默认的方法几乎没有意义。 -如果对象有一个唯一的网址,那么你就应该定义这个方法。 +[`get_absolute_url()`](../../ref/models/instances.html#django.db.models.Model.get_absolute_url "django.db.models.Model.get_absolute_url") -## 重写已定义的方法 ## +它告诉Django 如何计算一个对象的URL。Django 在它的管理站点中使用到这个方法,在其它任何需要计算一个对象的URL 时也将用到。 -还有另外一组 model 方法(model methods) 封装了你想定制的数据库的操作。有些情况下,你可能经常会改变 save() 和 delete() 的实现。 +任何具有唯一标识自己的URL 的对象都应该定义这个方法。 -你可以自由地重写这些方法 (以及任何其他的 model 方法) 来改变默认的实现。 +### 覆盖预定义的模型方法 -一个典型的重写内置方法的案例就是:在你保存对象时,触发某些操作。例如 (详见 save() 的参数说明): +还有另外一部分封装数据库行为的[_模型方法_](../../ref/models/instances.html#model-instance-methods),你可能想要自定义它们。特别是,你将要经常改变[`save()`](../../ref/models/instances.html#django.db.models.Model.save "django.db.models.Model.save") 和[`delete()`](../../ref/models/instances.html#django.db.models.Model.delete "django.db.models.Model.delete") 的工作方式。 + +你可以自由覆盖这些方法(和其它任何模型方法)来改变它们的行为。 + +覆盖内建模型方法的一个典型的使用场景是,你想在保存一个对象时做一些其它事情。例如(参见[`save()`](../../ref/models/instances.html#django.db.models.Model.save "django.db.models.Model.save") 中关于它接受的参数的文档): ``` +from django.db import models + class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() - def save(self, force_insert=False, force_update=False): + def save(self, *args, **kwargs): do_something() - super(Blog, self).save(force_insert, force_update) # Call the "real" save() method. + super(Blog, self).save(*args, **kwargs) # Call the "real" save() method. do_something_else() + ``` -你也可以阻止保存: +你还可以阻止保存: ``` +from django.db import models + class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() - def save(self, force_insert=False, force_update=False): + def save(self, *args, **kwargs): if self.name == "Yoko Ono's blog": return # Yoko shall never have her own blog! else: - super(Blog, self).save(force_insert, force_update) # Call the "real" save() method. + super(Blog, self).save(*args, **kwargs) # Call the "real" save() method. + ``` -别忘记调用父类的方法,这很重要 -- 上例中的父类方法是 super(Blog, self).save() ,它要做的就是确保将对象保存到数据库。如果忘记调用父类的方法,默认的行为就不会发生,也就不会对数据库进行操作。 +必须要记住调用超类的方法—— `super(Blog, self).save(*args, **kwargs)` —— 来确保对象被保存到数据库中。如果你忘记调用超类的这个方法,默认的行为将不会发生且数据库不会有任何改变。 + +还要记住传递参数给这个模型方法 —— 即`*args, **kwargs`。 Django 未来将一直会扩展内建模型方法的功能并添加新的参数。如果在你的方法定义中使用`*args, **kwargs`,将保证你的代码自动支持这些新的参数。 + +Overridden model methods are not called on bulk operations + +注意,当[_使用查询集批量删除对象时_](queries.html#topics-db-queries-delete),将不会为每个对象调用[`delete()`](../../ref/models/instances.html#django.db.models.Model.delete "django.db.models.Model.delete") 方法。为确保自定义的删除逻辑得到执行,你可以使用[`pre_delete`](../../ref/signals.html#django.db.models.signals.pre_delete "django.db.models.signals.pre_delete") 和/或[`post_delete`](../../ref/signals.html#django.db.models.signals.post_delete "django.db.models.signals.post_delete") 信号。 + +不幸的是,当批量[`creating`](../../ref/models/querysets.html#django.db.models.query.QuerySet.bulk_create "django.db.models.query.QuerySet.bulk_create") 或[`updating`](../../ref/models/querysets.html#django.db.models.query.QuerySet.update "django.db.models.query.QuerySet.update") 对象时没有变通方法,因为不会调用[`save()`](../../ref/models/instances.html#django.db.models.Model.save "django.db.models.Model.save")、[`pre_save`](../../ref/signals.html#django.db.models.signals.pre_save "django.db.models.signals.pre_save")和 [`post_save`](../../ref/signals.html#django.db.models.signals.post_save "django.db.models.signals.post_save")。 -## 运行定制的 SQL ## +### 执行自定义的SQL -另外一种常见的模式就是在 model 方法或是模块级(module-level)的方法中使用定制的 SQL 语句。想了解使用原始 SQL 的更多细节,请查看 使用原始 SQL (using raw SQL) 。 +另外一个常见的需求是在模型方法和模块级别的方法中编写自定义的SQL 语句。关于使用原始SQL 语句的更多细节,参见[_使用原始 SQL_](sql.html) 的文档。 -## Model 继承 ## +## 模型继承 -这部分是在 Django 1.0 中新增的: 请注意版本文档 -Django 中的 model 继承和 Python 中的类继承非常相似,只不过你要选择具体的实现方式:让父 model 拥有独立的数据库;还是让父 model 只包含基本的公共信息,由子 model 呈现公共信息。 +Django 中的模型继承与 Python 中普通类继承方式几乎完全相同,但是本页头部列出的模型基本的要求还是要遵守。这表示自定义的模型类应该继承[`django.db.models.Model`](../../ref/models/instances.html#django.db.models.Model "django.db.models.Model")。 -在 Django 中有三种继承方式: +你唯一需要作出的决定就是你是想让父模型具有它们自己的数据库表,还是让父模型只持有一些共同的信息而这些信息只有在子模型中才能看到。 -+ 通常,你只是想用父 model 来保存那些你不想在子 model 中重复录入的信息,父类并不单独使用。 抽象基类(Abstract base classes) 适用于这种情况。 -+ 如果你继承了某个已有的 model (可能是直接从其他应用中拿来的),并想让每个 model 都有自己的数据库。多表继承(Multi-table inheritance) 适用于这种情况。 -+ 最后,如果你只想在 model 中修改 Python-level 级的行为,而不涉及字段改变。 代理 model (Proxy models) 适用于这种场合。 +在Django 中有3中风格的继承。 -## 抽象基类 ## +1. 通常,你只想使用父类来持有一些信息,你不想在每个子模型中都敲一遍。这个类永远不会单独使用,所以你使用[_抽象基类_](#abstract-base-classes)。 +2. 如果你继承一个已经存在的模型且想让每个模型具有它自己的数据库表,那么应该使用[_多表继承_](#multi-table-inheritance)。 +3. 最后,如果你只是想改变模块Python 级别的行为,而不用修改模型的字段,你可以使用[_代理模型_](#proxy-models)。 -如果你想把某些公共信息添加到很多 model 中,抽象基类就显得非常有用。你编写完基类之后,在 Meta 内嵌类中设置 abstract=True ,该类就不能创建任何数据表。然而如果将它做为其他 model 的基类,那么该类的字段就会被添加到子类中。抽象基类和子类如果含有同名字段,就会导致错误(Django 将抛出异常)。 +### 抽象基类 -举个例子: +当你想将一些常见信息存储到很多model的时候,抽象化类是十分有用的。你编写完基类之后,在 [_Meta_](#meta-options)类中设置 `abstract=True` ,该类就不能创建任何数据表。取而代之的是,当它被用来作为一个其他model的基础类时,它将被加入那一子类中。如果抽象化基础类和它的子类有相同的项,那么将会出现error(并且Django将返回一个exception)。 + +一个例子 ``` +from django.db import models + class CommonInfo(models.Model): name = models.CharField(max_length=100) age = models.PositiveIntegerField() @@ -576,42 +673,53 @@ class CommonInfo(models.Model): class Student(CommonInfo): home_group = models.CharField(max_length=5) + ``` -学生(Student) model 会有三个字段: 姓名(name), 年龄(age) 和 分组(home_group)。 CommonInfo model 不能做为普通的 Django model 使用,因为它是一个抽象基类。他即不生成数据表,也没有 manager ,更不能直接被实例化和保存。 +`Student` 模型将有三个项:`name`, `age` 和 `home_group`。`CommonInfo` 模型无法像一般的Django模型一样使用,因为它是一个抽象化基础类。它无法生成数据表单或者管理器,并且不能实例化或者储存。 -对很多应用来说,这种继承方式正是你想要的。它提供一种在 Python 语言层级上提取公共信息的方式,但在数据库层级上,各个子类仍然只创建一个数据库。 +对很多用户来说, 这种类型的模型继承就是你想要的。它提供一种在 Python 语言层级上提取公共信息的方式,但在数据库层级上,各个子类仍然只创建一个数据库。 -## Meta 继承 ## +#### `元` 继承 -创建抽象基类的时候,Django 会将你在基类中所声明的有效的 Meta 内嵌类做为一个属性。如果子类没有声明它自己的 Meta 内嵌类,它就会继承父类的 Meta 。子类的 Meta 也可以直接继承父类的 Meta 内嵌类,对其进行扩展。例如: +当一个抽象类被创建的时候, Django会自动把你在基类中定义的 [_Meta_](#meta-options) 作为子类的一个属性。如果子类没有声明自己的[_Meta_](#meta-options) 类, 他将会继承父类的[_Meta_](#meta-options). 如果子类想要扩展父类 的,可以继承父类的 [_Meta_](#meta-options) 即可,例如 ``` +from django.db import models + class CommonInfo(models.Model): - ... + # ... class Meta: abstract = True ordering = ['name'] class Student(CommonInfo): - ... + # ... class Meta(CommonInfo.Meta): db_table = 'student_info' + ``` -继承时,Django 会对基类的 Meta 内嵌类做一个调整:在安装 Meta 属性之前,Django 会设置 abstract=False。 这意味着抽象基类的子类不会自动变成抽象类。当然,你可以让一个抽象类继承另一个抽象基类,不过每次都要显式地设置 abstract=True 。 +继承时,Django 会对基类的 [_Meta_](#meta-options)类做一个调整:在安装 [_Meta_](#meta-options)属性之前,Django 会设置 `abstract=False`。这意味着抽象基类的子类不会自动变成抽象类。 当然,你可以让一个抽象类继承另一个抽象基类,不过每次都要显式地设置 `abstract=True`。 -对于抽象基类而言,有些属性放在 Meta 内嵌类里面是没有意义的。例如,包含 db_table 将意味着所有的子类(是指那些没有指定自己的 Meta 内嵌类的子类)都使用同一张数据表,一般来说,这并不是我们想要的。 +对于抽象基类而言,有些属性放在 [_Meta_](#meta-options) 内嵌类里面是没有意义的。例如,包含 `db_table`将意味着所有的子类(是指那些没有指定自己的 [_Meta_](#meta-options) 类的子类)都使用同一张数据表,一般来说,这并不是我们想要的。 -## 小心使用 related_name ## +#### 小心使用 `related_name` -如果你在 ForeignKey 或 ManyToManyField 字段上使用 related_name 属性,你必须总是为该字段指定一个唯一的反向名称。但在抽象基类上这样做就会引发一个很严重的问题。因为 Django 会将基类字段添加到每个子类当中,而每个子类的字段属性值都完全相同 (这里面就包括 related_name)。注:这样每个子类的关联字段都会指向同一个字段。 +如果你在 `ForeignKey`或 `ManyToManyField`字段上使用 [`related_name`](../../ref/models/fields.html#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name")属性,你必须总是为该字段指定一个_唯一_的反向名称。但在抽象基类上这样做就会引发一个很严重的问题。因为 Django 会将基类字段添加到每个子类当中,而每个子类的字段属性值都完全相同 (这里面就包括[`related_name`](../../ref/models/fields.html#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name"))。 -当你在(且仅在)抽象基类中使用 related_name 时,如果想绕过这个问题,就要在属性值中包含 '%(class)s' 字符串。这个字符串会替换成字段所在子类的小写名称。因为每个子类的命名都不同,所以 related_name 也会不一样。例如: +当你在(且仅在)抽象基类中使用 [`related_name`](../../ref/models/fields.html#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name") 时,如果想绕过这个问题,名称中就要包含`'%(app_label)s'`和 `'%(class)s'`。 + +* `'%(class)s'` 会替换为子类的小写加下划线格式的名称,字段在子类中使用。 +* `'%(app_label)s'` 会替换为应用的小写加下划线格式的名称,应用包含子类。每个安装的应用名称都应该是唯一的,而且应用里每个模型类的名称也应该是唯一的,所以产生的名称应该彼此不同。 + +例如,假设有一个app叫做`common/models.py`: ``` +from django.db import models + class Base(models.Model): - m2m = models.ManyToManyField(OtherModel, related_name="%(class)s_related") + m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related") class Meta: abstract = True @@ -621,144 +729,184 @@ class ChildA(Base): class ChildB(Base): pass + +``` + +以及另一个应用 `rare/models.py`: + +``` +from common.models import Base + +class ChildB(Base): + pass + ``` -ChildA.m2m 字段的反向名称是 childa_related,而 ChildB.m2m 字段的反向名称是 childb_related。这取决于你如何使用 '%(class)s' 来构造你的反向名称。如果你没有这样做,Django 就会在验证 model (或运行 syncdb) 时抛出错误。 +ChildA.m2m 字段的反向名称是 childa_related,而 ChildB.m2m 字段的反向名称是 childb_related。这取决于你如何使用 `'%(class)s'` 和`'%(app_label)s`来构造你的反向名称。如果你没有这样做,Django 就会在验证 model (或运行 [`migrate`](../../ref/django-admin.html#django-admin-migrate)) 时抛出错误。 -如果你没有在抽象基类中为某个关联字段定义 related_name 属性,那么默认的反向名称就是子类名称加上 '_set',它能否正常工作取决于你是否在子类中定义了同名字段。例如,在上面的代码中,如果去掉 related_name 属性,在 ChildA 中,m2m 字段的反向名称就是 childa_set;而 ChildB 的 m2m 字段的反向名称就是 childb_set 。 +果你没有在抽象基类中为某个关联字段定义 [`related_name`](../../ref/models/fields.html#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name") 属性,那么默认的反向名称就是子类名称加上`'_set'`,它能否正常工作取决于你是否在子类中定义了同名字段。例如,在上面的代码中,如果去掉 [`related_name`](../../ref/models/fields.html#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name")属性,在 `ChildA`中,`m2m` 字段的反向名称就是 `childa_set`;而 `ChildB`的 m2m 字段的反向名称就是 `childb_set`。 -## 多表继承 ## +### 多表继承 -这是 Django 支持的第二种继承方式。使用这种继承方式时,同一层级下的每个子 model 都是一个真正意义上完整的 model 。每个子 model 都有专属的数据表,都可以查询和创建数据表。继承关系在子 model 和它的每个父类之间都添加一个链接 (通过一个自动创建的 OneToOneField 来实现)。 例如: +这是 Django 支持的第二种继承方式。使用这种继承方式时,同一层级下的每个子 model 都是一个真正意义上完整的 model 。 每个子 model 都有专属的数据表,都可以查询和创建数据表。 继承关系在子 model 和它的每个父类之间都添加一个链接 (通过一个自动创建的 [`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField")来实现)。 例如: ``` +from django.db import models + class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) class Restaurant(Place): - serves_hot_dogs = models.BooleanField() - serves_pizza = models.BooleanField() + serves_hot_dogs = models.BooleanField(default=False) + serves_pizza = models.BooleanField(default=False) + ``` -Place 里面的所有字段在 Restaurant 中也是有效的,只不过数据保存在另外一张数据表当中。所以下面两个语句都是可以运行的: +`Place`里面的所有字段在 `Restaurant`中也是有效的,只不过数据保存在另外一张数据表当中。所以下面两个语句都是可以运行的: ``` >>> Place.objects.filter(name="Bob's Cafe") >>> Restaurant.objects.filter(name="Bob's Cafe") + ``` -如果你有一个 Place,那么它同时也是一个 Restaurant, 那么你可以使用子 model 的小写形式从 Place 对象中获得与其对应的 Restaurant 对象: + +如果你有一个 `Place` ,那么它同时也是一个 `Restaurant`, 那么你可以使用子 model 的小写形式从 Pl`Place` 对象中获得与其对应的 `Restaurant`对象: ``` ->>> p = Place.objects.filter(name="Bob's Cafe") -# If Bob's Cafe is a Restaurant object, this will give the child class: +>>> p = Place.objects.get(id=12) +# If p is a Restaurant object, this will give the child class: >>> p.restaurant + ``` -但是,如果上例中的 p 并不是 Restaurant (比如它仅仅只是 Place 对象,或者它是其他类的父类),那么在引用 p.restaurant 就会抛开Restaurant.DoesNotExist 异常。 +但是,如果上例中的 `p` 并_不是_`Restaurant` (比如它仅仅只是 `Place`对象,或者它是其他类的父类),那么在引用 `p.restaurant`就会抛出`Restaurant.DoesNotExist` 异常。 -## 多表继承中的Meta ## +#### 多表继承中的`Meta` -在多表继承中,子类继承父类的 Meta 内嵌类是没什么意见的。所有的 Meta 选项已经对父类起了作用,再次使用只会起反作用。(这与使用抽象基类的情况正好相反,因为抽象基类并没有属于它自己的内容) +在多表继承中,子类继承父类的 [_Meta_](#meta-options)类是没什么意义的。所有的 [_Meta_](#meta-options) 选项已经对父类起了作用,再次使用只会起反作用。(这与使用抽象基类的情况正好相反,因为抽象基类并没有属于它自己的内容) -所以子 model 并不能访问它父类的 Meta 内嵌类。但是在某些受限的情况下,子类可以从父类继承某些 Meta :如果子类没有指定 django.db.models.Options.ordering 属性或 django.db.models.Options.get_latest_by 属性,它就会从父类中继承这些属性。 +所以子 model 并不能访问它父类的 [_Meta_](#meta-options) 类。但是在某些受限的情况下,子类可以从父类继承某些 Meta :如果子类没有指定 [`ordering`](../../ref/models/options.html#django.db.models.Options.ordering "django.db.models.Options.ordering")属性或 [`get_latest_by`](../../ref/models/options.html#django.db.models.Options.get_latest_by "django.db.models.Options.get_latest_by") 属性,它就会从父类中继承这些属性。 如果父类有了排序设置,而你并不想让子类有任何排序设置,你就可以显式地禁用排序: ``` class ChildModel(ParentModel): - ... + # ... class Meta: # Remove parent's ordering effect ordering = [] + ``` -## 继承与反向关联 ## +#### 继承与反向关联 -因为多表继承使用了一个隐含的 OneToOneField 来链接子类与父类,所以象上例那样,你可以用父类来指代子类。但是这个 OnetoOneField 字段默认的 related_name 值与 django.db.models.fields.ForeignKey 和 django.db.models.fields.ManyToManyField 默认的反向名称相同。如果你与其他 model 的子类做多对一或是多对多关系,你就必须在每个多对一和多对多字段上强制指定 related_name 。如果你没这么做,Django 就会在你运行 验证(validate) 或 同步数据库(syncdb) 时抛出异常。 +因为多表继承使用了一个隐含的 [`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField")来链接子类与父类,所以象上例那样,你可以用父类来指代子类。但是这个 OnetoOneField 字段默认的 [`related_name`](../../ref/models/fields.html#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name") 值与 [`ForeignKey`](../../ref/models/fields.html#django.db.models.ForeignKey "django.db.models.ForeignKey") 和 [`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField") 默认的反向名称相同。如果你与其他 model 的子类做多对一或是多对多关系,你就**必须**在每个多对一和多对多字段上强制指定 [`related_name`](../../ref/models/fields.html#django.db.models.ForeignKey.related_name "django.db.models.ForeignKey.related_name")。如果你没这么做,Django 就会在你运行 验证(validation) 时抛出异常。 -例如,仍以上面 Place 类为例,我们创建一个带有 ManyToManyField 字段的子类: +例如,仍以上面 `Place`类为例,我们创建一个带有 [`ManyToManyField`](../../ref/models/fields.html#django.db.models.ManyToManyField "django.db.models.ManyToManyField")字段的子类: ``` class Supplier(Place): - # Must specify related_name on all relations. - customers = models.ManyToManyField(Restaurant, related_name='provider') + customers = models.ManyToManyField(Place) + +``` + +这会产生一个错误: + +``` +Reverse query name for 'Supplier.customers' clashes with reverse query +name for 'Supplier.place_ptr'. + +HINT: Add or change a related_name argument to the definition for +'Supplier.customers' or 'Supplier.place_ptr'. + ``` -## 指定链接父类的字段 ## +像下面那样,向`customers`字段中添加`related_name`可以解决这个错误:`models.ManyToManyField(Place, related_name='provider')`。 + +#### 指定链接父类的字段 -之前我们提到,Django 会自动创建一个 OneToOneField 字段将子类链接至非抽象的父 model 。如果你想指定链接父类的属性名称,你可以创建你自己的 OneToOneField 字段并设置 parent_link=True ,从而使用该字段链接父类。 +之前我们提到,Django 会自动创建一个 [`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField")字段将子类链接至非抽象的父 model 。如果你想指定链接父类的属性名称,你可以创建你自己的 [`OneToOneField`](../../ref/models/fields.html#django.db.models.OneToOneField "django.db.models.OneToOneField")字段并设置 [`parent_link=True`](../../ref/models/fields.html#django.db.models.OneToOneField.parent_link "django.db.models.OneToOneField.parent_link") ,从而使用该字段链接父类。 -## 代理model ## +### 代理模型 -这部分是在 Django 1.1 中新增的: 请查看版本文档 -使用 多表继承(multi-table inheritance) 时,model 的每个子类都会创建一张新数据表,通常情况下,这正是我们想要的操作。这是因为子类需要一个空间来存储不包含在基类中的字段数据。但有时,你可能只想更改 model 在 Python 层的行为实现。比如:更改默认的 manager ,或是添加一个新方法。 +使用 [_多表继承_](#multi-table-inheritance)时,model 的每个子类都会创建一张新数据表,通常情况下,这正是我们想要的操作。这是因为子类需要一个空间来存储不包含在基类中的字段数据。 但有时,你可能只想更改 model 在 Python 层的行为实现。比如:更改默认的 manager ,或是添加一个新方法。 -而这,正是代理 model 继承方式要做的:为原始 model 创建一个代理(proxy)。你可以创建,删除,更新代理 model 的实例,而且所有的数据都可以象使用原始 model 一样被保存。不同之处在于:你可以在代理 model 中改变默认的排序设置和默认的 manager ,更不会对原始 model 产生影响。 +而这,正是代理 model 继承方式要做的:为原始 model 创建一个_代理_ 。你可以创建,删除,更新代理 model 的实例,而且所有的数据都可以象使用原始 model 一样被保存。 不同之处在于:你可以在代理 model 中改变默认的排序设置和默认的 manager ,更不会对原始 model 产生影响。 -声明代理 model 和声明普通 model 没有什么不同。设置Meta 内置类中 proxy 的值为 True,就完成了对代理 model 的声明。 +声明代理 model 和声明普通 model 没有什么不同。 设置`Meta`类中 [`proxy`](../../ref/models/options.html#django.db.models.Options.proxy "django.db.models.Options.proxy") 的值为 `True`,就完成了对代理 model 的声明。 -举个例子,假设你想给 Django 自带的标准 User model (它被用在你的模板中)添加一个方法: +举个例子,假设你想给 Django 自带的标准 `Person` model添加一个方法。你可以这样做: ``` -from django.contrib.auth.models import User +from django.db import models + +class Person(models.Model): + first_name = models.CharField(max_length=30) + last_name = models.CharField(max_length=30) -class MyUser(User): +class MyPerson(Person): class Meta: proxy = True def do_something(self): - ... + # ... + pass + ``` -MyUser 类和它的父类 User 操作同一个数据表。特别的是,User 的任何实例也可以通过 MyUser 访问,反之亦然: +`MyPerson`类和它的父类 `Person` 操作同一个数据表。特别的是,`Person` 的任何实例也可以通过 `MyPerson`访问,反之亦然: ``` ->>> u = User.objects.create(username="foobar") ->>> MyUser.objects.get(username="foobar") - +>>> p = Person.objects.create(first_name="foobar") +>>> MyPerson.objects.get(first_name="foobar") + + ``` -你也可以使用代理 model 给 model 定义不同的默认排序设置。Django 自带的 User model 没有定义排序设置(这是故意为之,是因为排序开销极大,我们不想在获取用户时浪费额外资源)。你可以利用代理对 username 属性进行排序,这很简单: +你也可以使用代理 model 给 model 定义不同的默认排序设置。 你可能并不想每次都给`Person`模型排序,但是使用代理的时候总是按照`last_name`属性排序。这非常容易: ``` -class OrderedUser(User): +class OrderedPerson(Person): class Meta: - ordering = ["username"] + ordering = ["last_name"] proxy = True -``` -普通的 User 查询,其结果是无序的;而 OrderedUser 查询的结果是按 username 排序。 +``` -查询集只返回请求时所使用的 model (Querysets still return the model that was requested) +现在,普通的`Person`查询时无序的,而 `OrderedPerson`查询会按照`last_name`排序。 -无论你何时查询 User 对象,Django 都不会返回 MyUser 对象。针对 User 对象的查询集只返回 User 对象。代理对象的精要就在于依赖原始 User 的代码仅对它自己有效,而你自己的代码就使用你扩展的内容。不管你怎么改动,都不会在查询 User 时得到 MyUser。 +#### 查询集始终返回请求的模型 -## 基类的限制 ## +也就是说,没有办法让DJango在查询`Person`对象时返回`MyPerson`对象。`Person` 对象的查询集会返回相同类型的对象。代理对象的要点是,依赖于原生`Person`对象的代码仍然使用它,而你可以使用你添加进来的扩展对象(它不会依赖其它任何代码)。而并不是将`Person`模型(或者其它)在所有地方替换为其它你自己创建的模型。 -代理 model 必须继承自一个非抽象基类。你不能继承自多个非抽象基类,这是因为一个代理 model 不能连接不同的数据表。代理 model 也可以继承任意多个抽象基类,但前提是它们没有定义任何 model 字段。 +#### 基类的限制 -代理 model 从非抽象基类中继承那些未在代理 model 定义的 Meta 选项。 +代理 模型必须继承自一个非抽象基类。 你不能继承自多个非抽象基类,这是因为一个代理 model 不能连接不同的数据表。 代理 model 也可以继承任意多个抽象基类,但前提是它们_没有_ 定义任何 model 字段。 -## 代理 model 的 manager ## +#### 代理模型的管理器 -如果你没有在代理 model 中定义任何 manager ,代理 model 就会从父类中继承 manager 。如果你在代理 model 中定义了一个 manager ,它就会变成默认的 manager ,不过定义在父类中的 manager 仍是有效的。 +如果你没有在代理 模型中定义任何 管理器 ,代理模型就会从父类中继承 管理器 。 如果你在代理 模型中定义了一个 管理器 ,它就会变成默认的管理器 ,不过定义在父类中的管理器仍然有效。 -继续上面的例子,你可以改变默认 manager,例如: +继续上面的例子,当你查询`Person`模型的时候,你可以改变默认 管理器,例如: ``` +from django.db import models + class NewManager(models.Manager): - ... + # ... + pass -class MyUser(User): +class MyPerson(Person): objects = NewManager() class Meta: proxy = True + ``` -如果你想给代理添加一个新的 manager ,却不想替换已有的默认 manager ,那么你可以参考 自定义 manager (custom manager) 中提到的方法:创建一个包含新 manager 的基类,然后放在主基类后面继承: +如果你想要向代理中添加新的管理器,而不是替换现有的默认管理器,你可以使用[_自定义管理器_](managers.html#custom-managers-and-inheritance)管理器文档中描述的技巧:创建一个含有新的管理器的基类,并且在主基类之后继承它: ``` # Create an abstract class for the new manager. @@ -768,39 +916,122 @@ class ExtraManagers(models.Model): class Meta: abstract = True -class MyUser(User, ExtraManagers): +class MyPerson(Person, ExtraManagers): class Meta: proxy = True + ``` 你可能不需要经常这样做,但这样做是可行的。 -## 代理 model 与非托管 model 之间的差异 ## +#### 代理 模型与非托管 模型之间的差异 -代理 model 继承看上去和使用 Meta 内嵌类中的 managed 属性的非托管 model 非常相似。但两者并不相同,你应当考虑选用哪种方案。 +代理 model 继承看上去和使用`Meta`类中的 [`managed`](../../ref/models/options.html#django.db.models.Options.managed "django.db.models.Options.managed") 属性的非托管 model 非常相似。但两者并不相同,你应当考虑选用哪种方案。 -一个不同之处是你可以在 Meta.managed=False 的 model 中定义字段(事实上,是必须指定,除非你真的想得到一个空 model )。在创建非托管 model 时要谨慎设置 Meta.db_table ,这是因为创建的非托管 model 映射某个已存在的 model ,并且有自己的方法。因此,如果你要保证这两个 model 同步并对程序进行改动,那么就会变得繁冗而脆弱。 +一个不同之处是你可以在`Meta.managed=False`的 model 中定义字段(事实上,是必须指定,除非你真的想得到一个空 model )。在创建非托管 model 时要谨慎设置[`Meta.db_table`](../../ref/models/options.html#django.db.models.Options.db_table "django.db.models.Options.db_table") ,这是因为创建的非托管 model 映射某个已存在的 model ,并且有自己的方法。因此,如果你要保证这两个 model 同步并对程序进行改动,那么就会变得繁冗而脆弱。 -另一个不同之处是两者对 manager 的处理方式不同。这对于代理 model 非常重要。代理 model 要与它所代理的 model 行为相似,所以代理 model 要继承父 model 的 managers ,包括它的默认 manager 。但在普通的多表继承中,子类不能继承父类的 manager ,这是因为在处理非基类字段时,父类的 manager 未必适用。在 manager documentation 有详细介绍。 +另一个不同之处是两者对 管理器的处理方式不同。 代理 model 要与它所代理的 model 行为相似,所以代理 model 要继承父 model 的 managers ,包括它的默认 manager 。 但在普通的多表继承中,子类不能继承父类的 manager ,这是因为在处理非基类字段时,父类的 manager 未必适用。 后一种情况在 [_管理器文档_](managers.html#custom-managers-and-inheritance)有详细介绍。 -我们实现了这两种特性(Meta.proxy和Meta.unmanaged)之后,曾尝试把两者结合到一起。结果证明,宏观的继承关系和微观的 manager 揉在一起,不仅导致 API 复杂难用,而且还难以理解。由于任何场合下都可能需要这两个选项,所以目前二者仍是各自独立使用的。 +我们实现了这两种特性之后,曾尝试把两者结合到一起。 结果证明,宏观的继承关系和微观的 管理器揉在一起,不仅导致 API 复杂难用,而且还难以理解。 由于任何场合下都可能需要这两个选项,所以目前二者仍是各自独立使用的。 所以,一般规则是: -如果你要镜像一个已有的 model 或数据表,且不想涉及所有的原始数据表的列,那就令 Meta.managed=False。通常情况下,对数据库视图创建 model 或是数据表不需要由 Django 控制时,就使用这个选项。 -如果你想对 model 做 Python 层级的改动,又想保留字段不变,那就令 Meta.proxy=True。因此在数据保存时,代理 model 相当于完全复制了原始 model 的存储结构。 -多重继承(Multiple inheritance) +1. 如果你要借鉴一个已有的 模型或数据表,且不想涉及所有的原始数据表的列,那就令 `Meta.managed=False`。通常情况下,对数据库视图创建 模型或是数据表不需要由 Django 控制时,就使用这个选项。 +2. 如果你想对 model 做 Python 层级的改动,又想保留字段不变,那就令 `Meta.proxy=True`。因此在数据保存时,代理 model 相当于完全复制了原始 模型的存储结构。 + +### 多重继承 + +就像Python的子类那样,DJango的模型可以继承自多个父类模型。切记一般的Python名称解析规则也会适用。出现特定名称的第一个基类(比如[_Meta_](#meta-options))是所使用的那个。例如,这意味着如果多个父类含有 [_Meta_](#meta-options)类,只有第一个会被使用,剩下的会忽略掉。 + +一般来说,你并不需要继承多个父类。多重继承主要对“mix-in”类有用:向每个继承mix-in的类添加一个特定的、额外的字段或者方法。你应该尝试将你的继承关系保持得尽可能简洁和直接,这样你就不必费很大力气来弄清楚某段特定的信息来自哪里。 + +Changed in Django 1.7\. + +Django 1.7之前,继承多个含有`id`主键字段的模型不会抛出异常,但是会导致数据丢失。例如,考虑这些模型(由于`id`字段的冲突,它们不再有效): + +``` +class Article(models.Model): + headline = models.CharField(max_length=50) + body = models.TextField() + +class Book(models.Model): + title = models.CharField(max_length=50) + +class BookReview(Book, Article): + pass + +``` + +这段代码展示了如何创建子类的对象,并覆写之前创建的父类对象中的值。 + +``` +>>> article = Article.objects.create(headline='Some piece of news.') +>>> review = BookReview.objects.create( +... headline='Review of Little Red Riding Hood.', +... title='Little Red Riding Hood') +>>> +>>> assert Article.objects.get(pk=article.pk).headline == article.headline +Traceback (most recent call last): + File "", line 1, in +AssertionError +>>> # the "Some piece of news." headline has been overwritten. +>>> Article.objects.get(pk=article.pk).headline +'Review of Little Red Riding Hood.' + +``` + +你可以在模型基类中使用显式的[`AutoField`](../../ref/models/fields.html#django.db.models.AutoField "django.db.models.AutoField")来合理使用多重继承: + +``` +class Article(models.Model): + article_id = models.AutoField(primary_key=True) + ... + +class Book(models.Model): + book_id = models.AutoField(primary_key=True) + ... + +class BookReview(Book, Article): + pass + +``` + +或者是使用一个公共的祖先来持有[`AutoField`](../../ref/models/fields.html#django.db.models.AutoField "django.db.models.AutoField"): + +``` +class Piece(models.Model): + pass + +class Article(Piece): + ... + +class Book(Piece): + ... + +class BookReview(Book, Article): + pass + +``` + +### Field name “hiding” is not permitted + +普通的 Python 类继承允许子类覆盖父类的任何属性。 但在 Django 中,重写 [`Field`](../../ref/models/fields.html#django.db.models.Field "django.db.models.Field")实例是不允许的(至少现在还不行)。如果基类中有一个 `author`字段,你就不能在子类中创建任何名为 `author`的字段。 -和 Python 一样,Django 的 model 也可以做多重继承。这里要记住 Python 的名称解析规则。如果某个特定名称 (例如,Meta) 出现在第一个基类当中,那么子类就会使用第一个基类的该特定名称。例如,如果多重父类都包含 Meta 内嵌类,只有第一个基类的 Meta 才会被使用,其他的都被会忽略。 +重写父类的字段会导致很多麻烦,比如:初始化实例(指定在 `Model.__init__` 中被实例化的字段) 和序列化。而普通的 Python 类继承机制并不能处理好这些特性。所以 Django 的继承机制被设计成与 Python 有所不同,这样做并不是随意而为的。 -一般来说,你没必要使用多重继承。多重继承常见是用在 "mix-in" (对Mixin不了解的,请参阅赖勇浩的文章http://blog.csdn.net/lanphaday/archive/2007/06/18/1656969.aspx):给继承自 mix-in的每个类添加某个特定的字段或方法。尽可能让继承结构简单直接,这样你就不必关注特定信息的来源。(注:这是说你不必花精力去穷究某个字段,属性,方法是从哪个父类继承的) +这些限制仅仅针对做为属性使用的 [`Field`](../../ref/models/fields.html#django.db.models.Field "django.db.models.Field")实例,并不是针对 Python 属性,Python 属性仍是可以被重写的。 在 Python 看来,上面的限制仅仅针对字段实例的名称:如果你手动指定了数据库的列名称,那么在多重继承中,你就可以在子类和某个祖先类当中使用同一个列名称。(因为它们使用的是两个不同数据表的字段)。 -## 不允许"隐藏"字段 ## +如果你在任何一个祖先类中重写了某个 model 字段,Django 都会抛出 [`FieldError`](../../ref/exceptions.html#django.core.exceptions.FieldError "django.core.exceptions.FieldError")异常。 -普通的 Python 类继承允许子类覆盖父类的任何属性。但在 Django 中,重写 Field 实例是不允许的(至少现在还不行)。如果基类中有一个 author 字段,你就不能在子类中创建任何名为 author 的字段。 +另见 -重写父类的字段会导致很多麻烦,比如:初始化实例(指定在 Model.__init__ 中被实例化的字段) 和序列化。而普通的 Python 类继承机制并不能处理好这些特性。所以 Django 的继承机制被设计成与 Python 有所不同,这样做并不是随意而为的。 +[_The Models Reference_](../../ref/models/index.html) -这些限制仅仅针对做为属性使用的 Field 实例,并不是针对 Python 属性,Python 属性仍是可以被重写的。在 Python 看来,上面的限制仅仅针对字段实例的名称:如果你手动指定了数据库的列名称,那么在多重继承中,你就可以在子类和某个祖先类当中使用同一个列名称。(因为它们使用的是两个不同数据表的字段)。 +Covers all the model related APIs including model fields, related +objects, and `QuerySet`. -如果你在任何一个祖先类中重写了某个 model 字段,Django 都会抛出 FieldError 异常。 \ No newline at end of file +> 译者:[Django 文档协作翻译小组](http://python.usyiyi.cn/django/index.html),原文:[Model syntax](https://docs.djangoproject.com/en/1.8/topics/db/models/)。 +> +> 本文以 [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/cn/) 协议发布,转载请保留作者署名和文章出处。 +> +> [Django 文档协作翻译小组](http://python.usyiyi.cn/django/index.html)人手紧缺,有兴趣的朋友可以加入我们,完全公益性质。交流群:467338606。 diff --git a/5_1_2_Form API.md b/5_1_2_Form API.md new file mode 100644 index 0000000..3c1ec03 --- /dev/null +++ b/5_1_2_Form API.md @@ -0,0 +1,956 @@ +# 表单 API + +关于这篇文档 + +这篇文档讲述Django 表单API 的详细细节。你应该先阅读[_表单简介_](../../topics/forms/index.html)。 + +## 绑定的表单和未绑定的表单 + +[`表单`](#django.forms.Form "django.forms.Form")要么是**绑定的**,要么是**未绑定的**。 + +* 如果是**绑定的**,那么它能够验证数据,并渲染表单及其数据成HTML。 +* 如果是**未绑定的**,那么它不能够完成验证(因为没有可验证的数据!),但是仍然能渲染空白的表单成HTML。 + +_class _`Form` + +若要创建一个未绑定的[`表单`](#django.forms.Form "django.forms.Form")实例,只需简单地实例化该类: + +``` +>>> f = ContactForm() + +``` + +若要绑定数据到表单,可以将数据以字典的形式传递给[`表单`](#django.forms.Form "django.forms.Form")类的构造函数的第一个参数: + +``` +>>> data = {'subject': 'hello', +... 'message': 'Hi there', +... 'sender': 'foo@example.com', +... 'cc_myself': True} +>>> f = ContactForm(data) + +``` + +在这个字典中,键为字段的名称,它们对应于[`表单`](#django.forms.Form "django.forms.Form")类中的属性。值为需要验证的数据。它们通常为字符串,但是没有强制要求必须是字符串;传递的数据类型取决于[`字段`](fields.html#django.forms.Field "django.forms.Field"),我们稍后会看到。 + +`Form.``is_bound` + +如果运行时刻你需要区分绑定的表单和未绑定的表单,可以检查下表单[`is_bound`](#django.forms.Form.is_bound "django.forms.Form.is_bound") 属性的值: + +``` +>>> f = ContactForm() +>>> f.is_bound +False +>>> f = ContactForm({'subject': 'hello'}) +>>> f.is_bound +True + +``` + +注意,传递一个空的字典将创建一个带有空数据的_绑定的_表单: + +``` +>>> f = ContactForm({}) +>>> f.is_bound +True + +``` + +如果你有一个绑定的[`表单`](#django.forms.Form "django.forms.Form")实例但是想改下数据,或者你想绑定一个未绑定的[`表单`](#django.forms.Form "django.forms.Form")表单到某些数据,你需要创建另外一个[`表单`](#django.forms.Form "django.forms.Form")实例。[`Form`](#django.forms.Form "django.forms.Form") 实例的数据没有办法修改。[`表单`](#django.forms.Form "django.forms.Form")实例一旦创建,你应该将它的数据视为不可变的,无论它有没有数据。 + +## 使用表单来验证数据 + +`Form.``clean`() + +当你需要为相互依赖的字段添加自定义的验证时,你可以实现`表单`的`clean()`方法。示例用法参见[_Cleaning and validating fields that depend on each other_](validation.html#validating-fields-with-clean)。 + +`Form.``is_valid`() + +[`表单`](#django.forms.Form "django.forms.Form")对象的首要任务就是验证数据。对于绑定的[`表单`](#django.forms.Form "django.forms.Form")实例,可以调用[`is_valid()`](#django.forms.Form.is_valid "django.forms.Form.is_valid")方法来执行验证并返回一个表示数据是否合法的布尔值。 + +``` +>>> data = {'subject': 'hello', +... 'message': 'Hi there', +... 'sender': 'foo@example.com', +... 'cc_myself': True} +>>> f = ContactForm(data) +>>> f.is_valid() +True + +``` + +让我们试下非法的数据。下面的情形中,`subject` 为空(默认所有字段都是必需的)且`sender` 是一个不合法的邮件地址: + +``` +>>> data = {'subject': '', +... 'message': 'Hi there', +... 'sender': 'invalid email address', +... 'cc_myself': True} +>>> f = ContactForm(data) +>>> f.is_valid() +False + +``` + +`Form.``errors` + +访问[`errors`](#django.forms.Form.errors "django.forms.Form.errors") 属性可以获得错误信息的一个字典: + +``` +>>> f.errors +{'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']} + +``` + +在这个字典中,键为字段的名称,值为表示错误信息的Unicode 字符串组成的列表。错误信息保存在列表中是因为字段可能有多个错误信息。 + +你可以在调用[`is_valid()`](#django.forms.Form.is_valid "django.forms.Form.is_valid") 之前访问[`errors`](#django.forms.Form.errors "django.forms.Form.errors")。表单的数据将在第一次调用[`is_valid()`](#django.forms.Form.is_valid "django.forms.Form.is_valid") 或者访问[`errors`](#django.forms.Form.errors "django.forms.Form.errors") 时验证。 + +验证将值调用一次,无论你访问[`errors`](#django.forms.Form.errors "django.forms.Form.errors") 或者调用[`is_valid()`](#django.forms.Form.is_valid "django.forms.Form.is_valid") 多少次。这意味着,如果验证过程有副作用,这些副作用将只触发一次。 + +`Form.errors.``as_data`() + +New in Django 1.7\. + +返回一个`字典`,它映射字段到原始的`ValidationError` 实例。 + +``` +>>> f.errors.as_data() +{'sender': [ValidationError(['Enter a valid email address.'])], +'subject': [ValidationError(['This field is required.'])]} + +``` + +每当你需要根据错误的`code` 来识别错误时,可以调用这个方法。它可以用来重写错误信息或者根据特定的错误编写自定义的逻辑。它还可以用来序列化错误为一个自定义的格式(例如,XML);[`as_json()`](#django.forms.Form.errors.as_json "django.forms.Form.errors.as_json") 就依赖于`as_data()`。 + +需要`as_data()` 方法是为了向后兼容。以前,`ValidationError` 实例在它们**渲染后** 的错误消息一旦添加到`Form.errors` 字典就立即被丢弃。理想情况下,`Form.errors` 应该已经保存`ValidationError` 实例而带有`as_` 前缀的方法可以渲染它们,但是为了不破坏直接使用`Form.errors` 中的错误消息的代码,必须使用其它方法来实现。 + +`Form.errors.``as_json`(_escape_html=False_) + +New in Django 1.7\. + +返回JSON 序列化后的错误。 + +``` +>>> f.errors.as_json() +{"sender": [{"message": "Enter a valid email address.", "code": "invalid"}], +"subject": [{"message": "This field is required.", "code": "required"}]} + +``` + +默认情况下,`as_json()` 不会转义它的输出。如果你正在使用AJAX 请求表单视图,而客户端会解析响应并将错误插入到页面中,你必须在客户端对结果进行转义以避免可能的跨站脚本攻击。使用一个JavaScript 库比如jQuery 来做这件事很简单 —— 只要使用`$(el).text(errorText)` 而不是`.html()` 就可以。 + +如果由于某种原因你不想使用客户端的转义,你还可以设置`escape_html=True`,这样错误消息将被转义而你可以直接在HTML 中使用它们。 + +`Form.``add_error`(_field_, _error_) + +New in Django 1.7\. + +这个方法允许在`Form.clean()` 方法内部或从表单的外部一起给字段添加错误信息;例如从一个视图中。 + +`field` 参数为字段的名称。如果值为`None`,error 将作为[`Form.non_field_errors()`](#django.forms.Form.non_field_errors "django.forms.Form.non_field_errors") 返回的一个非字段错误。 + +`error` 参数可以是一个简单的字符串,或者最好是一个`ValidationError` 实例。[_引发ValidationError_](validation.html#raising-validation-error) 中可以看到定义表单错误时的最佳实践。 + +注意,`Form.add_error()` 会自动删除`cleaned_data` 中的相关字段。 + +`Form.``has_error`(_field_, _code=None_) + +New in Django 1.8\. + +这个方法返回一个布尔值,指示一个字段是否具有指定错误`code` 的错误。当`code` 为`None` 时,如果字段有任何错误它都将返回`True`。 + +若要检查非字段错误,使用[`NON_FIELD_ERRORS`](../exceptions.html#django.core.exceptions.NON_FIELD_ERRORS "django.core.exceptions.NON_FIELD_ERRORS") 作为`field` 参数。 + +`Form.``non_field_errors`() + +这个方法返回[`Form.errors`](#django.forms.Form.errors "django.forms.Form.errors") 中不是与特定字段相关联的错误。它包含在[`Form.clean()`](#django.forms.Form.clean "django.forms.Form.clean") 中引发的`ValidationError` 和使用[`Form.add_error(None, "...")`](#django.forms.Form.add_error "django.forms.Form.add_error") 添加的错误。 + +### 未绑定表单的行为 + +验证没有绑定数据的表单是没有意义的,下面的例子展示了这种情况: + +``` +>>> f = ContactForm() +>>> f.is_valid() +False +>>> f.errors +{} + +``` + +## 动态的初始值 + +`Form.``initial` + +表单字段的初始值使用[`initial`](#django.forms.Form.initial "django.forms.Form.initial")声明。例如,你可能希望使用当前会话的用户名填充`username`字段。 + +使用[`Form`](#django.forms.Form "django.forms.Form")的[`initial`](#django.forms.Form.initial "django.forms.Form.initial")参数可以实现。该参数是字段名到初始值的一个字典。只需要包含你期望给出初始值的字段;不需要包含表单中的所有字段。例如: + +``` +>>> f = ContactForm(initial={'subject': 'Hi there!'}) + +``` + +这些值只显示在没有绑定的表单中,即使没有提供特定值它们也不会作为后备的值。 + +注意,如果[`字段`](fields.html#django.forms.Field "django.forms.Field")有定义[`initial`](#django.forms.Form.initial "django.forms.Form.initial"), _而_实例化`表单`时也提供`initial`,那么后面的`initial` 将优先。在下面的例子中,`initial` 在字段和表单实例化中都有定义,此时后者具有优先权: + +``` +>>> from django import forms +>>> class CommentForm(forms.Form): +... name = forms.CharField(initial='class') +... url = forms.URLField() +... comment = forms.CharField() +>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False) +>>> print(f) +Name: +Url: +Comment: + +``` + +## 检查表单数据是否改变 + +`Form.``has_changed`() + +当你需要检查表单的数据是否从初始数据发生改变时,可以使用`表单`的`has_changed()` 方法。 + +``` +>>> data = {'subject': 'hello', +... 'message': 'Hi there', +... 'sender': 'foo@example.com', +... 'cc_myself': True} +>>> f = ContactForm(data, initial=data) +>>> f.has_changed() +False + +``` + +当提交表单时,我们可以重新构建表单并提供初始值,这样可以实现比较: + +``` +>>> f = ContactForm(request.POST, initial=data) +>>> f.has_changed() + +``` + +如果`request.POST` 中的数据与[`initial`](#django.forms.Form.initial "django.forms.Form.initial") 中的不同,`has_changed()` 将为`True`,否则为`False`。 计算的结果是通过调用表单每个字段的[`Field.has_changed()`](fields.html#django.forms.Field.has_changed "django.forms.Field.has_changed") 得到的。 + +## 从表单中访问字段 + +`Form.``fields` + +你可以从[`表单`](#django.forms.Form "django.forms.Form")实例的`fields`属性访问字段: + +``` +>>> for row in f.fields.values(): print(row) +... + + + +>>> f.fields['name'] + + +``` + +可你可以修改[`表单`](#django.forms.Form "django.forms.Form")实例的字段来改变字段在表单中的表示: + +``` +>>> f.as_table().split('\n')[0] +'Name:' +>>> f.fields['name'].label = "Username" +>>> f.as_table().split('\n')[0] +'Username:' + +``` + +注意不要改变`base_fields` 属性,因为一旦修改将影响同一个Python 进程中接下来所有的`ContactForm` 实例: + +``` +>>> f.base_fields['name'].label = "Username" +>>> another_f = CommentForm(auto_id=False) +>>> another_f.as_table().split('\n')[0] +'Username:' + +``` + +## 访问“清洁”的数据 + +`Form.``cleaned_data` + +[`表单`](#django.forms.Form "django.forms.Form")类中的每个字段不仅负责验证数据,还负责“清洁”它们 —— 将它们转换为正确的格式。这是个非常好用的功能,因为它允许字段以多种方式输入数据,并总能得到一致的输出。 + +例如,[`DateField`](fields.html#django.forms.DateField "django.forms.DateField") 将输入转换为Python 的 `datetime.date` 对象。无论你传递的是`'1994-07-15'` 格式的字符串、`datetime.date` 对象、还是其它格式的数字,`DateField` 将始终将它们转换成`datetime.date` 对象,只要它们是合法的。 + +一旦你创建一个[`表单`](#django.forms.Form "django.forms.Form")实例并通过验证后,你就可以通过它的`cleaned_data` 属性访问清洁的数据: + +``` +>>> data = {'subject': 'hello', +... 'message': 'Hi there', +... 'sender': 'foo@example.com', +... 'cc_myself': True} +>>> f = ContactForm(data) +>>> f.is_valid() +True +>>> f.cleaned_data +{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'} + +``` + +注意,文本字段 —— 例如,`CharField` 和`EmailField` —— 始终将输入转换为Unicode 字符串。我们将在这篇文档的后面将是编码的影响。 + +如果你的数据_没有_ 通过验证,`cleaned_data` 字典中只包含合法的字段: + +``` +>>> data = {'subject': '', +... 'message': 'Hi there', +... 'sender': 'invalid email address', +... 'cc_myself': True} +>>> f = ContactForm(data) +>>> f.is_valid() +False +>>> f.cleaned_data +{'cc_myself': True, 'message': 'Hi there'} + +``` + +`cleaned_data` 始终_只_ 包含`表单`中定义的字段,即使你在构建`表单` 时传递了额外的数据。在下面的例子中,我们传递一组额外的字段给`ContactForm` 构造函数,但是`cleaned_data` 将只包含表单的字段: + +``` +>>> data = {'subject': 'hello', +... 'message': 'Hi there', +... 'sender': 'foo@example.com', +... 'cc_myself': True, +... 'extra_field_1': 'foo', +... 'extra_field_2': 'bar', +... 'extra_field_3': 'baz'} +>>> f = ContactForm(data) +>>> f.is_valid() +True +>>> f.cleaned_data # Doesn't contain extra_field_1, etc. +{'cc_myself': True, 'message': 'Hi there', 'sender': 'foo@example.com', 'subject': 'hello'} + +``` + +当`表单`合法时,`cleaned_data` 将包含_所有_字段的键和值,即使传递的数据不包含某些可选字段的值。在下面的例子中,传递的数据字典不包含`nick_name` 字段的值,但是`cleaned_data` 任然包含它,只是值为空: + +``` +>>> from django.forms import Form +>>> class OptionalPersonForm(Form): +... first_name = CharField() +... last_name = CharField() +... nick_name = CharField(required=False) +>>> data = {'first_name': 'John', 'last_name': 'Lennon'} +>>> f = OptionalPersonForm(data) +>>> f.is_valid() +True +>>> f.cleaned_data +{'nick_name': '', 'first_name': 'John', 'last_name': 'Lennon'} + +``` + +在上面的例子中,`cleaned_data` 中`nick_name` 设置为一个空字符串,这是因为`nick_name` 是`CharField`而 `CharField` 将空值作为一个空字符串。每个字段都知道自己的“空”值 —— 例如,`DateField` 的空值是`None` 而不是一个空字符串。关于每个字段空值的完整细节,参见“内建的`Field` 类”一节中每个字段的“空值”提示。 + +你可以自己编写代码来对特定的字段(根据它们的名字)或者表单整体(考虑到不同字段的组合)进行验证。更多信息参见[_表单和字段验证_](validation.html)。 + +## 输出表单为HTML + +`表单`对象的第二个任务是将它渲染成HTML。很简单,`print` 它: + +``` +>>> f = ContactForm() +>>> print(f) +Subject: +Message: +Sender: +Cc myself: + +``` + +如果表单是绑定的,输出的HTML 将包含数据。例如,如果字段是`` 的形式,其数据将位于`value` 属性中。如果字段是`` 的形式,HTML 将包含`checked="checked"`: + +``` +>>> data = {'subject': 'hello', +... 'message': 'Hi there', +... 'sender': 'foo@example.com', +... 'cc_myself': True} +>>> f = ContactForm(data) +>>> print(f) +Subject: +Message: +Sender: +Cc myself: + +``` + +默认的输出时具有两个列的HTML 表格,每个字段对应一个``。注意事项: + +* 为了灵活性,输出_不_包含`` 和``、`` 和`` 以及`` 标签。你需要添加它们。 +* 每个字段类型有一个默认的HTML 表示。`CharField` 表示为一个``,`EmailField` 表示为一个``。`BooleanField` 表示为一个``。注意,这些只是默认的表示;你可以使用Widget 指定字段使用哪种HTML,我们将稍后解释。 +* 每个标签的HTML `name` 直接从`ContactForm` 类中获取。 +* 每个字段的文本标签 —— 例如`'Subject:'`、`'Message:'` 和`'Cc myself:'` 通过将所有的下划线转换成空格并大写第一个字母生成。再次提醒,这些只是默认的表示;你可以手工指定标签。 +* 每个文本标签周围有一个HTML `` 标签,它指向表单字段的`id`。这个`id`,是通过在字段名称前面加上`'id_'` 前缀生成。`id` 属性和`` 标签默认包含在输出中,但你可以改变这一行为。 + +虽然`print` 表单时`` 是默认的输出格式,但是还有其它格式可用。每个格式对应于表单对象的一个方法,每个方法都返回一个Unicode 对象。 + +### as_p() + +`Form.``as_p`() + +`as_p()` 渲染表单为一系列的`` 标签,每个`` 标签包含一个字段: + +``` +>>> f = ContactForm() +>>> f.as_p() +'Subject: \nMessage: \nSender: \nCc myself: ' +>>> print(f.as_p()) +Subject: +Message: +Sender: +Cc myself: + +``` + +### as_ul() + +`Form.``as_ul`() + +`as_ul()` 渲染表单为一系列的``标签,每个`` 标签包含一个字段。它_不_包含`` 和``,所以你可以自己指定`` 的任何HTML 属性: + +``` +>>> f = ContactForm() +>>> f.as_ul() +'Subject: \nMessage: \nSender: \nCc myself: ' +>>> print(f.as_ul()) +Subject: +Message: +Sender: +Cc myself: + +``` + +### as_table() + +`Form.``as_table`() + +最后,`as_table()`输出表单为一个HTML ``。它与`print` 完全相同。事实上,当你`print` 一个表单对象时,在后台调用的就是`as_table()` 方法: + +``` +>>> f = ContactForm() +>>> f.as_table() +'Subject:\nMessage:\nSender:\nCc myself:' +>>> print(f.as_table()) +Subject: +Message: +Sender: +Cc myself: + +``` + +### 表单必填行和错误行的样式 + +`Form.``error_css_class` + +`Form.``required_css_class` + +将必填的表单行和有错误的表单行定义不同的样式特别常见。例如,你想将必填的表单行以粗体显示、将错误以红色显示。 + +[`表单`](#django.forms.Form "django.forms.Form")类具有一对钩子,可以使用它们来添加`class` 属性给必填的行或有错误的行:只需简单地设置[`Form.error_css_class`](#django.forms.Form.error_css_class "django.forms.Form.error_css_class") 和/或 [`Form.required_css_class`](#django.forms.Form.required_css_class "django.forms.Form.required_css_class") 属性: + +``` +from django.forms import Form + +class ContactForm(Form): + error_css_class = 'error' + required_css_class = 'required' + + # ... and the rest of your fields here + +``` + +一旦你设置好,将根据需要设置行的`"error"` 和/或`"required"` CSS 类型。 其HTML 看上去将类似: + +``` +>>> f = ContactForm(data) +>>> print(f.as_table()) +Subject: ... +Message: ... +Sender: ... +Cc myself: ... +>>> f['subject'].label_tag() +Subject: +>>> f['subject'].label_tag(attrs={'class': 'foo'}) +Subject: + +``` + +Changed in Django 1.8: + +`required_css_class` 添加到`` 标签,如上面所看到的。 + +### 配置表单元素的HTML `id` 属性和 `` 标签 + +`Form.``auto_id` + +默认情况下,表单的渲染方法包含: + +* 表单元素的HTML `id` 属性 +* 对应的`` 标签。HTML `` 标签指示标签文本关联的表单元素。这个小小的改进让表单在辅助设备上具有更高的可用性。使用`` 标签始终是个好想法。 + +`id` 属性值通过在表单字段名称的前面加上`id_` 生成。但是如果你想改变`id` 的生成方式或者完全删除 HTML `id` 属性和``标签,这个行为是可配置的。 + +`id` 和label 的行为使用`表单`构造函数的`auto_id` 参数控制。这个参数必须为`True`、`False` 或者一个字符串。 + +如果`auto_id` 为`False`,那么表单的输出将不包含`` 标签和`id` 属性: + +``` +>>> f = ContactForm(auto_id=False) +>>> print(f.as_table()) +Subject: +Message: +Sender: +Cc myself: +>>> print(f.as_ul()) +Subject: +Message: +Sender: +Cc myself: +>>> print(f.as_p()) +Subject: +Message: +Sender: +Cc myself: + +``` + +如果`auto_id` 设置为`True`,那么输出的表示_将_ 包含`` 标签并简单地使用字典名称作为每个表单字段的`id`: + +``` +>>> f = ContactForm(auto_id=True) +>>> print(f.as_table()) +Subject: +Message: +Sender: +Cc myself: +>>> print(f.as_ul()) +Subject: +Message: +Sender: +Cc myself: +>>> print(f.as_p()) +Subject: +Message: +Sender: +Cc myself: + +``` + +如果`auto_id` 设置为包含格式字符`'%s'` 的字符串,那么表单的输出将包含`` 标签,并将根据格式字符串生成`id` 属性。例如,对于格式字符串`'field_%s'`,名为`subject` 的字段的`id` 值将是`'field_subject'`。继续我们的例子: + +``` +>>> f = ContactForm(auto_id='id_for_%s') +>>> print(f.as_table()) +Subject: +Message: +Sender: +Cc myself: +>>> print(f.as_ul()) +Subject: +Message: +Sender: +Cc myself: +>>> print(f.as_p()) +Subject: +Message: +Sender: +Cc myself: + +``` + +如果`auto_id` 设置为任何其它的真值 —— 例如不包含`%s` 的字符串 —— 那么其行为将类似`auto_id` 等于`True`。 + +默认情况下,`auto_id` 设置为`'id_%s'`。 + +`Form.``label_suffix` + +一个字符串(默认为英文的`:`),表单渲染时将附加在每个label 名称的后面。 + +使用`label_suffix` 参数可以自定义这个字符,或者完全删除它: + +``` +>>> f = ContactForm(auto_id='id_for_%s', label_suffix='') +>>> print(f.as_ul()) +Subject +Message +Sender +Cc myself +>>> f = ContactForm(auto_id='id_for_%s', label_suffix=' ->') +>>> print(f.as_ul()) +Subject -> +Message -> +Sender -> +Cc myself -> + +``` + +注意,该标签后缀只有当label 的最后一个字符不是表单符号(`.`, `!`, `?` 和`:`)时才添加。 + +New in Django 1.8\. + +字段可以定义自己的[`label_suffix`](fields.html#django.forms.Field.label_suffix "django.forms.Field.label_suffix")。而且将优先于[`Form.label_suffix`](#django.forms.Form.label_suffix "django.forms.Form.label_suffix")。在运行时刻,后缀可以使用[`label_tag()`](#django.forms.BoundField.label_tag "django.forms.BoundField.label_tag") 的`label_suffix` 参数覆盖。 + +### 字段的顺序 + +在`as_p()`、`as_ul()` 和`as_table()` 中,字段以表单类中定义的顺序显示。例如,在`ContactForm` 示例中,字段定义的顺序为`subject`, `message`, `sender`, `cc_myself`。若要重新排序HTML 中的输出,只需改变字段在类中列出的顺序。 + +### 错误如何显示 + +如果你渲染一个绑定的`表单`对象,渲染时将自动运行表单的验证,HTML 输出将在出错字段的附近以`` 形式包含验证的错误。错误信息的位置与你使用的输出方法有关: + +``` +>>> data = {'subject': '', +... 'message': 'Hi there', +... 'sender': 'invalid email address', +... 'cc_myself': True} +>>> f = ContactForm(data, auto_id=False) +>>> print(f.as_table()) +Subject:This field is required. +Message: +Sender:Enter a valid email address. +Cc myself: +>>> print(f.as_ul()) +This field is required.Subject: +Message: +Enter a valid email address.Sender: +Cc myself: +>>> print(f.as_p()) +This field is required. +Subject: +Message: +Enter a valid email address. +Sender: +Cc myself: + +``` + +### 自定义错误清单的格式 + +默认情况下,表单使用`django.forms.utils.ErrorList` 来格式化验证时的错误。如果你希望使用另外一种类来显示错误,可以在构造时传递(在Python 2 中将 `__str__` 替换为`__unicode__`): + +``` +>>> from django.forms.utils import ErrorList +>>> class DivErrorList(ErrorList): +... def __str__(self): # __unicode__ on Python 2 +... return self.as_divs() +... def as_divs(self): +... if not self: return '' +... return '%s' % ''.join(['%s' % e for e in self]) +>>> f = ContactForm(data, auto_id=False, error_class=DivErrorList) +>>> f.as_p() +This field is required. +Subject: +Message: +Enter a valid email address. +Sender: +Cc myself: + +``` + +Changed in Django 1.7: + +`django.forms.util` 重命名为`django.forms.utils`。 + +### 更细粒度的输出 + +`as_p()`、`as_ul()` 和`as_table()` 方法是为懒惰的程序员准备的简单快捷方法 —— 它们不是显示表单的唯一方式。 + +_class _`BoundField` + +用于显示HTML 表单或者访问[`表单`](#django.forms.Form "django.forms.Form")实例的一个属性。 + +其`__str__()`(Python 2 上为`__unicode__`)方法显示该字段的HTML。 + +以字段的名称为键,用字典查询语法查询表单,可以获取一个 `BoundField`: + +``` +>>> form = ContactForm() +>>> print(form['subject']) + + +``` + +迭代表单可以获取所有的`BoundField`: + +``` +>>> form = ContactForm() +>>> for boundfield in form: print(boundfield) + + + + + +``` + +字段的输出与表单的`auto_id` 设置有关: + +``` +>>> f = ContactForm(auto_id=False) +>>> print(f['message']) + +>>> f = ContactForm(auto_id='id_%s') +>>> print(f['message']) + + +``` + +若要获取字段的错误列表,可以访问字段的`errors` 属性。 + +`BoundField.``errors` + +一个类列表对象,打印时以HTML `` 形式显示: + +``` +>>> data = {'subject': 'hi', 'message': '', 'sender': '', 'cc_myself': ''} +>>> f = ContactForm(data, auto_id=False) +>>> print(f['message']) + +>>> f['message'].errors +['This field is required.'] +>>> print(f['message'].errors) +This field is required. +>>> f['subject'].errors +[] +>>> print(f['subject'].errors) + +>>> str(f['subject'].errors) +'' + +``` + +`BoundField.``label_tag`(_contents=None_, _attrs=None_, _label_suffix=None_) + +可以调用`label_tag` 方法单独渲染表单字段的label 标签: + +``` +>>> f = ContactForm(data) +>>> print(f['message'].label_tag()) +Message: + +``` + +如果你提供一个可选的`contents` 参数,它将替换自动生成的label 标签。另外一个可选的`attrs` 参数可以包含`` 标签额外的属性。 + +生成的HTML 包含表单的[`label_suffix`](#django.forms.Form.label_suffix "django.forms.Form.label_suffix")(默认为一个冒号),或者当前字段的[`label_suffix`](fields.html#django.forms.Field.label_suffix "django.forms.Field.label_suffix")。可选的`label_suffix` 参数允许你覆盖之前设置的后缀。例如,你可以使用一个空字符串来隐藏已选择字段的label。如果在模板中需要这样做,你可以编写一个自定义的过滤器来允许传递参数给`label_tag`。 + +Changed in Django 1.8: + +如果可用,label 将包含[`required_css_class`](#django.forms.Form.required_css_class "django.forms.Form.required_css_class")。 + +`BoundField.``css_classes`() + +当你使用Django 的快捷的渲染方法时,习惯使用CSS 类型来表示必填的表单字段和有错误的字段。如果你是手工渲染一个表单,你可以使用`css_classes` 方法访问这些CSS 类型: + +``` +>>> f = ContactForm(data) +>>> f['message'].css_classes() +'required' + +``` + +除了错误和必填的类型之外,如果你还想提供额外的类型,你可以用参数传递它们: + +``` +>>> f = ContactForm(data) +>>> f['message'].css_classes('foo bar') +'foo bar required' + +``` + +`BoundField.``value`() + +这个方法用于渲染字段的原始值,与用`Widget` 渲染的值相同: + +``` +>>> initial = {'subject': 'welcome'} +>>> unbound_form = ContactForm(initial=initial) +>>> bound_form = ContactForm(data, initial=initial) +>>> print(unbound_form['subject'].value()) +welcome +>>> print(bound_form['subject'].value()) +hi + +``` + +`BoundField.``id_for_label` + +使用这个属性渲染字段的ID。例如,如果你在模板中手工构造一个``(尽管 [`label_tag()`](#django.forms.BoundField.label_tag "django.forms.BoundField.label_tag") 将为你这么做): + +``` +...{{ my_field }} + +``` + +默认情况下,它是在字段名称的前面加上`id_` (上面的例子中将是“`id_my_field`”)。你可以通过设置字段Widget 的[`attrs`](widgets.html#django.forms.Widget.attrs "django.forms.Widget.attrs") 来修改ID。例如,像这样声明一个字段: + +``` +my_field = forms.CharField(widget=forms.TextInput(attrs={'id': 'myFIELD'})) + +``` + +使用上面的模板,将渲染成: + +``` +... + +``` + +## 绑定上传的文件到表单 + +处理带有`FileField` 和`ImageField` 字段的表单比普通的表单要稍微复杂一点。 + +首先,为了上传文件,你需要确保你的`` 元素正确定义`enctype` 为`"multipart/form-data"`: + +``` + + +``` + +其次,当你使用表单时,你需要绑定文件数据。文件数据的处理与普通的表单数据是分开的,所以如果表单包含`FileField` 和`ImageField`,绑定表单时你需要指定第二个参数。所以,如果我们扩展ContactForm 并包含一个名为`mugshot` 的`ImageField`,我们需要绑定包含mugshot 图片的文件数据: + +``` +# Bound form with an image field +>>> from django.core.files.uploadedfile import SimpleUploadedFile +>>> data = {'subject': 'hello', +... 'message': 'Hi there', +... 'sender': 'foo@example.com', +... 'cc_myself': True} +>>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', )} +>>> f = ContactFormWithMugshot(data, file_data) + +``` + +实际上,你一般将使用`request.FILES` 作为文件数据的源(和使用`request.POST` 作为表单数据的源一样): + +``` +# Bound form with an image field, data from the request +>>> f = ContactFormWithMugshot(request.POST, request.FILES) + +``` + +构造一个未绑定的表单和往常一样 —— 将表单数据_和_文件数据同时省略: + +``` +# Unbound form with an image field +>>> f = ContactFormWithMugshot() + +``` + +### 测试multipart 表单 + +`Form.``is_multipart`() + +如果你正在编写可重用的视图或模板,你可能事先不知道你的表单是否是一个multipart 表单。`is_multipart()` 方法告诉你表单提交时是否要求multipart: + +``` +>>> f = ContactFormWithMugshot() +>>> f.is_multipart() +True + +``` + +下面是如何在模板中使用它的一个示例: + +``` +{% if form.is_multipart %} + +{% else %} + +{% endif %} +{{ form }} + + +``` + +## 子类化表单 + +如果你有多个`表单`类共享相同的字段,你可以使用子类化来减少冗余。 + +当你子类化一个自定义的`表单`类时,生成的子类将包含父类中的所有字段,以及在子类中定义的字段。 + +在下面的例子中,`ContactFormWithPriority` 包含`ContactForm` 中的所有字段,以及另外一个字段`priority`。排在前面的是`ContactForm` 中的字段: + +``` +>>> class ContactFormWithPriority(ContactForm): +... priority = forms.CharField() +>>> f = ContactFormWithPriority(auto_id=False) +>>> print(f.as_ul()) +Subject: +Message: +Sender: +Cc myself: +Priority: + +``` + +可以子类化多个表单,将表单作为“mix-ins”。在下面的例子中,`BeatleForm` 子类化`PersonForm` 和 `InstrumentForm` ,所以它的字段列表包含两个父类的所有字段: + +``` +>>> from django.forms import Form +>>> class PersonForm(Form): +... first_name = CharField() +... last_name = CharField() +>>> class InstrumentForm(Form): +... instrument = CharField() +>>> class BeatleForm(PersonForm, InstrumentForm): +... haircut_type = CharField() +>>> b = BeatleForm(auto_id=False) +>>> print(b.as_ul()) +First name: +Last name: +Instrument: +Haircut type: + +``` + +New in Django 1.7\. +* 在子类中,可以通过设置名字为`None` 来删除从父类中继承的`字段`。例如: + + ``` + >>> from django import forms + + >>> class ParentForm(forms.Form): + ... name = forms.CharField() + ... age = forms.IntegerField() + + >>> class ChildForm(ParentForm): + ... name = None + + >>> ChildForm().fields.keys() + ... ['age'] + + ``` + +## 表单前缀 + +`Form.``prefix` + +你可以将几个Django 表单放在一个`` 标签中。为了给每个`表单`一个自己的命名空间,可以使用`prefix` 关键字参数: + +``` +>>> mother = PersonForm(prefix="mother") +>>> father = PersonForm(prefix="father") +>>> print(mother.as_ul()) +First name: +Last name: +>>> print(father.as_ul()) +First name: +Last name: + +``` + +> 译者:[Django 文档协作翻译小组](http://python.usyiyi.cn/django/index.html),原文:[Form API](https://docs.djangoproject.com/en/1.8/ref/forms/api/)。 +> +> 本文以 [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/cn/) 协议发布,转载请保留作者署名和文章出处。 +> +> [Django 文档协作翻译小组](http://python.usyiyi.cn/django/index.html)人手紧缺,有兴趣的朋友可以加入我们,完全公益性质。交流群:467338606。 diff --git a/5_1_4_Built-in widgets.md b/5_1_4_Built-in widgets.md new file mode 100644 index 0000000..e7334bc --- /dev/null +++ b/5_1_4_Built-in widgets.md @@ -0,0 +1,639 @@ +# Widgets + +Widget 是Django 对HTML 输入元素的表示。Widget 负责渲染HTML和提取GET/POST 字典中的数据。 + +小贴士 + +不要将Widget 与[_表单字段_](fields.html)搞混淆。表单字段负责验证输入并直接在模板中使用。Widget 负责渲染网页上HTML 表单的输入元素和提取提交的原始数据。但是,Widget 需要[_赋值_](#widget-to-field)给表单的字段。 + +## 指定Widget + +每当你指定表单的一个字段的时候,Django 将使用适合其数据类型的默认Widget。若要查找每个字段使用的Widget,参见[_内建的字段_](fields.html#built-in-fields)文档。 + +然而,如果你想要使用一个不同的Widget,你可以在定义字段时使用[`widget`](fields.html#django.forms.Field.widget "django.forms.Field.widget") 参数。例如: + +``` +from django import forms + +class CommentForm(forms.Form): + name = forms.CharField() + url = forms.URLField() + comment = forms.CharField(widget=forms.Textarea) + +``` + +这将使用一个[`Textarea`](#django.forms.Textarea "django.forms.Textarea") Widget来设置表单的评论 ,而不是默认的[`TextInput`](#django.forms.TextInput "django.forms.TextInput") Widget。 + +## 设置Widget 的参数 + +很多Widget 都有可选的参数;它们可以在定义字段的Widget 时设置。在下面的示例中,设置了[`SelectDateWidget`](#django.forms.extras.widgets.SelectDateWidget "django.forms.extras.widgets.SelectDateWidget") 的[`years`](#django.forms.extras.widgets.SelectDateWidget.years "django.forms.extras.widgets.SelectDateWidget.years") 属性: + +``` +from django import forms +from django.forms.extras.widgets import SelectDateWidget + +BIRTH_YEAR_CHOICES = ('1980', '1981', '1982') +FAVORITE_COLORS_CHOICES = (('blue', 'Blue'), + ('green', 'Green'), + ('black', 'Black')) + +class SimpleForm(forms.Form): + birth_year = forms.DateField(widget=SelectDateWidget(years=BIRTH_YEAR_CHOICES)) + favorite_colors = forms.MultipleChoiceField(required=False, + widget=forms.CheckboxSelectMultiple, choices=FAVORITE_COLORS_CHOICES) + +``` + +可用的Widget 以及它们接收的参数,参见[_内建的Widget_](#built-in-widgets)。 + +## 继承自Select 的Widget + +继承自[`Select`](#django.forms.Select "django.forms.Select") 的Widget 负责处理HTML 选项。它们呈现给用户一个可以选择的选项列表。不同的Widget 以不同的方式呈现选项;[`Select`](#django.forms.Select "django.forms.Select") 使用HTML 的列表形式``,而[`RadioSelect`](#django.forms.RadioSelect "django.forms.RadioSelect") 使用单选按钮。 + +[`ChoiceField`](fields.html#django.forms.ChoiceField "django.forms.ChoiceField") 字段默认使用[`Select`](#django.forms.Select "django.forms.Select")。Widget 上显示的选项来自[`ChoiceField`](fields.html#django.forms.ChoiceField "django.forms.ChoiceField"),对[`ChoiceField.choices`](fields.html#django.forms.ChoiceField.choices "django.forms.ChoiceField.choices") 的改变将更新[`Select.choices`](#django.forms.Select.choices "django.forms.Select.choices")。例如: + +``` +>>> from django import forms +>>> CHOICES = (('1', 'First',), ('2', 'Second',)) +>>> choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES) +>>> choice_field.choices +[('1', 'First'), ('2', 'Second')] +>>> choice_field.widget.choices +[('1', 'First'), ('2', 'Second')] +>>> choice_field.widget.choices = () +>>> choice_field.choices = (('1', 'First and only',),) +>>> choice_field.widget.choices +[('1', 'First and only')] + +``` + +提供[`choices`](#django.forms.Select.choices "django.forms.Select.choices") 属性的Widget 也可以用于不是基于选项的字段 , 例如[`CharField`](fields.html#django.forms.CharField "django.forms.CharField") —— 当选项与模型有关而不只是Widget 时,建议使用基于[`ChoiceField`](fields.html#django.forms.ChoiceField "django.forms.ChoiceField") 的字段。 + +## 自定义Widget 的实例 + +当Django 渲染Widget 成HTML 时,它只渲染最少的标记 —— Django 不会添加class 的名称和特定于Widget 的其它属性。这表示,网页上所有[`TextInput`](#django.forms.TextInput "django.forms.TextInput") 的外观是一样的。 + +有两种自定义Widget 的方式:基于每个[_Widget 实例_](#styling-widget-instances)和基于每个[_Widget 类_](#styling-widget-classes)。 + +### 设置Widget 实例的样式 + +如果你想让某个Widget 实例与其它Widget 看上去不一样,你需要在Widget 对象实例化并赋值给一个表单字段时指定额外的属性(以及可能需要在你的CSS 文件中添加一些规则)。 + +例如下面这个简单的表单: + +``` +from django import forms + +class CommentForm(forms.Form): + name = forms.CharField() + url = forms.URLField() + comment = forms.CharField() + +``` + +这个表单包含三个默认的[`TextInput`](#django.forms.TextInput "django.forms.TextInput") Widget,以默认的方式渲染 —— 没有CSS 类、没有额外的属性。这表示每个Widget 的输入框将渲染得一模一样: + +``` +>>> f = CommentForm(auto_id=False) +>>> f.as_table() +Name: +Url: +Comment: + +``` + +在真正得网页中,你可能不想让每个Widget 看上去都一样。你可能想要给comment 一个更大的输入元素,你可能想让‘name’ Widget 具有一些特殊的CSS 类。可以指定‘type’ 属性来利用新式的HTML5 输入类型。在创建Widget 时使用[`Widget.attrs`](#django.forms.Widget.attrs "django.forms.Widget.attrs") 参数可以实现: + +``` +class CommentForm(forms.Form): + name = forms.CharField(widget=forms.TextInput(attrs={'class': 'special'})) + url = forms.URLField() + comment = forms.CharField(widget=forms.TextInput(attrs={'size': '40'})) + +``` + +Django 将在渲染的输出中包含额外的属性: + +``` +>>> f = CommentForm(auto_id=False) +>>> f.as_table() +Name: +Url: +Comment: + +``` + +你还可以使用[`attrs`](#django.forms.Widget.attrs "django.forms.Widget.attrs") 设置HTML `id`。参见[`BoundField.id_for_label`](api.html#django.forms.BoundField.id_for_label "django.forms.BoundField.id_for_label") 示例。 + +### 设置Widget 类的样式 + +可以添加(`css` 和`javascript`)给Widget,以及深度定制它们的外观和行为。 + +概况来讲,你需要子类化Widget 并[_定义一个“Media” 内联类_](../../topics/forms/media.html#assets-as-a-static-definition) 或 [_创建一个“media” 属性_](../../topics/forms/media.html#dynamic-property)。 + +这些方法涉及到Python 高级编程,详细细节在[_表单Assets_](../../topics/forms/media.html) 主题中讲述。 + +## Widget 的基类 + +[`Widget`](#django.forms.Widget "django.forms.Widget") 和[`MultiWidget`](#django.forms.MultiWidget "django.forms.MultiWidget") 是所有[_内建Widget_](#built-in-widgets) 的基类,并可用于自定义Widget 的基类。 + +_class _`Widget`(_attrs=None_) + +这是个抽象类,它不可以渲染,但是提供基本的属性[`attrs`](#django.forms.Widget.attrs "django.forms.Widget.attrs")。你可以在自定义的Widget 中实现或覆盖[`render()`](#django.forms.Widget.render "django.forms.Widget.render") 方法。 + +`attrs` + +包含渲染后的Widget 将要设置的HTML 属性。 + +``` +>>> from django import forms +>>> name = forms.TextInput(attrs={'size': 10, 'title': 'Your name',}) +>>> name.render('name', 'A name') +'' + +``` + +Changed in Django 1.8: + +如果你给一个属性赋值`True` 或`False`,它将渲染成一个HTML5 风格的布尔属性: + +``` +>>> name = forms.TextInput(attrs={'required': True}) +>>> name.render('name', 'A name') +'' +>>> +>>> name = forms.TextInput(attrs={'required': False}) +>>> name.render('name', 'A name') +'' + +``` + +`render`(_name_, _value_, _attrs=None_) + +返回Widget 的HTML,为一个Unicode 字符串。子类必须实现这个方法,否则将引发`NotImplementedError`。 + +它不会确保给出的‘value’ 是一个合法的输入,因此子类的实现应该防卫式地编程。 + +`value_from_datadict`(_data_, _files_, _name_) + +根据一个字典和该Widget 的名称,返回该Widget 的值。`files` may contain data coming from [`request.`](../request-response.html#django.http.HttpRequest.FILES "django.http.HttpRequest.FILES")FILES. 如果没有提供value,则返回`None`。 在处理表单数据的过程中,`value_from_datadict` 可能调用多次,所以如果你自定义并添加额外的耗时处理时,你应该自己实现一些缓存机制。 + +_class _`MultiWidget`(_widgets_, _attrs=None_) + +由多个Widget 组合而成的Widget。[`MultiWidget`](#django.forms.MultiWidget "django.forms.MultiWidget") 始终与[`MultiValueField`](fields.html#django.forms.MultiValueField "django.forms.MultiValueField") 联合使用。 + +[`MultiWidget`](#django.forms.MultiWidget "django.forms.MultiWidget") 具有一个必选参数: + +`widgets` + +一个包含需要的Widget 的可迭代对象。 + +以及一个必需的方法: + +`decompress`(_value_) + +这个方法接受来自字段的一个“压缩”的值,并返回“解压”的值的一个列表。可以假设输入的值是合法的,但不一定是非空的。 + +子类**必须实现** 这个方法,而且因为值可能为空,实现必须要防卫这点。 + +“解压”的基本原理是需要“分离”组合的表单字段的值为每个Widget 的值。 + +有个例子是,[`SplitDateTimeWidget`](#django.forms.SplitDateTimeWidget "django.forms.SplitDateTimeWidget") 将[`datetime`](https://docs.python.org/3/library/datetime.html#datetime.datetime "(in Python v3.4)") 值分离成两个独立的值分别表示日期和时间: + +``` +from django.forms import MultiWidget + +class SplitDateTimeWidget(MultiWidget): + + # ... + + def decompress(self, value): + if value: + return [value.date(), value.time().replace(microsecond=0)] + return [None, None] + +``` + +小贴士 + +注意,[`MultiValueField`](fields.html#django.forms.MultiValueField "django.forms.MultiValueField") 有一个[`compress()`](fields.html#django.forms.MultiValueField.compress "django.forms.MultiValueField.compress") 方法用于相反的工作 —— 将所有字段的值组合成一个值。 + +其它可能需要覆盖的方法: + +`render`(_name_, _value_, _attrs=None_) + +这个方法中的 `value`参数的处理方式与[`Widget`](#django.forms.Widget "django.forms.Widget")子类不同,因为需要弄清楚如何为了在不同widget中展示分割单一值。 + +渲染中使用的`value`参数可以是二者之一: + +* 一个`列表`。 +* 一个单一值(比如字符串),它是`列表`的“压缩”表现形式。 + +如果`value`是个列表,[`render()`](#django.forms.MultiWidget.render "django.forms.MultiWidget.render")的输出会是一系列渲染后的子widget。如果`value`不是一个列表,首先会通过[`decompress()`](#django.forms.MultiWidget.decompress "django.forms.MultiWidget.decompress")方法来预处理,创建列表,之后再渲染。 + +`render()`方法执行HTML渲染时,列表中的每个值都使用相应的widget来渲染 -- 第一个值在第一个widget中渲染,第二个值在第二个widget中渲染,以此类推。 + +不像单一值的widget,[`render()`](#django.forms.MultiWidget.render "django.forms.MultiWidget.render") 方法并不需要在子类中实现。 + +`format_output`(_rendered_widgets_) + +接受选然后的widget(以字符串形式)的一个列表,返回表示全部HTML的Unicode字符串。 + +这个钩子允许你以任何你想要的方式,格式化widget的HTML设计。 + +下面示例中的Widget 继承[`MultiWidget`](#django.forms.MultiWidget "django.forms.MultiWidget") 以在不同的选择框中显示年、月、日。这个Widget 主要想用于[`DateField`](fields.html#django.forms.DateField "django.forms.DateField") 而不是[`MultiValueField`](fields.html#django.forms.MultiValueField "django.forms.MultiValueField"),所以我们实现了[`value_from_datadict()`](#django.forms.Widget.value_from_datadict "django.forms.Widget.value_from_datadict"): + +``` +from datetime import date +from django.forms import widgets + +class DateSelectorWidget(widgets.MultiWidget): + def __init__(self, attrs=None): + # create choices for days, months, years + # example below, the rest snipped for brevity. + years = [(year, year) for year in (2011, 2012, 2013)] + _widgets = ( + widgets.Select(attrs=attrs, choices=days), + widgets.Select(attrs=attrs, choices=months), + widgets.Select(attrs=attrs, choices=years), + ) + super(DateSelectorWidget, self).__init__(_widgets, attrs) + + def decompress(self, value): + if value: + return [value.day, value.month, value.year] + return [None, None, None] + + def format_output(self, rendered_widgets): + return ''.join(rendered_widgets) + + def value_from_datadict(self, data, files, name): + datelist = [ + widget.value_from_datadict(data, files, name + '_%s' % i) + for i, widget in enumerate(self.widgets)] + try: + D = date(day=int(datelist[0]), month=int(datelist[1]), + year=int(datelist[2])) + except ValueError: + return '' + else: + return str(D) + +``` + +构造器在一个元组中创建了多个[`Select`](#django.forms.Select "django.forms.Select") widget。`超`类使用这个元组来启动widget。 + +[`format_output()`](#django.forms.MultiWidget.format_output "django.forms.MultiWidget.format_output")方法相当于在这里没有干什么新的事情(实际上,它和`MultiWidget`中默认实现的东西相同),但是这个想法是,你可以以自己的方式在widget之间添加自定义的HTML。 + +必需的[`decompress()`](#django.forms.MultiWidget.decompress "django.forms.MultiWidget.decompress")方法将`datetime.date` 值拆成年、月和日的值,对应每个widget。注意这个方法如何处理`value`为`None`的情况。 + +[`value_from_datadict()`](#django.forms.Widget.value_from_datadict "django.forms.Widget.value_from_datadict")的默认实现会返回一个列表,对应每一个`Widget`。当和[`MultiValueField`](fields.html#django.forms.MultiValueField "django.forms.MultiValueField")一起使用`MultiWidget`的时候,这样会非常合理,但是由于我们想要和拥有单一值得[`DateField`](fields.html#django.forms.DateField "django.forms.DateField")一起使用这个widget,我们必须覆写这一方法,将所有子widget的数据组装成`datetime.date`。这个方法从`POST` 字典中获取数据,并且构造和验证日期。如果日期有效,会返回它的字符串,否则会返回一个空字符串,它会使`form.is_valid`返回`False`。 + +## 内建的Widget + +Django 提供所有基本的HTML Widget,并在`django.forms.widgets` 模块中提供一些常见的Widget 组,包括[_文本的输入_](#text-widgets)、[_各种选择框_](#selector-widgets)、[_文件上传_](#file-upload-widgets)和[_多值输入_](#composite-widgets)。 + +### 处理文本输入的Widget + +这些Widget 使用HTML 元素`input` 和 `textarea`。 + +#### TextInput + +_class _`TextInput` + +文本输入:`` + +#### NumberInput + +_class _`NumberInput` + +文本输入:`` + +注意,不是所有浏览器的`number`输入类型都支持输入本地化的数字。Django 将字段的[`localize`](fields.html#django.forms.Field.localize "django.forms.Field.localize") 属性设置为`True` 以避免字段使用它们。 + +#### EmailInput + +_class _`EmailInput` + +文本输入:`` + +#### URLInput + +_class _`URLInput` + +文本输入:`` + +#### PasswordInput + +_class _`PasswordInput` + +密码输入:`` + +接收一个可选的参数: + +`render_value` + +决定在验证错误后重新显示表单时,Widget 是否填充(默认为`False`)。 + +#### HiddenInput + +_class _`HiddenInput` + +隐藏的输入:`` + +注意,还有一个[`MultipleHiddenInput`](#django.forms.MultipleHiddenInput "django.forms.MultipleHiddenInput") Widget,它封装一组隐藏的输入元素。 + +#### DateInput + +_class _`DateInput` + +日期以普通的文本框输入:`` + +接收的参数与[`TextInput`](#django.forms.TextInput "django.forms.TextInput") 相同,但是带有一些可选的参数: + +`format` + +字段的初始值应该显示的格式。 + +如果没有提供`format` 参数,默认的格式为参考[_本地化格式_](../../topics/i18n/formatting.html#format-localization)在[`DATE_INPUT_FORMATS`](../settings.html#std:setting-DATE_INPUT_FORMATS) 中找到的第一个格式。 + +#### DateTimeInput + +_class _`DateTimeInput` + +日期/时间以普通的文本框输入:`` + +接收的参数与[`TextInput`](#django.forms.TextInput "django.forms.TextInput") 相同,但是带有一些可选的参数: + +`format` + +字段的初始值应该显示的格式。 + +如果没有提供`format` 参数,默认的格式为参考[_本地化格式_](../../topics/i18n/formatting.html#format-localization)在[`DATETIME_INPUT_FORMATS`](../settings.html#std:setting-DATETIME_INPUT_FORMATS) 中找到的第一个格式。 + +#### TimeInput + +_class _`TimeInput` + +时间以普通的文本框输入:`` + +接收的参数与[`TextInput`](#django.forms.TextInput "django.forms.TextInput") 相同,但是带有一些可选的参数: + +`format` + +字段的初始值应该显示的格式。 + +如果没有提供`format` 参数,默认的格式为参考[_本地化格式_](../../topics/i18n/formatting.html#format-localization)在[`TIME_INPUT_FORMATS`](../settings.html#std:setting-TIME_INPUT_FORMATS) 中找到的第一个格式。 + +#### Textarea + +_class _`Textarea` + +文本区域:`...` + +### 选择和复选框Widget + +#### CheckboxInput + +_class _`CheckboxInput` + +复选框:`` + +接受一个可选的参数: + +`check_test` + +一个可调用的对象,接收`CheckboxInput` 的值并如果复选框应该勾上返回`True`。 + +#### Select + +_class _`Select` + +Select widget:`...` + +`choices` + +当表单字段没有`choices` 属性时,该属性是随意的。如果字段有choice 属性,当[`字段`](fields.html#django.forms.Field "django.forms.Field")的该属性更新时,它将覆盖你在这里的任何设置。 + +#### NullBooleanSelect + +_class _`NullBooleanSelect` + +Select Widget,选项为‘Unknown’、‘Yes’ 和‘No’。 + +#### SelectMultiple + +_class _`SelectMultiple` + +类似[`Select`](#django.forms.Select "django.forms.Select"),但是允许多个选择:`...` + +#### RadioSelect + +_class _`RadioSelect` + +类似[`Select`](#django.forms.Select "django.forms.Select"),但是渲染成`` 标签中的一个单选按钮列表: + +``` + + + ... + + +``` + +你可以迭代模板中的单选按钮来更细致地控制生成的HTML。假设表单`myform` 具有一个字段`beatles`,它使用`RadioSelect` 作为Widget: + +``` +{% for radio in myform.beatles %} + + {{ radio }} + +{% endfor %} + +``` + +它将生成以下HTML: + +``` + + John + + + Paul + + + George + + + Ringo + + +``` + +这包括`` 标签。你可以使用单选按钮的`tag`、`choice_label` 和 `id_for_label` 属性进行更细的控制。例如,这个模板: + +``` +{% for radio in myform.beatles %} + + {{ radio.choice_label }} + {{ radio.tag }} + +{% endfor %} + +``` + +... 将生成下面的HTML: + +``` + + John + + + + + Paul + + + + + George + + + + + Ringo + + + +``` + +如果你不迭代单选按钮 —— 例如,你的模板只是简单地包含`{{ myform.beatles }}` —— 它们将以`` 中的`` 标签输出,就像上面一样。 + +外层的`` 将带有定义在Widget 上的`id` 属性。 + +Changed in Django 1.7: + +当迭代单选按钮时,`label` 和`input` 标签分别包含`for` 和`id` 属性。每个单项按钮具有一个`id_for_label` 属性来输出元素的ID。 + +#### CheckboxSelectMultiple + +_class _`CheckboxSelectMultiple` + +类似[`SelectMultiple`](#django.forms.SelectMultiple "django.forms.SelectMultiple"),但是渲染成一个复选框列表: + +``` + + + ... + + +``` + +外层的`` 具有定义在Widget 上的`id` 属性。 + +类似[`RadioSelect`](#django.forms.RadioSelect "django.forms.RadioSelect"),你可以迭代列表的每个复选框。更多细节参见[`RadioSelect`](#django.forms.RadioSelect "django.forms.RadioSelect") 的文档。 + +Changed in Django 1.7: + +当迭代单选按钮时,`label` 和`input` 标签分别包含`for` 和`id` 属性。 每个单项按钮具有一个`id_for_label` 属性来输出元素的ID。 + +### 文件上传Widget + +#### FileInput + +_class _`FileInput` + +文件上传输入:`` + +#### ClearableFileInput + +_class _`ClearableFileInput` + +文件上传输入:``,带有一个额外的复选框,如果该字段不是必选的且有初始的数据,可以清除字段的值。 + +### 复合Widget + +#### MultipleHiddenInput + +_class _`MultipleHiddenInput` + +多个`` Widget。 + +一个处理多个隐藏的Widget 的Widget,用于值为一个列表的字段。 + +`choices` + +当表单字段没有`choices` 属性时,这个属性是可选的。如果字段有choice 属性,当[`字段`](fields.html#django.forms.Field "django.forms.Field")的该属性更新时,它将覆盖你在这里的任何设置。 + +#### SplitDateTimeWidget + +_class _`SplitDateTimeWidget` + +封装(使用[`MultiWidget`](#django.forms.MultiWidget "django.forms.MultiWidget"))两个Widget:[`DateInput`](#django.forms.DateInput "django.forms.DateInput") 用于日期,[`TimeInput`](#django.forms.TimeInput "django.forms.TimeInput") 用于时间。 + +`SplitDateTimeWidget` 有两个可选的属性: + +`date_format` + +类似[`DateInput.format`](#django.forms.DateInput.format "django.forms.DateInput.format") + +`time_format` + +类似[`TimeInput.format`](#django.forms.TimeInput.format "django.forms.TimeInput.format") + +#### SplitHiddenDateTimeWidget + +_class _`SplitHiddenDateTimeWidget` + +类似[`SplitDateTimeWidget`](#django.forms.SplitDateTimeWidget "django.forms.SplitDateTimeWidget"),但是日期和时间都使用[`HiddenInput`](#django.forms.HiddenInput "django.forms.HiddenInput")。 + +#### SelectDateWidget + +_class _`SelectDateWidget`[[source]](../../_modules/django/forms/extras/widgets.html#SelectDateWidget) + +封装三个[`Select`](#django.forms.Select "django.forms.Select") Widget:分别用于年、月、日。注意,这个Widget 与标准的Widget 位于不同的文件中。 + +接收一个可选的参数: + +`years` + +一个可选的列表/元组,用于”年“选择框。默认为包含当前年份和未来9年的一个列表。 + +`months` + +New in Django 1.7\. + +一个可选的字典,用于”月“选择框。 + +字典的键对应于月份的数字(从1开始),值为显示出来的月份: + +``` +MONTHS = { + 1:_('jan'), 2:_('feb'), 3:_('mar'), 4:_('apr'), + 5:_('may'), 6:_('jun'), 7:_('jul'), 8:_('aug'), + 9:_('sep'), 10:_('oct'), 11:_('nov'), 12:_('dec') +} + +``` + +`empty_label` + +New in Django 1.8\. + +如果[`DateField`](fields.html#django.forms.DateField "django.forms.DateField") 不是必选的,[`SelectDateWidget`](#django.forms.extras.widgets.SelectDateWidget "django.forms.extras.widgets.SelectDateWidget") 将有一个空的选项位于选项的顶部(默认为`---`)。你可以通过`empty_label` 属性修改这个文本。`empty_label` 可以是一个`字符串`、`列表` 或`元组`。当使用字符串时,所有的选择框都带有这个空选项。如果`empty_label` 为具有3个字符串元素的`列表` 或`元组`,每个选择框将具有它们自定义的空选项。空选项应该按这个顺序`('year_label', 'month_label', 'day_label')`。 + +``` +# A custom empty label with string +field1 = forms.DateField(widget=SelectDateWidget(empty_label="Nothing")) + +# A custom empty label with tuple +field1 = forms.DateField(widget=SelectDateWidget( + empty_label=("Choose Year", "Choose Month", "Choose Day")) + +``` + +> 译者:[Django 文档协作翻译小组](http://python.usyiyi.cn/django/index.html),原文:[Built-in widgets](https://docs.djangoproject.com/en/1.8/ref/forms/widgets/)。 +> +> 本文以 [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/cn/) 协议发布,转载请保留作者署名和文章出处。 +> +> [Django 文档协作翻译小组](http://python.usyiyi.cn/django/index.html)人手紧缺,有兴趣的朋友可以加入我们,完全公益性质。交流群:467338606。 diff --git a/6_5_1_Introduction.md b/6_5_1_Introduction.md new file mode 100644 index 0000000..f7883cd --- /dev/null +++ b/6_5_1_Introduction.md @@ -0,0 +1,24 @@ +# Django中的测试 # + +自动化测试对于现代web开发者来说,是非常实用的除错工具。你可以使用一系列测试-- 测试套件 -- 来解决或者避免大量问题: + ++ 当你编写新代码的时候,你可以使用测试来验证你的代码是否像预期一样工作。 ++ 当你重构或者修改旧代码的时候,你可以使用测试来确保你的修改不会在意料之外影响到你的应用的应为。 + +测试web应用是个复杂的任务,因为web应用由很多的逻辑层组成 -- 从HTTP层面的请求处理,到表单验证和处理,到模板渲染。使用Django的测试执行框架和各种各样的工具,你可以模拟请求,插入测试数据,检查你的应用的输出,以及大体上检查你的代码是否做了它应该做的事情。 + +最好的一点是,它非常简单。 + +在Django中编写测试的最佳方法是,使用构建于Python标准库的unittest模块。这在[编写和运行测试](http://python.usyiyi.cn/django/topics/testing/overview.html) 文档中会详细介绍。 + +你也可以使用任何其它 Python 的测试框架;Django为整合它们提供了API和工具。这在[高级测试话题](http://python.usyiyi.cn/django/topics/testing/advanced.html)的[使用不同的测试框架](http://python.usyiyi.cn/django/topics/testing/advanced.html#other-testing-frameworks) 一节中描述。 + ++ [编写和运行测试](http://python.usyiyi.cn/django/topics/testing/overview.html) ++ [测试工具](http://python.usyiyi.cn/django/topics/testing/tools.html) ++ [高级测试话题](http://python.usyiyi.cn/django/topics/testing/advanced.html) + +> 译者:[Django 文档协作翻译小组](http://python.usyiyi.cn/django/index.html),原文:[Introduction](https://docs.djangoproject.com/en/1.8/topics/testing/)。 +> +> 本文以 [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/cn/) 协议发布,转载请保留作者署名和文章出处。 +> +> [Django 文档协作翻译小组](http://python.usyiyi.cn/django/index.html)人手紧缺,有兴趣的朋友可以加入我们,完全公益性质。交流群:467338606。 -- GitLab
` 标签,每个`
` 标签包含一个字段: + +``` +>>> f = ContactForm() +>>> f.as_p() +'
Subject:
Message:
Sender:
Cc myself: