Skip to content

Commit 35bacff

Browse files
davidjbabsci
authored andcommitted
Merge PR #9 -- Backport fixes from django-mssql-backend
2 parents 3c6048a + c063460 commit 35bacff

File tree

6 files changed

+180
-9
lines changed

6 files changed

+180
-9
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,12 @@ Dictionary. Current available keys are:
135135
definition present in the ``freetds.conf`` FreeTDS configuration file
136136
instead of a hostname or an IP address.
137137

138-
But if this option is present and it's value is ``True``, this
139-
special behavior is turned off.
138+
But if this option is present and its value is ``True``, this
139+
special behavior is turned off. Instead, connections to the database
140+
server will be established using ``HOST`` and ``PORT`` options, without
141+
requiring ``freetds.conf`` to be configured.
140142

141-
See http://www.freetds.org/userguide/dsnless.htm for more information.
143+
See https://www.freetds.org/userguide/dsnless.html for more information.
142144

143145
- unicode_results
144146

manage.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
#!/usr/bin/env python
2+
13
# Copyright (c) Microsoft Corporation.
24
# Licensed under the MIT license.
35

4-
#!/usr/bin/env python
56
import os
67
import sys
78

mssql/schema.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
Table,
1818
)
1919
from django import VERSION as django_version
20-
from django.db.models import Index
20+
from django.db.models import Index, UniqueConstraint
2121
from django.db.models.fields import AutoField, BigAutoField
22+
from django.db.models.sql.where import AND
2223
from django.db.transaction import TransactionManagementError
2324
from django.utils.encoding import force_str
2425

@@ -689,7 +690,7 @@ def add_field(self, model, field):
689690
self.connection.close()
690691

691692
def _create_unique_sql(self, model, columns, name=None, condition=None, deferrable=None):
692-
if (deferrable and not self.connection.features.supports_deferrable_unique_constraints):
693+
if (deferrable and not getattr(self.connection.features, 'supports_deferrable_unique_constraints', False)):
693694
return None
694695

695696
def create_unique_name(*args, **kwargs):
@@ -955,3 +956,9 @@ def remove_field(self, model, field):
955956
for sql in list(self.deferred_sql):
956957
if isinstance(sql, Statement) and sql.references_column(model._meta.db_table, field.column):
957958
self.deferred_sql.remove(sql)
959+
960+
def add_constraint(self, model, constraint):
961+
if isinstance(constraint, UniqueConstraint) and constraint.condition and constraint.condition.connector != AND:
962+
raise NotImplementedError("The backend does not support %s conditions on unique constraint %s." %
963+
(constraint.condition.connector, constraint.name))
964+
super().add_constraint(model, constraint)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Generated by Django 3.1.5 on 2021-01-18 00:05
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('testapp', '0010_pizza_topping'),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='TestUnsupportableUniqueConstraint',
15+
fields=[
16+
(
17+
'id',
18+
models.AutoField(
19+
auto_created=True,
20+
primary_key=True,
21+
serialize=False,
22+
verbose_name='ID',
23+
),
24+
),
25+
('_type', models.CharField(max_length=50)),
26+
('status', models.CharField(max_length=50)),
27+
],
28+
options={
29+
'managed': False,
30+
},
31+
),
32+
migrations.CreateModel(
33+
name='TestSupportableUniqueConstraint',
34+
fields=[
35+
(
36+
'id',
37+
models.AutoField(
38+
auto_created=True,
39+
primary_key=True,
40+
serialize=False,
41+
verbose_name='ID',
42+
),
43+
),
44+
('_type', models.CharField(max_length=50)),
45+
('status', models.CharField(max_length=50)),
46+
],
47+
),
48+
migrations.AddConstraint(
49+
model_name='testsupportableuniqueconstraint',
50+
constraint=models.UniqueConstraint(
51+
condition=models.Q(
52+
('status', 'in_progress'),
53+
('status', 'needs_changes'),
54+
('status', 'published'),
55+
),
56+
fields=('_type',),
57+
name='and_constraint',
58+
),
59+
),
60+
migrations.AddConstraint(
61+
model_name='testsupportableuniqueconstraint',
62+
constraint=models.UniqueConstraint(
63+
condition=models.Q(status__in=['in_progress', 'needs_changes']),
64+
fields=('_type',),
65+
name='in_constraint',
66+
),
67+
),
68+
]

testapp/models.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import uuid
55

66
from django.db import models
7+
from django.db.models import Q
78
from django.utils import timezone
89

910

@@ -79,6 +80,7 @@ class TestRemoveOneToOneFieldModel(models.Model):
7980
class Topping(models.Model):
8081
name = models.UUIDField(primary_key=True, default=uuid.uuid4)
8182

83+
8284
class Pizza(models.Model):
8385
name = models.UUIDField(primary_key=True, default=uuid.uuid4)
8486
toppings = models.ManyToManyField(Topping)
@@ -88,3 +90,39 @@ def __str__(self):
8890
self.name,
8991
", ".join(topping.name for topping in self.toppings.all()),
9092
)
93+
94+
95+
class TestUnsupportableUniqueConstraint(models.Model):
96+
class Meta:
97+
managed = False
98+
constraints = [
99+
models.UniqueConstraint(
100+
name='or_constraint',
101+
fields=['_type'],
102+
condition=(Q(status='in_progress') | Q(status='needs_changes')),
103+
),
104+
]
105+
106+
_type = models.CharField(max_length=50)
107+
status = models.CharField(max_length=50)
108+
109+
110+
class TestSupportableUniqueConstraint(models.Model):
111+
class Meta:
112+
constraints = [
113+
models.UniqueConstraint(
114+
name='and_constraint',
115+
fields=['_type'],
116+
condition=(
117+
Q(status='in_progress') & Q(status='needs_changes') & Q(status='published')
118+
),
119+
),
120+
models.UniqueConstraint(
121+
name='in_constraint',
122+
fields=['_type'],
123+
condition=(Q(status__in=['in_progress', 'needs_changes'])),
124+
),
125+
]
126+
127+
_type = models.CharField(max_length=50)
128+
status = models.CharField(max_length=50)

testapp/tests/test_constraints.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT license.
33

4+
from django.db import connections, migrations, models
5+
from django.db.migrations.state import ProjectState
46
from django.db.utils import IntegrityError
5-
from django.test import TestCase, skipUnlessDBFeature
7+
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
68

9+
from mssql.base import DatabaseWrapper
710
from ..models import (
8-
Author, Editor, Post,
9-
TestUniqueNullableModel, TestNullableUniqueTogetherModel,
11+
Author,
12+
Editor,
13+
Post,
14+
TestUniqueNullableModel,
15+
TestNullableUniqueTogetherModel,
1016
)
1117

1218

@@ -55,3 +61,52 @@ def test_after_type_change(self):
5561
TestNullableUniqueTogetherModel.objects.create(a='aaa', b='bbb', c='ccc')
5662
with self.assertRaises(IntegrityError):
5763
TestNullableUniqueTogetherModel.objects.create(a='aaa', b='bbb', c='ccc')
64+
65+
66+
class TestUniqueConstraints(TransactionTestCase):
67+
def test_unsupportable_unique_constraint(self):
68+
# Only execute tests when running against SQL Server
69+
connection = connections['default']
70+
if isinstance(connection, DatabaseWrapper):
71+
72+
class TestMigration(migrations.Migration):
73+
initial = True
74+
75+
operations = [
76+
migrations.CreateModel(
77+
name='TestUnsupportableUniqueConstraint',
78+
fields=[
79+
(
80+
'id',
81+
models.AutoField(
82+
auto_created=True,
83+
primary_key=True,
84+
serialize=False,
85+
verbose_name='ID',
86+
),
87+
),
88+
('_type', models.CharField(max_length=50)),
89+
('status', models.CharField(max_length=50)),
90+
],
91+
),
92+
migrations.AddConstraint(
93+
model_name='testunsupportableuniqueconstraint',
94+
constraint=models.UniqueConstraint(
95+
condition=models.Q(
96+
('status', 'in_progress'),
97+
('status', 'needs_changes'),
98+
_connector='OR',
99+
),
100+
fields=('_type',),
101+
name='or_constraint',
102+
),
103+
),
104+
]
105+
106+
migration = TestMigration('testapp', 'test_unsupportable_unique_constraint')
107+
108+
with connection.schema_editor(atomic=True) as editor:
109+
with self.assertRaisesRegex(
110+
NotImplementedError, "does not support OR conditions"
111+
):
112+
return migration.apply(ProjectState(), editor)

0 commit comments

Comments
 (0)