创建对象(Creating objects)

创建 model 的新实例和实例化其他的 Python 类一样:

class Model(**kwargs)


其中的关键字参数就是你在你的 model 中定义的字段的名称。要注意实例化一个 model 并不会操作数据库,要保存到数据库的话,你需要使用 save() 方法。

保存对象(Saving objects)

要将对象保存至数据库,就调用 save():

Model.save([force_insert=Falseforce_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 会执行下面的步骤:

  1. 发出预保存信号(Emit a pre-save signal)。 发出 django.db.models.signals.pre_save 信号(signal),然后监听该信号的函式就会执行某些定制操作。
  2. 预处理数据(Pre-process the data)。 对角中的每个字段都根据字段所需的数据格式,对数据自动进行调整。
    大多数字段是不需要预处理数据的,它们保持不变,只有那些有特殊行为的字段才需要预处理数据。例如,如果你的 model 中含有一个 DateField 字段,并且它指定了 auto_now=True,在预处理数据这个阶段,就会修改该字段的数据,使其包含当前时间戳(我们的文档还没有将这些有“特殊行为”的字段完整地列出来)。
  3. 根据数据库进行数据转换(Prepare the data for the database)。 每个字段都要根据当前数据库所要求的数据格式对当前值进行转换。
    大多数字段无须转换,它们多是简单的数据类型,比如整数,字符串,都是随时可写的 Python 对象。但是,有更多的复杂数据类型需要对数据进行转换和调整。
    例如,DateFields 字段使用的是一个 datetime 对象,这是一个 Python 类的实例,因为数据库并不直接保存 datetime 对象,所以字段值必须被转换成符合 ISO 标准的(ISO-compliant)的日期字符串,这样数据库才能识别。
  4. 将数据添加到数据库中(Insert the data into the database)。 经过预处理和转换的数据被组合成一条用以插入数据的 SQL 语句,从而完成对数据的添加。
  5. 发出已保存信号(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 值做为第二条件,这样才能保证记录没有重复或遗漏。