我想以编程方式向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模型添加属性。”)。这个措辞不是最好的。我真正想要的是在“模型定义时间”或“类创建时间”添加这些字段。
答案 0 :(得分:4)
仍然不好,但比使用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
的实例),它不检查具有该名称的字段是否已存在并愉快地将字段详细信息添加到它知道的字段列表中。
答案 1 :(得分:3)
“我想以编程方式向Django模型添加属性。在类创建时”
别。 “以编程方式”添加列是愚蠢和令人困惑的。对你来说似乎很好 - 开发人员 - 深深地了解了Django的细微差别。
对我们来说,维护者,代码将(a)没有意义,(b)必须用简单,明显的代码替换,以最简单的方式完成相同的工作。
请记住,维护者是暴力的反社会人士,他们知道你住在哪里。用简单明了的代码向他们求助。
没有理由用一个看起来很棘手的循环替换一组简单的属性定义。没有改善也没有节省。
视图函数的运行时性能相同。
一次性类定义保存了几行代码,这些代码在应用程序启动期间执行一次。
开发成本(即解决这个问题)更高。
维护成本(即将此代码转交给其他人以保持其运行)是天文数字高。代码将简单地用更简单,更明显的代码替换。
即使您拥有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模型添加属性。”
别。绝对没有任何情况下您希望“在运行时”向数据库添加属性。
答案 2 :(得分:3)
我的解决方案是出于各种原因而不好的方法,但它有效:
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列的表格(我知道,它有多糟糕,但我没有影响它。)
答案 3 :(得分:0)
您无法在运行时执行此操作。它将改变数据库模型,并通过命令syncdb
更改数据库,不知怎的,这感觉真的很难看。
为什么不创建一个保存不同货币价格的第二个模型Price
。或者,如果可能的话,将价格即时转换为特定货币。