__title__ = 'slim.models.fields'
__version__ = '0.7'
__build__ = 0x000007
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__all__ = ('LanguageField', 'SimpleLanguageField')
from six import PY2
from django.db import models
from django.core import exceptions
from django.utils.translation import ugettext_lazy as _
from slim import get_languages, default_language, get_languages_keys
from slim.settings import ENABLE_MONKEY_PATCHING
from slim.monkey_patches import monkeypatch_method, monkeypatch_property
from slim.helpers import admin_change_url, admin_add_url
[docs]class LanguageField(models.CharField):
"""
LanguageField model. Stores language string in a ``CharField`` field.
Using `contrib_to_class` melthod adds `translation_of` field, which is simply a ``ForeignKey``
to the same class.
"""
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
"""
Create new field. Argument ``populate`` will be sent as-is to the form field.
"""
defaults = {
'verbose_name': _('Language'),
'populate': None,
'max_length': 10,
'choices': get_languages(),
'default': default_language
}
defaults.update(kwargs)
self.populate = defaults.pop('populate', None)
super(LanguageField, self).__init__(*args, **defaults)
[docs] def validate(self, value, model_instance):
"""
Validating the field.
We shall make sure that there are double translations for the same language for the same object. That's
why, in case if model is not yet saved (``translated_object`` does not yet have a primary key), we check
if there are already translations of the same object in the language we specify now.
Otherwise, if ``model_instance`` already has a primary key, we anyway try to get a ``translated_object``
and compare it with our ``model_instance``. In case if ``translated_object`` exists and not equal to our
``model_instance`` we raise an error.
NOTE: This has nothing to do with unique fields in the original ``model_instance``. Make sure you have
properly specified all unique attributes with respect to ``LanguageField` of your original
``model_instance`` if you need those records to be unique.
"""
if model_instance.original_translation:
translated_object = model_instance.original_translation.get_translation_for(value)
else:
translated_object = None
if not model_instance.pk:
if translated_object and translated_object.pk:
raise exceptions.ValidationError("Translation in language %s for this object already exists." % value)
else:
if translated_object and translated_object.pk and model_instance != translated_object:
raise exceptions.ValidationError("Translation in language %s for this object already exists." % value)
super(LanguageField, self).validate(value, model_instance)
[docs] def contribute_to_class(self, cls, name):
"""
Language field consists of more than one database record. We have ``lanaguage`` (CharField)
and ``translation_of`` (ForeignKey to ``cls``) in order to identify translated and
primary objects.
We have a set of very useful methods implemented in order to get translations easily.
"""
self.name = name
self.translation_of = models.ForeignKey(cls, blank=True, null=True, verbose_name=_('Translation of'), \
related_name='translations', \
limit_choices_to={'language': default_language}, \
help_text=_('Leave this empty for entries in the primary language.'))
cls.add_to_class('translation_of', self.translation_of)
super(LanguageField, self).contribute_to_class(cls, name)
if ENABLE_MONKEY_PATCHING:
@monkeypatch_property(cls)
def is_multilingual(self):
"""
Simple flat to use on objects to find our wheither they are multilinugal
or not
:return bool: Always returns boolean True
"""
return True
@monkeypatch_method(cls)
def get_redirect_to_target(self, request):
"""
Find an acceptable redirect target. If this is a local link, then try
to find the page this redirect references and translate it according
to the user's language. This way, one can easily implement a localized
"/"-url to welcome page redirection.
"""
target = self.redirect_to
if target and target.find('//') == -1: # Not an offsite link http://bla/blubb
try:
page = cls.objects.page_for_path(target)
page = page.get_translation(getattr(request, 'LANGUAGE_CODE', None))
target = page.get_absolute_url()
except cls.DoesNotExist:
pass
return target
@monkeypatch_method(cls)
def available_translations(self):
"""
Returns available translations.
:return interable: At this moment a list of objects.
"""
if not self.id: # New, unsaved pages have no translations
return []
if is_primary_language(self.language):
return self.translations.all()
elif self.translation_of:
return [self.translation_of] + list(self.translation_of.translations.exclude(
language=self.language))
else:
return []
@monkeypatch_method(cls)
def get_original_translation(self, *args, **kwargs):
"""
Gets original translation of current object.
:return obj: Object of the same class as the one queried.
"""
if is_primary_language(self.language):
return self
return self.translation_of
@monkeypatch_method(cls)
def translation_admin(self, *args, **kwargs):
"""
Gets a HTML with URL to the original translation of available. For admin use.
:return str:
"""
if self.translation_of:
if PY2:
url_title = unicode(self.translation_of)
else:
url_title = self.translation_of
return admin_change_url(
self._meta.app_label,
self._meta.module_name,
self.translation_of.id,
url_title = url_title
)
return ''
translation_admin.allow_tags = True
translation_admin.short_description = _('Translation of')
@monkeypatch_method(cls)
def _available_translations_admin(self, include_self=True):
"""
Gets a HTML with all available translation URLs for current object if available. For admin use.
:return str:
"""
try:
original_translation = self.original_translation
available_translations = list(self.available_translations())
languages_keys = get_languages_keys()
languages = dict(get_languages())
if include_self:
available_translations.append(self)
output = []
# Processing all available translations. Adding edit links.
if available_translations:
for translation in available_translations:
if PY2:
url_title = unicode(languages[translation.language])
else:
url_title = languages[translation.language]
output.append(
admin_change_url(
translation._meta.app_label,
translation._meta.module_name,
translation.id,
url_title = url_title
)
)
languages_keys.remove(translation.language)
if self.pk and self.language in languages_keys:
languages_keys.remove(self.language)
# For all languages that are still available (original object has no translations for)
for language in languages_keys:
url = admin_add_url(
self._meta.app_label,
self._meta.module_name,
'?translation_of=%s&language=%s' % (str(original_translation.id), language)
)
if PY2:
name = unicode(languages[language])
else:
name = languages[language]
output.append(u'<a href="%(url)s" style="color:#baa">%(name)s</a>' % {'url': url, 'name': name})
return ' | '.join(output)
except Exception as e:
return ''
@monkeypatch_method(cls)
def available_translations_admin(self, *args, **kwargs):
"""
Gets a HTML with all available translation URLs for current object if available. For admin use.
:return str:
"""
return self._available_translations_admin(include_self=True, *args, **kwargs)
available_translations_admin.allow_tags = True
available_translations_admin.short_description = _('Translations')
@monkeypatch_method(cls)
def available_translations_exclude_current_admin(self, *args, **kwargs):
"""
Same as `available_translations_admin` but does not include itself to the list.
:return str:
"""
return self._available_translations_admin(include_self=False, *args, **kwargs)
available_translations_exclude_current_admin.allow_tags = True
available_translations_exclude_current_admin.short_description = _('Translations')
@monkeypatch_property(cls)
def original_translation(self):
"""
Property for ``get_original_translation`` method.
:return obj: Object of the same class as the one queried.
"""
return self.get_original_translation()
@monkeypatch_method(cls)
def get_translation_for(self, language):
"""
Get translation article in given language.
:param str language: Which shall be one of the languages specified in ``LANGUAGES``
in `settings.py`.
:return obj: Either object of the same class as or None if no translations are
available for the given ``language``.
"""
if not language in get_languages_keys():
return None
if str(self.language) == str(language):
return self
if str(self.original_translation.language) == str(language):
return self.original_translation
try:
return self.original_translation.translations.get(language=language)
except Exception as e:
return None
[docs]class SimpleLanguageField(models.CharField):
"""
SimpleLanguageField model. Stores language string in a ``CharField`` field.
"""
def __init__(self, *args, **kwargs):
"""
Create new field. Argument ``populate`` will be sent as-is to the form field.
"""
defaults = {
'verbose_name': _('Language'),
'populate': None,
'max_length': 10,
'choices': get_languages(),
'default': default_language
}
defaults.update(kwargs)
self.populate = defaults.pop('populate', None)
super(SimpleLanguageField, self).__init__(*args, **defaults)
try:
from south.modelsinspector import add_introspection_rules
add_introspection_rules(
rules=[((LanguageField,), [], {'populate': ('populate', {'default':None}),})], \
patterns=['slim.models\.fields\.LanguageField', 'slim.models\.fields\.SimpleLanguageField']
)
except ImportError:
pass