Login

i18n base model for translatable content

Author:
foxbunny
Posted:
July 4, 2008
Language:
Python
Version:
.96
Score:
2 (after 2 ratings)

Together with my mentor, Dusty Phillips, I have developed a simple class that dynamically adds two fields to its subclasses.

This is useful in cases when a single piece of content is divided into translatable and non-translatable fields, connected by a 1-to-many relationship.

Update 2009/03/30

Since its inception, this snippet has grown into a significantly more powerful solution for translatable content (I use it myself with great joy :). The project is now hosted on github:

project page

Update 2008/07/09

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

class Meta:
    i18n_common_model = "MyCommonModel"

As you can see, it has to be a string, not the real class, and it is case-sensitive.

Example

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

    class Admin:
        pass

class ArticleI18N(I18NModel):
    title = models.CharField(max_length = 120)
    body = models.TextField()

    class Admin:
        pass

    # optionally, you can specify the base class
    # if it doesn't follow the naming convention:
    #
    # class Meta:
    #     i18m_common_model = "Article"

When the ArticleI18N class is created, it automatically gains two new fields. lang field is a CharField with choices limited to either settings.LANGUAGES or django.conf.global_settings.LANGUAGES. The other field is i18n_common field which is a ForeignKey to Article model.

The conventions

  • call the translation model SomeBaseModelI18N, and the non-translation model SomeBaseModel (i.e., the translation model is called basename+"I18N")

  • the first convention can be overriden by specifying the base model name using the i18n_common_model attribute in Meta section of the I18N model

  • I18N model is a subclass of I18NModel class

Original blog post

http://blog.papa-studio.com/2008/07/04/metaclasses-and-translations/

 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

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 2 months ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 2 months, 1 week ago
  3. Serializer factory with Django Rest Framework by julio 9 months, 1 week ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 9 months, 4 weeks ago
  5. Help text hyperlinks by sa2812 10 months, 3 weeks ago

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

#

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!

#

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.

#

Please login first before commenting.