内联类Meta
How
-
内联类Meta是用来定义一个Model的元数据的内联类。是否需要在Model中添加内联类Meta是可选的。
-
用法
class TestModel(models.Model): class Meta: pass
What
-
在
django.db.models.base.Model
的源码中可以看到一个opts
的属性,被赋值为self._meta
,源码如下:class Model(metaclass=ModelBase): def __init__(self, *args, **kwargs): ... opts = self._meta ...
-
经过追溯可以发现
self._meta
来源于元类django.db.models.base.ModelBase
,源码如下:class ModelBase(type): def __new__(cls, name, bases, attrs, **kwargs): ... # 元类ModelBase在创建类时注入属性_meta,通过Options对内联类Meta进行了封装 meta = attr_meta or getattr(new_class, "Meta", None) ... new_class.add_to_class("_meta", Options(meta, app_label)) ...
-
继续追溯到
django.db.models.options.Options
中可以看到其构造函数中有很多属性,Meta
所有可能用到的选项均来自于此,源码(此处只列出了后面将要介绍的属性,其他属性请参考源码)如下:class Options: def __init__(self, meta, app_label=None): ... self.verbose_name = None self.verbose_name_plural = None self.db_table = "" self.ordering = [] self.unique_together = [] self.permissions = [] self.app_label = app_label self.get_latest_by = None self.order_with_respect_to = None self.db_tablespace = settings.DEFAULT_TABLESPACE self.abstract = False self.managed = True self.proxy = False ...
Meta可能用到的选项
app_label
-
在
Model
没有被写在默认的应用程序包中时,可以用该选项指定Model
所属的app
。class TestModel(models.Model): class Meta: app_label = "test_app"
db_label
-
可通过该选项自定义
Model
在数据库中生成的表名。class TestModel(models.Model): class Meta: # 添加该选项之后,生成的表名为my_test_table,不设置则是test_app_testmodel db_label = "my_test_table"
db_tablespace
- 用于指定该
Model
对应的数据库表放在哪个数据库表空间,针对有数据库表空间的数据库生效,例如Oracle。
verbose_name
-
通过该选项给
Model
指定一个更可读的名字,一般定义为中文class TestModel(models.Model): class Meta: verbose_name = "测试"
verbose_name_plural
- 指定
Model
名称的复数形式,方式同verbose_name
,如果不指定则会在名称后面加一个”s”,类似”测试s”。
abstract
-
该选项是一个布尔值,为
True
则表示当前Model
为抽象类,为False
则表示不是抽象类,默认为False
。用于Model
的抽象类继承,可保证抽象父类不去创建表。子类继承抽象父类之后,子类会继承父类除abstract
的其他所有字段。子类如果没有显示指定该选项为True
,则会默认设置为False
。 -
在
django.db.models.base.ModelBase
中处理该选项的源码如下,可以看到在创建类的时候,先将该值重置为False
。class ModelBase(type): def __new__(cls, name, bases, attrs, **kwargs): ... # 此处用于判断的abstract取自父类 if abstract: attr_meta.abstract = False new_class.Meta = attr_meta return new_class ...
get_last_by
-
在通过
QuerySet
的lastest
方法查询数据的时候,如果没有传字段给lastest
,则会去get_last_by
的值,源码如下(源码位置–django.db.models.query.QuerySet
):class QuerySet: ... def _earliest(self, *fields): if fields: # 根据指定的字段取值 order_by = fields else: # 如果没有在latest中指定字段,则取Meta中的get_latest_by order_by = getattr(self.model._meta, "get_latest_by") if order_by and not isinstance(order_by, (tuple, list)): order_by = (order_by,) if order_by is None: # 如果没取到值就报错 raise ValueError( "earliest() and latest() require either fields as positional " "arguments or 'get_latest_by' in the model's Meta." ) # 取到值进行查询操作,并返回数据 ... def latest(self, *fields): if self.query.is_sliced: raise TypeError("Cannot change a query once a slice has been taken.") return self.reverse()._earliest(*fields) ...
managed
- 该选项用于控制
Django
在执行manage
的migrate
方法时,是否会去对数据库进行更改(生成、修改、删除数据表)。默认值为True
,如果不希望对数据库有更改操作,需手动指定为False
。 - 源码流程
-
先在
django.db.models.options.Options
中通过读取该选项值在判断方法can_migrate
中返回布尔值,用于后续操作。def can_migrate(self, connection): # 在此处进行了判断,还判断了是否是代理类,是否被装饰器装饰 if self.proxy or self.swapped or not self.managed: return Falseq # 后续其他判断 ...
-
而后在
django.core.management.commands.migrate.Command
的sync_apps
方法读取该值进行操作。class Command(BaseCommand): def sync_apps(self, connection, app_labels): ... with connection.schema_editor() as editor: for app_name, model_list in manifest.items(): for model in model_list: # 此处判断是否跳过该Model的创建 if not model._meta.can_migrate(connection): continue # 不跳过则创建该Model ... ...
-
ordering
-
通过该选项可以设置查询结果集按照那个字段排序。
-
可在字段前加
-
来指定结果集降序排序class TestModel(models.Model): username = models.CharField(max_length=32, unique=True) class Meta: # 此处在username前加了一个"-"前缀 app_label = "-username"
-
不加前缀则表示指定结果集升序排序
class TestModel(models.Model): username = models.CharField(max_length=32, unique=True) class Meta: # username前没有前缀 app_label = "username"
-
使用
?
作为前缀则表示随机排序class TestModel(models.Model): username = models.CharField(max_length=32, unique=True) class Meta: # 此处在username前加了一个"?"前缀 app_label = "?username"
order_with_respect_to
-
该选项通常用于指定
ForeignKey
字段为结果集的排序字段,使相关对象相对于父对象可排序。 -
当
Model
设置了order_with_respect_to
字段之后,会对查询对象提供两种额外的方法来检索和设置相关对象的顺序: get_XXX_order() 和 set_XXX_order(),其中XXX为所属Model
的名称 -
此处示例摘抄自vimsky
-
如果 Answer 与 Question 对象相关,并且一个问题有多个答案,并且答案的顺序很重要
from django.db import models class Question(models.Model): text = models.TextField() class Answer(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) class Meta: order_with_respect_to = 'question'
-
get_answer_order()用于获取排序的对象
>>> question = Question.objects.get(id=1) >>> question.get_answer_order() [1, 2, 3]
-
set_answer_order()可根据传入的Answer主键列表来设置
>>> question.set_answer_order([3, 1, 2])
-
-
在
django.db.models.options.Options
的源码中明确告知ordering
和order_with_respect_to
是互斥的。是因为在内部,order_with_respect_to
添加了一个名为_order
的附加字段/数据库列,并将Model
的ordering
选项设置为该字段。因此,order_with_respect_to 和ordering 不能一起使用,并且每当您获得此模型的对象列表时,都会应用由order_with_respect_to 添加的排序。class Options: ... def contribute_to_class(self, cls, name): ... # order_with_respect_and ordering are mutually exclusive. # order_with_respect_and和ordering互斥。 self._ordering_clash = bool(self.ordering and self.order_with_respect_to) ... ...
-
因为
order_with_respect_to
添加了一个新的数据库列,所以如果您在初始migrate
之后添加或更改order_with_respect_to
,请务必进行并应用适当的迁移。
permissions
-
创建此对象时输入权限表的额外权限。 自动为每个创建添加、删除和更改权限。
-
此示例指定一个额外的权限
can_deliver_pizzas
:permissions = (("can_deliver_pizzas", "Can deliver pizzas"),)
-
格式为2元组的列表或元组:
(permission_code, human_readable_permission_name)
proxy
- 该选项是为了实现代理模型使用的,如果
proxy = True
,表示Model
是其父的代理model。
写在最后
- 官方文档: https://docs.djangoproject.com/en/1.11/ref/models/options/
- 该文章结合了官方文档和源码,由于
Model
的源码量很大,文章中仅截取了个人觉得可突出主题的部分。第一次写文章,里面如果有错误的地方还请指出,谢谢