Skip to content

Commit 043b3bd

Browse files
committed
Rework handling of Django commands
1 parent 93a204c commit 043b3bd

File tree

2 files changed

+45
-103
lines changed

2 files changed

+45
-103
lines changed

ellar_django/commands.py

Lines changed: 33 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,45 @@
1-
import sys
21
import typing as t
32

43
import django
54
import ellar_cli.click as click
5+
from django.core.management import get_commands, load_command_class
6+
7+
BLACKLISTED_COMMANDS: t.Final[t.Set[str]] = {
8+
"runserver",
9+
"startapp",
10+
"startproject",
11+
}
612

713

814
class _CommandItem(t.NamedTuple):
915
name: str
1016
description: str
1117

1218

13-
_django_support_commands: t.List[_CommandItem] = [
14-
_CommandItem(
15-
name="migrate",
16-
description="Synchronizes the database state with the current set of models and migrations",
17-
),
18-
_CommandItem(
19-
name="makemigrations",
20-
description="Creates new migrations based on the changes detected to your models.",
21-
),
22-
_CommandItem(
23-
name="check",
24-
description="inspects the entire Django project for common problems",
25-
),
26-
_CommandItem(
27-
name="createcachetable",
28-
description="Creates the cache tables for use with the database cache backend",
29-
),
30-
_CommandItem(
31-
name="dbshell",
32-
description="Runs the command-line client for the database engine",
33-
),
34-
_CommandItem(
35-
name="diffsettings",
36-
description="Displays differences between the current settings and default settings",
37-
),
38-
_CommandItem(
39-
name="dumpdata",
40-
description="Outputs to standard output all data in the database",
41-
),
42-
_CommandItem(
43-
name="flush",
44-
description="Removes all data from the database and re-executes any post-synchronization handlers",
45-
),
46-
_CommandItem(
47-
name="inspectdb", description="Introspects the database tables in the database"
48-
),
49-
_CommandItem(
50-
name="loaddata",
51-
description="Searches for and loads the contents into the database.",
52-
),
53-
_CommandItem(
54-
name="optimizemigration",
55-
description="Optimizes the operations for the named migration and overrides the existing file",
56-
),
57-
_CommandItem(
58-
name="showmigrations", description="Shows all migrations in a project."
59-
),
60-
_CommandItem(
61-
name="sqlflush",
62-
description="Prints the SQL statements that would be executed for the flush command",
63-
),
64-
_CommandItem(
65-
name="sqlmigrate", description="Prints the SQL for the named migration."
66-
),
67-
_CommandItem(
68-
name="sqlsequencereset",
69-
description="Prints the SQL statements for resetting sequences for the given app name(s)",
70-
),
71-
_CommandItem(
72-
name="squashmigrations", description="Squashes the migrations for app_label"
73-
),
74-
_CommandItem(
75-
name="startapp", description="Creates a Django app directory structure"
76-
),
77-
_CommandItem(
78-
name="changepassword",
79-
description="This command is only available if Django’s authentication system",
80-
),
81-
_CommandItem(
82-
name="createsuperuser",
83-
description="Creates a superuser account (a user who has all permissions)",
84-
),
85-
_CommandItem(
86-
name="collectstatic", description="Expose static files to STATIC_ROOT folder"
87-
),
88-
_CommandItem(name="findstatic", description="Search for a static file location"),
89-
_CommandItem(
90-
name="clearsessions",
91-
description="Can be run as a cron job or directly to clean out expired sessions.",
92-
),
93-
]
19+
def get_command_description(command_name: str) -> str:
20+
module = get_commands()[command_name]
21+
CommandClass = load_command_class(module, command_name)
22+
return CommandClass.help or ""
23+
24+
25+
def generate_command_list() -> t.List[_CommandItem]:
26+
commands = get_commands()
27+
command_list: t.List[_CommandItem] = []
28+
for command in commands:
29+
if command in BLACKLISTED_COMMANDS:
30+
continue
31+
description = get_command_description(command)
32+
command_list.append(_CommandItem(name=command, description=description))
33+
return command_list
34+
35+
36+
_django_support_commands = generate_command_list()
9437

9538

9639
def version_callback(ctx: click.Context, _: t.Any, value: bool) -> None:
9740
if value:
9841
click.echo(f"Django Version: {django.__version__}")
99-
raise click.Exit(0)
42+
ctx.exit()
10043

10144

10245
@click.group(
@@ -118,15 +61,6 @@ def django_command(ctx: click.Context) -> None:
11861

11962

12063
def _add_django_command(command_item: _CommandItem) -> None:
121-
def help_callback(ctx: click.Context, _: t.Any, value: bool) -> None:
122-
from django.core.management import execute_from_command_line
123-
124-
if value:
125-
args = ["manage.py", command_item.name, "--help"]
126-
127-
execute_from_command_line(args)
128-
raise click.Exit(0)
129-
13064
@django_command.command(
13165
name=command_item.name,
13266
help=command_item.description,
@@ -136,21 +70,17 @@ def help_callback(ctx: click.Context, _: t.Any, value: bool) -> None:
13670
@click.option(
13771
"-h",
13872
"--help",
139-
callback=help_callback,
140-
help="Show the version and exit.",
73+
help="Show the command help.",
14174
is_flag=True,
14275
expose_value=False,
14376
is_eager=True,
14477
)
145-
@click.with_app_context
146-
def _command() -> None:
147-
from django.core.management import execute_from_command_line
148-
149-
args = ["manage.py", command_item.name]
150-
151-
for item in sys.argv[3:]:
152-
args.extend(item.split(" "))
153-
execute_from_command_line(args)
78+
@click.pass_context
79+
def command(
80+
ctx: click.Context, *args: t.Tuple[t.Any, ...], **kwargs: t.Dict[str, t.Any]
81+
) -> None:
82+
command_args = ["manage.py", command_item.name] + list(ctx.args)
83+
django.core.management.execute_from_command_line(command_args)
15484

15585

15686
list(map(_add_django_command, _django_support_commands))

tests/test_django_commands.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import pytest
2+
from django.core.management import call_command
3+
from django.core.management.base import CommandError
4+
5+
6+
def test_command_succeeds() -> None:
7+
call_command("check")
8+
9+
10+
def test_nonexistent_command_fails() -> None:
11+
with pytest.raises(CommandError, match="Unknown command"):
12+
call_command("nonexistent_command")

0 commit comments

Comments
 (0)