在第5章里,咱们介绍了Django的数据层如何定义数据模型以及如何使用数据库API来建立、检索、更新以及删除记录 在这章里,咱们将向你介绍Django在这方面的一些更高级功能。html
先让咱们回忆一下在第五章里的关于书本(book)的数据模型:python
from django.db import models class Publisher(models.Model): name = models.CharField(max_length=30) address = models.CharField(max_length=50) city = models.CharField(max_length=60) state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField() def __unicode__(self): return self.name class Author(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) email = models.EmailField() def __unicode__(self): return u'%s %s' % (self.first_name, self.last_name) class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) publication_date = models.DateField() def __unicode__(self): return self.title
如咱们在第5章的讲解,获取数据库对象的特定字段的值只需直接使用属性。 例如,要肯定ID为50的书本的标题,咱们这样作:mysql
>>> from mysite.books.models import Book >>> b = Book.objects.get(id=50) >>> b.title u'The Django Book'
可是,在以前有一件咱们没说起到的是表现为ForeignKey
或 ManyToManyField
的关联对象字段,它们的做用稍有不一样。web
当你获取一个ForeignKey
字段时,你会获得相关的数据模型对象。 例如:sql
>>> b = Book.objects.get(id=50) >>> b.publisher <Publisher: Apress Publishing> >>> b.publisher.website u'http://www.apress.com/'
对于用`` ForeignKey`` 来定义的关系来讲,在关系的另外一端也能反向的追溯回来,只不过因为不对称性的关系而稍有不一样。 经过一个`` publisher`` 对象,直接获取 books ,用 publisher.book_set.all() ,以下:shell
>>> p = Publisher.objects.get(name='Apress Publishing') >>> p.book_set.all() [<Book: The Django Book>, <Book: Dive Into Python>, ...]
实际上,book_set
只是一个 QuerySet
(参考第5章的介绍),因此它能够像QuerySet
同样,能实现数据过滤和分切,例如:数据库
>>> p = Publisher.objects.get(name='Apress Publishing') >>> p.book_set.filter(name__icontains='django') [<Book: The Django Book>, <Book: Pro Django>]
属性名称book_set
是由模型名称的小写(如book)加_set
组成的。django
多对多和外键工做方式相同,只不过咱们处理的是QuerySet
而不是模型实例。 例如,这里是如何查看书籍的做者:服务器
>>> b = Book.objects.get(id=50) >>> b.authors.all() [<Author: Adrian Holovaty>, <Author: Jacob Kaplan-Moss>] >>> b.authors.filter(first_name='Adrian') [<Author: Adrian Holovaty>] >>> b.authors.filter(first_name='Adam') []
反向查询也能够。 要查看一个做者的全部书籍,使用author.book_set
,就如这样:架构
>>> a = Author.objects.get(first_name='Adrian', last_name='Holovaty') >>> a.book_set.all() [<Book: The Django Book>, <Book: Adrian's Other Book>]
这里,就像使用 ForeignKey
字段同样,属性名book_set
是在数据模型(model)名后追加_set
。
在咱们在第5章介绍 syncdb
这个命令时, 咱们注意到 syncdb
仅仅建立数据库里尚未的表,它 并不 对你数据模型的修改进行同步,也不处理数据模型的删除。 若是你新增或修改数据模型里的字段,或是删除了一个数据模型,你须要手动在数据库里进行相应的修改。 这段将解释了具体怎么作:
当处理模型修改的时候,将Django的数据库层的工做流程铭记于心是很重要的。
若是模型包含一个不曾在数据库里创建的字段,Django会报出错信息。 当你第一次用Django的数据库API请求表中不存在的字段时会致使错误(就是说,它会在运行时出错,而不是编译时)。
Django不关心数据库表中是否存在未在模型中定义的列。
Django不关心数据库中是否存在未被模型表示的表格。
改变模型的模式架构意味着须要按照顺序更改Python代码和数据库。
当要向一个产品设置表(或者说是model)添加一个字段的时候,要使用的技巧是利用Django不关心表里是否包含model里所没有的列的特性。 策略就是如今数据库里加入字段,而后同步Django的模型以包含新字段。
然而 这里有一个鸡生蛋蛋生鸡的问题 ,因为要想了解新增列的SQL语句,你须要使用Django的 manage.py sqlall
命令进行查看 ,而这又须要字段已经在模型里存在了。 (注意:你并 不是非得使用与Django相同的SQL语句建立新的字段,可是这样作确实是一个好主意 ,它能让一切都保持同步。)
这个鸡-蛋的问题的解决方法是在开发者环境里而不是发布环境里实现这个变化。 (你正使用的是测试/开发环境,对吧?)下面是具体的实施步骤。
首先,进入开发环境(也就是说,不是在发布环境里):
在你的模型里添加字段。
运行 manage.py sqlall [yourapp]
来测试模型新的 CREATE TABLE
语句。 注意为新字段的列定义。
开启你的数据库的交互命令界面(好比, psql
或mysql
, 或者可使用 manage.py dbshell
)。 执行 ALTER TABLE
语句来添加新列。
使用Python的manage.py shell
,经过导入模型和选中表单(例如, MyModel.objects.all()[:5]
)来验证新的字段是否被正确的添加 ,若是一切顺利,全部的语句都不会报错。
而后在你的产品服务器上再实施一遍这些步骤。
启动数据库的交互界面。
执行在开发环境步骤中,第三步的ALTER TABLE
语句。
将新的字段加入到模型中。 若是你使用了某种版本控制工具,而且在第一步中,已经提交了你在开发环境上的修改,如今,能够在生产环境中更新你的代码了(例如,若是你使用Subversion,执行svn update
。
从新启动Web server,使修改生效。
让咱们实践下,好比添加一个num_pages字段到第五章中Book模型。首先,咱们会把开发环境中的模型改为以下形式:
class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) publication_date = models.DateField() **num_pages = models.IntegerField(blank=True, null=True)** def __unicode__(self): return self.title
(注意 阅读第六章的“设置可选字段”以及本章下面的“添加非空列”小节以了解咱们在这里添加blank=True
和null=True
的缘由。)
而后,咱们运行命令manage.py sqlall books 来查看CREATE TABLE语句。 语句的具体内容取决与你所使用的数据库, 大概是这个样子:
CREATE TABLE "books_book" ( "id" serial NOT NULL PRIMARY KEY, "title" varchar(100) NOT NULL, "publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id"), "publication_date" date NOT NULL, "num_pages" integer NULL );
新加的字段被这样表示:
"num_pages" integer NULL
接下来,咱们要在开发环境上运行数据库客户端,若是是PostgreSQL,运行 psql,,而后,我执行以下语句。
ALTER TABLE books_book ADD COLUMN num_pages integer;
添加 非NULL 字段
这里有个微妙之处值得一提。 在咱们添加字段num_pages的时候,咱们使用了 blank=True 和 null=True 选项。 这是由于在咱们第一次建立它的时候,这个数据库字段会含有空值。
然而,想要添加不能含有空值的字段也是能够的。 要想实现这样的效果,你必须先建立 NULL 型的字段,而后将该字段的值填充为某个默认值,而后再将该字段改成 NOT NULL 型。 例如:
BEGIN; ALTER TABLE books_book ADD COLUMN num_pages integer; UPDATE books_book SET num_pages=0; ALTER TABLE books_book ALTER COLUMN num_pages SET NOT NULL; COMMIT;
若是你这样作,记得你不要在模型中添加 blank=True 和 null=True 选项。
执行ALTER TABLE以后,咱们要验证一下修改结果是否正确。启动python并执行下面的代码:
>>> from mysite.books.models import Book >>> Book.objects.all()[:5]
若是没有异常发生,咱们将切换到生产服务器,而后在生产环境的数据库中执行命令ALTER TABLE
而后咱们更新生产环境中的模型,最后重启web服务器。
从Model中删除一个字段要比添加容易得多。 删除字段,仅仅只要如下几个步骤:
删除字段,而后从新启动你的web服务器。
用如下命令从数据库中删除字段:
ALTER TABLE books_book DROP COLUMN num_pages;
请保证操做的顺序正确。 若是你先从数据库中删除字段,Django将会当即抛出异常。
因为多对多关联字段不一样于普通字段,因此删除操做是不一样的。
从你的模型中删除
ManyToManyField
,而后重启web服务器。用下面的命令从数据库删除关联表:
DROP TABLE books_book_authors;
像上面同样,注意操做的顺序。
删除整个模型要比删除一个字段容易。 删除一个模型只要如下几个步骤:
从文件中删除你想要删除的模型,而后重启web 服务器
models.py
而后用如下命令从数据库中删除表:
DROP TABLE books_book;
当你须要从数据库中删除任何有依赖的表时要注意(也就是任何与表books_book有外键的表 )。
正如在前面部分,必定要按这样的顺序作。
在语句Book.objects.all()
中,objects
是一个特殊的属性,须要经过它查询数据库。 在第5章,咱们只是简要地说这是模块的manager 。如今是时候深刻了解managers是什么和如何使用了。
总之,模块manager是一个对象,Django模块经过它进行数据库查询。 每一个Django模块至少有一个manager,你能够建立自定义manager以定制数据库访问。
下面是你建立自定义manager的两个缘由: 增长额外的manager方法,和/或修manager返回的初始QuerySet。
增长额外的manager方法是为模块添加表级功能的首选办法。 (至于行级功能,也就是只做用于模型对象实例的函数,一下子将在本章后面解释。)
例如,咱们为Book模型定义了一个title_count()方法,它须要一个关键字,返回包含这个关键字的书的数量。 (这个例子有点牵强,不过它能够说明managers如何工做。)
# models.py from django.db import models # ... Author and Publisher models here ... **class BookManager(models.Manager):** **def title_count(self, keyword):** **return self.filter(title__icontains=keyword).count()** class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) publication_date = models.DateField() num_pages = models.IntegerField(blank=True, null=True) **objects = BookManager()** def __unicode__(self): return self.title
有了这个manager,咱们如今能够这样作:
>>> Book.objects.title_count('django') 4 >>> Book.objects.title_count('python') 18
下面是编码该注意的一些地方:
咱们创建了一个BookManager类,它继承了django.db.models.Manager。这个类只有一个title_count()方法,用来作统计。 注意,这个方法使用了self.filter(),此处self指manager自己。
咱们把BookManager()赋值给模型的objects属性。 它将取代模型的默认manager(objects
)若是咱们没有特别定义,它将会被自动建立。 咱们把它命名为objects,这是为了与自动建立的manager保持一致。
为何咱们要添加一个title_count()方法呢?是为了将常用的查询进行封装,这样咱们就没必要重复编码了。
manager的基本QuerySet返回系统中的全部对象。 例如,`` Book.objects.all()`` 返回数据库book中的全部书本。
咱们能够经过覆盖Manager.get_query_set()方法来重写manager的基本QuerySet。 get_query_set()按照你的要求返回一个QuerySet。
例如,下面的模型有* 两个* manager。一个返回全部对像,另外一个只返回做者是Roald Dahl的书。
from django.db import models **# First, define the Manager subclass.** **class DahlBookManager(models.Manager):** **def get_query_set(self):** **return super(DahlBookManager, self).get_query_set().filter(author='Roald Dahl')** **# Then hook it into the Book model explicitly.** class Book(models.Model): title = models.CharField(max_length=100) author = models.CharField(max_length=50) # ... **objects = models.Manager() # The default manager.** **dahl_objects = DahlBookManager() # The Dahl-specific manager.**
在这个示例模型中,Book.objects.all()返回了数据库中的全部书本,而 Book.dahl_objects.all()只返回了一本. 注意咱们明确地将objects设置成manager的实例,由于若是咱们不这么作,那么惟一可用的manager就将是dah1_objects。
固然,因为get_query_set()返回的是一个QuerySet对象,因此咱们可使用filter(),exclude()和其余一切QuerySet的方法。 像这些语法都是正确的:
Book.dahl_objects.all() Book.dahl_objects.filter(title='Matilda') Book.dahl_objects.count()
这个例子也指出了其余有趣的技术: 在同一个模型中使用多个manager。 只要你愿意,你能够为你的模型添加多个manager()实例。 这是一个为模型添加通用滤器的简单方法。
例如:
class MaleManager(models.Manager): def get_query_set(self): return super(MaleManager, self).get_query_set().filter(sex='M') class FemaleManager(models.Manager): def get_query_set(self): return super(FemaleManager, self).get_query_set().filter(sex='F') class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) sex = models.CharField(max_length=1, choices=(('M', 'Male'), ('F', 'Female'))) people = models.Manager() men = MaleManager() women = FemaleManager()
这个例子容许你执行`` Person.men.all()`` ,`` Person.women.all()`` ,`` Person.people.all()`` 查询,生成你想要的结果。
若是你使用自定义的Manager
对象,请注意,Django遇到的第一个Manager(以它在模型中被定义的位置为准)会有一个特殊状态。 Django将会把第一个Manager
定义为默认Manager
,Django的许多部分(可是不包括admin应用)将会明确地为模型使用这个manager
。 结论是,你应该当心地选择你的默认manager。由于覆盖get_query_set()
了,你可能接受到一个无用的返回对像,你必须避免这种状况。
为了给你的对像添加一个行级功能,那就定义一个自定义方法。 有鉴于manager常常被用来用一些整表操做(table-wide),模型方法应该只对特殊模型实例起做用。
这是一项在模型的一个地方集中业务逻辑的技术。
最好用例子来解释一下。 这个模型有一些自定义方法:
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 U.S.-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') def _get_full_name(self): "Returns the person's full name." return u'%s %s' % (self.first_name, self.last_name) full_name = property(_get_full_name)
例子中的最后一个方法是一个property。 想了解更多关于属性的信息请访问http://www.python.org/download/releases/2.2/descrintro/#property
这是用法的实例:
>>> p = Person.objects.get(first_name='Barack', last_name='Obama') >>> p.birth_date datetime.date(1961, 8, 4) >>> p.baby_boomer_status() 'Baby boomer' >>> p.is_midwestern() True >>> p.full_name # Note this isn't a method -- it's treated as an attribute u'Barack Obama'
有时候你会发现Django数据库API带给你的也只有这么多,那你能够为你的数据库写一些自定义SQL查询。 你能够经过导入django.db.connection
对像来轻松实现,它表明当前数据库链接。 要使用它,须要经过connection.cursor()
获得一个游标对像。 而后,使用cursor.execute(sql, [params])
来执行SQL语句,使用cursor.fetchone()
或者cursor.fetchall()
来返回记录集。 例如:
>>> from django.db import connection >>> cursor = connection.cursor() >>> cursor.execute(""" ... SELECT DISTINCT first_name ... FROM people_person ... WHERE last_name = %s""", ['Lennon']) >>> row = cursor.fetchone() >>> print row ['John']
connection
和cursor
几乎实现了标准Python DB-API,你能够访问` http://www.python.org/peps/pep-0249.html <http://www.python.org/peps/pep-0249.html>`__来获取更多信息。 若是你对Python DB-API不熟悉,请注意在cursor.execute()
的SQL语句中使用`` “%s”`` ,而不要在SQL内直接添加参数。 若是你使用这项技术,数据库基础库将会自动添加引号,同时在必要的状况下转意你的参数。
不要把你的视图代码和django.db.connection语句混杂在一块儿,把它们放在自定义模型或者自定义manager方法中是个不错的主意。 好比,上面的例子能够被整合成一个自定义manager方法,就像这样:
from django.db import connection, models class PersonManager(models.Manager): def first_names(self, last_name): cursor = connection.cursor() cursor.execute(""" SELECT DISTINCT first_name FROM people_person WHERE last_name = %s""", [last_name]) return [row[0] for row in cursor.fetchone()] class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) objects = PersonManager()
而后这样使用:
>>> Person.objects.first_names('Lennon') ['John', 'Cynthia']