Django定义数据模型

4 阅读18分钟

Django定义数据模型

1. 简单说明

定义模型就是实现一个django.db.models.Model类的子类,每个模型被表示为django.db.models.Model类的子类。每个模型有许多类变量,它们都表示模型里的一个数据库字段,每个字段都是Field类的实例,模型的文件名称默认为models.py,也可使用其他名称,一般放在应用内。

模型由三个部分组成:

  • Filed类型字段
  • 魔术方法__str__
  • Model元数据声明

代码示例:

from django.db import models
class Question(models.Model):
	question_text = models.CharField(max_length=200)
	pub_date = models.DateTimeField('date published')

2. 定义字段

字段的定义包括字段名、字段类型和字段选项,示例代码如下:

question = models.CharField(max_length=200,blank=True)

其中,question为字段名,CharField为字段类型,max_lengthblank为字段选项。

Django在 django.db.models.fields模块中定义了可用的字段类型。为了方便,Django已将fields模块导入到django.db.models模块中。通常,先用from django.db import models导入models模块,然后用models.xxxField来引用字段类型。

2.1. 字段类型

2.1.1. 数值字段
  • AutoField:自动增量,32位整数,取值范围是1~2^31-1
  • BigAutoField:自动增量,64 位整数,取值范围是1~2^63−1,也就是1-9223372036854775807
  • SmallAutoField() 自动增量,存储小整数。取值范围是−2^15~2^15−1,也就是1-32767
  • IntegerField:存储整数,取值范围是−2^31~2^31−1,字段的默认表单控件为NumberInput
  • BigIntegerField:64位整数,取值范围是−263~263−1,字段的默认表单控件为TextInput
  • SmallIntegerField:存储小整数。取值范围是−2^15~2^15−1
  • PositiveIntegerField:存储非负整数。取值范围是0~2^31−1
  • PositiveSmallIntegerField:储非负小整数。取值范围是0~2^15−1
  • PositiveBigIntegerField()整型,范围0-9223372036854775807
  • BooleanField:存储TrueFalse,字段的默认表单控件为CheckboxInput
  • FloatField:存储浮点数字。字段的默认表单控件为NumberInput
  • DecimalField(max_digits=None, decimal_places=None)存储固定精度的十进制数字段,字段值为Decimal实例,字段的默认表单控件为 NumberInputmax_digits:小数总长度,decimal_places:小数位长度
2.1.2. 文本字段
  • CharField:存储字符串。字段的默认表单控件为TextInput,必须添加max_length=选项
  • TextField:存储大量文本。字段的默认表单控件为Textarea
  • EmailField:存储 E-mail 地址
  • URLField:存储URL,字段的默认表单控件为TextInput
  • GenericIPAddressField:储字符串格式的IPv4IPv6地址,字段的默认表单控件为TextInput
  • SlugField:字符串字段,存储Slug数据,只包含字母、数字、下划线或连字符
2.1.3. 时间字段
  • DateField:存储日期,字段值为datetime.date实例。字段的默认表单控件为TextInput
  • TimeField:存储时间,字段值为datetime.time实例。字段的默认表单控件为TextInput
  • DateTimeField:存储日期时间,字段值为datetime.datetime实例,字段的默认表单控件为TextInput
  • DurationField:存储时间段
2.1.4. 文件相关字段
  • FileField:存储文件。字段的默认表单控件为ClearableFileInput
  • ImageField:存储图片,字段的默认表单控件为ClearableFileInput
  • FilePathField(path='', match=None, recursive=False, allow_files=True, allow_folders=False, max_length=100) :存储文件路径,path:文件夹路径,match=None:正则匹配,recursive=False:递归子文件夹,allow_files=True:允许文件,allow_folders=False:允许文件夹
2.1.5. 其他类型字段
  • BinaryField:存储原始二进制数据
  • UUIDField:存储唯一标识符,字段值为UUID类实例
  • JSONField(encoder=None, decoder=None):存储 JSON 编码数据,encoder,用于序列化标准 JSON 序列化器不支持的数据字段(例如 datetime.datetimeUUID),decoder,用于反序列化从数据库中获取的值

2.2. 字段选项

  • null:默认为False。为True时,Django在字段无数据时将空值NULL存入数据库(字符串字段存入空字符串)
  • blank:默认为False。为True时,字段允许为空,即表单验证将允许输入空值。blank影响数据验证,null影响数据库数据存储
  • choices:为字段定义选择项。字段值为选择项中的列表或元组中的值
  • db_column:定义字段在数据库表中的列名称。未设置时,Django用模型中的字段名作为数据库表的列名称
  • db_index:为True时,为该字段创建数据库索引
  • db_tablespace:若为字段创建了索引,则为字段索引设置数据库的表空间名称
  • default:设置字段默认值
  • editable:默认是True。为False时,字段不在模型表单中显示
  • error_messages:设置错误提示信息。该设置会覆盖默认的错误提示信息
  • help_text:设置字段的帮助信息
  • primary_key:设置为True时,字段成为模型的主键
  • unique:设置为True时,字段值在整个表中必须是唯一的
  • unique_for_date:设置为日期或日期时间字段名,关联的两个字段值在整个表中必须是唯一的
  • unique_for_month:类似unique_for_date。与关联的月份唯一
  • unique_for_year:类似unique_for_date。与关联的年份唯一
  • verbose_name:为字段设置备注名称
  • validators:为字段设置校验器
  • related_name:为字段设置对象内调用的别名
2.2.1. 特殊的字段选项choices

choices是一个映射或可迭代对象,用作模型字段的选项。 提供choices后,字段的默认表单小部件会变为选择框,并且选项会被模型验证强制执行。

choices 在 Django 中有以下几种形式:

  1. 简单的列表或元组形式

这是最常见的形式,choices 定义为一个包含 (值, 可读名称) 的列表或元组。适用于定义一组固定的选项。

# 1、列表形式 (定义在模型类之外)
YEAR_IN_SCHOOL_CHOICES = [
    ("FR", "Freshman"),
    ("SO", "Sophomore"),
    ("JR", "Junior"),
    ("SR", "Senior"),
    ("GR", "Graduate"),
]
# 2、元组形式 (定义在模型类之外)
# YEAR_IN_SCHOOL_CHOICES = (
#    ("FR", "Freshman"),
#    ("SO", "Sophomore"),
#    ("JR", "Junior"),
#    ("SR", "Senior"),
#    ("GR", "Graduate"),
#)
class Student(models.Model):
   # 3、列表/元组形式(定义在模型类内部,即将选项列表定义为类常量)
   # YEAR_IN_SCHOOL_CHOICES = [
   #     ("FR", "Freshman"),
   #     ("SO", "Sophomore"),
   #     ("JR", "Junior"),
   #     ("SR", "Senior"),
   #     ("GR", "Graduate"),
   # ]
    year_in_school = models.CharField(
        max_length=2,
        choices=YEAR_IN_SCHOOL_CHOICES,
        default="FR",
    )
  1. 字典映射形式

在 Django 5.0 中,choices支持使用字典定义,每个键表示实际存储的值,值为可读名称。适合需要清晰键值对的场景。

# 1、定义在模型类之外
YEAR_IN_SCHOOL_CHOICES = {
    "FR": "Freshman",
    "SO": "Sophomore",
    "JR": "Junior",
    "SR": "Senior",
    "GR": "Graduate",
}
class Student(models.Model):
   # 2、定义在模型类内部
   # YEAR_IN_SCHOOL_CHOICES = {
   #     "FR": "Freshman",
   #     "SO": "Sophomore",
   #     "JR": "Junior",
   #     "SR": "Senior",
   #     "GR": "Graduate",
   # }
    year_in_school = models.CharField(
        max_length=2,
        choices=YEAR_IN_SCHOOL_CHOICES,
        default="FR",
    )
  1. 分组形式

通过嵌套元组或字典分组的方式,将选项分为不同类别,适用于多级选择的场景。

# 1、使用嵌套元组 (定义在模型类之外)
MEDIA_CHOICES = [
    ("Audio", [("vinyl", "Vinyl"), ("cd", "CD")]),
    ("Video", [("vhs", "VHS Tape"), ("dvd", "DVD")]),
    ("unknown", "Unknown"),
]

# 2、使用字典 (定义在模型类之外)
# MEDIA_CHOICES = {
#    "Audio": {"vinyl": "Vinyl", "cd": "CD"},
#    "Video": {"vhs": "VHS Tape", "dvd": "DVD"},
#    "unknown": "Unknown",
#}
class Media(models.Model):
   # 3、使用嵌套元组 (定义在模型类内部)
   # MEDIA_CHOICES = [
   #     ("Audio", [("vinyl", "Vinyl"), ("cd", "CD")]),
   #     ("Video", [("vhs", "VHS Tape"), ("dvd", "DVD")]),
   #     ("unknown", "Unknown"),
   # ]

    media_type = models.CharField(
        max_length=10,
        choices=MEDIA_CHOICES,
        default="unknown",
    )
  1. 可调用对象

choices 定义为一个无参可调用对象(如函数),函数返回一个合法的 choices 结构(例如列表或字典)。适合动态生成选项的场景。

def get_currencies():
    return {i: i for i in ["USD", "EUR", "JPY"]}

class Expense(models.Model):
    currency = models.CharField(max_length=3, choices=get_currencies)
  1. 枚举类型

使用 Django 的 TextChoicesIntegerChoices 等枚举类来定义 choices,适合需要常量化、易读性高的选择项。Django 自动生成 .choices.labels.values 等属性,便于管理。

class Student(models.Model):
    class YearInSchool(models.TextChoices):
        FRESHMAN = "FR", "Freshman"
        SOPHOMORE = "SO", "Sophomore"
        JUNIOR = "JR", "Junior"
        SENIOR = "SR", "Senior"
        GRADUATE = "GR", "Graduate"
        
    year_in_school = models.CharField(max_length=2, choices=YearInSchool.choices)
  1. 自定义数据类型的枚举

Choices 类的基础上扩展,可以将 choices 与特定数据类型(如 datedatetime)结合,适合需要特定类型支持的场景,如日期 date

import datetime

class MoonLandings(datetime.date, models.Choices):
    APOLLO_11 = 1969, 7, 20, "Apollo 11 (Eagle)"
    APOLLO_12 = 1969, 11, 19, "Apollo 12 (Intrepid)"

补充:

Choices 类的基础上扩展,可以将 choices 与特定数据类型(如 datedatetime)结合,适合需要特定类型支持的场景,如日期 date

import datetime

class MoonLandings(datetime.date, models.Choices):
    APOLLO_11 = 1969, 7, 20, "Apollo 11 (Eagle)"
    APOLLO_12 = 1969, 11, 19, "Apollo 12 (Intrepid)"

类似的方式还可以应用于处理其他特定数据类型,例如 datetimetimeDecimalUUID 等。以下是一些常见数据类型的示例:

  1. 处理 datetime 类型的 choices

假设我们需要记录一些重要的时间点,可以使用 datetime.datetimemodels.Choices 来定义这些选择:

import datetime
from django.db import models

class ImportantMoments(datetime.datetime, models.Choices):
    NEW_YEAR = 2024, 1, 1, 0, 0, 0, "New Year 2024"
    SUMMER_SOLSTICE = 2024, 6, 21, 12, 0, 0, "Summer Solstice 2024"
    WINTER_SOLSTICE = 2024, 12, 21, 12, 0, 0, "Winter Solstice 2024"

class Event(models.Model):
    moment = models.DateTimeField(choices=ImportantMoments.choices)

这里的 moment 字段的可选项是特定的 datetime 值,比如“2024 年的新年”等。这样我们可以确保用户只能选择预定义的时间点。

  1. 处理 time 类型的 choices

如果需要记录某些特定的时间(比如营业时间、特殊的时刻),可以用 datetime.timemodels.Choices

import datetime
from django.db import models

class BusinessHours(datetime.time, models.Choices):
    OPENING = 9, 0, 0, "Opening Time"
    CLOSING = 18, 0, 0, "Closing Time"
    LUNCH_BREAK = 12, 0, 0, "Lunch Break"

class Store(models.Model):
    operation_time = models.TimeField(choices=BusinessHours.choices)

在这个例子中,operation_time 字段的可选值只能是 BusinessHours 定义的几个特定时刻,比如开门时间、关门时间和午休时间。

  1. 处理 Decimal 类型的 choices

有时我们需要记录固定的金额,可以用 Decimalmodels.Choices。例如,对于定价的选择,可以使用 Decimal 来精确表示金额。

from decimal import Decimal
from django.db import models

class FixedPrices(Decimal, models.Choices):
    BUDGET = Decimal("9.99"), "Budget Option"
    STANDARD = Decimal("19.99"), "Standard Option"
    PREMIUM = Decimal("29.99"), "Premium Option"

class Product(models.Model):
    price = models.DecimalField(max_digits=5, decimal_places=2, choices=FixedPrices.choices)

在这里,price 字段的选项是特定的价格,比如“预算选项”、“标准选项”等,可以确保用户只能选择固定的金额。

  1. 处理 UUID 类型的 choices

如果使用 UUID 来标识某些特定的设备或版本,我们可以用 uuid.UUIDmodels.Choices。例如,在记录设备时,指定唯一的 UUID 作为选项:

import uuid
from django.db import models

class KnownDevices(uuid.UUID, models.Choices):
    DEVICE_A = uuid.UUID("123e4567-e89b-12d3-a456-426614174000"), "Device A"
    DEVICE_B = uuid.UUID("123e4567-e89b-12d3-a456-426614174001"), "Device B"
    DEVICE_C = uuid.UUID("123e4567-e89b-12d3-a456-426614174002"), "Device C"

class Device(models.Model):
    device_id = models.UUIDField(choices=KnownDevices.choices)

这样可以确保 device_id 字段只能是已知设备的 UUID

  1. 处理 float 类型的 choices

在某些情况下,如果我们需要用浮点数记录物理常量(比如π值的不同精度),可以使用 float 类型。

from django.db import models

class PiValues(float, models.Choices):
    LOW_PRECISION = 3.14, "Low Precision Pi"
    MEDIUM_PRECISION = 3.14159, "Medium Precision Pi"
    HIGH_PRECISION = 3.1415926535, "High Precision Pi"

class Measurement(models.Model):
    pi_value = models.FloatField(choices=PiValues.choices)

通过这种方法,pi_value 字段仅限于选择特定精度的 π 值。

2.2.1.1. 取数据

只要是choices参数的字段,如果想要获取对应信息,固定写法get_字段名_display()

user_obj = models.Client.objects.filter(pk=1).first()
print(user_obj.get_gender_display())
# 输出:男

那么还有一个用户名为tom没有书写固定关系,他返回的会是什么?

user_obj = models.Client.objects.filter(pk=4).first()
print(user_obj.get_gender_display())
# 输出:4

3. 字段详解

3.1. 文本字段

3.1.1. CharField

CharField用于存储短字符串,其最大长度可以在模型定义中指定。例如,如果你有一个字段用于存储用户的电话号码或电子邮件地址,那么使用CharField会更合适。默认情况下,CharField使用最大长度为255的字符串。

3.1.2. TextField

CharField不同,TextField用于存储长文本数据。它没有最大长度限制,适用于存储如文章内容、评论或描述字段等大量文本数据。在模型定义中使用TextField,你可以将数据存储为长文本形式。

在选择使用CharField还是TextField时,需要考虑数据的大小和用途。如果你的数据量较小,如电话号码、邮箱地址等,使用CharField更为合适。而如果你的数据量较大,如文章内容、评论等,则应选择TextField
另外,值得注意的是,虽然TextField没有最大长度限制,但在实际应用中应考虑数据库的性能和可扩展性。大量的大型文本字段可能会对数据库性能产生负面影响,因此需要谨慎使用。

3.2. 时间字段

3.2.1. DateField

class DateField(auto_now=False, auto_now_add=False, **options)

一个日期,在Python中用一个datetime.date实例表示。有一些额外的、可选的参数。

3.2.1.1. DateField.auto_now

每次保存对象时,自动将该字段设置为现在。对于“最后修改”的时间戳很有用。请注意,当前日期总是被使用,而不仅仅是一个你可以覆盖的默认值。 只有在调用Model.save()时,该字段才会自动更新。当以其他方式对其他字段进行更新时,如QuerySet.update(),该字段不会被更新,尽管你可以在这样的更新中为该字段指定一个自定义值。

3.2.1.2. DateField.auto_now_add

当第一次创建对象时,自动将该字段设置为现在。对创建时间戳很有用。请注意,当前日期是始终使用的;它不是一个你可以覆盖的默认值。因此,即使你在创建对象时为该字段设置了一个值,它也会被忽略。如果你想修改这个字段,可以设置以下内容来代替auto_now_add=True: 对于DateField: default=date.today——来自datetime.date.today() 对于DateTimeField: default=timezone.now ——来自django.utils.timezone.now()

该字段的默认表单部件是一个DateInput。管理中增加了一个JavaScript日历,以及“今天”的快捷方式。包含一个额外的invalid_date错误信息键。

auto_now_addauto_nowdefault选项是相互排斥的。这些选项的任何组合都会导致错误。

备注:目前,将auto_nowauto_now_add设置为True,将导致该字段设置为editable=Falseblank=True

备注:auto_nowauto_now_add选项将始终使用创建或更新时默认时区的日期。如果你需要一些不同的东西,你可能需要考虑使用你自己的可调用的默认值,或者覆盖save()而不是使用auto_nowauto_now_add;或者使用DateTimeField而不是DateField,并决定如何在显示时间处理从日期时间到日期的转换。

3.2.2. DateTimeField

class DateTimeField(auto_now=False, auto_now_add=False, **options)

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

3.3. 文件字段

文件字段赋值是必须为flie对象或者其子类,其实实现.name._committed数据的对象应该也可以直接赋值

MyFile1.objects.create(name="test",file=fliedir)
# fliedir必须为`flie`对象

使用save()方法时候,可以分为分开参数为namecommitted

from django.core.files.base import ContentFile
file_content = b"Hello, World!"
file = ContentFile(file_content)
obj = MyModel()
obj.my_file.save('test.txt', file)
obj.save()

4. Meta选项

4.1. 初识Meta内部类

每个模型类(Model)下都有一个子类Meta,这个子类就是定义元数据的地方。Meta类封装了一些数据库的信息,称之为Model的元数据。Django会将Meta中的元数据选项定义附加到Model中。常见的元数据定义有 db_table(数据表名称)、abstract(抽象类)、ordering(字段排序)等,Meta作为内部类,它定义的元数据可以让admin管理后台对人类更加友好,数据的可读性更高。

Meta定义的元数据相当于Model的配置信息,所以我们可以根据自己的需求进行选择性的添加。当没有需要的时候也可以不定义Meta,这个时候Django会应用默认的Meta元数据。

4.2. Meta类元数据

通过上面的介绍我们知道Meta类的作用就是用于定义Model的元数据,即不属于Model的字段,但是可以用来标识字段一些属性,下面我们介绍Meta定义的常见元数据以及如何在Model中使用它们。

4.2.1. abstract[抽象类标识]

一个布尔类型的变量。这个属性是定义当前的模型是不是一个抽象类。所谓抽象类是不会对应数据库表的(当作基类使用)。一般我们用它来归纳一些公共属性字段,然后继承它的子类可以继承这些字段。如果abstract = True这个model就是一个抽象类。

4.2.2. ordering[排序]

用于执行获取对象列表时的排序规则。它是一个字符串的列表或元组对象,它的使用格式是由代表字段的字符串和一个表明降序的'-'构成。当字段名前面没有'-'时,将默认使用升序排列。使用'?'将会随机排列。示例如下所示:

ordering=["add_time"] #按照升序排序
ordering=["-add_time"]#按照降序
ordering=["?add_time"]#随机排序
#同时指定多个字段来进行排序
ordering=['add_time','-last_login_time']#先按升序,在按降序
4.2.3. get_latest_by[最新排序]

指定一个DateField或者DateTimeField字段的名字,即model的属性名字。使用示例如下: get_latest_by = "order_date" 这个设置让你在使用模型管理器的lastest()方法时,默认使用order_date指定字段来排序。

4.2.4. order_with_respect_to[关联排序]

这个选项一般用于多对多的关系中,它指向一个关联对象并将该对象进行排序,使用元数据项后你会得到一个 get_xxx_order()set_xxx_order()的方法,通过它们你可以设置或者得到排序的对象。

4.2.5. verbose_name[展示名称]

数据的友好展示名称

4.2.6. verbose_name_plural[复数展示名称]

这个元数据主要用在管理后台的展示上,verbose_name_plural是模型类的复数名 。如果不设置的话,Django会使用小写的模型名作为默认值,并且在结尾加上 s。通过此项元数据设置名字可以去掉s

4.2.7. db_table[数据表名称]

这个字段用于指定数据表的名称,通常没有特别需求,将按照 Django 默认的规则生成 Model 对应的数据库表名。

#定义该model在数据库中的表名称
  db_table = 'Students'
#使用自定义的表名,可以通过以下属性
  table_name = 'my_owner_table'
4.2.8. app_lable[应用命名空间]

这个选项只在一种使用情形,就是你的模型不在默认的应用程序包下的models.py文件中,这时候需要指定你这个模型是哪个应用程序的app_label = 'app_name'

4.2.9. managed[管理数据]

它是一个布尔类型的变量,默认为Ture,代表Django会管理数据的生命周期,即利用Django提供的syncdbreset命令可以完成创建和删除数据表。如果为False,则不会对此模型执行数据库表创建或删除操作。比如数据表之间存在ManyToMany的关系,在指定为managed=False的情况下,Django不会自动创建中间表,需要我们自己手动创建。

4.2.10. indexs[索引]

它是一个列表类型的元数据项,用来定义Model的索引,列表中的每一个元素都是Index类型的实例。 Index引自django.db.models.indexes.Index

4.2.11. default_permissions[默认权限]

Django默认会给每一个定义的Model设置三个权限即添加、更改、删除,它使用格式: default_permissions=('add','change','delete','view')

4.2.12. permissions[权限]

除了 Django 默认给Model添加的三个权限之外,还可以通过permisssionsModel添加额外的权限。不过permissions是一个包含二元组的元组或者列表,所以使用时应该注意格式,即permissions=[(权限代码,权限名称)],示例如下所示: permissions = [(have_read_permission', '有读的权限')]

4.2.13. unique_together[共同唯一]

这个选项用于下面情形:当你需要通过两个字段保持唯一性时使用。比如用户的姓名(name)和 身份证号码(ID number)两者的组合必须是唯一的,那么需要这样设置: unique_together = (("first_name", "last_name"),) 一个ManyToManyField不能包含在unique_together中。如果你需要验证ManyToManyField字段的唯一验证,尝试使用through属性进行关联。

4.2.14. proxy[代理]

默认值为为False,如果设置成Ture,则表示为基类、父类的代理模型。这个选项在后续章节还会进行相关介绍,它的主要作用就是创建父模型的代理模型。

4.2.15. db_tablespace[表空间]

表空间,用于优化数据库性能,常用于OraclePostgerSQL数据库。MySQL数据库不支持表空间,所以当数据存储后端数据库不支持的时候,Django会在自动忽略这个元数据选项。