mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
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:
parent
3a85aae2ea
commit
011abb7d96
@ -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({
|
||||||
|
@ -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" % (
|
||||||
|
@ -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.
|
|||||||
|
|
||||||
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):
|
|||||||
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):
|
|||||||
|
|
||||||
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):
|
|||||||
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')
|
||||||
|
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
|||||||
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
|
|||||||
|
|
||||||
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):
|
|||||||
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):
|
|||||||
|
|
||||||
|
|
||||||
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):
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user