diff --git a/django/contrib/admin/static/admin/js/SelectFilter2.js b/django/contrib/admin/static/admin/js/SelectFilter2.js
index fde59d30293f..99efa1e0cabf 100644
--- a/django/contrib/admin/static/admin/js/SelectFilter2.js
+++ b/django/contrib/admin/static/admin/js/SelectFilter2.js
@@ -18,18 +18,6 @@ Requires core.js and SelectBox.js.
from_box.setAttribute('aria-labelledby', field_id + '_from_label');
from_box.setAttribute('aria-describedby', `${field_id}_helptext ${field_id}_choose_helptext`);
- for (const p of from_box.parentNode.getElementsByTagName('p')) {
- if (p.classList.contains("info")) {
- // Remove
, because it just gets in the way.
- from_box.parentNode.removeChild(p);
- } else if (p.classList.contains("help")) {
- // Move help text up to the top so it isn't below the select
- // boxes or wrapped off on the side to the right of the add
- // button:
- from_box.parentNode.insertBefore(p, from_box.parentNode.firstChild);
- }
- }
-
//
or
const selector_div = quickElement('div', from_box.parentNode);
// Make sure the selector div appears between the label and the add link.
diff --git a/django/contrib/postgres/apps.py b/django/contrib/postgres/apps.py
index a8ee3fbf4b7e..63fd9b274e2f 100644
--- a/django/contrib/postgres/apps.py
+++ b/django/contrib/postgres/apps.py
@@ -65,6 +65,11 @@ def ready(self):
3910: "django.contrib.postgres.fields.DateTimeRangeField",
3912: "django.contrib.postgres.fields.DateRangeField",
3926: "django.contrib.postgres.fields.BigIntegerRangeField",
+ # PostgreSQL OIDs may vary depending on the
+ # installation, especially for datatypes from
+ # extensions, e.g. "hstore". In such cases, the
+ # type_display attribute (psycopg 3.2+) should be used.
+ "hstore": "django.contrib.postgres.fields.HStoreField",
}
)
if conn.connection is not None:
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index 17ac417fff54..e2728afea5f4 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -8,6 +8,7 @@
import threading
import warnings
from contextlib import contextmanager
+from functools import lru_cache
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
@@ -29,6 +30,7 @@
raise ImproperlyConfigured("Error loading psycopg2 or psycopg module")
+@lru_cache
def psycopg_version():
version = Database.__version__.split(" ", 1)[0]
return get_version_tuple(version)
diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py
index 30edaf10dafd..791d729ea480 100644
--- a/django/db/backends/postgresql/introspection.py
+++ b/django/db/backends/postgresql/introspection.py
@@ -3,6 +3,7 @@
from django.db.backends.base.introspection import BaseDatabaseIntrospection
from django.db.backends.base.introspection import FieldInfo as BaseFieldInfo
from django.db.backends.base.introspection import TableInfo as BaseTableInfo
+from django.db.backends.postgresql.base import psycopg_version
from django.db.models import DB_CASCADE, DB_SET_DEFAULT, DB_SET_NULL, DO_NOTHING, Index
FieldInfo = namedtuple("FieldInfo", [*BaseFieldInfo._fields, "is_autofield", "comment"])
@@ -120,10 +121,19 @@ def get_table_description(self, cursor, table_name):
cursor.execute(
"SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name)
)
+
+ # PostgreSQL OIDs may vary depending on the installation, especially
+ # for datatypes from extensions, e.g. "hstore". In such cases, the
+ # type_display attribute (psycopg 3.2+) should be used.
+ type_display_available = psycopg_version() >= (3, 2)
return [
FieldInfo(
line.name,
- line.type_code,
+ (
+ line.type_display
+ if type_display_available and line.type_display == "hstore"
+ else line.type_code
+ ),
# display_size is always None on psycopg2.
line.internal_size if line.display_size is None else line.display_size,
line.internal_size,
diff --git a/docs/releases/6.1.txt b/docs/releases/6.1.txt
index e0edf6876af0..8c5594d5b69d 100644
--- a/docs/releases/6.1.txt
+++ b/docs/releases/6.1.txt
@@ -126,7 +126,9 @@ Minor features
:mod:`django.contrib.postgres`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-* ...
+* :djadmin:`inspectdb` now introspects
+ :class:`~django.contrib.postgres.fields.HStoreField` when ``psycopg`` 3.2+ is
+ installed and ``django.contrib.postgres`` is in :setting:`INSTALLED_APPS`.
:mod:`django.contrib.redirects`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/backends/postgresql/tests.py b/tests/backends/postgresql/tests.py
index f7e7d1e68ccf..e03c37cb574f 100644
--- a/tests/backends/postgresql/tests.py
+++ b/tests/backends/postgresql/tests.py
@@ -517,8 +517,11 @@ def test_lookup_cast_isnull_noop(self):
def test_correct_extraction_psycopg_version(self):
from django.db.backends.postgresql.base import Database, psycopg_version
+ psycopg_version.cache_clear()
with mock.patch.object(Database, "__version__", "4.2.1 (dt dec pq3 ext lo64)"):
+ self.addCleanup(psycopg_version.cache_clear)
self.assertEqual(psycopg_version(), (4, 2, 1))
+ psycopg_version.cache_clear()
with mock.patch.object(
Database, "__version__", "4.2b0.dev1 (dt dec pq3 ext lo64)"
):
diff --git a/tests/postgres_tests/test_introspection.py b/tests/postgres_tests/test_introspection.py
index 73c426d1bafc..1ae940fdb91a 100644
--- a/tests/postgres_tests/test_introspection.py
+++ b/tests/postgres_tests/test_introspection.py
@@ -33,3 +33,16 @@ def test_range_fields(self):
"null=True)",
],
)
+
+ def test_hstore_field(self):
+ from django.db.backends.postgresql.base import psycopg_version
+
+ if psycopg_version() < (3, 2):
+ self.skipTest("psycopg 3.2+ is required.")
+ self.assertFieldsInModel(
+ "postgres_tests_hstoremodel",
+ [
+ "field = django.contrib.postgres.fields.HStoreField(blank=True, "
+ "null=True)",
+ ],
+ )