summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2018-08-05 22:30:44 -0400
committerTim Graham <timograham@gmail.com>2018-11-13 17:57:27 -0500
commitdb13bca60a6758d5fe63eeb01c00c3f54f650715 (patch)
treee1c3620032623488f2dac8320e3e646ed18cef1a /django
parent8eae094638acf802c8047b341d126d94bc9b45a3 (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.py9
-rw-r--r--django/db/models/base.py11
-rw-r--r--django/db/models/constraints.py38
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