Django 4.0.dev 文档
一、模型
模型是关于您的数据的唯一、确定的信息源。它包含要存储的数据的基本字段和行为。通常,每个模型映射到一个数据库表。
基础知识:
-
每个模型都是一个python类,子类
django.db.models.Model
. -
模型的每个属性表示一个数据库字段。
-
通过所有这些,Django为您提供了一个自动生成的数据库访问API;请参见 进行查询 .
进行查询
一旦你创建了你的 data models ,django自动为您提供一个数据库抽象API,允许您创建、检索、更新和删除对象。本文档解释如何使用此API。参考 data model reference 有关所有各种模型查找选项的完整详细信息。
在本指南的整个过程中(以及在参考文献中),我们将参考以下模型,这些模型包含一个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)
number_of_comments = models.IntegerField()
number_of_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语句。在您显式调用之前,Django不会命中数据库 save()
.
这个 save()
方法没有返回值。
参见
save()
采用了许多这里没有描述的高级选项。参见文档 save()
有关完整的详细信息。
要在单个步骤中创建和保存对象,请使用 create()
方法。
保存对对象的更改
要保存对数据库中已有对象的更改,请使用 save()
.
给出了一个 Blog
实例 b5
已经保存到数据库中的,此示例更改其名称并更新数据库中的记录:
>>> b5.name = 'New name'
>>> b5.save()
这将执行 UPDATE
后台的SQL语句。在您显式调用之前,Django不会命中数据库 save()
.
储蓄 ForeignKey
和 ManyToManyField
领域
更新一个 ForeignKey
field的工作方式与保存普通字段的方式完全相同——为有问题的字段分配一个正确类型的对象。此示例更新 blog
AN属性 Entry
实例 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
到 entry
对象:
>>> 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
通过A Manager
在你的模型课上。
A QuerySet
表示数据库中的对象集合。它可以有零个,一个或多个 过滤器 . 过滤器根据给定的参数缩小查询结果的范围。在SQL术语中,A QuerySet
等同于 SELECT
语句,过滤器是限制子句,例如 WHERE
或 LIMIT
.
你得到一个 QuerySet
使用你的模型 Manager
. 每个模型至少有一个 Manager
它被称为 objects
默认情况下。通过模型类直接访问它,如:
>>> 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."
备注
Managers
只能通过模型类而不是模型实例来访问,以在“表级”操作和“记录级”操作之间实现分离。
这个 Manager
是 QuerySets
对于一个模型。例如, Blog.objects.all()
返回A QuerySet
包含所有 Blog
数据库中的对象。
正在检索所有对象
从表中检索对象的最简单方法是获取所有对象。为此,请使用 all()
A方法 Manager
::
>>> all_entries = Entry.objects.all()
这个 all()
方法返回 QuerySet
数据库中的所有对象。
使用筛选器检索特定对象
这个 QuerySet
返回的 all()
描述数据库表中的所有对象。不过,通常只需要选择完整对象集的一个子集。
要创建这样一个子集,需要细化初始值 QuerySet
,添加筛选条件。两种最常用的方法来改进 QuerySet
是:
-
filter(**kwargs)
返回新的
QuerySet
包含与给定查找参数匹配的对象。 -
exclude(**kwargs)
返回新的
QuerySet
包含执行此操作的对象 not 匹配给定的查找参数。
查找参数 (**kwargs
在上述函数定义中)应采用 Field lookups 下面。
例如,要获得 QuerySet
在2006年的博客条目中,使用 filter()
像这样::
Entry.objects.filter(pub_date__year=2006)
对于默认的管理器类,它与以下内容相同:
Entry.objects.all().filter(pub_date__year=2006)
链接过滤器
精炼A的结果 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
包含2005年1月30日至当日发布的标题以“what”开头的所有条目。
过滤的 QuerySet
是独一无二的
每次你精炼 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())
这三 QuerySets
是分开的。第一个是基地 QuerySet
包含包含以“what”开头的标题的所有条目。第二个是第一个的子集,带有一个附加的条件,该条件排除了 pub_date
是现在还是将来。第三个是第一个的子集,附加的条件只选择 pub_date
是现在还是将来。最初的 QuerySet
(q1
)不受精化过程的影响。
QuerySet
是懒惰的
QuerySets
懒惰——创造 QuerySet
不涉及任何数据库活动。您可以整天将过滤器堆叠在一起,Django将不会实际运行查询,直到 QuerySet
是 评价的 . 看看这个例子:
>>> 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 s被评估 .
检索单个对象 get()
filter()
总会给你一个 QuerySet
,即使只有一个对象与查询匹配-在本例中,它将是 QuerySet
包含单个元素。
如果知道只有一个对象与查询匹配,则可以使用 get()
A方法 Manager
直接返回对象:
>>> one_entry = Entry.objects.get(pk=1)
可以将任何查询表达式用于 get()
就像和 filter()
再次看到 Field lookups 下面。
注意,使用 get()
并使用 filter()
用一片 [0]
. 如果没有与查询匹配的结果, get()
将提高 DoesNotExist
例外。这个异常是正在执行查询的模型类的一个属性,所以在上面的代码中,如果没有 Entry
主键为1的对象,Django将提升 Entry.DoesNotExist
.
同样,如果多个项目与 get()
查询。在这种情况下,它会上升 MultipleObjectsReturned
这也是模型类本身的一个属性。
其他 QuerySet
方法
大部分时间你会用到 all()
, get()
, filter()
和 exclude()
当需要从数据库中查找对象时。然而,这还远远不够;看 QuerySet API Reference 要获得所有 QuerySet
方法。
限制 QuerySet
的S
使用python数组切片语法的子集来限制 QuerySet
一定数量的结果。这相当于SQL的 LIMIT
和 OFFSET
条款。
例如,这将返回前5个对象 (LIMIT 5
):
>>> Entry.objects.all()[:5]
这将返回第六个到第十个对象 (OFFSET 5 LIMIT 5
):
>>> Entry.objects.all()[5:10]
负索引(即 Entry.objects.all()[-1]
)不支持。
一般来说,切片 QuerySet
返回新的 QuerySet
--它不计算查询。一个例外是如果使用python slice语法的“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';
这是怎么可能的
python能够定义接受名称和值在运行时计算的任意名称值参数的函数。有关详细信息,请参阅 Keyword Arguments 在官方的python教程中。
查找中指定的字段必须是模型字段的名称。但是有一个例外,在 ForeignKey
可以指定后缀为 _id
. 在这种情况下,value参数应该包含外部模型主键的原始值。例如:
>>> Entry.objects.filter(blog_id=4)
如果传递的关键字参数无效,则查找函数将引发 TypeError
.
数据库API支持大约24种查找类型;可以在 field lookup reference . 为了让您了解可用的功能,下面是一些您可能会使用的常见查找:
-
“精确”匹配。例如::
>>> Entry.objects.get(headline__exact="Cat bites dog")
将沿着这些行生成SQL:SELECT ... WHERE headline = 'Cat bites dog';
如果不提供查找类型——也就是说,如果关键字参数不包含双下划线——则假定查找类型为exact
.例如,以下两个语句是等效的:>>> Blog.objects.get(id__exact=14) # Explicit form >>> Blog.objects.get(id=14) # __exact is implied
这是为了方便,因为exact
查找是常见的情况。 -
不区分大小写的匹配。因此,查询:
>>> Blog.objects.get(name__iexact="beatles blog")
将匹配Blog
有头衔的"Beatles Blog"
,"beatles blog"
,甚至"BeAtlES blOG"
. -
区分大小写的安全壳测试。例如::
Entry.objects.get(headline__contains='Lennon')
大致翻译成这个SQL:SELECT ... WHERE headline LIKE '%Lennon%';
注意这将与标题匹配'Today Lennon honored'
但不是'today lennon honored'
.还有一个不区分大小写的版本,icontains
. -
分别从搜索开始和结束。还有一些不区分大小写的版本称为
istartswith
和iendswith
.
同样,这只会划伤表面。完整的参考资料可在 field lookup reference .
跨越关系的查找
Django提供了一种强大而直观的方式来“跟踪”查找中的关系,处理SQL JOIN
在幕后自动给你。若要跨越关系,请跨模型使用相关字段的字段名,用双下划线分隔,直到到达所需的字段为止。
此示例检索所有 Entry
对象与A Blog
谁的 name
是 'Beatles Blog'
::
>>> Entry.objects.filter(blog__name='Beatles Blog')
这个跨度可以像你想的那样深。
它也是逆向的。当它 can be customized
,默认情况下,使用模型的小写名称在查找中引用“反向”关系。
此示例检索所有 Blog
至少有一个的对象 Entry
谁的 headline
包含 'Lennon'
::
>>> Blog.objects.filter(entry__headline__contains='Lennon')
如果您正在跨多个关系进行筛选,并且其中一个中间模型没有满足筛选条件的值,Django会将其视为存在空值(所有值都是 NULL
,但有效,对象在那里。这意味着不会出现错误。例如,在此筛选器中:
Blog.objects.filter(entry__authors__name='Lennon')
(如果有相关的 Author
模型),如果没有 author
与一个条目关联,它将被视为 name
附加,而不是由于缺少 author
. 通常这正是你想要发生的事情。唯一可能令人困惑的情况是,如果您使用 isnull
. 因此:
Blog.objects.filter(entry__authors__name__isnull=True)
将返回 Blog
具有空的对象 name
上 author
还有那些空的 author
上 entry
. 如果不需要后一个对象,可以编写:
Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)
跨越多值关系
当您基于 ManyToManyField
或者相反 ForeignKey
,您可能感兴趣的过滤器有两种。考虑一下 Blog
/Entry
关系 (Blog
到 Entry
是一对多关系)。我们可能会有兴趣找到一个有两个入口的博客 “列侬” 并于2008年出版。或者我们可能希望找到包含 “列侬” 以及2008年出版的一篇文章。因为有多个条目与一个 Blog
,这两个查询都是可能的,在某些情况下是有意义的。
同样的情况发生在 ManyToManyField
. 例如,如果 Entry
有一个 ManyToManyField
调用 tags
,我们可能希望找到链接到名为 “音乐” 和 “乐队” 或者我们可能需要一个包含一个名为 “音乐” 和一个状态 “公共” .
为了处理这两种情况,Django有一种一致的处理方法 filter()
调用。单间房里的一切 filter()
调用同时应用于筛选符合所有这些要求的项。连续的 filter()
调用进一步限制了对象集,但对于多值关系,它们适用于链接到主模型的任何对象,不一定是由早期 filter()
调用。
这听起来可能有点令人困惑,所以希望有一个例子可以澄清。选择包含两个项目的所有日志 “列侬” 在2008年出版的标题中(同样的条目满足这两个条件),我们会写:
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)
选择包含带有 “列侬” 在标题中 以及 我们将在2008年发表一篇文章:
Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)
假设只有一个博客同时包含 “列侬” 和2008年的条目,但2008年的条目中没有包含 “列侬” . 第一个查询不会返回任何博客,但第二个查询将返回该博客。
在第二个示例中,第一个过滤器将查询集限制为所有链接到带有 “列侬” 在标题中。第二个过滤器限制博客集 进一步的 与那些与2008年发表的文章相关联的文章。第二个筛选器选择的条目可能与第一个筛选器中的条目相同,也可能不同。我们正在过滤 Blog
包含每个筛选语句的项,而不是 Entry
项目。
备注
行为 filter()
对于跨越多值关系的查询,如上所述,其实现方式与 exclude()
. 相反,条件是 exclude()
调用不一定引用同一项。
例如,以下查询将排除包含 both 条目与 “列侬” 在标题中 and 2008年出版的作品:
Blog.objects.exclude(
entry__headline__contains='Lennon',
entry__pub_date__year=2008,
)
但是,与使用时的行为不同 filter()
,这不会基于满足这两个条件的条目限制博客。为了做到这一点,即选择所有不包含与 “列侬” 这是2008年发布的,您需要进行两个查询:
Blog.objects.exclude(
entry__in=Entry.objects.filter(
headline__contains='Lennon',
pub_date__year=2008,
),
)
过滤器可以引用模型上的字段
在目前给出的示例中,我们构建了过滤器,将模型字段的值与常量进行比较。但是,如果您想将一个模型字段的值与同一个模型上的另一个字段进行比较呢?
Django提供 F expressions
允许这样的比较。实例 F()
作为对查询中模型字段的引用。然后,可以在查询筛选器中使用这些引用来比较同一模型实例上两个不同字段的值。
例如,为了找到所有评论数超过pingbacks的博客条目的列表,我们构建了一个 F()
对象引用pingback计数,并使用 F()
查询中的对象::
>>> from django.db.models import F
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks'))
Django支持使用加法、减法、乘法、除法、模和幂运算 F()
对象,包括常量和其他 F()
对象。查找所有超过 两次 与pingbacks一样多的注释,我们修改查询::
>>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2)
若要查找条目的分级小于pingback计数和注释计数之和的所有条目,我们将发出查询::
>>> Entry.objects.filter(rating__lt=F('number_of_comments') + F('number_of_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()
, .bitxor()
, .bitrightshift()
和 .bitleftshift()
. 例如::
>>> F('somefield').bitand(16)
甲骨文公司
Oracle不支持按位异或操作。
表达式可以引用转换
New in Django 3.2.
Django支持在表达式中使用转换。
例如,要查找所有 Entry
与上次修改同年发布的对象::
>>> Entry.objects.filter(pub_date__year=F('mod_date__year'))
要查找条目发布的最早年份,我们可以发出查询::
>>> Entry.objects.aggregate(first_published_year=Min('pub_date__year'))
此示例查找评分最高的条目的值以及每年所有条目上的评论总数:
>>> Entry.objects.values('pub_date__year').annotate(
... top_rating=Subquery(
... Entry.objects.filter(
... pub_date__year=OuterRef('pub_date__year'),
... ).order_by('-rating').values('rating')[:1]
... ),
... total_comments=Sum('number_of_comments'),
... )
这个 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
语句——百分号和下划线。(在A中) LIKE
语句中,百分号表示多字符通配符,下划线表示单字符通配符。)
这意味着事情应该直观地工作,这样抽象就不会泄漏。例如,要检索包含百分号的所有条目,请将百分号用作任何其他字符:
>>> Entry.objects.filter(headline__contains='%')
Django负责为您报价;得到的SQL如下所示:
SELECT ... WHERE headline LIKE '%\%%';
下划线也是如此。百分比符号和下划线都是透明地为您处理的。
缓存和 QuerySet
的S
各 QuerySet
包含用于最小化数据库访问的缓存。了解它的工作方式将使您能够编写最有效的代码。
在新创建的 QuerySet
,缓存为空。第一次A QuerySet
被计算——因此,会发生数据库查询——django将查询结果保存在 QuerySet
的缓存并返回已显式请求的结果(例如,下一个元素,如果 QuerySet
正在迭代)。后续评估 QuerySet
重用缓存的结果。
记住这种缓存行为,因为如果不使用 QuerySet
正确。例如,下面将创建两个 QuerySet
S,评估它们,然后扔掉它们:
>>> 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.
什么时候? QuerySet
S未缓存
查询集并不总是缓存它们的结果。仅在评估时 part 对于查询集,将检查缓存,但如果未填充缓存,则不会缓存后续查询返回的项。具体来说,这意味着 limiting the queryset 使用数组切片或索引不会填充缓存。
例如,重复获取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)
备注
只打印查询集不会填充缓存。这是因为 __repr__()
只返回整个查询集的一个切片。
查询 JSONField
查找实现与 JSONField
,主要是由于密钥变换的存在。为了演示,我们将使用以下示例模型:
from django.db import models
class Dog(models.Model):
name = models.CharField(max_length=200)
data = models.JSONField(null=True)
def __str__(self):
return self.name
存储和查询 None
与其他领域一样,存储 None
因为字段的值将存储为SQL NULL
. 虽然不推荐,但可以存储JSON标量 null
而不是SQL NULL
通过使用 Value('null')
.
从数据库中检索时,无论存储哪个值,JSON标量的Python表示 null
与SQL相同 NULL
,即 None
. 因此,很难区分它们。
这只适用于 None
作为字段的顶级值。如果 None
在一个 list
或 dict
,它将始终被解释为JSON null
.
查询时, None
值将始终被解释为JSON null
. 查询SQL NULL
使用 isnull
::
>>> Dog.objects.create(name='Max', data=None) # SQL NULL.
<Dog: Max>
>>> Dog.objects.create(name='Archie', data=Value('null')) # JSON null.
<Dog: Archie>
>>> Dog.objects.filter(data=None)
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data=Value('null'))
<QuerySet [<Dog: Archie>]>
>>> Dog.objects.filter(data__isnull=True)
<QuerySet [<Dog: Max>]>
>>> Dog.objects.filter(data__isnull=False)
<QuerySet [<Dog: Archie>]>
除非您确定希望使用SQL NULL
值,考虑设置 null=False
并为空值提供适当的默认值,例如 default=dict
.
备注
存储JSON标量 null
不违反 null=False
.
键、索引和路径转换
若要基于给定字典键进行查询,请将该键用作查找名称:
>>> Dog.objects.create(name='Rufus', data={
... 'breed': 'labrador',
... 'owner': {
... 'name': 'Bob',
... 'other_pets': [{
... 'name': 'Fishy',
... }],
... },
... })
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': None})
<Dog: Meg>
>>> Dog.objects.filter(data__breed='collie')
<QuerySet [<Dog: Meg>]>
可以将多个键链接在一起以形成路径查找:
>>> Dog.objects.filter(data__owner__name='Bob')
<QuerySet [<Dog: Rufus>]>
如果键是整数,它将被解释为数组中的索引转换:
>>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy')
<QuerySet [<Dog: Rufus>]>
如果要查询的键与另一个查找的名称冲突,请使用 contains
查找相反。
要查询缺少的键,请使用 isnull
查找:
>>> Dog.objects.create(name='Shep', data={'breed': 'collie'})
<Dog: Shep>
>>> Dog.objects.filter(data__owner__isnull=True)
<QuerySet [<Dog: Shep>]>
备注
上面给出的查找示例隐式使用 exact
查找。键、索引和路径变换也可以链接到: icontains
, endswith
, iendswith
, iexact
, regex
, iregex
, startswith
, istartswith
, lt
, lte
, gt
和 gte
,以及 控制和关键查找 .
备注
由于密钥路径查询的工作方式, exclude()
和 filter()
不能保证生产出详尽的设备。如果要包括没有路径的对象,请添加 isnull
查找。
警告
由于任何字符串都可以是JSON对象中的键,因此除下面列出的字符串以外的任何查找都将被解释为键查找。未引发任何错误。要格外小心打字错误,并且总是检查你的查询是否按你的意愿工作。
MariaDB和Oracle用户
使用 order_by()
on key、index或path转换将使用值的字符串表示对对象进行排序。这是因为MariaDB和Oracle数据库没有提供将JSON值转换为等效SQL值的函数。
Oracle用户
在Oracle数据库上,使用 None
作为中的查找值 exclude()
查询将返回没有 null
作为给定路径的值,包括不具有该路径的对象。在其他数据库后端,查询将返回具有路径而值不是的对象 null
.
PostgreSQL用户
在PostgreSQL上,如果只使用一个键或索引,则SQL运算符 ->
使用。如果使用多个运算符,则 #>
使用运算符。
控制和关键查找
contains
这个 contains
查找在上被重写 JSONField
. 返回的对象是给定 dict
的键值对都包含在字段的顶层。例如::
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.create(name='Fred', data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contains={'owner': 'Bob'})
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
>>> Dog.objects.filter(data__contains={'breed': 'collie'})
<QuerySet [<Dog: Meg>]>
Oracle和SQLite
contains
Oracle和SQLite不支持。
contained_by
这与 contains
查找-返回的对象将是那些对象上的键值对是传递的值中的键值对的子集。例如::
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador', 'owner': 'Bob'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.create(name='Fred', data={})
<Dog: Fred>
>>> Dog.objects.filter(data__contained_by={'breed': 'collie', 'owner': 'Bob'})
<QuerySet [<Dog: Meg>, <Dog: Fred>]>
>>> Dog.objects.filter(data__contained_by={'breed': 'collie'})
<QuerySet [<Dog: Fred>]>
Oracle和SQLite
contained_by
Oracle和SQLite不支持。
has_key
返回给定键位于数据顶层的对象。例如::
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.filter(data__has_key='owner')
<QuerySet [<Dog: Meg>]>
has_keys
返回所有给定键都位于数据顶层的对象。例如::
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'breed': 'collie', 'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.filter(data__has_keys=['breed', 'owner'])
<QuerySet [<Dog: Meg>]>
has_any_keys
返回任何给定键位于数据顶层的对象。例如::
>>> Dog.objects.create(name='Rufus', data={'breed': 'labrador'})
<Dog: Rufus>
>>> Dog.objects.create(name='Meg', data={'owner': 'Bob'})
<Dog: Meg>
>>> Dog.objects.filter(data__has_any_keys=['owner', 'breed'])
<QuerySet [<Dog: Rufus>, <Dog: Meg>]>
复杂查找 Q
对象
关键字参数查询--在 filter()
等等——是“和”合在一起的。如果需要执行更复杂的查询(例如, OR
语句),您可以使用 Q objects
.
A Q object
(django.db.models.Q
)是用于封装关键字参数集合的对象。这些关键字参数在上面的“字段查找”中指定。
例如,这个 Q
对象封装单个 LIKE
查询:
from django.db.models import Q
Q(question__startswith='What')
Q
对象可以使用 &
和 |
运算符。当一个运算符用于2时 Q
对象,它生成一个新的 Q
对象。
例如,此语句生成一个 Q
表示两个对象的“或”的对象 "question__startswith"
查询:
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
对象参数到查找函数,参数将“和”一起使用。例如::
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))
)
…无效。
参见
这个 OR lookups examples 在Django的单元测试中显示了 Q
.
比较对象
要比较两个模型实例,请使用标准的Python比较运算符双等号: ==
. 在幕后,比较两个模型的主键值。
使用 Entry
上面的示例,以下两个语句是等效的:
>>> some_entry == other_entry
>>> some_entry.id == other_entry.id
如果没有调用模型的主键 id
没问题。比较总是使用主键,不管它叫什么。例如,如果模型的主键字段被调用 name
,这两个语句等价:
>>> some_obj == other_obj
>>> some_obj.name == other_obj.name
删除对象
删除方法,方便地命名为 delete()
. 此方法立即删除对象,并返回已删除的对象数和一个字典,其中包含每个对象类型的删除数。例子::
>>> e.delete()
(1, {'weblog.Entry': 1})
还可以批量删除对象。每个 QuerySet
有一个 delete()
方法,删除该方法的所有成员 QuerySet
.
例如,这将删除所有 Entry
对象与A pub_date
2005岁:
>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})
请记住,只要可能,这将完全在SQL中执行,因此 delete()
在此过程中,不必调用单个对象实例的方法。如果您提供了自定义 delete()
方法,如果要确保调用该方法,则需要“手动”删除该模型的实例(例如,通过迭代 QuerySet
呼唤 delete()
在每个对象上)而不是使用批量 delete()
A方法 QuerySet
.
当Django删除对象时,默认情况下它模拟SQL约束的行为 ON DELETE CASCADE
--换句话说,任何具有指向要删除的对象的外键的对象都将与它一起删除。例如::
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
这个级联行为可以通过 on_delete
论据 ForeignKey
.
注意 delete()
是唯一的 QuerySet
未在上公开的方法 Manager
本身。这是一个安全机制,防止您意外请求 Entry.objects.delete()
删除 all 参赛作品。如果你 do 要删除所有对象,则必须显式请求完整的查询集::
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
如果使用继承,事情会变得更复杂。考虑的子类 Blog
::
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
,必须复制相关对象并将其分配给新对象的字段,以避免违反一对一的唯一约束。例如,假设 entry
已如上所述复制::
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.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 expressions
根据模型中另一个字段的值更新一个字段。这对于根据计数器的当前值递增计数器特别有用。例如,要增加日志中每个条目的pingback计数:
>>> Entry.objects.all().update(number_of_pingbacks=F('number_of_pingbacks') + 1)
然而,不像 F()
在筛选和排除子句中的对象,使用时不能引入联接 F()
更新中的对象——您只能引用被更新模型的本地字段。如果您尝试使用 F()
对象,A FieldError
将被提升:
# This will raise a FieldError
>>> Entry.objects.update(headline=F('blog__name'))
相关对象
在模型中定义关系时(即 ForeignKey
, OneToOneField
或 ManyToManyField
,该模型的实例将有一个方便的API来访问相关对象。
例如,使用本页顶部的模型 Entry
对象 e
可以将其关联 Blog
通过访问 blog
属性: e.blog
.
(在幕后,此功能由python实现 descriptors . 这对你来说并不重要,但我们在这里为好奇者指出这一点。)
Django还为关系的“另一方”创建API访问器——从相关模型到定义关系的模型的链接。例如,A Blog
对象 b
可以访问所有相关的列表 Entry
对象通过 entry_set
属性: b.entry_set.all()
.
本节中的所有示例都使用示例 Blog
, Author
和 Entry
此页顶部定义的模型。
一对多关系
福沃德
如果模型有 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()
如果A ForeignKey
字段已 null=True
设置(即,它允许 NULL
值),可以指定 None
移除关系。例子::
>>> 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.
跟随关系“后退”
如果模型有 ForeignKey
,外键模型的实例将可以访问 Manager
返回第一个模型的所有实例。默认情况下, Manager
被命名 FOO_set
在哪里 FOO
是源模型名称,低成本。这个 Manager
收益率 QuerySets
,可以按照上面“检索对象”部分中的描述进行过滤和操作。
例子::
>>> 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()
您可以覆盖 FOO_set
通过设置 related_name
中的参数 ForeignKey
定义。例如,如果 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
用于反向关系的是 default manager 对于那个型号。如果要为给定查询指定其他管理器,可以使用以下语法:
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
具有用于处理相关对象集的其他方法。下面是每一个的概要,完整的细节可以在 related objects reference .
-
add(obj1, obj2, ...)
将指定的模型对象添加到相关对象集。
-
create(**kwargs)
创建一个新对象,保存它并将其放入相关的对象集中。返回新创建的对象。
-
remove(obj1, obj2, ...)
从相关对象集中删除指定的模型对象。
-
clear()
从相关对象集中删除所有对象。
-
set(objs)
替换相关对象集。
要分配相关集合的成员,请使用 set()
方法的对象实例不能为。例如,如果 e1
和 e2
是 Entry
实例::
b = Blog.objects.get(id=1)
b.entry_set.set([e1, e2])
如果 clear()
方法可用,将从 entry_set
在ITerable中的所有对象(在本例中是一个列表)添加到集合之前。如果 clear()
方法是 not 可用,将添加iterable中的所有对象,而不删除任何现有元素。
本节中描述的每个“反向”操作都会立即对数据库产生影响。每次添加、创建和删除都会立即自动保存到数据库中。
多对多关系
多对多关系的两端都可以自动访问另一端的API。API的工作原理类似于上面的“向后”一对多关系。
一个区别在于属性命名:定义 ManyToManyField
使用该字段本身的属性名,而“反向”模型使用原始模型的低基模型名,加上 '_set'
(就像颠倒一对多的关系一样)。
一个例子使这更容易理解:
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
. 在上面的示例中,如果 ManyToManyField
在里面 Entry
指定的 related_name='entries'
,然后每一个 Author
实例将具有 entries
属性而不是 entry_set
.
一对多关系的另一个区别是,除了模型实例外, add()
, set()
和 remove()
多对多关系上的方法接受主键值。例如,如果 e1
和 e2
是 Entry
实例,然后这些 set()
调用工作相同:
a = Author.objects.get(id=5)
a.entry_set.set([e1, e2])
a.entry_set.set([e1.pk, e2.pk])
一对一关系
一对一关系与多对一关系非常相似。如果定义了 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
反向关系如何可能?
其他对象关系映射器要求您在两侧定义关系。Django开发人员认为这违反了Dry(不要重复自己)原则,因此Django只要求您在一端定义关系。
但是,如果一个模型类在加载其他模型类之前不知道与它相关的其他模型类,这怎么可能呢?
答案在于 app registry
. 当Django启动时,它将导入中列出的每个应用程序 INSTALLED_APPS
然后 models
每个应用程序中的模块。无论何时创建新的模型类,Django都会向任何相关模型添加向后关系。如果相关模型尚未导入,Django会跟踪这些关系,并在相关模型最终导入时添加它们。
因此,在中列出的应用程序中定义正在使用的所有模型尤为重要。 INSTALLED_APPS
. 否则,反向关系可能无法正常工作。
对相关对象的查询
涉及相关对象的查询遵循与涉及普通值字段的查询相同的规则。指定要匹配的查询值时,可以使用对象实例本身,也可以使用对象的主键值。
例如,如果您有一个博客对象 b
具有 id=5
,以下三个查询将相同:
Entry.objects.filter(blog=b) # Query using object instance
Entry.objects.filter(blog=b.id) # Query using id from instance
Entry.objects.filter(blog=5) # Query using id directly
返回原始SQL
如果您发现自己需要编写一个对Django的数据库映射器来说太复杂的SQL查询,那么您可以手工编写SQL。Django有几个用于编写原始SQL查询的选项;请参见 执行原始SQL查询 .
最后,需要注意的是,django数据库层只是到数据库的接口。您可以通过其他工具、编程语言或数据库框架访问数据库;您的数据库没有任何Django特定的内容。
二、快速实例
此示例模型定义了 Person
,它有一个 first_name
和 last_name
::
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
first_name
和 last_name
是 fields 模型。每个字段都被指定为类属性,并且每个属性映射到数据库列。
以上 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是根据您的 settings file .
使用模型
一旦你定义了你的模型,你需要告诉 Django 你将要 use 那些模型。通过编辑设置文件并更改 INSTALLED_APPS
用于添加包含您的 models.py
.
例如,如果应用程序的模型位于模块中 myapp.models
(由 manage.py startapp
脚本) INSTALLED_APPS
应阅读,部分内容:
INSTALLED_APPS = [
#...
'myapp',
#...
]
当您将新应用程序添加到 INSTALLED_APPS
,一定要运行 manage.py migrate
,可以选择先使用 manage.py makemigrations
.
字段
模型最重要的部分——也是模型唯一需要的部分——是它定义的数据库字段列表。字段由类属性指定。注意不要选择与 models 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 widget 在呈现表单字段时使用(例如
<input type="text">
,<select>
) - 在Django的管理员和自动生成的表单中使用的最小验证要求。
Django提供了几十种内置字段类型;您可以在 model field reference . 如果Django的内置字段不起作用,您可以轻松地编写自己的字段;请参见 正在写入自定义模型字段 .
字段选项
每个字段采用一组特定于字段的参数(记录在 model field reference )例如, CharField
(及其子类)要求 max_length
参数,指定 VARCHAR
用于存储数据的数据库字段。
还有一组公共参数可用于所有字段类型。所有都是可选的。它们在 reference ,但以下是最常用的摘要:
-
如果
True
,django将空值存储为NULL
在数据库中。默认是False
. -
如果
True
,该字段允许为空。默认是False
.注意这与null
.null
纯粹与数据库相关,而blank
是否与验证相关。如果字段有blank=True
,表单验证将允许输入空值。如果字段有blank=False
,字段将是必需的。 -
A sequence 两个元组中的一个用作此字段的选项。如果指定了此选项,则默认表单小部件将是一个选择框,而不是标准文本字段,并将选项限制为给定的选项。选项列表如下所示:
YEAR_IN_SCHOOL_CHOICES = [ ('FR', 'Freshman'), ('SO', 'Sophomore'), ('JR', 'Junior'), ('SR', 'Senior'), ('GR', 'Graduate'), ]
备注每次按choices
变化。每个元组中的第一个元素是将存储在数据库中的值。第二个元素由字段的表单小部件显示。给定模型实例,字段的显示值choices
可以使用get_FOO_display()
方法。例如::from django.db import models class 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'
也可以使用枚举类来定义choices
简而言之:from django.db import models class Runner(models.Model): MedalType = models.TextChoices('MedalType', 'GOLD SILVER BRONZE') name = models.CharField(max_length=60) medal = models.CharField(blank=True, choices=MedalType.choices, max_length=10)
更多示例可在 model field reference . -
字段的默认值。这可以是一个值或可调用对象。如果可调用,则每次创建新对象时都会调用它。
-
要与表单小部件一起显示的额外“帮助”文本。它对文档很有用,即使表单上没有使用您的字段。
-
如果
True
,此字段是模型的主键。如果您不指定primary_key=True
对于模型中的任何字段,Django将自动添加IntegerField
保留主键,这样就不需要设置primary_key=True
在任何字段上,除非要重写默认的主键行为。更多,请参见 自动主键字段 .主键字段是只读的。如果更改现有对象的主键值,然后保存它,则将在旧对象旁边创建一个新对象。例如::from django.db import models class Fruit(models.Model): name = models.CharField(max_length=100, primary_key=True) ``>>> fruit = Fruit.objects.create(name='Apple') >>> fruit.name = 'Pear' >>> fruit.save() >>> Fruit.objects.values_list('name', flat=True) <QuerySet ['Apple', 'Pear']>
-
如果
True
,此字段在整个表中必须是唯一的。
同样,这些只是最常见字段选项的简短描述。详细信息可在 common model field option reference .
自动主键字段
默认情况下,Django为每个模型提供一个自动递增的主键,其类型为中每个应用程序指定的类型 AppConfig.default_auto_field
或全局地在 DEFAULT_AUTO_FIELD
设置。例如::
id = models.BigAutoField(primary_key=True)
如果要指定自定义主键,请指定 primary_key=True
在你的某个领域。如果Django看到你已经明确设置了 Field.primary_key
,它不会添加自动 id
列。
每个模型只需要一个字段 primary_key=True
(显式声明或自动添加)。
Changed in Django 3.2:
在旧版本中,自动创建的主键字段始终 AutoField
s.
详细字段名
每个字段类型,除了 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将在需要的地方自动大写第一个字母。
关系
显然,关系数据库的功能在于将表相互关联。Django提供了定义三种最常见的数据库关系类型的方法:多对一、多对多和一对一。
多对一关系
要定义多对一关系,请使用 django.db.models.ForeignKey
. 你像其他人一样使用它 Field
类型:通过将其作为模型的类属性包含进来。
ForeignKey
需要位置参数:与模型相关的类。
例如,如果 Car
模型有 Manufacturer
也就是说,A Manufacturer
制造多辆车,但每辆 Car
只有一个 Manufacturer
--使用以下定义:
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...
您还可以创建 recursive relationships (与自身有多对一关系的对象)和 relationships to models not yet defined 见 the model field reference 有关详细信息。
建议,但不要求 ForeignKey
领域 (manufacturer
在上面的例子中)是模型的名称,小写。你想叫什么都行。例如::
class Car(models.Model):
company_that_makes_it = models.ForeignKey(
Manufacturer,
on_delete=models.CASCADE,
)
# ...
参见
ForeignKey
字段接受多个额外参数,这些参数在 the model field reference . 这些选项有助于定义关系的工作方式;所有选项都是可选的。
有关访问向后相关对象的详细信息,请参见 Following relationships backward example .
有关示例代码,请参见 Many-to-one relationship model example .
多对多关系
要定义多对多关系,请使用 ManyToManyField
. 你像其他人一样使用它 Field
类型:通过将其作为模型的类属性包含进来。
ManyToManyField
需要位置参数:与模型相关的类。
例如,如果 Pizza
有多重 Topping
对象——也就是说, Topping
可以放在多个比萨饼上 Pizza
有多个浇头——下面是您如何表示的:
from django.db import models
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
和一样 ForeignKey
,也可以创建 recursive relationships (与自身有多对多关系的对象)和 relationships to models not yet defined .
建议,但不要求 ManyToManyField
(toppings
在上面的示例中)是描述相关模型对象集的复数。
哪种型号的 ManyToManyField
但是您应该只将它放在其中一个模型中——而不是同时放在这两个模型中。
一般来说, ManyToManyField
实例应该放在要在窗体上编辑的对象中。在上面的例子中, toppings
是在 Pizza
(而不是 Topping
有一个 pizzas
ManyToManyField
)因为想一个比萨有配料比多个比萨有配料更自然。上面的设置方式, Pizza
表单允许用户选择浇头。
参见
见 Many-to-many relationship model example 举一个完整的例子。
ManyToManyField
字段还接受一些额外的参数,这些参数在 the model field reference . 这些选项有助于定义关系的工作方式;所有选项都是可选的。
多对多关系上的额外字段
当你只处理多对多的关系时,比如混合搭配比萨饼和配料,这是一个标准 ManyToManyField
是你所需要的。但是,有时您可能需要将数据与两个模型之间的关系相关联。
例如,考虑应用程序跟踪音乐家所属的音乐组的情况。一个人和他们所属的组之间存在多对多关系,因此您可以使用 ManyToManyField
代表这种关系。但是,关于您可能想要收集的成员身份有很多详细信息,例如此人加入该组的日期。
对于这些情况,Django允许您指定用于管理多对多关系的模型。然后可以在中间模型上放置额外的字段。中间模型与 ManyToManyField
使用 through
指向将充当中介的模型的参数。对于我们的音乐家示例,代码看起来像这样:
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)
在设置中介模型时,可以显式地指定涉及多对多关系的模型的外键。此显式声明定义了两个模型的关联方式。
中间模型有一些限制:
- 中间模型必须包含一个-和 only 源模型的一个外键(这将是
Group
在我们的示例中),或者必须显式指定关系应该使用的外键djangoManyToManyField.through_fields
. 如果您有多个外键并且through_fields
未指定,将引发验证错误。类似的限制适用于目标模型的外键(这将是Person
在我们的例子中)。 - 对于通过中介模型与自身具有多对多关系的模型,允许同一模型的两个外键,但它们将被视为多对多关系的两个(不同)面。如果有 more 但是,必须指定两个以上的外键
through_fields
如上所述,否则将引发验证错误。
既然你已经设置了 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()
要创建关系,只要指定 through_defaults
对于任何必填字段:
>>> beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.create(name="George Harrison", through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})
您可能更喜欢直接创建中间模型的实例。
如果中间模型定义的自定义通过表没有在 (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 deletes both of the intermediate model instances for Ringo Starr
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>
这个 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]>
如果您需要访问成员的信息,可以通过直接查询 Membership
型号:
>>> 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.'
访问相同信息的另一种方法是查询 many-to-many reverse relationship 从A 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.'
一对一关系
要定义一对一关系,请使用 OneToOneField
. 你像其他人一样使用它 Field
类型:通过将其作为模型的类属性包含进来。
当对象以某种方式“扩展”另一个对象时,对于该对象的主键来说,这是最有用的。
OneToOneField
需要位置参数:与模型相关的类。
例如,如果您正在构建一个“地点”数据库,那么您将在数据库中构建相当标准的内容,例如地址、电话号码等。然后,如果你想在Restaurant的顶部建立一个数据库,而不是重复你自己并在 Restaurant
模型,你可以做 Restaurant
有一个 OneToOneField
到 Place
(因为Restaurant“是”的地方;事实上,要处理这个问题,你通常会使用 inheritance 其中包含一对一的隐式关系)。
和一样 ForeignKey
,A recursive relationship 可以定义和 references to as-yet undefined models 可以制作。
参见
见 One-to-one relationship model example 举一个完整的例子。
OneToOneField
字段还接受可选 parent_link
参数。
OneToOneField
用于自动成为模型上的主键的类。这不再是正确的(尽管您可以手动传递 primary_key
如果你愿意的话。因此,现在可以有多个类型的字段 OneToOneField
在一个模型上。
跨文件模型
将一个模型与另一个应用程序中的模型关联起来是完全可以的。为此,请在定义模型的文件顶部导入相关模型。然后,在需要的地方参考其他模型类。例如::
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
选择权。
SQL保留字,例如 join
, where
或 select
, are 允许作为模型字段名,因为django会在每个基础SQL查询中转义所有数据库表名和列名。它使用特定数据库引擎的引用语法。
自定义字段类型
如果一个现有的模型字段不能用于您的目的,或者您希望利用一些不太常见的数据库列类型,则可以创建自己的字段类。在中提供了创建自己字段的全部覆盖范围。 正在写入自定义模型字段 .
Meta
选项
通过使用内部 class Meta
,像这样::
from django.db import models
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
模型元数据是“任何非字段的内容”,例如排序选项 (ordering
)数据库表名 (db_table
)或人类可读的单数和复数名称 (verbose_name
和 verbose_name_plural
)不需要,并添加 class Meta
对于模型是完全可选的。
所有可能的完整清单 Meta
选项可以在 model option reference .
模型属性
-
objects
模型最重要的属性是
Manager
. 它是向django模型提供数据库查询操作的接口,用于 retrieve the instances 从数据库中。如果没有习惯Manager
已定义,默认名称为objects
. 管理器只能通过模型类访问,而不能通过模型实例访问。
模型方法
在模型上定义自定义方法,以便向对象添加自定义的“行级别”功能。反之 Manager
方法是用来做“表范围”的事情,模型方法应该作用于特定的模型实例。
这是一种将业务逻辑保存在一个地方的有价值的技术——模型。
例如,此模型有几个自定义方法:
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)
本例中的最后一个方法是 property .
这个 model instance reference 有一个完整的列表 methods automatically given to each model . 您可以覆盖其中的大部分--请参见 overriding predefined model methods ,下面--但您几乎总是希望定义以下几项:
-
返回任何对象的字符串表示形式的python“magic method”。每当需要强制模型实例并将其显示为纯字符串时,python和django都将使用这种方法。最值得注意的是,当您在交互式控制台或管理员中显示对象时,就会发生这种情况。您将始终希望定义此方法;默认值一点也不太有用。
-
这将告诉Django如何计算对象的URL。Django在其管理界面中使用它,并且在任何时候它都需要为一个对象找到一个URL。任何具有唯一标识该对象的URL的对象都应定义此方法。
重写预定义的模型方法
还有一套 model methods 它封装了一系列您想要定制的数据库行为。尤其是你经常想改变的方式 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
在方法定义中,可以保证在添加参数时,代码将自动支持这些参数。
未对批量操作调用重写的模型方法
请注意 delete()
当 deleting objects in bulk using a QuerySet 或者由于 cascading delete
. 为了确保执行自定义的删除逻辑,可以使用 pre_delete
和/或 post_delete
信号。
不幸的是,当 creating
或 updating
大量对象,因为没有 save()
, pre_save
和 post_save
被称为。
正在执行自定义SQL
另一种常见的模式是在模型方法和模块级方法中编写自定义SQL语句。有关使用原始SQL的详细信息,请参阅 using raw SQL .
模型继承
Django中的模型继承的工作方式与Python中的正常类继承的工作方式几乎相同,但仍应遵循页面开头的基础知识。这意味着基类应该子类 django.db.models.Model
.
您需要做的唯一决定是,您是否希望父模型本身就是模型(具有自己的数据库表),或者父模型只是公共信息的持有者,这些公共信息只能通过子模型可见。
Django有三种继承方式。
- 通常,您只需要使用父类来保存不需要为每个子模型键入的信息。这个类不会单独使用,所以 抽象基类 就是你想要的。
- 如果您要对现有模型进行子类化(可能完全来自另一个应用程序),并希望每个模型都有自己的数据库表, 多表继承 是前进的道路。
- 最后,如果只想修改模型的Python级行为,而不想以任何方式更改模型字段,那么可以使用 代理模型 .
抽象基类
当您希望将一些公共信息放入许多其他模型中时,抽象基类非常有用。你写你的基础课 abstract=True
在 Meta 类。然后,该模型将不会用于创建任何数据库表。相反,当它用作其他模型的基类时,它的字段将添加到子类的字段中。
一个例子:
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)
这个 Student
模型将有三个字段: name
, age
和 home_group
. 这个 CommonInfo
模型不能用作普通的Django模型,因为它是抽象基类。它不生成数据库表或没有管理器,不能直接实例化或保存。
从抽象基类继承的字段可以用另一个字段或值重写,也可以用 None
.
对于许多用途,这种类型的模型继承将完全满足您的需要。它提供了一种在Python级别分解公共信息的方法,同时在数据库级别为每个子模型只创建一个数据库表。
Meta
遗传
创建抽象基类时,Django将 Meta 在基类中声明的内部类可用作属性。如果子类不声明自己的类 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 )将使用相同的数据库表,这几乎肯定不是您想要的。
由于Python继承的工作方式,如果一个子类从多个抽象基类继承,则只有 Meta 默认情况下,将继承第一个列出的类中的选项。继承 Meta 来自多个抽象基类的选项,必须显式声明 Meta 继承权。例如::
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
ordering = ['name']
class Unmanaged(models.Model):
class Meta:
abstract = True
managed = False
class Student(CommonInfo, Unmanaged):
home_group = models.CharField(max_length=5)
class Meta(CommonInfo.Meta, Unmanaged.Meta):
pass
当心 related_name
和 related_query_name
如果您正在使用 related_name
或 related_query_name
在一 ForeignKey
或 ManyToManyField
,必须始终指定 独特的 字段的反向名称和查询名称。这通常会在抽象基类中引起问题,因为这个类上的字段包含在每个子类中,并且属性值完全相同(包括 related_name
和 related_query_name
)每次。
在使用时解决此问题 related_name
或 related_query_name
在抽象基类中(仅限),部分值应包含 '%(app_label)s'
和 '%(class)s'
.
'%(class)s'
替换为字段所用的子类的低基名称。'%(app_label)s'
替换为包含子类的应用程序的低基名称。每个安装的应用程序名称都必须是唯一的,并且每个应用程序中的模型类名称也必须是唯一的,因此最终得到的名称将是不同的。
例如,给定一个应用程序 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
. 这取决于你如何使用 '%(class)s'
和 '%(app_label)s'
用于构造相关名称或相关查询名称的部分,但如果忘记使用它,Django将在执行系统检查(或运行)时引发错误 migrate
)
如果不指定 related_name
属性为抽象基类中的字段,默认的反向名称将是子类的名称,后跟 '_set'
,就像通常情况下直接在子类上声明字段一样。例如,在上面的代码中,如果 related_name
属性被省略,其反向名称为 m2m
将是场 childa_set
在 ChildA
案例与 childb_set
对于 ChildB
字段。
多表继承
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
,您可以从 Place
对象到 Restaurant
对象,使用模型名称的小写版本:
>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>
然而,如果 p
在上面的例子中 not 一 Restaurant
(它被直接创建为 Place
对象或是其他类的父级),指 p.restaurant
会提高 Restaurant.DoesNotExist
例外。
自动创建的 OneToOneField
在 Restaurant
链接到 Place
如下所示:
place_ptr = models.OneToOneField(
Place, on_delete=models.CASCADE,
parent_link=True,
primary_key=True,
)
您可以通过声明自己的 OneToOneField
具有 parent_link=True
在 Restaurant
.
Meta
多表继承
在多表继承情况下,子类从其父类继承是没有意义的。 Meta 类。所有的 Meta 选项已经应用到父类,再次应用它们通常只会导致矛盾的行为(这与抽象的基类情况相反,在抽象的基类情况下,基类本身并不存在)。
因此子模型不能访问其父模型 Meta 类。但是,在一些有限的情况下,子级继承父级的行为:如果子级没有指定 ordering
属性或A get_latest_by
属性,它将从其父级继承这些属性。
如果父级具有排序,并且您不希望子级具有任何自然排序,则可以显式禁用它:
class ChildModel(ParentModel):
# ...
class Meta:
# Remove parent's ordering effect
ordering = []
继承与逆向关系
因为多表继承使用隐式 OneToOneField
要链接子级和父级,可以从父级向下移动到子级,如上面的示例所示。但是,这会使用默认名称 related_name
价值观 ForeignKey
和 ManyToManyField
关系。如果要将这些类型的关系放到父模型的子类上,则 must 指定 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'.
添加 related_name
到 customers
以下字段将解决错误: models.ManyToManyField(Place, related_name='provider')
.
指定父链接字段
如前所述,Django将自动创建 OneToOneField
将子类链接回任何非抽象父模型。如果要控制链接回父级的属性的名称,可以创建自己的 OneToOneField
并设置 parent_link=True
以指示您的字段是返回父类的链接。
代理模型
使用时 multi-table inheritance 为模型的每个子类创建一个新的数据库表。这通常是所需的行为,因为子类需要一个位置来存储基类上不存在的任何其他数据字段。但是,有时您只想更改模型的python行为——可能是更改默认管理器,或者添加一个新方法。
这就是代理模型继承的目的:创建 代理 对于原始模型。您可以创建、删除和更新代理模型的实例,所有数据都将像使用原始(非代理)模型一样保存。区别在于,您可以更改默认模型排序或代理中的默认管理器等内容,而不必更改原始模型。
代理模型与普通模型一样声明。通过设置 proxy
的属性 Meta
类到 True
.
例如,假设您要将一个方法添加到 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
现在正常 Person
查询将无序,并且 OrderedPerson
查询将按 last_name
.
代理模型继承 Meta
属性 in the same way as regular models .
QuerySet
S仍然返回请求的模型
没有办法让 Django 回来,比如说 MyPerson
无论何时查询 Person
对象。一个查询集 Person
对象将返回这些类型的对象。代理对象的关键是依赖原始代码的代码 Person
将使用这些代码,并且您自己的代码可以使用所包含的扩展(其他代码都不依赖)。它不是替代 Person
(或任何其他)用你自己创造的东西做模型。
基类限制
代理模型只能从一个非抽象模型类继承。不能从多个非抽象模型继承,因为代理模型不提供不同数据库表中的行之间的任何连接。代理模型可以从任意数量的抽象模型类继承,前提是它们可以继承 not 定义任何模型字段。代理模型还可以从共享公共非抽象父类的任意数量的代理模型继承。
代理模型管理器
如果不在代理模型上指定任何模型管理器,它将从其模型父代继承这些管理器。如果您在代理模型上定义了一个管理器,它将成为默认的,尽管在父类上定义的任何管理器仍然可用。
继续上面的示例,可以更改查询 Person
模型如下:
from django.db import models
class NewManager(models.Manager):
# ...
pass
class MyPerson(Person):
objects = NewManager()
class Meta:
proxy = True
如果要向代理添加新的管理器,而不替换现有的默认值,则可以使用 custom manager 文档:创建一个包含新管理器的基类,并在主基类之后继承它:
# 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
你可能不需要经常这样做,但是,当你这样做的时候,这是可能的。
代理继承和非托管模型之间的区别
代理模型继承看起来可能与使用 managed
模型的属性 Meta
类。
小心设置 Meta.db_table
您可以创建一个非托管模型,该模型隐藏现有模型并向其中添加python方法。然而,这将是非常重复和脆弱的,因为您需要保持两个副本同步,如果您做任何更改。
另一方面,代理模型的行为与它们代理的模型完全相同。它们总是与父模型同步,因为它们直接继承其字段和管理器。
一般规则如下:
- 如果要镜像现有模型或数据库表,但不需要所有原始数据库表列,请使用
Meta.managed=False
. 该选项通常用于建模不在Django控制下的数据库视图和表。 - 如果您希望更改模型的纯Python行为,但要保留与原始字段相同的所有字段,请使用
Meta.proxy=True
. 这将进行设置,以便在保存数据时,代理模型是原始模型存储结构的精确副本。
多重继承
正如Python的子类化一样,django模型也可能从多个父模型继承。请记住,标准的Python名称解析规则适用。一个特定名称(例如 Meta )出现在中的将是所使用的;例如,这意味着如果多个父级包含 Meta 类,只有第一个将被使用,其他所有将被忽略。
一般来说,您不需要从多个父级继承。这对“混入”类很有用的主要用例是:向继承混入的每个类添加一个特定的额外字段或方法。尽量保持继承层次结构的简单和简单,这样您就不必费劲去找出特定信息的来源。
请注意,继承自具有公共 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
不允许字段名“隐藏”
在普通的Python类继承中,允许子类重写父类中的任何属性。在Django中,这通常不允许用于模型字段。如果非抽象模型基类有一个名为 author
,不能创建其他模型字段或定义名为 author
在继承该基类的任何类中。
此限制不适用于从抽象模型继承的模型字段。这些字段可以被另一个字段或值覆盖,也可以通过设置 field_name = None
.
警告
模型管理器是从抽象基类继承的。重写由继承的引用的继承字段 Manager
可能导致细微的错误。见 custom managers and model inheritance .
备注
一些字段定义模型上的额外属性,例如 ForeignKey
使用定义额外属性 _id
附加到字段名,以及 related_name
和 related_query_name
关于外国模式。
这些额外属性不能被重写,除非定义它的字段被更改或删除,以便它不再定义额外属性。
覆盖父模型中的字段会在初始化新实例(指定在中初始化哪个字段)等方面造成困难 Model.__init__
)和序列化。这些是正常的Python类继承不需要以完全相同的方式处理的特性,因此django模型继承和Python类继承之间的区别不是任意的。
此限制仅适用于以下属性: Field
实例。如果愿意,可以重写普通的python属性。它也只适用于python看到的属性名:如果手动指定数据库列名,则可以在多表继承的子模型和祖先模型中显示相同的列名(它们是两个不同数据库表中的列)。
Django将提高 FieldError
如果覆盖任何祖先模型中的任何模型字段。
在包中组织模型
这个 manage.py startapp
命令创建包含 models.py
文件。如果您有许多模型,将它们组织在单独的文件中可能很有用。
为此,请创建 models
包。去除 models.py
创建一个 myapp/models/
目录 __init__.py
文件和用于存储模型的文件。必须将模型导入到 __init__.py
文件。
例如,如果你有 organic.py
和 synthetic.py
在 models
目录:
myapp/models/init.py
from .organic import Person
from .synthetic import Robot
显式导入每个模型,而不是使用 from .models import *
它的优点是不使名称空间混乱,使代码更具可读性,并使代码分析工具保持有用。
参见
-
涵盖所有与模型相关的API,包括模型字段、相关对象和
QuerySet
.