diff options
| author | Simon Charette <charette.s@gmail.com> | 2018-08-05 22:30:44 -0400 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2018-11-13 17:57:27 -0500 |
| commit | db13bca60a6758d5fe63eeb01c00c3f54f650715 (patch) | |
| tree | e1c3620032623488f2dac8320e3e646ed18cef1a /django | |
| parent | 8eae094638acf802c8047b341d126d94bc9b45a3 (diff) | |
Fixed #29641 -- Added support for unique constraints in Meta.constraints.
This constraint is similar to Meta.unique_together but also allows
specifying a name.
Co-authored-by: Ian Foote <python@ian.feete.org>
Diffstat (limited to 'django')
| -rw-r--r-- | django/db/backends/sqlite3/introspection.py | 9 | ||||
| -rw-r--r-- | django/db/models/base.py | 11 | ||||
| -rw-r--r-- | django/db/models/constraints.py | 38 |
3 files changed, 56 insertions, 2 deletions
diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py index 47ca25a78a..4f4a54eacf 100644 --- a/django/db/backends/sqlite3/introspection.py +++ b/django/db/backends/sqlite3/introspection.py @@ -260,6 +260,15 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): name_token = next_ttype(sqlparse.tokens.Literal.String.Symbol) name = name_token.value[1:-1] token = next_ttype(sqlparse.tokens.Keyword) + if token.match(sqlparse.tokens.Keyword, 'UNIQUE'): + constraints[name] = { + 'unique': True, + 'columns': [], + 'primary_key': False, + 'foreign_key': False, + 'check': False, + 'index': False, + } if token.match(sqlparse.tokens.Keyword, 'CHECK'): # Column check constraint if name is None: diff --git a/django/db/models/base.py b/django/db/models/base.py index 89faf9d1e1..b57726fbcf 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -16,7 +16,7 @@ from django.db import ( connections, router, transaction, ) from django.db.models.constants import LOOKUP_SEP -from django.db.models.constraints import CheckConstraint +from django.db.models.constraints import CheckConstraint, UniqueConstraint from django.db.models.deletion import CASCADE, Collector from django.db.models.fields.related import ( ForeignObjectRel, OneToOneField, lazy_related_operation, resolve_relation, @@ -982,9 +982,12 @@ class Model(metaclass=ModelBase): unique_checks = [] unique_togethers = [(self.__class__, self._meta.unique_together)] + constraints = [(self.__class__, self._meta.constraints)] for parent_class in self._meta.get_parent_list(): if parent_class._meta.unique_together: unique_togethers.append((parent_class, parent_class._meta.unique_together)) + if parent_class._meta.constraints: + constraints.append((parent_class, parent_class._meta.constraints)) for model_class, unique_together in unique_togethers: for check in unique_together: @@ -992,6 +995,12 @@ class Model(metaclass=ModelBase): # Add the check if the field isn't excluded. unique_checks.append((model_class, tuple(check))) + for model_class, model_constraints in constraints: + for constraint in model_constraints: + if (isinstance(constraint, UniqueConstraint) and + not any(name in exclude for name in constraint.fields)): + unique_checks.append((model_class, constraint.fields)) + # These are checks for the unique_for_<date/year/month>. date_checks = [] diff --git a/django/db/models/constraints.py b/django/db/models/constraints.py index 698b278fe8..f6ea0fa12a 100644 --- a/django/db/models/constraints.py +++ b/django/db/models/constraints.py @@ -1,6 +1,6 @@ from django.db.models.sql.query import Query -__all__ = ['CheckConstraint'] +__all__ = ['CheckConstraint', 'UniqueConstraint'] class BaseConstraint: @@ -68,3 +68,39 @@ class CheckConstraint(BaseConstraint): path, args, kwargs = super().deconstruct() kwargs['check'] = self.check return path, args, kwargs + + +class UniqueConstraint(BaseConstraint): + def __init__(self, *, fields, name): + if not fields: + raise ValueError('At least one field is required to define a unique constraint.') + self.fields = tuple(fields) + super().__init__(name) + + def constraint_sql(self, model, schema_editor): + columns = ( + model._meta.get_field(field_name).column + for field_name in self.fields + ) + return schema_editor.sql_unique_constraint % { + 'columns': ', '.join(map(schema_editor.quote_name, columns)), + } + + def create_sql(self, model, schema_editor): + columns = [model._meta.get_field(field_name).column for field_name in self.fields] + return schema_editor._create_unique_sql(model, columns, self.name) + + def __repr__(self): + return '<%s: fields=%r name=%r>' % (self.__class__.__name__, self.fields, self.name) + + def __eq__(self, other): + return ( + isinstance(other, UniqueConstraint) and + self.name == other.name and + self.fields == other.fields + ) + + def deconstruct(self): + path, args, kwargs = super().deconstruct() + kwargs['fields'] = self.fields + return path, args, kwargs |
