diff --git a/modeltranslation/tests/models.py b/modeltranslation/tests/models.py index a965f92..aa281b3 100644 --- a/modeltranslation/tests/models.py +++ b/modeltranslation/tests/models.py @@ -351,3 +351,61 @@ class MultitableConflictModelA(models.Model): class MultitableConflictModelB(MultitableConflictModelA): title = models.CharField(ugettext_lazy('title'), max_length=255) + + +# ######### Complex M2M with abstract classes and custom managers + +class CustomQuerySetX(models.query.QuerySet): + pass + + +class CustomManagerX(models.Manager): + def get_queryset(self): + return CustomQuerySetX(self.model, using=self._db) + get_query_set = get_queryset + + +class AbstractBaseModelX(models.Model): + name = models.CharField(max_length=255) + objects = CustomManagerX() + + class Meta: + abstract = True + + +class AbstractModelX(AbstractBaseModelX): + class Meta: + abstract = True + + +class ModelX(AbstractModelX): + pass + + +class AbstractModelXY(models.Model): + model_x = models.ForeignKey('ModelX') + model_y = models.ForeignKey('ModelY') + + class Meta: + abstract = True + + +class ModelXY(AbstractModelXY): + pass + + +class CustomManagerY(models.Manager): + pass + + +class AbstractModelY(models.Model): + title = models.CharField(max_length=255) + xs = models.ManyToManyField('ModelX', through='ModelXY') + objects = CustomManagerY() + + class Meta: + abstract = True + + +class ModelY(AbstractModelY): + pass diff --git a/modeltranslation/tests/tests.py b/modeltranslation/tests/tests.py index 678d59f..bf2dc62 100644 --- a/modeltranslation/tests/tests.py +++ b/modeltranslation/tests/tests.py @@ -56,7 +56,7 @@ models = translation = None request = None # How many models are registered for tests. -TEST_MODELS = 29 + (1 if MIGRATIONS else 0) +TEST_MODELS = 31 + (1 if MIGRATIONS else 0) class reload_override_settings(override_settings): @@ -3090,3 +3090,31 @@ class TestRequired(ModeltranslationTestBase): self.assertEqual(set(('req_reg_en', 'req_en_reg', 'req_en_reg_en')), error_fields) else: self.fail('ValidationError not raised!') + + +class M2MTest(ModeltranslationTestBase): + def test_m2m(self): + # Create 1 instance of Y, linked to 2 instance of X, with different + # English and German names. + x1 = models.ModelX.objects.create(name_en="foo", name_de="bar") + x2 = models.ModelX.objects.create(name_en="bar", name_de="baz") + y = models.ModelY.objects.create(title='y1') + models.ModelXY.objects.create(model_x=x1, model_y=y) + models.ModelXY.objects.create(model_x=x2, model_y=y) + + with override("en"): + # There's 1 X named "foo" and it's x1 + y_foo = models.ModelY.objects.filter(xs__name="foo") + self.assertEqual(1, y_foo.count()) + + # There's 1 X named "bar" and it's x2 (in English) + y_bar = models.ModelY.objects.filter(xs__name="bar") + self.assertEqual(1, y_bar.count()) + + # But in English, there's no X named "baz" + y_baz = models.ModelY.objects.filter(xs__name="baz") + self.assertEqual(0, y_baz.count()) + + # Again: 1 X named "bar" (but through the M2M field) + x_bar = y.xs.filter(name="bar") + self.assertIn(x2, x_bar) diff --git a/modeltranslation/tests/translation.py b/modeltranslation/tests/translation.py index 9b9919e..7f89475 100644 --- a/modeltranslation/tests/translation.py +++ b/modeltranslation/tests/translation.py @@ -10,7 +10,7 @@ from modeltranslation.tests.models import ( RichText, RichTextPage, MultitableModelA, MultitableModelB, MultitableModelC, ManagerTestModel, CustomManagerTestModel, CustomManager2TestModel, GroupFieldsetsModel, NameModel, ThirdPartyRegisteredModel, ProxyTestModel, UniqueNullableModel, OneToOneFieldModel, - RequiredModel, DecoratedModel) + RequiredModel, DecoratedModel, ModelX, ModelY) class TestTranslationOptions(TranslationOptions): @@ -207,6 +207,18 @@ class DecoratedTranslationOptions(TranslationOptions): fields = ('title',) +# ######### Complex M2M with abstract classes and custom managers + +class ModelXOptions(TranslationOptions): + fields = ('name',) +translator.register(ModelX, ModelXOptions) + + +class ModelYOptions(TranslationOptions): + fields = ('title',) +translator.register(ModelY, ModelYOptions) + + # ######### 3-rd party with custom manager if VERSION >= (1, 8) and "django.contrib.auth" in settings.INSTALLED_APPS: diff --git a/modeltranslation/translator.py b/modeltranslation/translator.py index a773edc..ebe93cc 100644 --- a/modeltranslation/translator.py +++ b/modeltranslation/translator.py @@ -214,9 +214,24 @@ def add_manager(model): manager.__class__ = NewMultilingualManager - managers = (model._meta.local_managers if NEW_MANAGER_API else - (getattr(model, x[1]) for x in - model._meta.concrete_managers + model._meta.abstract_managers)) + if NEW_MANAGER_API: + # Inspired by django.db.models.options.Options.managers (find all + # managers by following the normal Python MRO rules), but keeps the + # original managers instead of making copies. + managers = [] + seen = set() + bases = (b for b in model.mro() if hasattr(b, '_meta')) + for base in bases: + for manager in base._meta.local_managers: + if manager.name in seen: + continue + managers.append(manager) + seen.add(manager.name) + + else: + managers = ((getattr(model, x[1]) for x in + model._meta.concrete_managers + model._meta.abstract_managers)) + for current_manager in managers: prev_class = current_manager.__class__ patch_manager_class(current_manager)