mirror of https://github.com/zulip/zulip.git
manage: Edit help text to hide unwanted commands.
Remove unwanted error producing commands from the help text displayed by `./manage.py help` to avoid confusion. Fixes #18770.
This commit is contained in:
parent
1b51792459
commit
6a8a259356
106
manage.py
106
manage.py
|
@ -2,6 +2,7 @@
|
||||||
import configparser
|
import configparser
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
if sys.version_info <= (3, 0):
|
if sys.version_info <= (3, 0):
|
||||||
print("Error: Zulip is a Python 3 project, and cannot be run with Python 2.")
|
print("Error: Zulip is a Python 3 project, and cannot be run with Python 2.")
|
||||||
|
@ -14,8 +15,112 @@ from scripts.lib.setup_path import setup_path
|
||||||
|
|
||||||
setup_path()
|
setup_path()
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from django.core.management import ManagementUtility, get_commands
|
||||||
|
from django.core.management.color import color_style
|
||||||
|
|
||||||
from scripts.lib.zulip_tools import assert_not_running_as_root
|
from scripts.lib.zulip_tools import assert_not_running_as_root
|
||||||
|
|
||||||
|
|
||||||
|
def get_filtered_commands() -> Dict[str, str]:
|
||||||
|
"""Because Zulip uses management commands in production, `manage.py
|
||||||
|
help` is a form of documentation for users. Here we exclude from
|
||||||
|
that documentation built-in commands that are not constructive for
|
||||||
|
end users or even Zulip developers to run.
|
||||||
|
|
||||||
|
Ideally, we'd do further customization to display management
|
||||||
|
commands with more organization in the help text, and also hide
|
||||||
|
development-focused management commands in production.
|
||||||
|
"""
|
||||||
|
all_commands = get_commands()
|
||||||
|
documented_commands = dict()
|
||||||
|
documented_apps = [
|
||||||
|
# "auth" removed because its commands are not applicable to Zulip.
|
||||||
|
# "contenttypes" removed because we don't use that subsystem, and
|
||||||
|
# even if we did.
|
||||||
|
"django.core",
|
||||||
|
"analytics",
|
||||||
|
# "otp_static" removed because it's a 2FA internals detail.
|
||||||
|
# "sessions" removed because it's just a cron job with a misleading
|
||||||
|
# name, since all it does is delete expired sessions.
|
||||||
|
# "social_django" removed for similar reasons to sessions.
|
||||||
|
# "staticfiles" removed because its commands are only usefully run when
|
||||||
|
# wrapped by Zulip tooling.
|
||||||
|
# "two_factor" removed because it's a 2FA internals detail.
|
||||||
|
"zerver",
|
||||||
|
"zilencer",
|
||||||
|
]
|
||||||
|
documented_command_subsets = {
|
||||||
|
"django.core": {
|
||||||
|
"dbshell",
|
||||||
|
"makemigrations",
|
||||||
|
"migrate",
|
||||||
|
"shell",
|
||||||
|
"showmigrations",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for command, app in all_commands.items():
|
||||||
|
if app not in documented_apps:
|
||||||
|
continue
|
||||||
|
if app in documented_command_subsets:
|
||||||
|
if command not in documented_command_subsets[app]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
documented_commands[command] = app
|
||||||
|
return documented_commands
|
||||||
|
|
||||||
|
|
||||||
|
class FilteredManagementUtility(ManagementUtility):
|
||||||
|
"""Replaces the main_help_text function of ManagementUtility with one
|
||||||
|
that calls our get_filtered_commands(), rather than the default
|
||||||
|
get_commands() function.
|
||||||
|
|
||||||
|
All other change are just code style differences to pass the Zulip linter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def main_help_text(self, commands_only: bool = False) -> str:
|
||||||
|
"""Return the script's main help text, as a string."""
|
||||||
|
if commands_only:
|
||||||
|
usage = sorted(get_filtered_commands())
|
||||||
|
else:
|
||||||
|
usage = [
|
||||||
|
"",
|
||||||
|
f"Type '{self.prog_name} help <subcommand>' for help on a specific subcommand.",
|
||||||
|
"",
|
||||||
|
"Available subcommands:",
|
||||||
|
]
|
||||||
|
commands_dict = defaultdict(lambda: [])
|
||||||
|
for name, app in get_filtered_commands().items():
|
||||||
|
if app == "django.core":
|
||||||
|
app = "django"
|
||||||
|
else:
|
||||||
|
app = app.rpartition(".")[-1]
|
||||||
|
commands_dict[app].append(name)
|
||||||
|
style = color_style()
|
||||||
|
for app in sorted(commands_dict):
|
||||||
|
usage.append("")
|
||||||
|
usage.append(style.NOTICE(f"[{app}]"))
|
||||||
|
for name in sorted(commands_dict[app]):
|
||||||
|
usage.append(f" {name}")
|
||||||
|
# Output an extra note if settings are not properly configured
|
||||||
|
if self.settings_exception is not None:
|
||||||
|
usage.append(
|
||||||
|
style.NOTICE(
|
||||||
|
"Note that only Django core commands are listed "
|
||||||
|
f"as settings are not properly configured (error: {self.settings_exception})."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return "\n".join(usage)
|
||||||
|
|
||||||
|
|
||||||
|
def execute_from_command_line(argv: Optional[List[str]] = None) -> None:
|
||||||
|
"""Run a FilteredManagementUtility."""
|
||||||
|
utility = FilteredManagementUtility(argv)
|
||||||
|
utility.execute()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
assert_not_running_as_root()
|
assert_not_running_as_root()
|
||||||
|
|
||||||
|
@ -38,7 +143,6 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "zproject.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "zproject.settings")
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management import execute_from_command_line
|
|
||||||
from django.core.management.base import CommandError
|
from django.core.management.base import CommandError
|
||||||
|
|
||||||
from scripts.lib.zulip_tools import log_management_command
|
from scripts.lib.zulip_tools import log_management_command
|
||||||
|
|
Loading…
Reference in New Issue