i18n base model for translatable content

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from django.db.models.base import ModelBase
from django.db.models.base import Model
from django.db import models
from django.conf import settings


# Returns class object from a specified module
def getclass(classname, modulename):
    from_module = __import__(modulename, globals(), locals(), classname)
    return getattr(from_module, classname)

class I18NBase(ModelBase):
    def __new__(cls, name, bases, attrs):
        try:
            if I18NModel in bases:
                attr_meta = attrs.pop('Meta', None)
                # Find out if `i18n_common_model` is defined in `class Meta`,
                # and use that. Otherwise, use this class name -4 chars:
                common_classname = getattr(attr_meta, 'i18n_common_model',
                                           name[:-4])
                I18NCommonModel = getclass(common_classname, attrs['__module__'])
                attrs['i18n_common'] = models.ForeignKey(I18NCommonModel)
                attrs['lang'] = models.CharField(max_length = 5,
                                                 choices = settings.LANGUAGES)
        except NameError:
            pass
        return ModelBase.__new__(cls, name, bases, attrs)


class I18NModel(Model):
    __metaclass__ = I18NBase

    class Meta:
        abstract = True

Comments

izibi (on July 5, 2008):

I would import the settings like this:

from django.conf import settings

#

foxbunny (on July 5, 2008):

Heh, and there also a typo there. Thanks. I'll fix that.

#

willhardy (on July 7, 2008):

Why not use model inheritance to do the same thing?

class Article(models.Model):
    author = models.CharField(max_length = 40)

class TranslatedArticle(Article):
    language = models.CharField(max_length = 5, choices=settings.LANGUAGES)
    title = models.CharField(max_length = 120)
    body = models.TextField()

If you really wanted, you could create a mixin to keep things cleaner, and automatically add the language field eg:

class Article(models.Model):
    author = models.CharField(max_length = 40)

class TranslatedArticle(Article, TranslationModel):
    title = models.CharField(max_length = 120)
    body = models.TextField()

But I like to keep things explicit :-)

#

foxbunny (on July 7, 2008):

@willhardy

It will not work. Did you try it? When you inherit another non-abstract model, the record for that (parent) model are duplicated each time you create a new child, effectively beating the purpose of separating translated and non-translated parts.

Unless there is something I've missed while messing with module inheritance, your code will simply duplicate the author field each time you create a new TranslatedArticle entry, which is not what I want.

Basically, the metaclass above does nothing too fancy. It just DRY version of the code I was writing. You can see what I was doing in one of my earlier blog posts.

#

willhardy (on July 8, 2008):

Good call, that's exactly the case. Your approach is now my favourite.

#

foxbunny (on July 9, 2008):

It is now possible to define i18n_common_model attribute in class Meta section. Here's an example:

class Meta: 
    i18n_common_model = "MyCommonModel"

#

foxbunny (on July 9, 2008):

The above makes it a bit more explicit if you want that. I might make it a requirement in future.

#

foxbunny (on July 10, 2008):

I've added

class Meta:
    abstract = True

to the I18NModel. It seems it has to be there or this thing fails when deleting models.

#

mirobe (on August 21, 2008):

Can you please write about usage / querying for values in views/templates ?

Thanks!

#

mirobe (on August 21, 2008):

Btw, found similar project at:

http://code.google.com/p/django-multilingual-model/

#

foxbunny (on August 23, 2008):

I've seen the project you mentioned, but it's a totally different philosophy.

The querying of I18N models is the same as any other model. You just keep in mind that you have a many-to-1 link from the I18N model to the non-I18N models. The I18N model has a field called i18n_common, which you use to find the non-translated (non-translatable) fields (see line 23). The language of the I18N model is defined by the lang field (line 24).

What this code does is basically just add the above two fields into the I18N model based on the name of the model. Nothing more, and nothing less. The rest is up to you. You do with them what you'd do with related models.

#

foxbunny (on August 23, 2008):

Oh, and btw, this has nothing to do with the Admin app. The admin app will treat the two models just like any two models. If someone's interested in writing the admin part so that admin will use the two models as one, that's cool, but I don't have the skills yet.

#

(Forgotten your password?)

You may use Markdown syntax here, but raw HTML will be removed.