Django 亮点:模型、管理和利用关系数据库(第 3 部分)

已发表: 2022-03-10
快速总结↬管理面板是 Django Web 框架提供的最强大、最灵活的功能之一,它结合了即时现成的功能和无限的自定义。 使用基于图书馆库存系统的示例项目,我们将使用管理面板来了解如何在 Django 中创建模型和与关系数据库进行交互。

在我们开始之前,我想指出 Django 的内置管理功能,即使在定制之后,也不适合最终用户。 管理面板作为开发人员、操作员和管理员工具存在,用于创建和维护软件。 它的目的不是为最终用户提供审核功能或您开发的平台上的任何其他管理员功能。

本文基于两个部分的假设:

  1. Django 管理面板非常直观,您基本上已经知道如何使用它。
  2. Django 管理面板非常强大,我们可以将其用作学习使用 Django 模型在关系数据库中表示数据的工具。

我提出这些想法的一个警告是,我们仍然需要编写一些配置代码来激活管理面板更强大的功能,并且我们仍然需要使用 Django 的基于模型的 ORM(对象关系映射)来指定数据的表示在我们的系统中。

推荐阅读

“Django Highlights”是一个介绍 Django Web 开发重要概念的系列。 您可能想阅读有关提供安全用户身份验证流程的内容,并关注使用 Django 模板编写复杂页面的演示。

跳跃后更多! 继续往下看↓

配置

我们将在本文中使用一个示例项目。 该项目对图书馆将存储的有关其书籍和顾客的一些数据进行建模。 该示例应该相当适用于管理用户和/或库存的许多类型的系统。 以下是数据的外观:

数据模型。 (大预览)

请完成以下步骤以使示例代码在您的本地计算机上运行。

1. 安装包

安装 Python 3.6 或更高版本后,创建目录和虚拟环境。 然后,安装以下软件包:

 pip install django django-grappelli

Django 是我们在本文中使用的 Web 框架。 ( django-grappelli是一个我们将简要介绍的管理面板主题。)

2. 获得项目

安装之前的包后,从 GitHub 下载示例代码。 跑步:

 git clone https://github.com/philipkiely/library_records.git cd library_records/library

3. 创建超级用户

使用以下命令,设置您的数据库并创建一个超级用户。 命令行界面将引导您完成创建超级用户的过程。 您的超级用户帐户将成为您访问管理面板的方式,因此请务必记住您设置的密码。 采用:

 python manage.py migrate python manage.py createsuperuser

4. 加载数据

为了我们的探索,我创建了一个称为夹具的数据集,您可以将其加载到数据库中(有关如何创建夹具的更多信息,请参见文章末尾)。 在管理面板中浏览数据库之前,请使用夹具填充您的数据库。 跑步:

 python manage.py loaddata ../fixture.json

5. 运行示例项目

最后,您已准备好运行示例代码。 要运行服务器,请使用以下命令:

 python manage.py runserver

打开浏览器到 https://127.0.0.1:8000 查看项目。 请注意,您会自动重定向到位于/admin/的管理面板。 我通过library/urls.py中的以下配置实现了这一点:

 from django.contrib import admin from django.urls import path from records import views urlpatterns = [ path('admin/', admin.site.urls), path('', views.index), ]

结合records/views.py中的以下简单重定向:

 from django.http import HttpResponseRedirect def index(request): return HttpResponseRedirect('/admin/')

使用管理面板

我们成功了! 加载页面时,您应该会看到如下内容:

Django 管理面板主页。 (大预览)

此视图是使用records/admin.py中的以下样板代码完成的:

 from django.contrib import admin from .models import Book, Patron, Copy admin.site.register(Book) admin.site.register(Copy) admin.site.register(Patron)

该视图应该让您初步了解系统存储的数据。 我将揭开一些谜团: GroupsUsers由 Django 定义,并存储系统上帐户的信息和权限。 您可以在本系列的前一篇文章中阅读有关User模型的更多信息。 BooksCopysPatrons是我们在运行迁移时创建并通过加载夹具填充的数据库中的表。 请注意,Django 通过附加“s”天真地使模型名称复数,即使在拼写错误的“副本”等情况下也是如此。

数据模型。 (大预览)

在我们的项目中, Book是包含标题、作者、出版日期和 ISBN(国际标准书号)的记录。 图书馆维护每BookCopy ,或者可能是多个。 每个Copy都可以由Patron签出,或者当前可以签入PatronUser的扩展,记录了他们的地址和出生日期。

创建、读取、更新、销毁

管理面板的一项标准功能是添加每个模型的实例。 点击“书籍”进入模型页面,点击右上角的“添加书籍”按钮。 这样做会拉出一个表格,您可以填写并保存以创建一本书。

创建一本书(大预览)

创建Patron揭示了管理员创建表单的另一个内置功能:您可以直接从同一表单创建连接模型。 下面的屏幕截图显示了由User下拉右侧的绿色加号触发的弹出窗口。 因此,您可以在同一个管理页面上创建两个模型。

创建一个赞助人。 (大预览)

您可以通过相同的机制创建COPY

对于每条记录,您可以单击该行以使用相同的表单对其进行编辑。 您还可以使用管理员操作删除记录。

管理员操作

虽然管理面板的内置功能非常有用,但您可以使用管理操作创建自己的工具。 我们将创建两个:一个用于创建书籍副本,另一个用于签入已归还图书馆的书籍。

要创建Book Copy ,请转到 URL /admin/records/book/并使用“操作”下拉菜单选择“添加书籍副本”,然后使用左侧列上的复选框以选择将哪本书或哪些书籍添加到库存中。

创建复制操作。 (大预览)

创建它依赖于我们稍后将介绍的模型方法。 我们可以通过在records/admin.py中为Profile模型创建一个ModelAdmin类来将其称为管理操作,如下所示:

 from django.contrib import admin from .models import Book, Patron, Copy class BookAdmin(admin.ModelAdmin): list_display = ("title", "author", "published") actions = ["make_copys"] def make_copys(self, request, queryset): for q in queryset: q.make_copy() self.message_user(request, "copy(s) created") make_copys.short_description = "Add a copy of book(s)" admin.site.register(Book, BookAdmin)

list_display属性表示在模型的概览页面中使用哪些字段来表示模型。 actions属性列出了管理员操作。 我们的管理操作被定义为BookAdmin中的一个函数,并接受三个参数:管理对象本身、请求(客户端发送的实际 HTTP 请求)和查询集(选中框的对象列表)。 我们对查询集中的每个项目执行相同的操作,然后通知用户操作已完成。 每个管理员操作都需要简短的描述,以便可以在下拉菜单中正确识别。 最后,我们现在在注册模型时添加BookAdmin

为批量设置属性编写管理操作非常重复。 这是签入Copy的代码,请注意它与上一个操作几乎等价。

 from django.contrib import admin from .models import Book, Patron, Copy class CopyAdmin(admin.ModelAdmin): actions = ["check_in_copys"] def check_in_copys(self, request, queryset): for q in queryset: q.check_in() self.message_user(request, "copy(s) checked in") check_in_copys.short_description = "Check in copy(s)" admin.site.register(Copy, CopyAdmin)

管理主题

默认情况下,Django 为管理面板提供了相当简单的样式。 您可以创建自己的主题或使用第三方主题来让管理面板焕然一新。 一个流行的开源主题是 grappelli,我们在本文前面安装了它。 您可以查看文档以了解其全部功能。

安装主题非常简单,只需要两行。 首先,在library/settings.py中将grappelli添加到INSTALLED_APPS中,如下所示:

 INSTALLED_APPS = [ 'grappelli', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'records', ]

然后,调整library/urls.py

 from django.contrib import admin from django.urls import path, include from records import views urlpatterns = [ path('grappelli/', include('grappelli.urls')), path('admin/', admin.site.urls), path('', views.index), ]

完成这些更改后,管理面板应如下所示:

带有主题的管理面板。 (大预览)

还有许多其他主题,您可以再次开发自己的主题。 在本文的其余部分,我将坚持使用默认外观。

了解模型

现在您已经熟悉了管理面板并使用它来导航数据,让我们来看看定义我们的数据库结构的模型。 每个模型代表关系数据库中的一个表。

关系数据库将数据存储在一个或多个表中。 这些表中的每一个都有一个指定的列结构,包括一个主键(每个元素的唯一标识符)和一列或多列值,这些值有各种类型,如字符串、整数和日期。 存储在数据库中的每个对象都表示为一行。 名称的“关系”部分来自可以说是该技术最重要的特性:在表之间创建关系。 对象(行)可以与其他表中的行进行一对一、一对多(外键)或多对多映射。 我们将在示例中进一步讨论这一点。

默认情况下,Django 使用 SQLite3 进行开发。 SQLite3 是一个简单的关系数据库引擎,当您第一次运行python manage.py migrate时,您的数据库会自动创建为db.sqlite3 。 我们将在本文中继续使用 SQLite3,但它不适合生产使用,主要是因为并发用户可能会覆盖。 在生产环境中,或者在编写您打算部署的系统时,请使用 PostgreSQL 或 MySQL。

Django 使用模型与数据库交互。 使用 Django 的 ORM 的一部分, records/models.py文件包含多个模型,它允许为每个对象指定字段、属性和方法。 在创建模型时,我们力求在合理范围内实现“胖模型”架构。 这意味着应在模型本身的规范中处理尽可能多的数据验证、解析、处理、业务逻辑、异常处理、边缘案例解决和类似任务。 在底层,Django 模型是非常复杂、功能丰富的对象,具有广泛有用的默认行为。 这使得即使不编写大量代码也可以轻松实现“胖模型”架构。

让我们来看看示例应用程序中的三个模型。 我们无法涵盖所有​​内容,因为这应该是一篇介绍性文章,而不是 Django 框架的完整文档,但我将重点介绍我在构建这些简单模型时所做的最重要的选择。

Book类是模型中最直接的。 这是来自records/models.py

 from django.db import models class Book(models.Model): title = models.CharField(max_length=300) author = models.CharField(max_length=150) published = models.DateField() isbn = models.IntegerField(unique=True) def __str__(self): return self.title + " by " + self.author def make_copy(self): Copy.objects.create(book=self)

所有CharField字段都需要指定的max_length属性。 常规长度为 150 个字符,如果标题很长,我将其加倍作为title 。 当然,仍然有一个任意的限制,可以超过。 对于无限制的文本长度,请使用TextFieldpublished的字段是DateField 。 这本书的出版时间无关紧要,但如果确实如此,我会使用DateTimeField 。 最后,ISBN 是一个整数(ISBN 是 10 位或 13 位数字,因此都符合整数的最大值),我们使用unique=True因为没有两本书可以具有相同的 ISBN,然后在数据库级别强制执行。

所有对象都有一个定义其字符串表示的方法__str__(self) 。 我们覆盖了models.Model类提供的默认实现,而是在模型将被表示为字符串的所有地方都将书籍表示为“作者的标题”。 回想一下,之前我们在Book的管理对象中使用list_display来确定管理面板列表中将显示哪些字段。 如果该list_display不存在,则管理列表将显示模型的字符串表示形式,就像对PatronCopy一样。

最后,我们在Book上有一个方法,我们在之前编写的管理操作中调用了该方法。 这个函数创建一个与数据库中给定的Book实例相关的Copy

继续讨论Patron ,这个模型引入了一对一关系的概念,在这个例子中是内置的User模型。 从records/models.py 中查看:

 from django.db import models from django.contrib.auth.models import User class Patron(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) address = models.CharField(max_length=150) dob = models.DateField() def __str__(self): return self.user.username

user字段不完全是双射函数。 可以有一个没有关联的Patron实例的User实例。 但是,一个User不能与一个以上的Patron实例相关联,并且如果没有与用户的确切关系,一个Patron就不能存在。 这是在数据库级别强制执行的,并且由on_delete=models.CASCADE规范保证:如果删除User实例,则将删除关联的Profile文件。

我们之前见过的其他字段和__str__(self)函数。 值得注意的是,您可以通过一对一的关系来获取模型函数中的属性,在本例中为user.username

为了扩展数据库关系的用处,让我们将注意力转向从records/models.py Copy

 from django.db import models class Copy(models.Model): book = models.ForeignKey(Book, on_delete=models.CASCADE) out_to = models.ForeignKey(Patron, blank=True, null=True, on_delete=models.SET_NULL) def __str__(self): has_copy = "checked in" if self.out_to: has_copy = self.out_to.user.username return self.book.title + " -> " + has_copy def check_out(self, p): self.out_to = p self.save() def check_in(self): self.out_to = None self.save()

同样,我们之前已经看过大部分内容,所以让我们关注新的东西: models.ForeignKey 。 一个Copy必须是一个Book ,但图书馆可能有每Book的多个CopyBook可以存在于数据库中,而图书馆的目录中没有Copy ,但如果没有基础Book ,则Copy不能存在。

这种复杂的关系用下面这行来表示:

 book = models.ForeignKey(Book, on_delete=models.CASCADE)

删除行为与PatronUser的引用相同。

CopyPatron之间的关系略有不同。 一个Copy最多可以被一个Patron签出,但是每个Patron可以签出尽可能多的Copy ,只要图书馆允许他们。 但是,这不是永久的关系,有时不会签出CopyPatronCopy在数据库中相互独立存在; 删除一个实例不应删除另一个实例。

这种关系仍然是外键的一个用例,但有不同的参数:

 out_to = models.ForeignKey(Patron, blank=True, null=True, on_delete=models.SET_NULL)

在这里,具有blank=True允许表单接受None作为关系的值,而null=True允许数据库中Copy表中的Patron关系的列接受null作为值。 如果在签出该Copy时删除了Patron实例,则会在Copy上触发删除行为,该行为是通过将Patron字段设置为 null 来切断关系,同时保持Copy不变。

相同的字段类型models.ForeignKey可以表达对象之间非常不同的关系。 在这个例子中我无法完全适应的一个关系是一个多对多字段,它就像一个一对一的字段,除了,正如它的名字所暗示的那样,每个实例都可以与许多其他实例相关联并且每个人都可以与许多其他人相关联,例如一本书如何有多个作者,每个作者都写过多本书。

迁移

您可能想知道数据库如何知道模型中表达的内容。 根据我的经验,迁移是非常简单的事情之一,直到它们不是,然后它们会吃掉你的脸。 对于初学者,以下是保持杯子完好无损的方法:了解迁移以及如何与它们交互,但尽量避免对迁移文件进行手动编辑。 如果您已经知道自己在做什么,请跳过本节并继续了解适合您的方法。

无论哪种方式,请查看官方文档以获得对该主题的完整处理。

迁移将模型中的更改转换为数据库模式中的更改。 您不必自己编写它们,Django 使用python manage.py makemigrations命令创建它们。 您应该在创建新模型或编辑现有模型的字段时运行此命令,但在创建或编辑模型方法时无需这样做。 重要的是要注意迁移以链的形式存在,每个迁移都引用前一个迁移,以便可以对数据库模式进行无错误的编辑。 因此,如果您在一个项目上进行协作,那么在版本控制中保持一个一致的迁移历史记录是很重要的。 当有未应用的迁移时,运行python manage.py migrate在运行服务器之前应用它们。

示例项目与单个迁移一起分发, records/migrations/0001_initial.py 。 同样,这是自动生成的代码,您不必编辑,所以我不会在这里复制它,但是如果您想了解幕后发生的事情,请继续看看它。

夹具

与迁移不同,固定装置不是 Django 开发的常见方面。 我使用它们将样本数据与文章一起分发,并且从未使用过它们。 但是,因为我们之前使用过一个,所以我不得不介绍这个主题。

这一次,官方文档在这个话题上有点苗条。 总的来说,您应该知道的是,fixture 是一种从数据库中导入和导出各种格式数据的方法,包括我使用的 JSON。 此功能主要用于帮助进行自动化测试等工作,而不是备份系统或在实时数据库中编辑数据的方式。 此外,fixture 不会随着迁移而更新,如果您尝试将fixture 应用于具有不兼容模式的数据库,它将失败。

要为整个数据库生成一个夹具,请运行:

 python manage.py dumpdata --format json > fixture.json

要加载夹具,请运行:

 python manage.py loaddata fixture.json

结论

在 Django 中编写模型是一个很大的话题,而使用管理面板则是另一个话题。 3000字,我只介绍了每一个。 希望使用管理面板为您提供了更好的界面来探索模型如何工作和相互关联,让您有信心试验和开发自己的数据关系表示。

如果您正在寻找一个简单的起点,请尝试添加一个像Profile一样从User继承的Librarian模型。 对于更多挑战,请尝试为每个Copy和/或Patron实施结帐历史记录(有几种方法可以完成此操作)。

Django Highlights 是一个介绍 Django 中 Web 开发的重要概念的系列。 每篇文章都是作为 Django 开发方面的独立指南编写的,旨在帮助前端开发人员和设计人员更深入地了解代码库的“另一半”。 这些文章主要是为了帮助您了解理论和约定,但包含一些用 Django 3.0 编写的代码示例。

延伸阅读

您可能对以下文章和文档感兴趣。

  • Django 亮点:用户模型和身份验证(第 1 部分)
  • Django 亮点:模板化保存行(第 2 部分)
  • Django 亮点:处理静态资产和媒体文件(第 4 部分)
  • Django 管理文档
  • Django 模型
  • Django 模型字段参考
  • Django 迁移