Skip to content

Commit

Permalink
Add support to built-in django-admin command
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanhiebert committed Dec 21, 2024
1 parent 73e6cd1 commit 1ca771f
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 36 deletions.
5 changes: 5 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
2.3 (2024-12-20)
++++++++++++++++

* Add configuration to the built-in ``django-admin`` command.

2.2 (2024-12-19)
++++++++++++++++

Expand Down
1 change: 1 addition & 0 deletions django_cmd.pths
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import django_cmd; print('hi'); django_cmd.patch_django()
16 changes: 13 additions & 3 deletions django_cmd.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import configparser
import os
import sys
from functools import wraps
from pathlib import Path

try:
Expand All @@ -9,10 +10,10 @@
# Python < 3.11
import tomli as tomllib

from django.core.management import execute_from_command_line
import django.core.management


def main():
def configure():
"""Run Django, getting the default from a file if needed."""
settings_module = None

Expand All @@ -37,4 +38,13 @@ def main():
if settings_module == os.environ["DJANGO_SETTINGS_MODULE"]:
sys.path.insert(0, os.getcwd())

execute_from_command_line(sys.argv)

@wraps(django.core.management.ManagementUtility, updated=())
class ConfiguredManagementUtility(django.core.management.ManagementUtility):
def execute(self):
configure()
return super().execute()


def patch_django():
django.core.management.ManagementUtility = ConfiguredManagementUtility
52 changes: 21 additions & 31 deletions django_cmd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from django_cmd import main
from django_cmd import configure


@contextmanager
Expand All @@ -21,95 +21,85 @@ def restore_environ(keys):


@restore_environ(["DJANGO_SETTINGS_MODULE"])
def test_main_passthru(monkeypatch, mocker, tmpdir):
def test_configure_passthru(monkeypatch, tmpdir):
"""It shouldn't change a given DJANGO_SETTINGS_MODULE."""
cmd = mocker.patch("django_cmd.execute_from_command_line")
monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "spam.eggs")
content = "[django]\nsettings_module = ball.yarn\n"
tmpdir.chdir()
tmpdir.join("setup.cfg").write(content.encode("utf-8"))
main()
configure()
assert os.environ.get("DJANGO_SETTINGS_MODULE") == "spam.eggs"
assert cmd.called


@restore_environ(["DJANGO_SETTINGS_MODULE"])
def test_main_from_pyproject_toml(mocker, tmpdir):
def test_configure_from_pyproject_toml(tmpdir):
"""Read settings module path from toml file."""
cmd = mocker.patch("django_cmd.execute_from_command_line")
content = '[tool.django]\nsettings_module = "ball.yarn"\n'
tmpdir.chdir()
tmpdir.join("pyproject.toml").write(content.encode("utf-8"))
main()
configure()
assert os.environ.get("DJANGO_SETTINGS_MODULE") == "ball.yarn"
assert cmd.called


@restore_environ(["DJANGO_SETTINGS_MODULE"])
def test_main_from_pyproject_toml_nosetting(mocker, tmpdir):
def test_configure_from_pyproject_toml_nosetting(mocker, tmpdir):
"""Handle if there's a tool.django section with no settings module."""
cmd = mocker.patch("django_cmd.execute_from_command_line")
content = '[tool.django]\nsomesetting = "notrelevant"\n'
tmpdir.chdir()
tmpdir.join("pyproject.toml").write(content.encode("utf-8"))
main()
configure()
assert "DJANGO_SETTINGS_MODULE" not in os.environ
assert cmd.called


@restore_environ(["DJANGO_SETTINGS_MODULE"])
def test_main_from_pyproject_toml_nodjango(mocker, tmpdir):
def test_main_from_pyproject_toml_nodjango(tmpdir):
"""Handle if there's no tool.django section."""
cmd = mocker.patch("django_cmd.execute_from_command_line")
content = '[project]\nname = "ball"\n'
tmpdir.chdir()
tmpdir.join("pyproject.toml").write(content.encode("utf-8"))
main()
configure()
assert "DJANGO_SETTINGS_MODULE" not in os.environ
assert cmd.called


@restore_environ(["DJANGO_SETTINGS_MODULE"])
def test_main_from_setup_cfg(mocker, tmpdir):
def test_configure_from_setup_cfg(tmpdir):
"""Read settings module path from config file."""
cmd = mocker.patch("django_cmd.execute_from_command_line")
content = "[django]\nsettings_module = ball.yarn\n"
tmpdir.chdir()
tmpdir.join("setup.cfg").write(content.encode("utf-8"))
main()
configure()
assert os.environ.get("DJANGO_SETTINGS_MODULE") == "ball.yarn"
assert cmd.called


@restore_environ(["DJANGO_SETTINGS_MODULE"])
def test_main_no_configfile(mocker, tmpdir):
def test_configure_no_configfile(tmpdir):
"""Try to read settings module, but fail and still run command."""
cmd = mocker.patch("django_cmd.execute_from_command_line")
tmpdir.chdir()
main()
configure()
assert "DJANGO_SETTINGS_MODULE" not in os.environ
assert cmd.called


@pytest.mark.parametrize("command", ["django", "django-admin"])
@restore_environ(["DJANGO_SETTINGS_MODULE"])
def test_new_project(tmpdir):
def test_new_project(command, tmpdir):
"""Should be able to use with a new project."""
tmpdir.chdir()
subprocess.run(["django", "startproject", "myproject", "."], check=True)
subprocess.run([command, "startproject", "myproject", "."], check=True)
config = '[tool.django]\nsettings_module = "myproject.settings"\n'
tmpdir.join("pyproject.toml").write(config.encode("utf-8"))
subprocess.run(["django", "check"], check=True)
subprocess.run([command, "check"], check=True)


@pytest.mark.skipif(
os.environ.get("TOX"),
reason="Doesn't release the port quickly enough to run multiple times in quick succession with tox.",
)
@pytest.mark.parametrize("command", ["django", "django-admin"])
@restore_environ(["DJANGO_SETTINGS_MODULE"])
def test_runserver(tmpdir):
def test_runserver(command, tmpdir):
"""Should be able to run the development server for several seconds."""
tmpdir.chdir()
subprocess.run(["django", "startproject", "myproject", "."], check=True)
subprocess.run([command, "startproject", "myproject", "."], check=True)
config = '[tool.django]\nsettings_module = "myproject.settings"\n'
tmpdir.join("pyproject.toml").write(config.encode("utf-8"))
with pytest.raises(subprocess.TimeoutExpired):
Expand All @@ -119,4 +109,4 @@ def test_runserver(tmpdir):
# 2 seems to be OK, but to make it hopefully more reliable
# we'll use 3 seconds. Otherwise this might not break even
# if the functionality does.
subprocess.run(["django", "runserver"], check=True, timeout=3)
subprocess.run([command, "runserver"], check=True, timeout=3)
14 changes: 12 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "django-cmd"
version = "2.2"
version = "2.3"
description = "Have a django command"
authors = [{ name = "Ryan Hiebert", email = "ryan@ryanhiebert.com" }]
license = "MIT"
Expand Down Expand Up @@ -40,7 +40,7 @@ homepage = "https://github.com/ryanhiebert/django-cmd"
repository = "https://github.com/ryanhiebert/django-cmd"

[project.scripts]
django = "django_cmd:main"
django = "django.core.management:execute_from_command_line"

[build-system]
requires = ["hatchling"]
Expand All @@ -52,3 +52,13 @@ dev = ["pytest", "pytest-mock", "coverage", "ruff", "isort"]
[tool.coverage.run]
branch = true
source = "django_cmd"

# [tool.hatch.build]
# artifacts = ["django_cmd.pth"]

[tool.hatch.build.hooks.autorun]
dependencies = ["hatch-autorun"]
code = """
import django_cmd
django_cmd.patch_django()
"""

0 comments on commit 1ca771f

Please sign in to comment.