L'uso di questo sito
autorizza anche l'uso dei cookie
necessari al suo funzionamento.
(Altre informazioni)

Thursday, January 24, 2013

Custom column names on Django ManyToMany fields.

Adapting Django  to legacy databases requires you to control table and column names.

manage.py inspectdb does a nice enough job of it (but beware of missing to_field on ForeignKey relationshiops when not linking to the primary key). 

ManyToMany fields, however, are left in the cold. Table name can be set with db_table=, but there is no (documented) hook to influence column names. Finer control can be achieved through the relatively new 'through' argument: that, however, kinda ruins many of the ORM language and of the admin application capabilities.

I googled myself blind and visited a few blind alleys. Postgres, for instance, does not have updatable views, or one could use a view to sort of rename columns - without actually doing it - thus appeasing django's engine; updatable views could be emulated with a CREATE RULE, but I decided against going there.

I dived in django code - the related.py and construction.py files - not pretty .I will heretofore direct to those files anyone extolling to me the clarity of python coding.

I eventually came across a Django Snippet that the author appears to have  retracted, but that the internet wayback machine still preserves. It did still require a modification, but I finally came up with:

from django.db import models

class CustomManyToManyField(models.ManyToManyField):

    def __init__(self, *args, **kwargs):

        source = kwargs.pop('source_db_column', None)

        reverse = kwargs.pop('reverse_db_column', None)

        if source is not None:

            self._m2m_column_cache = source

        if reverse is not None:

            self._m2m_reverse_column_cache = reverse

        super(CustomManyToManyField, self).__init__(*args, **kwargs)

# later.... class TipoCommesse(models.Model):     id          = models.IntegerField(primary_key=True)     descrizione = models.TextField() # This field type is a guess.     tipo_fasi   = CustomManyToManyField('TipoFasi',db_table='t_fasi4t_commessa',source_db_column='id_tipo_com',reverse_db_column='id_tipo_fase')     def __unicode__(self): return self.descrizione     class Meta:         managed =  False         db_table = u'tipo_commesse'         verbose_name_plural='TipiCommesse'
Note the managed = False line in the Meta inner class indicating that syncdb & Co. need not alter the database for this type. Besides being a sane precaution to set on everything when dealing with legacy DBs, it is doubly necessary here because finding out how to persuade Django to honor the column specifications when actually creating the tables proved to be beyond my ability - that's the above mentioned - and by now infamous - construction.py file. The effect is that (as can be seen with manage.py sql, with manage=True) Django would still generate columns of the form re11rel2_id even for the custom m2m field.

Also note that this trick is fragile: it depends on django actually using the 'column' and 'reverse_column' names when currying the attribute accessor (don't ask). This has already been broken once in the past.

Finally, a big 'Thank You' to Quentin Tarantino for making Django searches a more interesting experience with his "Django Unchained" release ;-)