import inspect

from django.contrib import admin
from django.conf import settings
from django.db import models
from django.forms import ModelForm
from django.utils import translation
from django.utils.translation import ugettext_lazy as _


class TranslationModelForm(ModelForm):
  """
    Form used by the StackedInline object created at run-time.
  """
  def __init__(self, *args, **kwargs):
    super(TranslationModelForm, self).__init__(*args, **kwargs)

    # This little flag makes sure that
    # the user has to fill the form.
    self.empty_permitted = False


class LocaleManager(models.Manager):
  """
    Manager used to
  """
  use_for_related_fields = True

  def __init__(self, name=None, table=None, fieldnames=None):
    super(LocaleManager, self).__init__()
    self.name = name
    self.table = table
    self.fieldnames = fieldnames

  def _locale(self):
    query = super(LocaleManager, self).get_query_set()

    if self.table and self.fieldnames:
      lang = translation.get_language()

      tablei18n = "%s_i18n" % self.table

      # Extra fields to select.
      extrafield = {}
      for name in self.fieldnames:
        fieldname = "%s__%s" % (self.name, name)
        extrafield[name] = '%s.%s' % (tablei18n, name)

      kwargs = {"%s_i18n__lang" % (self.name.lower(), ):lang}
      query = query.filter(**kwargs).extra(select=extrafield,
                                           tables=[tablei18n,])
    return query

  def get_query_set(self):
    return self._locale()


class Modeli18nMeta(models.Model.__metaclass__):
  """
    Metaclass used to create a sub-Model attribute
    to the current class that will hold the
    translated fields.
  """
  def __new__(cls, name, bases, attrs):

    # Create a new Model class reference
    # to hold the parent Model translation.
    #
    # Add this Model class as an attribute
    # of the parent class.
    if name != "Modeli18n":
      if "locale" in attrs:
        # Language utilities.
        lenlang = len(settings.LANGUAGES)
        languages = []
        for lan, value in settings.LANGUAGES:
          languages.append([lan, _(value)])

        ###############################
        # Create a new Model subclass #
        # by copying the attributes   #
        # from the locale subclass.   #
        ###############################

        # Get the module name.
        module = attrs['__module__']

        # Set the sub-Model attributes.
        attributes = {'__module__': module}

        # Get locale class definition object.
        locale = attrs["locale"]

        # Get all defined attributes.
        members = inspect.getmembers(locale)

        # Get attributes and record their names.
        locale_attrs = []
        for obj_name, obj in members:
          if "Field" in str(obj):
            obj.null = True
            obj.blank = True
            attributes[obj_name] = obj
            locale_attrs.append(obj_name)

        # Set the table name.
        table = None
        if "Meta" in attrs:
          table = getattr(attrs['Meta'], 'db_table', None)
        if not table:
          table = "%s_%s" % (module.split('.')[-2], name.lower())

        # Set the sub-Model Meta class.
        newtable = "%s_i18n" % table
        newname = "%s_i18n" % name
        values = {'db_table': newtable}
        values['unique_together'] = ("parent", "lang")
        values['verbose_name'] = _("translation")
        values['verbose_name_plural'] = _("translations")
        meta = type('Meta', (object,), values)
        attributes['Meta'] = meta

        # Parent's foreign key.
        attributes["parent"] = models.ForeignKey("%s" % (name), null=True, blank=False)

        # Language field.
        attributes['lang'] = models.CharField(name="lang", verbose_name=_('language'),
                                              help_text=_("The language is required even if the fields are empty."),
                                              max_length=lenlang,
                                              choices=languages)
        # Friendly verbose.
        attributes['__unicode__'] = lambda x : x.lang

        # Create the sub-Model...
        attrs["locale"] = type(newname, (models.Model,), attributes)
        # and add it to its parent Model as "locale" attribute.
        attrs["objects"] = LocaleManager(name, table, locale_attrs)

        #
        # Add a StackedInline(admin.StackedInline) class
        # for validating and displaying as convenience.
        #
        del attributes
        attributes = {'max_num': lenlang,
                      'extra': lenlang,
                      'model': attrs["locale"],
                      'form': TranslationModelForm,
                      'can_delete': False,}
        inlines = type('StackedInline', (admin.StackedInline, ), attributes)
        attrs["StackedInline"] = inlines

    return super(Modeli18nMeta, cls).__new__(cls, name, bases, attrs)


class Modeli18n(models.Model):
  """
    Abstract class used as base class for all models
    that needs to be translated.

    i.e.: Product(Modeli18n)
  """
  __metaclass__ = Modeli18nMeta

  class Meta:
    abstract = True

  def get_locale(self, lang=None):
    # Return the locale object
    # for the given language.
    obj = None
    if lang:
      try:
        obj = self.locale.objects.get(lang__exact=lang, parent__exact=self.id)[0]
      except self.locale.DoesNotExist:
        try:
          # As a fallback we use the default language.
          obj = self.locale.objects.get(lang__exact=settings.LANGUAGE_CODE, parent__exact=self.id)[0]
        except self.locale.DoesNotExist:
          pass
    return obj

#######################
#                     #
# settings.py         #
#                     #
#######################
# ...

LANGUAGE_CODE = 'en' # Default language used.

ADMIN_LANGUAGE_CODE = LANGUAGE_CODE

LANGUAGES = (
    ('fr', gettext('French')),
    ('en', gettext('English')),
    ('es', gettext('Spanish')),
)

#######################
#                     #
# Model Usage example #
#                     #
# i.e.: models.py     #
#                     #
#######################

import Modeli18n, LocaleManager

class Activity(Modeli18n):
  # A tag nothing special.
  tag = models.CharField(max_length=30, blank=False, null=False, db_index=True)

  # Important! Use this manager to sort correctly by language.
  objects = LocaleManager('Activity')

  class locale:
    # Define all locale fields here!
    # name to be translated
    name = models.CharField(verbose_name=_('name'), max_length=100)

  class Meta:
    # Ordering the class by it's translated name.
    # Here, activity_i18n is the sub-Model 
    # generated by the code.
    ordering = ('activity_i18n__name',)

  def get_name(self):
    try:
      return self.name
    except AttributeError:
      return None

  def __unicode__(self):
    str = self.get_name()
    if not str:
      str = self.tag
    return "%s" % (str)

#######################
#                     #
# Admin Usage example #
#                     #
# i.e.: admin.py      #
#                     #
#######################

from django.contrib import admin
from models import Activity

#
# Activity.StackedInline is available to the
# model as a courtesy and simplicity.
# 
class ActivityAdmin(admin.ModelAdmin):
  inlines = [Activity.StackedInline,]
  list_display = ('get_name',)

admin.site.register(Activity, ActivityAdmin)


#######################
#                     #
# Simple usage        #
#                     #
#######################
from models import Activity

import operator

activity = Activity.objects.all()[0]
print activity.name

# Get a specific locale.
french = activity.get_locale('fr')
if french:
  print french.name
