Django

Code

root/django/trunk/django/core/management/validation.py

Revision 8697, 15.1 kB (checked in by jacob, 3 months ago)

Fixed #8687, a bad variable name in validation. Thanks, vung.

  • Property svn:eol-style set to native
Line 
1 import sys
2 from django.core.management.color import color_style
3 from django.utils.itercompat import is_iterable
4
5 class ModelErrorCollection:
6     def __init__(self, outfile=sys.stdout):
7         self.errors = []
8         self.outfile = outfile
9         self.style = color_style()
10
11     def add(self, context, error):
12         self.errors.append((context, error))
13         self.outfile.write(self.style.ERROR("%s: %s\n" % (context, error)))
14
15 def get_validation_errors(outfile, app=None):
16     """
17     Validates all models that are part of the specified app. If no app name is provided,
18     validates all models of all installed apps. Writes errors, if any, to outfile.
19     Returns number of errors.
20     """
21     from django.conf import settings
22     from django.db import models, connection
23     from django.db.models.loading import get_app_errors
24     from django.db.models.fields.related import RelatedObject
25
26     e = ModelErrorCollection(outfile)
27
28     for (app_name, error) in get_app_errors().items():
29         e.add(app_name, error)
30
31     for cls in models.get_models(app):
32         opts = cls._meta
33
34         # Do field-specific validation.
35         for f in opts.local_fields:
36             if f.name == 'id' and not f.primary_key and opts.pk.name == 'id':
37                 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)
38             if f.name.endswith('_'):
39                 e.add(opts, '"%s": Field names cannot end with underscores, because this would lead to ambiguous queryset filters.' % f.name)
40             if isinstance(f, models.CharField) and f.max_length in (None, 0):
41                 e.add(opts, '"%s": CharFields require a "max_length" attribute.' % f.name)
42             if isinstance(f, models.DecimalField):
43                 if f.decimal_places is None:
44                     e.add(opts, '"%s": DecimalFields require a "decimal_places" attribute.' % f.name)
45                 if f.max_digits is None:
46                     e.add(opts, '"%s": DecimalFields require a "max_digits" attribute.' % f.name)
47             if isinstance(f, models.FileField) and not f.upload_to:
48                 e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name)
49             if isinstance(f, models.ImageField):
50                 try:
51                     from PIL import Image
52                 except ImportError:
53                     e.add(opts, '"%s": To use ImageFields, you need to install the Python Imaging Library. Get it at http://www.pythonware.com/products/pil/ .' % f.name)
54             if f.choices:
55                 if isinstance(f.choices, basestring) or not is_iterable(f.choices):
56                     e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
57                 else:
58                     for c in f.choices:
59                         if not type(c) in (tuple, list) or len(c) != 2:
60                             e.add(opts, '"%s": "choices" should be a sequence of two-tuples.' % f.name)
61             if f.db_index not in (None, True, False):
62                 e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name)
63
64             # Perform any backend-specific field validation.
65             connection.validation.validate_field(e, opts, f)
66
67             # Check to see if the related field will clash with any existing
68             # fields, m2m fields, m2m related objects or related objects
69             if f.rel:
70                 if f.rel.to not in models.get_models():
71                     e.add(opts, "'%s' has a relation with model %s, which has either not been installed or is abstract." % (f.name, f.rel.to))
72                 # it is a string and we could not find the model it refers to
73                 # so skip the next section
74                 if isinstance(f.rel.to, (str, unicode)):
75                     continue
76
77                 rel_opts = f.rel.to._meta
78                 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
79                 rel_query_name = f.related_query_name()
80                 for r in rel_opts.fields:
81                     if r.name == rel_name:
82                         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))
83                     if r.name == rel_query_name:
84                         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))
85                 for r in rel_opts.local_many_to_many:
86                     if r.name == rel_name:
87                         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))
88                     if r.name == rel_query_name:
89                         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))
90                 for r in rel_opts.get_all_related_many_to_many_objects():
91                     if r.get_accessor_name() == rel_name:
92                         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))
93                     if r.get_accessor_name() == rel_query_name:
94                         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))
95                 for r in rel_opts.get_all_related_objects():
96                     if r.field is not f:
97                         if r.get_accessor_name() == rel_name:
98                             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))
99                         if r.get_accessor_name() == rel_query_name:
100                             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))
101
102         seen_intermediary_signatures = []
103         for i, f in enumerate(opts.local_many_to_many):
104             # Check to see if the related m2m field will clash with any
105             # existing fields, m2m fields, m2m related objects or related
106             # objects
107             if f.rel.to not in models.get_models():
108                 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))
109                 # it is a string and we could not find the model it refers to
110                 # so skip the next section
111                 if isinstance(f.rel.to, (str, unicode)):
112                     continue
113
114             # Check that the field is not set to unique.  ManyToManyFields do not support unique.
115             if f.unique:
116                 e.add(opts, "ManyToManyFields cannot be unique.  Remove the unique argument on '%s'." % f.name)
117
118             if getattr(f.rel, 'through', None) is not None:
119                 if hasattr(f.rel, 'through_model'):
120                     from_model, to_model = cls, f.rel.to
121                     if from_model == to_model and f.rel.symmetrical:
122                         e.add(opts, "Many-to-many fields with intermediate tables cannot be symmetrical.")
123                     seen_from, seen_to, seen_self = False, False, 0
124                     for inter_field in f.rel.through_model._meta.fields:
125                         rel_to = getattr(inter_field.rel, 'to', None)
126                         if from_model == to_model: # relation to self
127                             if rel_to == from_model:
128                                 seen_self += 1
129                             if seen_self > 2:
130                                 e.add(opts, "Intermediary model %s has more than two foreign keys to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
131                         else:
132                             if rel_to == from_model:
133                                 if seen_from:
134                                     e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, from_model._meta.object_name))
135                                 else:
136                                     seen_from = True
137                             elif rel_to == to_model:
138                                 if seen_to:
139                                     e.add(opts, "Intermediary model %s has more than one foreign key to %s, which is ambiguous and is not permitted." % (f.rel.through_model._meta.object_name, rel_to._meta.object_name))
140                                 else:
141                                     seen_to = True
142                     if f.rel.through_model not in models.get_models():
143                         e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed." % (f.name, f.rel.through))
144                     signature = (f.rel.to, cls, f.rel.through_model)
145                     if signature in seen_intermediary_signatures:
146                         e.add(opts, "The model %s has two manually-defined m2m relations through the model %s, which is not permitted. Please consider using an extra field on your intermediary model instead." % (cls._meta.object_name, f.rel.through_model._meta.object_name))
147                     else:
148                         seen_intermediary_signatures.append(signature)
149                     seen_related_fk, seen_this_fk = False, False
150                     for field in f.rel.through_model._meta.fields:
151                         if field.rel:
152                             if not seen_related_fk and field.rel.to == f.rel.to:
153                                 seen_related_fk = True
154                             elif field.rel.to == cls:
155                                 seen_this_fk = True
156                     if not seen_related_fk or not seen_this_fk:
157                         e.add(opts, "'%s' has a manually-defined m2m relation through model %s, which does not have foreign keys to %s and %s" % (f.name, f.rel.through, f.rel.to._meta.object_name, cls._meta.object_name))
158                 else:
159                     e.add(opts, "'%s' specifies an m2m relation through model %s, which has not been installed" % (f.name, f.rel.through))
160
161             rel_opts = f.rel.to._meta
162             rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
163             rel_query_name = f.related_query_name()
164             # If rel_name is none, there is no reverse accessor (this only
165             # occurs for symmetrical m2m relations to self). If this is the
166             # case, there are no clashes to check for this field, as there are
167             # no reverse descriptors for this field.
168             if rel_name is not None:
169                 for r in rel_opts.fields:
170                     if r.name == rel_name:
171                         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))
172                     if r.name == rel_query_name:
173                         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))
174                 for r in rel_opts.local_many_to_many:
175                     if r.name == rel_name:
176                         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))
177                     if r.name == rel_query_name:
178                         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))
179                 for r in rel_opts.get_all_related_many_to_many_objects():
180                     if r.field is not f:
181                         if r.get_accessor_name() == rel_name:
182                             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))
183                         if r.get_accessor_name() == rel_query_name:
184                             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))
185                 for r in rel_opts.get_all_related_objects():
186                     if r.get_accessor_name() == rel_name:
187                         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))
188                     if r.get_accessor_name() == rel_query_name:
189                         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))
190
191         # Check ordering attribute.
192         if opts.ordering:
193             for field_name in opts.ordering:
194                 if field_name == '?': continue
195                 if field_name.startswith('-'):
196                     field_name = field_name[1:]
197                 if opts.order_with_respect_to and field_name == '_order':
198                     continue
199                 # Skip ordering in the format field1__field2 (FIXME: checking
200                 # this format would be nice, but it's a little fiddly).
201                 if '_' in field_name:
202                     continue
203                 try:
204                     opts.get_field(field_name, many_to_many=False)
205                 except models.FieldDoesNotExist:
206                     e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name)
207
208         # Check unique_together.
209         for ut in opts.unique_together:
210             for field_name in ut:
211                 try:
212                     f = opts.get_field(field_name, many_to_many=True)
213                 except models.FieldDoesNotExist:
214                     e.add(opts, '"unique_together" refers to %s, a field that doesn\'t exist. Check your syntax.' % field_name)
215                 else:
216                     if isinstance(f.rel, models.ManyToManyRel):
217                         e.add(opts, '"unique_together" refers to %s. ManyToManyFields are not supported in unique_together.' % f.name)
218                     if f not in opts.local_fields:
219                         e.add(opts, '"unique_together" refers to %s. This is not in the same model as the unique_together statement.' % f.name)
220
221     return len(e.errors)
Note: See TracBrowser for help on using the browser.