diff --git a/modeltranslation/fields.py b/modeltranslation/fields.py index f160217..9d0fc53 100644 --- a/modeltranslation/fields.py +++ b/modeltranslation/fields.py @@ -25,6 +25,7 @@ SUPPORTED_FIELDS = ( fields.TimeField, fields.files.FileField, fields.files.ImageField, + fields.related.ForeignKey, ) try: SUPPORTED_FIELDS += (fields.GenericIPAddressField,) # Django 1.4+ only @@ -50,6 +51,8 @@ def create_translation_field(model, field_name, lang): if not (isinstance(field, SUPPORTED_FIELDS) or cls_name in mt_settings.CUSTOM_FIELDS): raise ImproperlyConfigured( '%s is not supported by modeltranslation.' % cls_name) + if isinstance(field, fields.related.ForeignKey) and field.rel.related_name != '+': + raise ImproperlyConfigured('Translated ForeignKey fields must use related_name="+"') translation_class = field_factory(field.__class__) return translation_class(translated_field=field, language=lang) @@ -127,8 +130,10 @@ class TranslationField(object): def get_attname_column(self): attname = self.get_attname() - column = build_localized_fieldname( - self.translated_field.db_column or self.translated_field.name, self.language) or attname + if self.translated_field.db_column: + column = build_localized_fieldname(self.translated_field.db_column) + else: + column = attname return attname, column def south_field_triple(self): @@ -137,8 +142,12 @@ class TranslationField(object): """ # We'll just introspect the _actual_ field. from south.modelsinspector import introspector - field_class = '%s.%s' % (self.translated_field.__class__.__module__, - self.translated_field.__class__.__name__) + try: + # Check if the field provides its own 'field_class': + field_class = self.translated_field.south_field_triple()[0] + except AttributeError: + field_class = '%s.%s' % (self.translated_field.__class__.__module__, + self.translated_field.__class__.__name__) args, kwargs = introspector(self) # That's our definition! return (field_class, args, kwargs) diff --git a/modeltranslation/tests/__init__.py b/modeltranslation/tests/__init__.py index 8e8d777..f933837 100644 --- a/modeltranslation/tests/__init__.py +++ b/modeltranslation/tests/__init__.py @@ -43,8 +43,8 @@ except ImportError: # so we'll just pass in None. request = None -# How much models are registered for tests. -TEST_MODELS = 22 +# How many models are registered for tests. +TEST_MODELS = 23 class reload_override_settings(override_settings): @@ -619,6 +619,64 @@ class FileFieldsTest(ModeltranslationTestBase): inst.image_de.delete() +class ForeignKeyFieldsTest(ModeltranslationTestBase): + + def test_translated_models(self): + field_names = dir(models.ForeignKeyModel()) + self.failUnless('id' in field_names) + for f in ('test', 'test_de', 'test_en', 'optional', 'optional_en', 'optional_de'): + self.failUnless(f in field_names) + self.failUnless('%s_id' % f in field_names) + + def test_db_column_names(self): + meta = models.ForeignKeyModel._meta + + # Make sure the correct database columns always get used: + attname, col = meta.get_field('test').get_attname_column() + self.failUnlessEqual(attname, 'test_id') + self.failUnlessEqual(attname, col) + + attname, col = meta.get_field('test_en').get_attname_column() + self.failUnlessEqual(attname, 'test_en_id') + self.failUnlessEqual(attname, col) + + attname, col = meta.get_field('test_de').get_attname_column() + self.failUnlessEqual(attname, 'test_de_id') + self.failUnlessEqual(attname, col) + + def test_translated_models_instance(self): + test_inst1 = models.TestModel(title_en='title1_en', title_de='title1_de') + test_inst1.save() + test_inst2 = models.TestModel(title_en='title2_en', title_de='title2_de') + test_inst2.save() + inst = models.ForeignKeyModel() + + trans_real.activate("de") + inst.test = test_inst1 + inst.optional = None + + trans_real.activate("en") + inst.optional = test_inst2 + inst.save() + + trans_real.activate("de") + self.failUnlessEqual(inst.test.title, 'title1_de') + self.failUnlessEqual(inst.test_de_id, test_inst1.pk) + self.failUnlessEqual(inst.test_de.title, 'title1_de') + self.failUnlessEqual(inst.optional, None) + + # Test fallbacks: + trans_real.activate("en") + with default_fallback(): + self.failUnlessEqual(inst.test.pk, test_inst1.pk) + self.failUnlessEqual(inst.test.title, 'title1_en') + + # Test English: + self.failUnlessEqual(inst.optional.title, 'title2_en') + self.failUnlessEqual(inst.optional_en_id, test_inst2.pk) + self.failUnlessEqual(inst.optional_en.title, 'title2_en') + + class OtherFieldsTest(ModeltranslationTestBase): def test_translated_models(self): inst = models.OtherFieldsModel.objects.create() diff --git a/modeltranslation/tests/models.py b/modeltranslation/tests/models.py index 8db3dd6..d09912f 100644 --- a/modeltranslation/tests/models.py +++ b/modeltranslation/tests/models.py @@ -34,6 +34,11 @@ class FileFieldsModel(models.Model): file = models.FileField(upload_to='modeltranslation_tests', null=True, blank=True) image = models.ImageField(upload_to='modeltranslation_tests', null=True, blank=True) +########## Foreign Key fields testing + +class ForeignKeyModel(models.Model): + test = models.ForeignKey(TestModel, null=True, related_name="+") + optional = models.ForeignKey(TestModel, blank=True, null=True, related_name="+") ########## Custom fields testing diff --git a/modeltranslation/tests/translation.py b/modeltranslation/tests/translation.py index 397ac29..433f4ba 100644 --- a/modeltranslation/tests/translation.py +++ b/modeltranslation/tests/translation.py @@ -3,10 +3,10 @@ from django.utils.translation import ugettext_lazy from modeltranslation.translator import translator, TranslationOptions from modeltranslation.tests.models import ( - TestModel, FallbackModel, FallbackModel2, FileFieldsModel, OtherFieldsModel, DescriptorModel, - AbstractModelA, AbstractModelB, Slugged, MetaData, Displayable, Page, RichText, RichTextPage, - MultitableModelA, MultitableModelB, MultitableModelC, ManagerTestModel, CustomManagerTestModel, - CustomManager2TestModel, GroupFieldsetsModel, NameModel) + TestModel, FallbackModel, FallbackModel2, FileFieldsModel, ForeignKeyModel, OtherFieldsModel, + DescriptorModel, AbstractModelA, AbstractModelB, Slugged, MetaData, Displayable, Page, + RichText, RichTextPage, MultitableModelA, MultitableModelB, MultitableModelC, ManagerTestModel, + CustomManagerTestModel, CustomManager2TestModel, GroupFieldsetsModel, NameModel) class TestTranslationOptions(TranslationOptions): @@ -35,6 +35,13 @@ class FileFieldsModelTranslationOptions(TranslationOptions): translator.register(FileFieldsModel, FileFieldsModelTranslationOptions) +########## Foreign Key fields testing + +class ForeignKeyModelTranslationOptions(TranslationOptions): + fields = ('test', 'optional',) +translator.register(ForeignKeyModel, ForeignKeyModelTranslationOptions) + + ########## Custom fields testing class OtherFieldsModelTranslationOptions(TranslationOptions):