Django学习笔记:Model中的内联类--Meta

内联类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

  • 在通过QuerySetlastest方法查询数据的时候,如果没有传字段给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在执行managemigrate方法时,是否会去对数据库进行更改(生成、修改、删除数据表)。默认值为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.Commandsync_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的源码中明确告知orderingorder_with_respect_to是互斥的。是因为在内部,order_with_respect_to添加了一个名为_order的附加字段/数据库列,并将Modelordering选项设置为该字段。因此,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。

写在最后