以编程方式指定Django模型属性

moj*_*bro 6 python django django-models

我想以编程方式向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())
Run Code Online (Sandbox Code Playgroud)

这看起来似乎很有效:

$ ./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)
Run Code Online (Sandbox Code Playgroud)

但是当我尝试创造一辆新车时,它不再适用了:

>>> 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...
                                                             ^
Run Code Online (Sandbox Code Playgroud)

但显然,我的代码似乎运行了几次,导致"price_eur"属性被添加了几次.

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

Łuk*_*asz 5

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

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()
Run Code Online (Sandbox Code Playgroud)

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


Jon*_*nan 4

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

for currency in currencies:
    models.IntegerField().contribute_to_class(Car, 'price_%s' % currency.lower())
Run Code Online (Sandbox Code Playgroud)

我将它用于 MPTT(为此我一直是一个非常糟糕的维护者*隐藏*)

编辑:再想一想,您的代码工作正常(为您Car.add_to_class调用字段contribute_to_class),但问题似乎是添加附加字段的代码正在执行多次,因此您的模型认为它需要使用一样的名字。您需要在其中添加一些内容以确保仅动态添加字段一次。

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