Programmatically Defining Django Model Attributes

I want to add attributes in a Django model programmatically. At class creation time (model class definition time). The model will not change after that at runtime. For example, let's say I want to define a Car model class and want to add one price attribute (database column) per currency, given a list of currencies. (This list of currencies should be considered a constant that will not change the execution time. I do not want to have an appropriate model for these prices.)

What would be the best way to do this?

I had an approach that I thought would work, but this is not entirely true. Here's how I tried to do it using the car example above:

 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()) 

At first glance, this looks very good:

 $ ./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) 

But when I try to create a new car, it no longer works:

 >>> 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... ^ 

Apparently, my code seems to run several times, causing the price_eur attribute to be added several times.

Comment: Initially, I used the phrase "at runtime" ("I would like to add attributes to Django models programmatically at runtime."). This wording was not the best. I really wanted to add these fields to "model definition time" or "class creation time".

+6
python django django-models
source share
4 answers

Still not nice, but better than using the locals solution, use Field.contribute_to_class :

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

I used it for MPTT (for which I supported very poorly * hid *)

Edit: Secondly, your code worked fine ( Car.add_to_class calls the contribute_to_class field for you), but the problem is that the code to add additional fields is executed several times, so your model thinks that it needs to save several fields so same name. You need to add something so that you only dynamically add fields once.

django.db.models.fields.Field.contribute_to_class calls django.db.models.options.Options.add_field (the _meta attribute is an instance of Options ), which does not check if a field with this name exists and gladly adds information about the field to list of fields that he knows about.

+4
source share

"I would like to add attributes to the Django model programmatically. At the time of class creation

not to do. Programmatically adding columns is stupid and confusing. It seems good to you - a developer who deeply gets the nuances of Django.

For us, the companions, this code will (a) not make sense and (b) should be replaced by a simple, obvious code that makes the same work the simplest, most obvious way.

Remember that companions are violent sociopaths who know where you live. Emphasize simple, clear code to them.

There is no reason to replace a cluster of simple attribute descriptions with a complex loop. There is no improvement and no savings.

  • The performance of representing runtime functions is the same.

  • A one-time class definition has saved several lines of code that are executed once during application startup.

  • The development cost (i.e. solving this issue) is higher.

  • The cost of maintenance (i.e. switching this code to someone else to make it work) was astronomically high. The code will simply be replaced with something simpler and more understandable.

Even if you have 100 currencies, this is still a completely bad idea.

"I do not need an appropriate model for these prices"

Why not? The associated model (1) is simple, (2) obvious, (3) standard, (4) extensible. It has virtually no measurable cost at runtime or development time, and, of course, without complexity.

"For example, let's say I have a car model class and you want to add one price attribute (database column) per currency, taking into account the list of currencies."

This is not a new attribute.

This is the new value (in a row) of the table in which there is a car model and currency as keys.

The class looks something like this:

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

Previous version of the question

"I want to add attributes to the Django model programmatically at runtime."

not to do. There are absolutely no conditions under which you will ever want to add attributes to the runtime database.

+3
source share

My solution is that which is bad for various reasons, but it works:

 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() 

In the place where I was supposed to do this, I had a choice between something similar and supporting a table with more than 200 columns (I know how bad it was, but I did not affect it).

+3
source share

You cannot do this at run time. This will change the database model, and the database will be modified using the syncdb , and somehow it seems really ugly.

Why not create a second Price model that contains the price for different currencies. Or, if possible, convert the price on the fly to a specific currency.

0
source share

All Articles