How contribute_to_model can handle ManyToMany fields ?

I work on a crm made in django (cremecrm.com), and sometimes for customers I use the snippet contribute_to_model in order to avoid to modify existing models. It’s the best solution we found for staying the most mainstream and avoid huge merges. The problem with this work around is it doesn’t handle many to many. Here’s the tip I found for managing m2m without modifying contribute to model.

The tips to deal with many to many :

# -*- coding: utf-8 -*-

from django.db import models
from django.db.models.fields import CharField, BooleanField
from django.db.models.fields.related import ManyToManyField
from django.utils.translation import ugettext_lazy as _

from contribute_to_model import contribute_to_model

class MyModel(models.Model):
    title = CharField(_(u'Title'), max_length=100)

    class Meta:
        app_label = "my_app"

class MyFutureM2MModel(models.Model):
    foo = CharField(_(u'Foo'), max_length=100)

    class Meta:
        app_label = "my_app"

class MyModelWhichContribute(models.Model):
    field_to_add1 = CharField(_(u'field_to_add1 to MyModel'), max_length=100, blank=True, null=True)
    field_to_add2 = BooleanField(_(u'field_to_add2 to MyModel'), default=False)

    class Meta:
        abstract = True
        db_table = 'my_app_mymodel'

MyModel.add_to_class('my_m2m_field_name', ManyToManyField(MyFutureM2MModel, verbose_name=_(u'My m2m'), blank=True, null=True))
contribute_to_model(MyModelWhichContribute, MyModel)

The contribute_to_model code (which is not from me):


from django.db import models
from django.utils.functional import curry


def contribute_to_model(contrib, destination):
    """
    Update ``contrib`` model based on ``destination``.

    Every new field will be created. Existing fields will have some properties
    updated.

    Methods and properties of ``contrib`` will populate ``destination``.

    Usage example:

    >>> from django.contrib.auth.models import User
    >>> from django.db import models
    >>> 
    >>> class MyUser(models.Model):
    >>>     class Meta:
    >>>         abstract = True
    >>>         db_table = 'user' # new auth_user table name
    >>>
    >>>     # New field
    >>>     phone = models.CharField('phone number', blank=True, max_length=20)
    >>> 
    >>>     # Email could be null
    >>>     email = models.EmailField(blank=True, null=True)
    >>>
    >>>     # New (stupid) method
    >>>     def get_phone(self):
    >>>         return self.phone
    >>> 
    >>> contribute_to_model(MyUser, User)
    """
    # Contrib should be abstract
    if not contrib._meta.abstract:
        raise ValueError('Your contrib model should be abstract.')

    protected_get_display_method = []
    # Update or create new fields
    for field in contrib._meta.fields:
        try:
            destination._meta.get_field_by_name(field.name)
        except models.FieldDoesNotExist:
            field.contribute_to_class(destination, field.name)
            if field.choices:
                setattr(destination, 'get_%s_display' % field.name, curry(destination._get_FIELD_display, field=field))
                protected_get_display_method.append ('get_%s_display' % field.name)
        else:
            current_field = destination._meta.get_field_by_name(field.name)[0]
            current_field.null = field.null
            current_field.blank = field.blank
            current_field.max_length = field.max_length

    # Change some meta information
    if hasattr(contrib.Meta, 'db_table'):
        destination._meta.db_table = contrib._meta.db_table

    # Add (or replace) properties and methods
    protected_items = dir(models.Model) + ['Meta', '_meta'] + protected_get_display_method #TODO: use a set() ??

    for k, v in contrib.__dict__.items(): #TODO: iteritems() instead ??
        if k not in protected_items:
            setattr(destination, k, v)
Advertisements
This entry was posted in django, python. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s