Commit b2b33677 authored by Luuk Scholten's avatar Luuk Scholten
Browse files

Merge branch 'translatemeta-verbosename-slugs' into 'master'

Extend TranslateMeta for model admin

This makes it possible to use MultilingualField in`fields`, `fieldsets` and `prepopulated_fields`.

This also fixes a minor bug that occured when no `verbose_name` was specified (introduced in 1d30bf43)

See merge request !27
parents 0422f974 bf17dc7d
......@@ -47,6 +47,18 @@ class TestTranslateMeta(TestCase):
self.assertIn('Text', fr)
self.assertEqual(len({nl, en, fr}), 3)
def test_no_verbose_name(self):
class TestItem3b(models.Model, metaclass=ModelTranslateMeta):
text = MultilingualField(models.TextField)
nl = TestItem3b._meta.get_field('text_nl').verbose_name
en = TestItem3b._meta.get_field('text_en').verbose_name
fr = TestItem3b._meta.get_field('text_fr').verbose_name
self.assertEqual('text (NL)', nl)
self.assertEqual('text (EN)', en)
self.assertEqual('text (FR)', fr)
self.assertEqual(len({nl, en, fr}), 3)
def test_other_kwargs(self):
class TestItem4(models.Model, metaclass=ModelTranslateMeta):
text = MultilingualField(models.CharField, 'Text', max_length=100)
......
from django.contrib import admin
from django.db import models
from django.db.models.fields.related import RelatedField
from django.conf import settings
......@@ -20,6 +21,15 @@ See the following usage example;
name = MultilingualField(models.CharField, max_length=100)
description = MultilingualField(models.TextField)
In order to use the fields in ModelAdmin configuration (such as in the
fields, fieldsets or prepopulated_fields attributes), subclass the Admin object
from TranslatedModelAdmin instead;
from utils.translation import TranslatedModelAdmin
class SomeItemAdmin(TranslatedModelAdmin):
fields = (name, description)
"""
......@@ -51,28 +61,75 @@ def _i18n_attr_accessor(attr):
class ModelTranslateMeta(models.base.ModelBase):
def __new__(cls, name, bases, dct):
field_i18n = {'default': {}, 'fields': {}}
for attr, field in list(dct.items()):
if isinstance(field, MultilingualField):
# ForeignKey, OneToOneField and ManyToManyField do not have
# a verbose name as first positional argument.
# But those are not translatable (see above).
if len(field.args) > 0:
verbose_base = ('args', field.args[0])
if not isinstance(field, MultilingualField):
continue
# ForeignKey, OneToOneField and ManyToManyField do not have
# a verbose name as first positional argument.
# But those are not translatable (see above).
if len(field.args) > 0:
verbose_base = ('args', field.args[0])
else:
verbose_base = ('kwargs', field.kwargs.get('verbose_name',
attr))
fields = []
for lang in settings.LANGUAGES:
attr_i18n = I18N_FIELD_FORMAT.format(attr, lang[0])
verbose_name = '{} ({})'.format(verbose_base[1],
lang[0].upper())
if verbose_base[0] == 'args':
field.args = (verbose_name,) + field.args[1:]
else:
verbose_base = ('kwargs', field.kwargs.get('verbose_name',
None))
for lang in settings.LANGUAGES:
attr_i18n = I18N_FIELD_FORMAT.format(attr, lang[0])
if verbose_base is not None:
verbose_name = '{} ({})'.format(verbose_base[1],
lang[0].upper())
if verbose_base[0] == 'args':
field.args = (verbose_name,) + field.args[1:]
else:
field.kwargs['verbose_name'] = verbose_name
if attr_i18n in dct:
raise FieldError("Explicit field {} is shadowed "
"by TranslateMeta.".format(attr_i18n))
dct[attr_i18n] = field.cls(*field.args, **field.kwargs)
dct[attr] = property(_i18n_attr_accessor(attr))
return super(ModelTranslateMeta, cls).__new__(cls, name, bases, dct)
field.kwargs['verbose_name'] = verbose_name
if attr_i18n in dct:
raise FieldError("Explicit field {} is shadowed "
"by TranslateMeta.".format(attr_i18n))
dct[attr_i18n] = field.cls(*field.args, **field.kwargs)
fields.append(attr_i18n)
dct[attr] = property(_i18n_attr_accessor(attr))
default = I18N_FIELD_FORMAT.format(attr, settings.LANGUAGE_CODE)
if default not in dct:
raise ImproperlyConfigured("LANGUAGE_CODE not in LANGUAGES.")
field_i18n['default'][attr] = default
field_i18n['fields'][attr] = fields
model = super(ModelTranslateMeta, cls).__new__(cls, name, bases, dct)
if hasattr(model._meta, '_field_i18n'):
raise FieldError("TranslateMeta map already exists!")
model._meta._field_i18n = field_i18n
return model
class TranslatedModelAdmin(admin.ModelAdmin):
"""This class should be used when the ModelAdmin is used with a
translated model and one refers to such a field in the `fields`
or `fieldsets` attributes, or in `prepopulated_fields`.
This works because admin.ModelAdmin has an empty metaclass; we can hook
in to __init__ and modify the attributes when model is known."""
def __init__(self, model, admin_site):
for key, fields in list(type(self).prepopulated_fields.items()):
# Replace translated fields in `fields`
fields = tuple(model._meta._field_i18n['default'].get(field, field)
for field in fields)
# ..and in `key`
del type(self).prepopulated_fields[key]
key = model._meta._field_i18n['default'].get(key, key)
type(self).prepopulated_fields[key] = fields
def trans_fields(fields):
if fields is None:
return None
fields = [model._meta._field_i18n['fields']
.get(field, (field, )) for field in fields]
return tuple(field for fieldset in fields for field in fieldset)
# In fields, we replace a translated field by all resulting fields.
type(self).fields = trans_fields(type(self).fields)
type(self).exclude = trans_fields(type(self).exclude)
if type(self).fieldsets is not None:
for fieldset in type(self).fieldsets:
fieldset[1]['fields'] = trans_fields(fieldset[1]['fields'])
super(TranslatedModelAdmin, self).__init__(model, admin_site)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment