diff --git a/django/bin/django-admin.py b/django/bin/django-admin.py index f518cdc463..594b0f11db 100755 --- a/django/bin/django-admin.py +++ b/django/bin/django-admin.py @@ -1,5 +1,21 @@ #!/usr/bin/env python +# When the django-admin.py deprecation ends, remove this script. +import warnings + from django.core import management +try: + from django.utils.deprecation import RemovedInDjango40Warning +except ImportError: + raise ImportError( + 'django-admin.py was deprecated in Django 3.1 and removed in Django ' + '4.0. Please manually remove this script from your virtual environment ' + 'and use django-admin instead.' + ) + if __name__ == "__main__": + warnings.warn( + 'django-admin.py is deprecated in favor of django-admin.', + RemovedInDjango40Warning, + ) management.execute_from_command_line() diff --git a/docs/faq/troubleshooting.txt b/docs/faq/troubleshooting.txt index f90d0e8e6e..3a5c47de55 100644 --- a/docs/faq/troubleshooting.txt +++ b/docs/faq/troubleshooting.txt @@ -14,15 +14,9 @@ Problems running ``django-admin`` ----------------------------------- :doc:`django-admin ` should be on your system path if you -installed Django via ``pip``. If it's not on your path, you can find it in -``site-packages/django/bin``, where ``site-packages`` is a directory within -your Python installation. Consider symlinking to :doc:`django-admin -` from some place on your path, such as -:file:`/usr/local/bin`. - -If ``django-admin`` doesn't work but ``django-admin.py`` does, you're probably -using a version of Django that doesn't match the version of this documentation. -``django-admin`` is new in Django 1.7. +installed Django via ``pip``. If it's not in your path, ensure you have your +virtual environment activated and you can try running the equivalent command +``python -m django``. macOS permissions ----------------- diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt index af04ad317a..3a049046fe 100644 --- a/docs/internals/contributing/writing-documentation.txt +++ b/docs/internals/contributing/writing-documentation.txt @@ -287,7 +287,7 @@ define some extra description units: :ticket:`12345` Django's documentation uses a custom ``console`` directive for documenting -command-line examples involving ``django-admin.py``, ``manage.py``, ``python``, +command-line examples involving ``django-admin``, ``manage.py``, ``python``, etc.). In the HTML documentation, it renders a two-tab UI, with one tab showing a Unix-style command prompt and a second tab showing a Windows prompt. diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 044d35bfd5..9aa90f32a9 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -42,6 +42,8 @@ details on these changes. * The ``django.db.models.query_utils.InvalidQuery`` exception class will be removed. +* The ``django-admin.py`` entry point will be removed. + See the :ref:`Django 3.1 release notes ` for more details on these changes. diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index c08fe44346..58fb34e821 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -11,14 +11,8 @@ does the same thing as ``django-admin`` but also sets the project's ``settings.py`` file. The ``django-admin`` script should be on your system path if you installed -Django via ``pip``. If it's not on your path, you can find it in -``site-packages/django/bin`` within your Python installation. Consider -symlinking it from some place on your path, such as ``/usr/local/bin``. - -For Windows users, who do not have symlinking functionality available, you can -copy ``django-admin.exe`` to a location on your existing path or edit the -``PATH`` settings (under ``Settings - Control Panel - System - Advanced - -Environment...``) to point to its installed location. +Django via ``pip``. If it's not in your path, ensure you have your virtual +environment activated. Generally, when working on a single Django project, it's easier to use ``manage.py`` than ``django-admin``. If you need to switch between multiple diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt index ec107bf29b..ae266e877d 100644 --- a/docs/releases/3.1.txt +++ b/docs/releases/3.1.txt @@ -384,6 +384,9 @@ Miscellaneous :class:`~django.core.exceptions.FieldDoesNotExist` and :class:`~django.core.exceptions.FieldError`. +* The ``django-admin.py`` entry point is deprecated in favor of + ``django-admin``. + .. _removed-features-3.1: Features removed in 3.1 diff --git a/extras/django_bash_completion b/extras/django_bash_completion index 3c2f14c263..fa77d59aff 100755 --- a/extras/django_bash_completion +++ b/extras/django_bash_completion @@ -1,6 +1,5 @@ -# ######################################################################### -# This bash script adds tab-completion feature to django-admin.py and -# manage.py. +# ############################################################################# +# This bash script adds tab-completion feature to django-admin and manage.py. # # Testing it out without installing # ================================= @@ -37,6 +36,7 @@ _django_completion() COMP_CWORD=$COMP_CWORD \ DJANGO_AUTO_COMPLETE=1 $1 ) ) } +# When the django-admin.py deprecation ends, remove django-admin.py. complete -F _django_completion -o default django-admin.py manage.py django-admin _python_django_completion() diff --git a/setup.cfg b/setup.cfg index d8fac7076f..db0387c775 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,6 +34,7 @@ project_urls = [options] python_requires = >=3.6 packages = find: +# When the django-admin.py deprecation ends, remove "scripts". scripts = django/bin/django-admin.py include_package_data = true zip_safe = false diff --git a/tests/admin_scripts/test_django_admin_py.py b/tests/admin_scripts/test_django_admin_py.py new file mode 100644 index 0000000000..56f5f0c1b5 --- /dev/null +++ b/tests/admin_scripts/test_django_admin_py.py @@ -0,0 +1,37 @@ +import subprocess +import sys +from pathlib import Path + +import django +from django.test import SimpleTestCase + + +class DeprecationTests(SimpleTestCase): + DEPRECATION_MESSAGE = ( + b'RemovedInDjango40Warning: django-admin.py is deprecated in favor of ' + b'django-admin.' + ) + + def _run_test(self, args): + p = subprocess.run( + [sys.executable, *args], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + return p.stdout, p.stderr + + def test_django_admin_py_deprecated(self): + django_admin_py = Path(django.__file__).parent / 'bin' / 'django-admin.py' + _, err = self._run_test(['-Wd', django_admin_py, '--version']) + self.assertIn(self.DEPRECATION_MESSAGE, err) + + def test_main_not_deprecated(self): + _, err = self._run_test(['-Wd', '-m', 'django', '--version']) + self.assertNotIn(self.DEPRECATION_MESSAGE, err) + + def test_django_admin_py_equivalent_main(self): + django_admin_py = Path(django.__file__).parent / 'bin' / 'django-admin.py' + django_admin_py_out, _ = self._run_test([django_admin_py, '--version']) + django_out, _ = self._run_test(['-m', 'django', '--version']) + self.assertEqual(django_admin_py_out, django_out) diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index a0b7770757..6266619216 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -14,7 +14,6 @@ import unittest from io import StringIO from unittest import mock -import django from django import conf, get_version from django.conf import settings from django.core.management import ( @@ -125,8 +124,7 @@ class AdminScriptTestCase(SimpleTestCase): return p.stdout, p.stderr def run_django_admin(self, args, settings_file=None): - script_dir = os.path.abspath(os.path.join(os.path.dirname(django.__file__), 'bin')) - return self.run_test([os.path.join(script_dir, 'django-admin.py'), *args], settings_file) + return self.run_test(['-m', 'django', *args], settings_file) def run_manage(self, args, settings_file=None, manage_py=None): template_manage_py = ( @@ -1898,7 +1896,12 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase): # running again.. out, err = self.run_django_admin(args) self.assertNoOutput(out) - self.assertOutput(err, "already exists") + self.assertOutput( + err, + "CommandError: 'testproject' conflicts with the name of an " + "existing Python module and cannot be used as a project name. " + "Please try another name.", + ) def test_invalid_project_name(self): "Make sure the startproject management command validates a project name" @@ -2160,8 +2163,10 @@ class StartApp(AdminScriptTestCase): ) def test_overlaying_app(self): - self.run_django_admin(['startapp', 'app1']) - out, err = self.run_django_admin(['startapp', 'app2', 'app1']) + # Use a subdirectory so it is outside the PYTHONPATH. + os.makedirs(os.path.join(self.test_dir, 'apps/app1')) + self.run_django_admin(['startapp', 'app1', 'apps/app1']) + out, err = self.run_django_admin(['startapp', 'app2', 'apps/app1']) self.assertOutput( err, "already exists. Overlaying an app into an existing directory " @@ -2261,11 +2266,6 @@ class Dumpdata(AdminScriptTestCase): class MainModule(AdminScriptTestCase): """python -m django works like django-admin.""" - def test_runs_django_admin(self): - cmd_out, _ = self.run_django_admin(['--version']) - mod_out, _ = self.run_test(['-m', 'django', '--version']) - self.assertEqual(mod_out, cmd_out) - def test_program_name_in_help(self): out, err = self.run_test(['-m', 'django', 'help']) self.assertOutput(out, "Type 'python -m django help ' for help on a specific subcommand.")