【问题标题】:Django: Abstract base class for managing db_tableDjango:用于管理 db_table 的抽象基类
【发布时间】:2021-07-17 00:21:02
【问题描述】:

我正在尝试构建我的第二个 django(以及 python,我的第一个项目是 django 教程 :))项目。因为这应该是真实的,所以在我进入项目的实质之前,我想彻底了解并构建一个良好的代码结构。

我有几个像这样的简单模型

class Task(models.Model):
    name = models.CharField(max_length=50, null=False)
    description = models.CharField(max_length=255, null=True)
    dueDate = models.DateTimeField()

我正在使用 PostgreSQL,我通过像这样定义每个模型的元类来设置我的模型以使用应用程序标签作为数据库模式

class Meta:
    managed = True
    db_table = 'app_name\".\"modelname'

这很好用。但是我必须对每个模型都这样做。

我想保持干燥。所以我现在要做的是有一个抽象的基类来自动执行此操作 所以我尝试了以下方法:

class SchemaModel(models.Model):

    class Meta():
        abstract = True
        managed = True
        db_table = AppConfig.label+'\".\"'+self.__class__.lower()+'s'

(当然,基类随后被继承,我将嵌套的Meta 类从普通模型中取出)

这不起作用,因为在Meta 中无法访问self

在咨询了documentation 之后,我尝试了这个:

class SchemaModel(models.Model):

    class Meta():
        abstract = True
        managed = True
        db_table = '%(app_label)\".\"%(class)s'

这导致每个模型的属性db_table"%(app_label)\".\"%(class)s"

>>> t = Task()
>>> t._meta.db_table
'%(app_label)"."%(class)s'
>>>

我在互联网上没有找到类似的东西。我是在尝试做一些不可能或“禁止”的事情吗?

解决方案

解决方案如elyas回答中所示,通过循环遍历所有__subclasses__()models.py末尾设置db_table属性

for model in SchemaModel.__subclasses__():
    db_table = '{}s'.format(model._meta.label_lower.replace('.','\".\"'))
    model._meta.original_attrs['db_table'] = db_table
    model._meta.db_table = db_table

【问题讨论】:

    标签: python-3.x django metaclass


    【解决方案1】:

    我不会说这是被禁止的。但我想不出任何方式来声明式地做到这一点。但是有一种方法可以做到这一点。

    首先,关于您现有的尝试:

    访问“自我”

    db_table = AppConfig.label+'\".\"'+self.__class__.lower()+'s'
    

    加载模型时,永远不会从 Meta 类创建实例对象,因此没有要引用的 self。但是即使创建了一个实例对象,db_table也是一个attribute of the class object,所以它是在创建类对象时评估的,也就是在创建任何实例对象之前,所以定义类属性时不能访问self以这种方式。

    编辑:正如你所提到的,app_label 不能通过AppConfig.label 访问。

    字符串格式

    db_table = '%(app_label)\".\"%(class)s'
    

    当在抽象基类中定义ForeignKeyOneToOneField 的字段的related_namerelated_query_name 属性时,这些占位符仅用于very specific situation

    解决方案

    正如我所说,我想不出任何声明方式来实现这一点。例如,尝试使用__qualname__ 是行不通的,因为您每次都会以SchemaModel.Meta 结束。

    但是你可以像这样在你的 models.py 的底部放置一个 for 循环:

    for model in SchemaModel.__subclasses__():
        # Name your db_table here
        db_table = model._meta.app_label + '\".\"' + model._meta.model_name.lower() + 's'
        # Set db_table here
        model._meta.original_attrs['db_table'] = db_table
        model._meta.db_table = db_table
    
    • 可以使用内置的__subclasses__() 方法找到SchemaModel 的所有子代。
    • db_table 属性需要在两个地方更新。首先在_meta 中,它(部分)是通过从Meta 类复制属性创建的,其次在_meta.original_attrs 中存储原始Meta 属性并在迁移期间由Django 读取。

    替代解决方案

    就我个人而言,我会手动定义db_table 名称,并简单地进行一个单元测试来检查所有模型是否符合我提出的任何命名约定。我更喜欢这个,所以如果另一个开发人员看到一个他们从未见过的模型,他们可以根据模型(和抽象基类)中的声明获得完整的图片,并且不要对在其他地方修改它们的操作感到困惑。

    【讨论】:

    • 非常感谢您的深入回答。正如我所说,我很新:) 当你说没有办法做到声明性时,这意味着没有办法在 django 迁移中保留这些更改,对吗?在那种情况下,我想我会做替代解决方案
    • 不客气。当我说声明式时,我指的是声明式编程和命令式编程之间的区别。 Django 在幕后做了很多工作,因此我们可以轻松地声明模型应该是什么样子。不过,使用 for 循环解决方案在迁移方面绝对有效 - 随意尝试一下,看看运行 makemigrations 时会发生什么。
    • 很好,它工作得很好。虽然我没有意识到我试图将AppConfig.label 引用为一个显然不起作用的静态属性。但我看到我可以从Model._meta.label 获得完整的名称'app_name.model_name'。所以我使用了它,只是用'\".\"' 替换了'.'。我会相应地提出我的解决方案
    • 很高兴听到它有效!是的,关于AppConfig.label 的好点。我会在我的答案中进行一些编辑,这样它就不会抓住任何人:)
    猜你喜欢
    • 1970-01-01
    • 2014-03-21
    • 2016-04-15
    • 2021-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-08
    • 1970-01-01
    相关资源
    最近更新 更多