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