创建对象(Creating objects)
创建 model 的新实例和实例化其他的 Python 类一样:
class Model(**kwargs)
其中的关键字参数就是你在你的 model 中定义的字段的名称。要注意实例化一个 model 并不会操作数据库,要保存到数据库的话,你需要使用 save() 方法。
保存对象(Saving objects)
要将对象保存至数据库,就调用 save():
Model.save([force_insert=False, force_update=False])
这个方法颇有些微妙之处,请查看下面几节。
这部分是在 Django 1.0 中新增的: 请查看版本文档
save() 方法的形式与之前的版本已经有所不同(加入了 force_insert 和 force_update 参数)。如果你要重写该方法,就要留意这两个参数。
自增主键(Auto-incrementing primary keys)
如果你的 model 有一个 AutoField 字段,它是一个自增主键,那么在你第一次调用 save() 方法时,就会计算得出这个自增主键的值,然后做为对象的一个属性保存起来:
>>> b2 = Blog(name='Cheddar Talk', tagline='Thoughts on cheese.')
>>> b2.id # Returns None, because b doesn't have an ID yet.
>>> b2.save()
>>> b2.id # Returns the ID of your new object.
在调用 save() 之前是拿不到 ID 值的,因为这个值是要靠数据库来计算得出的,而非 Django。
(为使用方便,每个 model 默认都有一个 AutoField 自增字段,它的名称是 id。不过你可以在某个字段上指定 primary_key=True,从而将这个字段变成主键字段。详情请查看 AutoField。
pk 属性 (The pk property)
这部分是在 Django 1.0 中新增的: 请查看版本文档
Model.pk
无论是你自己定义了主键,还是使用 Django 默认提供的主键,每个 model 都有一个指向主键的属性,叫做 pk。它看上去只是是一个属性,其实它是主键字段的一个别名。你可以象使用其他属性一样,获取或是设置它的值,它会自动对主键字段的值进行更改。
显式指定自增主键的值(Explicitly specifying auto-primary-key values)
如果一个 model 中含有一个 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() # Overrides the previous blog with ID=3!
要了解为什么会覆盖掉记录,请查看下面的 Django 如何判断是更新还是创建(How Django knows to UPDATE vs. INSERT) 一节。
如果你能确实主键值不发生冲突,那么对于大量保存对象而言,指定自增主键的值是非常有用的。
在保存对象时都发生了什么(What happens when you save?)
在你保存对象时,Django 会执行下面的步骤:
- 发出预保存信号(Emit a pre-save signal)。 发出 django.db.models.signals.pre_save 信号(signal),然后监听该信号的函式就会执行某些定制操作。
- 预处理数据(Pre-process the data)。 对角中的每个字段都根据字段所需的数据格式,对数据自动进行调整。
大多数字段是不需要预处理数据的,它们保持不变,只有那些有特殊行为的字段才需要预处理数据。例如,如果你的 model 中含有一个 DateField 字段,并且它指定了 auto_now=True,在预处理数据这个阶段,就会修改该字段的数据,使其包含当前时间戳(我们的文档还没有将这些有“特殊行为”的字段完整地列出来)。 - 根据数据库进行数据转换(Prepare the data for the database)。 每个字段都要根据当前数据库所要求的数据格式对当前值进行转换。
大多数字段无须转换,它们多是简单的数据类型,比如整数,字符串,都是随时可写的 Python 对象。但是,有更多的复杂数据类型需要对数据进行转换和调整。
例如,DateFields 字段使用的是一个 datetime 对象,这是一个 Python 类的实例,因为数据库并不直接保存 datetime 对象,所以字段值必须被转换成符合 ISO 标准的(ISO-compliant)的日期字符串,这样数据库才能识别。 - 将数据添加到数据库中(Insert the data into the database)。 经过预处理和转换的数据被组合成一条用以插入数据的 SQL 语句,从而完成对数据的添加。
- 发出已保存信号(Emit a post-save signal)。 发出 django.db.models.signals.post_save 信号,监听该信号的函式们会执行某些既定的操作。
(Django 是如何判别更新还是创建的How Django knows to UPDATE vs. INSERT)
你可能已注意到,Django 的数据对象无论是创建对象还是保存修改过的对象,都是使用同样的 save() 方法,因为 Django 已经对使用 INSERT 或 UPDATE SQL 语句的需求进行了抽象。因此,在你调用 save() 方法时,Django 会按照下列算法进行处理:
- 如果对象的主键属性是被赋与一个真值(True)时,(例如,即不是 None,也不是空字符串),Django 会执行一个 SELECT 查询以判定与主键相绑定的记录是否存在。
- 如果该记录存在,Django 就会执行另一个 UPDATE 查询query。
- 如果未设置对象的主键属性,或者如果虽然设置了主键,但是与其绑定的记录却不存在,那么 Django 不会执行一个 INSERT 操作。
有一点要注意的是:如果你不能保证主键值并被用过,那么在保存新对象时,要注意不要显式指定主键的值。想在这个细节上有更多了解,请查看上面的 显示指定自增变量的值(Explicitly specifying auto-primary-key values) 和下面的 强制添加或更新(Forcing an INSERT or UPDATE) 。
强制新增或更新(Forcing an INSERT or UPDATE)
这部分是在 Django 1.0 中新增的: Please, see the release notes
在一些少见的场合中,运行 save() 方法时要求强制运行一条 SQL INSERT 语句,而非 UPDATE 语句。同理在某些情况下又强迫 UPDATE 而非 INSERT。在这种情况下,你可以通过设置 force_insert=True 或 force_update=True 参数的方式运行 save() 方法。同时提供两个参数会抛开错误,因为 Django 不知道该 INSERT 还是 UPDATE。
需要使用这两个参数的用例是很少见的。一般来说,Django 的默认操作大多是正确的,如果使用了这两个参数,如果出现错误,会使得调试跟踪变得困难,所以这个特性仅适用于高级应用。
根据已存在的字段更新属性(Updating attributes based on existing fields)
有些时候,你需要对某个字段做简单的运算,比如对当前值递增或递减,可以用下面的方法达到这个目的:
>>> product = Product.objects.get(name='Venezuelan Beaver Cheese')
>>> product.number_sold += 1
>>> product.save()
从数据库中得到旧的 number_sold 值是 10,然后再将 11 写回到数据库中。
根据某个字段进行赋值,要好于直接地显式赋值。Django 提供了 F()表达式(F() expressions) 来解决根据字段赋值的问题。使用 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()<> 操作的时候才根据取值字段从数据库中取出原始值。
一旦对象被保存,你必须马上重新得到对象,这样才能得到你刚才得赋的字段值:
>>> product = Products.objects.get(pk=product.pk)
>>> print product.number_sold
42
详情请查看 use in update queries 文档中 F() expressions这一节
对象删除(Deleting objects)
Model.delete()
对当前对象执行一条 DELETE SQL 语句。删除操作仅仅作用于数据库,Python 的实例对象依然存在,它所包含的字段同样也存在。.
要了解更多细节,比如如何一次性删除多个对象,请查看 对象删除(Deleting objects).
其他的 model 实例方法(Other model instance methods)
有一部分方法有另外的特殊作用。
__str__
Model.__str__()
__str__() 是一个 Python 式的 "魔术方法" ,它定义了在你调用了对象的 str() 方法时,返回值的内容。Django 在很多场合使用 str(obj) (或是相关的其他的函式,比如下面提到的 unicode(obj)),最常见的就是在 Django 管理后台中做为对象的显示值;或是用来在模板中显示对象。因此,你应该总是为 __str__ 返回一个友好且易读的字符串。这样做虽然不是必须的,不过我们仍建议您这样做。不过在您到处狂写 __str__ 之前,请继续阅读下面所讲的 __unicode__ 方法。
例如:
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
def __str__(self):
# Note use of django.utils.encoding.smart_str() here because
# first_name and last_name will be unicode strings.
return smart_str('%s %s' % (self.first_name, self.last_name))
__unicode__
Model.__unicode__()
在你使用对象的 unicode() 方法时,就会调用 __unicode__() 。因为你的 model 中的字段从数据库中得到的是 Unicode 字符串,所以一般情况下你要为你的 model 写一个 __unicode__() 方法。上面的例子可以简单地改写为:
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
def __unicode__(self):
return u'%s %s' % (self.first_name, self.last_name)
如果你在 model 上只定义了 __unicode__() 方法却并没有定义 __str__() 方法,Django 将自动提供一个会在内部调用 __unicode__() 的 __str__() 方法,并且将返回值自动转换成 UTF-8 字符串。对此,在开发时有如下建议:只定义 __unicode__() 方法,让 Django 在必要时自动做字符串转换。
get_absolute_url
Model.get_absolute_url()
定义 get_absolute_url() 方法会让 Django 知道如何计算得到当前对象对应的网址 URL,例如:
def get_absolute_url(self):
return "/people/%i/" % self.id
Django 在管理后台使用该方法。如果某个对象定义了 get_absolute_url() 方法,在它的对象修改页就会出现一个 "View on site" 链接,它会弹出一个与当前对象相关的网页,网站就是 get_absolute_url() 返回的 URL。
此外,Django 有些其他部分,也用到了 get_absolute_url() ,比如 RSS种子(syndication feed framework)。 get_absolute_url() 是一个对实际应用中很有用的快捷方法。
在模板中使用 get_absolute_url() 比直接在拼合网址要更好。例如,这种模板代码就不是很好:
<a href="/people/{{ object.id }}/">{{ object.name }}</a>
但这种写法就很好:
<a href="{{ object.get_absolute_url }}">{{ object.name }}</a>
注意
get_absolute_url() 返回的 URL 只能包含 ASCII 字符串 (这是 URI 的规则所要求的,详见 RFC 2396) ,所以在必要的时候,会进行重编码 URL-encoded,而在代码和模板中 get_absolute_url() 时,会直接输出结果而不会进行网址重编码。如果你有大量的 Unicode 网址要进行重编码,可以使用 django.utils.encoding.iri_to_uri() 函式来处理。
permalink 装饰器(The permalink decorator)
使用 get_absolute_url() 来获取网址会带来这样一个问题:它违背了 DRY 原则: 对象的 URL 定义同时出现在 URLConf 配置文件(urls.py)和 model 定义中。
你可以使用 permalink 装饰器将 model 与 URLConf 相剥离:
permalink()
该装饰器需要一个视图函式,一个位置参数的列表以及一个命名参数的字典(这个是可选的),然后 Django 就会根据 URLconf ,填入你传递的参数,从而得到完整的网址 URL。例如,你的 URLconf 有这么一行:
(r'^people/(\d+)/$', 'people.views.details'),
...你 model 中的 get_absolute_url 方法如下:
from django.db import models
@models.permalink
def get_absolute_url(self):
return ('people.views.details', [str(self.id)])
再来一个例子,如果你的 URLconf 有这么一行:
(r'/archive/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/$', archive_view)
...然后 permalink()如下:
@models.permalink
def get_absolute_url(self):
return ('archive_view', (), {
'year': self.created.year,
'month': self.created.month,
'day': self.created.day})
注意上例中的第二个参数是一个空队列,因为我们只要传递关键字参数就行了,用不到位置参数。
用这种方法,你就可以将对象的绝对网址与用到它的视图联系起来,就不用再重复定义 URL 信息了,仍然可以象往常一样在模板中 get_absolute_url 方法。
在某些情况下,比如使用通用视图或是对多个 model 重用自定义视图,指定视图函式可能就会与反向 URL 匹配相冲突(因为多个匹配模式同时指向同一个视图)。
为了解决这个问题,Django 提供了命名URL匹配模式(named URL patterns)。使用命名 URL 匹配时,我们可以给匹配模式命名,然后可以直接引用这个名称而不用使用视图函式。只要将模式元组变成一个 url 函式就能完成对命名匹配模式的定义:
from django.conf.urls.defaults import *
url(r'^people/(\d+)/$',
'django.views.generic.list_detail.object_detail',
name='people_view'),
...接下来用模式的命名代替视图函式:
from django.db.models import permalink
def get_absolute_url(self):
return ('people_view', [str(self.id)])
get_absolute_url = permalink(get_absolute_url)
要了解命名 URL 匹配模式,请查看 URL 部分(URL dispatch documentation).
其他的实例方法(Extra instance methods)
除了 save(), delete() 之外,一个 model 对象可能还有下列方法:
Model.get_FOO_display()
如果 model 有字段设置了 choices ,对象就会提供 get_FOO_display() 方法,这里的 FOO 就是字段的名称。这个方法返回给用户看的那个可读性好的值,例如:
GENDER_CHOICES = (
('M', 'Male'),
('F', 'Female'),
)
class Person(models.Model):
name = models.CharField(max_length=20)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
...每个 Person 实例都有一个 get_gender_display() 方法。例如:
>>> p = Person(name='John', gender='M')
>>> p.save()
>>> p.gender
'M'
>>> p.get_gender_display()
'Male'
Model.get_next_by_FOO(**kwargs)
Model.get_previous_by_FOO(**kwargs)
如果 model 中的 DateField 字段和 DateTimeField 字段没有设置 null=True,那么对象就会提供 get_next_by_FOO() 方法和 get_previous_by_FOO() 方法。这里的 FOO 就是时间字段的名称。它根据当时时间字段的值返回下一个或是前一个对象。如果没有找到就会抛开 DoesNotExist 异常。
这两个方法的参数都是可选的,这些参数在 字段筛选条件(Field lookups) 中有详细介绍。
要注意:如果在使用这两个方法时遇到了相同的时间,那么它们就会将 ID 值做为第二条件,这样才能保证记录没有重复或遗漏。