Kaydet (Commit) 011abb7d authored tarafından Anubhav Joshi's avatar Anubhav Joshi Kaydeden (comit) Tim Graham

Fixed #19671 -- Added warnings that null and validators are ignored for ManyToManyField.

Thanks Loic Bistuer and Tim Graham for help and review.
üst 3a85aae2
...@@ -8,6 +8,7 @@ certain test -- e.g. being a DateField or ForeignKey. ...@@ -8,6 +8,7 @@ certain test -- e.g. being a DateField or ForeignKey.
import datetime import datetime
from django.db import models from django.db import models
from django.db.models.fields.related import ManyToManyField
from django.core.exceptions import ImproperlyConfigured, ValidationError from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.utils.encoding import smart_text, force_text from django.utils.encoding import smart_text, force_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
...@@ -207,8 +208,8 @@ class RelatedFieldListFilter(FieldListFilter): ...@@ -207,8 +208,8 @@ class RelatedFieldListFilter(FieldListFilter):
'display': val, 'display': val,
} }
if (isinstance(self.field, models.related.RelatedObject) and if (isinstance(self.field, models.related.RelatedObject) and
self.field.field.null or hasattr(self.field, 'rel') and (self.field.field.null or isinstance(self.field.field, ManyToManyField)) or
self.field.null): hasattr(self.field, 'rel') and (self.field.null or isinstance(self.field, ManyToManyField))):
yield { yield {
'selected': bool(self.lookup_val_isnull), 'selected': bool(self.lookup_val_isnull),
'query_string': cl.get_query_string({ 'query_string': cl.get_query_string({
......
...@@ -1899,6 +1899,7 @@ class ManyToManyField(RelatedField): ...@@ -1899,6 +1899,7 @@ class ManyToManyField(RelatedField):
errors = super(ManyToManyField, self).check(**kwargs) errors = super(ManyToManyField, self).check(**kwargs)
errors.extend(self._check_unique(**kwargs)) errors.extend(self._check_unique(**kwargs))
errors.extend(self._check_relationship_model(**kwargs)) errors.extend(self._check_relationship_model(**kwargs))
errors.extend(self._check_ignored_options(**kwargs))
return errors return errors
def _check_unique(self, **kwargs): def _check_unique(self, **kwargs):
...@@ -1913,6 +1914,31 @@ class ManyToManyField(RelatedField): ...@@ -1913,6 +1914,31 @@ class ManyToManyField(RelatedField):
] ]
return [] return []
def _check_ignored_options(self, **kwargs):
warnings = []
if self.null:
warnings.append(
checks.Warning(
'null has no effect on ManyToManyField.',
hint=None,
obj=self,
id='fields.W340',
)
)
if len(self._validators) > 0:
warnings.append(
checks.Warning(
'ManyToManyField does not support validators.',
hint=None,
obj=self,
id='fields.W341',
)
)
return warnings
def _check_relationship_model(self, from_model=None, **kwargs): def _check_relationship_model(self, from_model=None, **kwargs):
if hasattr(self.rel.through, '_meta'): if hasattr(self.rel.through, '_meta'):
qualified_model_name = "%s.%s" % ( qualified_model_name = "%s.%s" % (
......
...@@ -148,6 +148,8 @@ Related Fields ...@@ -148,6 +148,8 @@ Related Fields
* **fields.E338**: The intermediary model ``<through model>`` has no field * **fields.E338**: The intermediary model ``<through model>`` has no field
``<field name>``. ``<field name>``.
* **fields.E339**: ``<model>.<field name>`` is not a foreign key to ``<model>``. * **fields.E339**: ``<model>.<field name>`` is not a foreign key to ``<model>``.
* **fields.W340**: ``null`` has no effect on ``ManyToManyField``.
* **fields.W341**: ``ManyToManyField`` does not support ``validators``.
Signals Signals
~~~~~~~ ~~~~~~~
......
...@@ -1448,6 +1448,10 @@ that control how the relationship functions. ...@@ -1448,6 +1448,10 @@ that control how the relationship functions.
If in doubt, leave it to its default of ``True``. If in doubt, leave it to its default of ``True``.
:class:`ManyToManyField` does not support :attr:`~Field.validators`.
:attr:`~Field.null` has no effect since there is no way to require a
relationship at the database level.
.. _ref-onetoone: .. _ref-onetoone:
......
...@@ -10,7 +10,7 @@ class Book(models.Model): ...@@ -10,7 +10,7 @@ class Book(models.Model):
title = models.CharField(max_length=50) title = models.CharField(max_length=50)
year = models.PositiveIntegerField(null=True, blank=True) year = models.PositiveIntegerField(null=True, blank=True)
author = models.ForeignKey(User, verbose_name="Verbose Author", related_name='books_authored', blank=True, null=True) author = models.ForeignKey(User, verbose_name="Verbose Author", related_name='books_authored', blank=True, null=True)
contributors = models.ManyToManyField(User, verbose_name="Verbose Contributors", related_name='books_contributed', blank=True, null=True) contributors = models.ManyToManyField(User, verbose_name="Verbose Contributors", related_name='books_contributed', blank=True)
is_best_seller = models.NullBooleanField(default=0) is_best_seller = models.NullBooleanField(default=0)
date_registered = models.DateField(null=True) date_registered = models.DateField(null=True)
no = models.IntegerField(verbose_name='number', blank=True, null=True) # This field is intentionally 2 characters long. See #16080. no = models.IntegerField(verbose_name='number', blank=True, null=True) # This field is intentionally 2 characters long. See #16080.
......
...@@ -63,7 +63,7 @@ class Inventory(models.Model): ...@@ -63,7 +63,7 @@ class Inventory(models.Model):
class Event(models.Model): class Event(models.Model):
main_band = models.ForeignKey(Band, limit_choices_to=models.Q(pk__gt=0), related_name='events_main_band_at') main_band = models.ForeignKey(Band, limit_choices_to=models.Q(pk__gt=0), related_name='events_main_band_at')
supporting_bands = models.ManyToManyField(Band, null=True, blank=True, related_name='events_supporting_band_at') supporting_bands = models.ManyToManyField(Band, blank=True, related_name='events_supporting_band_at')
start_date = models.DateField(blank=True, null=True) start_date = models.DateField(blank=True, null=True)
start_time = models.TimeField(blank=True, null=True) start_time = models.TimeField(blank=True, null=True)
description = models.TextField(blank=True) description = models.TextField(blank=True)
......
...@@ -92,7 +92,7 @@ class ChoiceFieldModel(models.Model): ...@@ -92,7 +92,7 @@ class ChoiceFieldModel(models.Model):
class OptionalMultiChoiceModel(models.Model): class OptionalMultiChoiceModel(models.Model):
multi_choice = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='not_relevant', multi_choice = models.ManyToManyField(ChoiceOptionModel, blank=False, related_name='not_relevant',
default=lambda: ChoiceOptionModel.objects.filter(name='default')) default=lambda: ChoiceOptionModel.objects.filter(name='default'))
multi_choice_optional = models.ManyToManyField(ChoiceOptionModel, blank=True, null=True, multi_choice_optional = models.ManyToManyField(ChoiceOptionModel, blank=True,
related_name='not_relevant2') related_name='not_relevant2')
......
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.core.checks import Error from django.core.checks import Error, Warning as DjangoWarning
from django.db import models from django.db import models
from django.test.utils import override_settings from django.test.utils import override_settings
from django.test.testcases import skipIfDBFeature from django.test.testcases import skipIfDBFeature
...@@ -60,6 +60,35 @@ class RelativeFieldTests(IsolatedModelsTestCase): ...@@ -60,6 +60,35 @@ class RelativeFieldTests(IsolatedModelsTestCase):
] ]
self.assertEqual(errors, expected) self.assertEqual(errors, expected)
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=[''])
errors = ModelM2M.check()
field = ModelM2M._meta.get_field('m2m')
expected = [
DjangoWarning(
'null has no effect on ManyToManyField.',
hint=None,
obj=field,
id='fields.W340',
)
]
expected.append(
DjangoWarning(
'ManyToManyField does not support validators.',
hint=None,
obj=field,
id='fields.W341',
)
)
self.assertEqual(errors, expected)
def test_ambiguous_relationship_model(self): def test_ambiguous_relationship_model(self):
class Person(models.Model): class Person(models.Model):
......
...@@ -60,7 +60,7 @@ class Line(models.Model): ...@@ -60,7 +60,7 @@ class Line(models.Model):
class Worksheet(models.Model): class Worksheet(models.Model):
id = models.CharField(primary_key=True, max_length=100) id = models.CharField(primary_key=True, max_length=100)
lines = models.ManyToManyField(Line, blank=True, null=True) lines = models.ManyToManyField(Line, blank=True)
# Regression for #11226 -- A model with the same name that another one to # Regression for #11226 -- A model with the same name that another one to
......
...@@ -5,4 +5,4 @@ from django.db import models ...@@ -5,4 +5,4 @@ from django.db import models
class Article(models.Model): class Article(models.Model):
sites = models.ManyToManyField(Site) sites = models.ManyToManyField(Site)
headline = models.CharField(max_length=100) headline = models.CharField(max_length=100)
publications = models.ManyToManyField("model_package.Publication", null=True, blank=True,) publications = models.ManyToManyField("model_package.Publication", blank=True)
...@@ -10,9 +10,7 @@ from .models.article import Article ...@@ -10,9 +10,7 @@ from .models.article import Article
class Advertisement(models.Model): class Advertisement(models.Model):
customer = models.CharField(max_length=100) customer = models.CharField(max_length=100)
publications = models.ManyToManyField( publications = models.ManyToManyField("model_package.Publication", blank=True)
"model_package.Publication", null=True, blank=True
)
class ModelPackageTests(TestCase): class ModelPackageTests(TestCase):
......
...@@ -101,7 +101,7 @@ class Item(models.Model): ...@@ -101,7 +101,7 @@ class Item(models.Model):
name = models.CharField(max_length=10) name = models.CharField(max_length=10)
created = models.DateTimeField() created = models.DateTimeField()
modified = models.DateTimeField(blank=True, null=True) modified = models.DateTimeField(blank=True, null=True)
tags = models.ManyToManyField(Tag, blank=True, null=True) tags = models.ManyToManyField(Tag, blank=True)
creator = models.ForeignKey(Author) creator = models.ForeignKey(Author)
note = models.ForeignKey(Note) note = models.ForeignKey(Note)
......
...@@ -168,7 +168,7 @@ class FKDataNaturalKey(models.Model): ...@@ -168,7 +168,7 @@ class FKDataNaturalKey(models.Model):
class M2MData(models.Model): class M2MData(models.Model):
data = models.ManyToManyField(Anchor, null=True) data = models.ManyToManyField(Anchor)
class O2OData(models.Model): class O2OData(models.Model):
...@@ -181,7 +181,7 @@ class FKSelfData(models.Model): ...@@ -181,7 +181,7 @@ class FKSelfData(models.Model):
class M2MSelfData(models.Model): class M2MSelfData(models.Model):
data = models.ManyToManyField('self', null=True, symmetrical=False) data = models.ManyToManyField('self', symmetrical=False)
class FKDataToField(models.Model): class FKDataToField(models.Model):
...@@ -193,7 +193,7 @@ class FKDataToO2O(models.Model): ...@@ -193,7 +193,7 @@ class FKDataToO2O(models.Model):
class M2MIntermediateData(models.Model): class M2MIntermediateData(models.Model):
data = models.ManyToManyField(Anchor, null=True, through='Intermediate') data = models.ManyToManyField(Anchor, through='Intermediate')
class Intermediate(models.Model): class Intermediate(models.Model):
......
...@@ -25,7 +25,7 @@ class Person(models.Model): ...@@ -25,7 +25,7 @@ class Person(models.Model):
class Employee(Person): class Employee(Person):
employee_num = models.IntegerField(default=0) employee_num = models.IntegerField(default=0)
profile = models.ForeignKey('Profile', related_name='profiles', null=True) profile = models.ForeignKey('Profile', related_name='profiles', null=True)
accounts = models.ManyToManyField('Account', related_name='employees', blank=True, null=True) accounts = models.ManyToManyField('Account', related_name='employees', blank=True)
@python_2_unicode_compatible @python_2_unicode_compatible
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment