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

Thanks Loic Bistuer and Tim Graham for help and review.
This commit is contained in:
Anubhav Joshi 2014-07-09 02:12:40 +05:30 committed by Tim Graham
parent 3a85aae2ea
commit 011abb7d96
14 changed files with 76 additions and 16 deletions

View File

@ -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):
'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({

View File

@ -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):
] ]
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" % (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
# -*- 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):
] ]
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):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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