diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py index afd035df77..c20d7b659e 100644 --- a/django/db/backends/postgresql/introspection.py +++ b/django/db/backends/postgresql/introspection.py @@ -44,11 +44,11 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): SELECT c.relname, c.relkind FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind IN ('r', 'v') + WHERE c.relkind IN ('f', 'r', 'v') AND n.nspname NOT IN ('pg_catalog', 'pg_toast') AND pg_catalog.pg_table_is_visible(c.oid) """) - mapping = {'r': 't', 'v': 'v'} + mapping = {'f': 't', 'r': 't', 'v': 'v'} return [ TableInfo(row[0], mapping[row[1]]) for row in cursor.fetchall() if row[0] not in self.ignored_tables diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index b94bcbea9e..b6a2319341 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -395,6 +395,18 @@ table's lifecycle, you'll need to change the :attr:`~django.db.models.Options.managed` option to ``True`` (or simply remove it because ``True`` is its default value). +Database-specific notes +~~~~~~~~~~~~~~~~~~~~~~~ + +PostgreSQL +^^^^^^^^^^ + +* Models are created for foreign tables. + +.. versionchanged:: 2.2 + + Support for foreign tables was added. + .. django-admin-option:: --database DATABASE Specifies the database to introspect. Defaults to ``default``. diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt index b308ec95d3..95aded1920 100644 --- a/docs/releases/2.2.txt +++ b/docs/releases/2.2.txt @@ -177,6 +177,8 @@ Management Commands * The new :option:`--force-color` option forces colorization of the command output. +* :djadmin:`inspectdb` now creates models for foreign tables on PostgreSQL. + Migrations ~~~~~~~~~~ diff --git a/tests/inspectdb/tests.py b/tests/inspectdb/tests.py index e994b2cb74..83c49eb7e3 100644 --- a/tests/inspectdb/tests.py +++ b/tests/inspectdb/tests.py @@ -309,3 +309,32 @@ class InspectDBTransactionalTests(TransactionTestCase): finally: with connection.cursor() as cursor: cursor.execute('DROP VIEW inspectdb_people_view') + + @skipUnless(connection.vendor == 'postgresql', 'PostgreSQL specific SQL') + def test_foreign_data_wrapper(self): + with connection.cursor() as cursor: + cursor.execute('CREATE EXTENSION IF NOT EXISTS file_fdw') + cursor.execute('CREATE SERVER inspectdb_server FOREIGN DATA WRAPPER file_fdw') + cursor.execute('''\ + CREATE FOREIGN TABLE inspectdb_iris_foreign_table ( + petal_length real, + petal_width real, + sepal_length real, + sepal_width real + ) SERVER inspectdb_server OPTIONS ( + filename '/dev/null' + ) + ''') + out = StringIO() + foreign_table_model = 'class InspectdbIrisForeignTable(models.Model):' + foreign_table_managed = 'managed = False' + try: + call_command('inspectdb', stdout=out) + output = out.getvalue() + self.assertIn(foreign_table_model, output) + self.assertIn(foreign_table_managed, output) + finally: + with connection.cursor() as cursor: + cursor.execute('DROP FOREIGN TABLE IF EXISTS inspectdb_iris_foreign_table') + cursor.execute('DROP SERVER IF EXISTS inspectdb_server') + cursor.execute('DROP EXTENSION IF EXISTS file_fdw')