From 386d89ab55e620440d30590a8a104fe6d5eef830 Mon Sep 17 00:00:00 2001
From: Simon Charette <charette.s@gmail.com>
Date: Sat, 16 Mar 2019 13:48:59 -0400
Subject: [PATCH] Fixed #30258 -- Adjusted postgres schema value quoting of
 ranges.

Thanks Tilman Koschnick for the report and patch.
---
 django/db/backends/postgresql/schema.py  |  3 +-
 tests/postgres_tests/test_constraints.py | 35 ++++++++++++++++++++++++
 2 files changed, 37 insertions(+), 1 deletion(-)
 create mode 100644 tests/postgres_tests/test_constraints.py

diff --git a/django/db/backends/postgresql/schema.py b/django/db/backends/postgresql/schema.py
index 2dbd9e28a1..6c27bdc2ec 100644
--- a/django/db/backends/postgresql/schema.py
+++ b/django/db/backends/postgresql/schema.py
@@ -23,7 +23,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
     sql_delete_procedure = 'DROP FUNCTION %(procedure)s(%(param_types)s)'
 
     def quote_value(self, value):
-        return psycopg2.extensions.adapt(value)
+        # getquoted() returns a quoted byte string of the adapted value.
+        return psycopg2.extensions.adapt(value).getquoted().decode()
 
     def _field_indexes_sql(self, model, field):
         output = super()._field_indexes_sql(model, field)
diff --git a/tests/postgres_tests/test_constraints.py b/tests/postgres_tests/test_constraints.py
new file mode 100644
index 0000000000..0e09a1c546
--- /dev/null
+++ b/tests/postgres_tests/test_constraints.py
@@ -0,0 +1,35 @@
+from django.db import connection, transaction
+from django.db.models import Q
+from django.db.models.constraints import CheckConstraint
+from django.db.utils import IntegrityError
+
+from . import PostgreSQLTestCase
+from .models import RangesModel
+
+try:
+    from psycopg2.extras import NumericRange
+except ImportError:
+    pass
+
+
+class SchemaTests(PostgreSQLTestCase):
+    def get_constraints(self, table):
+        """Get the constraints on the table using a new cursor."""
+        with connection.cursor() as cursor:
+            return connection.introspection.get_constraints(cursor, table)
+
+    def test_check_constraint_range_value(self):
+        constraint_name = 'ints_between'
+        self.assertNotIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
+        constraint = CheckConstraint(
+            check=Q(ints__contained_by=NumericRange(10, 30)),
+            name=constraint_name,
+        )
+        with connection.schema_editor() as editor:
+            editor.add_constraint(RangesModel, constraint)
+        with connection.cursor() as cursor:
+            constraints = connection.introspection.get_constraints(cursor, RangesModel._meta.db_table)
+        self.assertIn(constraint_name, constraints)
+        with self.assertRaises(IntegrityError), transaction.atomic():
+            RangesModel.objects.create(ints=(20, 50))
+        RangesModel.objects.create(ints=(10, 30))