Django模型
在当代 Web 应用中,主观逻辑经常牵涉到与数据库的交互。 数据库驱动网站 在后台连接数据库服务器,从中取出一些数据,然后在 Web 页面用漂亮的格式展示这些数据。 这个网站也可能会向访问者提供修改数据库数据的方法。由于先天具备 Python 简单而强大的数据库查询执行方法,Django 非常适合开发数据库驱动网站。 本章深入介绍了该功能: Django 数据库层。
示例版本:python 3.6 + django 2.2.2 。
1. 数据库配置
在settings.py中配置数据库信息,这里mysql配置信息参数如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'spiderdb', 'USER': 'spider', 'PASSWORD': '123456', 'HOST': '127.0.0.1', 'PORT': '3306', } }
2. 第一个应用程序
创建一个 Django app-一个包含模型,视图和Django代码,并且形式为独立Python包的完整Django应用。创建之前先解释下 django中的project和app的区别。
一个project包含很多个Django app以及对它们的配置。技术上,project的作用是提供配置文件,比方说哪里定义数据库连接信息, 安装的app列表, TEMPLATE_DIRS ,等等。一个app是一套Django功能的集合,通常包括模型和视图,按Python的包结构的方式存在。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 执行命令 python manage.py startapp books # 创建的app目录结构 books/ __init__.py models.py tests.py views.py
3. 定义一个模型
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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() class Author(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) email = models.EmailField() class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField(Author) publisher = models.ForeignKey(Publisher) publication_date = models.DateField()
“每个数据库表对应一个类”这条规则的例外情况是多对多关系。 在我们的范例模型中, Book 有一个 多对多字段叫做 authors 。 该字段表明一本书籍有一个或多个作者,但 Book 数据库表却并没有 authors 字段。 相反,Django创建了一个额外的表(多对多连接表)来处理书籍和作者之间的映射关系。
最后需要注意的是,我们并没有显式地为这些模型定义任何主键。 除非你单独指明,否则Django会自动为每个模型生成一个自增长的整数主键字段每个Django模型都要求有单独的主键。id
4. 模型安装
完成这些代码之后,现在让我们来在数据库中创建这些表。 要完成该项工作,第一步是在 Django 项目中 激活这些模型。 将 books app 添加到配置文件的已安装应用列表中即可完成此步骤。再次编辑 settings.py 文件, 找到 INSTALLED_APPS 设置。 INSTALLED_APPS 告诉 Django 项目哪些 app 处于激活状态。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'spider', 'books', ]
执行命令,生成表到数据库。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
python manage.py makemigrations
python manage.py migrate
5. 基本数据访问
一旦你创建了模型,Django自动为这些模型提供了高级的Python API。 运行 python manage.py shell 并输入下面的内容试试看:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> 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 [<Publisher: Publisher object>, <Publisher: Publisher object>]
-
首先,导入Publisher模型类, 通过这个类我们可以与包含 出版社 的数据表进行交互。
-
接着,创建一个`` Publisher`` 类的实例并设置了字段`` name, address`` 等的值。
-
调用该对象的 save() 方法,将对象保存到数据库中。 Django 会在后台执行一条 INSERT 语句。
-
最后,使用`` Publisher.objects`` 属性从数据库取出出版商的信息,这个属性可以认为是包含出版商的记录集。 这个属性有许多方法, 这里先介绍调用`` Publisher.objects.all()`` 方法获取数据库中`` Publisher`` 类的所有对象。这个操作的幕后,Django执行了一条SQL `` SELECT`` 语句。
6. 添加模块字符串表现
当我们打印整个publisher列表时,我们没有得到想要的有用信息,无法将对象信息分开。可以添加一个方法 __str__() 来实现。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.db import models # Create your models here. 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(blank=True, verbose_name='e-mail') 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, on_delete=models.CASCADE) publication_date = models.DateField() def __str__(self): return self.title
7. 插入和更新数据
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> p = Publisher(name='Apress', ... address='2855 Telegraph Ave.', ... city='Berkeley', ... state_province='CA', ... country='U.S.A.', ... website='http://www.apress.com/')
8. 选择对象
当然,创建新的数据库,并更新之中的数据是必要的,但是,对于 Web 应用程序来说,更多的时候是在检索查询数据库。 我们已经知道如何从一个给定的模型中取出所有记录:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> Publisher.objects.all() [<Publisher: Apress>, <Publisher: O'Reilly>]
注意到 Django在选择所有数据时并没有使用 SELECT* ,而是显式列出了所有字段。 设计的时候就是这样: SELECT* 会更慢,而且最重要的是列出所有字段遵循了Python 界的一个信条: 明言胜于暗示。(提示: 在python命令行输入 import this 看看!)
9. 数据过滤
我们很少会一次性从数据库中取出所有的数据;通常都只针对一部分数据进行操作。 在Django API中,我们可以使用`` filter()`` 方法对数据进行过滤:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 单条件过滤 >>> Publisher.objects.filter(name='Apress') [<Publisher: Apress>] # 多条件过滤 >>> Publisher.objects.filter(country="U.S.A.", state_province="CA") [<Publisher: Apress>] # SQL缺省的 = 操作符是精确匹配的, 其他类型的查找也可以使用: >>> Publisher.objects.filter(name__contains="press") [<Publisher: Apress>] # 在 name 和 contains 之间有双下划线。和Python一样,Django也使用双下划线来表明会进行一些魔术般的操作。这里,contains部分会被Django翻译成LIKE语句: SELECT id, name, address, city, state_province, country, website FROM books_publisher WHERE name LIKE '%press%';
10 . 获取单个对象
上面的例子中`` filter()`` 函数返回一个记录集,这个记录集是一个列表。 相对列表来说,有些时候我们更需要获取单个的对象, `` get()`` 方法就是在此时使用的:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> Publisher.objects.get(name="Apress") <Publisher: Apress> # 这样,就返回了单个对象,而不是列表(更准确的说,QuerySet)。 所以,如果结果是多个对象,会导致抛出异常: >>> Publisher.objects.get(country="U.S.A.") Traceback (most recent call last): ... MultipleObjectsReturned: get() returned more than one Publisher -- it returned 2! Lookup parameters were {'country': 'U.S.A.'} # 如果查询没有返回结果也会抛出异常: >>> Publisher.objects.get(name="Penguin") Traceback (most recent call last): ... DoesNotExist: Publisher matching query does not exist. # 这个 DoesNotExist 异常 是 Publisher 这个 model 类的一个属性,即 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."
11. 数据排序
在运行前面的例子中,你可能已经注意到返回的结果是无序的。 我们还没有告诉数据库 怎样对结果进行排序,所以我们返回的结果是无序的。在你的 Django 应用中,你或许希望根据某字段的值对检索结果排序,比如说,按字母顺序。 那么,使用 order_by() 这个方法就可以搞定了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> Publisher.objects.order_by("name") [<Publisher: Apress>, <Publisher: O'Reilly>]
我们可以对任意字段进行排序:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> Publisher.objects.order_by("address") [<Publisher: O'Reilly>, <Publisher: Apress>] >>> Publisher.objects.order_by("state_province") [<Publisher: Apress>, <Publisher: O'Reilly>]
如果需要以多个字段为标准进行排序(第二个字段会在第一个字段的值相同的情况下被使用到),使用多个参数就可以了,如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> Publisher.objects.order_by("state_province", "address") [<Publisher: Apress>, <Publisher: O'Reilly>]
尽管很灵活,但是每次都要用 order_by() 显得有点啰嗦。 大多数时间你通常只会对某些 字段进行排序。 在这种情况下,Django让你可以指定模型的缺省排序方式:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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']
class Meta,内嵌于 Publisher 这个类的定义中(如果 class Publisher是顶格的,那么 class Meta 在它之下要缩进4个空格--按 Python 的传统 )。你可以在任意一个 模型 类中使用 Meta 类,来设置一些与特定模型相关的选项。 在 附录B 中有 Meta 中所有可选项的完整参考,现在,我们关注 ordering 这个选项就够了。 如果你设置了这个选项,那么除非你检索时特意额外地使用了 order_by(),否则,当你使用 Django 的数据库 API 去检索时,Publisher对象的相关返回值默认地都会按 name 字段排序。
12 . 连锁查询
我们已经知道如何对数据进行过滤和排序。 当然,通常我们需要同时进行过滤和排序查询的操作。 因此,你可以简单地写成这种“链式”的形式:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> Publisher.objects.filter(country="U.S.A.").order_by("-name") [<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;
13 限制返回数据
另一个常用的需求就是取出固定数目的记录。 想象一下你有成千上万的出版商在你的数据库里, 但是你只想显示第一个。 你可以使用标准的Python列表裁剪语句:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
>>> 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的range-slicing语法来取出数据的特定子集: >>> Publisher.objects.order_by('name')[0:2] # 这个例子返回两个对象,等同于以下的SQL语句: SELECT id, name, address, city, state_province, country, website FROM books_publisher ORDER BY name OFFSET 0 LIMIT 2;
14 . 更新多个对象
在“插入和更新数据”小节中,我们有提到模型的save()方法,这个方法会更新一行里的所有列。 而某些情况下,我们只需要更新行里的某几列。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 例如说我们现在想要将Apress Publisher的名称由原来的”Apress”更改为”Apress Publishing”。若使用save()方法,如: >>> p = Publisher.objects.get(name='Apress') >>> p.name = 'Apress Publishing' >>> p.save() # 在这个例子里我们可以看到Django的save()方法更新了不仅仅是name列的值,还有更新了所有的列。 若name以外的列有可能会被其他的进程所改动的情况下,只更改name列显然是更加明智的。 更改某一指定的列,我们可以调用结果集(QuerySet)对象的update()方法: 示例如下: >>> Publisher.objects.filter(id=52).update(name='Apress Publishing') # update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录。 以下示例演示如何将所有Publisher的country字段值由’U.S.A’更改为’USA’: >>> Publisher.objects.all().update(country='USA') 2
15 . 删除对象
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 删除数据库中的对象只需调用该对象的delete()方法即可: >>> p = Publisher.objects.get(name="O'Reilly") >>> p.delete() >>> Publisher.objects.all() [<Publisher: Apress Publishing>] # 同样我们可以在结果集上调用delete()方法同时删除多条记录。这一点与我们上一小节提到的update()方法相似: >>> Publisher.objects.filter(country='USA').delete() >>> Publisher.objects.all().delete() >>> Publisher.objects.all() [] # 删除数据时要谨慎! 为了预防误删除掉某一个表内的所有数据,Django要求在删除表内所有数据时显示使用all()。 比如,下面的操作将会出错: >>> Publisher.objects.delete() Traceback (most recent call last): File "<console>", line 1, in <module> AttributeError: 'Manager' object has no attribute 'delete' # 而一旦使用all()方法,所有数据将会被删除: >>> Publisher.objects.all().delete() # 如果只需要删除部分的数据,就不需要调用all()方法。再看一下之前的例子: >>> Publisher.objects.filter(country='USA').delete()