【问题标题】:Programmatically specifying Django model attributes以编程方式指定 Django 模型属性
【发布时间】:2010-03-23 15:30:39
【问题描述】:

我想以编程方式向 Django 模型添加属性。在类创建时(模型类的定义时)。之后模型在运行时不会改变。例如,假设我想定义一个Car 模型类,并希望在给定货币列表的情况下为每种货币添加一个price 属性(数据库列)。 (这个货币列表应该被认为是一个不会改变运行时的常量。我不想要这些价格的相关模型。)

最好的方法是什么?

我有一个我认为可行的方法,但它并不完全正确。这就是我尝试这样做的方式,使用上面的汽车示例:

from django.db import models

class Car(models.Model):
    name = models.CharField(max_length=50)

currencies = ['EUR', 'USD']
for currency in currencies:
    Car.add_to_class('price_%s' % currency.lower(), models.IntegerField())

乍一看,这确实很有效:

$ ./manage.py syncdb
Creating table shop_car

$ ./manage.py dbshell
shop=# \d shop_car
                                  Table "public.shop_car"
  Column   |         Type          |                       Modifiers                       
-----------+-----------------------+-------------------------------------------------------
 id        | integer               | not null default nextval('shop_car_id_seq'::regclass)
 name      | character varying(50) | not null
 price_eur | integer               | not null
 price_usd | integer               | not null
Indexes:
    "shop_car_pkey" PRIMARY KEY, btree (id)

但是当我尝试创建一辆新汽车时,它不再起作用了:

>>> from shop.models import Car
>>> mycar = Car(name='VW Jetta', price_eur=100, price_usd=130)
>>> mycar
<Car: Car object>
>>> mycar.save()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/django/db/models/base.py", line 410, in save
    self.save_base(force_insert=force_insert, force_update=force_update)
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/django/db/models/base.py", line 495, in save_base
    result = manager._insert(values, return_id=update_pk)
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/django/db/models/manager.py", line 177, in _insert
    return insert_query(self.model, values, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/django/db/models/query.py", line 1087, in insert_query
    return query.execute_sql(return_id)
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/django/db/models/sql/subqueries.py", line 320, in execute_sql
    cursor = super(InsertQuery, self).execute_sql(None)
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/django/db/models/sql/query.py", line 2369, in execute_sql
    cursor.execute(sql, params)
  File "/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages/django/db/backends/util.py", line 19, in execute
    return self.cursor.execute(sql, params)
ProgrammingError: column "price_eur" specified more than once
LINE 1: ...NTO "shop_car" ("name", "price_eur", "price_usd", "price_eur...
                                                             ^

但显然,我的代码似乎运行了多次,导致“price_eur”属性被添加了多次。

评论:最初我使用“在运行时”的措辞(“我想在运行时以编程方式向 Django 模型添加属性。”)。这个措辞不是最好的。我真正想要的是在“模型定义时间”或“类创建时间”添加这些字段。

【问题讨论】:

  • 在相关说明中 - 我知道以这种方式以编程方式添加字段很丑陋。更“漂亮”的方式是使用相关模型。但这不是这里的问题,因为在这种情况下增加数据库复杂性和降低性能根本不值得。
  • “性能降低”?你量过吗?您将很难看到这种完全匹配关系连接的效果。
  • @S.Lott:是的。我没有准确测量过这种特殊情况,但我有一些大型数据集的示例(在连接的一侧),其中额外的 SQL 连接只是某些查询中的大性能问题。当然,您可能是对的,在这种特殊情况下,它可能没有那么大的影响。
  • 这不是一个大数据集。这是几种不同货币的价格。试试看。很容易用假数据来装配这两个模型并获得性能数据。通常,获得性能数据比探索棘手、微妙的代码更有价值。

标签: python django django-models


【解决方案1】:

仍然不是很好,但比使用locals 更好的解决方案是使用Field.contribute_to_class

for currency in currencies:
    models.IntegerField().contribute_to_class(Car, 'price_%s' % currency.lower())

I used it for MPTT(我一直是一个非常糟糕的维护者*隐藏*)

编辑:再想一想,您的代码运行良好(Car.add_to_class 为您调用该字段的 contribute_to_class)但问题似乎是添加附加字段的代码正在多次执行,因此您的模型认为它需要保存多个具有相同名称的字段。您需要在其中添加一些内容以确保只动态添加一次字段。

django.db.models.fields.Field.contribute_to_class 调用django.db.models.options.Options.add_field(对象的_meta 属性是Options 的一个实例),它不会检查具有该名称的字段是否已经存在,并愉快地将字段详细信息添加到它知道的字段列表。

【讨论】:

  • 太棒了!我认为我的主要问题是我使用了add_to_class而不是contribute_to_class。是的,我同意这有点难看。我想我的整个问题有点难看。
  • 嘿,你得做你该做的:)
  • 感谢编辑!你就在这里。 add_to_class 应该可以工作,并且由于某种原因我的代码运行了几次。现在我对此进行了检查,一切正常。谢谢!
【解决方案2】:

由于各种原因,我的解决方案很糟糕,但它确实有效:

from django.db import models

currencies = ["EUR", "USD"]

class Car(models.Model):

    name = models.CharField(max_length=50)

    for currency in currencies:
        locals()['price_%s' % currency.lower()] = models.IntegerField()

在我必须这样做的地方,我不得不在类似的事情和维护有 200 多列的表之间做出选择(我知道这有多糟糕,但我对此没有影响)。

【讨论】:

  • 太棒了!我不知道你能做到这一点(在类定义中使用 for 循环)。这完美地解决了我的问题。我同意这是不好的 - 在运行时操纵模型可能被视为不好有几个原因 - 但我仍然认为有时是必要的。
【解决方案3】:

“我想以编程方式向 Django 模型添加属性。在创建类时”

不要。 “以编程方式”添加列既愚蠢又令人困惑。对深入了解 Django 细微差别的开发人员来说,这似乎很好。

对于我们维护者来说,这些代码 (a) 毫无意义,并且 (b) 必须用简单、明显的代码替换,这些代码以最简单最明显的方式完成相同的工作。

请记住,维护者是暴力反社会者,他们知道您住在哪里。用简单明了的代码迎合他们。

没有理由用看起来很棘手的循环替换一组简单的属性定义。没有改善,也没有节省。

  1. 视图函数的运行时性能是一样的。

  2. 一次性类定义保存了几行代码,这些代码在应用程序启动期间执行一次。

  3. 开发成本(即解决这个问题)较高。

  4. 维护成本(即,将此代码交给其他人以保持其运行)高得天文数字。代码将简单地替换为更简单、更明显的代码。

即使您有 100 种货币,这仍然是一个非常糟糕的主意。

“我不想要这些价格的相关型号”

为什么不呢?一个相关的模型是 (1) 简单的,(2) 明显的,(3) 标准的,(4) 可扩展的。它在运行时或开发时几乎没有可衡量的成本,当然也没有复杂性。

“例如,假设我有一个 Car 模型类并且想要添加一个价格属性 (数据库列)每种货币,给定货币列表。”

根本不是一个新属性。

这是一个以 Car Model 和 Currency 作为键的表的新值(在一行中)。

你的类看起来像这样:

class Price( models.Model ):
    car = models.ForeignKey( Car )
    currency = models.ForeignKey( Currency )
    amount = models.DecimalField()

问题的先前版本

“我想在运行时以编程方式向 Django 模型添加属性。”

不要。在任何情况下,您都不想“在运行时”向数据库添加属性。

【讨论】:

  • 为什么会有这种自大的态度?我完全理解你在这里的看法。但在这种情况下,我希望将货币作为数据库中的列 - 而不是作为相关模型。使用单独的模型(和数据库表)引入了一个 SQL 连接,在这种情况下根本不行,因为有一组固定的货币很少改变(比如大约一年一次左右)。添加第二层抽象很好,但也会在性能和复杂性方面引入开销。
  • 我同意我在这里选择的词语“在运行时”并不是最好的。但我完全不同意您的观点,即绝对没有您希望以编程方式更改模型定义的情况。
  • @mojbro:你没抓住重点。重点不在于性能。这是一个定义问题。使用关系模型时,绝对没有理由尝试在运行时添加列。如果你想改变你的问题,我可以改变我的答案。但是关系模型经过精心设计,完全没有必要在运行时添加属性。任何在运行时添加属性的尝试都意味着忽略了关系设计模式。
  • @mojbro:如果您有一组固定的货币并希望将它们非规范化为列,那么您不会在运行时添加任何内容。请更正您的问题以描述您真正想做的事情。
  • 好吧,我就放弃了。你完全专注于“运行时”这个词,尽管我特别评论说“运行时”这个词不是最好的。我真正想要的是生成模型定义的代码。在定义模型时(顺便说一下,这发生在 Python 的运行时)。更好的措辞可能是“在编译时”(在 Python 中确实不是这种情况)。
【解决方案4】:

您不能在运行时执行此操作。它会更改数据库模型,并通过命令syncdb 更改数据库,不知何故这感觉真的很难看。

为什么不创建第二个模型Price 来保存不同货币的价格。或者如果可能的话,将价格即时转换为特定货币。

【讨论】:

  • 我在这里理解你的意见,但我想这样做是有原因的。一个相关的模型只是简单地降低了性能,在我的情况下是无法接受的。当商店想要添加新货币时,在多个地方对货币进行硬编码并不好。这种情况很少发生,当发生这种情况时,当然必须更改数据库。就像编辑模型时一样。 “货币”列表或多或少是模型定义的一部分。
猜你喜欢
  • 1970-01-01
  • 2013-08-31
  • 1970-01-01
  • 2023-01-10
  • 1970-01-01
  • 2011-03-17
  • 2023-04-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多