Fixed #32292 -- Added support for connection by service name to PostgreSQL.

This commit is contained in:
Hasan Ramezani 2020-12-23 23:39:43 +01:00 committed by Mariusz Felisiak
parent f054468cac
commit dcb3ad3319
6 changed files with 92 additions and 7 deletions

View File

@ -152,10 +152,14 @@ class DatabaseWrapper(BaseDatabaseWrapper):
def get_connection_params(self):
settings_dict = self.settings_dict
# None may be used to connect to the default 'postgres' db
if settings_dict['NAME'] == '':
if (
settings_dict['NAME'] == '' and
not settings_dict.get('OPTIONS', {}).get('service')
):
raise ImproperlyConfigured(
"settings.DATABASES is improperly configured. "
"Please supply the NAME value.")
"Please supply the NAME or OPTIONS['service'] value."
)
if len(settings_dict['NAME'] or '') > self.ops.max_name_length():
raise ImproperlyConfigured(
"The database name '%s' (%d characters) is longer than "
@ -166,10 +170,19 @@ class DatabaseWrapper(BaseDatabaseWrapper):
self.ops.max_name_length(),
)
)
conn_params = {
'database': settings_dict['NAME'] or 'postgres',
**settings_dict['OPTIONS'],
}
conn_params = {}
if settings_dict['NAME']:
conn_params = {
'database': settings_dict['NAME'],
**settings_dict['OPTIONS'],
}
elif settings_dict['NAME'] is None:
# Connect to the default 'postgres' db.
settings_dict.get('OPTIONS', {}).pop('service', None)
conn_params = {'database': 'postgres', **settings_dict['OPTIONS']}
else:
conn_params = {**settings_dict['OPTIONS']}
conn_params.pop('isolation_level', None)
if settings_dict['USER']:
conn_params['user'] = settings_dict['USER']

View File

@ -16,6 +16,7 @@ class DatabaseClient(BaseDatabaseClient):
dbname = settings_dict.get('NAME') or 'postgres'
user = settings_dict.get('USER')
passwd = settings_dict.get('PASSWORD')
service = options.get('service')
sslmode = options.get('sslmode')
sslrootcert = options.get('sslrootcert')
sslcert = options.get('sslcert')
@ -33,6 +34,8 @@ class DatabaseClient(BaseDatabaseClient):
env = {}
if passwd:
env['PGPASSWORD'] = str(passwd)
if service:
env['PGSERVICE'] = str(service)
if sslmode:
env['PGSSLMODE'] = str(sslmode)
if sslrootcert:

View File

@ -108,11 +108,43 @@ required, though the latest release is recommended.
.. _psycopg2: https://www.psycopg.org/
.. _postgresql-connection-settings:
PostgreSQL connection settings
-------------------------------
See :setting:`HOST` for details.
To connect using a service name from the `connection service file`_, you must
specify it in the :setting:`OPTIONS` part of your database configuration in
:setting:`DATABASES`:
.. code-block:: python
:caption: settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'OPTIONS': {'service': 'my_service'},
}
}
.. code-block:: text
:caption: .pg_service.conf
[my_service]
host=localhost
user=USER
dbname=NAME
password=PASSWORD
port=5432
.. _connection service file: https://www.postgresql.org/docs/current/libpq-pgservice.html
.. versionchanged:: 4.0
Support for connecting by a service name was added.
Optimizing PostgreSQL's configuration
-------------------------------------

View File

@ -64,7 +64,8 @@ Minor features
:mod:`django.contrib.postgres`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* ...
* The PostgreSQL backend now supports connecting by a service name. See
:ref:`postgresql-connection-settings` for more details.
:mod:`django.contrib.redirects`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -68,6 +68,36 @@ class Tests(TestCase):
with self.assertRaisesMessage(ImproperlyConfigured, msg):
DatabaseWrapper(settings).get_connection_params()
def test_database_name_empty(self):
from django.db.backends.postgresql.base import DatabaseWrapper
settings = connection.settings_dict.copy()
settings['NAME'] = ''
msg = (
"settings.DATABASES is improperly configured. Please supply the "
"NAME or OPTIONS['service'] value."
)
with self.assertRaisesMessage(ImproperlyConfigured, msg):
DatabaseWrapper(settings).get_connection_params()
def test_service_name(self):
from django.db.backends.postgresql.base import DatabaseWrapper
settings = connection.settings_dict.copy()
settings['OPTIONS'] = {'service': 'my_service'}
settings['NAME'] = ''
params = DatabaseWrapper(settings).get_connection_params()
self.assertEqual(params['service'], 'my_service')
self.assertNotIn('database', params)
def test_service_name_default_db(self):
# None is used to connect to the default 'postgres' db.
from django.db.backends.postgresql.base import DatabaseWrapper
settings = connection.settings_dict.copy()
settings['NAME'] = None
settings['OPTIONS'] = {'service': 'django_test'}
params = DatabaseWrapper(settings).get_connection_params()
self.assertEqual(params['database'], 'postgres')
self.assertNotIn('service', params)
def test_connect_and_rollback(self):
"""
PostgreSQL shouldn't roll back SET TIME ZONE, even if the first

View File

@ -67,6 +67,12 @@ class PostgreSqlDbshellCommandTestCase(SimpleTestCase):
)
)
def test_service(self):
self.assertEqual(
self.settings_to_cmd_args_env({'OPTIONS': {'service': 'django_test'}}),
(['psql', 'postgres'], {'PGSERVICE': 'django_test'}),
)
def test_column(self):
self.assertEqual(
self.settings_to_cmd_args_env({