原文地址:http://www.woodpecker.org.cn/obp/django /django-faq/db-api.html
一旦 数据模型 创建完毕, 自然会有存取数据的需要.本文档介绍了由 models 衍生而来的数据库抽象API,及如何创建,得到及更新对象. 贯穿本参考, 我们都会引用下面的民意测验(Poll)应用程序: class Poll(models.Model): 及下面的简单会话: >>> from datetime import datetime 查询如何运作Django 的数据查询基于构建结果集及对结果集进行取值. 结果集是独立于数据库的符合某个查询条件的一组数据对象的集合.这是一个惰性集合:在对该集合取值之前,无法知道该集合有哪些成员. 要生成一个满足你需求的结果集,首先要得到一个描述给定类型的所有对象的初始结果集.这个初始结果集可以通过一系列函数进行更精细的优化处理.当经过处理后的结果集符合你的要求时, 就可以对它进行取值操作(使用迭代操作,slicing操作,或一系列其它技术), 以得到一个你需要的对象或对象的列表. 获得初始结果集每个 Django model 都有一个与生俱来的管理器对象 objects, 管理器最重要的角色就是作为初始结果的来源. 一个管理器就是一个描述给定类型所有对象的特殊的初始结果集.Poll.objects 就是包含所有 Poll 对象的一个初始结果集. 它唯一特殊之处在于它不能被取值. 要克服此限制, 管理器对象有一个all() 方法. 该方法生成一个 可以 被取值的初始结果集的拷贝: all_polls = Poll.objects.all() 参阅 Model API 的 Managers 小节以了解管理器的定位及创建细节. 优化定制结果集管理器提供的初始结果集描述了给定类型的所有对象.不过通常你只需要这个对象集合中的一小部分(一个子集). 要生这样一个结果集,你需要对初始结果集进行优化定制处理, 增加一些限制条件直到描述的子集满足你的需要.最常用的两个定制结果集的方法是:
参数格式在下面 "字段查询" 小节有描述. 这两个方法的返回值都是结果集对象,因此结果集可以进行链式处理: Poll.objects.filter( ...以一个初始结果集作为参数, 然后进行过滤, 再进行排除, 再进行另一个过滤. 这样得到的最终结果就一个问题开头单词是 "What", 发布日期在 2005年1月1日至今的所有民意测验的集合. 每个结果集都是一个独一无二的对象. 以上操作的每一步都生成了一个新的结果集: q1 = Poll.objects.filter(question__startswith="What") 这三步生成了三个结果集; 一个初始结果集包含所有的以"What"开头的民意测验, 两个初始结果集的子集(一个排除条件,一个过滤条件).对原始结果集的改进过程并没有影响到原始的结果集. 值得注意的是结果集的创建根本没有访问数据库.只有当对结果集取值时才会访问数据库. 字段查询以 field__lookuptype (注意是双下线)形式进行基本的字段查询,举例来说: polls.objects.filter(pub_date__lte=datetime.now()) 该查询翻译成SQL就是: SELECT * FROM polls_polls WHERE pub_date <= NOW(); 实现细节 Python 能够在定义函数时接受任意的 name-value(names和values均可以在运行时通过计算得到)参数. 要了解更多信息,参阅官方 Python 教程中的关键字参数 . DB API 支持下列查找类型:
如果未提供查找类型, 系统就认为查找类型是 exact . 下面两个语句是等价的: Poll.objects.get(id=14) 查询允许多个条件参数, 逗号分隔的多个条件参数会被 "AND" 起来使用: polls.objects.filter( ...得到2005年1月公布的带有一个"Would"开头的问题的所有民意测验. 为了使用更加方便, 还提供有一个 pk 查找类型, 可以翻译成 (primary_key)__exact. 在这个民意测试的例子里, 下面两个语句是等价的.: polls.get_object(id__exact=3) pk 也可以通过连接进行查询. 在这个民意测试的例子里, 下面两个语句是等价的: choices.objects.filter(poll__id__exact=3) 如果传递的关键字参数非法, 将引发 TypeError 异常. OR 查询关键字参数查询的各个条件都是 "AND" 关系. 如果你需要一个复杂的查询(举例来说,你需要一个 OR 语句), 你需要使用Q 对象. Q 对象是 django.core.meta.Q 的实例, 用来装载一系列关键字参数. 这些关键字参数就象指定给 get() 和 filter() 函数的关键字参数一样. 举例来说: Q(question__startswith='What') Q 对象可以使用 & 和 | 运算符进行组合. 当两个Q对象进行 & 或 | 运算时,会生成一个新的Q对象.举例来说语句: Q(question__startswith='Who') | Q(question__startswith='What') ... 生成一个新的 Q 对象表示这两个 "question__startswith" 查询条件的 "OR" 关系. 等同于下面的 SQL WHERE 子句: ... WHERE question LIKE 'Who%' OR question LIKE 'What%' 通过对多个 Q 对象的 & 和 | 运算你能得到任意复杂的查询语句. 也可以使用圆括号分组. 查询函数可以接受一个或多个 Q 对象作为参数.如果提供有多个 Q 对象参数, 它们将被 "AND" 到一起. 举例来说: polls.get_object( ... 翻译成 SQL 就是这样: SELECT * from polls WHERE question LIKE 'Who%' 如果需要,查询函数可以混合使用 Q 对象参数和关键字参数. 所有提供给查询函数的参数(不管是关键字参数还是Q对象)都被 "AND" 到一起. 如果提供了Q 对象作为参数,它就必须在其它关键字参数(如果有的话)的前面. 举例来说: polls.get_object( ... 这是一个合法的查询, 等价于前一个例子,不过: # INVALID QUERY ... 这个查询则不符合我们的规则,会引发异常. Q 对象也可以以 complex 关键字参数的形式使用. 举例来说: polls.get_object( 参阅 OR 查询示例 以阅读更多实例. 从结果集中取值只有通过取值操作才能得到结果集包含的对象.取值操作可以通过迭代,切片,或其它专门的函数来实现. 一个结果集就是一个可迭代对象.因此,可以通过一个循环来取出它的值: for p in Poll.objects.all(): 将使用 Poll 对象的 __repr__() 方法打印出所有的 Poll 对象. 一个结果集也可以被切片, 使用数组符号操作: fifth_poll = Poll.objects.all()[4] 结果集对象是惰性对象 - 也就是说,他们不是 真正的 包含他们表示对象的集合 (或列表). Python 的协议魔法让结果集看起来是一个可迭代,可切片的对象. 事实上在幕后, Django 使用了缓存技术.. 如果你真的需要一个列表, 你可以强制对一个惰性对象取值: querylist = list(Poll.objects.all()) 不过,最好不要这么做,尤其当一个结果集相当大时. 由于 Django 要创建每一个对象的内存表示,这将占用相当大的内存. 结果集及其缓存行为每个结果集都包含一个 cache. 对一个新创建的结果集来说, 缓存区是空的.当一个结果集第一次被取值, Django 会进行一次数据库查询,并将查询结果放入缓存中, 之后返回用户需要的数据. 后面的取值操作会使用缓存中的数据而不用再次访问数据库. 必须时刻记住:结果集具有缓存行为. 下面两行语句生成了两个临时的结果集,并进行了取值,之后舍弃: print [p for p in Poll.objects.all()] # Evaluate the Query Set 对一个小型的,低流量的站点来说,这不会造成严重问题.不过,对一个高访问量的站点来说,它双倍增加了数据库服务器的负担.另外,由于在两次操作之间可能有其它的用户增加或删除了投票,因此这两次操作得到结果可能并不相同. 要避免这个问题, 保存这个结果集并在后面重用该结果集: queryset = Poll.objects.all() |
专门的结果集取值函数下面这些函数也可以用来从一个结果集中取值.不同于迭代及切片操作,这些方法不具有缓存行为.每次使用这些函数,都会访问数据库. get(**kwargs)以下文描述的 "字段查询" 格式返回匹配查找参数的对象.如果没有找到符合给定参数的对象,会引发一个模块级的 DoesNotExist 异常. 如果找到不止一个对象,引发AssertionError 异常. count()返回结果集的行数.``count()`` 永远不会引发异常. 根据你使用的数据库引擎 (比如 PostgreSQL vs. MySQL), 它可能返回一个长整数而不是普通整数. in_bulk(id_list)接受一个 ID 列表作为参数, 返回一个字典(每个ID映射一个具有给定ID的对象实例). 也接受可选的关键字查询参数(参数格式在下面 "字段查询" 小节有描述),这里有一个例子,使用上面定义的Poll model. >>> Poll.objects.in_bulk([1]) latest(field_name=None)根据 model 的 'get_latest_by' 选项或可选的字段名参数返回最新的对象. 例子: >>> Poll.objects.latest() 关系 (连接)当你在 model 中定义了一个关系字段(也就是,一个ForeignKey, OneToOneField, 或 ManyToManyField). Django 使用关系字段的名字为 model 的每个实例添加一个描述符. 在访问对象或关联对象时, 这个描述符就象一个常规属性. 举例来说,mychoice.poll 会返回 Choice 实例对象关联的 Poll 对象. 通过下面的关系,连接可以以非显式的方式进行: choices.objects.filter(poll__slug="eggs") 得到一个Choice 对象列表, 这些对象关联的 Poll 对象的 slug 字段值为 eggs. 允许多级连接. 通过一个对象实例的便利函数(convenience functions)就可直接查询该对象的关联对象. 举例来说, 如果 p 是一个Poll 实例, p.choice_set() 将返回所有关联的 Choice 对象列表. 聪明的读者会注意到它等价于choices.objects.filter(poll__id=p.id), 只是更加清晰. 每一种关系类型会为关系中的每个对象自动创建一系列便利方法(类似 choice_set() 这样的方法).这些方法被双向创建, 这样被关联的对象就不需要明确的定义反向关系, 这一切都是自动完成的. One-to-one relationsone-to-one 关系中的每个对象拥有一个 get_relatedobjectname() 方法. 举例来说: class Place(meta.Model): 在上面的例子里, 每个 Place 会自动拥有一个 get_restaurant() 方法, 且每个Restaurant 会自动拥有一个 get_the_place() 方法. Many-to-one relations在 many-to-one 关系中, 关联对象(Many)会自动拥有一个 get_relatedobject() 方法. 被关联的对象(one)会自动拥有get_relatedobject(), get_relatedobject_list(), 和get_relatedobject_count() 方法 (功能与模块级的 get_object(),filter(), 和 get_count() 相同). 在上面的民意测试例子里, 一个 Poll 对象 p 自动拥有下列方法: p.get_choice() Choice 对象 c 则自动拥有下面的方法: c.get_poll() Many-to-many 关系Many-to-many 关系类似`Many-to-one relations`_, 它生成同样的方法集.例外的是关联对象的 get_relatedobject_list() 方法返回一个实例的列表而不是一个仅一个实例.因此,若Poll 和 Choice 是 many-to-many 关系, choice.get_poll_list() 将返回一个列表. 专门的结果集除 filter 和 exclude() 之外, Django 提供了一系列结果集处理方法, 修改结果的类型, 或修改 sql 查询在数据库执行的方式. order_by(*fields)根据 model 中提供 ordering tuple, 结果集会被自动排序. 不过, 排序也可以通过 order_by 方法显式的进行: Poll.objects.filter(pub_date__year=2005, 结果集将按降序排列 pub_date, 然后按升序排列 question."-pub_date" 中的负号表示降序(递减).要取随机序,使用"?", 象下面这样: Poll.objects.order_by=('?') 要按另一个表中的字段排序, 添加另一个表的名字和一个句点,象下面这样: Choice.objects.order_by=('Poll.pub_date', 'choice') 无法指定排序是否大小写敏感, 不管你的数据库后端如何排序, Django都会以大小写敏感的方式重新排序结果集.. (这样是不是会降低效率? 不能关掉这个特性么?) distinct()默认的, 一个结果集不会自动除去重复的行. 尤其当你进行跨关系查询时, 很容易出现重复的行. distinct() 返回一个除去了重复行的新的结果集,它等价于 SELECTDISTINCT SQL 语句. values(*fields)类似 filter(), 不过它返回一个字典的列表而不是 model 实例对象的列表. 它接受一个可选参数: fields, 这是一个字段名列表或tuple.如果你没有指定 fields, 每个字段都会返回.否则就只返回你指定的字段名和值.这里有一个例子,使用上面定义的Poll model >>> from datetime import datetime 当你知道你要取得哪些字段的值时并且你不需要那些 model实例对象的功能时,使用 values() 函数.它具有上佳的效率.. dates(field, kind, order='ASC')每个管理器拥有一个 dates() 方法, 它返回一个 datetime.datetime 对象的列表, 表示经过给定的过滤器(由kind 参数定义)过滤后的所有可用的 dates . field 是 model 模块中的一个 DateField 或 DateTimeField 属性名. kind 是 "year", "month" 或"day" 中的一个. 结果列表中的每个 datetime.datetime 对象被截短为给定的type 形式.
order 只能是 "ASC" 或 "DESC", 默认值是'ASC'. 它指定如何排序结果. 这里有一个例子, 使用上面定义的 Poll model >>> from datetime import datetime select_related()关系是数据库的根本, select_related() 方法"追踪" 所有的关系对象, 并将它们预先存放到一个简单的缓存中.这样当需要调用一个具有一对多关系的对象时就不必再次访问数据库.要做到这一点, 只需对一个结果集使用select_related() 方法.这可能导致(某些时候相当)大的(有可能是不必要的)查询, 但这却意味着后续的关系使用会快很多.(俺的建议:对一个频繁变化的多用户数据库,不要使用该参数) 举例来说, 上面的 Poll 和 Choice models 中, 如果你这样做: c = Choice.objects.select_related().get(id=5) 那么后面的 c.poll() 将不用访问数据库. 注意这个 select_related 方法会尽可能远的追踪外键. 如果你有下面的 models: class Poll(models.Model): 那么调用 SingleVotes.objects.select_related().get(id=4) 会缓存相关的 choice和 相关的 poll: >>> sv = SingleVotes.objects.select_related().get(id=4) extra(params, select, where, tables)有时候, Django 提供的查询语法不太够用. 为了满足这些边缘需求, Django 提供了 extra() 结果集修改器 - 一种提供额外查询参数的机制. 要注意这些额外的方式对不同的数据库引擎可能存在移植性问题.(因为你在显式的书写SQL语句),除非万不得已,尽量避免这样做: params下面描述的所有 额外-SQL 参数都必须是标准 Python 字符串格式(数据库引擎会自动用引号将它引起).``params`` 参数可以包含任意多个的SQL参数. selectselect 关键字参数允许你选择特定的字段. 它是一个字典(属性名与 SQL 语句的映射). 举例来说: Poll.objects.extra(
where 和 tables 都接受一个字符串列表作为它们的值.所有的where 参数都被 "AND" 到其它的查询条件中. 举例来说: Poll.objects.filter( 改变对象一旦你使用任何上面介绍的方法从数据库中得到一个对象, 改变这个对象就是相当容易的一件事情了.直接操作这些对象的字段,然后调用 save() 方法保存它们: >>> p = Poll.objects.get(id__exact=15) 创建对象通过创建一个新的实例并调用 save() 保存之,就可以创建一个新的数据记录(也就是 INSERT): >>> p = Poll(slug="eggs", 一个主键值为 None 的对象调用 save() 方法表示这是一个新记录,应该被插入到数据表中. 通过便利方法(convenience function)可以很容易的创建关联对象 (比如 Choices): >>> p.choice_set.create(choice="Over easy", votes=0) 这些 add_choice 方法于下面的函数等价(却更简单): >>> c = Choice(poll_id=p.id, choice="Over easy", votes=0) 注意当使用这种 add_foo()` 方法时, 你不要给 id 字段提供任何值, 也不要给保存关系的字段提供任何值.(此处是poll_id ). 这类 add_FOO() 方法总是返回刚刚创建的对象. 删除对象如同你想象的一样,删除对象的方法是 delete(). 该方法立即删除该对象并返回 None.比如: >>> c.delete() 通过使用同样的查询条件(参数), get_object 和其它查询方法也可以用来批量删除对象. 比如: >>> Polls.objects.filter(pub_date__year=2005).delete() 此举将删除2005年的所有民意测验数据. 注意 delete() 是唯一一个管理器对象不能直接访问的结果集方法. 这提供了一种安全机制,可以避免发生意外事故: 否则Poll.objects.delete(), 将删除 所有的 投票. 如果你 真的 想删除表中的所有数据,你只能这么做: Polls.objects.all().delete() 此举将从数据库中删除所有的 Poll 实例. 比较对象要比较两个 model 对象, 使用标准的 Python 比较运算符,即双等号: ==. (在幕后进行比较的是两个对象的 主键 的值). 仍然是上面的 Poll 例子, 下面两个语句是等价的: some_poll == other_poll 如果一个 model 的主键名字不是 ID, 没问题, 比较总是发生在主键之间,不管它叫什么名字. 举例来说, 如果一个 model 的主键字段叫 name, 则下面这两个语句是等价的: some_obj == other_obj 其它实例方法除了 save(), delete() 和所有的 add_* 及get_* 关联对象方法之外,一个 model 对象可能还拥有下面的部分或全部方法: get_FOO_display()如果一个字段有 choices 选项集事, 这个对象将有一个 get_FOO_display() 方法.这里FOO 是该字段的名字. 这个方法返回一个 "human-readable" 的字段值. 举例来说, 下面的model中: GENDER_CHOICES = ( ...每个 Person 实例会拥有一个 get_gender_display() 的方法. 示例: >>> p = Person(name='John', gender='M') get_next_by_FOO(**kwargs) 和 get_previous_by_FOO(**kwargs)不存在 null=True 的每个 DateField 和 DateTimeField 自动拥有 get_next_by_FOO() 和 get_previous_by_FOO() 方法, 此处的 FOO 是字段的名字. 它们分别返回该字段的上一个对象和下一个对象. 如果上一对象或下一对象不存在,则抛出*DoesNotExist 异常. 这两个方法均接受可选关键字参数, 这些参数应该遵循上文中 "Field 查询" 中提到的格式. 注意如果遇到相同值的对象, 这些方法会使用 ID 字段进行检查. 这保证了没有一条记录会被跳过或重复记数.参阅 lookup API sample model_ ,那里有一个完整的例子. get_FOO_filename()对一个 FileField 对象来说, 它自动拥有一个 get_FOO_filename() 方法. 这里FOO 是字段名,它根据你的 MEDIA_ROOT 设置返回一个完整的路径名称. 注意 ImageField 技术上是 FileField 的一个子类, 因此每个有ImageField 的 model 自动拥有此方法. get_FOO_url()含有 FileField 字段的每个对象自动拥有一个 get_FOO_url() 方法,这里的FOO 是字段的名字. 该方法根据你的 MEDIA_URL 设置返回该文件的完整 URL ,如果MEDIA_URL 设置为空, 该方法返回一个空的字符串. get_FOO_size()含有 FileField 字段的每个对象自动拥有一个 get_FOO_filename() 方法, 这里的FOO 是字段的名字. 该方法返回文件的长度(字节数).(在后台, Django 使用 os.path.getsize.) save_FOO_file(filename, raw_contents)含有 FileField 字段的每个对象自动拥有一个 get_FOO_filename() 方法, 这里的FOO 是字段的名字. 该方法使用给定的文件名将文件保存到文件系统.. 如果同目录下已经有同名文件存在,Django 会在文件名后添加一个下划线(扩展名之前)直到文件名有效为止(也就是如果加了下划线还重名,就再加....直到没有重名为止). get_FOO_height() 和 get_FOO_width()含有 ImageField 字段的每个对象自动拥有 get_FOO_height() 和get_FOO_width() 方法, 这里的 FOO 是字段的名字. 它们返回相应的图片高度和宽度(整数,以像素计). |