Программно определяющие атрибуты модели Django - PullRequest
6 голосов
/ 23 марта 2010

Я хотел бы добавить атрибуты к моделям 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 программно, во время выполнения».). Эта формулировка была не самой лучшей. Я действительно хотел добавить эти поля в «время определения модели» или «время создания класса».

Ответы [ 4 ]

4 голосов
/ 23 марта 2010

Все еще не приятно, но лучше, чем использовать locals решение - использовать Field.contribute_to_class:

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

Я использовал его для 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), который не проверяет, существует ли поле с таким именем, и успешно добавляет сведения о поле к список полей, о которых он знает.

3 голосов
/ 23 марта 2010

Мое решение - это нечто плохое по разным причинам, но оно работает:

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 голосов
/ 23 марта 2010

«Я хотел бы добавить атрибуты к моделям Django программным способом. Во время создания класса»

Не. «Программно» добавлять столбцы глупо и сбивает с толку. Вам, разработчику, кажется, хорошо, кто глубоко понимает нюансы Django.

Для нас, сопровождающих, этот код (а) не имеет смысла и (б) должен быть заменен простым, очевидным кодом, который выполняет ту же работу самым простым и очевидным способом.

Помните, что сопровождающие - это жестокие социопаты, которые знают, где вы живете. Подарите им простой и очевидный код.

Нет причин заменять кластер простых определений атрибутов хитроумным циклом. Нет улучшений и нет сбережений.

  1. Производительность функций представления во время выполнения одинакова.

  2. Одноразовое определение класса сохранило несколько строк кода, которые выполняются один раз при запуске приложения.

  3. Стоимость разработки (т. Е. Решение этого вопроса) выше.

  4. Стоимость обслуживания (т. Е. Передача этого кода кому-то другому, чтобы он работал) астрономически высока. Код будет просто заменен чем-то более простым и более очевидным.

Даже если у вас есть сотни валют, это все равно очень плохая идея.

"Я не хочу похожую модель по этим ценам"

Почему бы и нет? Родственная модель является (1) простой, (2) очевидной, (3) стандартной, (4) расширяемой. Он практически не имеет измеримых затрат во время выполнения или разработки и, конечно, не имеет сложности.

"Например, допустим, у меня есть класс модели автомобиля и я хочу добавить один ценовой атрибут (столбец базы данных) для каждой валюты, учитывая список валют. "

То есть не новый атрибут вообще.

Это новое значение (в строке) таблицы, в которой ключами являются Модель автомобиля и Валюта.

Ваш класс выглядит примерно так:

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

Предыдущая версия вопроса

«Я хотел бы добавить атрибуты к моделям Django программно, во время выполнения».

Не. Нет абсолютно никаких обстоятельств, при которых вы когда-либо захотите добавить атрибуты в базу данных «во время выполнения».

0 голосов
/ 23 марта 2010

Вы не можете сделать это во время выполнения. Это изменило бы модель базы данных, и база данных изменилась с помощью команды syncdb, и это почему-то выглядит ужасно

Почему бы не создать вторую модель Price, которая будет содержать цену для разных валют. Или, если возможно, конвертируйте цену на лету в конкретную валюту.

...