Django 教程第 3 部分:使用模型

本文介绍如何为 LocalLibrary 网站定义模型。它解释了什么是模型,如何声明模型以及一些主要字段类型。它还简要介绍了几种访问模型数据的主要方法。

先决条件 Django 教程第 2 部分:创建网站框架.
目标

能够设计和创建自己的模型,并适当地选择字段。

概述

Django Web 应用程序通过称为模型的 Python 对象访问和管理数据。模型定义存储数据的结构,包括字段类型,以及可能的最大大小、默认值、选择列表选项、文档的帮助文本、表单的标签文本等。模型的定义独立于底层数据库——您可以选择多个数据库之一作为项目设置的一部分。一旦您选择了要使用的数据库,您就不需要直接与它进行任何通信——您只需编写模型结构和其他代码,Django 会处理与数据库通信的所有繁琐工作。

本教程展示了如何定义和访问LocalLibrary 网站示例的模型。

设计 LocalLibrary 模型

在您开始编写模型代码之前,花几分钟时间思考一下我们需要存储哪些数据以及不同对象之间的关系是值得的。

我们知道我们需要存储有关书籍的信息(标题、摘要、作者、书写语言、类别、ISBN),并且我们可能有多个副本可用(具有全局唯一的 ID、可用状态等)。我们可能需要存储有关作者的更多信息,而不仅仅是他们的姓名,并且可能有许多作者拥有相同或相似的姓名。我们希望能够根据书名、作者、书写语言和类别对信息进行排序。

在设计模型时,为每个“对象”(一组相关信息)创建单独的模型是有意义的。在这种情况下,明显的对象是书籍、书籍实例和作者。

您可能还想使用模型来表示选择列表选项(例如,下拉列表中的选择),而不是将选择硬编码到网站本身——当所有选项不是一开始就已知或可能发生更改时,建议这样做。在这种情况下,模型的明显候选包括书籍类型(例如,科幻小说、法语诗歌等)和语言(英语、法语、日语)。

一旦我们决定了模型和字段,我们就需要考虑关系。Django 允许您定义一对一 (OneToOneField)、一对多 (ForeignKey) 和多对多 (ManyToManyField) 的关系。

考虑到这一点,下面的 UML 关联图显示了我们将在此案例中定义的模型(作为方框)。

LocalLibrary Model UML with fixed Author multiplicity inside the Book class

我们为书籍(书籍的通用详细信息)、书籍实例(系统中可用的书籍的特定物理副本的状态)和作者创建了模型。我们还决定为类型创建一个模型,以便可以通过管理界面创建/选择值。我们决定不为BookInstance:status创建模型——我们已将值 (LOAN_STATUS) 硬编码,因为我们预计这些值不会发生变化。在每个方框内,您可以看到模型名称、字段名称和类型,以及方法及其返回类型。

该图还显示了模型之间的关系,包括它们的多重性。多重性是图上的数字,显示了关系中可能存在每个模型的数字(最大值和最小值)。例如,连接书籍和类型方框的线显示了书籍和类型之间的关系。靠近类型模型的数字显示了书籍必须具有一种或多种类型(只要您喜欢),而靠近书籍模型的线的另一端的数字显示了类型可以具有零个或多个关联的书籍。

注意:下一部分提供了一个基本入门,解释了如何定义和使用模型。在您阅读时,请考虑我们将如何构建上图中的每个模型。

模型入门

本部分简要概述了模型的定义方式以及一些更重要的字段和字段参数。

模型定义

模型通常在应用程序的models.py文件中定义。它们被实现为django.db.models.Model的子类,并且可以包含字段、方法和元数据。下面的代码片段显示了一个“典型”模型,名为MyModelName

python
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

在下面的部分中,我们将详细探讨模型内的每个功能

字段

模型可以具有任意数量的字段,任何类型——每个字段都表示我们想要在数据库表中的一个列中存储的数据。每个数据库记录(行)将包含每个字段值之一。让我们看一下下面的示例

python
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 名称。
  • 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文件中看到这一点

    py
    class CatalogConfig(AppConfig):
      default_auto_field = 'django.db.models.BigAutoField'
    

还有许多其他选项——您可以在这里查看字段选项的完整列表

常见字段类型

以下列表描述了一些最常用的字段类型。

  • CharField用于定义短到中等大小的固定长度字符串。您必须指定要存储数据的max_length
  • TextField用于大型任意长度字符串。您可以为字段指定max_length,但这仅在表单中显示字段时使用(它不在数据库级别强制执行)。
  • IntegerField是用于存储整数值(整数)以及在表单中将输入值验证为整数的字段。
  • DateFieldDateTimeField用于存储/表示日期和日期/时间信息(分别为 Python datetime.datedatetime.datetime 对象)。这些字段还可以声明(互斥的)参数auto_now=True(在每次保存模型时将字段设置为当前日期)、auto_now_add(仅在首次创建模型时设置日期)以及default(设置可以被用户覆盖的默认日期)。
  • EmailField用于存储和验证电子邮件地址。
  • FileFieldImageField分别用于上传文件和图像(ImageField添加额外的验证,以确保上传的文件是图像)。这些参数用于定义上传文件的存储方式和位置。
  • AutoField 是一种特殊的 IntegerField 类型,它会自动递增。如果您没有显式指定主键,则会自动将此类型的主键添加到您的模型中。
  • ForeignKey 用于指定与另一个数据库模型的一对多关系(例如,一辆汽车有一个制造商,但一个制造商可以制造许多汽车)。关系的“一对”端是包含“键”的模型(包含指向该“键”的“外键”的模型位于这种关系的“多”端)。
  • ManyToManyField 用于指定多对多关系(例如,一本书可以有多个类型,每个类型可以包含多本书)。在我们的图书馆应用程序中,我们将非常类似地使用这些方法 ForeignKeys,但它们可以在更复杂的方式中使用来描述组之间的关系。这些方法具有参数 on_delete 来定义当关联记录被删除时会发生什么(例如,值为 models.SET_NULL 将值设置为 NULL)。

还有许多其他类型的字段,包括用于不同类型的数字(大整数、小整数、浮点数)、布尔值、URL、slug、唯一 ID 以及其他“与时间相关”的信息(持续时间、时间等)。您可以查看 完整列表

元数据

您可以通过声明 class Meta 为您的模型声明模型级元数据,如所示。

python
class Meta:
    ordering = ['-my_field_name']

此元数据最实用的功能之一是控制查询模型类型时返回的记录的默认排序。您可以通过在字段名称列表中指定匹配顺序来执行此操作,以 ordering 属性,如上所示。排序将取决于字段的类型(字符字段按字母顺序排序,而日期字段按时间顺序排序)。如上所示,您可以为字段名称加上减号 (-) 前缀以反转排序顺序。

因此,例如,如果我们选择默认按以下方式对书籍进行排序

python
ordering = ['title', '-pubdate']

书籍将按标题按字母顺序排序,从 A 到 Z,然后按每个标题内的出版日期排序,从最新到最旧。

另一个常用属性是 verbose_name,它是类的单数和复数形式的详细名称

python
verbose_name = 'BetterName'

类元数据可用于创建和应用模型的新“访问权限”(默认权限会自动应用)、允许基于另一个字段进行排序、定义对可存储数据的可能值的约束,或者声明该类是“抽象”的(无法为其创建记录的基类,而是从中派生以创建其他模型)。

许多其他元数据选项控制必须为模型使用哪个数据库以及如何存储数据(这些选项只有在您需要将模型映射到现有数据库时才真正有用)。

完整元数据选项列表如下:模型元数据选项(Django 文档)。

方法

模型也可以包含方法。

最少,在每个模型中,您都应该定义标准 Python 类方法 __str__() 来为每个对象返回一个可读的字符串。 该字符串用于在管理站点(以及您需要引用模型实例的任何其他位置)中表示单个记录。通常,这将从模型中返回标题或名称字段。

python
def __str__(self):
    return self.my_field_name

另一个常见的在 Django 模型中包含的方法是 get_absolute_url(),它返回一个 URL,用于在网站上显示单个模型记录(如果您定义此方法,则 Django 会自动在管理站点的模型记录编辑屏幕中添加一个“在站点上查看”按钮)。下面显示了 get_absolute_url() 的典型模式。

python
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)])

注意: 假设您将使用类似 /myapplication/mymodelname/2 的 URL 来显示模型的单个记录(其中“2”是特定记录的 id),您将需要创建一个 URL 映射器将响应和 ID 传递给“模型详细信息视图”(它将执行显示记录所需的工作)。上面的 reverse() 函数能够“反转”您的 URL 映射器(在上面的情况下名为 'model-detail-view')以创建格式正确的 URL。

当然,要使它正常工作,您仍然需要编写 URL 映射、视图和模板!

您还可以定义任何其他您喜欢的 方法,并从您的代码或模板中调用它们(前提是它们不接受任何参数)。

模型管理

定义完模型类后,您可以使用它们来创建、更新或删除记录,以及运行查询以获取所有记录或特定记录子集。我们将在教程中定义视图时向您展示如何执行此操作,但这里简要概述一下。

创建和修改记录

要创建记录,您可以定义模型的实例,然后调用 save()

python
# 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() 将修改后的值存储到数据库中。

python
# 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 属性(由基类提供)搜索与特定条件匹配的记录。

注意: 解释如何使用“抽象”模型和字段名称搜索记录可能有点令人困惑。在下面的讨论中,我们将引用一个具有 titlegenre 字段的 Book 模型,其中类型也是一个具有单个字段 name 的模型。

我们可以使用 objects.all() 获取模型的所有记录作为 QuerySetQuerySet 是一个可迭代对象,这意味着它包含我们可以迭代/循环访问的多个对象。

python
all_books = Book.objects.all()

Django 的 filter() 方法允许我们过滤返回的 QuerySet 以使指定的文本数字字段与特定条件匹配。例如,要过滤标题中包含“wild”的书籍并计算它们,我们可以执行以下操作。

python
wild_books = Book.objects.filter(title__contains='wild')
number_wild_books = wild_books.count()

要匹配的字段和匹配类型是在过滤器参数名称中定义的,使用格式:field_name__match_type(请注意上面 titlecontains 之间的双下划线)。上面我们正在使用区分大小写的匹配来过滤 title。您可以执行许多其他类型的匹配:icontains(不区分大小写)、iexact(不区分大小写的精确匹配)、exact(区分大小写的精确匹配)以及 ingt(大于)、startswith 等。 完整列表

在某些情况下,您需要过滤定义与另一个模型的一对多关系的字段(例如 ForeignKey)。在这种情况下,您可以使用额外的双下划线“索引”到相关模型中的字段。因此,例如,要过滤具有特定类型模式的书籍,您必须通过 genre 字段对 name 进行索引,如下所示

python
# 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

python
from django.db import models

# Create your models here.

Genre 模型

复制下面显示的 Genre 模型代码,并将其粘贴到您的 models.py 文件的底部。此模型用于存储有关书籍类别的信息,例如它是小说还是非小说,浪漫还是军事历史等。如上所述,我们将类型创建为模型而不是作为自由文本或选择列表,以便可以通过数据库而不是硬编码的方式管理可能的值。

python
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 字段中值的 lowercase 必须在数据库中唯一,并且如果它不唯一,则显示 violation_error_message 字符串。在这里,我们不需要执行任何其他操作,但您可以针对一个或多个字段定义多个约束。有关详细信息,请参阅 约束参考,包括 UniqueConstraint()(和 Lower())。

Book 模型

复制下面的 Book 模型,并再次将其粘贴到文件的底部。Book 模型表示有关书籍的一般性信息,但不是特定物理“实例”或可借阅的“副本”。

该模型使用 CharField 来表示书籍的 titleisbn。对于 isbn,请注意第一个未命名的参数如何将标签显式设置为“ISBN”(否则,它将默认为“Isbn”)。我们还将参数 unique 设置为 true,以确保所有书籍都具有唯一的 ISBN(unique 参数使字段值在表中全局唯一)。与 isbn(和类型名称)不同,title 未设置为唯一,因为不同的书籍可能具有相同的名称。该模型使用 TextField 表示 summary,因为此文本可能需要很长。

python
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,该 URL 可用于访问该模型的详细信息记录(我们将必须定义一个名为book-detail的 URL 映射,并定义一个关联的视图和模板)。

BookInstance 模型

接下来,将BookInstance模型(如下所示)复制到其他模型下面。BookInstance表示某人可能借阅的书籍的特定副本,包括副本是否可用或预计归还日期、"印记"或版本详细信息,以及图书馆中书籍的唯一 ID。

现在,一些字段和方法应该很熟悉了。该模型使用

  • ForeignKey来识别关联的Book(每本书可以有多个副本,但一个副本只能有一个Book)。密钥指定on_delete=models.RESTRICT以确保Book不能在被BookInstance引用时被删除。
  • CharField来表示书籍的印记(特定版本)。
python
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日期(在该日期之后,预计书籍将在被借出或处于维护状态后变得可用)。该值可以是blanknull(在书籍可用时需要)。模型元数据(Class Meta)使用该字段在查询返回记录时对记录进行排序。
  • status是一个CharField,它定义了一个选择/选择列表。如你所见,我们定义了一个包含键值对元组的元组,并将它传递给choices参数。键值对中的值是一个用户可以选择的显示值,而键是选项被选中后实际保存的值。我们还设置了一个默认值为'm'(维护),因为书籍最初会在被放在书架上之前被创建为不可用。

方法__str__()使用其唯一 ID 和关联的Book的标题的组合来表示BookInstance对象。

注意:一些 Python

  • 从 Python 3.6 开始,你可以使用字符串插值语法(也称为 f-字符串):f'{self.id} ({self.book.title})'
  • 在这个教程的早期版本中,我们使用的是格式化字符串语法,这在 Python 中也是一种有效的格式化字符串的方式(例如'{0} ({1})'.format(self.id,self.book.title))。

作者模型

Author模型(如下所示)复制到models.py中现有代码的下方。

python
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。

重新运行数据库迁移

现在,所有模型都已创建。现在,重新运行数据库迁移,将它们添加到你的数据库中。

bash
python3 manage.py makemigrations
python3 manage.py migrate

语言模型 - 挑战

想象一个当地的捐助者捐赠了一些用另一种语言(比如波斯语)写的新书。挑战在于找出如何最好地将这些书表示在我们的图书馆网站上,然后将它们添加到模型中。

一些需要考虑的事情

  • "语言"应该与BookBookInstance还是其他对象关联?
  • 不同的语言应该使用模型、自由文本字段还是硬编码的选择列表来表示?

在做出决定后,添加该字段。你可以在 GitHub 上查看我们的决定这里

不要忘记,在更改模型后,应该再次重新运行数据库迁移来添加更改。

bash
python3 manage.py makemigrations
python3 manage.py migrate

总结

在这篇文章中,我们学习了如何定义模型,然后使用这些信息设计和实现适合LocalLibrary网站的模型。

在这一点上,我们将暂时偏离网站的创建,并查看Django 管理网站。这个网站将允许我们向图书馆添加一些数据,然后我们可以使用我们(尚未创建的)视图和模板来显示这些数据。

另请参阅