Ticket #11964: check_constraints.diff

File check_constraints.diff, 15.8 KB (added by Matthew Schinckel, 9 years ago)

Contains patches against trunk that enable check constraints

  • django/core/management/validation.py

    diff -r 01b4ede45410 -r 7820036d6b68 django/core/management/validation.py
    a b  
    2222    from django.db import models, connection
    2323    from django.db.models.loading import get_app_errors
    2424    from django.db.models.fields.related import RelatedObject
    25     from django.core.exceptions import ValidationError
    2625
    2726    e = ModelErrorCollection(outfile)
    2827
     
    6362                            e.add(opts, '"%s": "choices" should be a sequence of two-tuples.' % f.name)
    6463            if f.db_index not in (None, True, False):
    6564                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                     continue
    73                 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)
    8265
    8366            # Perform any backend-specific field validation.
    8467            connection.validation.validate_field(e, opts, f)
     
    237220                    if f not in opts.local_fields:
    238221                        e.add(opts, '"unique_together" refers to %s. This is not in the same model as the unique_together statement.' % f.name)
    239222
    240         # Check check_constraints
    241         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                 continue
    247             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 
    266223    return len(e.errors)
  • django/db/backends/creation.py

    diff -r 01b4ede45410 -r 7820036d6b68 django/db/backends/creation.py
    a b  
    6262                field_output.append(style.SQL_KEYWORD('PRIMARY KEY'))
    6363            elif f.unique:
    6464                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))
    6865            if tablespace and f.unique:
    6966                # We must specify the index tablespace inline, because we
    7067                # won't be generating a CREATE INDEX statement for this field.
     
    8279        for field_constraints in opts.unique_together:
    8380            table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
    8481                ", ".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 columns
    88             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_constraint
    94             ))
    9582
    9683        full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
    9784        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  
    6464            db_index=False, rel=None, default=NOT_PROVIDED, editable=True,
    6565            serialize=True, unique_for_date=None, unique_for_month=None,
    6666            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):
    6868        self.name = name
    6969        self.verbose_name = verbose_name
    7070        self.primary_key = primary_key
     
    8585        self.db_column = db_column
    8686        self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE
    8787        self.auto_created = auto_created
    88         self.constraint = constraint
    8988
    9089        # Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
    9190        self.db_index = db_index
  • django/db/models/options.py

    diff -r 01b4ede45410 -r 7820036d6b68 django/db/models/options.py
    a b  
    2121DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
    2222                 'unique_together', 'permissions', 'get_latest_by',
    2323                 'order_with_respect_to', 'app_label', 'db_tablespace',
    24                  'abstract', 'managed', 'proxy', 'check_constraints')
     24                 'abstract', 'managed', 'proxy')
    2525
    2626class Options(object):
    2727    def __init__(self, meta, app_label=None):
     
    3232        self.db_table = ''
    3333        self.ordering = []
    3434        self.unique_together =  []
    35         self.check_constraints = []
    3635        self.permissions =  []
    3736        self.object_name, self.app_label = None, app_label
    3837        self.get_latest_by = None
  • django/forms/models.py

    diff -r 01b4ede45410 -r 7820036d6b68 django/forms/models.py
    a b  
    243243        bad_fields.union(field_errors)
    244244        form_errors.extend(global_errors)
    245245
    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 
    251246        for field_name in bad_fields:
    252247            del self.cleaned_data[field_name]
    253248        if form_errors:
     
    298293                date_checks.append(('month', name, f.unique_for_month))
    299294        return unique_checks, date_checks
    300295
    301     def _get_check_constraints(self):
    302         from django.db.models.fields import FieldDoesNotExist, Field as ModelField
    303        
    304         # Find all of the constraints that need to be applied to this model
    305         # Since a comparison of NULL with any value will return NULL, and
    306         # a constraint will 'succeed' if it return either NULL or TRUE, then
    307         # we can safely ignore any constraints with values that are None.
    308         # We can also ignore any fields that did not validate, as they will
    309         # 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                 continue
    319             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 it
    328                 continue
    329             if not isinstance(f, ModelField):
    330                 # This is an accessor with the same name as a field?
    331                 # Ignore
    332                 continue
    333             if self.cleaned_data.get(name) is None:
    334                 continue
    335             if f.constraint:
    336                column_check_constraints.append((name, f.constraint))
    337                
    338         return check_constraints, column_check_constraints
    339296
    340297    def _perform_unique_checks(self, unique_checks):
    341298        bad_fields = set()
     
    406363                bad_fields.add(field)
    407364        return bad_fields, []
    408365
    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 are
    415             # statements, I'm not sure how to.  Moved into this inner func
    416             # as we use it in two places.
    417             if op == "<":
    418                 return value1 < value2
    419             if op == ">":
    420                 return value1 > value2
    421             if op == "=":
    422                 return value1 == value2
    423             if op == "<=":
    424                 return value1 <= value2
    425             if op == ">=":
    426                 return value1 >= value2
    427             if op == "!=":
    428                 return value1 != value2
    429             return False
    430            
    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 by
    435             # 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_errors
    450 
    451366    def date_error_message(self, lookup_type, field, unique_for):
    452367        return _(u"%(field_name)s must be unique for %(date_field)s %(lookup)s.") % {
    453368            '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  
    182182    """ Model to test for unique ManyToManyFields, which are invalid. """
    183183    unique_people = models.ManyToManyField( Person, unique=True )
    184184
    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 = True
    211         check_constraints = ('int1 < int2',)
    212 
    213185model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute.
    214186invalid_models.fielderrors: "decimalfield": DecimalFields require a "decimal_places" attribute.
    215187invalid_models.fielderrors: "decimalfield": DecimalFields require a "max_digits" attribute.
     
    306278invalid_models.abstractrelationmodel: 'fk1' has a relation with model AbstractModel, which has either not been installed or is abstract.
    307279invalid_models.abstractrelationmodel: 'fk2' has an m2m relation with model AbstractModel, which has either not been installed or is abstract.
    308280invalid_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.
    324281"""
Back to Top