Django之模型层官网翻译(五)
#模型层主要是跟数据库方面的交互,可忽略看了估计意思也不大:https://docs.djangoproject.com/en/2.0/topics/db/
一、模型
介绍模型:
模型是关于您的数据的单一,明确的信息来源。 它包含您正在存储的数据的重要字段和行为。 通常,每个模型映射到单个数据库表。
基础:
每个模型都是一个Python类,它是django.db.models.Model的子类。 模型的每个属性表示一个数据库字段。 #综上所述,Django为您提供了一个自动生成的数据库访问API; 请参阅制作查询。
快速示例:
此示例模型定义了一个Person,它具有first_name和last_name:
from django.db import modelsclass Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30)
first_name和last_name是模型的字段。 每个字段都被指定为一个类属性,并且每个属性映射到一个数据库列。上面的Person模型会像这样创建一个数据库表:
CREATE TABLE myapp_person ( "id" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL);
一些技术说明:
表myapp_person的名称自动从某些模型元数据派生,但可以覆盖。 有关更多详细信息,请参阅表名 id字段会自动添加,但是此行为可以被覆盖。 请参阅自动主键字段。 #本示例中的CREATE TABLE SQL使用PostgreSQL语法格式化,但值得注意的是,Django使用针对您的设置文件中指定的数据库后端定制的SQL。
使用models:
一旦你定义了你的模型,你需要告诉Django你要使用这些模型。 通过编辑设置文件并更改INSTALLED_APPS设置来添加包含models.py的模块的名称。例如,如果您的应用程序的模型位于模块myapp.models(由manage.py startapp脚本为应用程序创建的包结构)中,则INSTALLED_APPS应部分读取:
INSTALLED_APPS = [ #... 'myapp', #...]
当您将新应用程序添加到INSTALLED_APPS时,请确保运行manage.py migrate,可选择使用manage.py makemigrations首先进行迁移。
字段
模型中最重要的部分 - 也是模型的唯一必需部分 - 是它定义的数据库字段的列表。 字段由类属性指定。 小心不要选择与模型API冲突的字段名称,例如clean,save或delete。例:
from django.db import models class Musician(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) instrument = models.CharField(max_length=100) class Album(models.Model): artist = models.ForeignKey(Musician, on_delete=models.CASCADE) name = models.CharField(max_length=100) release_date = models.DateField() num_stars = models.IntegerField()
字段类型:
模型中的每个字段都应该是相应Field类的实例。 Django使用字段类来确定一些事情:
列类型,它告诉数据库要存储什么类型的数据(例如INTEGER,VARCHAR,TEXT)。 呈现表单字段时使用的默认HTML小部件(例如<input type =“text”>,<select>)。 Django管理员和自动生成的表单中使用的最小验证要求。
Django带有几十种内置字段类型; 您可以在模型字段参考(https://docs.djangoproject.com/en/2.0/ref/models/fields/#model-field-types)中找到完整列表。 如果Django的内置函数没有做到这一点,你可以轻松地编写自己的字段; 请参阅编写定制模型字段(https://docs.djangoproject.com/en/2.0/howto/custom-model-fields/)。
字段选项:
每个字段都有一组特定于字段的参数(记录在模型字段参考中)。 例如,CharField(及其子类)需要一个max_length参数,它指定用于存储数据的VARCHAR数据库字段的大小。还有一套适用于所有字段类型的通用参数。 全部都是可选的。 在参考文献中对它们进行了全面的介绍,但下面是对最常用的一个的简要总结:
null #如果为True,Django将在数据库中将空值存储为NULL。 默认值是False。 blank #如果为True,则允许该字段为空。 默认值是False。请注意,这与null不同。 null与纯数据库相关,而空白与验证相关。 如果一个字段的值为空,则表单验证将允许输入一个空值。 如果一个字段有空白= False,则该字段将是必需的。 choices #一个可迭代的(例如,一个列表或元组)用作该字段的选择的2元组。 如果这是给定的,默认表单小部件将是一个选择框而不是标准文本字段,并会限制选择给出的选择。 default #字段的默认值。 这可以是一个值或一个可调用的对象。 如果可调用,则每次创建新对象时都会调用它。 help_text #用窗体小部件显示额外的“帮助”文本。 即使您的字段未用于表单,对于文档也很有用。 primary_key #如果为True,则此字段是模型的主键。 unique #如果为True,则该字段在整个表格中必须是唯一的。
选择列表如下所示:
YEAR_IN_SCHOOL_CHOICES = ( ('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ('GR', 'Graduate'),)
每个元组中的第一个元素是将存储在数据库中的值。 第二个元素由字段的表单小部件显示。给定一个模型实例,可以使用get_FOO_display()方法访问具有选项的字段的显示值。 例如:
from django.db import modelsclass Person(models.Model): SHIRT_SIZES = ( ('S', 'Small'), ('M', 'Medium'), ('L', 'Large'), ) name = models.CharField(max_length=60) shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
显示内容:
>>> p = Person(name="Fred Flintstone", shirt_size="L") >>> p.save() >>> p.shirt_size 'L' >>> p.get_shirt_size_display() 'Large'
如果您没有为模型中的任何字段指定primary_key = True,那么Django会自动添加一个IntegerField来保存主键,因此您不需要在任何字段上设置primary_key = True,除非您想覆盖 默认的主键行为。 有关更多信息,请参阅自动主键字段(https://docs.djangoproject.com/en/2.0/topics/db/models/#automatic-primary-key-fields)。主键字段是只读的。 如果您更改现有对象上主键的值并保存它,则会在旧对象旁边创建一个新对象。 例如:
from django.db import models class Fruit(models.Model): name = models.CharField(max_length=100, primary_key=True)
python:
>>> fruit = Fruit.objects.create(name='Apple') >>> fruit.name = 'Pear' >>> fruit.save() >>> Fruit.objects.values_list('name', flat=True) <QuerySet ['Apple', 'Pear']>
自动主键字段:
默认情况下,Django为每个模型提供以下字段:
id = models.AutoField(primary_key=True)
这是一个自动递增的主键。
如果您想要指定自定义主键,只需在其中一个字段上指定primary_key = True。 如果Django发现你已经明确设置了Field.primary_key,它将不会添加自动ID列。
每个模型只需要一个字段就具有primary_key = True(显式声明或自动添加)。
详细字段名称:
除ForeignKey,ManyToManyField和OneToOneField外,每个字段类型都采用可选的第一个位置参数 - 一个详细名称。 如果没有给出详细名称,Django将使用该字段的属性名称自动创建它,并将下划线转换为空格。
在这个例子中,详细名称是“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", )
约定不是要大写verbose_name的第一个字母。 Django会自动把它需要的第一个字母大写。
跨文件的模型:
将模型与另一个应用程序中的模型相关联完全可以。 为此,请在定义模型的文件顶部导入相关模型。 然后,只需在需要的地方引用其他模型类。 例如:
from django.db import models from geography.models import ZipCode class Restaurant(models.Model): # ... zip_code = models.ForeignKey( ZipCode, on_delete=models.SET_NULL, blank=True, null=True, )
字段名称限制:
Django对模型字段名称只有两个限制:
字段名称不能是Python保留字,因为这会导致Python语法错误。 例如:
class Example(models.Model): pass = models.IntegerField() # 'pass' is a reserved word!
由于Django查询查找语法的工作方式,字段名称不能在一行中包含多个下划线。 例如:
class Example(models.Model): foo__bar = models.IntegerField() # 'foo__bar' has two underscores!
不过,这些限制可以解决,因为您的字段名称不一定必须与您的数据库列名称匹配。
请参阅db_column选项(https://docs.djangoproject.com/en/2.0/ref/models/fields/#django.db.models.Field.db_column)。
由于Django会在每个基础SQL查询中转义所有数据库表名和列名,因此SQL保留字(例如join,where或select)被允许作为模型字段名称。 它使用特定数据库引擎的引用语法。
自定义字段类型:
如果其中一个现有模型字段不能用于符合您的目的,或者您希望利用一些不常用的数据库列类型,则可以创建自己的字段类。 编写自定义模型字段提供了创建自己的字段的全面介绍(https://docs.djangoproject.com/en/2.0/howto/custom-model-fields/)。
关系:
显然,关系数据库的强大之处在于相互关联表。 Django提供了定义三种最常见类型的数据库关系的方法:多对一,多对多和一对一。
多对一的关系(Many-to-one relationships):
要定义多对一关系,请使用django.db.models.ForeignKey。 您可以像使用其他字段类型一样使用它:将它作为模型的类属性包含在其中。ForeignKey需要一个位置参数:与模型相关的类。
例如,如果汽车模型有制造商 - 即制造商制造多辆汽车,但每辆汽车只有一个制造商 - 则使用以下定义:
from django.db import models class Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) # ...
您还可以创建递归关系(与自身具有多对一关系的对象)以及与尚未定义的模型的关系; 详细信息请参阅型号字段参考。有人建议,但不是必需的,ForeignKey字段的名称(上例中的制造商)是小写字母的名称。 当然,无论你想要什么,你都可以打电话给该领域。 例如:
class Car(models.Model): company_that_makes_it = models.ForeignKey( Manufacturer, on_delete=models.CASCADE, ) # ...
多对多关系(Many-to-many relationships):
要定义多对多关系,请使用ManyToManyField。 您可以像使用其他字段类型一样使用它:将它作为模型的类属性包含在其中。ManyToManyField需要一个位置参数:与模型相关的类。
例如,如果比萨饼有多个Topping对象 - 也就是说,Topping可以在多个比萨饼上,每个比萨饼都有多个顶部 - 下面是您如何表示这一点:
from django.db import models class Topping(models.Model): # ... pass class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping)
和ForeignKey一样,您也可以创建递归关系(一个与自身具有多对多关系的对象)以及与尚未定义的模型的关系。有人建议,但不是必须的,ManyToManyField的名字(上面例子中的浇头)是描述相关模型对象集合的复数形式。
哪个模型具有ManyToManyField并不重要,但是您只应将其放入其中一个模型中 - 而不是两者。通常,ManyToManyField实例应该放入要在表单上编辑的对象中。 在上面的例子中,toppings是在比萨(而不是Topping有一个比萨饼ManyToManyField),因为比起在多个比萨饼上浇头,想想比萨饼更自然。 按照上面的设置,比萨饼形式可以让用户选择配料。
ManyToManyField字段还接受许多额外的参数,这些参数在模型字段参考(https://docs.djangoproject.com/en/2.0/ref/models/fields/#manytomany-arguments)中进行了解释。 这些选项有助于确定关系应该如何工作; 全部都是可选的。
多对多关系中的额外字段:
当您只处理简单的多对多关系时,比如混合和匹配比萨饼和配料,标准的ManyToManyField就是您所需要的。但是,有时您可能需要将数据与两个模型之间的关系相关联。
例如,考虑追踪音乐家所属音乐组的应用程序的情况。在一个人与他们所属的小组之间存在着多对多的关系,因此您可以使用ManyToManyField来表示这种关系。但是,关于您可能想要收集的会员资格有很多详细信息,例如加入该组的人的日期。
对于这些情况,Django允许您指定将用于管理多对多关系的模型。然后您可以在中间模型上添加额外的字段。中间模型使用through参数与ManyToManyField关联以指向将作为中介的模型。对于我们音乐家的例子,代码看起来像这样:
from django.db import models class 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)
在设置中介模型时,显式指定涉及多对多关系的模型的外键。 这个明确的声明定义了两个模型是如何相关的。中间模型有几个限制:
您的中间模型必须包含一个 - 且只有一个 - 源模型的外键(在我们的示例中,这将是Group),或者您必须显式指定Django应该用于关系的外键使用ManyToManyField.through_fields。 如果您有多个外键,并且未指定through_fields,则会引发验证错误。 类似的限制适用于目标模型的外键(在我们的示例中,这将是Person)。 对于通过中间模型与自身有多对多关系的模型,允许使用同一模型的两个外键,但它们将被视为多对多关系的两个(不同)方面。 如果有两个以上的外键,那么你还必须像上面那样指定through_fields,否则会出现验证错误。 在定义从模型到其自身的多对多关系时,使用中间模型,必须使用symmetrical = False(请参阅模型字段参考)。
现在您已经将您的ManyToManyField设置为使用您的中介模型(在这种情况下为Membership),您已准备好开始创建一些多对多关系。 您可以通过创建中间模型的实例来执行此操作:
>>> ringo = Person.objects.create(name="Ringo Starr") >>> paul = Person.objects.create(name="Paul McCartney") >>> beatles = Group.objects.create(name="The Beatles") >>> m1 = Membership(person=ringo, group=beatles, ... date_joined=date(1962, 8, 16), ... invite_reason="Needed a new drummer.") >>> m1.save() >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>]> >>> ringo.group_set.all() <QuerySet [<Group: The Beatles>]> >>> m2 = Membership.objects.create(person=paul, group=beatles, ... date_joined=date(1960, 8, 1), ... invite_reason="Wanted to form a band.") >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>
与普通的多对多字段不同,您不能使用add(),create()或set()来创建关系:
>>> # The following statements will not work >>> beatles.members.add(john) >>> beatles.members.create(name="George Harrison") >>> beatles.members.set([john, paul, ringo, george])
为什么? 您不能仅在Person和Group之间创建关系 - 您需要指定Membership模型所需关系的所有详细信息。 简单的添加,创建和赋值调用不提供指定这些额外细节的方法。 因此,它们在使用中间模型的多对多关系中被禁用。 创建这种类型的关系的唯一方法是创建中间模型的实例。
由于类似的原因,remove()方法被禁用。 例如,如果由中间模型定义的自定义直通表没有在(model1,model2)对上强制实施唯一性,则remove()调用将不会提供足够的信息来确定应删除哪个中间模型实例:
>>> Membership.objects.create(person=ringo, group=beatles, ... date_joined=date(1968, 9, 4), ... invite_reason="You've been gone for a month and we miss you.") >>> beatles.members.all() <QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]> >>> # This will not work because it cannot tell which membership to remove >>> beatles.members.remove(ringo)
但是,clear()方法可用于删除实例的所有多对多关系:
>>> # Beatles have broken up >>> beatles.members.clear() >>> # Note that this deletes the intermediate model instances >>> Membership.objects.all() <QuerySet []>
一旦通过创建中间模型的实例建立了多对多关系,就可以发出查询。 与正常的多对多关系一样,您可以使用多对多关联模型的属性进行查询:
# Find all the groups with a member whose name starts with 'Paul' >>> Group.objects.filter(members__name__startswith='Paul') <QuerySet [<Group: The Beatles>]>
在您使用中间模型时,您还可以查询其属性:
# Find all the members of the Beatles that joined after 1 Jan 1961 >>> Person.objects.filter( ... group__name='The Beatles', ... membership__date_joined__gt=date(1961,1,1)) <QuerySet [<Person: Ringo Starr]>
如果您需要访问会员资格信息,您可以直接查询会员资格模型:
>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo) >>> ringos_membership.date_joined datetime.date(1962, 8, 16) >>> ringos_membership.invite_reason 'Needed a new drummer.'
访问相同信息的另一种方法是通过查询Person对象中的多对多反向关系:
>>> ringos_membership = ringo.membership_set.get(group=beatles) >>> ringos_membership.date_joined datetime.date(1962, 8, 16) >>> ringos_membership.invite_reason 'Needed a new drummer.'
一对一关系(One-to-one relationships):
要定义一对一关系,请使用OneToOneField。 您可以像使用其他字段类型一样使用它:将它作为模型的类属性包含在其中。当对象以某种方式“扩展”另一个对象时,这对于对象的主键非常有用。
OneToOneField需要一个位置参数:与模型相关的类。例如,如果您正在构建“地点”数据库,则可以在数据库中构建诸如地址,电话号码等相当标准的内容。 然后,如果您想在餐厅的顶部建立餐厅数据库,而不是在餐厅模型中重复自己并复制这些字段,则可以让餐厅有一个OneToOneField来放置(因为餐厅“是”一个地方; 事实上,为了处理这个问题,你通常会使用继承,这涉及隐式的一对一关系)。和ForeignKey一样,可以定义递归关系,并可以引用尚未定义的模型。
OneToOneField字段也接受可选的parent_link参数。OneToOneField类用于自动成为模型的主键。 这不再是真的(虽然你可以手动传递primary_key参数)。 因此,现在可以在单个模型上具有多个OneToOneField类型的字段。
Meta选项:
通过使用内部类Meta来提供模型元数据,如下所示:
from django.db import models class Ox(models.Model): horn_length = models.IntegerField() class Meta: ordering = ["horn_length"] verbose_name_plural = "oxen"
模型元数据是“任何不是字段”,例如排序选项(排序),数据库表名(db_table)或可读的单数和复数名称(verbose_name和verbose_name_plural)。 没有必要,并且向模型中添加类Meta是完全可选的。在模型选项参考中可以找到所有可能的Meta选项的完整列表。
Model属性:
objects:
Manager最重要的一个模型属性。 它是通过它向Django模型提供数据库查询操作的接口,用于从数据库中检索实例。 如果未定义自定义管理器,则默认名称是对象。 管理者只能通过模型类访问,而不能访问模型实例。
Model方法:
在模型上定义自定义方法以将自定义的“行级”功能添加到对象中。 鉴于管理器方法旨在做“整个表”的事情,模型方法应该对特定的模型实例起作用。
这是将业务逻辑保存在一个地方的一种有价值的技术 - 模型。
例如,这个模型有几个自定义方法:
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) birth_date = models.DateField() def baby_boomer_status(self): "Returns the person's baby-boomer status." import datetime if self.birth_date < datetime.date(1945, 8, 1): return "Pre-boomer" elif self.birth_date < datetime.date(1965, 1, 1): return "Baby boomer" else: return "Post-boomer" @property def full_name(self): "Returns the person's full name." return '%s %s' % (self.first_name, self.last_name)
这个例子中的最后一个方法是属性。模型实例引用具有自动提供给每个模型的完整方法列表。 您可以覆盖其中的大部分 - 请参阅下面的覆盖预定义的模型方法 - 但有几个您几乎总是想要定义:
__str__():
一种Python“魔术方法”,它返回任何对象的字符串表示形式。 这是Python和Django在模型实例需要强制显示为纯字符串时将使用的内容。 最值得注意的是,当您在交互式控制台或管理中显示对象时会发生这种情况。
你总是想要定义这个方法; 默认不是很有帮助。
get_absolute_url():
这告诉Django如何计算一个对象的URL。 Django在其管理界面中使用它,任何时候都需要找出对象的URL。任何具有唯一标识它的URL的对象都应定义此方法。
重写预定义的模型方法:
还有另外一组模型方法可以封装一些您想要自定义的数据库行为。 特别是你经常想要改变save()和delete()的工作方式。你可以自由地重写这些方法(和任何其他模型方法)来改变行为。覆盖内置方法的经典用例是,如果您希望在保存对象时发生某些事情。 例如(请参阅save()以获取它接受的参数的文档):
from django.db import models class 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()
您也可以防止保存:
from django.db import models class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def save(self, *args, **kwargs): if self.name == "Yoko Ono's blog": return # Yoko shall never have her own blog! else: super().save(*args, **kwargs) # Call the "real" save() method.
记住调用超类方法很重要 - 那就是super()。save(* args,** kwargs)业务 - 以确保该对象仍然保存到数据库中。 如果您忘记调用超类方法,则默认行为将不会发生,并且数据库不会被触及。
传递可以传递给模型方法的参数也很重要 - 这就是* args,** kwargs位的作用。 Django将不时扩展内置模型方法的功能,增加新的参数。 如果您在方法定义中使用* args,** kwargs,则可以保证您的代码在添加时自动支持这些参数。
批量操作不会调用重写的模型方法。请注意,在使用QuerySet批量删除对象或作为级联删除的结果时,不一定会调用对象的delete()方法。 为确保自定义的删除逻辑得到执行,您可以使用pre_delete和/或post_delete信号。
不幸的是,在批量创建或更新对象时没有解决方法,因为没有调用save(),pre_save和post_save。
执行自定义SQL:
另一种常见模式是在模型方法和模块级方法中编写自定义SQL语句。 有关使用原始SQL的更多详细信息,请参阅有关使用原始SQL的文档(https://docs.djangoproject.com/en/2.0/topics/db/sql/)。
Model继承:
Django中的模型继承与Python中正常的类继承的工作方式几乎完全相同,但仍应遵循页面开头的基础知识。 这意味着基类应该继承django.db.models.Model。你必须做出的唯一决定是你是否希望父模型本身(使用它们自己的数据库表),还是父母只是通过子模型可见的公共信息的持有者。
在Django中有三种可能的继承类型。
通常,您只想使用父类来保存您不希望为每个子模型输入的信息。 这个类不会被孤立地使用,所以抽象基类就是你所追求的。 如果您正在对现有模型进行子类化(可能完全是来自另一个应用程序的某个模型),并且希望每个模型都有自己的数据库表,那么多表继承就是一种可行的方法。 最后,如果您只想修改模型的Python级行为,而不以任何方式更改模型字段,则可以使用代理模型。
抽象基类:
当您想将一些常用信息放入其他许多模型中时,抽象基类很有用。 你编写你的基类并把Meta类中的abstract=True。 这个模型不会被用来创建任何数据库表。 相反,当它被用作其他模型的基类时,它的字段将被添加到子类的那些字段中。 抽象基类中的字段与子元素中的字段名称相同(并且Django会引发异常)是错误的。
例子:
from django.db import models class 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)
学生模型将有三个字段:name, age and home_group。 CommonInfo模型不能用作普通的Django模型,因为它是一个抽象基类。 它不会生成数据库表或拥有管理器,也不能直接实例化或保存。对于很多用途,这种类型的模型继承将正是你想要的。 它提供了一种在Python级别分解常见信息的方法,但仍然只在数据库级别为每个子模型创建一个数据库表。
meta继承:
当创建一个抽象基类时,Django会将您在基类中声明的任何Meta内部类作为属性提供。 如果子类没有声明自己的Meta类,它将继承父类的元。 如果孩子想要扩展父类的Meta类,它可以继承它的子类。 例如:
from django.db import models class 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。某些属性在抽象基类的Meta类中包含是没有意义的。 例如,包含db_table将意味着所有的子类(没有指定自己的Meta的子类)将使用相同的数据库表,这几乎肯定不是您想要的。
关注related_name和related_query_name:
如果在ForeignKey或ManyToManyField上使用related_name或related_query_name,则必须始终为该字段指定唯一的反向名称和查询名称。 这通常会导致抽象基类中的问题,因为此类中的字段包含在每个子类中,每次属性(包括related_name和related_query_name)的值完全相同。
要解决此问题,当您在抽象基类(仅)中使用related_name或related_query_name时,部分值应包含'%(app_label)s' and '%(class)s'。
'%(class)s'被该字段用于的子类的底层名称替换。 '%(app_label)s'被包含在子类中的app的底层名称替换。 每个已安装的应用程序名称必须是唯一的,并且每个应用程序中的模型类名称也必须是唯一的,因此结果名称最终会有所不同。
例如,给定一个应用程序common/models.py:
from django.db import models class Base(models.Model): m2m = models.ManyToManyField( OtherModel, related_name="%(app_label)s_%(class)s_related", related_query_name="%(app_label)s_%(class)ss", ) class Meta: abstract = True class ChildA(Base): pass class ChildB(Base): pass
除了另一个应用程序rare/models.py:
from common.models import Base class ChildB(Base): pass
common.ChildA.m2m字段的反向名称将为common_childa_related,反向查询名称为common_childas。 common.ChildB.m2m字段的反向名称将为common_childb_related,反向查询名称为common_childbs。最后,rare.ChildB.m2m字段的反向名称将为rare_childb_related,反向查询名称将为rare_childbs。您如何使用'%(app_label)s' and '%(class)s'部分来构造相关名称或相关查询名称,但如果您忘记使用它,Django会在执行系统检查时引发错误(或运行迁移)。
如果您没有为抽象基类中的字段指定related_name属性,那么默认的反向名称将是子类的名称,后跟'_set',就像通常直接声明字段一样在儿童班上。例如,在上面的代码中,如果省略related_name属性,则m2m字段的反向名称将在ChildA情况下为childa_set,在ChildB字段中为childb_set。
多表继承:
Django支持的第二种模型继承是层次结构中的每个模型都是模型本身。 每个模型对应于它自己的数据库表,并且可以单独查询和创建。 继承关系引入了子模型与其父母(通过自动创建的OneToOneField)之间的链接。 例如:
from django.db import models class 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")
如果您有一个也是餐厅的地方,则可以使用模型名称的小写版本从Place对象获取到Restaurant对象:
>>> p = Place.objects.get(id=12) # If p is a Restaurant object, this will give the child class: >>> p.restaurant <Restaurant: ...>
但是,如果上例中的p不是餐厅(它已直接作为Place对象创建,或者是其他类的父级),则引用p.restaurant会引发一个Restaurant.DoesNotExist异常。Restaurant中自动创建的OneToOneField将其链接到Place,如下所示:
place_ptr = models.OneToOneField( Place, on_delete=models.CASCADE, parent_link=True, )
您可以通过在Restaurant中用parent_link=True声明您自己的OneToOneField来覆盖该字段。
元和多表继承:
在多表继承的情况下,从父类的Meta类继承子类是没有意义的。 所有的Meta选项已经被应用到父类,并且再次应用它们通常只会导致矛盾的行为(这与抽象基类情况相反,其中基类本身并不存在)。
所以一个孩子模型不能访问其父类的Meta类。 但是,有些情况下,孩子会从父级继承行为:如果孩子未指定排序属性或get_latest_by属性,则会从父级继承这些属性。
如果家长有订单,并且您不希望孩子有任何自然排序,您可以明确禁用它:
class ChildModel(ParentModel): # ... class Meta: # Remove parent's ordering effect ordering = []
继承和反向关系:
因为多表继承使用隐式OneToOneField来链接子对象和父对象,所以可以从父对象移到子对象,如上例所示。 但是,这将使用ForeignKey和ManyToManyField关系的默认related_name值的名称。 如果要将这些类型的关系放在父模型的子类上,则必须在每个此类字段上指定related_name属性。 如果你忘记了,Django会提出一个验证错误。
例如,再次使用上面的Place类,让我们用ManyToManyField创建另一个子类:
class Supplier(Place): customers = models.ManyToManyField(Place)
这会导致错误:
Reverse query name for 'Supplier.customers' clashes with reverse query name for 'Supplier.place_ptr'. HINT: Add or change a related_name argument to the definition for 'Supplier.customers' or 'Supplier.place_ptr'.
如下将customers_name添加到customers字段将解决错误:models.ManyToManyField(Place,related_name ='provider')。
指定父链接字段:
如前所述,Django会自动创建一个将您的子类连接回任何非抽象父模型的OneToOneField。 如果要控制链接回父级的属性的名称,可以创建自己的OneToOneField并设置parent_link=True,以指示您的字段是回到父级的链接。
proxy模式:
使用多表继承时,会为模型的每个子类创建一个新的数据库表。这通常是所需的行为,因为子类需要一个地方来存储基类中不存在的任何其他数据字段。但是,有时候,您只想更改模型的Python行为 - 可能是更改默认管理器,或者添加新方法。
这就是代理模型继承的用途:为原始模型创建代理。您可以创建,删除和更新代理模型的实例,并将所有数据保存为您正在使用原始(非代理)模型。不同之处在于,您可以在代理中更改默认模型顺序或默认管理器等内容,而无需更改原始内容。
代理模型被声明为正常模型。您通过将Meta类的代理属性设置为True,告诉Django它是一个代理模型。
例如,假设您想要将方法添加到Person模型。你可以这样做:
from django.db import models class 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") <MyPerson: foobar>
您还可以使用代理模型在模型上定义不同的默认排序。 您可能并不总是想要订购Person模型,但是在使用代理时按照last_name属性定期订购。 这很容易:
class OrderedPerson(Person): class Meta: ordering = ["last_name"] proxy = True
现在正常的人员查询将是无序的,OrderedPerson查询将按姓氏排序。代理模型以与常规模型相同的方式继承Meta属性。
QuerySets仍然返回被请求的模型:
每当查询Person对象时,都无法让Django返回,比如说MyPerson对象。 Person对象的查询集将返回这些类型的对象。 代理对象的重点在于,依赖原始Person的代码将使用这些代码,并且您自己的代码可以使用您包含的扩展(不管其他代码依赖于哪个代码)。 这不是一种用任何你自己创造的东西来替代人(或任何其他)模型的方法。
基类限制:
代理模型必须从一个非抽象模型类继承。 您不能从多个非抽象模型继承,因为代理模型不会在不同数据库表中的行之间提供任何连接。 代理模型可以继承任意数量的抽象模型类,只要它们不定义任何模型字段。 代理模型也可以从任何数目的共享非抽象父类的代理模型继承。
代理模型管理器:
如果您没有在代理模型中指定任何模型管理器,它会继承模型父级的管理器。 如果您在代理模型上定义管理器,它将成为默认值,但在父类上定义的任何管理器仍然可用。继续上面的示例,您可以更改在查询Person模型时使用的默认管理器,如下所示:
from django.db import models class NewManager(models.Manager): # ... pass class MyPerson(Person): objects = NewManager() class Meta: proxy = True
如果您希望将新管理器添加到代理中,而无需替换现有的缺省值,则可以使用定制管理器文档中描述的技术:创建包含新管理器的基类,并在基类之后继承该基类:
# Create an abstract class for the new manager. class ExtraManagers(models.Model): secondary = NewManager() class Meta: abstract = True class MyPerson(Person, ExtraManagers): class Meta: proxy = True
你可能不需要经常这样做,但是,当你这样做时,这是可能的。
代理继承和非托管模型之间的区别:
使用模型的Meta类中的托管属性,代理模型继承可能看起来非常类似于创建非托管模型。
通过仔细设置Meta.db_table,您可以创建一个非托管模型,它会隐藏现有模型并向其添加Python方法。 但是,如果您进行任何更改,则需要保持两个副本同步,因此这会非常重复并且很脆弱。
另一方面,代理模型的行为与其代理的模型完全相同。 由于他们直接继承其领域和经理,他们始终与父模型同步。
一般规则是:
如果您正在mirroring现有模型或数据库表并且不想要所有原始数据库表列,请使用Meta.managed=False。 该选项通常用于建模不受Django控制的数据库视图和表。 如果您想要更改模型的仅Python行为,但保留与原始字体相同的所有字段,请使用Meta.proxy=True。 这设置了一些东西,以便在保存数据时代理模型是原始模型存储结构的精确副本。
多重继承:
就像Python的子类一样,Django模型可以从多个父模型继承。请记住,正常的Python名称解析规则适用。特定名称(例如Meta)出现的第一个基类将是使用的基类;例如,这意味着如果多个父母包含一个Meta类,则只会使用第一个父类,而其他所有其他类都将被忽略。
一般来说,你不需要继承多个父母。这对于“mix-in”类来说很有用的主要用例是:为每个继承混音的类添加一个特定的额外字段或方法。尽量保持你的继承层次结构尽可能简单直接,这样你就不必费心研究出一个特定的信息来自哪里。
请注意,从具有公共ID主键字段的多个模型继承将引发错误。要正确使用多重继承,可以在基本模型中使用显式AutoField:
class Article(models.Model): article_id = models.AutoField(primary_key=True) ... class Book(models.Model): book_id = models.AutoField(primary_key=True) ... class BookReview(Book, Article): pass
或者使用共同的祖先来保存AutoField。 这需要使用从每个父模型到公共祖先的显式OneToOneField来避免由子自动生成和继承的字段之间的冲突:
class Piece(models.Model): pass class Article(Piece): article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True) ... class Book(Piece): book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True) ... class BookReview(Book, Article): pass
禁止使用字段名称“hiding”:
在普通的Python类继承中,子类可以覆盖父类的任何属性。 在Django中,这通常不允许用于模型字段。 如果非抽象模型基类有一个名为author的字段,则不能在任何继承自该基类的类中创建另一个模型字段或定义一个名为author的属性。
此限制不适用于从抽象模型继承的模型字段。 这些字段可能会被另一个字段或值覆盖,或者通过设置field_name=None来删除。
警告:
模型管理器是从抽象基类继承而来的。 覆盖由继承管理器引用的继承字段可能会导致微妙的错误。 查看自定义管理器和模型继承。
注意:
某些字段在模型上定义了额外的属性,例如 一个ForeignKey定义了一个额外的属性,其中带有_id的字段名称以及外部模型的related_name和related_query_name。这些额外的属性不能被重写,除非定义它的字段被更改或删除,以便它不再定义额外的属性。
在父模型中重写字段会导致在初始化新实例(指定在Model .__ init__中初始化哪个字段)和序列化等方面的困难。 这些是普通Python类继承不需要以相同方式处理的特性,因此Django模型继承和Python类继承之间的区别并不是任意的。
此限制仅适用于字段实例的属性。 如果你愿意,普通的Python属性可以被覆盖。 它也只适用于Python看到的属性名称:如果您手动指定数据库列名称,则可以在子表和祖先模型中出现相同的列名以实现多表继承(它们是列 在两个不同的数据库表中)。
如果您覆盖任何祖先模型中的任何模型字段,Django将引发FieldError。
在一个包中组织模型:
manage.py startapp命令创建一个包含models.py文件的应用程序结构。 如果你有很多模型,将它们组织在单独的文件中可能会有用。
为此,请创建一个模型包。 删除models.py并使用__init__.py文件和存储模型的文件创建myapp/models/目录。 您必须在__init__.py文件中导入模型。
例如,如果在models目录中有organic.py和synthetic.py:
from .organic import Person from .synthetic import Robot
明确地导入每个模型而不是使用.models import *具有不混乱名称空间,使代码更具可读性并保持代码分析工具有用的优点。
二、查询
2.1 进行查询
一旦创建了数据模型,Django就会自动为您提供一个数据库抽象API,使您可以创建,检索,更新和删除对象。 本文档介绍了如何使用此API。 有关所有各种模型查找选项的完整详细信息,请参阅数据模型参考。
在本指南(和参考文献)中,我们将参考以下模型,它们构成了一个Weblog应用程序:
from django.db import models class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def __str__(self): return self.name class Author(models.Model): name = models.CharField(max_length=200) email = models.EmailField() def __str__(self): return self.name class Entry(models.Model): blog = models.ForeignKey(Blog, on_delete=models.CASCADE) headline = models.CharField(max_length=255) body_text = models.TextField() pub_date = models.DateField() mod_date = models.DateField() authors = models.ManyToManyField(Author) n_comments = models.IntegerField() n_pingbacks = models.IntegerField() rating = models.IntegerField() def __str__(self): return self.headline
创建对象:
为了在Python对象中表示数据库表数据,Django使用直观的系统:模型类表示数据库表,该类的实例表示数据库表中的特定记录。
要创建一个对象,请使用模型类的关键字参数将其实例化,然后调用save()将其保存到数据库中。
假设模型存在mysite/blog/models.py文件中,这里是一个例子:
>>> from blog.models import Blog >>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') >>> b.save()
这在幕后执行INSERT SQL语句。 直到你显式地调用save()之前,Django才会打到数据库。save()方法没有返回值。
保存对对象的更改:
要保存对数据库中已有对象的更改,请使用save()。
给定一个已经保存到数据库的Blog实例b5,这个例子改变它的名字并更新它在数据库中的记录:
>>> b5.name = 'New name' >>> b5.save()
这在幕后执行UPDATE SQL语句。 直到你显式地调用save()之前,Django才会打到数据库。
保存ForeignKey和ManyToManyField字段:
更新ForeignKey字段的方式与保存普通字段的方式完全相同 - 只需将正确类型的对象分配给相关字段即可。 此示例更新Entry实例条目的博客属性,假设Entry和Blog的适当实例已保存到数据库(所以我们可以在下面检索它们):
>>> from blog.models import Blog, Entry >>> entry = Entry.objects.get(pk=1) >>> cheese_blog = Blog.objects.get(name="Cheddar Talk") >>> entry.blog = cheese_blog >>> entry.save()
更新ManyToManyField的工作方式有点不同 - 在字段上使用add()方法将记录添加到关系中。 本示例将Author实例joe添加到条目对象:
>>> from blog.models import Author >>> joe = Author.objects.create(name="Joe") >>> entry.authors.add(joe)
要一次将多个记录添加到ManyToManyField,请在add()的调用中包含多个参数,如下所示:
>>> john = Author.objects.create(name="John") >>> paul = Author.objects.create(name="Paul") >>> george = Author.objects.create(name="George") >>> ringo = Author.objects.create(name="Ringo") >>> entry.authors.add(john, paul, george, ringo)
如果您尝试分配或添加错误类型的对象,Django会指出。
检索对象
要从数据库中检索对象,请在您的模型类上通过管理器构建一个QuerySet。
QuerySet表示数据库中的对象集合。 它可以有零个,一个或多个过滤器。 过滤器根据给定的参数缩小查询结果的范围。 在SQL术语中,QuerySet等同于SELECT语句,过滤器是限制性子句,如WHERE或LIMIT。
您可以使用模型的管理器获取QuerySet。 每个模型至少有一个管理器,默认情况下称为对象。 通过模型类直接访问它,如下所示:
>>> Blog.objects <django.db.models.manager.Manager object at ...> >>> b = Blog(name='Foo', tagline='Bar') >>> b.objects Traceback: ... AttributeError: "Manager isn't accessible via Blog instances."
注意:管理人员只能通过模型类访问,而不能通过模型实例访问,以实现“table-level”操作和“record-level”操作之间的分离。
Manager是模型的QuerySets的主要来源。 例如,Blog.objects.all()返回包含数据库中所有Blog对象的QuerySet。
检索所有对象:
从表中检索对象的最简单方法是获取所有对象。 为此,请使用Manager上的all()方法:
>>> all_entries = Entry.objects.all()
all()方法返回数据库中所有对象的QuerySet。
使用过滤器检索特定对象:
all()返回的QuerySet描述数据库表中的所有对象。 但是,通常情况下,您只需要选择完整对象集的一部分。
要创建这样的子集,可以优化初始QuerySet,添加过滤条件。 提炼QuerySet的两种最常用的方法是:
filter(** kwargs) :返回包含匹配给定查找参数的对象的新QuerySet。
exclude(** kwargs) :返回包含与给定查找参数不匹配的对象的新QuerySet。
查找参数(上述函数定义中的** kwargs)应采用以下字段查找中描述的格式。
例如,要从2006年开始获取博客条目的QuerySet,请使用filter(),如下所示:
Entry.objects.filter(pub_date__year=2006)
使用默认管理器类,它与以下内容相同:
Entry.objects.all().filter(pub_date__year=2006)
链接过滤器:
改进QuerySet的结果本身就是一个QuerySet,因此可以将改进链接在一起。 例如:
>>> Entry.objects.filter( ... headline__startswith='What' ... ).exclude( ... pub_date__gte=datetime.date.today() ... ).filter( ... pub_date__gte=datetime.date(2005, 1, 30) ... )
这需要数据库中所有条目的初始QuerySet,添加一个过滤器,然后是一个排除,然后是另一个过滤器。 最终结果是一个QuerySet,其中包含标题以“What”开头的所有条目,这些条目在2005年1月30日和当天发布。
过滤的QuerySets是独特的:
每次您完善QuerySet时,都会获得全新的QuerySet,它绝不会绑定到以前的QuerySet。 每个优化都会创建一个独立且不同的QuerySet,可以存储,使用和重用。例:
>>> q1 = Entry.objects.filter(headline__startswith="What") >>> q2 = q1.exclude(pub_date__gte=datetime.date.today()) >>> q3 = q1.filter(pub_date__gte=datetime.date.today())
这三个QuerySet是分开的。 第一个是包含所有包含以“What”开头的标题的条目的QuerySet。 第二个是第一个的子集,另外还有一个排除pub_date是今天或将来的记录的标准。 第三个是第一个的子集,附加标准只选择pub_date是今天或将来的记录。 初始QuerySet(q1)不受refinement process的影响。
QuerySets是lazy的:
QuerySets是lazy的 - 创建QuerySet的行为不涉及任何数据库活动。 您可以整天将过滤器堆叠在一起,并且在评估QuerySet之前,Django不会真正运行查询。 看看这个例子:
>>> q = Entry.objects.filter(headline__startswith="What") >>> q = q.filter(pub_date__lte=datetime.date.today()) >>> q = q.exclude(body_text__icontains="food") >>> print(q)
虽然这看起来像三个数据库命中,但实际上它只在最后一行(print(q))处命中数据库一次。 一般来说,直到你“询问”它们之前,QuerySet的结果都不会从数据库中获取。 当你这样做时,通过访问数据库来评估QuerySet。 有关确切何时进行评估的更多详细信息,请参阅何时评估QuerySet。
用get()检索单个对象:
即使只有一个对象与查询匹配,filter()将始终为您提供一个QuerySet - 在这种情况下,它将是包含单个元素的QuerySet。
如果您知道只有一个对象与您的查询匹配,则可以在Manager上直接使用返回对象的get()方法:
>>> one_entry = Entry.objects.get(pk=1)
您可以使用get()来使用任何查询表达式,就像使用filter()一样 - 再次请参阅下面的Field lookups。
请注意,使用get()和使用filter()与[0]切片之间存在差异。 如果没有与查询匹配的结果,则get()将引发DoesNotExist异常。 此异常是正在执行查询的模型类的属性 - 因此在上面的代码中,如果没有主键为1的Entry对象,则Django将引发Entry.DoesNotExist。
同样,如果多个项目与get()查询相匹配,Django会投诉。 在这种情况下,它会引发MultipleObjectsReturned,它又是模型类本身的属性。
其他QuerySet方法:
大多数情况下,当您需要从数据库中查找对象时,您将使用all(),get(),filter()和exclude()。 但是,这远不止于此。 查看QuerySet API参考以获取所有各种QuerySet方法的完整列表(https://docs.djangoproject.com/en/2.0/ref/models/querysets/#queryset-api)。
限制查询集:
使用Python的数组切片语法的子集将QuerySet限制为一定数量的结果。 这相当于SQL的LIMIT和OFFSET子句。
例如,这将返回前5个对象(限制5):
>>> Entry.objects.all()[:5]
这将返回第六到第十个对象(OFFSET 5 LIMIT 5):
>>> Entry.objects.all()[5:10]
否定索引(即Entry.objects.all()[ - 1])不受支持。
通常,切片QuerySet会返回一个新的QuerySet - 它不评估查询。 如果使用Python切片语法的“step”参数,则是一个例外。 例如,这将实际执行查询以返回第一个10的每个第二个对象的列表:
>>> Entry.objects.all()[:10:2]
由于可能工作的模糊性质,禁止进一步过滤或对切片查询集进行排序。
要检索单个对象而不是列表(例如,SELECT foo FROM bar LIMIT 1),请使用简单索引而不是片段。 例如,在按照标题按字母顺序排序后,这会返回数据库中的第一个Entry:
>>> Entry.objects.order_by('headline')[0]
这大致相当于:
>>> Entry.objects.order_by('headline')[0:1].get()
但是,请注意,如果没有对象与给定条件匹配,则第一个将引发IndexError,而第二个将引发DoesNotExist。 有关更多详细信息,请参阅get()。
字段查找:
字段查找是您如何指定SQL WHERE子句的肉。 它们被指定为QuerySet方法filter(),exclude()和get()的关键字参数。
基本查找关键字参数采用表格field__lookuptype = value。 (这是一个双下划线)。 例如:
>>> Entry.objects.filter(pub_date__lte='2006-01-01')
将(大致)转换为以下SQL:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
在查找中指定的字段必须是模型字段的名称。 虽然有一个例外,但在ForeignKey的情况下,您可以指定带_id后缀的字段名称。 在这种情况下,value参数应该包含外部模型主键的原始值。 例如:
>>> Entry.objects.filter(blog_id=4)
如果传递无效的关键字参数,查找函数将引发TypeError。
数据库API支持大约二十几种查找类型; 可以在字段查找参考中找到完整的参考。 为了让您了解可用的功能,以下是您可能会用到的一些更常见的查找:
exact:
一个“确切”的匹配。 例如:
>>> Entry.objects.get(headline__exact="Cat bites dog")
将根据这些行生成SQL:
SELECT ... WHERE headline = 'Cat bites dog';
如果您没有提供查找类型 - 也就是说,如果您的关键字参数不包含双下划线 - 则查找类型被假定为确切的。例如,以下两条语句是等价的:
>>> Blog.objects.get(id__exact=14) # Explicit form >>> Blog.objects.get(id=14) # __exact is implied
iexact:
不区分大小写的匹配。 所以,查询:
>>> Blog.objects.get(name__iexact="beatles blog")
会匹配一个名为"Beatles Blog", "beatles blog", or even "BeAtlES blOG".
contains:
区分大小写的容器测试。 例如:
Entry.objects.get(headline__contains='Lennon')
粗略地转换为这个SQL:
SELECT ... WHERE headline LIKE '%Lennon%';
请注意,这将符合标题 'Today Lennon honored'但不是'today lennon honored'.还有一个不区分大小写的版本,icontains。
startswith, endswith:
分别以搜索开始 - 结束 - 开始。 还有一些不区分大小写的版本,称为istartswith和iendswith。可以在字段查找参考中找到完整的参考(https://docs.djangoproject.com/en/2.0/ref/models/querysets/#field-lookups)。
跨越关系的查找:
Django提供了一种强大而直观的方式来在查找中“追踪”关系,在幕后自动为您处理SQL JOIN。 要跨越关系,只需使用模型中相关字段的字段名称(用双下划线分隔),直到您到达所需的字段。
本示例使用名为“Beatles Blog”的博客检索所有Entry对象:
>>> Entry.objects.filter(blog__name='Beatles Blog')
这种跨越可以尽可能深。它也可以倒退。 要引用“反向”关系,只需使用模型的小写名称即可。此示例检索所有至少包含一个其标题包含“Lennon”的条目的Blog对象:
>>> Blog.objects.filter(entry__headline__contains='Lennon')
如果您跨多个关系进行筛选,并且其中一个中间模型没有满足筛选条件的值,那么Django会将它视为有空(所有值均为NULL),但在此处为有效对象。 所有这一切意味着不会出现任何错误。 例如,在这个过滤器中:
Blog.objects.filter(entry__authors__name='Lennon')
(如果存在相关的Author模型),如果没有作者与某个条目相关联,那么它将被视为没有附加名称,而不是因为缺少作者而引发错误。 通常这正是你想要发生的事情。 唯一可能引起混淆的情况是如果你使用isnull。 从而:
Blog.objects.filter(entry__authors__name__isnull=True)
将返回作author上具有空name的博客对象以及条目上具有空作者的博客对象。 如果你不想要后面那些对象,你可以这样写:
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)
跨越多值关系:
当您根据ManyToManyField或反向ForeignKey过滤对象时,您可能会感兴趣的是两种不同类型的过滤器。考虑Blog / Entry关系(Blog to Entry是一对多关系)。我们可能有兴趣找到具有“列侬”标题并于2008年出版的条目的博客。或者我们可能希望查找标题为“Lennon”的博客以及条目于2008年发布。由于有多个条目与单个Blog相关联,所以这些查询都是可能的,并且在某些情况下是有意义的。
ManyToManyField出现相同类型的情况。例如,如果一个Entry有一个名为Tags的ManyToManyField,我们可能希望查找与名为“音乐”和“乐队”的标签相关的条目,或者我们可能需要一个包含名称为“音乐”的标签并且状态为“上市”。
为了处理这两种情况,Django拥有一致的处理filter()调用的方法。同时应用单个filter()调用中的所有内容以筛选出满足所有这些要求的项目。连续的filter()调用进一步限制了对象集合,但对于多值关系,它们适用于链接到主模型的任何对象,而不一定是由较早的filter()调用选择的那些对象。
这听起来有点令人困惑,所以希望有一个例子可以澄清。要选择包含标题为“Lennon”并且在2008年发布的条目(满足这两个条件的同一条目)的所有博客,我们会写:
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
要选择标题中包含“Lennon”条目的所有博客以及2008年发布的条目,我们将编写:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
假设只有一个博客同时包含来自2008年的“Lennon”和条目的条目,但2008年没有包含“Lennon”的条目。 第一个查询不会返回任何博客,但第二个查询将返回该博客。
在第二个示例中,第一个过滤器将查询集限制为链接到标题中带有“Lennon”条目的所有博客。 第二个过滤器将这组博客进一步限制为那些也链接到2008年发布的条目的博客。第二个过滤器选择的条目可能与第一个过滤器中的条目相同或不同。 我们使用每个过滤器语句过滤Blog项目,而不是Entry项目。
过滤器可以引用模型上的字段:
在迄今为止给出的例子中,我们构建了一个过滤器,它将模型字段的值与常量进行比较。 但是如果您想要将模型字段的值与同一模型中的另一个字段进行比较呢?
Django提供了F表达式来允许这样的比较。 F()的实例充当对查询中模型字段的引用。 然后可以在查询过滤器中使用这些引用来比较同一模型实例上两个不同字段的值。
例如,要查找比pingbacks有更多注释的所有博客条目的列表,我们构造一个F()对象来引用pingback计数,并在查询中使用该F()对象:
>>> from django.db.models import F >>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django支持对F()对象使用常量和其他F()对象的加法,减法,乘法,除法,模和幂运算。 要查找所有比pingbacks多两倍的评论的博客条目,我们修改查询:
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
要查找条目评级小于pingback计数和评论计数总和的所有条目,我们将发出查询:
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
您还可以使用双下划线表示法来跨越F()对象中的关系。 具有双下划线的F()对象将引入访问相关对象所需的任何连接。 例如,要检索作者姓名与博客名称相同的所有条目,我们可以发出查询:
>>> Entry.objects.filter(authors__name=F('blog__name'))
对于日期和日期/时间字段,可以添加或减去timedelta对象。 以下内容将返回发布后超过3天修改的所有条目:
>>> from datetime import timedelta >>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
F()对象支持.bitand(),.bitor(),.bitrightshift()和.bitleftshift()的按位运算。 例如:
>>> F('somefield').bitand(16)
pk查找快捷方式:
为了方便起见,Django提供了一个pk查找快捷键,代表“主键”。在示例Blog模型中,主键是id字段,所以这三个语句是等同的:
>>> Blog.objects.get(id__exact=14) # Explicit form >>> Blog.objects.get(id=14) # __exact is implied >>> Blog.objects.get(pk=14) # pk implies id__exact
pk的使用不限于__exact查询 - 任何查询术语都可以与pk结合,以对模型的主键执行查询:
# Get blogs entries with id 1, 4 and 7 >>> Blog.objects.filter(pk__in=[1,4,7]) # Get all blog entries with id > 14 >>> Blog.objects.filter(pk__gt=14)
pk查找也可以在连接中使用。 例如,这三个陈述是等同的:
>>> Entry.objects.filter(blog__id__exact=3) # Explicit form >>> Entry.objects.filter(blog__id=3) # __exact is implied >>> Entry.objects.filter(blog__pk=3) # __pk implies __id__exact
在LIKE语句中转义百分号和下划线:
等同于LIKE SQL语句(iexact,contains,icontains,startswith,istartswith,endswith和iendswith)的字段查找将自动转义LIKE语句中使用的两个特殊字符 - 百分号和下划线。 (在LIKE语句中,百分号表示多字符通配符,下划线表示单字符通配符。)
这意味着事情应该直观地工作,所以抽象不会泄漏。 例如,要检索包含百分号的所有条目,只需使用百分号作为其他任何字符:
>>> Entry.objects.filter(headline__contains='%')
Django负责为你引用; 生成的SQL将如下所示:
SELECT ... WHERE headline LIKE '%\%%';
下划线也是如此。 透明地处理您的百分比符号和下划线。
缓存和查询集:
每个QuerySet都包含一个缓存以最大限度地减少数据库访 了解它的工作原理将允许您编写最高效的代码。
在新创建的QuerySet中,缓存为空。 第一次对QuerySet进行评估 - 并因此发生数据库查询时 - Django将查询结果保存在QuerySet的缓存中,并返回已明确请求的结果(例如,如果QuerySet正在迭代,则返回下一个元素)。 随后对QuerySet的评估重用了缓存的结果。
记住这个缓存行为,因为如果你没有正确使用你的QuerySets,它可能会咬你。 例如,以下内容将创建两个QuerySets,对它们进行评估并丢弃它们:
>>> print([e.headline for e in Entry.objects.all()]) >>> print([e.pub_date for e in Entry.objects.all()])
这意味着相同的数据库查询将执行两次,有效地加倍你的数据库负载。 另外,这两个列表可能不包含相同的数据库记录,因为在两个请求之间可能已经添加或删除了一个Entry。
为了避免这个问题,只需保存QuerySet并重用它:
>>> queryset = Entry.objects.all() >>> print([p.headline for p in queryset]) # Evaluate the query set. >>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
当QuerySets没有被缓存时:
查询集并不总是缓存它们的结果。 在仅评估部分查询集时,会检查缓存,但如果未填充,则后续查询返回的项不会被缓存。 具体而言,这意味着使用数组切片或索引限制查询集不会填充缓存。
例如,重复获取queryset对象中的某个索引将每次查询数据库:
>>> queryset = Entry.objects.all() >>> print(queryset[5]) # Queries the database >>> print(queryset[5]) # Queries the database again
但是,如果整个查询集已经被评估,缓存将被检查:
>>> queryset = Entry.objects.all() >>> [entry for entry in queryset] # Queries the database >>> print(queryset[5]) # Uses cache >>> print(queryset[5]) # Uses cache
以下是将导致整个查询集被评估并因此填充缓存的其他操作的一些示例:
>>> [entry for entry in queryset] >>> bool(queryset) >>> entry in queryset >>> list(queryset)
使用Q对象进行复杂查找:
关键字参数查询 - 在filter()等中 - 是“AND”编辑在一起的。 如果您需要执行更复杂的查询(例如,使用OR语句的查询),则可以使用Q对象。
Q对象(django.db.models.Q)是一个用于封装关键字参数集合的对象。 这些关键字参数在上面的“字段查找”中指定。
例如,这个Q对象封装了一个LIKE查询:
from django.db.models import Q Q(question__startswith='What')
Q对象可以使用&和|进行组合运营商。 当一个操作符用于两个Q对象时,它会生成一个新的Q对象。
例如,这个语句产生一个Q对象,它表示两个“question__starts with”查询的“或”:
Q(question__startswith='Who') | Q(question__startswith='What')
这相当于以下SQL WHERE子句:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
通过将Q对象与&和|相结合,您可以编写任意复杂的语句 运算符并使用括号分组。 此外,可以使用〜运算符来否定Q对象,从而允许将普通查询和否定(NOT)查询组合在一起的组合查找:
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
每个使用关键字参数(例如filter(),exclude(),get())的查找函数也可以传递一个或多个Q对象作为位置(未命名)参数。 如果您为查找函数提供多个Q对象参数,则参数将一起“AND”。 例如:
Poll.objects.get( Q(question__startswith='Who'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) )
...粗略地翻译成SQL:
SELECT * from polls WHERE question LIKE 'Who%' AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
查找函数可以混合使用Q对象和关键字参数。 提供给查找函数的所有参数(不管它们是关键字参数还是Q对象)都是“与”一起编辑的。 但是,如果提供了Q对象,则必须先于任何关键字参数的定义。 例如:
Poll.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), question__startswith='Who', )
...将是一个有效的查询,相当于前面的示例; 但:
# INVALID QUERY Poll.objects.get( question__startswith='Who', Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) )
...无效。
比较对象:
要比较两个模型实例,只需使用标准的Python比较运算符double等号:==。 在幕后,比较了两个模型的主要关键值。
使用上面的Entry例子,以下两个语句是等价的:
>>> some_entry == other_entry >>> some_entry.id == other_entry.id
如果模型的主键不被称为id,则没有问题。 不管它叫什么,比较总是使用主键。 例如,如果模型的主键字段被称为名称,则这两个语句是等同的:
>>> some_obj == other_obj >>> some_obj.name == other_obj.name
删除对象:
方便的删除方法被命名为delete()。 此方法立即删除对象并返回删除的对象数量和每个对象类型具有删除次数的字典。 例:
>>> e.delete() (1, {'weblog.Entry': 1})
您也可以批量删除对象。 每个QuerySet都有一个delete()方法,该方法删除该QuerySet的所有成员。例如,这会删除pub_date年份为2005年的所有Entry对象:
>>> Entry.objects.filter(pub_date__year=2005).delete() (5, {'webapp.Entry': 5})
请记住,只要有可能,这将纯粹在SQL中执行,因此单个对象实例的delete()方法在进程中不一定会被调用。 如果你在模型类上提供了一个自定义的delete()方法,并且希望确保它被调用,那么你需要“手动”删除该模型的实例(例如,通过迭代QuerySet并调用delete() 每个对象单独)而不是使用QuerySet的批量delete()方法。
当Django删除一个对象时,默认情况下它会模拟SQL约束ON DELETE CASCADE的行为 - 换句话说,任何有外键指向要删除的对象的对象都将被删除。 例如:
b = Blog.objects.get(pk=1) # This will delete the Blog and all of its Entry objects. b.delete()
此级联行为可通过ForeignKey的on_delete参数进行自定义。
请注意,delete()是唯一不在管理器本身公开的QuerySet方法。 这是一种安全机制,可防止您意外地请求Entry.objects.delete()并删除所有条目。 如果您确实要删除所有对象,则必须明确请求一个完整的查询集:
Entry.objects.all().delete()
复制模型实例:
虽然没有用于复制模型实例的内置方法,但可以轻松创建复制了所有字段值的新实例。 在最简单的情况下,你可以将pk设置为None。 使用我们的博客示例:
blog = Blog(name='My blog', tagline='Blogging is easy') blog.save() # blog.pk == 1 blog.pk = None blog.save() # blog.pk == 2
如果你使用继承,事情变得更加复杂。 考虑一下博客的一个子类:
class ThemeBlog(Blog): theme = models.CharField(max_length=200) django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python') django_blog.save() # django_blog.pk == 3
由于继承是如何工作的,你必须将pk和id都设置为None:
django_blog.pk = None django_blog.id = None django_blog.save() # django_blog.pk == 4
此过程不会复制不属于模型数据库表的关系。 例如,Entry有一个ManyToManyField来 Author。 复制一个条目后,您必须设置新条目的多对多关系:
entry = Entry.objects.all()[0] # some previous entry old_authors = entry.authors.all() entry.pk = None entry.save() entry.authors.set(old_authors)
对于OneToOneField,您必须复制相关对象并将其分配给新对象的字段,以避免违反一对一唯一约束。 例如,假设条目已经重复如上:
detail = EntryDetail.objects.all()[0] detail.pk = None detail.entry = entry detail.save()
一次更新多个对象:
有时,您想要为QuerySet中的所有对象设置一个字段为特定的值。 你可以用update()方法做到这一点。 例如:
# Update all the headlines with pub_date in 2007. Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
您只能使用此方法设置非关系字段和ForeignKey字段。 要更新非关系字段,请将新值作为常量提供。 要更新ForeignKey字段,请将新值设置为要指向的新模型实例。 例如:
>>> b = Blog.objects.get(pk=1) # Change every Entry so that it belongs to this Blog. >>> Entry.objects.all().update(blog=b)
update()方法立即应用并返回查询匹配的行数(如果某些行已具有新值,则该行数可能不等于更新的行数)。 正在更新的QuerySet的唯一限制是它只能访问一个数据库表:模型的主表。 您可以根据相关字段进行过滤,但只能更新模型主表中的列。 例:
>>> b = Blog.objects.get(pk=1) # Update all the headlines belonging to this Blog. >>> Entry.objects.select_related().filter(blog=b).update(headline='Everything is the same')
请注意update()方法直接转换为SQL语句。 这是直接更新的批量操作。 它不会在模型上运行任何save()方法,或者发出pre_save或post_save信号(这是调用save()的结果),也可以是auto_now字段选项。 如果要将每个项目保存在QuerySet中并确保在每个实例上调用save()方法,则不需要任何特殊函数来处理该项目。 只需循环它们并调用save():
for item in my_queryset: item.save()
调用更新也可以使用F表达式根据模型中另一个字段的值更新一个字段。 这对于根据计数器的当前值递增计数器特别有用。 例如,要增加博客中每个条目的pingback计数:
>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
但是,与过滤器和排除子句中的F()对象不同,当在更新中使用F()对象时,不能引入连接 - 只能引用正在更新的模型的本地字段。 如果您尝试使用F()对象引入联接,则会引发FieldError:
# This will raise a FieldError >>> Entry.objects.update(headline=F('blog__name'))
相关对象:
当您在模型中定义关系(即ForeignKey,OneToOneField或ManyToManyField)时,该模型的实例将具有访问相关对象的方便的API。
例如,使用本页顶部的模型,Entry对象e可以通过访问blog属性来获取其关联的Blog对象:e.blog。
(在幕后,这个功能是由Python描述符实现的,这对你并不重要,但我们在这里指出它的好奇。)
Django还为关系的“其他”一方创建API访问器 - 从相关模型到定义关系的模型的链接。 例如,Blog对象b可以通过entry_set属性访问所有相关Entry对象的列表:b.entry_set.all()。
本节中的所有示例均使用本页顶部定义的示例Blog,Author和Entry模型。
一对多的关系:
Forward:
如果模型具有ForeignKey,则该模型的实例将通过模型的简单属性访问相关(外部)对象。例如:
>>> e = Entry.objects.get(id=2) >>> e.blog # Returns the related Blog object.
您可以通过外键属性获取和设置。 如您所料,在调用save()之前,对外键的更改不会保存到数据库。 例:
>>> e = Entry.objects.get(id=2) >>> e.blog = some_blog >>> e.save()
如果ForeignKey字段具有空值=真集(即,它允许NULL值),则可以分配无以移除关系。 例:
>>> e = Entry.objects.get(id=2) >>> e.blog = None >>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
第一次访问相关对象时,缓存对一对多关系的前向访问。 随后访问同一对象实例上的外键将被缓存。 例:
>>> e = Entry.objects.get(id=2) >>> print(e.blog) # Hits the database to retrieve the associated Blog. >>> print(e.blog) # Doesn't hit the database; uses cached version.
请注意,select_related()QuerySet方法提前预先填充所有一对多关系的缓存。 例:
>>> e = Entry.objects.select_related().get(id=2) >>> print(e.blog) # Doesn't hit the database; uses cached version. >>> print(e.blog) # Doesn't hit the database; uses cached version.
关系“backward”:
如果模型具有ForeignKey,则外键模型的实例将有权访问管理器,该管理器返回第一个模型的所有实例。 默认情况下,此管理器名为FOO_set,其中FOO是源型号名称,小写。 该管理器返回QuerySet,可以按照上面的“检索对象”一节中所述进行过滤和操作。例:
>>> b = Blog.objects.get(id=1) >>> b.entry_set.all() # Returns all Entry objects related to Blog. # b.entry_set is a Manager that returns QuerySets. >>> b.entry_set.filter(headline__contains='Lennon') >>> b.entry_set.count()
您可以通过在ForeignKey定义中设置related_name参数来覆盖FOO_set名称。 例如,如果Entry模型被更改为blog = ForeignKey(Blog,on_delete = models.CASCADE,related_name ='entries'),上面的示例代码将如下所示:
>>> b = Blog.objects.get(id=1) >>> b.entries.all() # Returns all Entry objects related to Blog. # b.entries is a Manager that returns QuerySets. >>> b.entries.filter(headline__contains='Lennon') >>> b.entries.count()
使用自定义的反向管理器:
默认情况下,用于反向关系的RelatedManager是该模型的默认管理器的子类。 如果您想为给定的查询指定不同的管理器,可以使用以下语法:
from django.db import models class Entry(models.Model): #... objects = models.Manager() # Default Manager entries = EntryManager() # Custom Manager b = Blog.objects.get(id=1) b.entry_set(manager='entries').all()
如果EntryManager在其get_queryset()方法中执行了默认过滤,则该过滤将应用于all()调用。当然,指定一个自定义反向管理器也可以让你调用它的自定义方法:
b.entry_set(manager='entries').is_published()
处理相关对象的其他方法:
除了上面“检索对象”中定义的QuerySet方法之外,ForeignKey Manager还具有用于处理相关对象集的附加方法。 每一个的概要如下,完整的细节可以在相关的对象参考中找到。
add(obj1, obj2, ...) #将指定的模型对象添加到相关的对象集。
create(**kwargs) #创建一个新对象,将其保存并放入相关对象集中。 返回新创建的对象。
remove(obj1, obj2, ...) #从相关对象集中删除指定的模型对象。
clear() #从相关对象集中删除所有对象。
set(objs) #替换一组相关的对象。
要分配相关集的成员,请将set()方法与可迭代的对象实例或主键值列表一起使用。 例如:
b = Blog.objects.get(id=1) b.entry_set.set([e1, e2])
在本例中,e1和e2可以是完整的条目实例或整数主键值。
如果clear()方法可用,则在iterable(本例中为列表)中的所有对象都添加到set之前,将从entry_set中删除任何预先存在的对象。 如果clear()方法不可用,则会添加迭代中的所有对象而不删除任何现有元素。
本节中描述的每个“reverse”操作对数据库都有直接影响。 每一次添加,创建和删除都会立即自动保存到数据库中。
多对多的关系:
多对多关系的两端都可以自动访问另一端的API。 API的作用正如上面的“落后”一对多关系。
唯一的区别在于属性命名:定义ManyToManyField的模型使用该字段本身的属性名称,而“反向”模型使用原始模型的小写模型名称和“_set”(就像反向one- 多对多关系)。
一个例子使得这更容易理解:
e = Entry.objects.get(id=3) e.authors.all() # Returns all Author objects for this Entry. e.authors.count() e.authors.filter(name__contains='John') a = Author.objects.get(id=5) a.entry_set.all() # Returns all Entry objects for this Author.
像ForeignKey一样,ManyToManyField可以指定related_name。 在上面的例子中,如果Entry中的ManyToManyField指定了related_name ='entries',那么每个Author实例将具有entries属性而不是entry_set。
一对一关系:
一对一关系与多对一关系非常相似。 如果您在模型上定义了OneToOneField,则该模型的实例将通过模型的简单属性访问相关对象。例如:
class EntryDetail(models.Model): entry = models.OneToOneField(Entry, on_delete=models.CASCADE) details = models.TextField() ed = EntryDetail.objects.get(id=2) ed.entry # Returns the related Entry object.
差异出现在“反向”查询中。 一对一关系中的相关模型也可以访问Manager对象,但该Manager表示一个对象,而不是一组对象:
e = Entry.objects.get(id=2) e.entrydetail # returns the related EntryDetail object
如果没有对象被分配给这个关系,Django将引发一个DoesNotExist异常。
实例可以按照与分配转发关系相同的方式分配给反向关系:
e.entrydetail = ed