Ticket #11964: check_constraints.diff
File check_constraints.diff, 15.8 KB (added by , 15 years ago) |
---|
-
django/core/management/validation.py
diff -r 01b4ede45410 -r 7820036d6b68 django/core/management/validation.py
a b 22 22 from django.db import models, connection 23 23 from django.db.models.loading import get_app_errors 24 24 from django.db.models.fields.related import RelatedObject 25 from django.core.exceptions import ValidationError26 25 27 26 e = ModelErrorCollection(outfile) 28 27 … … 63 62 e.add(opts, '"%s": "choices" should be a sequence of two-tuples.' % f.name) 64 63 if f.db_index not in (None, True, False): 65 64 e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name) 66 if f.constraint:67 # This section seems somewhat verbose: I wonder if it can be simplified.68 try:69 op, value = f.constraint.split()70 except ValueError:71 e.add(opts, '"%s": "constraint" must be like "< value".' % f.name)72 continue73 if op not in ("<",">","=","<=",">=","!="):74 e.add(opts, '"%s": "constraint" must have valid comparison operator.' % f.name)75 try:76 value = f.to_python(value)77 except ValidationError:78 if value in map(lambda x:x.name, opts.local_fields):79 e.add(opts, '"%s": "constraint" operand must not be another column name. Use a table-based constraint instead.' % f.name)80 else:81 e.add(opts, '"%s": "constraint" operand must be a valid value for this column.' % f.name)82 65 83 66 # Perform any backend-specific field validation. 84 67 connection.validation.validate_field(e, opts, f) … … 237 220 if f not in opts.local_fields: 238 221 e.add(opts, '"unique_together" refers to %s. This is not in the same model as the unique_together statement.' % f.name) 239 222 240 # Check check_constraints241 for cc in opts.check_constraints:242 try:243 fname1, op, fname2 = cc.split()244 except ValueError:245 e.add(opts, '"check_constraints" does not look like "column1 < column2". Check your syntax.')246 continue247 for field_name in (fname1, fname2):248 try:249 f = opts.get_field(field_name)250 except models.FieldDoesNotExist:251 e.add(opts, '"check_constraints" refers to "%s", a field that doesn\'t exist. Check your syntax.' % field_name)252 if op not in ('<','>','=','<=','>=','!='):253 e.add(opts, '"check_constraints" uses "%s", an operator that is unknown. Check your syntax.' % op)254 try:255 field1 = opts.get_field(fname1)256 field2 = opts.get_field(fname2)257 if type(field1) != type(field2):258 # This is probably the only part of this phase I am really not that happy with.259 # How do I find out if two types can be compared? How does SQL decide.260 e.add(opts, '"check_constraints" compares two columns that cannot be compared.')261 except models.FieldDoesNotExist:262 pass # This has been dealt with above.263 if opts.abstract:264 e.add(opts, 'Abstract models cannot be given "check_constraints" options. Or something.')265 266 223 return len(e.errors) -
django/db/backends/creation.py
diff -r 01b4ede45410 -r 7820036d6b68 django/db/backends/creation.py
a b 62 62 field_output.append(style.SQL_KEYWORD('PRIMARY KEY')) 63 63 elif f.unique: 64 64 field_output.append(style.SQL_KEYWORD('UNIQUE')) 65 if f.constraint:66 field_output.append(style.SQL_KEYWORD('CHECK') + " (%s %s)" % \67 (style.SQL_FIELD(qn(f.column)), f.constraint))68 65 if tablespace and f.unique: 69 66 # We must specify the index tablespace inline, because we 70 67 # won't be generating a CREATE INDEX statement for this field. … … 82 79 for field_constraints in opts.unique_together: 83 80 table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \ 84 81 ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints])) 85 for check_constraint in opts.check_constraints:86 # Turn 'start < finish' into 'start_finish'87 # This assumes that only one constraint per pair of columns88 constraint_name = "_".join(check_constraint.split()[0:3:2])89 table_output.append("%s %s %s (%s)" % (90 style.SQL_KEYWORD('CONSTRAINT'),91 style.SQL_TABLE(qn(opts.db_table + '_' + constraint_name)),92 style.SQL_KEYWORD('CHECK'),93 check_constraint94 ))95 82 96 83 full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' ('] 97 84 for i, line in enumerate(table_output): # Combine and add commas. -
django/db/models/fields/__init__.py
diff -r 01b4ede45410 -r 7820036d6b68 django/db/models/fields/__init__.py
a b 64 64 db_index=False, rel=None, default=NOT_PROVIDED, editable=True, 65 65 serialize=True, unique_for_date=None, unique_for_month=None, 66 66 unique_for_year=None, choices=None, help_text='', db_column=None, 67 db_tablespace=None, auto_created=False , constraint=None):67 db_tablespace=None, auto_created=False): 68 68 self.name = name 69 69 self.verbose_name = verbose_name 70 70 self.primary_key = primary_key … … 85 85 self.db_column = db_column 86 86 self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE 87 87 self.auto_created = auto_created 88 self.constraint = constraint89 88 90 89 # Set db_index to True if the field has a relationship and doesn't explicitly set db_index. 91 90 self.db_index = db_index -
django/db/models/options.py
diff -r 01b4ede45410 -r 7820036d6b68 django/db/models/options.py
a b 21 21 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', 22 22 'unique_together', 'permissions', 'get_latest_by', 23 23 'order_with_respect_to', 'app_label', 'db_tablespace', 24 'abstract', 'managed', 'proxy' , 'check_constraints')24 'abstract', 'managed', 'proxy') 25 25 26 26 class Options(object): 27 27 def __init__(self, meta, app_label=None): … … 32 32 self.db_table = '' 33 33 self.ordering = [] 34 34 self.unique_together = [] 35 self.check_constraints = []36 35 self.permissions = [] 37 36 self.object_name, self.app_label = None, app_label 38 37 self.get_latest_by = None -
django/forms/models.py
diff -r 01b4ede45410 -r 7820036d6b68 django/forms/models.py
a b 243 243 bad_fields.union(field_errors) 244 244 form_errors.extend(global_errors) 245 245 246 check_constraints, column_check_constraints = self._get_check_constraints()247 field_errors, global_errors = self._perform_check_constraints(check_constraints, column_check_constraints)248 bad_fields.union(field_errors)249 form_errors.extend(global_errors)250 251 246 for field_name in bad_fields: 252 247 del self.cleaned_data[field_name] 253 248 if form_errors: … … 298 293 date_checks.append(('month', name, f.unique_for_month)) 299 294 return unique_checks, date_checks 300 295 301 def _get_check_constraints(self):302 from django.db.models.fields import FieldDoesNotExist, Field as ModelField303 304 # Find all of the constraints that need to be applied to this model305 # Since a comparison of NULL with any value will return NULL, and306 # a constraint will 'succeed' if it return either NULL or TRUE, then307 # we can safely ignore any constraints with values that are None.308 # We can also ignore any fields that did not validate, as they will309 # have the error message from the invalid data.310 311 column_check_constraints = []312 check_constraints = []313 314 for check_constraint in self.instance._meta.check_constraints[:]:315 f1, op, f2 = check_constraint.split()316 if self.cleaned_data.get(f1) is None or \317 self.cleaned_data.get(f2) is None:318 continue319 check_constraints.append(check_constraint)320 321 # Also need to append individual column check constraints.322 # Skip any fields that did not validate.323 for name in self.fields:324 try:325 f = self.instance._meta.get_field_by_name(name)[0]326 except FieldDoesNotExist:327 # This is an extra field that's not on the ModelForm, ignore it328 continue329 if not isinstance(f, ModelField):330 # This is an accessor with the same name as a field?331 # Ignore332 continue333 if self.cleaned_data.get(name) is None:334 continue335 if f.constraint:336 column_check_constraints.append((name, f.constraint))337 338 return check_constraints, column_check_constraints339 296 340 297 def _perform_unique_checks(self, unique_checks): 341 298 bad_fields = set() … … 406 363 bad_fields.add(field) 407 364 return bad_fields, [] 408 365 409 def _perform_check_constraints(self, check_constraints, column_check_constraints):410 bad_fields = set()411 form_errors = []412 413 def _test(value1, op, value2):414 # I'd love to see a better way to do this, but since these are415 # statements, I'm not sure how to. Moved into this inner func416 # as we use it in two places.417 if op == "<":418 return value1 < value2419 if op == ">":420 return value1 > value2421 if op == "=":422 return value1 == value2423 if op == "<=":424 return value1 <= value2425 if op == ">=":426 return value1 >= value2427 if op == "!=":428 return value1 != value2429 return False430 431 for field, tester in column_check_constraints:432 value = self.cleaned_data.get(field)433 op, other = tester.split()434 # Convert the comparison value into a python object as parsed by435 # this field.436 other = self.instance._meta.get_field_by_name(field)[0].to_python(other)437 if not _test(value, op, other):438 self._errors[field] = ErrorList(["Invalid data for %s. Must be %s" % (field, tester)])439 bad_fields.add(field)440 441 for constraint in check_constraints:442 field_name1, op, field_name2 = constraint.split()443 value1 = self.cleaned_data.get(field_name1)444 value2 = self.cleaned_data.get(field_name2)445 446 if not _test(value1, op, value2):447 form_errors.append("Invalid data. Does not satisfy " + constraint)448 449 return bad_fields, form_errors450 451 366 def date_error_message(self, lookup_type, field, unique_for): 452 367 return _(u"%(field_name)s must be unique for %(date_field)s %(lookup)s.") % { 453 368 'field_name': unicode(self.fields[field].label), -
deleted file tests/modeltests/check_constraints/__init__.py
diff -r 01b4ede45410 -r 7820036d6b68 tests/modeltests/check_constraints/__init__.py
+ - 1 -
deleted file tests/modeltests/check_constraints/models.py
diff -r 01b4ede45410 -r 7820036d6b68 tests/modeltests/check_constraints/models.py
+ - 1 -
tests/modeltests/invalid_models/models.py
diff -r 01b4ede45410 -r 7820036d6b68 tests/modeltests/invalid_models/models.py
a b 182 182 """ Model to test for unique ManyToManyFields, which are invalid. """ 183 183 unique_people = models.ManyToManyField( Person, unique=True ) 184 184 185 class Constraints(models.Model):186 """ Model to test for invalid constraints.187 Do I have too many tests here?"""188 int1 = models.IntegerField( constraint="<" )189 int2 = models.IntegerField( constraint="< a" )190 int3 = models.IntegerField( constraint="< int2" )191 int4 = models.IntegerField( constraint="a 5" )192 int5 = models.IntegerField( constraint="1 <" )193 int6 = models.IntegerField( constraint="< 3 a" )194 int7 = models.IntegerField( constraint="< 5.7" )195 int8 = models.IntegerField( constraint=">> 5" )196 date1 = models.DateField( constraint="> 2006-60-30" )197 time1 = models.TimeField( constraint="!= 66:66" )198 class Meta:199 check_constraints = (200 "int1>int2",201 "int6 > int9",202 "int1 e int2",203 "date1 < int2",204 )205 206 class AbstractConstraints(models.Model):207 int1 = models.IntegerField()208 int2 = models.IntegerField()209 class Meta:210 abstract = True211 check_constraints = ('int1 < int2',)212 213 185 model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute. 214 186 invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute. 215 187 invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute. … … 306 278 invalid_models.abstractrelationmodel: 'fk1' has a relation with model AbstractModel, which has either not been installed or is abstract. 307 279 invalid_models.abstractrelationmodel: 'fk2' has an m2m relation with model AbstractModel, which has either not been installed or is abstract. 308 280 invalid_models.uniquem2m: ManyToManyFields cannot be unique. Remove the unique argument on 'unique_people'. 309 invalid_models.constraints: "int1": "constraint" must be like "< value".310 invalid_models.constraints: "int2": "constraint" operand must be a valid value for this column.311 invalid_models.constraints: "int3": "constraint" operand must not be another column name. Use a table-based constraint instead.312 invalid_models.constraints: "int4": "constraint" must have valid comparison operator.313 invalid_models.constraints: "int5": "constraint" must have valid comparison operator.314 invalid_models.constraints: "int5": "constraint" operand must be a valid value for this column.315 invalid_models.constraints: "int6": "constraint" must be like "< value".316 invalid_models.constraints: "int7": "constraint" operand must be a valid value for this column.317 invalid_models.constraints: "int8": "constraint" must have valid comparison operator.318 invalid_models.constraints: "date1": "constraint" operand must be a valid value for this column.319 invalid_models.constraints: "time1": "constraint" operand must be a valid value for this column.320 invalid_models.constraints: "check_constraints" does not look like "column1 < column2". Check your syntax.321 invalid_models.constraints: "check_constraints" refers to "int9", a field that doesn't exist. Check your syntax.322 invalid_models.constraints: "check_constraints" uses "e", an operator that is unknown. Check your syntax.323 invalid_models.constraints: "check_constraints" compares two columns that cannot be compared.324 281 """