diff --git a/django/core/management/base.py b/django/core/management/base.py index 68bcb188e56..869a11bce93 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -18,6 +18,7 @@ import django from django.core import checks from django.core.exceptions import ImproperlyConfigured from django.core.management.color import color_style, no_style +from django.db import connections from django.utils.deprecation import RemovedInDjango19Warning, RemovedInDjango20Warning from django.utils.encoding import force_str @@ -398,6 +399,8 @@ class BaseCommand(object): else: self.stderr.write('%s: %s' % (e.__class__.__name__, e)) sys.exit(1) + finally: + connections.close_all() def execute(self, *args, **options): """ diff --git a/django/db/utils.py b/django/db/utils.py index acb90c0a1ad..a355bbe466d 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -252,6 +252,14 @@ class ConnectionHandler(object): def all(self): return [self[alias] for alias in self] + def close_all(self): + for alias in self: + try: + connection = getattr(self._connections, alias) + except AttributeError: + continue + connection.close() + class ConnectionRouter(object): def __init__(self, routers=None): diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 7379e1da8f6..507aed65844 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -352,6 +352,9 @@ Logging Management Commands ^^^^^^^^^^^^^^^^^^^ +* Database connections are now always closed after a management command called + from the command line has finished doing its job. + * :djadmin:`dumpdata` now has the option :djadminopt:`--output` which allows specifying the file to which the serialized data is written. diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index 518c83b4bef..086e5365a18 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -25,7 +25,7 @@ from django.core.management import BaseCommand, CommandError, call_command, colo from django.utils.encoding import force_text from django.utils._os import npath, upath from django.utils.six import StringIO -from django.test import LiveServerTestCase, TestCase, override_settings +from django.test import LiveServerTestCase, TestCase, mock, override_settings from django.test.runner import DiscoverRunner @@ -1581,6 +1581,18 @@ class CommandTypes(AdminScriptTestCase): with self.assertRaises(SystemExit): command.run_from_argv(['', '']) + def test_run_from_argv_closes_connections(self): + """ + A command called from the command line should close connections after + being executed (#21255). + """ + command = BaseCommand(stderr=StringIO()) + command.handle = lambda *args, **kwargs: args + with mock.patch('django.core.management.base.connections') as mock_connections: + command.run_from_argv(['', '']) + # Test connections have been closed + self.assertTrue(mock_connections.close_all.called) + def test_noargs(self): "NoArg Commands can be executed" args = ['noargs_command']