Django - 模型

  • 模型准确且唯一的描述了数据。它包含您储存的数据的重要字段和行为。一般来说,每一个模型都映射一张数据库表。
  • 每个模型都是一个 Python 的类,这些类继承 django.db.models.Model
  • 模型类的每个属性都相当于一个数据库的字段。
  • 利用这些,Django 提供了一个自动生成访问数据库的 API;请参阅 执行查询

配置数据库

  • Django 支持 MySQL 5.7 及以上版本。
  • 配置 MySQL 数据库
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    DATABASES = {
    "default": {
    "ENGINE": "django.db.backends.mysql",
    "NAME": "db_name",
    "HOST": "host",
    "PORT": 3306,
    "USER": "user",
    "PASSWORD": "password"
    }
    }

配置语言与时区

1
2
3
4
5
6
7
LANGUAGE_CODE = "zh-hans"

TIME_ZONE = "Asia/Shanghai"

USE_I18N = True

USE_TZ = False

设置 USE_TZ = False ,以保证表中字段的创建时间和本地时间一致。

  • 一旦你定义了你的模型,你需要告诉 Django 你准备 使用 这些模型。你需要修改设置文件中的 INSTALLED_APPS ,在这个设置中添加包含 models.py 文件的模块名称。
  • 例如,若模型位于项目中的 user.models 模块( 此包结构由 manage.py startapp 命令创建), INSTALLED_APPS 应设置如下:
    1
    2
    3
    4
    5
    INSTALLED_APPS = [
    #...
    'user',
    #...
    ]
  • 当你向 INSTALLED_APPS 添加新的应用的时候,请务必运行 manage.py migrate,此外你也可以先使用以下命令进行迁移 manage.py makemigrations
    1
    2
    3
    4
    5
    # 迁移:只是在当前app中的migrations文件夹中产生了一个类似0001_initial.py的文件
    python .\manage.py makemigrations

    # 同步:同步模型,生成数据表
    python .\manage.py migrate

字段

  • 字段概述

    模型中最重要且唯一必要的是数据库的字段定义。字段在类属性中定义。定义字段名时应小心避免使用与 模型 API 冲突的名称, 如 cleansave,or delete 等。

  • 字段选项

    • null

      • 如果是 True, Django 将在数据库中存储空值为 NULL。默认为 False
      • 免在基于字符串的字段上使用 null,如 CharFieldTextField。如果一个基于字符串的字段有 null=True,这意味着它有两种可能的 “无数据” 值。NULL,和空字符串。在大多数情况下,“无数据” 有两种可能的值是多余的,Django 的惯例是使用空字符串,而不是 NULL。一个例外是当一个 CharField 同时设置了 unique=Trueblank=True。在这种情况下,null=True 是需要的,以避免在保存具有空白值的多个对象时违反唯一约束。
      • 无论是基于字符串的字段还是非字符串的字段,如果希望在表单中允许空值,还需要设置 blank=True,因为 null 参数只影响数据库的存储(参见 blank )。
    • blank

      • 如果是 True ,该字段允许为空。默认为 False
      • 注意,这与 null 不同。 null 纯属数据库相关,而 blank 则与验证相关。如果一个字段有 blank=True,表单验证将允许输入一个空值。如果一个字段有 blank=False,则该字段为必填字段。
    • choices

      • 一个 sequence 本身由正好两个项目的迭代项组成(例如 [(A,B),(A,B)...] ),作为该字段的选择。如果给定了选择,它们会被 模型验证 强制执行,默认的表单部件将是一个带有这些选择的选择框,而不是标准的文本字段。
      • 每个元组中的第一个元素是要在模型上设置的实际值,第二个元素是人可读的名称。例如:
        1
        2
        3
        4
        5
        6
        7
        YEAR_IN_SCHOOL_CHOICES = [
        ('FR', 'Freshman'),
        ('SO', 'Sophomore'),
        ('JR', 'Junior'),
        ('SR', 'Senior'),
        ('GR', 'Graduate'),
        ]
    • db_column

      • 这个字段要使用的数据库列名。如果没有给出列名,Django 将使用字段名。
      • 如果你的数据库列名是 SQL 的保留字,或者包含了 Python 变量名中不允许的字符 —— 特别是连字符 —— 那也没关系。Django 会在幕后引用列名和表名。
    • db_index

      如果是 True,将为该字段创建数据库索引。

    • db_tablespace

      如果这个字段有索引,那么要为这个字段的索引使用的 数据库表空间 的名称。默认是项目的 DEFAULT_INDEX_TABLESPACE 设置(如果有设置),或者是模型的 db_tablespace (如果有)。如果后端不支持索引的表空间,则忽略此选项。

    • default

      • 该字段的默认值。可以是一个值或者是个可调用的对象,如果是个可调用对象,每次实例化模型时都会调用该对象。
      • 默认值不能是一个可更改的对象(模型实例、listset 等),因为对该对象同一实例的引用将被用作所有新模型实例的缺省值。相反,将所需的默认值包裹在一个可调用对象中。
    • editable

      如果是 False,该字段将不会在管理或任何其他 ModelForm 中显示。在 模型验证 中也会跳过。默认为 True

    • error_messages

      • error_messages 参数可以让你覆盖该字段引发的默认消息。传入一个与你想覆盖的错误信息相匹配的键值的字典。
      • 错误信息键包括 nullblankinvalidinvalid_choiceuniqueunique_for_date。在下面的 字段类型 一节中为每个字段指定了额外的错误信息键。
      • 这些错误信息通常不会传播到表单中。参见 有关模型的 error_messages 的注意事项
    • help_text

      • 额外的 “帮助” 文本,随表单控件一同显示。即便你的字段未用于表单,它对于生成文档也是很有用的。
      • 请注意,在自动生成的表格中,这个值 不是 HTML 转义的。如果你愿意的话,你可以在 help_text 中加入 HTML。例如:
        1
        help_text="Please use the following format: <em>YYYY-MM-DD</em>."
      • 或者你可以使用纯文本和 django.utils.html.escape() 来转义任何 HTML 特殊字符。确保你转义任何可能来自不受信任的用户的帮助文本,以避免跨站脚本攻击。
    • primary_key

      • 如果设置为 True ,将该字段设置为该模型的主键。
      • 如果你没有为模型中的任何字段指定 primary_key=True,Django 会自动添加一个字段来保存主键,所以你不需要在任何字段上设置 primary_key=True,除非你想覆盖默认主键行为。自动创建的主键字段的类型可以在 AppConfig.default_auto_field 中为每个应用程序指定,或者在 DEFAULT_AUTO_FIELD 配置中全局指定。更多信息,请参阅 自动设置主键
      • primary_key=True 意味着 null=Falseunique=True。一个对象只允许有一个主键。
      • 主键字段是只读的。如果您改变了现有对象的主键值,然后将其保存,则会在旧对象旁边创建一个新对象。
    • unique

      • 如果设置为 True,这个字段必须在整个表中保持值唯一。
      • 这是在数据库级别和模型验证中强制执行的。如果你试图保存一个在 unique 字段中存在重复值的模型,模型的 save() 方法将引发 django.db.IntegrityError
      • 除了 ManyToManyFieldOneToOneField 之外,该选项对所有字段类型有效。
      • 请注意,当 uniqueTrue 时,你不需要指定 db_index,因为 unique 意味着创建一个索引。
    • unique_for_date

      • 将其设置为 DateFieldDateTimeField 的名称,要求该字段的日期字段值是唯一的。
      • 例如,如果你的字段 titleunique_for_date="pub_date",那么 Django 就不允许输入两条相同 titlepub_date 的记录。
      • 请注意,如果将其设置为指向 DateTimeField,则只考虑该字段的日期部分。此外,当 USE_TZTrue 时,检查将在对象保存时的 当前时区 中进行。
      • 这在模型验证过程中由 Model.validate_unique() 强制执行,但在数据库级别上不执行。如果任何 unique_for_date 约束涉及的字段不属于 ModelForm (例如,如果其中一个字段被列在 exclude 中,或者有 editable=False ), Model.validate_unique() 将跳过对该特定约束的验证。
    • unique_for_date

      unique_for_date 一样,但要求字段对月份是唯一的。

    • unique_for_year

      unique_fordateunique_formonth

    • verbose_name

      字段的一个人类可读名称,如果没有给定详细名称,Django 会使用字段的属性名自动创建,并将下划线转换为空格。参见 详细字段名

    • validators

      要为该字段运行的验证器列表。更多信息请参见 验证器文档

  • 字段类型

    • AutoField

      一个 IntegerField,根据可用的 ID 自动递增。你通常不需要直接使用它;如果你没有指定,主键字段会自动添加到你的模型中。参见 自动设置主键

    • BigAutoField

      一个 64 位整数,与 AutoField 很相似,但保证适合 19223372036854775807 的数字。

    • BigIntegerField

      一个 64 位的整数,和 IntegerField 很像,只是它保证适合从 -92233720368547758089223372036854775807 的数字。该字段的默认表单部件是一个 NumberInput

    • BinaryField

      • 一个用于存储原始二进制数据的字段。可以指定为 bytesbytearraymemoryview
      • 默认情况下,BinaryFieldediditable 设置为 False,在这种情况下,它不能被包含在 ModelForm 中。
    • BooleanField

    • CharField

      • 一个字符串字段,适用于小到大的字符串。
      • 对于大量的文本,使用 TextField
      • 该字段的默认表单部件是一个 TextInput
      • CharField.max_length
        必须的。该字段的最大长度(以字符为单位)。max_length 在数据库层面强制执行,在 Django 的验证中使用 MaxLengthValidator
      • CharField.db_collation
        可选的。该字段的数据库字符序名称。
    • DateField

      • 一个日期,在 Python 中用一个 datetime.date 实例表示。有一些额外的、可选的参数。
      • DateField.auto_now
        • 每次保存对象时,自动将该字段设置为现在。对于 “最后修改” 的时间戳很有用。请注意,当前日期 总是 被使用,而不仅仅是一个你可以覆盖的默认值。
        • 只有在调用 Model.save() 时,该字段才会自动更新。当以其他方式对其他字段进行更新时,如 QuerySet.update(),该字段不会被更新,尽管你可以在这样的更新中为该字段指定一个自定义值。
      • DateField.auto_now_add
        • 当第一次创建对象时,自动将该字段设置为现在。对创建时间戳很有用。请注意,当前日期是 始终 使用的;它不是一个你可以覆盖的默认值。因此,即使你在创建对象时为该字段设置了一个值,它也会被忽略。如果你想修改这个字段,可以设置以下内容来代替 auto_now_add=True
        • 该字段的默认表单部件是一个 DateInput。管理中增加了一个 JavaScript 日历,以及 “今天” 的快捷方式。包含一个额外的 invalid_date 错误信息键。
        • auto_now_addauto_nowdefault 选项是相互排斥的。这些选项的任何组合都会导致错误。
    • DateTimeField

      • 一个日期和时间,在 Python 中用一个 datetime.datetime 实例表示。与 DateField 一样,使用相同的额外参数。
      • 该字段的默认表单部件是一个单独的 DateTimeInput。管理中使用两个单独的 TextInput 部件,并使用 JavaScript 快捷方式。
    • DecimalField

      • 一个固定精度的十进制数,在 Python 中用一个 Decimal 实例来表示。它使用 DecimalValidator 验证输入。
      • 具有以下必需参数:
        • DecimalField.max_digits
          数字中允许的最大位数。请注意,这个数字必须大于或等于 decimal_places
        • DecimalField.decimal_places
          与数字一起存储的小数位数。
      • 例如,要存储最高为 999.99 的数字,精度为小数点后 2 位,你可以使用:
        1
        models.DecimalField(..., max_digits=5, decimal_places=2)
    • DurationField

      一个用于存储时间段的字段 —— 在 Python 中用 timedelta 建模。当在 PostgreSQL 上使用时,使用的数据类型是 interval,在 Oracle 上使用的数据类型是 INTERVAL DAY(9) TO SECOND(6)。否则使用微秒的 bigint

    • EmailField

      一个 CharField,使用 EmailValidator 来检查该值是否为有效的电子邮件地址。

    • FileField

      • 一个文件上传字段
      • primary_key 参数不支持,如果使用,会引起错误。
      • 具有以下可选参数:
        • FileField.upload_to
          • 这个属性提供了一种设置上传目录和文件名的方式,可以有两种设置方式。在这两种情况下,值都会传递给 Storage.save() 方法。
          • 如果你指定一个字符串值或一个 Path,它可能包含 strftime() 格式,它将被文件上传的日期/时间所代替(这样上传的文件就不会填满指定的目录)。
          • 如果你使用的是默认的 FileSystemStorage,这个字符串的值将被附加到你的 MEDIA_ROOT 路径后面,形成本地文件系统中上传文件的存储位置。如果你使用的是不同的存储系统,请检查该存储系统的文档,看看它是如何处理 upload_to 的。
          • upload_to 也可以是一个可调用对象,如函数。这个函数将被调用以获得上传路径,包括文件名。这个可调用对象必须接受两个参数,并返回一个 Unix 风格的路径(带斜线),以便传给存储系统。
            参数 描述
            instance 定义 FileField 的模型实例。更具体地说,这是附加当前文件的特定实例。在大多数情况下,这个对象还没有被保存到数据库,所以如果它使用默认的 AutoField它的主键字段可能还没有一个值
            filename 最初给文件的文件名。在确定最终目标路径时,可能会考虑到,也可能不会考虑到。
        • FileField.storage
          一个存储对象,或是一个返回存储对象的可调用对象。它处理你的文件的存储和检索。参见 管理文件,了解如何提供这个对象。
      • 该字段的默认表单部件是一个 ClearableFileInput
      • 在模型中使用 FileFieldImageField (见下文)需要几个步骤:
        1. 在你的配置文件中,你需要将 MEDIA_ROOT 定义为你希望 Django 存储上传文件的目录的完整路径。(为了提高性能,这些文件不会存储在数据库中。)将 MEDIA_URL 定义为该目录的基本公共 URL。确保这个目录是可以被网络服务器的用户账户写入的。
        2. FileFieldImageField 添加到你的模型中,定义 upload_to 选项,指定 MEDIA_ROOT 的子目录,用于上传文件。
        3. 所有这些将被存储在你的数据库中的是一个文件的路径(相对于 MEDIA_ROOT )。你很可能要使用 Django 提供的方便的 url 属性。例如,如果你的 ImageField 叫做 mug_shot,你可以在模板中使用 {{ object.mug_shot.url }} 获取图片的绝对路径。
      • 如果你想检索上传文件的盘上文件名,或者文件的大小,可以分别使用 namesize 属性;关于可用属性和方法的更多信息,请参见 File 类参考和 管理文件 主题指南。
    • FilePathField

      • 一个 CharField,其选择仅限于文件系统中某个目录下的文件名。有一些特殊的参数,其中第一个参数是 必须的

      • FilePathField.path

        • 必须的。一个目录的绝对文件系统路径,这个 FilePathField 应从该目录中获取其选择。例如:"/home/images"
        • path 也可以是一个可调用对象,可以是在运行时动态设置路径的函数。
      • FilePathField.match

        • 可选。一个正则表达式,作为一个字符串, FilePathField 将用于过滤文件名。请注意,正则表达式将被应用于基本文件名,而不是完整的路径。例如:"foo.*.txt$",它将匹配名为 foo23.txt 的文件,但不匹配 bar.txtfoo23.png
        • 一个潜在的问题是 match 适用于基本文件名,而不是完整的路径。
      • FilePathField.recursive

        可选。TrueFalse。默认为 False。指定是否包含 path 的所有子目录。

      • FilePathField.allow_files

        可选。 TrueFalse。 默认值是 True。 指定是否应该包含指定位置的文件。 这个或 allow_folders 必须是 True

      • FilePathField.allow_folders

        可选。 TrueFalse。 默认为 False。 指定是否应该包含指定位置的文件夹。 这个或 allow_files 必须是 True

      • FilePathField 实例在数据库中作为 varchar 列创建,默认最大长度为 100 个字符。与其他字段一样,你可以使用 max_length 参数改变最大长度。

    • FloatField

      • 在 Python 中用一个 float 实例表示的浮点数。
      • localizeFalse 时是 NumberInput 否则,该字段的默认表单部件是 TextInput
    • GenericIPAddressField

      • IPv4 或 IPv6 地址,字符串格式(如 192.0.2.302a02:42fe::4 )。该字段的默认表单部件是一个 TextInput

      • IPv6 地址规范化遵循 RFC 4291#section-2.2 第 2.2 节,包括使用该节第 3 段建议的 IPv4 格式,如 ::fffff:192.0.2.0。例如,2001:0::0:01 将被标准化为 2001::1::fffff:0a0a:0a0a 将被标准化为 ::fffff:10.10.10.10。所有字符都转换为小写。

      • GenericIPAddressField.protocol

        将有效输入限制为指定协议。接受的值是 'both' (默认)、'IPv4''IPv6'。匹配是不分大小写的。

      • GenericIPAddressField.unpack_ipv4

        解压 IPv4 映射地址,如 ::fffff:192.0.2.1。如果启用该选项,该地址将被解压为 192.0.2.1。默认为禁用。只有当 protocol 设置为 'both' 时才会启用。

      • 如果允许空值,就必须允许 null 值,因为空值会被存储为 null。

    • ImageField

      • 继承 FileField 的所有属性和方法,但也验证上传的对象是有效的图像。
      • 除了 FileField 的特殊属性外, ImageField 也有 heightwidth 属性。
      • 为了方便查询这些属性,ImageField 有以下可选参数:
        • ImageField.height_field
          模型字段的名称,每次保存模型实例时将自动填充图像的高度。
        • ImageField.width_field
          模型字段的名称,每次保存模型实例时将自动填充图像的宽度。
      • 需要 Pillow 库。
      • ImageField 实例在数据库中创建为 varchar 列,默认最大长度为 100 个字符。与其他字段一样,你可以使用 max_length 参数改变最大长度。
      • 该字段的默认表单部件是一个 ClearableFileInput
    • IntegerField

      • 一个整数。从 -21474836482147483647 的值在 Django 支持的所有数据库中都是安全的。
      • 它使用 MinValueValidatorMaxValueValidator 根据默认数据库支持的值来验证输入。
      • localizeFalse 时是 NumberInput 否则,该字段的默认表单部件是 TextInput
    • JSONField

      • 一个用于存储 JSON 编码数据的字段。在 Python 中,数据以其 Python 本地格式表示:字典、列表、字符串、数字、布尔值和 None
      • JSONField 支持 MariaDB、MySQL 5.7.8+、Oracle、PostgreSQL 和 SQLite(启用了 JSON1 扩展)。
      • JSONField.encoder
        • 一个可选的 json.JSONEncoder 子类,用于序列化标准 JSON 序列化器不支持的数据类型(例如 datetime.datetimeUUID )。例如,你可以使用 DjangoJSONEncoder 类。
        • 默认为 json.JSONEncoder
      • JSONField.decoder
        • 一个可选的 json.JSONDecoder 子类,用于反序列化从数据库中获取的值。该值将采用自定义编码器选择的格式(通常是字符串)。你的反序列化可能需要考虑到你无法确定输入类型的事实。例如,你有可能返回一个 datetime,实际上是一个字符串,而这个字符串恰好与 datetime 选择的格式相同。
        • 默认为 json.JSONDecoder
      • 如果你给字段一个 default,确保它是一个不可变的对象,比如 str,或者是一个每次返回一个新的可变对象的可调用对象,比如 dict 或一个函数。提供一个像 default={}default=[] 这样的可改变的默认对象,在所有模型实例之间共享一个对象。
      • 要在数据库中查询 JSONField,请看 查询 JSONField
    • PositiveBigIntegerField

      就像一个 PositiveIntegerField,但只允许在某一特定点下的值(依赖于数据库)。09223372036854775807 的值在 Django 支持的所有数据库中都是安全的。

    • PositiveIntegerField

      就像 IntegerField 一样,但必须是正值或零( 0 )。从 02147483647 的值在 Django 支持的所有数据库中都是安全的。出于向后兼容的原因,接受 0 的值。

    • PositiveSmallIntegerField

      就像一个 PositiveIntegerField,但只允许在某一特定(数据库依赖的)点下取值。032767 的值在 Django 支持的所有数据库中都是安全的。

    • SlugField

      • Slug 是一个报纸术语。slug 是一个简短的标签,只包含字母、数字、下划线或连字符。它们一般用于 URL 中。
      • CharField 一样,你可以指定 max_length (也请阅读那一节中关于数据库可移植性和 max_length 的说明)。如果没有指定 max_length,Django 将使用默认长度 50。
      • 意味着将 Field.db_index 设置为 True
      • 基于其他值的值自动预填充一个 SlugField 通常是很有用的。 你可以在管理中使用 prepopulated_fields 来自动完成。
      • 它使用 validate_slugvalidate_unicode_slug 进行验证。
      • SlugField.allow_unicode
        如果是 True,该字段除了接受 ASCII 字母外,还接受 Unicode 字母。默认值为 False
    • SmallAutoField

      就像一个 AutoField,但只允许值在一定(依赖于数据库)的限制下。132767 的值在 Django 支持的所有数据库中都是安全的。

    • SmallAutoField

      就像一个 IntegerField,但只允许在某一特定(依赖于数据库的)点下取值。从 -3276832767 的值在 Django 支持的所有数据库中都是安全的。

    • TextField

      • 一个大的文本字段。该字段的默认表单部件是一个 Textarea
      • 如果你指定了 max_length 属性,它将反映在自动生成的表单字段的 Textarea 部件中。但是,它并没有在模型或数据库层面被强制执行。使用一个 CharField 来实现。
      • TextField.db_collation
        可选的。该字段的数据库字符序名称。
      • Oracle 不支持 TextField 的字符序。
    • TimeField

      • 一个时间,在 Python 中用 datetime.time 实例表示。接受与 DateField 相同的自动填充选项。
      • 该字段默认的表单部件 t 是一个 TimeInput。管理中添加了一些 JavaScript 快捷方式。
    • URLField

    • UUIDField

      • 一个用于存储通用唯一标识符的字段。使用 Python 的 UUID 类。当在 PostgreSQL 上使用时,它存储在一个 uuid 的数据类型中,否则存储在一个 char(32) 中。
      • 通用唯一标识符是 primary_keyAutoField 的一个很好的替代方案。数据库不会为你生成 UUID,所以建议使用 default
        1
        2
        3
        4
        5
        6
        import uuid
        from django.db import models

        class MyUUIDModel(models.Model):
        id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
        # other fields
      • 请注意,一个可调用对象(省略括号)被传递给 default,而不是 UUID 的实例。
  • 可用的 Meta 选项

    • abstract

      如果 abstract = True,这个模型将是一个 抽象基类,模型类作为父类需要添加。

    • app_label

      • 如果在 INSTALLED_APPS 中定义了一个应用程序之外的模型,它必须声明它属于哪个应用程序:
        1
        app_label = 'myapp'
      • 如果你想用 app_label.object_nameapp_label.model_name 来表示一个模型,你可以分别使用 model._meta.labelmodel._meta.label_lower
    • base_manager_name

      管理器的属性名,例如,'objects',用于模型的 _base_manager

    • db_table

      • 用于模型的数据库表的名称:
        1
        db_table = 'music_album'
      • 表名称
        • 为了节省你的时间,Django 会自动从你的模型类和包含它的应用程序的名称中导出数据库表的名称。一个模型的数据库表名是通过将模型的 “app label” —— 你在 manage.py startapp 中使用的名称 —— 与模型的类名连接起来,并在两者之间加上下划线。
        • 例如,如果你有一个应用程序 bookstore (由 manage.py startapp bookstore 创建),一个定义为 class Book 的模型将有一个名为 bookstore_book 的数据库表。
        • 要覆盖数据库表名,使用 class Meta 中的 db_table 参数。
        • 如果你的数据库表名是 SQL 的保留字,或者包含 Python 变量名中不允许的字符 —— 特别是连字符 —— 那也没关系。Django 会在幕后引用列名和表名。
    • db_tablespace

      模型要使用的 数据库表空间 名称。如果有设置的话,默认是项目的 DEFAULT_TABLESPACE 配置。如果后端不支持表空间,则忽略此选项。

    • default_manager_name

      模型的 _default_manager 管理器名称。

      • 从相关对象到这个对象的关系默认使用的名称。默认为 _set
      • 这个选项还可以设置 related_query_name
      • 由于字段的反向名称应该是唯一的,所以如果你打算对你的模型进行子类化,就要小心了。为了避免名称冲突,名称的一部分应该包含 '%(app_label)s''%(model_name)s',它们分别被模型所在的应用程序的名称和模型的名称所取代,都是小写的。见 抽象模型的相关名称 段落。
    • get_latest_by

      • 模型中的字段名或字段名列表,通常是 DateFieldDateTimeFieldIntegerField。这指定了在你的模型中使用的默认字段 Managerlast()earliest() 方法。例如:
        1
        2
        3
        4
        5
        # Latest by ascending order_date.
        get_latest_by = "order_date"

        # Latest by priority descending, order_date ascending.
        get_latest_by = ['-priority', 'order_date']
      • 更多内容请参见 last() 文档。
    • managed

      • 默认为 True,意味着 Django 会在 migrate 中创建相应的数据库表,或者作为迁移的一部分,并作为 flush 管理命令的一部分删除它们。也就是说,Django 管理 数据库表的生命周期。
      • 如果 False,将不对该模型进行数据库表的创建、修改或删除操作。如果该模型代表一个现有的表或一个通过其他方式创建的数据库视图,这一点很有用。这是在 managed=False唯一 的区别。模型处理的所有其他方面都与正常情况完全相同。这包括
        1. 如果不声明的话,在模型中增加一个自动主键字段。 为了避免给后面来的代码读者带来困惑,建议在使用非托管模型时,指定你所建模的数据库表的所有列。
        2. 如果一个带有 managed=False 的模型包含一个 ManyToManyField 指向另一个非托管模型,那么多对多连接的中间表也不会被创建。但是,一个托管模型和一个非托管模型之间的中间表会被创建。
        3. 如果你需要改变这种默认行为,请将中间表创建为显式模型(根据需要设置 managed ),并使用 ManyToManyField.through 属性让关系使用你的自定义模型。
      • 对于涉及 managed=False 模型的测试,你要确保创建正确的表作为测试设置的一部分。
      • 如果你对改变模型类的 Python 级行为感兴趣,你可以使用 managed=False 并创建一个现有模型的副本。然而,对于这种情况,有一个更好的方法: 代理模型
    • order_with_respect_to

      • 使该对象可以根据给定字段(通常是 ForeignKey )进行排序。这可以用来使相关对象相对于父对象可排序。例如,如果一个 Answer 与一个 Question 对象相关,而一个问题有多个答案,并且答案的顺序很重要,你可以这样做:
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        from django.db import models

        class Question(models.Model):
        text = models.TextField()
        # ...

        class Answer(models.Model):
        question = models.ForeignKey(Question, on_delete=models.CASCADE)
        # ...

        class Meta:
        order_with_respect_to = 'question'
      • 当设置了 order_with_respect_to 时,还提供了两个额外的方法来检索和设置相关对象的顺序:get_RELATED_order()set_RELATED_order()
    • ordering

      • 对象的默认排序,用于获取对象列表时:
        1
        2
        3
        4
        5
        6
        # 按 pub_date 字段升序排列
        ordering = ['pub_date']
        # 按 pub_date 降序排列
        ordering = ['-pub_date']
        # 先按 pub_date 降序,然后再按 author 升序
        ordering = ['-pub_date', 'author']
      • 这是一个字符串和/或查询表达式的元组或列表。每一个字符串都是一个字段名,前面有一个可选的 - 字头,表示降序。没有前缀 - 的字段将按升序排列。使用字符串 ? 来随机排序。
    • permissions

      • 创建此对象时要输入权限表的额外权限。为每个模型自动创建添加、更改、删除和查看权限。这个例子指定了一个额外的权限,can_deliver_pizzas
        1
        permissions = [('can_deliver_pizzas', 'Can deliver pizzas')]
      • 这是一个由二元元组组成的列表或元组,格式为 (permission_code, human_readable_permission_name)
    • default_permissions

      默认值为 ('add', 'change', 'delete', 'view') 。你可以自定义这个列表,例如,如果你的应用不需要任何默认的权限,可以将其设置为空列表。它必须在模型创建之前通过 migrate 在模型上指定,以防止任何遗漏的权限被创建。

    • proxy

      如果 proxy = True,作为另一个模型子类的模型将被视为 代理模型

    • required_db_features

      当前连接应具备的数据库特征列表,以便在迁移阶段考虑模型。例如,如果你将此列表设置为 ['gis_enabled'],则模型将只在支持 GIS 的数据库上同步。在使用多个数据库后端进行测试时,跳过一些模型也很有用。避免模型之间的关系,这些模型可能会被创建,也可能不会被创建,因为 ORM 不会处理这个问题。

    • required_db_vendor

      本模型所特有的支持的数据库厂商名称。目前的内置厂商名称是: sqlitepostgresqlmysqloracle。如果该属性不为空,且当前连接厂商与之不匹配,则该模型将不会同步。

    • select_on_save

      • 确定 Django 是否会使用 1.6 之前的 django.db.models.Model.save() 算法。旧的算法使用 SELECT 来确定是否有一条现有的记录需要更新。新算法直接尝试 UPDATE。在一些罕见的情况下,Django 看不到现有行的 UPDATE。例如 PostgreSQL 的 ON UPDATE 触发器会返回 NULL。在这种情况下,即使数据库中存在一条记录,新算法最终也会进行 INSERT
      • 通常不需要设置这个属性。默认值是 False
      • 关于新旧保存算法,请参见 django.db.models.Model.save()
    • indexes

      你想在模型上定义的 indexes 的列表:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      from django.db import models

      class Customer(models.Model):
      first_name = models.CharField(max_length=100)
      last_name = models.CharField(max_length=100)

      class Meta:
      indexes = [
      models.Index(fields=['last_name', 'first_name']),
      models.Index(fields=['first_name'], name='first_name_idx'),
      ]
    • unique_together

      • 一组字段名,合起来必须是唯一的:
        1
        unique_together = [['driver', 'restaurant']]
      • 这是一个列表,这些列表在一起考虑时必须是唯一的。它在 Django 管理中使用,并在数据库级别执行(即在 CREATE TABLE 语句中包含适当的 UNIQUE 语句)。
      • 为方便起见,unique_together 在处理单组字段时可以是一个单一的列表:
        1
        unique_together = ['driver', 'restaurant']
      • 一个 ManyToManyField 不能被包含在 unique_together 中。(不清楚那意味着什么!)如果你需要验证与 ManyToManyField 相关的唯一性,可以尝试使用信号或显式 through 模型。
      • 在模型验证过程中,当约束条件被违反时引发的 ValidationError 具有 unique_together 错误代码。
    • index_together

      • 一组字段名,合在一起,是有索引的:
        1
        2
        3
        index_together = [
        ["pub_date", "deadline"],
        ]
      • 该字段清单将被编入索引(即发出适当的 CREATE INDEX 语句)。
      • 为方便起见,index_together 在处理一组字段时,可以是一个单一的列表:
        1
        index_together = ["pub_date", "deadline"]
    • constraints

      你想在模型上定义的 约束 列表:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      from django.db import models

      class Customer(models.Model):
      age = models.IntegerField()

      class Meta:
      constraints = [
      models.CheckConstraint(check=models.Q(age__gte=18), name='age_gte_18'),
      ]
    • verbose_name

      • 对象的可读名称,单数:
        1
        verbose_name = "pizza"
      • 如果没有给定,Django 将使用一个 munged 版本的类名:CamelCase 变成 camel case
    • verbose_name_plural

      • 对象的复数名称:
        1
        verbose_name_plural = "stories"
      • 如果没有给定,Django 将使用 verbose_name + "s"
  • 只读的 Meta 属性

    • label

      对象的表示,返回 app_label.object_name,例如 'polls.Question'

    • label_lower

      模型的表示,返回 app_label.model_name,例如 'polls.question'

  • 增加

    • 方式一

      1
      2
      3
      4
      5
      6
      7
      user = User()
      user.username = username
      user.password = hashlib.sha256(password.encode('utf-8')).hexdigest()
      if phone:
      user.phone = phone
      user.save()
      return HttpResponse('用户注册成功!')
    • 方式二

      1
      2
      3
      4
      password = hashlib.sha256(password.encode('utf-8')).hexdigest()
      user = User.objects.create(username=username, password=password, phone=phone)
      if user:
      return HttpResponse('用户注册成功!')
  • 删除

    1. 找到要删除的对象
    2. 该对象去调用 delete()
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      user = User.objects.get(pk=1)
      if user:
      # 删除:逻辑删除
      user.is_delete = 1
      user.save()
      # 删除:物理删除
      result = user.delete()
      print(result)
      # (1, {'user.User': 1})
      # 第一个1表示受影响的行数(删除的行数),第二个1表示被删除的id
  • 修改

    • 方式一

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      # 获取需要更新的数据
      id = request.POST.get('id')
      username = request.POST.get('username')
      phone = request.POST.get('phone')
      is_delete = True if request.POST.get('is_delete') else False
      # 查找更新的是哪个用户
      user = User.objects.get(pk=id)
      # 更新数据
      user.username = username
      user.phone = phone
      user.is_delete = is_delete
      user.save()
      return redirect('user:show')
    • 方式二

      1
      2
      3
      4
      5
      6
      7
      8
      9
      # 获取需要更新的数据
      id = request.POST.get('id')
      username = request.POST.get('username')
      phone = request.POST.get('phone')
      is_delete = True if request.POST.get('is_delete') else False
      # 更新数据
      result = User.objects.filter(id=id).update(username=username, phone=phone, is_delete=is_delete)
      print(result) # 结果为:1,即:受影响的行数
      return redirect('user:show')
    • 备注

      1
      <input type="checkbox" name="is_delete" {% if user.is_delete %}checked{% endif %}> 用户是否删除

      这样设置后,如果复选框被选中 (checked),其值为 on,如果没有被选中,其值为 None,所以 is_delete 的值可以如此获得:

      1
      is_delete = True if request.POST.get('is_delete') else False
  • 查找

    • filter()

      • 返回一个新的 QuerySet,其中包含与给定查找参数相匹配的对象。
      • 查询参数(**kwargs)的格式应在下文 Field lookups 中描述。多个参数通过底层 SQL 语句中的 AND 连接。
      • 如果你需要执行更复杂的查询(例如,带有 OR 语句的查询),你可以使用 Q 对象*args)。
      • 示例
        1
        User.objects.filter(pk=1)
    • exclude()

      • 返回一个新的 QuerySet,其中包含与给定查找参数不匹配的对象。
      • 查询参数(**kwargs)的格式应在下文 Field lookups 中描述。多个参数通过底层 SQL 语句中的 AND 连接,整个过程用 NOT() 括起来。
    • order_by()

      默认情况下,QuerySet 返回的结果是按照模型 Meta 中的 ordering 选项给出的排序元组排序的。你可以通过使用 order_by 方法在每个 QuerySet 的基础上覆盖这一点。

    • values()

      • 返回一个 QuerySet,当用作可迭代对象时,返回字典,而不是模型实例。
      • 其中每一个字典都代表一个对象,键与模型对象的属性名相对应。
    • all()

      • 返回当前 QuerySet (或 QuerySet 子类)的 副本。 这在以下情况下很有用:你可能想传入一个模型管理器或一个 QuerySet,并对结果做进一步过滤。在任何一个对象上调用 all() 后,你肯定会有一个 QuerySet 可以使用。
      • 当一个 QuerySet执行 时,它通常会缓存其结果。如果数据库中的数据可能在 QuerySet 被评估后发生了变化,你可以通过调用 all() 对以前执行过的 QuerySet 进行更新。
      • 示例
        1
        User.objects.all()
    • get()

      返回与给定的查找参数相匹配的对象,其格式应该在 Field lookups 中描述。你应该使用保证唯一的查询,比如主键或唯一约束中的字段。

      1
      User.objects.get(pk=1)
    • count()

      返回一个整数,表示数据库中与 QuerySet 匹配的对象数量。

    • first()

      返回查询集匹配的第一个对象,如果没有匹配的对象,则返回 None。如果 QuerySet 没有定义排序,那么查询集自动按主键排序。这可能会影响聚合结果,如 Interaction with order_by() 中所述。

    • last()

      first() 工作原理相同,但返回的是查询集中的最后一个对象。

    • exists()

      • 如果 QuerySet 包含任何结果,则返回 True,如果不包含,则返回 False。该函数试图以最简单、最快速的方式执行查询,但它 执行 的查询与普通的 QuerySet 查询几乎相同。
      • existence() 对于与 QuerySet 中任何对象的存在有关的搜索很有用,特别是在一个大的 QuerySet 的背景下。

模型关系 (ORM)

  • 1-1 (OneToOneField)

    • 1-1 概述

      • 一对一的关系。概念上,这类似于 ForeignKeyunique=True,但关系的 “反向” 将直接返回一个单一对象。
      • 最有用的是作为某种方式 “扩展” 另一个模型的主键;多表继承 是通过添加一个从子模型到父模型的隐式一对一关系来实现的,例如:
      • 需要一个位置参数:模型将与之相关的类。这与 ForeignKey 的工作原理完全相同,包括关于 递归惰性 关系的所有选项。
      • 如果没有为 OneToOneField 指定 related_name 参数,Django 将使用当前模型的小写名作为默认值。
    • 创建模型

      • 用户表
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        ## 用户表(主表)
        class User(models.Model):
        username = models.CharField(max_length=12, unique=True)
        password = models.CharField(max_length=100)
        phone = models.CharField(max_length=11, null=True, blank=True)
        email = models.CharField(max_length=50, null=True, blank=True)
        add_time = models.DateTimeField(auto_now_add=True)

        def __str__(self):
        return self.username

        class Meta:
        db_table = 'user'
      • 补充表
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        ## 补充表(从表)
        class UserProfile(models.Model):
        realname = models.CharField(max_length=12, null=True, blank=True)
        address = models.CharField(max_length=200, null=True, blank=True)
        age = models.IntegerField(null=True, blank=True)
        job = models.CharField(max_length=50, null=True, blank=True)
        gender = models.CharField(max_length=5, choices=(('boy', '男'), ('girl', '女')))
        user = models.OneToOneField(to=User, on_delete=models.CASCADE) # 建立表之间的关系

        class Meta:
        db_table = 'userprofile'
    • 添加数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      def register(request):
      # 用户表
      user = User()
      user.username = '张三'
      user.password = '123456'
      user.phone = '13611111111'
      user.email = '13611111111@163.com'
      user.save()

      # 补充表
      userProfile = UserProfile()
      userProfile.address = '北京'
      userProfile.age = '22'
      userProfile.gender = 'boy'
      userProfile.realname = '张三丰'
      userProfile.job = '唱、跳、rap'
      userProfile.user_id = user.id
      userProfile.save()
      return HttpResponse('用户添加成功!')
    • 获取数据

      级联查询:对象.关系模型名(小写).属性名

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <table class="table table-hover">
      <tr>
      <td>序号</td>
      <td>用户名</td>
      <td>真实姓名</td>
      <td>电话</td>
      <td>邮箱</td>
      <td>住址</td>
      </tr>
      {% for user in users %}
      <tr>
      <td>{{ forloop.counter }}</td>
      <td>{{ user.username }}</td>
      <td>{{ user.userprofile.realname }}</td>
      <td>{{ user.phone }}</td>
      <td>{{ user.email }}</td>
      <td>{{ user.userprofile.address }}</td>
      </tr>
      {% endfor %}
      </table>
  • 1-n (ForeignKey)

    • 1-n 概述

      • 一个一对多的关系。需要两个位置参数:模型相关的类和 on_delete 选项。
      • 要创建一个与自己有一对多关系的对象,使用 odels.ForeignKey('self', on_delete=models.CASCADE)
    • 1-n 参数

      • on_delete
        • 当一个由 ForeignKey 引用的对象被删除时,Django 将模拟 on_delete 参数所指定的 SQL 约束的行为。

        • CASCADE
          • 级联删除。Django 模拟了 SQL 约束 ON DELETE CASCADE 的行为,也删除了包含 ForeignKey 的对象。
          • Model.delete() 在相关的模型上没有被调用,但是 pre_deletepost_delete 信号是为所有被删除的对象发送的。
        • PROTECT

          通过引发 ProtectedError,即 django.db.IntegrityError 的子类,防止删除被引用对象。

        • RESTRICT

          通过引发 RestrictedErrordjango.db.IntegrityError 的一个子类)来防止删除被引用的对象。与 PROTECT 不同的是,如果被引用的对象也引用了一个在同一操作中被删除的不同对象,但通过 CASCADE 关系,则允许删除被引用的对象。

        • SET_NULL

          设置 ForeignKey 为空;只有当 nullTrue 时,才有可能。

        • SET_DEFAULT

          ForeignKey 设置为默认值,必须为 ForeignKey 设置一个默认值。

        • SET()

          ForeignKey 设置为传递给 SET() 的值,或者如果传入可调用对象,则为调用它的结果。 在大多数情况下,传递可调用对象是必要的,以避免在导入 models.py 时执行查询

        • DO_NOTHING

          不采取任何行动。如果你的数据库后端强制执行引用完整性,这将导致一个 IntegrityError 除非你手动添加一个 SQL ON DELETE 约束条件到数据库字段。

      • limit_choices_to
        • 当使用 ModelForm 或管理中渲染该字段时,设置该字段的可用选择限制(默认情况下,查询集中的所有对象都可以选择)。可以使用字典、 Q 对象,或者返回字典或 Q 对象的可调用对象。
        • 导致 ModelForm 上的对应字段只列出有 is_staff=TrueUsers。这在 Django 管理中可能会有帮助。
        • 如果 limit_choices_to 是或返回一个 Q对象,这对 复杂的查询 很有用,那么只有当该字段没有在 ModelAdmin 中的 raw_id_fields 中列出时,它才会对管理中可用的选择产生影响。
        • 用于从相关对象到这个对象的关系的名称。这也是 related_query_name 的默认值(用于从目标模型反向过滤名称的名称)。请参阅 关联对象文档 以获得完整的解释和示例。请注意,当你在 抽象模型 是可用的。
        • 如果你不希望 Django 创建一个反向关系,可以将 related_name 设置为 '+' 或者以 '+' 结束。
      • to_field

        关联对象的字段。默认情况下,Django 使用相关对象的主键。如果你引用了一个不同的字段,这个字段必须有 unique=True

      • db_constraint
        • 控制是否应该在数据库中为这个外键创建一个约束。默认值是 True,这几乎是你想要的;将其设置为 False 对数据完整性非常不利。话虽如此,下面是一些你可能想要这样做的情况:
          • 你有无效的冗余数据
          • 你正在共享你的数据库
        • 如果将此设置为 False,访问一个不存在的相关对象将引发 DoesNotExist 异常。
      • swappable
        • 控制迁移框架的反应,如果这个 ForeignKey 指向一个可交换的模型。如果它是 True —— 默认值 -—— 那么如果 ForeignKey 指向的模型与 settings.AUTH_USER_MODEL 的当前值相匹配(或其他可互换模型配置),则关系将在迁移中使用对配置的引用而不是直接对模型进行存储。
        • 只有当你确定你的模型应该总是指向换入的模型时,你才想把它覆盖为 False,例如,如果它是一个专门为你的自定义用户模型设计的配置文件模型。
        • 将它设置为 False 并不意味着你可以引用一个可交换的模型,即使它被交换了 —— False 意味着用这个外键进行的迁移将始终引用你指定的确切模型(所以如果用户试图用你不支持的 User 模型运行,它将失败,例如)。
        • 如果不确定,就保留它在默认为 True 的状态。
    • 创建模型

      • 类别表
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        # 主表
        class Type(models.Model):
        tname = models.CharField(max_length=200)
        add_time = models.DateTimeField(auto_now_add=True)

        def __str__(self):
        return self.tname

        class Meta:
        db_table = 'type'
      • 商品表
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        # 从表
        class Goods(models.Model):
        gname = models.CharField(max_length=200)
        price = models.DecimalField(max_digits=6, decimal_places=2)
        sale_num = models.IntegerField(default=0)
        store_num = models.IntegerField(default=20)
        save_num = models.IntegerField(default=0)
        # 1 - n
        type = models.ForeignKey(to=Type, on_delete=models.CASCADE)

        def __str__(self):
        return self.gname

        class Meta:
        db_table = 'goods'
    • 添加数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      def add_goods(request):
      if request.method == 'POST':
      gname = request.POST.get('gname')
      price = request.POST.get('price')
      type = request.POST.get('type')

      try:
      # 添加数据:create()
      goods = Goods.objects.create(gname=gname, price=price, type_id=type)
      if goods:
      return HttpResponse('商品添加成功!')
      except Exception as e:
      return HttpResponse(e)
      else:
      types = Type.objects.all()
      return render(request, 'goods/add.html', {'types': types})
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      {% extends 'base.html' %}

      {% block title %}
      商品添加
      {% endblock %}

      {% block content %}
      <h3 style="text-align: center">商品添加</h3>
      <form class="form-horizontal" action="{% url 'goods:add' %}" method="post">
      {% csrf_token %}
      <div class="form-group">
      <label class="col-sm-2 control-label">商品名称</label>
      <div class="col-sm-6">
      <input type="text" class="form-control" placeholder="商品名称" name="gname">
      </div>
      </div>
      <div class="form-group">
      <label class="col-sm-2 control-label">商品价格</label>
      <div class="col-sm-6">
      <input type="text" class="form-control" placeholder="商品价格" name="price">
      </div>
      </div>
      <div class="form-group">
      <label class="col-sm-2 control-label">商品分类</label>
      <div class="col-sm-6">
      <select class="form-control" name="type">
      {% for type in types %}
      <option value="{{ type.id }}">{{ type.tname }}</option>
      {% endfor %}
      </select>
      </div>
      </div>
      <div class="form-group">
      <div class="col-sm-offset-2 col-sm-10">
      <button type="submit" class="btn btn-success">添加商品</button>
      </div>
      </div>
      </form>
      {% endblock %}

    • 获取数据

      • 一获取多 (主获取从)

        根据类别获取商品

        1
        2
        3
        def detail_type(request, tid):
        type = Type.objects.get(pk=tid)
        return render(request, 'goods/detail_type.html', {'type': type})
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        <table class="table table-hover">
        {% for goods in type.goods_set.all %}
        <tr>
        <td>{{ forloop.counter }}</td>
        <td>{{ goods.gname }}</td>
        <td>{{ goods.price }}</td>
        </tr>
        {% empty %}
        <tr>
        <td colspan="3">还没有商品,赶快添加吧!</td>
        </tr>
        {% endfor %}
        </table>

        隐性属性 对象.关联对象(小写)_set.all、filter

      • 多获取一 (从获取主)

        根据商品获取类别

        1
        2
        3
        def detail_goods(request, gid):
        goods = Goods.objects.get(pk=gid)
        return render(request, 'goods/detail_goods.html', {'goods': goods})
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        {% extends 'base.html' %}

        {% block title %}
        商品详情
        {% endblock %}

        {% block content %}
        <p>商品名:{{ goods.gname }}</p>
        <p>商品价格:{{ goods.price }}</p>
        <p>商品销量:{{ goods.sale_num }}</p>
        <p>商品库存:{{ goods.store_num }}</p>
        <p>商品收藏量:{{ goods.save_num }}</p>
        <p>所属分类:{{ goods.type.tname }}</p>
        {% endblock %}
    • 删除数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      {% extends 'base.html' %}

      {% block title %}
      所有分类
      {% endblock %}

      {% block content %}
      <h2 style="text-align: center">商城所有分类</h2>
      <ul class="list-group" style="text-align: center">
      {% for type in types %}
      <li class="list-group-item">
      <a href="{% url 'goods:detail_type' type.id %}">{{ type.tname }}</a> |
      <a href="{% url 'goods:delete_type' %}?id={{ type.id }}">删除分类</a>
      </li>
      {% endfor %}
      </ul>
      {% endblock %}
      1
      2
      3
      4
      5
      6
      # 删除分类:delete()
      def delete_type(request):
      id = request.GET.get('id')
      type = Type.objects.get(pk=id)
      type.delete()
      return redirect('goods:show_type')
      • 如果设置 on_delete=models.CASCADE,删除时会级联删除。例如:删除某个商品分类,会将属于这个分类的所有商品一并删除。
      • 如果设置 on_delete=models.PROTECT,删除时会报错。例如:删除某个商品分类,如果商品表中有属于这个分类的商品,则会报错。
      • 如果设置 on_delete=models.DO_NOTHING,删除时不会采取任何行动。例如:删除某个商品分类,但是属于这个分类的所有商品并不会被删除,仅仅是删除了分类表中的数据,商品表中的数据无论是否关联都不会被删除。
  • m-n (ManyToManyField)

    • m-n 概述

      • 一个多对多的关系。需要一个位置参数:模型相关的类,它的工作原理与 ForeignKey 完全相同,包括 递归惰性 关系。
      • 可以通过字段的 RelatedManager 来添加、删除或创建相关对象。
      • 在幕后,Django 创建了一个中间连接表来表示多对多的关系。默认情况下,这个表名是使用多对多字段的名称和包含它的模型的表名生成的。由于有些数据库不支持超过一定长度的表名,这些表名将被自动截断,并使用唯一性哈希,例如 author_books_9cdf。你可以使用 db_table 选项手动提供连接表的名称。
    • m-n 参数

      • 参数概述
        • ManyToManyField 接受一组额外的参数 —— 都是可选的 —— 控制关系如何运作。
        • ManyToManyField 不支持 validators
        • null 没有效果,因为没有办法在数据库层面要求建立关系。
      • ForeignKey.related_name 相同。

      • ForeignKey.related_query_name 相同。

      • limit_choices_to

        ForeignKey.limit_choices_to 相同。

      • symmetrical
        • 仅在自身上定义多对多字段关系时。考虑以下模型

          1
          2
          3
          4
          from django.db import models

          class Person(models.Model):
          friends = models.ManyToManyField("self")
        • 当 Django 处理这个模型时,它识别出它本身有一个 ManyToManyField,因此,它没有给 Person 类添加 person_set 属性。相反, ManyToManyField 被认为是对称的,也就是说,如果我是你的朋友,那么你就是我的朋友。

        • 如果你不想让 self 的多对多关系对称,可以将 symmetrical 设置为 False。这样会强制 Django 添加反向关系的描述符,允许 ManyToManyField 关系是非对称的。

      • through
        • Django 会自动生成一个表来管理多对多关系。但是,如果你想手动指定中间表,你可以使用 through 选项来指定代表你要使用的中间表的 Django 模型。

        • 这个选项最常见的用法是当你想把 额外的数据与多对多关系 联系起来。

        • 如果你没有指定一个显式的 through 模型,你仍然可以使用一个隐式的 through 模型类来直接访问为保持关联而创建的表。它有三个字段来链接模型。

          • 如果源模型和目标模型不同,则会生成以下字段:
            • id :关系的主键。
            • <containing_model>_id :声明 ManyToManyField 的模型的 id
            • <other_model>_idManyToManyField 指向的模型的 id
          • 如果 ManyToManyField 指向的来源和目标是相同的模型,下面的字段会生成:
            • id :关系的主键。
            • from_<model>_id :指向模型的实例(即源实例)的 id
            • to_<model>_id :关系所指向的实例(即目标模型实例)的 id
        • 这个类可以像普通模型一样,用于查询给定模型实例的关联记录:

          1
          Model.m2mfield.through.objects.all()
      • through_fields
        • 只有当指定了一个自定义的中间模型时才会使用,Django 通常会决定使用中介模型的哪些字段来自动建立多对多的关系。然而,考虑以下模型:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          from django.db import models

          class Person(models.Model):
          name = models.CharField(max_length=50)

          class Group(models.Model):
          name = models.CharField(max_length=128)
          members = models.ManyToManyField(
          Person,
          through='Membership',
          through_fields=('group', 'person'),
          )

          class Membership(models.Model):
          group = models.ForeignKey(Group, on_delete=models.CASCADE)
          person = models.ForeignKey(Person, on_delete=models.CASCADE)
          inviter = models.ForeignKey(
          Person,
          on_delete=models.CASCADE,
          related_name="membership_invites",
          )
          invite_reason = models.CharField(max_length=64)
        • MembershipPerson两个 外键( personinviter ),这就使得两者的关系变得模糊不清,Django 无法知道应该使用哪个外键。在这种情况下,你必须使用 through_fields 明确指定 Django 应该使用哪个外键,就像上面的例子一样。

        • through_fields 接受一个二元元组 ('field1', 'field2'),其中 field1 是定义在 ManyToManyField 上的模型(本例中为 group )的外键名称,field2 是目标模型(本例中为 person )的外键名称。

        • 当你在中间模型上有一个以上的外键到任何一个(甚至两个)参与多对多关系的模型时,你 必须 指定 through_fields。这也适用于 递归关系,当使用一个中间模型,并且该模型有两个以上的外键,或者你想明确指定 Django 应该使用哪两个外键。

      • db_table

        要创建的用于存储多对多数据的表的名称。如果没有提供这个表名,Django 将根据以下表名创建一个默认表名:定义关系的模型表和字段本身的名称。

      • db_constraint
        • 控制是否应该在数据库中为中间表的外键创建约束。默认值是 True,这几乎是你想要的;将其设置为 False 对数据完整性非常不利。话说回来,下面是一些你可能想要这样做的情况:

          • 你有无效的冗余数据
          • 你正在共享你的数据库
        • 同时传递 db_constraintthrough 会引发错误。

      • swappable
        • 控制迁移框架的反应,如果这个 ManyToManyField 指向一个可交换的模型。如果它是 True —— 默认值 —— 那么如果 ManyToManyField 指向的模型与 settings.AUTH_USER_MODEL 的当前值相匹配(或其他可交换模型配置),关系将被存储在迁移中,使用对配置的引用,而不是直接对模型的引用。
        • 只有当你确定你的模型应该总是指向换入的模型时,你才想把它覆盖为 False,例如,如果它是一个专门为你的自定义用户模型设计的配置文件模型。
        • 如果不确定,就保留它在默认为 True 的状态。
    • 创建模型

      • 用户表
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        # 主表
        class User(models.Model):
        username = models.CharField(max_length=12, unique=True)
        password = models.CharField(max_length=100)
        phone = models.CharField(max_length=11, null=True, blank=True)
        email = models.CharField(max_length=50, null=True, blank=True)
        add_time = models.DateTimeField(auto_now_add=True)

        def __str__(self):
        return self.username

        class Meta:
        db_table = 'user'
      • 商品表
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        # 从表
        class Goods(models.Model):
        gname = models.CharField(max_length=200)
        price = models.DecimalField(max_digits=6, decimal_places=2)
        sale_num = models.IntegerField(default=0)
        store_num = models.IntegerField(default=20)
        save_num = models.IntegerField(default=0)
        # 1 - n
        type = models.ForeignKey(to=Type, on_delete=models.CASCADE)
        # m - n
        users = models.ManyToManyField(to=User)

        def __str__(self):
        return self.gname

        class Meta:
        db_table = 'goods'
    • 添加数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      {% extends 'base.html' %}

      {% block title %}
      商品详情
      {% endblock %}

      {% block content %}
      <p>商品名:{{ goods.gname }}</p>
      <p>商品价格:{{ goods.price }}</p>
      <p>商品销量:{{ goods.sale_num }}</p>
      <p>商品库存:{{ goods.store_num }}</p>
      <p>商品收藏量:<span>{{ goods.save_num }}</span>
      <span id="save_icon" class="glyphicon glyphicon-star" style="color: black"></span>
      </p>
      <p>所属分类:{{ goods.type.tname }}</p>

      <button id="btn_add" type="button" class="btn btn-success">加入购物车</button>
      {% endblock %}

      {% block myjs %}
      <script>
      $('#btn_add').click(function () {
      $.getJSON("{% url 'goods:add_cart' %}", {'id': {{ goods.id }}}, function (data) {
      if (data.msg === 'success') {
      alert('添加购物车成功')
      }
      })
      })
      </script>
      {% endblock %}
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      # 商品添加到购物车
      def add_cart(request):
      id = request.GET.get('id')
      # 找到goods对象
      goods = Goods.objects.get(pk=id)
      user = User.objects.get(pk=1)
      # 添加数据:对象A.集合.add(对象B)
      # goods.users.add(user) 或 user.goods_set.add(goods)
      user.goods_set.add(goods)
      return JsonResponse({'msg': 'success'})
    • 获取数据

      1
      2
      3
      4
      # 显示购物车商品
      def show_cart(request):
      user = User.objects.get(pk=1)
      return render(request, 'goods/show_cart.html', {'user': user})
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      {% extends 'base.html' %}

      {% block title %}
      购物车
      {% endblock %}

      {% block content %}
      <h1 style="text-align: center">{{ user.username }} 购物车中的商品如下</h1>
      <table class="table table-hover">
      <tr>
      <td>序号</td>
      <td>商品名称</td>
      <td>价格</td>
      <td>数量</td>
      <td>操作</td>
      </tr>
      {# 获取数据 #}
      {% for goods in user.goods_set.all %}
      <tr>
      <td>{{ forloop.counter }}</td>
      <td>{{ goods.gname }}</td>
      <td>{{ goods.price }}</td>
      <td>1</td>
      {# 删除数据 #}
      <td><a href="{% url 'goods:delete_cart' %}?id={{ goods.id }}">删除</a></td>
      </tr>
      {% endfor %}

      </table>
      {% endblock %}
    • 删除数据

      1
      2
      3
      4
      5
      6
      7
      8
      # 删除购物车商品
      def delete_cart(request):
      id = request.GET.get('id')
      goods = Goods.objects.get(pk=id)
      user = User.objects.get(pk=1)
      # 删除数据:remove()
      user.goods_set.remove(goods)
      return redirect('goods:show_cart')