4
from django.conf import settings
5
from django.core.management.color import color_style
6
from django.utils.encoding import force_str
7
from django.utils.itercompat import is_iterable
8
from django.utils import six
11
class ModelErrorCollection:
12
def __init__(self, outfile=sys.stdout):
14
self.outfile = outfile
15
self.style = color_style()
17
def add(self, context, error):
18
self.errors.append((context, error))
19
self.outfile.write(self.style.ERROR(force_str("%s: %s\n" % (context, error))))
22
def get_validation_errors(outfile, app=None):
24
Validates all models that are part of the specified app. If no app name is provided,
25
validates all models of all installed apps. Writes errors, if any, to outfile.
26
Returns number of errors.
28
from django.db import models, connection
29
from django.db.models.loading import get_app_errors
30
from django.db.models.deletion import SET_NULL, SET_DEFAULT
32
e = ModelErrorCollection(outfile)
34
for (app_name, error) in get_app_errors().items():
35
e.add(app_name, error)
37
for cls in models.get_models(app, include_swapped=True):
40
# Check swappable attribute.
43
app_label, model_name = opts.swapped.split('.')
45
e.add(opts, "%s is not of the form 'app_label.app_name'." % opts.swappable)
47
if not models.get_model(app_label, model_name):
48
e.add(opts, "Model has been swapped out for '%s' which has not been installed or is abstract." % opts.swapped)
49
# No need to perform any other validation checks on a swapped model.
52
# If this is the current User model, check known validation problems with User models
53
if settings.AUTH_USER_MODEL == '%s.%s' % (opts.app_label, opts.object_name):
54
# Check that REQUIRED_FIELDS is a list
55
if not isinstance(cls.REQUIRED_FIELDS, (list, tuple)):
56
e.add(opts, 'The REQUIRED_FIELDS must be a list or tuple.')
58
# Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS.
59
if cls.USERNAME_FIELD in cls.REQUIRED_FIELDS:
60
e.add(opts, 'The field named as the USERNAME_FIELD should not be included in REQUIRED_FIELDS on a swappable User model.')
62
# Check that the username field is unique
63
if not opts.get_field(cls.USERNAME_FIELD).unique:
64
e.add(opts, 'The USERNAME_FIELD must be unique. Add unique=True to the field parameters.')
66
# Model isn't swapped; do field-specific validation.
67
for f in opts.local_fields:
68
if f.name == 'id' and not f.primary_key and opts.pk.name == 'id':
69
e.add(opts, '"%s": You can\'t use "id" as a field name, because each model automatically gets an "id" field if none of the fields have primary_key=True. You need to either remove/rename your "id" field or add primary_key=True to a field.' % f.name)
70
if f.name.endswith('_'):
71
e.add(opts, '"%s": Field names cannot end with underscores, because this would lead to ambiguous queryset filters.' % f.name)
72
if (f.primary_key and f.null and
73
not connection.features.interprets_empty_strings_as_nulls):
74
# We cannot reliably check this for backends like Oracle which
75
# consider NULL and '' to be equal (and thus set up
76
# character-based fields a little differently).
77
e.add(opts, '"%s": Primary key fields cannot have null=True.' % f.name)
78
if isinstance(f, models.CharField):
80
max_length = int(f.max_length)
82
e.add(opts, '"%s": CharFields require a "max_length" attribute that is a positive integer.' % f.name)
83
except (ValueError, TypeError):
84
e.add(opts, '"%s": CharFields require a "max_length" attribute that is a positive integer.' % f.name)
85
if isinstance(f, models.DecimalField):
86
decimalp_ok, mdigits_ok = False, False
87
decimalp_msg = '"%s": DecimalFields require a "decimal_places" attribute that is a non-negative integer.'
89
decimal_places = int(f.decimal_places)
90
if decimal_places < 0:
91
e.add(opts, decimalp_msg % f.name)
94
except (ValueError, TypeError):
95
e.add(opts, decimalp_msg % f.name)
96
mdigits_msg = '"%s": DecimalFields require a "max_digits" attribute that is a positive integer.'
98
max_digits = int(f.max_digits)
100
e.add(opts, mdigits_msg % f.name)
103
except (ValueError, TypeError):
104
e.add(opts, mdigits_msg % f.name)
105
invalid_values_msg = '"%s": DecimalFields require a "max_digits" attribute value that is greater than or equal to the value of the "decimal_places" attribute.'
106
if decimalp_ok and mdigits_ok:
107
if decimal_places > max_digits:
108
e.add(opts, invalid_values_msg % f.name)
109
if isinstance(f, models.FileField) and not f.upload_to:
110
e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name)
111
if isinstance(f, models.ImageField):
113
from django.utils.image import Image
115
e.add(opts, '"%s": To use ImageFields, you need to install Pillow. Get it at https://pypi.python.org/pypi/Pillow.' % f.name)
116
if isinstance(f, models.BooleanField) and getattr(f, 'null', False):
117
e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name)
118
if isinstance(f, models.FilePathField) and not (f.allow_files or f.allow_folders):
119
e.add(opts, '"%s": FilePathFields must have either allow_files or allow_folders set to True.' % f.name)
120
if isinstance(f, models.GenericIPAddressField) and not getattr(f, 'null', False) and getattr(f, 'blank', False):
121
e.add(opts, '"%s": GenericIPAddressField can not accept blank values if null values are not allowed, as blank values are stored as null.' % f.name)
123
if isinstance(f.choices, six.string_types) or not is_iterable(f.choices):
124
e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
127
if isinstance(c, six.string_types) or not is_iterable(c) or len(c) != 2:
128
e.add(opts, '"%s": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).' % f.name)
129
if f.db_index not in (None, True, False):
130
e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name)
132
# Perform any backend-specific field validation.
133
connection.validation.validate_field(e, opts, f)
135
# Check if the on_delete behavior is sane
136
if f.rel and hasattr(f.rel, 'on_delete'):
137
if f.rel.on_delete == SET_NULL and not f.null:
138
e.add(opts, "'%s' specifies on_delete=SET_NULL, but cannot be null." % f.name)
139
elif f.rel.on_delete == SET_DEFAULT and not f.has_default():
140
e.add(opts, "'%s' specifies on_delete=SET_DEFAULT, but has no default value." % f.name)
142
# Check to see if the related field will clash with any existing
143
# fields, m2m fields, m2m related objects or related objects
145
if f.rel.to not in models.get_models():
146
# If the related model is swapped, provide a hint;
147
# otherwise, the model just hasn't been installed.
148
if not isinstance(f.rel.to, six.string_types) and f.rel.to._meta.swapped:
149
e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
151
e.add(opts, "'%s' has a relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to))
152
# it is a string and we could not find the model it refers to
153
# so skip the next section
154
if isinstance(f.rel.to, six.string_types):
157
# Make sure the related field specified by a ForeignKey is unique
158
if f.requires_unique_target:
159
if len(f.foreign_related_fields) > 1:
160
has_unique_field = False
161
for rel_field in f.foreign_related_fields:
162
has_unique_field = has_unique_field or rel_field.unique
163
if not has_unique_field:
164
e.add(opts, "Field combination '%s' under model '%s' must have a unique=True constraint" % (','.join([rel_field.name for rel_field in f.foreign_related_fields]), f.rel.to.__name__))
166
if not f.foreign_related_fields[0].unique:
167
e.add(opts, "Field '%s' under model '%s' must have a unique=True constraint." % (f.foreign_related_fields[0].name, f.rel.to.__name__))
169
rel_opts = f.rel.to._meta
170
rel_name = f.related.get_accessor_name()
171
rel_query_name = f.related_query_name()
172
if not f.rel.is_hidden():
173
for r in rel_opts.fields:
174
if r.name == rel_name:
175
e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
176
if r.name == rel_query_name:
177
e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
178
for r in rel_opts.local_many_to_many:
179
if r.name == rel_name:
180
e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
181
if r.name == rel_query_name:
182
e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
183
for r in rel_opts.get_all_related_many_to_many_objects():
184
if r.get_accessor_name() == rel_name:
185
e.add(opts, "Accessor for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
186
if r.get_accessor_name() == rel_query_name:
187
e.add(opts, "Reverse query name for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
188
for r in rel_opts.get_all_related_objects():
190
if r.get_accessor_name() == rel_name:
191
e.add(opts, "Accessor for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
192
if r.get_accessor_name() == rel_query_name:
193
e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
195
seen_intermediary_signatures = []
196
for i, f in enumerate(opts.local_many_to_many):
197
# Check to see if the related m2m field will clash with any
198
# existing fields, m2m fields, m2m related objects or related
200
if f.rel.to not in models.get_models():
201
# If the related model is swapped, provide a hint;
202
# otherwise, the model just hasn't been installed.
203
if not isinstance(f.rel.to, six.string_types) and f.rel.to._meta.swapped:
204
e.add(opts, "'%s' defines a relation with the model '%s.%s', which has been swapped out. Update the relation to point at settings.%s." % (f.name, f.rel.to._meta.app_label, f.rel.to._meta.object_name, f.rel.to._meta.swappable))
206
e.add(opts, "'%s' has an m2m relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to))
208
# it is a string and we could not find the model it refers to
209
# so skip the next section
210
if isinstance(f.rel.to, six.string_types):
213
# Check that the field is not set to unique. ManyToManyFields do not support unique.
215
e.add(opts, "ManyToManyFields cannot be unique. Remove the unique argument on '%s'." % f.name)
217
if f.rel.through is not None and not isinstance(f.rel.through, six.string_types):
218
from_model, to_model = cls, f.rel.to
219
if from_model == to_model and f.rel.symmetrical and not f.rel.through._meta.auto_created:
220
e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
221
seen_from, seen_to, seen_self = False, False, 0
222
for inter_field in f.rel.through._meta.fields:
223
rel_to = getattr(inter_field.rel, 'to', None)
224
if from_model == to_model: # relation to self
225
if rel_to == from_model:
228
e.add(opts, "Intermediary model %s has more than "
229
"two foreign keys to %s, which is ambiguous "
230
"and is not permitted." % (
231
f.rel.through._meta.object_name,
232
from_model._meta.object_name
236
if rel_to == from_model:
238
e.add(opts, "Intermediary model %s has more "
239
"than one foreign key to %s, which is "
240
"ambiguous and is not permitted." % (
241
f.rel.through._meta.object_name,
242
from_model._meta.object_name
247
elif rel_to == to_model:
249
e.add(opts, "Intermediary model %s has more "
250
"than one foreign key to %s, which is "
251
"ambiguous and is not permitted." % (
252
f.rel.through._meta.object_name,
253
rel_to._meta.object_name
258
if f.rel.through not in models.get_models(include_auto_created=True):
259
e.add(opts, "'%s' specifies an m2m relation through model "
260
"%s, which has not been installed." % (f.name, f.rel.through)
262
signature = (f.rel.to, cls, f.rel.through)
263
if signature in seen_intermediary_signatures:
264
e.add(opts, "The model %s has two manually-defined m2m "
265
"relations through the model %s, which is not "
266
"permitted. Please consider using an extra field on "
267
"your intermediary model instead." % (
268
cls._meta.object_name,
269
f.rel.through._meta.object_name
273
seen_intermediary_signatures.append(signature)
274
if not f.rel.through._meta.auto_created:
275
seen_related_fk, seen_this_fk = False, False
276
for field in f.rel.through._meta.fields:
278
if not seen_related_fk and field.rel.to == f.rel.to:
279
seen_related_fk = True
280
elif field.rel.to == cls:
282
if not seen_related_fk or not seen_this_fk:
283
e.add(opts, "'%s' is a manually-defined m2m relation "
284
"through model %s, which does not have foreign keys "
285
"to %s and %s" % (f.name, f.rel.through._meta.object_name,
286
f.rel.to._meta.object_name, cls._meta.object_name)
288
elif isinstance(f.rel.through, six.string_types):
289
e.add(opts, "'%s' specifies an m2m relation through model %s, "
290
"which has not been installed" % (f.name, f.rel.through)
293
rel_opts = f.rel.to._meta
294
rel_name = f.related.get_accessor_name()
295
rel_query_name = f.related_query_name()
296
# If rel_name is none, there is no reverse accessor (this only
297
# occurs for symmetrical m2m relations to self). If this is the
298
# case, there are no clashes to check for this field, as there are
299
# no reverse descriptors for this field.
300
if rel_name is not None:
301
for r in rel_opts.fields:
302
if r.name == rel_name:
303
e.add(opts, "Accessor for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
304
if r.name == rel_query_name:
305
e.add(opts, "Reverse query name for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
306
for r in rel_opts.local_many_to_many:
307
if r.name == rel_name:
308
e.add(opts, "Accessor for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
309
if r.name == rel_query_name:
310
e.add(opts, "Reverse query name for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name))
311
for r in rel_opts.get_all_related_many_to_many_objects():
313
if r.get_accessor_name() == rel_name:
314
e.add(opts, "Accessor for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
315
if r.get_accessor_name() == rel_query_name:
316
e.add(opts, "Reverse query name for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
317
for r in rel_opts.get_all_related_objects():
318
if r.get_accessor_name() == rel_name:
319
e.add(opts, "Accessor for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
320
if r.get_accessor_name() == rel_query_name:
321
e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name))
323
# Check ordering attribute.
325
for field_name in opts.ordering:
326
if field_name == '?':
328
if field_name.startswith('-'):
329
field_name = field_name[1:]
330
if opts.order_with_respect_to and field_name == '_order':
332
# Skip ordering in the format field1__field2 (FIXME: checking
333
# this format would be nice, but it's a little fiddly).
334
if '__' in field_name:
336
# Skip ordering on pk. This is always a valid order_by field
337
# but is an alias and therefore won't be found by opts.get_field.
338
if field_name == 'pk':
341
opts.get_field(field_name, many_to_many=False)
342
except models.FieldDoesNotExist:
343
e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
345
# Check unique_together.
346
for ut in opts.unique_together:
347
validate_local_fields(e, opts, "unique_together", ut)
348
if not isinstance(opts.index_together, collections.Sequence):
349
e.add(opts, '"index_together" must a sequence')
351
for it in opts.index_together:
352
validate_local_fields(e, opts, "index_together", it)
357
def validate_local_fields(e, opts, field_name, fields):
358
from django.db import models
360
if not isinstance(fields, collections.Sequence):
361
e.add(opts, 'all %s elements must be sequences' % field_name)
365
f = opts.get_field(field, many_to_many=True)
366
except models.FieldDoesNotExist:
367
e.add(opts, '"%s" refers to %s, a field that doesn\'t exist.' % (field_name, field))
369
if isinstance(f.rel, models.ManyToManyRel):
370
e.add(opts, '"%s" refers to %s. ManyToManyFields are not supported in %s.' % (field_name, f.name, field_name))
371
if f not in opts.local_fields:
372
e.add(opts, '"%s" refers to %s. This is not in the same model as the %s statement.' % (field_name, f.name, field_name))