Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatically specifying Django model attributes

I would like to add attributes to a Django models programmatically. At class creation time (the time of the definition of the model class). The model is not going to change after that in run time. For instance, lets 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 won't change runtime. I don't want a related model for these prices.)

What would be the best way to do this?

I had an approach that I thought would work, but it didn't exactly. This is how I tried doing 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())

This does seem to work pretty well at first sight:

$ ./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 doesn't really work anymore:

>>> 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, though, my code seem to run several times, causing the "price_eur" attribute to be added several times.

Comment: Initially I used the wording "at run time" ("I would like to add attributes to a Django models programmatically, at run time."). This wording wasn't the best. What I really want to was to add those fields at "model definition time" or "class creation time".

like image 560
mojbro Avatar asked Mar 23 '10 15:03

mojbro


People also ask

What is def __ str __( self in Django?

def __str__( self ): return "something" This will display the objects as something always in the admin interface.

What is __ Str__ in Django?

The __str__ method just tells Django what to print when it needs to print out an instance of the any model. It is also what lets your admin panel, go from this. Note: how objects are just plainly numbered.

What is attribute in model Django?

Each attribute of the model represents a database field. With all of this, Django gives you an automatically-generated database-access API; see Making queries.


3 Answers

The still not nice, but nicer than using locals solution is to 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've been a very bad maintainer *hides*)

Edit: On second thoughts, your code was working fine (Car.add_to_class calls the field's contribute_to_class for you) but the problem seems to be that the code to add the additional fields is being executed multiple times, so your model thinks it needs to save multiple fields with the same name. You need to put something in there to make sure you only dynamically add the fields once.

django.db.models.fields.Field.contribute_to_class calls django.db.models.options.Options.add_field (the object's _meta attribute is an instance of an Options), which doesn't check to see if a field with that name already exists and happily adds the field details to the list of fields it knows about.

like image 102
Jonny Buchanan Avatar answered Sep 23 '22 05:09

Jonny Buchanan


My solution is something which is bad from 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 had to do this I had choice between something like that and maintaining table with more than 200 columns (I know, how bad it was, but I had no influence on it).

like image 42
Łukasz Avatar answered Sep 20 '22 05:09

Łukasz


"I would like to add attributes to a Django models programmatically. At class creation time"

Don't. "Programmatically" adding columns is silly and confusing. It seems fine to you -- the developer -- who deeply gets the nuances of Django.

For us, the maintainers, that code will (a) make no sense and (b) have to be replaced with simple, obvious code that does the same job the simplest most obvious way.

Remember, the maintainers are violent sociopaths who know where you live. Pander to them with simple and obvious code.

There's no reason to replace a cluster of simple attribute definitions with a tricky-looking loop. There's no improvement and no savings.

  1. The run-time performance of the view functions is the same.

  2. The one-time class-definition has saved a few lines of code that are executed once during application start-up.

  3. The development cost (i.e., solving this question) is higher.

  4. The maintenance cost (i.e., turning this code over to someone else to keep it running) is astronomically high. The code will simply be replaced with something simpler and more obvious.

Even if you have 100's of currencies, this is still a perfectly bad idea.

"I don't want a related model for these prices"

Why not? A related model is (1) simple, (2) obvious, (3) standard, (4) extensible. It has almost no measurable cost at run time or development time and certainly no complexity.

"For instance, lets say I have a Car model class and want to add one price attribute (database column) per currency, given a list of currencies."

That is not a new attribute at all.

That is a new value (in a row) of a table that has Car Model and Currency as the keys.

You 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 would like to add attributes to a Django models programmatically, at run time."

Don't. There are absolutely no circumstances under which you ever want to add attributes to a database "at run time".

like image 26
S.Lott Avatar answered Sep 23 '22 05:09

S.Lott