模型
模型是您的数据唯一而且准确的信息来源。它包含您正在储存的数据的重要字段和行为。一般来说,每一个模型都映射一个数据库表。
基础:
- 每个模型都是一个 Python 的类,这些类继承
django.db.models.Model
- 模型类的每个属性都相当于一个数据库的字段。每个属性映射为一个数据库列。
- 综上诉说,Django 给你一个自动生成访问数据库的 API
默认情况下表名是appname_classname,由app名和你创建的模型名组成,可以自定义。
默认情况下会在表中自动创建一个'id'字段,它是默认主键字段,可以自己手动设置
一旦你定义了你的模型,你需要告诉Django你准备*使用*这些模型。你需要修改设置文件中的INSTALLED_APPS
,在这个设置中添加包含你 models.py
文件的模块的名字。
字段
模型中的每个字段都应该是相应Field
类的实例 。Django使用字段类类型来确定一些事情:
- 字段类型用以指定数据库数据类型(如:
INTEGER
,VARCHAR
,TEXT
) - 默认的HTML表单输入框 ref / forms / widgets>(如:
- 用于Django admin和自动生成表单的基本验证。
每个字段都采用一组特定于字段的参数。例如, CharField
(及其子类)需要一个max_length
参数,该 参数指定VARCHAR
用于存储数据的数据库字段的大小。还有一些通用参数:
null
如果True
,Django将NULL
在数据库中存储空值。默认是False
。
blank如果True
,该字段允许为空。默认是False
。
请注意,这不同于null
。 null
纯粹与数据库相关,而 blank
与验证相关。如果字段有blank=True
,则表单验证将允许输入空值。如果字段有blank=False
,则需要该字段。
choices
2元组的可迭代(例如,列表或元组),用作此字段的选项。如果给出了这个,则默认表单小部件将是一个选择框而不是标准文本字段,并将限制对给定选项的选择。
选项列表如下所示:
YEAR_IN_SCHOOL_CHOICES = (('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ('GR', 'Graduate'), )
每个元组中的第一个元素是将存储在数据库中的值。第二个元素由字段的窗体小部件显示。
给定模型实例,choices
可以使用该get_FOO_display()
方法访问字段的显示值。
default
字段的默认值。这可以是值或可调用对象。如果可调用,则每次创建新对象时都会调用它。help_text
使用表单小部件显示额外的“帮助”文本。即使您的字段未在表单上使用,它也对文档很有用。
primary_key如果True
,此字段是模型的主键。
如果没有primary_key=True
为模型中的任何字段指定,Django将自动添加一个 IntegerField
来保存主键,因此primary_key=True
除非要覆盖默认的主键行为,否则不需要设置 任何字段。主键字段是只读的。如果更改现有对象上主键的值然后保存它,则将创建一个与旧对象并排的新对象。
unique
如果True
,该字段在整个表格中必须是唯一的。
自动主键字段
默认情况下,Django为每个模型提供以下字段:
id = models.AutoField(primary_key=True)
这是一个自动递增的主键。
如果您要指定自定义主键,只需primary_key=True
在其中一个字段中指定即可 。如果Django看到你明确设置Field.primary_key
,它将不会添加自动 id
列。
每个模型只需要一个字段primary_key=True
(显式声明或自动添加)。
详细字段名
除了和 之外ForeignKey
, 每个字段类型都采用可选的第一个位置参数 - 一个详细的名称。如果没有给出详细名称,Django将使用字段的属性名称自动创建它,将下划线转换为空格。ManyToManyField
OneToOneField
在此示例中,详细名称为:"person's first name"
first_name = models.CharField("person's first name", max_length=30)
在此示例中,详细名称为:"first name"
first_name = models.CharField(max_length=30)
ForeignKey
, ManyToManyField
并 OneToOneField
要求第一个参数是一个模型类,所以使用verbose_name
关键字参数:
poll = models.ForeignKey( Poll, on_delete=models.CASCADE, verbose_name="the related poll", ) sites = models.ManyToManyField(Site, verbose_name="list of sites") place = models.OneToOneField( Place, on_delete=models.CASCADE, verbose_name="related place", )
关系
显然,关系数据库的力量在于将表相互关联。Django提供了定义三种最常见数据库关系类型的方法:多对一,多对多和一对一。
要定义多对一关系,请使用django.db.models.ForeignKey
。您可以像使用任何其他Field
类型一样使用它:将其包含为模型的类属性。
ForeignKey
需要一个位置参数:与模型相关的类。
from django.db import modelsclass Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) # ...
多对多关系
要定义多对多关系,请使用 ManyToManyField
。您可以像使用任何其他Field
类型一样使用它 :将其包含为模型的类属性。
ManyToManyField
需要一个位置参数:与模型相关的类。
例如,如果a Pizza
有多个Topping
对象 - 也就是说,a Topping
可以在多个比萨饼上,每个Pizza
都有多个浇头 - 这就是你如何表示:
from django.db import modelsclass Topping(models.Model): # ... pass class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping)
建议(但不要求)a的名称 ManyToManyField
(toppings
在上面的示例中)是描述相关模型对象集的复数。
哪种型号有什么关系并不重要 ManyToManyField
,但您应该只将其放在其中一种型号中 - 而不是两种型号。
通常,ManyToManyField
实例应该放在要在表单上编辑的对象中。在上面的例子中, toppings
是Pizza
(而不是Topping
拥有pizzas
ManyToManyField
),因为考虑披萨的配料比在多个披萨上打顶更自然。在上面设置的方式,Pizza
表单将允许用户选择浇头。
多对多关系中的额外字段
当您只处理简单的多对多关系时,例如混合和匹配比萨饼和浇头,ManyToManyField
您只需要一个标准 。但是,有时您可能需要将数据与两个模型之间的关系相关联。
例如,考虑应用程序跟踪音乐家所属的音乐组的情况。一个人与他们所属的团体之间存在多对多的关系,因此您可以使用a ManyToManyField
来表示这种关系。但是,您可能希望收集的成员资格有很多详细信息,例如此人加入该组的日期。
对于这些情况,Django允许您指定将用于管理多对多关系的模型。然后,您可以在中间模型上添加额外的字段。中间模型与ManyToManyField
使用 through
参数指向将充当中介的模型相关联 。对于我们的音乐家示例,代码看起来像这样:
from django.db import modelsclass Person(models.Model): name = models.CharField(max_length=128) def __str__(self): return self.name class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') def __str__(self): return self.name class Membership(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) date_joined = models.DateField() invite_reason = models.CharField(max_length=64)
设置中间模型时,您明确指定多对多关系中涉及的模型的外键。此显式声明定义了两个模型的关联方式。
一对一的关系
要定义一对一的关系,请使用 OneToOneField
。您可以像使用任何其他Field
类型一样使用它 :将其包含为模型的类属性。
当对象以某种方式“扩展”另一个对象时,这对于对象的主键最有用。
OneToOneField
需要一个位置参数:与模型相关的类。
例如,如果您正在构建“地点”数据库,您将在数据库中构建非常标准的内容,例如地址,电话号码等。然后,如果你想在这些地方建立一个餐馆数据库,而不是重复自己并在Restaurant
模型中复制这些字段,你可以做Restaurant
一个OneToOneField
to Place
(因为一个餐馆“是一个”地方;事实上,处理这通常使用 继承,它涉及隐式的一对一关系)。
OneToOneField
字段也接受可选 parent_link
参数。
OneToOneField
用于自动成为模型主键的类。这不再是真的(尽管你可以手动传入primary_key
参数)。因此,现在可以OneToOneField
在单个模型上具有多个类型的字段 。
注意:
由于Django的查询查找语法的工作方式,字段名称不能在一行中包含多个下划线。例如:
class Example(models.Model): foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
Meta
选项
使用内部提供模型元数据,如下所示:class Meta
from django.db import modelsclass Ox(models.Model): horn_length = models.IntegerField() class Meta: ordering = ["horn_length"] verbose_name_plural = "oxen"
模型元数据是“任何不是字段的东西”,例如排序选项(ordering
),数据库表名(db_table
)或人类可读的单数和复数名称(verbose_name
和 verbose_name_plural
)。不是必要,添加到模型是完全可选的。
模型属性
objects
模型最重要的属性是 Manager
。它是为Django模型提供数据库查询操作的接口,用于 从数据库中检索实例。如果Manager
未定义自定义,则默认名称为 objects
。模型方法
在模型上定义自定义方法,以向对象添加自定义“行级”功能。虽然Manager
方法旨在执行“表格范围”的事情,但模型方法应该作用于特定的模型实例。
__str__()
Python“魔术方法”,返回任何对象的字符串表示形式。这是Python和Django在模型实例需要被强制并显示为纯字符串时将使用的内容。最值得注意的是,当您在交互式控制台或管理员中显示对象时会发生这种情况。
你总是想要定义这个方法; 默认情况下根本没有用。
get_absolute_url()
这告诉Django如何计算对象的URL。Django在其管理界面中使用它,并且只要它需要找出对象的URL。
具有唯一标识它的URL的任何对象都应定义此方法。
覆盖预定义的模型方法
还有另一组模型方法,它们封装了一些您想要自定义的数据库行为。特别是你经常想改变方式save()
和 delete()
工作方式。
您可以自由地覆盖这些方法(以及任何其他模型方法)来改变行为。
用于覆盖内置方法的经典用例是,如果您希望在保存对象时发生某些事情。例如(参见 save()
它接受的参数的文档):
from django.db import modelsclass Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def save(self, *args, **kwargs): do_something() super().save(*args, **kwargs) # Call the "real" save() method. do_something_else()
重要的是要记住调用超类方法 - 那就是业务 - 以确保对象仍然保存到数据库中。如果您忘记调用超类方法,则不会发生默认行为,也不会触及数据库。super().save(*args, **kwargs)
传递可以传递给模型方法的参数也很重要 - 这就是位的作用。Django将不时扩展内置模型方法的功能,增加新的参数。如果在方法定义中使用,则可以保证代码在添加时自动支持这些参数。*args,**kwargs
*args, **kwargs
继承模型
模型继承在Django中与普通类继承在Python中的工作方式几乎完全相同,但也仍有遵循本页开头的内容。这意味着其基类应该继承自django.db.models.Model
。
您必须做出的唯一决定是,您是希望父模型本身是模型(使用自己的数据库表),还是父母只是通过子模型可见的公共信息的持有者。
Django中有三种可能的继承方式。
- 通常,您只想使用父类来保存您不希望为每个子模型键入的信息。这个类不会被孤立使用,所以抽象基类就是你所追求的。
- 如果你是现有模型的子类(可能是完全来自另一个应用程序的东西),并希望每个模型都有自己的数据库表,那么 多表继承是最佳选择。
- 最后,如果您只想修改模型的Python级行为,而不以任何方式更改模型字段,则可以使用 代理模型。
抽象基类
当您想要将一些公共信息放入许多其他模型时,抽象基类非常有用。你写你的基类,并把abstract=True
在元 类。然后,此模型将不用于创建任何数据库表。相反,当它用作其他模型的基类时,其字段将添加到子类的字段中。
一个例子:
from django.db import modelsclass CommonInfo(models.Model): name = models.CharField(max_length=100) age = models.PositiveIntegerField() class Meta: abstract = True class Student(CommonInfo): home_group = models.CharField(max_length=5)
该Student
模型将有三个领域:name
,age
和 home_group
。该CommonInfo
模型不能用作普通的Django模型,因为它是一个抽象基类。它不生成数据库表或具有管理器,并且无法直接实例化或保存。
从抽象基类继承的字段可以使用其他字段或值覆盖,也可以使用删除None
。
对于许多用途,这种类型的模型继承将完全符合您的要求。它提供了一种在Python级别分解公共信息的方法,同时仍然只在数据库级别为每个子模型创建一个数据库表。
Meta
继承
当创建抽象基类时,Django使 您在基类中声明的任何Meta内部类可用作属性。如果子类没有声明自己的Meta 类,它将继承父类的Meta。如果孩子想要扩展父类的Meta类,它可以将其子类化。例如:
from django.db import modelsclass CommonInfo(models.Model): # ... class Meta: abstract = True ordering = ['name'] class Student(CommonInfo): # ... class Meta(CommonInfo.Meta): db_table = 'student_info'
Django确实对抽象基类的Meta类进行了一次调整:在安装Meta属性之前,它设置了abstract=False
。这意味着抽象基类的子节点本身不会自动成为抽象类。当然,您可以创建一个继承自另一个抽象基类的抽象基类。您只需要记住abstract=True
每次都明确设置。
多表继承
Django支持的第二种模型继承是当层次结构中的每个模型都是模型本身时。每个模型对应于自己的数据库表,可以单独查询和创建。继承关系引入子模型与其每个父模型之间的链接(通过自动创建OneToOneField
)。例如:
from django.db import modelsclass Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) class Restaurant(Place): serves_hot_dogs = models.BooleanField(default=False) serves_pizza = models.BooleanField(default=False)
尽管数据将驻留在不同的数据库表Place
中Restaurant
,但所有字段都将可用。所以这些都是可能的:
>>> Place.objects.filter(name="Bob's Cafe") >>> Restaurant.objects.filter(name="Bob's Cafe")
Meta
和多表继承
在多表继承情况下,子类从其父类的Meta类继承是没有意义的。所有的Meta选项都已经应用于父类,并且再次应用它们通常只会导致矛盾的行为(这与基类本身不存在的抽象基类情况形成对比)。
因此,子模型无法访问其父级的Meta类。但是,有一些有限的情况,子进程从父进程继承行为:如果子进程没有指定 ordering
属性或 get_latest_by
属性,它将从其父进程继承它们。
如果父级有一个排序而你不希望孩子有任何自然顺序,你可以明确地禁用它:
class ChildModel(ParentModel):# ... class Meta: # Remove parent's ordering effect ordering = []
继承和反向关系
因为多表继承使用隐式 OneToOneField
链接子项和父项,所以可以从父项向下移动到子项,如上例所示。但是,这会占用名称和 关系的默认related_name
值 。如果要将这些类型的关系放在父模型的子类上,则 必须 在每个此类字段上指定该属性。
class Supplier(Place):customers = models.ManyToManyField(Place)
这会导致错误,添加related_name
到该customers
字段将解决错误:models.ManyToManyField(Place,related_name='provider')
代理模型
使用多表继承时,会为模型的每个子类创建一个新的数据库表。这通常是所需的行为,因为子类需要一个位置来存储基类上不存在的任何其他数据字段。但是,有时您只想更改模型的Python行为 - 可能更改默认管理器或添加新方法。
这就是代理模型继承的用途:为原始模型创建代理。您可以创建,删除和更新代理模型的实例,并且将保存所有数据,就像使用原始(非代理)模型一样。不同之处在于您可以更改代理中的默认模型排序或默认管理器等内容,而无需更改原始内容。
代理模型声明为普通模型。你通过设置类的proxy
属性告诉Django它是一个代理模型。Meta
True
例如,假设您要向Person
模型添加方法。你可以这样做:
from django.db import modelsclass Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) class MyPerson(Person): class Meta: proxy = True def do_something(self): # ... pass
该MyPerson
班在同一个数据库表作为它的父工作 Person
类。特别是,任何新的实例Person
也可以通过MyPerson
,反之亦然:
>>> p = Person.objects.create(first_name="foobar") >>> MyPerson.objects.get(first_name="foobar")
你仍然可以使用一个代理模型来定义模型的默认排序方法你也许不会想一直对“Persion”进行排序,但是通常情况下用代理模型根据“姓氏”属性进行排序这很简单。:
class OrderedPerson(Person):class Meta: ordering = ["last_name"] proxy = True
现在,正常Person
查询将是无序的,OrderedPerson
查询将按顺序排序last_name
。
代理继承和非托管模型之间的差异¶
代理模型继承可能看起来与使用managed
模型Meta
类的属性创建非托管模型非常相似。
通过仔细设置,Meta.db_table
您可以创建一个非托管模型,该模型可以隐藏现有模型并为其添加Python方法。但是,如果您进行任何更改,则需要保持两个副本同步,这将是非常重复和脆弱的。
另一方面,代理模型的行为与它们所代表的模型完全相同。它们始终与父模型同步,因为它们直接继承其字段和管理器。
一般规则是:
- 如果要镜像现有模型或数据库表,并且不想要所有原始数据库表列,请使用
Meta.managed=False
。该选项通常用于建模不受Django控制的数据库视图和表。 - 如果您想要更改模型的仅Python行为,但保留与原始字段相同的字段,请使用
Meta.proxy=True
。这进行了设置,以便在保存数据时代理模型是原始模型的存储结构的精确副本。