Django 教程第三部分:使用模型
本文展示了如何为 LocalLibrary 网站定义模型。它解释了什么是模型、如何声明模型以及一些主要的字段类型。它还简要介绍了访问模型数据的一些主要方法。
预备知识 | Django 教程第二部分:创建一个骨架网站. |
---|---|
目标 |
能够设计和创建自己的模型,并选择合适的字段。 |
概述
Django Web 应用程序通过称为模型的 Python 对象访问和管理数据。模型定义了存储数据的结构,包括字段类型,也可能包括其最大大小、默认值、选择列表选项、文档的帮助文本、表单的标签文本等。模型的定义独立于底层数据库——你可以在项目设置中选择其中一个。一旦你选择了要使用的数据库,你根本不需要直接与它对话——你只需编写模型结构和其他代码,Django 会为你处理所有与数据库通信的繁琐工作。
本教程展示了如何定义和访问 LocalLibrary 网站示例的模型。
设计 LocalLibrary 模型
在你开始编写模型代码之前,值得花几分钟时间思考我们需要存储哪些数据以及不同对象之间的关系。
我们知道需要存储书籍信息(标题、摘要、作者、书写语言、类别、ISBN),并且可能有多本可用的副本(具有全局唯一 ID、可用状态等)。我们可能需要存储比作者姓名更多的作者信息,并且可能存在姓名相同或相似的多个作者。我们希望能够根据书名、作者、书写语言和类别对信息进行排序。
在设计模型时,为每个“对象”(一组相关信息)设置单独的模型是有意义的。在这种情况下,明显的对象是书籍、书籍实例和作者。
你可能还希望使用模型来表示选择列表选项(例如,像下拉选择列表),而不是将选择项硬编码到网站本身——当并非所有选项都事先已知或可能会更改时,建议这样做。在这种情况下,明显的模型候选对象包括书籍类型(例如,科幻小说、法国诗歌等)和语言(英语、法语、日语)。
一旦我们确定了模型和字段,我们需要考虑关系。Django 允许你定义一对一 (OneToOneField
)、一对多 (ForeignKey
) 和多对多 (ManyToManyField
) 关系。
考虑到这一点,下面的 UML 关联图显示了我们将在此案例中定义的模型(作为框)。
我们创建了书籍(书籍的通用详细信息)、书籍实例(系统中可用书籍特定物理副本的状态)和作者的模型。我们还决定为类型设置一个模型,以便可以通过管理界面创建/选择值。我们决定不为 BookInstance:status
设置模型——我们硬编码了这些值 (LOAN_STATUS
),因为我们预计它们不会改变。在每个框中,你可以看到模型名称、字段名称和类型,以及方法及其返回类型。
该图还显示了模型之间的关系,包括它们的多重性。多重性是图上的数字,显示了关系中可能存在的每个模型的数量(最大值和最小值)。例如,框之间的连接线显示书籍和类型是相关的。靠近类型模型的数字显示一本书必须有一个或多个类型(你喜欢的任意数量),而靠近书籍模型另一端的数字显示一个类型可以有零个或多个关联书籍。
注意:下一节提供了一个基本入门,解释了如何定义和使用模型。在阅读时,请考虑我们将如何构建上面图中的每个模型。
模型基础知识
本节简要概述了如何定义模型以及一些更重要的字段和字段参数。
模型定义
模型通常定义在应用程序的 models.py 文件中。它们作为 django.db.models.Model
的子类实现,可以包含字段、方法和元数据。下面的代码片段显示了一个名为 MyModelName
的“典型”模型。
from django.db import models
from django.urls import reverse
class MyModelName(models.Model):
"""A typical class defining a model, derived from the Model class."""
# Fields
my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
# …
# Metadata
class Meta:
ordering = ['-my_field_name']
# Methods
def get_absolute_url(self):
"""Returns the URL to access a particular instance of MyModelName."""
return reverse('model-detail-view', args=[str(self.id)])
def __str__(self):
"""String for representing the MyModelName object (in Admin site etc.)."""
return self.my_field_name
在以下部分中,我们将详细探讨模型中的每个特性。
字段
模型可以有任意数量的任意类型的字段——每个字段代表我们希望存储在数据库表中的一列数据。每个数据库记录(行)将包含每个字段的一个值。让我们看看下面的例子。
my_field_name = models.CharField(max_length=20, help_text='Enter field documentation')
我们上面的示例有一个名为 my_field_name
的字段,类型为 models.CharField
——这意味着该字段将包含字母数字字符串。字段类型使用特定的类进行分配,这些类决定了用于在数据库中存储数据记录的类型,以及从 HTML 表单接收值时要使用的验证标准(即,什么构成有效值)。字段类型还可以采用进一步指定字段存储或使用方式的参数。在这种情况下,我们为字段提供了两个参数。
max_length=20
— 表示此字段值的最大长度为 20 个字符。help_text='Enter field documentation'
— 在表单中可能显示的有用文本,帮助用户理解字段的用途。
字段名用于在查询和模板中引用它。字段也有一个标签,它使用 verbose_name
参数指定(默认值为 None
)。如果未设置 verbose_name
,则通过将下划线替换为空格并将第一个字母大写来从字段名创建标签(例如,字段 my_field_name
在表单中使用时将具有默认标签 My field name)。
如果模型在表单中呈现(例如,在管理站点中),字段的声明顺序将影响其默认顺序,尽管这可能会被覆盖。
常用字段参数
在声明许多/大多数不同字段类型时,可以使用以下常用参数:
-
help_text: 为 HTML 表单提供文本标签(例如,在管理站点中),如上所述。
-
verbose_name: 字段的易读名称,用于字段标签。如果未指定,Django 将根据字段名推断默认的 verbose name。
-
default: 字段的默认值。这可以是一个值或一个可调用对象,在这种情况下,每次创建新记录时都会调用该对象。
-
null: 如果为
True
,Django 会将空白值作为NULL
存储在数据库中,适用于此字段类型(CharField
会存储空字符串)。默认值为False
。 -
blank: 如果为
True
,则字段允许在表单中为空。默认值为False
,这意味着 Django 的表单验证将强制你输入一个值。这通常与null=True
一起使用,因为如果你允许空白值,你也会希望数据库能够适当地表示它们。 -
choices: 此字段的一组选择。如果提供此参数,则默认的相应表单小部件将是一个带有这些选择的下拉框,而不是标准的文本字段。
-
unique: 如果为
True
,则确保字段值在整个数据库中是唯一的。这可用于防止字段值重复。默认值为False
。 -
primary_key: 如果为
True
,则将当前字段设置为模型的主键(主键是用于唯一标识所有不同表记录的特殊数据库列)。如果未指定任何字段作为主键,Django 将自动为此目的添加一个字段。自动创建的主键字段的类型可以在AppConfig.default_auto_field
中为每个应用程序指定,或在DEFAULT_AUTO_FIELD
设置中全局指定。注意:使用 manage.py 创建的应用程序将主键类型设置为 BigAutoField。你可以在本地库 catalog/apps.py 文件中看到这一点。
pythonclass CatalogConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField'
还有许多其他选项——你可以在此处查看 字段选项的完整列表。
常用字段类型
以下列表描述了一些更常用的字段类型。
- CharField 用于定义短到中等长度的固定长度字符串。你必须指定要存储数据的
max_length
。 - TextField 用于大型任意长度字符串。你可以为字段指定
max_length
,但这仅在字段在表单中显示时使用(在数据库级别不强制执行)。 - IntegerField 是一个用于存储整数(整数)值,并在表单中将输入值验证为整数的字段。
- DateField 和 DateTimeField 用于存储/表示日期和日期/时间信息(分别作为 Python
datetime.date
和datetime.datetime
对象)。这些字段可以额外声明(互斥)参数auto_now=True
(在每次保存模型时将字段设置为当前日期)、auto_now_add
(仅在首次创建模型时设置日期)和default
(设置一个可以被用户覆盖的默认日期)。 - EmailField 用于存储和验证电子邮件地址。
- FileField 和 ImageField 分别用于上传文件和图像(
ImageField
添加了额外的验证,即上传的文件是图像)。这些字段具有定义上传文件存储方式和位置的参数。 - AutoField 是一种特殊类型的
IntegerField
,它会自动递增。如果你没有明确指定主键,则会自动向你的模型添加此类型的主键。 - ForeignKey 用于指定与另一个数据库模型的一对多关系(例如,一辆汽车有一个制造商,但一个制造商可以生产许多汽车)。关系中的“一”方是包含“键”的模型(包含引用该“键”的“外键”的模型,处于此类关系的“多”方)。
- ManyToManyField 用于指定多对多关系(例如,一本书可以有多种类型,每种类型可以包含多本书)。在我们的图书馆应用程序中,我们将非常类似于
ForeignKeys
使用它们,但它们可以以更复杂的方式用于描述组之间的关系。这些字段具有on_delete
参数,用于定义关联记录被删除时会发生什么(例如,models.SET_NULL
的值会将值设置为NULL
)。
还有许多其他类型的字段,包括用于不同类型数字(大整数、小整数、浮点数)、布尔值、URL、slug、唯一 ID 和其他“时间相关”信息(持续时间、时间等)的字段。你可以在此处查看 完整列表。
元数据
你可以通过声明 class Meta
来为你的模型声明模型级元数据,如下所示。
class Meta:
ordering = ['-my_field_name']
此元数据最有用的功能之一是控制查询模型类型时返回记录的默认排序。你通过在 ordering
属性的字段名称列表中指定匹配顺序来完成此操作,如上所示。排序将取决于字段类型(字符字段按字母顺序排序,而日期字段按时间顺序排序)。如上所示,你可以在字段名称前加上减号 (-) 以反转排序顺序。
因此,举例来说,如果我们选择默认按如下方式对书籍进行排序:
ordering = ['title', '-publish_date']
书籍将按标题从 A 到 Z 按字母顺序排序,然后每个标题内部按出版日期从新到旧排序。
另一个常用属性是 verbose_name
,它是类的单数和复数形式的详细名称。
verbose_name = 'BetterName'
类元数据可用于创建和应用模型的新“访问权限”(默认权限会自动应用),允许基于另一个字段进行排序,定义可存储数据可能值的约束,或声明该类是“抽象”的(一个你无法为其创建记录的基础类,而是从中派生以创建其他模型)。
许多其他元数据选项控制必须用于模型的数据库以及数据如何存储(这些选项只有在你需要将模型映射到现有数据库时才真正有用)。
元数据选项的完整列表可在以下位置获取:模型元数据选项 (Django docs)。
方法
模型还可以有方法。
至少,在每个模型中,你应该定义标准的 Python 类方法 __str__()
,以便为每个对象返回一个人类可读的字符串。此字符串用于在管理站点(以及你需要引用模型实例的任何其他地方)中表示单个记录。通常,它将返回模型中的标题或名称字段。
def __str__(self):
return self.my_field_name
Django 模型中包含的另一个常用方法是 get_absolute_url()
,它返回一个用于在网站上显示单个模型记录的 URL(如果你定义了此方法,Django 将自动在管理站点中模型的记录编辑屏幕上添加一个“在站点上查看”按钮)。下面显示了 get_absolute_url()
的典型模式。
def get_absolute_url(self):
"""Returns the URL to access a particular instance of the model."""
return reverse('model-detail-view', args=[str(self.id)])
注意:假设你将使用类似 /my-application/my-model-name/2
的 URL 来显示模型的单个记录(其中“2”是特定记录的 id
),你将需要创建一个 URL 映射器来将响应和 id 传递给一个“模型详细视图”(它将完成显示记录所需的工作)。上面的 reverse()
函数能够“反转”你的 URL 映射器(在上面的例子中名为 'model-detail-view'),以创建正确格式的 URL。
当然,要使其工作,你仍然必须编写 URL 映射、视图和模板!
你还可以定义任何其他你喜欢的方法,并从你的代码或模板中调用它们(前提是它们不带任何参数)。
模型管理
一旦你定义了模型类,就可以使用它们来创建、更新或删除记录,并运行查询以获取所有记录或特定记录子集。我们将在教程中定义视图时向你展示如何操作,但这里有一个简要总结。
创建和修改记录
要创建记录,你可以定义模型的一个实例,然后调用 save()
。
# Create a new record using the model's constructor.
record = MyModelName(my_field_name="Instance #1")
# Save the object into the database.
record.save()
注意:如果你没有声明任何字段为 primary_key
,新记录将自动获得一个,字段名为 id
。你可以在保存上述记录后查询此字段,其值将为 1。
你可以使用点语法访问此新记录中的字段,并更改值。你必须调用 save()
才能将修改后的值存储到数据库中。
# Access model field values using Python attributes.
print(record.id) # should return 1 for the first record.
print(record.my_field_name) # should print 'Instance #1'
# Change record by modifying the fields, then calling save().
record.my_field_name = "New Instance Name"
record.save()
搜索记录
你可以使用模型的 objects
属性(由基类提供)搜索符合特定条件的记录。
注意:解释如何使用“抽象”模型和字段名搜索记录可能会有点令人困惑。在下面的讨论中,我们将引用一个带有 title
和 genre
字段的 Book
模型,其中 genre 也是一个只有一个 name
字段的模型。
我们可以使用 objects.all()
获取模型的所有记录,作为 QuerySet
。QuerySet
是一个可迭代对象,这意味着它包含许多我们可以迭代/遍历的对象。
all_books = Book.objects.all()
Django 的 filter()
方法允许我们过滤返回的 QuerySet
,以使指定的文本或数字字段匹配特定条件。例如,要筛选标题中包含“wild”的书籍,然后统计它们,我们可以这样做:
wild_books = Book.objects.filter(title__contains='wild')
number_wild_books = wild_books.count()
要匹配的字段和匹配类型在过滤器参数名称中定义,使用格式:field_name__match_type
(请注意上面 title
和 contains
之间的双下划线)。上面我们正在使用区分大小写的匹配来过滤 title
。你可以进行许多其他类型的匹配:icontains
(不区分大小写)、iexact
(不区分大小写的精确匹配)、exact
(区分大小写的精确匹配)以及 in
、gt
(大于)、startswith
等等。完整列表在这里。
在某些情况下,你需要根据定义与其他模型一对多关系(例如,ForeignKey
)的字段进行过滤。在这种情况下,你可以使用额外的双下划线“索引”到相关模型中的字段。因此,例如,要过滤具有特定类型模式的书籍,你必须通过 genre
字段索引到 name
,如下所示:
# Will match on: Fiction, Science fiction, non-fiction etc.
books_containing_genre = Book.objects.filter(genre__name__icontains='fiction')
注意:你可以使用下划线(__
)导航任意多层关系(ForeignKey
/ManyToManyField
)。例如,一本具有不同类型的 Book
,通过进一步的“cover”关系定义,可能具有一个参数名称:type__cover__name__exact='hard'
。
查询还可以做更多事情,包括从相关模型进行反向搜索、链接过滤器、返回更小的值集等。有关更多信息,请参阅进行查询(Django 文档)。
定义 LocalLibrary 模型
在本节中,我们将开始定义图书馆的模型。打开 models.py
(在 /django-locallibrary-tutorial/catalog/)。页面顶部的样板代码导入了 models 模块,其中包含我们的模型将继承的基类 models.Model
。
from django.db import models
# Create your models here.
类型模型
复制下面所示的 Genre
模型代码,并将其粘贴到 models.py
文件的底部。此模型用于存储有关书籍类别的信息——例如,它是小说还是非小说,浪漫还是军事历史等。如上所述,我们将类型创建为模型,而不是自由文本或选择列表,以便可以通过数据库而不是硬编码来管理可能的值。
from django.urls import reverse # Used in get_absolute_url() to get URL for specified ID
from django.db.models import UniqueConstraint # Constrains fields to unique values
from django.db.models.functions import Lower # Returns lower cased value of field
class Genre(models.Model):
"""Model representing a book genre."""
name = models.CharField(
max_length=200,
unique=True,
help_text="Enter a book genre (e.g. Science Fiction, French Poetry etc.)"
)
def __str__(self):
"""String for representing the Model object."""
return self.name
def get_absolute_url(self):
"""Returns the url to access a particular genre instance."""
return reverse('genre-detail', args=[str(self.id)])
class Meta:
constraints = [
UniqueConstraint(
Lower('name'),
name='genre_name_case_insensitive_unique',
violation_error_message = "Genre already exists (case insensitive match)"
),
]
该模型有一个 CharField
字段 (name
),用于描述类型(限制为 200 个字符并带有一些 help_text
)。我们将此字段设置为唯一 (unique=True
),因为每个类型应该只有一个记录。
在字段之后,我们声明了一个 __str__()
方法,它返回特定记录定义的类型的名称。没有定义详细名称,因此当在表单中使用时,字段标签将是 Name
。然后我们声明 get_absolute_url()
方法,它返回一个 URL,可用于访问此模型的详细记录(为此,我们将不得不定义一个名为 genre-detail
的 URL 映射,并定义一个关联的视图和模板)。
上面字段上的 unique=True
设置可以防止创建名称完全相同的类型,但不能防止“fantasy”、“Fantasy”甚至“FaNtAsY”等变体。模型定义的最后一部分使用模型元数据上的 constraints
选项来指定 name
字段中的小写值在数据库中必须是唯一的,如果不是,则显示 violation_error_message
字符串。这里我们不需要做其他任何事情,但是你可以针对一个或多个字段定义多个约束。有关更多信息,请参阅约束参考,包括 UniqueConstraint()
(和 Lower()
)。
书籍模型
复制下面的 Book
模型,然后再次将其粘贴到文件的底部。Book
模型以通用意义表示所有关于可用书籍的信息,而不是可供借阅的特定物理“实例”或“副本”。
该模型使用 CharField
来表示书籍的 title
和 isbn
。对于 isbn
,请注意第一个未命名参数如何明确将标签设置为“ISBN”(否则,它将默认为“Isbn”)。我们还将参数 unique
设置为 true
,以确保所有书籍都具有唯一的 ISBN(unique 参数使字段值在表中全局唯一)。与 isbn
(和类型名称)不同,title
未设置为唯一,因为不同的书籍可能具有相同的名称。该模型使用 TextField
作为 summary
,因为此文本可能需要很长。
class Book(models.Model):
"""Model representing a book (but not a specific copy of a book)."""
title = models.CharField(max_length=200)
author = models.ForeignKey('Author', on_delete=models.RESTRICT, null=True)
# Foreign Key used because book can only have one author, but authors can have multiple books.
# Author as a string rather than object because it hasn't been declared yet in file.
summary = models.TextField(
max_length=1000, help_text="Enter a brief description of the book")
isbn = models.CharField('ISBN', max_length=13,
unique=True,
help_text='13 Character <a href="https://www.isbn-international.org/content/what-isbn'
'">ISBN number</a>')
# ManyToManyField used because genre can contain many books. Books can cover many genres.
# Genre class has already been defined so we can specify the object above.
genre = models.ManyToManyField(
Genre, help_text="Select a genre for this book")
def __str__(self):
"""String for representing the Model object."""
return self.title
def get_absolute_url(self):
"""Returns the URL to access a detail record for this book."""
return reverse('book-detail', args=[str(self.id)])
类型是一个 ManyToManyField
,因此一本书可以有多种类型,一个类型可以包含多本书。作者被声明为 ForeignKey
,所以每本书只有一个作者,但一个作者可以有许多书(实际上,一本书可能有多个作者,但在此实现中没有!)。
在这两种字段类型中,关联的模型类被声明为第一个未命名参数,使用模型类或包含关联模型名称的字符串。如果关联类在引用之前尚未在此文件中定义,则必须使用模型名称作为字符串!author
字段中其他感兴趣的参数是 null=True
,它允许数据库在未选择作者时存储 Null
值,以及 on_delete=models.RESTRICT
,它将阻止删除书籍的关联作者(如果它被任何书籍引用)。
警告:默认情况下 on_delete=models.CASCADE
,这意味着如果作者被删除,这本书也会被删除!我们这里使用 RESTRICT
,但我们也可以使用 PROTECT
来防止作者在使用它的任何书籍时被删除,或者使用 SET_NULL
来在记录被删除时将书籍的作者设置为 Null
。
该模型还定义了 __str__()
,使用书籍的 title
字段来表示 Book
记录。最后一个方法 get_absolute_url()
返回一个 URL,可用于访问此模型的详细记录(我们将不得不定义一个名为 book-detail
的 URL 映射,并定义一个关联的视图和模板)。
书籍实例模型
接下来,将 BookInstance
模型(如下所示)复制到其他模型下方。BookInstance
表示一本书的特定副本,某人可能会借阅,并包含有关副本是否可用、预计归还日期、“印记”或版本详细信息,以及图书馆中该书的唯一 ID 的信息。
现在,有些字段和方法会让你感到熟悉。该模型使用:
ForeignKey
来标识关联的Book
(每本书可以有许多副本,但一个副本只能有一个Book
)。该键指定on_delete=models.RESTRICT
以确保在被BookInstance
引用时不能删除Book
。CharField
来表示书籍的印记(特定版本)。
import uuid # Required for unique book instances
class BookInstance(models.Model):
"""Model representing a specific copy of a book (i.e. that can be borrowed from the library)."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4,
help_text="Unique ID for this particular book across whole library")
book = models.ForeignKey('Book', on_delete=models.RESTRICT, null=True)
imprint = models.CharField(max_length=200)
due_back = models.DateField(null=True, blank=True)
LOAN_STATUS = (
('m', 'Maintenance'),
('o', 'On loan'),
('a', 'Available'),
('r', 'Reserved'),
)
status = models.CharField(
max_length=1,
choices=LOAN_STATUS,
blank=True,
default='m',
help_text='Book availability',
)
class Meta:
ordering = ['due_back']
def __str__(self):
"""String for representing the Model object."""
return f'{self.id} ({self.book.title})'
我们额外声明了几种新类型的字段:
UUIDField
用于id
字段,将其设置为此模型的primary_key
。这种类型的字段为每个实例分配一个全局唯一值(图书馆中每本你可以找到的书籍一个)。DateField
用于due_back
日期(借出或维护后预计可用的日期)。此值可以为blank
或null
(当书籍可用时需要)。模型元数据(Class Meta
)使用此字段在查询中返回记录时进行排序。status
是一个CharField
,它定义了一个选择/选择列表。如你所见,我们定义了一个包含键值对元组的元组,并将其传递给 choices 参数。键/值对中的值是用户可以选择的显示值,而键是如果选择了该选项实际保存的值。我们还设置了默认值 'm'(维护),因为书籍最初在上架之前将被创建为不可用。
方法 __str__()
使用其唯一 ID 和关联 Book
的标题的组合来表示 BookInstance
对象。
注意:一点 Python
- 从 Python 3.6 开始,你可以使用字符串插值语法(也称为 f-strings):
f'{self.id} ({self.book.title})'
。 - 在本教程的旧版本中,我们使用的是 格式化字符串 语法,这也是 Python 中格式化字符串的有效方法(例如,
'{0} ({1})'.format(self.id,self.book.title)
)。
作者模型
将 Author
模型(如下所示)复制到 models.py 中现有代码的下方。
class Author(models.Model):
"""Model representing an author."""
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
date_of_birth = models.DateField(null=True, blank=True)
date_of_death = models.DateField('Died', null=True, blank=True)
class Meta:
ordering = ['last_name', 'first_name']
def get_absolute_url(self):
"""Returns the URL to access a particular author instance."""
return reverse('author-detail', args=[str(self.id)])
def __str__(self):
"""String for representing the Model object."""
return f'{self.last_name}, {self.first_name}'
现在所有字段/方法都应该很熟悉了。该模型定义了一个作者,包括名字、姓氏、出生日期和死亡日期(两者都是可选的)。它指定默认情况下 __str__()
以姓氏、名字的顺序返回姓名。get_absolute_url()
方法反转 author-detail
URL 映射以获取显示单个作者的 URL。
重新运行数据库迁移
你的所有模型都已创建。现在重新运行数据库迁移以将它们添加到你的数据库中。
python3 manage.py makemigrations
python3 manage.py migrate
语言模型 — 挑战
想象一位当地的慈善家捐赠了许多用另一种语言(比如说,波斯语)编写的新书。挑战在于找出如何在我们的图书馆网站中最好地表示这些书籍,然后将它们添加到模型中。
一些需要考虑的事情:
- “语言”应该与
Book
、BookInstance
还是其他对象关联? - 不同的语言应该使用模型、自由文本字段还是硬编码选择列表来表示?
做出决定后,添加字段。你可以在 GitHub 上的项目中查看我们是如何决定的。
请记住,模型更改后,应再次重新运行数据库迁移以添加更改。
python3 manage.py makemigrations
python3 manage.py migrate
总结
在本文中,我们学习了如何定义模型,然后利用这些信息为 LocalLibrary 网站设计并实现了合适的模型。
此时,我们将暂时偏离创建网站,并查看 Django 管理站点。该站点将允许我们向图书馆添加一些数据,然后我们可以使用我们(尚未创建)的视图和模板来显示这些数据。
另见
- 编写你的第一个 Django 应用,第 2 部分 (Django docs)
- 进行查询 (Django docs)
- QuerySet API 参考 (Django docs)