该文档讲述Model
API的详细信息。 它建立在模型和数据库查询指南文档之上,所以在阅读这篇文档之前,你可能会想要先阅读并理解那两篇文档。
我们将用数据库查询指南中所展现的Weblog模型示例来贯穿这篇参考文档。
要创建模型的一个新实例,只需要像其它Python 类一样实例化它:
关键字参数就是在你的模型中定义的字段的名称。 请注意,实例化模型绝对不会触及你的数据库;为此,你需要save()
。
注
也许你会想通过重写 __init__
方法来自定义模型。 无论如何,如果你这么做了,小心不要改变了调用形式 — 任何改变都可能导致模型实例不能被保存。
尝试使用下面这些方法之一,而不是重写__init__
:
在模型类上添加一个类方法:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
@classmethod
def create(cls, title):
book = cls(title=title)
# do something with the book
return book
book = Book.create("Pride and Prejudice")
在自定义管理器中添加一个方法(推荐):
class BookManager(models.Manager):
def create_book(self, title):
book = self.create(title=title)
# do something with the book
return book
class Book(models.Model):
title = models.CharField(max_length=100)
objects = BookManager()
book = Book.objects.create_book("Pride and Prejudice")
from_db()
方法可以用于自定义从数据库加载模型时自定义实例的创建。
db
参数包含加载模型的数据库的别名,field_names
包含所有加载的字段的名称,values
包含field_names
中每个字段加载的值。 field_names
与values
的顺序相同。 如果模型的所有字段都提供, values
需要被保证其顺序与__init__()
所期望的一致。 这表示此时实例可以通过cls(*values)
创建。 如果任何字段被推迟,它们将不会出现在field_names
中。 在这种情况下,为每个缺少的字段分配一个值django.db.models.DEFERRED
。
除创建新模型之外,from_db()
方法必须在新实例的属性_state
中设置_state
和db
标识。
下面的示例演示如何保存从数据库中加载进来的字段原始值:
from django.db.models import DEFERRED
@classmethod
def from_db(cls, db, field_names, values):
# from_db()的默认实现(可以修改并可以
# 用super()代替)。
if len(values) != len(cls._meta.concrete_fields):
values = list(values)
values.reverse()
values = [
values.pop() if f.attname in field_names else DEFERRED
for f in cls._meta.concrete_fields
]
instance = cls(*values)
instance._state.adding = False
instance._state.db = db
# 将字段的原始值保存在实例上
instance._loaded_values = dict(zip(field_names, values))
return instance
def save(self, *args, **kwargs):
# 检查当前的值与._loaded_values如何不一样。 例如,
# 防止修改模型的creator_id。 (这个示例不
# 支持'creator_id'延迟加载的情况)。
if not self._state.adding and (
self.creator_id != self._loaded_values['creator_id']):
raise ValueError("Updating the value of creator isn't allowed")
super(...).save(*args, **kwargs)
上面的示例演示from_db()
的完整实现。 当然,这里的from_db()
中完全可以只用super()
调用。
在旧版本中,你可以通过查询cls._deferred
来检查所有字段是否已加载。 该属性被删除,新增django.db.models.DEFERRED
。
如果从模型实例中删除一个字段,再次访问它将从数据库重新加载该值:
>>> obj = MyModel.objects.first()
>>> del obj.field
>>> obj.field # 从数据库加载这个字段
在旧版本中,访问已删除的字段会引发AttributeError
,而不是重新加载它。
如果你需要从数据库重新加载模型的一个值,你可以使用 refresh_from_db()
方法。 当不带参数调用这个方法时,将完成以下的动作:
Author
,那么如果 obj.author_id != obj.author.id
,obj.author
将被扔掉并在下次访问它时根据obj.author_id
的值重新加载。只有模型的字段才能从数据库中重新加载。 其他数据库相关值(如注解)不会重新加载。 任何@cached_property
属性也不会被清除。
重新加载使用的数据库与实例加载时使用的数据库相同,如果实例不是从数据库加载的则使用默认的数据库。 可以使用using
参数来强制指定重新加载的数据库。
可以回使用fields
参数强制设置加载的字段。
例如,要测试update()
调用是否得到预期的更新,可以编写类似下面的测试:
def test_update_result(self):
obj = MyModel.objects.create(val=1)
MyModel.objects.filter(pk=obj.pk).update(val=F('val') + 1)
# 在这个时候obj.val仍然是1,但是数据库中的值
# 被更新成了2。 这个对象更新后的值需要从数据库重新
# 加载。
obj.refresh_from_db()
self.assertEqual(obj.val, 2)
注意,当访问延迟的字段时,延迟字段通过这个方法加载。 所以可以自定义延迟加载的行为。 下面的实例演示如何在重新加载一个延迟字段时重新加载所有的实例字段:
class ExampleModel(models.Model):
def refresh_from_db(self, using=None, fields=None, **kwargs):
# fields contains the name of the deferred field to be
# loaded.
if fields is not None:
fields = set(fields)
deferred_fields = self.get_deferred_fields()
# If any deferred field is going to be loaded
if fields.intersection(deferred_fields):
# then load all of them
fields = fields.union(deferred_fields)
super(ExampleModel, self).refresh_from_db(using, fields, **kwargs)
一个辅助方法,它返回一个集合,包含模型当前所有延迟字段的属性名称。
验证一个模型涉及三个步骤:
Model.clean_fields()
Model.clean()
Model.validate_unique()
当你调用模型的full_clean()
方法时,这三个方法都将执行。
当你使用ModelForm
时,is_valid()
将为表单中的所有字段执行这些验证。 更多信息参见ModelForm文档。 如果你计划自己处理验证出现的错误,或者你已经将需要验证的字段从ModelForm
中去除掉,你只需调用模型的full_clean()
方法。
该方法按顺序调用Model.clean_fields()
、Model.clean()
和Model.validate_unique()
(如果validate_unique
为True
),并引发一个ValidationError
,该异常的message_dict
属性包含三个步骤的所有错误。
可选的exclude
参数用来提供一个可以从验证和清洁中排除的字段名称的列表。
ModelForm
使用这个参数来排除表单中没有出现的字段,使它们不需要验证,因为用户无法修正这些字段的错误。
注意,当你调用模型的save()
方法时,full_clean()
不会 自动调用。 如果你想一步就可以为你手工创建的模型运行验证,你需要手工调用它。 像这样:
from django.core.exceptions import ValidationError
try:
article.full_clean()
except ValidationError as e:
# 基于e.message_dict中包含的错误完成一些事情。
# 将它们显示给用户,或者用程序处理它们。
pass
full_clean()
第一步执行的是验证每个字段。
这个方法将验证模型的所有字段。 可选的exclude
参数让你提供一个字段名称列表来从验证中排除。 如果有字段验证失败,它将引发一个ValidationError
。
full_clean()
第二步执行的是调用Model.clean()
。
如要实现模型自定义的验证,应该覆盖这个方法。
应该用这个方法来提供自定义的模型验证,以及修改模型的属性。 例如,你可以使用它来给一个字段自动提供值,或者用于多个字段需要一起验证的情形:
import datetime
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Article(models.Model):
...
def clean(self):
# Don't allow draft entries to have a pub_date.
if self.status == 'draft' and self.pub_date is not None:
raise ValidationError(_('Draft entries may not have a publication date.'))
# Set the pub_date for published items if it hasn't been set already.
if self.status == 'published' and self.pub_date is None:
self.pub_date = datetime.date.today()
然而请注意,和Model.full_clean()
类似,调用模型的save()
方法时不会引起clean()
方法的调用。
在上面的示例中,Model.clean()
引发的ValidationError
异常通过一个字符串实例化,所以它将被保存在一个特殊的错误字典键NON_FIELD_ERRORS
中。 这个键用于整个模型出现的错误而不是一个特定字段出现的错误:
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
try:
article.full_clean()
except ValidationError as e:
non_field_errors = e.message_dict[NON_FIELD_ERRORS]
若要引发一个特定字段的异常,可以使用一个字典实例化ValidationError
,其中字典的键为字段的名称。 我们可以更新前面的例子,只引发pub_date
字段上的异常:
class Article(models.Model):
...
def clean(self):
# Don't allow draft entries to have a pub_date.
if self.status == 'draft' and self.pub_date is not None:
raise ValidationError({'pub_date': _('Draft entries may not have a publication date.')})
...
如果你在Model.clean()
期间检测到多个字段中的错误,还可以传递一个字段名称映射到错误信息的字典:
raise ValidationError({
'title': ValidationError(_('Missing title.'), code='required'),
'pub_date': ValidationError(_('Invalid date.'), code='invalid'),
})
最后,full_clean()
将检查模型的唯一性约束。
如果这些字段不出现在ModelForm
中,如何引发特定字段的验证错误
你不能在Model.clean()
中引发没有在模型表单中出现的字段的验证错误(表单可能会使用Meta.fields
或Meta.exclude
限制它的字段)。 这样做会引发ValueError
,因为验证错误将无法与排除的字段相关联。
为了解决这个困境,可以覆盖Model.clean_fields()
,因为它接收排除验证的字段列表。 像这样:
class Article(models.Model):
...
def clean_fields(self, exclude=None):
super(Article, self).clean_fields(exclude=exclude)
if self.status == 'draft' and self.pub_date is not None:
if exclude and 'status' in exclude:
raise ValidationError(
_('Draft entries may not have a publication date.')
)
else:
raise ValidationError({
'status': _(
'Set status to draft if there is not a '
'publication date.'
),
})
该方法与clean_fields()
类似,只是验证的是模型的所有唯一性约束而不是单个字段的值。 可选的exclude
参数允许你提供一个字段名称的列表来从验证中排除。 如果有字段验证失败,它将引发一个ValidationError
。
注意,如果你提供一个exclude
参数给validate_unique()
,任何涉及到其中一个字段的unique_together
约束将不检查。
将一个对象保存到数据库,需要调用 save()
方法:
Model.
save
(force_insert=False, force_update=False, using=DEFAULT_DB_ALIAS, update_fields=None)[source]¶如果你想要自定义保存的动作,你可以重写 save()
方法。 请看覆盖模型的预定义方法了解更多细节。
模型保存过程也有一些细微之处;请参阅以下部分。
如果模型具有一个AutoField
— 一个自增的主键 — 那么该自增的值将在第一次调用对象的save()
时计算并保存:
>>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b2.id # 返回None,因为b还没有ID。
>>> b2.save()
>>> b2.id # 返回新对象的ID。
在调用save()
之前无法知道ID 的值,因为这个值是通过数据库而不是Django 计算。
为了方便,默认情况下每个模型都有一个AutoField
叫做id
,除非你显式指定模型某个字段的 primary_key=True
。 更多细节参见AutoField
的文档。
pk
属性¶Model.
pk
¶无论你是自己定义还是让Django 为你提供一个主键字段, 每个模型都将具有一个属性叫做pk
。 它的行为类似模型的一个普通属性,但实际上是模型主键字段属性的别名。 你可以读取并设置它的值,就和其它属性一样,它会更新模型中正确的值。
如果模型具有一个AutoField
,但是你想在保存时显式定义一个新的对象ID,你只需要在保存之前显式指定它而不用依赖ID 自动分配的值:
>>> b3 = Blog(id=3, name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b3.id # Returns 3.
>>> b3.save()
>>> b3.id # Returns 3.
如果你手工赋值一个自增主键的值,请确保不要使用一个已经存在的主键值! 如果你使用数据库中已经存在的主键值创建一个新的对象,Django 将假设你正在修改这个已存在的记录而不是创建一个新的记录。
接着上面的'Cheddar Talk'
博客示例,下面这个例子将覆盖数据库中之前的记录:
b4 = Blog(id=3, name='Not Cheddar', tagline='Anything but cheese.')
b4.save() # 覆盖前面的ID=3的blog!
出现这种情况的原因,请参见下面的Django 如何知道是UPDATE 还是INSERT。
显式指定自增主键的值对于批量保存对象最有用,但你必须有信心不会有主键冲突。
如果你使用PostgreSQL,则可能需要更新与主键关联的序列;请参阅手工指定自增主键的值。
当你保存一个对象时,Django 执行以下步骤:
发出预先保存的信号。 发送pre_save
信号,允许监听该信号的任何函数执行某些操作。
预处理数据。 调用每个字段的pre_save()
方法来执行所需的任何自动数据修改。 例如,日期/时间字段覆盖pre_save()
来实现auto_now_add
和auto_now
。
准备数据库的数据。 要求每个字段的get_db_prep_save()
方法将其当前值提供给可写入数据库的数据类型。
大多数字段不需要数据准备。 简单的数据类型,例如整数和字符串,是可以直接写入的Python 对象。 但是,复杂的数据类型通常需要一些改动。
例如,DateField
字段使用Python 的 datetime
对象来保存数据。 数据库保存的不是datetime
对象,所以该字段的值必须转换成ISO兼容的日期字符串才能插入到数据库中。
将数据插入数据库。 预处理的准备数据组成一个用于插入数据库的SQL语句。
发出一个保存后的信号。 发送post_save
信号,允许监听该信号的任何函数执行某些操作。
你可能已经注意到Django 数据库对象使用同一个save()
方法来创建和改变对象。 Django 对INSERT
和UPDATE
SQL 语句的使用进行抽象。 当你调用save()
时,Django 使用下面的算法:
True
的值(例如,非None
值或非空字符串),Django将执行UPDATE
。UPDATE
没有更新任何记录,Django将执行INSERT
。现在应该明白了,当保存一个新的对象时,如果不能保证主键的值没有使用,你应该注意不要显式指定主键值。 关于这个细微差别的更多信息,参见上文的显示指定主键的值 和下文的强制使用INSERT 或UPDATE。
在Django 1.5 和更早的版本中,在设置主键的值时,Django 会作一个 SELECT
。 如果SELECT
找到一行,那么Django执行UPDATE
,否则执行INSERT
。 旧的算法导致UPDATE
情况下多一次查询。 有极少数的情况,数据库不会报告有一行被更新,即使数据库包含该对象的主键值。 有个例子是PostgreSQL 的ON UPDATE
触发器,它返回NULL
。 在这些情况下,可能要通过将select_on_save
选项设置为True
以启用旧的算法。
有时候你需要在一个字段上执行简单的算法操作,例如增加或者减少当前值。 实现这点的简单方法是像下面这样:
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
>>> product.number_sold += 1
>>> product.save()
如果从数据库中读取的旧的number_sold
值为10,那么写回到数据库中的值将为11。
通过将更新基于原始字段的值而不是显式赋予一个新值,这个过程可以避免竞态条件而且更快。 Django提供F表达式
用于这种类型的相对更新。 利用F表达式
,前面的示例可以表示成:
>>> from django.db.models import F
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
>>> product.number_sold = F('number_sold') + 1
>>> product.save()
如果传递给save()
的update_fields
关键字参数一个字段名称列表,那么将只有该列表中的字段会被更新。
如果你想更新对象的一个或几个字段,这可能是你想要的。 不让模型的所有字段都更新将会带来一些轻微的性能提升。 像这样:
product.name = 'Name changed again'
product.save(update_fields=['name'])
update_fields
参数可以是任何包含字符串的可迭代对象。 空的update_fields
可迭代对象将会忽略保存。 如果为None 值,将执行所有字段上的更新。
指定update_fields
将强制使用更新操作。
当保存通过延迟模型加载(only()
或defer()
)进行访问的模型时,只有从数据库中加载的字段才会得到更新。 这种情况下,有个自动的update_fields
。 如果你赋值或者改变延迟字段的值,该字段将会添加到更新的字段中。
发出一个SQL DELETE
操作。 这只会删除数据库中的对象; Python实例仍将存在,并且它的字段中仍然有数据。 此方法返回删除对象的数量和每个对象类型的删除数量的字典。
更多细节,包括如何批量删除对象,请参见Deleting objects。
如果你想自定义删除的行为,你可以覆盖delete()
方法。 请看 Overriding predefined model methods 了解更多细节。
有时使用多表继承,你可能只想删除子模型的数据。 指定keep_parents=True
将保留父模型的数据。
有几个实例方法具有特殊的目的。
__str__()
¶__str__()
方法在每当你对一个对象调用str()
时调用。
Django在许多地方使用str(obj)
。 最明显的是在Django 的Admin 站点显示一个对象和在模板中插入对象的值的时候。 所以,你应该始终让__str__()
方法返回模型的一个友好的、人类可读的形式。
像这样:
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible # only if you need to support Python 2
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
如果你希望与Python 2兼容,则可以使用python_2_unicode_compatible()
来装饰模型类,如上所示。
__eq__()
¶定义相等方法,使具有相同主键值和相同具体类的实例被认为是相等的,除了具有主键值None
的实例不等于它们之外的任何东西。 对于代理模型,具体类定义为模型的第一个非代理父代;对于所有其他型号,它只是模型的类。
像这样:
from django.db import models
class MyModel(models.Model):
id = models.AutoField(primary_key=True)
class MyProxyModel(MyModel):
class Meta:
proxy = True
class MultitableInherited(MyModel):
pass
# Primary keys compared
MyModel(id=1) == MyModel(id=1)
MyModel(id=1) != MyModel(id=2)
# Primay keys are None
MyModel(id=None) != MyModel(id=None)
# Same instance
instance = MyModel(id=None)
instance == instance
# Proxy model
MyModel(id=1) == MyProxyModel(id=1)
# Multi-table inheritance
MyModel(id=1) != MultitableInherited(id=1)
__hash__()
¶__hash__()
方法是基于实例的主键值。 这是有效的hash(obj.pk)
。 如果实例没有主键值,则将引发TypeError
,否则__hash__()
方法将在保存实例之前和之后返回不同的值,但是在Python中禁止更改实例的__hash__()
值。
get_absolute_url()
¶Model.
get_absolute_url
()¶get_absolute_url()
方法告诉Django 如何计算对象的标准URL。 对于调用者,该方法返回的字符串应该可以通过HTTP 引用到这个对象。
像这样:
def get_absolute_url(self):
return "/people/%i/" % self.id
虽然这个代码是正确和简单的,但它可能不是最便携的方式来编写这种方法。 reverse()
函数通常是最好的方法。
像这样:
def get_absolute_url(self):
from django.urls import reverse
return reverse('people.views.details', args=[str(self.id)])
Django 使用get_absolute_url()
的一个地方是在Admin 应用中。 如果某个对象定义了该方法,那么在该对象的编辑页面将会出现“View on site”链接,点击该链接将会转到该对象由get_absolute_url()
函数指向的公开视图。
类似地,Django 的另外一些小功能,例如syndication feed
framework 也使用get_absolute_url()
。 如果模型的每个实例都具有一个唯一的URL 是合理的,你应该定义get_absolute_url()
。
警告
你应该避免从没有验证过的用户输入构建URL,以减少有害的链接和重定向:
def get_absolute_url(self):
return '/%s/' % self.name
如果self.name
为'/example.com'
,将返回'//example.com/'
, 而它是一个合法的相对URL而不是期望的'/%2Fexample.com/'
。
在模板中使用get_absolute_url()
而不是硬编码对象的URL 是很好的实践。 例如,下面的模板代码很糟糕:
<!-- BAD template code. Avoid! -->
<a href="/people/{{ object.id }}/">{{ object.name }}</a>
下面的模板代码要好多了:
<a href="{{ object.get_absolute_url }}">{{ object.name }}</a>
如果你改变了对象的URL 结构,即使是一些简单的拼写错误,你不需要检查每个可能创建该URL 的地方。 在get_absolute_url()
中定义一次,然后在其它代码调用它。
注
get_absolute_url()
返回的字符串必须只包含ASCII 字符(URI 规范RFC 2396 的要求),并且如需要必须要URL-encoded。
代码和模板中对get_absolute_url()
的调用应该可以直接使用而不用做进一步处理。 你可能想使用django.utils.encoding.iri_to_uri()
函数来帮助你解决这个问题,如果你正在使用ASCII 范围之外的Unicode 字符串。
除了save()
、delete()
之外,模型的对象还可能具有以下一些方法:
Model.
get_FOO_display
()¶对于每个具有choices
的字段,每个对象将具有一个get_FOO_display()
方法,其中FOO
为该字段的名称。 这个方法返回该字段对“人类可读”的值。
像这样:
from django.db import models
class Person(models.Model):
SHIRT_SIZES = (
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
)
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES)
>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'
Model.
get_next_by_FOO
(**kwargs)¶Model.
get_previous_by_FOO
(**kwargs)¶如果DateField
和DateTimeField
没有设置 null=True
,那么该对象将具有get_next_by_FOO()
和get_previous_by_FOO()
方法,其中FOO
为字段的名称。 它根据日期字段返回下一个和上一个对象,并适时引发一个DoesNotExist
。
这两个方法都将使用模型默认的管理器来执行查询。 如果你需要使用自定义的管理器或者你需要自定义的筛选,这个两个方法还接受可选的参数,它们应该用Field lookups 中提到的格式。
注意,对于完全相同的日期,这些方法还将利用主键来进行查找。 这保证不会有记录遗漏或重复。 这还意味着你不可以在未保存的对象上使用这些方法。
DoesNotExist
¶Model.
DoesNotExist
¶ORM 在好几个地方会引发这个异常,例如QuerySet.get()
根据给定的查询参数找不到对象时。
Django 为每个类提供一个DoesNotExist
异常属性是为了区别找不到的对象所属的类,并让你可以利用try/except
捕获一个特定模型的类。 这个异常是django.core.exceptions.ObjectDoesNotExist
的子类。
2017年9月6日