Skip to content

Commit 205c93b

Browse files
abscidavidjb
authored andcommitted
Fix Django3.1 related issues
1 parent 1fef391 commit 205c93b

File tree

7 files changed

+172
-61
lines changed

7 files changed

+172
-61
lines changed

azure-pipelines.yml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ variables:
66
agent.preferPowerShellOnContainers: true
77

88
schedules:
9-
- cron: "0 0 * * *"
9+
- cron: "0 9 * * *"
1010
displayName: Daily midnight build
1111
branches:
1212
include:
@@ -20,12 +20,26 @@ jobs:
2020

2121
strategy:
2222
matrix:
23+
Python 3.8 - Django 3.1:
24+
python.version: '3.8'
25+
tox.env: 'py38-django31'
26+
Python 3.7 - Django 3.1:
27+
python.version: '3.7'
28+
tox.env: 'py37-django31'
29+
Python 3.6 - Django 3.1:
30+
python.version: '3.6'
31+
tox.env: 'py36-django31'
32+
33+
Python 3.8 - Django 3.0:
34+
python.version: '3.8'
35+
tox.env: 'py38-django30'
2336
Python 3.7 - Django 3.0:
2437
python.version: '3.7'
2538
tox.env: 'py37-django30'
2639
Python 3.6 - Django 3.0:
2740
python.version: '3.6'
2841
tox.env: 'py36-django30'
42+
2943
Python 3.7 - Django 2.2:
3044
python.version: '3.7'
3145
tox.env: 'py37-django22'
@@ -62,12 +76,26 @@ jobs:
6276

6377
strategy:
6478
matrix:
79+
Python 3.8 - Django 3.1:
80+
python.version: '3.8'
81+
tox.env: 'py38-django31'
82+
Python 3.7 - Django 3.1:
83+
python.version: '3.7'
84+
tox.env: 'py37-django31'
85+
Python 3.6 - Django 3.1:
86+
python.version: '3.6'
87+
tox.env: 'py36-django31'
88+
89+
Python 3.8 - Django 3.0:
90+
python.version: '3.8'
91+
tox.env: 'py38-django30'
6592
Python 3.7 - Django 3.0:
6693
python.version: '3.7'
6794
tox.env: 'py37-django30'
6895
Python 3.6 - Django 3.0:
6996
python.version: '3.6'
7097
tox.env: 'py36-django30'
98+
7199
Python 3.7 - Django 2.2:
72100
python.version: '3.7'
73101
tox.env: 'py37-django22'
@@ -87,7 +115,7 @@ jobs:
87115
curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
88116
curl https://packages.microsoft.com/config/ubuntu/18.04/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
89117
sudo apt-get update
90-
sudo ACCEPT_EULA=Y apt-get install -y msodbcsql17 g++ unixodbc-dev
118+
sudo ACCEPT_EULA=Y apt-get install -y msodbcsql17 g++ unixodbc-dev libmemcached-dev
91119
displayName: Install SQL Server
92120
93121
- script: |

mssql/creation.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@
55
import os
66

77
from django.db.backends.base.creation import BaseDatabaseCreation
8+
from django import VERSION as django_version
89

910

1011
class DatabaseCreation(BaseDatabaseCreation):
1112

13+
def cursor(self):
14+
if django_version >= (3, 1):
15+
return self.connection._nodb_cursor()
16+
17+
return self.connection._nodb_connection.cursor()
18+
1219
def _destroy_test_db(self, test_database_name, verbosity):
1320
"""
1421
Internal implementation - remove the test db tables.
@@ -17,7 +24,7 @@ def _destroy_test_db(self, test_database_name, verbosity):
1724
# ourselves. Connect to the previous database (not the test database)
1825
# to do so, because it's not allowed to delete a database while being
1926
# connected to it.
20-
with self.connection._nodb_connection.cursor() as cursor:
27+
with self.cursor() as cursor:
2128
to_azure_sql_db = self.connection.to_azure_sql_db
2229
if not to_azure_sql_db:
2330
cursor.execute("ALTER DATABASE %s SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
@@ -39,7 +46,7 @@ def enable_clr(self):
3946
This function will not fail if current user doesn't have
4047
permissions to enable clr, and clr is already enabled
4148
"""
42-
with self._nodb_connection.cursor() as cursor:
49+
with self.cursor() as cursor:
4350
# check whether clr is enabled
4451
cursor.execute('''
4552
SELECT value FROM sys.configurations
@@ -89,7 +96,7 @@ def install_regex_clr(self, database_name):
8996

9097
self.enable_clr()
9198

92-
with self._nodb_connection.cursor() as cursor:
99+
with self.self.cursor() as cursor:
93100
for s in sql:
94101
cursor.execute(s)
95102

mssql/features.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ class DatabaseFeatures(BaseDatabaseFeatures):
2424
requires_literal_defaults = True
2525
requires_sqlparse_for_splitting = False
2626
supports_boolean_expr_in_select_clause = False
27+
supports_deferrable_unique_constraints = False
2728
supports_ignore_conflicts = False
2829
supports_index_on_text_field = False
30+
supports_json_field = False
2931
supports_paramstyle_pyformat = False
3032
supports_regex_backreferencing = True
3133
supports_sequence_reset = False

mssql/operations.py

Lines changed: 85 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77

88
from django.conf import settings
99
from django.db.backends.base.operations import BaseDatabaseOperations
10+
from django.db.models.expressions import Exists, ExpressionWrapper, RawSQL
11+
from django.db.models.sql.where import WhereNode
1012
from django.utils import timezone
1113
from django.utils.encoding import force_str
12-
14+
from django import VERSION as django_version
1315
import pytz
1416

1517

@@ -315,7 +317,33 @@ def savepoint_rollback_sql(self, sid):
315317
"""
316318
return "ROLLBACK TRANSACTION %s" % sid
317319

318-
def sql_flush(self, style, tables, sequences, allow_cascade=False):
320+
def _build_sequences(self, sequences, cursor):
321+
seqs = []
322+
for seq in sequences:
323+
cursor.execute("SELECT COUNT(*) FROM %s" % self.quote_name(seq["table"]))
324+
rowcnt = cursor.fetchone()[0]
325+
elem = {}
326+
if rowcnt:
327+
elem['start_id'] = 0
328+
else:
329+
elem['start_id'] = 1
330+
elem.update(seq)
331+
seqs.append(elem)
332+
return seqs
333+
334+
def _sql_flush_new(self, style, tables, *, reset_sequences=False, allow_cascade=False):
335+
if reset_sequences:
336+
return [
337+
sequence
338+
for sequence in self.connection.introspection.sequence_list()
339+
]
340+
341+
return []
342+
343+
def _sql_flush_old(self, style, tables, sequences, allow_cascade=False):
344+
return sequences
345+
346+
def sql_flush(self, style, tables, *args, **kwargs):
319347
"""
320348
Returns a list of SQL statements required to remove all data from
321349
the given database tables (without actually removing the tables
@@ -330,56 +358,50 @@ def sql_flush(self, style, tables, sequences, allow_cascade=False):
330358
The `allow_cascade` argument determines whether truncation may cascade
331359
to tables with foreign keys pointing the tables being truncated.
332360
"""
333-
if tables:
334-
# Cannot use TRUNCATE on tables that are referenced by a FOREIGN KEY
335-
# So must use the much slower DELETE
336-
from django.db import connections
337-
cursor = connections[self.connection.alias].cursor()
338-
# Try to minimize the risks of the braindeaded inconsistency in
339-
# DBCC CHEKIDENT(table, RESEED, n) behavior.
340-
seqs = []
341-
for seq in sequences:
342-
cursor.execute("SELECT COUNT(*) FROM %s" % self.quote_name(seq["table"]))
343-
rowcnt = cursor.fetchone()[0]
344-
elem = {}
345-
if rowcnt:
346-
elem['start_id'] = 0
347-
else:
348-
elem['start_id'] = 1
349-
elem.update(seq)
350-
seqs.append(elem)
351-
COLUMNS = "TABLE_NAME, CONSTRAINT_NAME"
352-
WHERE = "CONSTRAINT_TYPE not in ('PRIMARY KEY','UNIQUE')"
353-
cursor.execute(
354-
"SELECT {} FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE {}".format(COLUMNS, WHERE))
355-
fks = cursor.fetchall()
356-
sql_list = ['ALTER TABLE %s NOCHECK CONSTRAINT %s;' %
357-
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks]
358-
sql_list.extend(['%s %s %s;' % (style.SQL_KEYWORD('DELETE'), style.SQL_KEYWORD('FROM'),
359-
style.SQL_FIELD(self.quote_name(table))) for table in tables])
360-
361-
if self.connection.to_azure_sql_db and self.connection.sql_server_version < 2014:
362-
warnings.warn("Resetting identity columns is not supported "
363-
"on this versios of Azure SQL Database.",
364-
RuntimeWarning)
365-
else:
366-
# Then reset the counters on each table.
367-
sql_list.extend(['%s %s (%s, %s, %s) %s %s;' % (
368-
style.SQL_KEYWORD('DBCC'),
369-
style.SQL_KEYWORD('CHECKIDENT'),
370-
style.SQL_FIELD(self.quote_name(seq["table"])),
371-
style.SQL_KEYWORD('RESEED'),
372-
style.SQL_FIELD('%d' % seq['start_id']),
373-
style.SQL_KEYWORD('WITH'),
374-
style.SQL_KEYWORD('NO_INFOMSGS'),
375-
) for seq in seqs])
376-
377-
sql_list.extend(['ALTER TABLE %s CHECK CONSTRAINT %s;' %
378-
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks])
379-
return sql_list
380-
else:
361+
362+
if not tables:
381363
return []
382364

365+
if django_version >= (3, 1):
366+
sequences = self._sql_flush_new(style, tables, *args, **kwargs)
367+
else:
368+
sequences = self._sql_flush_old(style, tables, *args, **kwargs)
369+
370+
from django.db import connections
371+
cursor = connections[self.connection.alias].cursor()
372+
373+
seqs = self._build_sequences(sequences, cursor)
374+
375+
COLUMNS = "TABLE_NAME, CONSTRAINT_NAME"
376+
WHERE = "CONSTRAINT_TYPE not in ('PRIMARY KEY','UNIQUE')"
377+
cursor.execute(
378+
"SELECT {} FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE {}".format(COLUMNS, WHERE))
379+
fks = cursor.fetchall()
380+
sql_list = ['ALTER TABLE %s NOCHECK CONSTRAINT %s;' %
381+
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks]
382+
sql_list.extend(['%s %s %s;' % (style.SQL_KEYWORD('DELETE'), style.SQL_KEYWORD('FROM'),
383+
style.SQL_FIELD(self.quote_name(table))) for table in tables])
384+
385+
if self.connection.to_azure_sql_db and self.connection.sql_server_version < 2014:
386+
warnings.warn("Resetting identity columns is not supported "
387+
"on this versios of Azure SQL Database.",
388+
RuntimeWarning)
389+
else:
390+
# Then reset the counters on each table.
391+
sql_list.extend(['%s %s (%s, %s, %s) %s %s;' % (
392+
style.SQL_KEYWORD('DBCC'),
393+
style.SQL_KEYWORD('CHECKIDENT'),
394+
style.SQL_FIELD(self.quote_name(seq["table"])),
395+
style.SQL_KEYWORD('RESEED'),
396+
style.SQL_FIELD('%d' % seq['start_id']),
397+
style.SQL_KEYWORD('WITH'),
398+
style.SQL_KEYWORD('NO_INFOMSGS'),
399+
) for seq in seqs])
400+
401+
sql_list.extend(['ALTER TABLE %s CHECK CONSTRAINT %s;' %
402+
(self.quote_name(fk[0]), self.quote_name(fk[1])) for fk in fks])
403+
return sql_list
404+
383405
def start_transaction_sql(self):
384406
"""
385407
Returns the SQL statement required to start a transaction.
@@ -445,3 +467,16 @@ def time_trunc_sql(self, lookup_type, field_name):
445467
elif lookup_type == 'second':
446468
sql = "CONVERT(time, SUBSTRING(CONVERT(varchar, %s, 114), 0, 9))" % field_name
447469
return sql
470+
471+
def conditional_expression_supported_in_where_clause(self, expression):
472+
"""
473+
Following "Moved conditional expression wrapping to the Exact lookup" in django 3.1
474+
https://github.com/django/django/commit/37e6c5b79bd0529a3c85b8c478e4002fd33a2a1d
475+
"""
476+
if isinstance(expression, (Exists, WhereNode)):
477+
return True
478+
if isinstance(expression, ExpressionWrapper) and expression.conditional:
479+
return self.conditional_expression_supported_in_where_clause(expression.expression)
480+
if isinstance(expression, RawSQL) and expression.conditional:
481+
return True
482+
return False

mssql/schema.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
Statement as DjStatement,
1717
Table,
1818
)
19+
from django import VERSION as django_version
1920
from django.db.models import Index
2021
from django.db.models.fields import AutoField, BigAutoField
2122
from django.db.transaction import TransactionManagementError
@@ -687,7 +688,10 @@ def add_field(self, model, field):
687688
if self.connection.features.connection_persists_old_columns:
688689
self.connection.close()
689690

690-
def _create_unique_sql(self, model, columns, name=None, condition=None):
691+
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+
return None
694+
691695
def create_unique_name(*args, **kwargs):
692696
return self.quote_name(self._create_index_name(*args, **kwargs))
693697

@@ -697,20 +701,26 @@ def create_unique_name(*args, **kwargs):
697701
else:
698702
name = self.quote_name(name)
699703
columns = Columns(table, columns, self.quote_name)
704+
statement_args = {
705+
"deferrable": self._deferrable_constraint_sql(deferrable)
706+
} if django_version >= (3, 1) else {}
707+
700708
if condition:
701709
return Statement(
702710
self.sql_create_unique_index,
703711
table=table,
704712
name=name,
705713
columns=columns,
706714
condition=' WHERE ' + condition,
715+
**statement_args
707716
) if self.connection.features.supports_partial_indexes else None
708717
else:
709718
return Statement(
710719
self.sql_create_unique,
711720
table=table,
712721
name=name,
713722
columns=columns,
723+
**statement_args
714724
)
715725

716726
def _create_index_sql(self, model, fields, *, name=None, suffix='', using='',

testapp/settings.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@
3232

3333
SECRET_KEY = "django_tests_secret_key"
3434

35-
# Use a fast hasher to speed up tests.
3635
PASSWORD_HASHERS = [
37-
'django.contrib.auth.hashers.MD5PasswordHasher',
36+
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
3837
]
3938

4039
ENABLE_REGEX_TESTS = False
@@ -151,6 +150,7 @@
151150
'expressions.tests.FTimeDeltaTests.test_date_subquery_subtraction',
152151
'expressions.tests.FTimeDeltaTests.test_datetime_subquery_subtraction',
153152
'expressions.tests.FTimeDeltaTests.test_time_subquery_subtraction',
153+
'expressions.tests.BasicExpressionsTests.test_filtering_on_q_that_is_boolean',
154154
'migrations.test_operations.OperationTests.test_alter_field_reloads_state_on_fk_with_to_field_target_type_change',
155155
'migrations.test_operations.OperationTests.test_autofield__bigautofield_foreignfield_growth',
156156
'migrations.test_operations.OperationTests.test_smallfield_autofield_foreignfield_growth',
@@ -159,7 +159,35 @@
159159
'schema.tests.SchemaTests.test_alter_autofield_pk_to_bigautofield_pk_sequence_owner',
160160
'schema.tests.SchemaTests.test_alter_autofield_pk_to_smallautofield_pk_sequence_owner',
161161
'schema.tests.SchemaTests.test_alter_primary_key_quoted_db_table',
162-
'schema.tests.SchemaTests.test_alter_smallint_pk_to_smallautofield_pk'
162+
'schema.tests.SchemaTests.test_alter_smallint_pk_to_smallautofield_pk',
163+
164+
'annotations.tests.NonAggregateAnnotationTestCase.test_combined_expression_annotation_with_aggregation',
165+
'bulk_create.tests.BulkCreateTests.test_bulk_insert_nullable_fields',
166+
'db_functions.comparison.test_cast.CastTests.test_cast_to_integer',
167+
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_func',
168+
'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_iso_weekday_func',
169+
'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_func',
170+
'constraints.tests.CheckConstraintTests.test_database_constraint_expression',
171+
'constraints.tests.CheckConstraintTests.test_database_constraint_expressionwrapper',
172+
'constraints.tests.UniqueConstraintTests.test_database_constraint_with_condition',
173+
'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_iso_weekday_func',
174+
'datetimes.tests.DateTimesTests.test_datetimes_ambiguous_and_invalid_times',
175+
'expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_xor',
176+
'expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_xor_null',
177+
'inspectdb.tests.InspectDBTestCase.test_number_field_types',
178+
'inspectdb.tests.InspectDBTestCase.test_json_field',
179+
'model_fields.test_integerfield.PositiveBigIntegerFieldTests.test_backend_range_save',
180+
'model_fields.test_integerfield.PositiveBigIntegerFieldTests.test_coercing',
181+
'model_fields.test_integerfield.PositiveBigIntegerFieldTests.test_documented_range',
182+
'model_fields.test_integerfield.PositiveBigIntegerFieldTests.test_types',
183+
'ordering.tests.OrderingTests.test_default_ordering_by_f_expression',
184+
'ordering.tests.OrderingTests.test_order_by_nulls_first',
185+
'ordering.tests.OrderingTests.test_order_by_nulls_last',
186+
'queries.test_bulk_update.BulkUpdateTests.test_json_field',
187+
'queries.test_qs_combinators.QuerySetSetOperationTests.test_ordering_by_f_expression_and_alias',
188+
'queries.test_db_returning.ReturningValuesTests.test_insert_returning_multiple',
189+
'dbshell.tests.DbshellCommandTestCase.test_command_missing',
190+
'schema.tests.SchemaTests.test_char_field_pk_to_auto_field'
163191
]
164192

165193
REGEX_TESTS = ['lookup.tests.LookupTests.test_regex',

0 commit comments

Comments
 (0)