diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index c5157dc1fb..8070457088 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1247,17 +1247,6 @@ class ManyToManyField(RelatedField): id='fields.W341', ) ) - if (self.remote_field.limit_choices_to and self.remote_field.through and - not self.remote_field.through._meta.auto_created): - warnings.append( - checks.Warning( - 'limit_choices_to has no effect on ManyToManyField ' - 'with a through model.', - obj=self, - id='fields.W343', - ) - ) - if self.remote_field.symmetrical and self._related_name: warnings.append( checks.Warning( diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index 2ea2fd0df2..c65cb4b6ce 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -310,7 +310,7 @@ Related fields * **fields.W342**: Setting ``unique=True`` on a ``ForeignKey`` has the same effect as using a ``OneToOneField``. * **fields.W343**: ``limit_choices_to`` has no effect on ``ManyToManyField`` - with a ``through`` model. + with a ``through`` model. *This check appeared before Django 4.0.* * **fields.W344**: The field's intermediary table ```` clashes with the table name of ````/``.``. * **fields.W345**: ``related_name`` has no effect on ``ManyToManyField`` with a diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 1a3558ac17..e03b8fa82a 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1810,10 +1810,6 @@ that control how the relationship functions. Same as :attr:`ForeignKey.limit_choices_to`. - ``limit_choices_to`` has no effect when used on a ``ManyToManyField`` with a - custom intermediate table specified using the - :attr:`~ManyToManyField.through` parameter. - .. attribute:: ManyToManyField.symmetrical Only used in the definition of ManyToManyFields on self. Consider the diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py index 6fef14c335..6e885f1705 100644 --- a/tests/invalid_models_tests/test_relative_fields.py +++ b/tests/invalid_models_tests/test_relative_fields.py @@ -81,32 +81,12 @@ class RelativeFieldTests(SimpleTestCase): field = Model._meta.get_field('m2m') self.assertEqual(field.check(from_model=Model), []) - def test_many_to_many_with_limit_choices_auto_created_no_warning(self): - class Model(models.Model): - name = models.CharField(max_length=20) - - class ModelM2M(models.Model): - m2m = models.ManyToManyField(Model, limit_choices_to={'name': 'test_name'}) - - self.assertEqual(ModelM2M.check(), []) - def test_many_to_many_with_useless_options(self): class Model(models.Model): name = models.CharField(max_length=20) class ModelM2M(models.Model): - m2m = models.ManyToManyField( - Model, - null=True, - validators=[lambda x: x], - limit_choices_to={'name': 'test_name'}, - through='ThroughModel', - through_fields=('modelm2m', 'model'), - ) - - class ThroughModel(models.Model): - model = models.ForeignKey('Model', models.CASCADE) - modelm2m = models.ForeignKey('ModelM2M', models.CASCADE) + m2m = models.ManyToManyField(Model, null=True, validators=[lambda x: x]) field = ModelM2M._meta.get_field('m2m') self.assertEqual(ModelM2M.check(), [ @@ -120,12 +100,6 @@ class RelativeFieldTests(SimpleTestCase): obj=field, id='fields.W341', ), - DjangoWarning( - 'limit_choices_to has no effect on ManyToManyField ' - 'with a through model.', - obj=field, - id='fields.W343', - ), ]) def test_many_to_many_with_useless_related_name(self): diff --git a/tests/model_forms/models.py b/tests/model_forms/models.py index 4e9ed2b1e4..c0b3a64148 100644 --- a/tests/model_forms/models.py +++ b/tests/model_forms/models.py @@ -480,3 +480,20 @@ class NullableUniqueCharFieldModel(models.Model): email = models.EmailField(blank=True, null=True) slug = models.SlugField(blank=True, null=True) url = models.URLField(blank=True, null=True) + + +class Number(models.Model): + value = models.IntegerField() + + +class NumbersToDice(models.Model): + number = models.ForeignKey('Number', on_delete=models.CASCADE) + die = models.ForeignKey('Dice', on_delete=models.CASCADE) + + +class Dice(models.Model): + numbers = models.ManyToManyField( + Number, + through=NumbersToDice, + limit_choices_to=models.Q(value__gte=1), + ) diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index 9931fa50e3..7907aa1c3d 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -21,10 +21,10 @@ from django.test.utils import isolate_apps from .models import ( Article, ArticleStatus, Author, Author1, Award, BetterWriter, BigInt, Book, Category, Character, Colour, ColourfulItem, CustomErrorMessage, CustomFF, - CustomFieldForExclusionModel, DateTimePost, DerivedBook, DerivedPost, + CustomFieldForExclusionModel, DateTimePost, DerivedBook, DerivedPost, Dice, Document, ExplicitPK, FilePathModel, FlexibleDatePost, Homepage, ImprovedArticle, ImprovedArticleWithParentLink, Inventory, - NullableUniqueCharFieldModel, Person, Photo, Post, Price, Product, + NullableUniqueCharFieldModel, Number, Person, Photo, Post, Price, Product, Publication, PublicationDefaults, StrictAssignmentAll, StrictAssignmentFieldSpecific, Student, StumpJoke, TextFile, Triple, Writer, WriterProfile, test_images, @@ -2896,6 +2896,19 @@ class LimitChoicesToTests(TestCase): [self.marley, self.threepwood], ) + def test_limit_choices_to_m2m_through(self): + class DiceForm(forms.ModelForm): + class Meta: + model = Dice + fields = ['numbers'] + + Number.objects.create(value=0) + n1 = Number.objects.create(value=1) + n2 = Number.objects.create(value=2) + + form = DiceForm() + self.assertCountEqual(form.fields['numbers'].queryset, [n1, n2]) + class FormFieldCallbackTests(SimpleTestCase):