Python 学习笔记

Django模型:基本数据访问

2018-01-30  本文已影响20人  大爷的二舅

一旦你创建了一个模型,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>]>

这几行代码完成了很多。 以下是亮点:

有一件事值得一提,如果从这个例子中不清楚的话。 在使用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()行的每个部分:

任何数据库查询都将遵循这个通用模式 - 我们将调用连接到我们要查询的模型的管理器上的方法。

过滤数据

当然,很少要一次从数据库中选择一切; 在大多数情况下,你会想要处理你的数据的一个子集。 在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的管理界面,正是因为这个原因才存在这个界面。

上一篇下一篇

猜你喜欢

热点阅读