Django模型:基本数据访问
一旦你创建了一个模型,Django自动提供了一个高级的Python API来处理这些模型。 通过在虚拟环境中运行python manage.py shell并键入以下命令来试用它:
>>> from books.models import Publisher
>>> p1 = Publisher(name='Apress', address='2855 Telegraph Avenue',
... city='Berkeley', state_province='CA', country='U.S.A.',
... website='http://www.apress.com/')
>>> p1.save()
>>> p2 = Publisher(name="O'Reilly", address='10 Fawcett St.',
... city='Cambridge', state_province='MA', country='U.S.A.',
... website='http://www.oreilly.com/')
>>> p2.save()
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
<QuerySet [<Publisher: Publisher object>, <Publisher: Publisher object>]>
这几行代码完成了很多。 以下是亮点:
-
首先,我们导入我们的Publisher模型类。 这让我们与包含发布者的数据库表进行交互。
-
我们通过实例化每个字段的值来创建Publisher对象 - 名称,地址等。
-
要将对象保存到数据库,请调用save()方法。 在幕后,Django在这里执行SQL INSERT语句。
-
要从数据库检索发布者,请使用属性Publisher.objects,您可以将其视为一组所有发布者。 使用语句Publisher.objects.all()获取数据库中所有Publisher对象的列表。 在幕后,Django在这里执行SQL SELECT语句。
有一件事值得一提,如果从这个例子中不清楚的话。 在使用Django模型API创建对象时,在调用save()方法之前,Django不会将对象保存到数据库中:
p1 = Publisher(...)
# At this point, p1 is not saved to the database yet!
p1.save()
# Now it is.
如果要创建一个对象并将其单步保存到数据库中,请使用objects.create()方法。 这个例子等同于上面的例子:
>>> p1 = Publisher.objects.create(name='Apress',
... address='2855 Telegraph Avenue',
... city='Berkeley', state_province='CA', country='U.S.A.',
... website='http://www.apress.com/')
>>> p2 = Publisher.objects.create(name="O'Reilly",
... address='10 Fawcett St.', city='Cambridge',
... state_province='MA', country='U.S.A.',
... website='http://www.oreilly.com/')
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
<QuerySet [<Publisher: Publisher object>, <Publisher: Publisher object>]>
当然,你可以用Django数据库API做很多事情 - 但首先,让我们来处理一个小麻烦。
添加模型字符串表示
当我们打印出版商名单时,我们所得到的只是这个无益的展示,使得很难区分发布商对象:
<QuerySet [<Publisher: Publisher object>, <Publisher: Publisher object>]>
我们可以通过在我们的Publisher类中添加一个名为__str __()的方法来解决这个问题。 __str __()方法告诉Python如何显示对象的可读表示。 你可以通过在这三个模型中添加一个__str __()方法来看到这一点:
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 __str__(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 __str__(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 __str__(self):
return self.title
正如你所看到的,一个__str __()方法可以做任何它需要做的事情来返回一个对象的表示。 这里,Publisher和Book的__str __()方法分别简单地返回对象的名称和标题,但是作者的__str __()稍微复杂一点 - 它将first_name和last_name字段拼在一起,用空格分隔。 __str __()的唯一要求是它返回一个字符串对象。 如果__str __()没有返回一个字符串对象 - 如果说它返回一个整数 - 那么Python会引发一个类型错误:
TypeError: __str__ returned non-string (type int).
为使__str __()更改生效,请退出Python shell并使用python manage.py shell重新输入。 (这是使代码更改生效的最简单方法。)现在,Publisher对象列表更容易理解:
>>> from books.models import Publisher
>>> publisher_list = Publisher.objects.all()
>>> publisher_list
<QuerySet [<Publisher: Apress>, <Publisher: O'Reilly>]>
确保你定义的任何模型都有一个__str __()方法 - 不仅为了您在使用交互式解释器时的方便,而且还因为Django在需要显示对象时在几个地方使用__str __()的输出。 最后,请注意__str __()是向模型添加行为的一个好例子。 Django模型描述的不仅仅是对象的数据库表格布局, 它还描述了对象知道如何做的任何功能。 __str __()就是这种功能的一个例子 - 一个模型知道如何显示自己。
插入和更新数据
您已经看到了这一点:为了在数据库中插入一行,首先使用关键字参数创建模型的一个实例,如下所示:
>>> p = Publisher(name='GNW Independent Publishing',
... address='123 Some Street',
... city='Hamilton',
... state_province='NSW',
... country='AUSTRALIA',
... website='http://djangobook.com/')
正如我们上面提到的,这个实例化模型类的行为不会触及数据库。 除非您调用save(),否则记录不会保存到数据库中,如下所示:
>>> p.save()
在SQL中,这大致可以翻译成以下内容:
INSERT INTO books_publisher
(name, address, city, state_province, country, website)
VALUES
('GNW Independent Publishing', '123 Some Street', 'Hamilton', 'NSW',
'Australia', 'http://djangobook.com/');
因为Publisher模型使用自动递增主键id,所以对save()的初始调用还有一件事情:它计算记录的主键值并将其设置为实例上的id属性:
>>> p.id
3 # this will differ based on your own data
随后对save()的调用将保存记录,而不会创建新记录(即,执行SQL UPDATE语句而不是INSERT):
>>> p.name = 'GNW Independent Publishing'
>>> p.save()
前面的save()语句将导致大致如下的SQL:
UPDATE books_publisher SET
name = 'GNW Independent Publishing',
address='123 Some Street',
city='Hamilton',
state_province='NSW',
country='AUSTRALIA',
website='http://djangobook.com/')
WHERE id = 3;
是的,请注意,所有的字段将被更新,而不仅仅是被更改的字段。 根据您的应用程序,这可能会导致竞争条件。 请参阅下面的“在一个语句中更新多个对象”以了解如何执行此(略有不同)的查询:
UPDATE books_publisher SET
name = 'GNW Independent Publishing'
WHERE id=3;
选择对象
知道如何创建和更新数据库记录是必不可少的,但是有可能您将构建的Web应用程序将更多地查询现有对象而不是创建新对象。 我们已经看到了一种方法来检索给定模型的每个记录:
>>> Publisher.objects.all()
<QuerySet [<Publisher: Apress>, <Publisher: O'Reilly>, <Publisher: GNW Independent Publishing>]>
这大致转换为这个SQL:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher;
请注意,Django在查找数据时不使用SELECT *,而是显式地列出所有的字段。 这是设计的:在某些情况下,SELECT *可能会变慢,更重要的是,列表字段更接近Python的Zen的一个宗旨:“显式比隐式更好”。关于Python的更多信息,请尝试键入 在Python提示符下导入。
让我们仔细看一下这个Publisher.objects.all()行的每个部分:
-
首先,我们有我们定义的模型,Publisher。 这里没有什么值得惊讶的:当你想查询数据时,你可以使用该数据的模型。
-
接下来,我们有对象属性。 这被称为管理者。 第9章将详细讨论管理者。现在,您需要知道的一点是,管理者将负责所有的数据表操作,包括最重要的数据查询。 所有模型自动获得对象管理器; 随时可以使用它来查找模型实例。
-
最后,我们有all()。 这是对象管理器上的一个方法,该方法返回QuerySet中的数据库中的所有行 - 一个对象,表示数据库中特定的一组行。 附录C详细讨论QuerySet。
任何数据库查询都将遵循这个通用模式 - 我们将调用连接到我们要查询的模型的管理器上的方法。
过滤数据
当然,很少要一次从数据库中选择一切; 在大多数情况下,你会想要处理你的数据的一个子集。 在Django API中,可以使用filter()方法过滤数据:
>>> Publisher.objects.filter(name='Apress')
<QuerySet [<Publisher: Apress>]>
filter()接收关键字参数,并将其转换为相应的SQL WHERE子句。 前面的例子会被翻译成这样的东西:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name = 'Apress';
您可以将多个参数传递给filter()以进一步缩小范围:
>>> Publisher.objects.filter(country="U.S.A.",
state_province="CA")
<QuerySet [<Publisher: Apress>]>
这些多个参数被转换成SQL AND子句。 因此,代码片段中的例子翻译成以下内容:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = 'U.S.A.'
AND state_province = 'CA';
请注意,默认情况下,查找使用SQL =运算符来执行完全匹配查找。 其他查找类型可用:
>>> Publisher.objects.filter(name__contains="press")
<QuerySet [<Publisher: Apress>]>
这是名称和包含之间的双下划线。 与Python本身一样,Django使用双下划线来表示正在发生“魔术” - 在这里,__contains部分被Django翻译成SQL LIKE语句:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name LIKE '%press%';
许多其他类型的查找可用,包括icontains(不区分大小写的LIKE),startswith和endswith和range(SQL BETWEEN查询)。 附录C详细描述了所有这些查找类型。
检索单个对象
上面的filter()例子都返回了一个QuerySet,你可以像列表一样处理。 有时,与QuerySet相比,仅提取单个对象更方便。 这就是get()方法的用途:
>>> Publisher.objects.get(name="Apress")
<Publisher: Apress>
而不是一个QuerySet,只返回一个对象。 因此,导致多个对象的查询将导致异常:
>>> Publisher.objects.get(country="U.S.A.")
Traceback (most recent call last):
...
books.models.MultipleObjectsReturned: get() returned more than one Publisher - it returned 2!
不返回对象的查询也会导致异常:
>>> Publisher.objects.get(name="Penguin")
Traceback (most recent call last):
...
books.models.DoesNotExist: Publisher matching query does not exist.
DoesNotExist异常是模型类的一个属性 - Publisher.DoesNotExist。 在您的应用程序中,您将要捕获这些异常,如下所示:
try:
p = Publisher.objects.get(name='Apress')
except Publisher.DoesNotExist:
print ("Apress isn't in the database yet.")
else:
print ("Apress is in the database.")
数据排序
当你玩弄前面的例子时,你可能会发现这些对象是以一种看似随机的顺序被返回的。 这些不是你想象的东西; 到目前为止,我们还没有告诉数据库如何排序结果,所以我们只是简单地以数据库选择的任意顺序取回数据。 在你的Django应用程序中,你可能想要按照一定的值排序你的结果 - 比如按字母顺序排列。 要做到这一点,请使用order_by()方法:
>>> Publisher.objects.order_by("name")
<QuerySet [<Publisher: Apress>, <Publisher: GNW Independent Publishing>, <Publisher: O'Reilly>]>
这与前面的all()例子看起来没什么两样,但是SQL现在包含了一个特定的顺序:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name;
您可以通过您喜欢的任何领域进行排序:
>>> Publisher.objects.order_by("address")
<QuerySet [<Publisher: O'Reilly>, <Publisher: GNW Independent Publishing>, <Publisher: Apress>]>
>>> Publisher.objects.order_by("state_province")
<QuerySet [<Publisher: Apress>, <Publisher: O'Reilly>, <Publisher: GNW Independent Publishing>]>
要通过多个字段进行排序(其中第二个字段用于消除第一个字段相同的情况下的排序),请使用多个参数:
>>> Publisher.objects.order_by("state_province", "address")
<QuerySet [<Publisher: Apress>, <Publisher: O'Reilly>, <Publisher: GNW Independent Publishing>]>
您也可以通过在字段名称前添加一个“ - ”(这是一个负号)来指定反向排序:
>>> Publisher.objects.order_by("-name")
<QuerySet [<Publisher: O'Reilly>, <Publisher: GNW Independent Publishing>, <Publisher: Apress>]>
尽管这种灵活性是有用的,但是使用order_by()始终是相当重复的。 大多数情况下,你会有一个特定的领域,你通常要排序。 在这些情况下,Django可以让你在模型中指定一个默认的顺序:
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 __str__(self):
return self.name
class Meta:
ordering = ['name']
在这里,我介绍了一个新的概念:Meta类,它是嵌入到Publisher类定义中的一个类(即,缩进到类Publisher中)。 您可以在任何模型上使用这个Meta类来指定各种模型特定的选项。 附录B提供了Meta选项的完整参考,但现在我们关注排序选项。 如果指定了这个参数,它会告诉Django,除非使用order_by()明确给出了一个排序,否则每当使用Django数据库API检索它时,所有Publisher对象都应该按名称排序。
链接查找
您已经了解了如何过滤数据,并且您已经了解如何对数据进行排序。 当然,你通常都需要这样做。 在这些情况下,您只需将查询“链接”在一起即可:
>>> Publisher.objects.filter(country="U.S.A.").order_by("-name")
<QuerySet [<Publisher: O'Reilly>, <Publisher: Apress>]>
正如你可能期望的那样,这转换为一个SQL查询同时具有一个WHERE和一个ORDER BY:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE country = 'U.S.A'
ORDER BY name DESC;
切片数据
另一个常见的需求是只查找固定数量的行。 想象一下,您的数据库中有数千个发布者,但您只想显示第一个发布者。 谢天谢地,Django QuerySet可以像Python列表一样对待。 这意味着你可以使用Python的标准列表切片语法来完成这个工作:
>>> Publisher.objects.order_by('name')[0]
<Publisher: Apress>
这大致转化为:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
LIMIT 1;
同样,您可以使用Python的范围切片语法检索特定的数据子集:
>>> Publisher.objects.order_by('name')[0:2]
这返回两个对象,大致转换为:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
ORDER BY name
OFFSET 0 LIMIT 2;
请注意,不支持反向切片:
>>> Publisher.objects.order_by('name')[-1]
Traceback (most recent call last):
...
AssertionError: Negative indexing is not supported.
虽然这很容易解决。 只需更改order_by()语句,如下所示:
>>> Publisher.objects.order_by('-name')[0]
在一个语句中更新多个对象
我在“插入和更新数据”一节中指出,模型save()方法更新了一行中的所有列。 根据您的应用程序,您可能只想更新列的一个子集。 例如,假设我们要更新Apress Publisher,将名称从“Apress”更改为“Apress Publishing”。 使用save(),它看起来像这样:
>>> p = Publisher.objects.get(name='Apress')
>>> p.name = 'Apress Publishing'
>>> p.save()
这大致转换为以下SQL:
SELECT id, name, address, city, state_province, country, website
FROM books_publisher
WHERE name = 'Apress';
UPDATE books_publisher SET
name = 'Apress Publishing',
address = '2855 Telegraph Ave.',
city = 'Berkeley',
state_province = 'CA',
country = 'U.S.A.',
website = 'http://www.apress.com'
WHERE id = 1;
(注意,这个例子假定Apress的发布者ID是1.)在这个例子中你可以看到Django的save()方法设置了所有的列值,而不仅仅是名称列。 如果您处于其他数据库列由于其他进程而可能发生更改的环境中,只更改需要更改的列是明智的。 为此,请在QuerySet对象上使用update()方法。 这是一个例子:
>>> Publisher.objects.filter(id=1).update(name='Apress Publishing')
这里的SQL翻译效率更高,没有竞争条件:
UPDATE books_publisher
SET name = 'Apress Publishing'
WHERE id = 1;
update()方法适用于任何QuerySet,这意味着您可以批量编辑多个记录。 以下是你如何从“U.S.A”改变USA。 到每个发布商记录中的USA:
>>> Publisher.objects.all().update(country='USA')
3
update()方法有一个返回值 - 一个表示有多少记录被改变的整数。 在上面的例子中,我们得到了3。
删除对象
要从数据库中删除一个对象,只需调用对象的delete()方法:
>>> p = Publisher.objects.get(name="O'Reilly")
>>> p.delete()
(1, {'books.Publisher': 1})
>>> Publisher.objects.all()
<QuerySet [<Publisher: Apress>, <Publisher: GNW Independent Publishing>]>
注意Django在删除对象时的返回值 - Django首先列出将受到影响的记录的总数(在本例中为1)以及包含每个受影响的模型(表)的字典以及每个记录中删除了多少条记录 表。
您也可以通过对任何QuerySet的结果调用delete()批量删除对象。 这与我们在上一节中展示的update()方法类似:
>>> Publisher.objects.filter(country='USA').delete()
(1, {'books.Publisher': 1})
>>> Publisher.objects.all().delete()
(1, {'books.Publisher': 1})
>>> Publisher.objects.all()
<QuerySet []>
小心删除你的数据! 为防止删除特定表中的所有数据,Django要求您明确使用all()(如果要删除表中的所有内容)。 例如,这不起作用:
>>> Publisher.objects.delete()
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'Manager' object has no attribute 'delete'
但是,如果你添加了all()方法,它将会工作:
>>> Publisher.objects.all().delete()
请注意,如果您只是删除了一部分数据,则不需要包含all()。 重复前面的例子:
>>> Publisher.objects.filter(country='USA').delete()
下一步是什么?
阅读本章后,您对Django模型有足够的了解,可以编写基本的数据库应用程序。 第9章将提供关于Django数据库层的更高级用法的一些信息。 一旦你定义了你的模型,下一步就是用数据填充你的数据库。 您可能会有遗留数据,在这种情况下,第21章将为您提供有关与遗留数据库集成的建议。 您可能会依靠网站用户提供您的数据,在这种情况下,第6章将教您如何处理用户提交的表单数据。 但在某些情况下,您或您的团队可能需要手动输入数据,在这种情况下,有一个基于Web的界面可以输入和管理数据。 下一章将介绍Django的管理界面,正是因为这个原因才存在这个界面。