Merge pull request #431 from Schnouki/fix-m2m-and-inherited-managers

Fix M2M and inherited managers
This commit is contained in:
Dirk Eschler 2017-11-24 14:38:37 +01:00 committed by GitHub
commit 0c50dffefe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 5 deletions

View file

@ -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

View file

@ -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)

View file

@ -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:

View file

@ -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)