We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Install issue 2.0.1 -> collect static fails to create directories
<!--
    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.

    This form is only for reporting reproducible bugs. If you need assistance
    with Nautobot installation, or if you have a general question, please start a
    discussion instead: https://github.com/nautobot/nautobot/discussions

    Please describe the environment in which you are running Nautobot. Be sure
    that you are running an unmodified instance of the latest stable release
    before submitting a bug report, and that any plugins have been disabled.
-->
### Environment
* Nautobot version (Docker tag too if applicable): 2.0.1
* Python version: 3.10.12
* Database platform, version: postgres
* Middleware(s):none

<!--
    Describe in detail the exact steps that someone else can take to reproduce
    this bug using the current stable release of Nautobot. Begin with the
    creation of any necessary database objects and call out every operation
    being performed explicitly. If reporting a bug in the REST API, be sure to
    reconstruct the raw HTTP request(s) being made: Don't rely on a client
    library such as pynautobot.
-->
### Steps to Reproduce
1. New installation of nautobot
2. Following instructions at https://blog.networktocode.com/post/installing-nautobot/
3. Issue command nautobot-server collectstatic

<!-- What did you expect to happen? -->
### Expected Behavior
Expected directories to be created and static files to be collected

<!-- What happened instead? -->
Error occurred

nautobot@server:~$ nautobot-server collectstatic
16:21:56.992 ERROR   nautobot.core.apps :
  Error in link construction for Notes: Reverse for 'note_list' not found. 'note_list' is not a valid view function or pattern name.
Traceback (most recent call last):
  File "/opt/nautobot/bin/nautobot-server", line 8, in <module>
    sys.exit(main())
  File "/opt/nautobot/lib/python3.10/site-packages/nautobot/core/cli/__init__.py", line 54, in main
    run_app(
  File "/opt/nautobot/lib/python3.10/site-packages/nautobot/core/runner/runner.py", line 297, in run_app
    management.execute_from_command_line([runner_name, command] + command_args)
  File "/opt/nautobot/lib/python3.10/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/opt/nautobot/lib/python3.10/site-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/opt/nautobot/lib/python3.10/site-packages/django/core/management/base.py", line 354, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/opt/nautobot/lib/python3.10/site-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args, **options)
  File "/opt/nautobot/lib/python3.10/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 187, in handle
    collected = self.collect()
  File "/opt/nautobot/lib/python3.10/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 105, in collect
    for path, storage in finder.list(self.ignore_patterns):
  File "/opt/nautobot/lib/python3.10/site-packages/django/contrib/staticfiles/finders.py", line 130, in list
    for path in utils.get_files(storage, ignore_patterns):
  File "/opt/nautobot/lib/python3.10/site-packages/django/contrib/staticfiles/utils.py", line 23, in get_files
    directories, files = storage.listdir(location)
  File "/opt/nautobot/lib/python3.10/site-packages/django/core/files/storage.py", line 330, in listdir
    for entry in os.scandir(path):
FileNotFoundError: [Errno 2] No such file or directory: '/opt/nautobot/ui/build/static'



As a workaround, I manually created /opt/nautobot/ui/build/static and re-ran collectstatic

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nautobot/core/cli/__init__.py`
Content:
```
1 """
2 Utilities and primitives for the `nautobot-server` CLI command.
3 """
4 
5 import os
6 
7 from django.core.exceptions import ImproperlyConfigured
8 from django.core.management.utils import get_random_secret_key
9 from jinja2 import BaseLoader, Environment
10 
11 from nautobot.core.runner import run_app
12 from nautobot.extras.plugins.utils import load_plugins
13 
14 
15 # Default file location for the generated config emitted by `init`
16 NAUTOBOT_ROOT = os.getenv("NAUTOBOT_ROOT", os.path.expanduser("~/.nautobot"))
17 DEFAULT_CONFIG_PATH = os.path.join(NAUTOBOT_ROOT, "nautobot_config.py")
18 
19 # Default settings to use when building the config
20 DEFAULT_SETTINGS = "nautobot.core.settings"
21 
22 # Name of the environment variable used to specify path of config
23 SETTINGS_ENVVAR = "NAUTOBOT_CONFIG"
24 
25 # Base directory for this module
26 BASE_DIR = os.path.dirname(__file__)
27 
28 # File path of template used to generate config emitted by `init`
29 CONFIG_TEMPLATE = os.path.join(BASE_DIR, "../templates/nautobot_config.py.j2")
30 
31 DESCRIPTION = """
32 Nautobot server management utility.
33 
34 Type '%(prog)s help' to display a list of included sub-commands.
35 
36 Type '%(prog)s init' to generate a new configuration.
37 """
38 
39 
40 def main():
41     """
42     The main server CLI command that replaces `manage.py` and allows a
43     configuration file to be passed in.
44 
45     How this works:
46 
47     - Process CLI args
48     - Load default settings
49     - Read config file from path
50     - Overlay config settings on top of default settings
51     - Overlay special/conditional settings (see `_configure_settings`)
52     """
53     run_app(
54         project="nautobot",
55         description=DESCRIPTION,
56         default_config_path=DEFAULT_CONFIG_PATH,
57         default_settings=DEFAULT_SETTINGS,
58         settings_initializer=generate_settings,
59         settings_envvar=SETTINGS_ENVVAR,
60         initializer=_configure_settings,  # Called after defaults
61     )
62 
63 
64 def generate_settings(config_template=CONFIG_TEMPLATE, **kwargs):
65     """
66     This command is ran when `default_config_path` doesn't exist, or `init` is
67     ran and returns a string representing the default data to put into the
68     settings file.
69     """
70     template_vars = {
71         "secret_key": get_random_secret_key(),
72         "installation_metrics_enabled": kwargs.get("installation_metrics_enabled", True),
73     }
74 
75     with open(config_template) as fh:
76         environment = Environment(loader=BaseLoader, keep_trailing_newline=True)
77         config = environment.from_string(fh.read())
78 
79     return config.render(**template_vars)
80 
81 
82 def _configure_settings(config):
83     """
84     Callback for processing conditional or special purpose settings.
85 
86     Any specially prepared settings will be handled here, such as loading
87     plugins, enabling social auth, etc.
88 
89     This is intended to be called by `run_app` and should not be invoked
90     directly.
91 
92     :param config:
93         A dictionary of `config_path`, `project`, `settings`
94 
95     Example::
96 
97         {
98             'project': 'nautobot',
99             'config_path': '/path/to/nautobot_config.py',
100             'settings': <LazySettings "nautobot_config">
101         }
102     """
103 
104     settings = config["settings"]
105 
106     # Include the config path to the settings to align with builtin
107     # `settings.SETTINGS_MODULE`. Useful for debugging correct config path.
108     settings.SETTINGS_PATH = config["config_path"]
109 
110     #
111     # Storage directories
112     #
113     os.makedirs(settings.GIT_ROOT, exist_ok=True)
114     os.makedirs(settings.JOBS_ROOT, exist_ok=True)
115     os.makedirs(settings.MEDIA_ROOT, exist_ok=True)
116     os.makedirs(os.path.join(settings.MEDIA_ROOT, "devicetype-images"), exist_ok=True)
117     os.makedirs(os.path.join(settings.MEDIA_ROOT, "image-attachments"), exist_ok=True)
118     os.makedirs(settings.STATIC_ROOT, exist_ok=True)
119 
120     #
121     # Databases
122     #
123 
124     # If metrics are enabled and postgres is the backend, set the driver to the
125     # one provided by django-prometheus.
126     if settings.METRICS_ENABLED and "postgres" in settings.DATABASES["default"]["ENGINE"]:
127         settings.DATABASES["default"]["ENGINE"] = "django_prometheus.db.backends.postgresql"
128 
129     # Create secondary db connection for job logging. This still writes to the default db, but because it's a separate
130     # connection, it allows allows us to "escape" from transaction.atomic() and ensure that job log entries are saved
131     # to the database even when the rest of the job transaction is rolled back.
132     settings.DATABASES["job_logs"] = settings.DATABASES["default"].copy()
133     # When running unit tests, treat it as a mirror of the default test DB, not a separate test DB of its own
134     settings.DATABASES["job_logs"]["TEST"] = {"MIRROR": "default"}
135 
136     #
137     # Media storage
138     #
139 
140     if settings.STORAGE_BACKEND is not None:
141         settings.DEFAULT_FILE_STORAGE = settings.STORAGE_BACKEND
142 
143         # django-storages
144         if settings.STORAGE_BACKEND.startswith("storages."):
145             try:
146                 import storages.utils
147             except ModuleNotFoundError as e:
148                 if getattr(e, "name") == "storages":
149                     raise ImproperlyConfigured(
150                         f"STORAGE_BACKEND is set to {settings.STORAGE_BACKEND} but django-storages is not present. It "
151                         f"can be installed by running 'pip install django-storages'."
152                     )
153                 raise e
154 
155             # Monkey-patch django-storages to fetch settings from STORAGE_CONFIG or fall back to settings
156             def _setting(name, default=None):
157                 if name in settings.STORAGE_CONFIG:
158                     return settings.STORAGE_CONFIG[name]
159                 return getattr(settings, name, default)
160 
161             storages.utils.setting = _setting
162 
163     #
164     # Plugins
165     #
166 
167     # Process the plugins and manipulate the specified config settings that are
168     # passed in.
169     load_plugins(settings)
170 
171 
172 if __name__ == "__main__":
173     main()
174
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/nautobot/core/cli/__init__.py b/nautobot/core/cli/__init__.py
--- a/nautobot/core/cli/__init__.py
+++ b/nautobot/core/cli/__init__.py
@@ -115,6 +115,7 @@
     os.makedirs(settings.MEDIA_ROOT, exist_ok=True)
     os.makedirs(os.path.join(settings.MEDIA_ROOT, "devicetype-images"), exist_ok=True)
     os.makedirs(os.path.join(settings.MEDIA_ROOT, "image-attachments"), exist_ok=True)
+    os.makedirs(os.path.join(settings.NAUTOBOT_UI_DIR, "build", "static"), exist_ok=True)
     os.makedirs(settings.STATIC_ROOT, exist_ok=True)
 
     #

{"golden_diff": "diff --git a/nautobot/core/cli/__init__.py b/nautobot/core/cli/__init__.py\n--- a/nautobot/core/cli/__init__.py\n+++ b/nautobot/core/cli/__init__.py\n@@ -115,6 +115,7 @@\n     os.makedirs(settings.MEDIA_ROOT, exist_ok=True)\n     os.makedirs(os.path.join(settings.MEDIA_ROOT, \"devicetype-images\"), exist_ok=True)\n     os.makedirs(os.path.join(settings.MEDIA_ROOT, \"image-attachments\"), exist_ok=True)\n+    os.makedirs(os.path.join(settings.NAUTOBOT_UI_DIR, \"build\", \"static\"), exist_ok=True)\n     os.makedirs(settings.STATIC_ROOT, exist_ok=True)\n \n     #\n", "issue": "Install issue 2.0.1 -> collect static fails to create directories\n<!--\r\n    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.\r\n\r\n    This form is only for reporting reproducible bugs. If you need assistance\r\n    with Nautobot installation, or if you have a general question, please start a\r\n    discussion instead: https://github.com/nautobot/nautobot/discussions\r\n\r\n    Please describe the environment in which you are running Nautobot. Be sure\r\n    that you are running an unmodified instance of the latest stable release\r\n    before submitting a bug report, and that any plugins have been disabled.\r\n-->\r\n### Environment\r\n* Nautobot version (Docker tag too if applicable): 2.0.1\r\n* Python version: 3.10.12\r\n* Database platform, version: postgres\r\n* Middleware(s):none\r\n\r\n<!--\r\n    Describe in detail the exact steps that someone else can take to reproduce\r\n    this bug using the current stable release of Nautobot. Begin with the\r\n    creation of any necessary database objects and call out every operation\r\n    being performed explicitly. If reporting a bug in the REST API, be sure to\r\n    reconstruct the raw HTTP request(s) being made: Don't rely on a client\r\n    library such as pynautobot.\r\n-->\r\n### Steps to Reproduce\r\n1. New installation of nautobot\r\n2. Following instructions at https://blog.networktocode.com/post/installing-nautobot/\r\n3. Issue command nautobot-server collectstatic\r\n\r\n<!-- What did you expect to happen? -->\r\n### Expected Behavior\r\nExpected directories to be created and static files to be collected\r\n\r\n<!-- What happened instead? -->\r\nError occurred\r\n\r\nnautobot@server:~$ nautobot-server collectstatic\r\n16:21:56.992 ERROR   nautobot.core.apps :\r\n  Error in link construction for Notes: Reverse for 'note_list' not found. 'note_list' is not a valid view function or pattern name.\r\nTraceback (most recent call last):\r\n  File \"/opt/nautobot/bin/nautobot-server\", line 8, in <module>\r\n    sys.exit(main())\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/nautobot/core/cli/__init__.py\", line 54, in main\r\n    run_app(\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/nautobot/core/runner/runner.py\", line 297, in run_app\r\n    management.execute_from_command_line([runner_name, command] + command_args)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/core/management/__init__.py\", line 419, in execute_from_command_line\r\n    utility.execute()\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/core/management/__init__.py\", line 413, in execute\r\n    self.fetch_command(subcommand).run_from_argv(self.argv)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/core/management/base.py\", line 354, in run_from_argv\r\n    self.execute(*args, **cmd_options)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/core/management/base.py\", line 398, in execute\r\n    output = self.handle(*args, **options)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py\", line 187, in handle\r\n    collected = self.collect()\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py\", line 105, in collect\r\n    for path, storage in finder.list(self.ignore_patterns):\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/contrib/staticfiles/finders.py\", line 130, in list\r\n    for path in utils.get_files(storage, ignore_patterns):\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/contrib/staticfiles/utils.py\", line 23, in get_files\r\n    directories, files = storage.listdir(location)\r\n  File \"/opt/nautobot/lib/python3.10/site-packages/django/core/files/storage.py\", line 330, in listdir\r\n    for entry in os.scandir(path):\r\nFileNotFoundError: [Errno 2] No such file or directory: '/opt/nautobot/ui/build/static'\r\n\r\n\r\n\r\nAs a workaround, I manually created /opt/nautobot/ui/build/static and re-ran collectstatic\n", "before_files": [{"content": "\"\"\"\nUtilities and primitives for the `nautobot-server` CLI command.\n\"\"\"\n\nimport os\n\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.core.management.utils import get_random_secret_key\nfrom jinja2 import BaseLoader, Environment\n\nfrom nautobot.core.runner import run_app\nfrom nautobot.extras.plugins.utils import load_plugins\n\n\n# Default file location for the generated config emitted by `init`\nNAUTOBOT_ROOT = os.getenv(\"NAUTOBOT_ROOT\", os.path.expanduser(\"~/.nautobot\"))\nDEFAULT_CONFIG_PATH = os.path.join(NAUTOBOT_ROOT, \"nautobot_config.py\")\n\n# Default settings to use when building the config\nDEFAULT_SETTINGS = \"nautobot.core.settings\"\n\n# Name of the environment variable used to specify path of config\nSETTINGS_ENVVAR = \"NAUTOBOT_CONFIG\"\n\n# Base directory for this module\nBASE_DIR = os.path.dirname(__file__)\n\n# File path of template used to generate config emitted by `init`\nCONFIG_TEMPLATE = os.path.join(BASE_DIR, \"../templates/nautobot_config.py.j2\")\n\nDESCRIPTION = \"\"\"\nNautobot server management utility.\n\nType '%(prog)s help' to display a list of included sub-commands.\n\nType '%(prog)s init' to generate a new configuration.\n\"\"\"\n\n\ndef main():\n    \"\"\"\n    The main server CLI command that replaces `manage.py` and allows a\n    configuration file to be passed in.\n\n    How this works:\n\n    - Process CLI args\n    - Load default settings\n    - Read config file from path\n    - Overlay config settings on top of default settings\n    - Overlay special/conditional settings (see `_configure_settings`)\n    \"\"\"\n    run_app(\n        project=\"nautobot\",\n        description=DESCRIPTION,\n        default_config_path=DEFAULT_CONFIG_PATH,\n        default_settings=DEFAULT_SETTINGS,\n        settings_initializer=generate_settings,\n        settings_envvar=SETTINGS_ENVVAR,\n        initializer=_configure_settings,  # Called after defaults\n    )\n\n\ndef generate_settings(config_template=CONFIG_TEMPLATE, **kwargs):\n    \"\"\"\n    This command is ran when `default_config_path` doesn't exist, or `init` is\n    ran and returns a string representing the default data to put into the\n    settings file.\n    \"\"\"\n    template_vars = {\n        \"secret_key\": get_random_secret_key(),\n        \"installation_metrics_enabled\": kwargs.get(\"installation_metrics_enabled\", True),\n    }\n\n    with open(config_template) as fh:\n        environment = Environment(loader=BaseLoader, keep_trailing_newline=True)\n        config = environment.from_string(fh.read())\n\n    return config.render(**template_vars)\n\n\ndef _configure_settings(config):\n    \"\"\"\n    Callback for processing conditional or special purpose settings.\n\n    Any specially prepared settings will be handled here, such as loading\n    plugins, enabling social auth, etc.\n\n    This is intended to be called by `run_app` and should not be invoked\n    directly.\n\n    :param config:\n        A dictionary of `config_path`, `project`, `settings`\n\n    Example::\n\n        {\n            'project': 'nautobot',\n            'config_path': '/path/to/nautobot_config.py',\n            'settings': <LazySettings \"nautobot_config\">\n        }\n    \"\"\"\n\n    settings = config[\"settings\"]\n\n    # Include the config path to the settings to align with builtin\n    # `settings.SETTINGS_MODULE`. Useful for debugging correct config path.\n    settings.SETTINGS_PATH = config[\"config_path\"]\n\n    #\n    # Storage directories\n    #\n    os.makedirs(settings.GIT_ROOT, exist_ok=True)\n    os.makedirs(settings.JOBS_ROOT, exist_ok=True)\n    os.makedirs(settings.MEDIA_ROOT, exist_ok=True)\n    os.makedirs(os.path.join(settings.MEDIA_ROOT, \"devicetype-images\"), exist_ok=True)\n    os.makedirs(os.path.join(settings.MEDIA_ROOT, \"image-attachments\"), exist_ok=True)\n    os.makedirs(settings.STATIC_ROOT, exist_ok=True)\n\n    #\n    # Databases\n    #\n\n    # If metrics are enabled and postgres is the backend, set the driver to the\n    # one provided by django-prometheus.\n    if settings.METRICS_ENABLED and \"postgres\" in settings.DATABASES[\"default\"][\"ENGINE\"]:\n        settings.DATABASES[\"default\"][\"ENGINE\"] = \"django_prometheus.db.backends.postgresql\"\n\n    # Create secondary db connection for job logging. This still writes to the default db, but because it's a separate\n    # connection, it allows allows us to \"escape\" from transaction.atomic() and ensure that job log entries are saved\n    # to the database even when the rest of the job transaction is rolled back.\n    settings.DATABASES[\"job_logs\"] = settings.DATABASES[\"default\"].copy()\n    # When running unit tests, treat it as a mirror of the default test DB, not a separate test DB of its own\n    settings.DATABASES[\"job_logs\"][\"TEST\"] = {\"MIRROR\": \"default\"}\n\n    #\n    # Media storage\n    #\n\n    if settings.STORAGE_BACKEND is not None:\n        settings.DEFAULT_FILE_STORAGE = settings.STORAGE_BACKEND\n\n        # django-storages\n        if settings.STORAGE_BACKEND.startswith(\"storages.\"):\n            try:\n                import storages.utils\n            except ModuleNotFoundError as e:\n                if getattr(e, \"name\") == \"storages\":\n                    raise ImproperlyConfigured(\n                        f\"STORAGE_BACKEND is set to {settings.STORAGE_BACKEND} but django-storages is not present. It \"\n                        f\"can be installed by running 'pip install django-storages'.\"\n                    )\n                raise e\n\n            # Monkey-patch django-storages to fetch settings from STORAGE_CONFIG or fall back to settings\n            def _setting(name, default=None):\n                if name in settings.STORAGE_CONFIG:\n                    return settings.STORAGE_CONFIG[name]\n                return getattr(settings, name, default)\n\n            storages.utils.setting = _setting\n\n    #\n    # Plugins\n    #\n\n    # Process the plugins and manipulate the specified config settings that are\n    # passed in.\n    load_plugins(settings)\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "nautobot/core/cli/__init__.py"}], "after_files": [{"content": "\"\"\"\nUtilities and primitives for the `nautobot-server` CLI command.\n\"\"\"\n\nimport os\n\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.core.management.utils import get_random_secret_key\nfrom jinja2 import BaseLoader, Environment\n\nfrom nautobot.core.runner import run_app\nfrom nautobot.extras.plugins.utils import load_plugins\n\n\n# Default file location for the generated config emitted by `init`\nNAUTOBOT_ROOT = os.getenv(\"NAUTOBOT_ROOT\", os.path.expanduser(\"~/.nautobot\"))\nDEFAULT_CONFIG_PATH = os.path.join(NAUTOBOT_ROOT, \"nautobot_config.py\")\n\n# Default settings to use when building the config\nDEFAULT_SETTINGS = \"nautobot.core.settings\"\n\n# Name of the environment variable used to specify path of config\nSETTINGS_ENVVAR = \"NAUTOBOT_CONFIG\"\n\n# Base directory for this module\nBASE_DIR = os.path.dirname(__file__)\n\n# File path of template used to generate config emitted by `init`\nCONFIG_TEMPLATE = os.path.join(BASE_DIR, \"../templates/nautobot_config.py.j2\")\n\nDESCRIPTION = \"\"\"\nNautobot server management utility.\n\nType '%(prog)s help' to display a list of included sub-commands.\n\nType '%(prog)s init' to generate a new configuration.\n\"\"\"\n\n\ndef main():\n    \"\"\"\n    The main server CLI command that replaces `manage.py` and allows a\n    configuration file to be passed in.\n\n    How this works:\n\n    - Process CLI args\n    - Load default settings\n    - Read config file from path\n    - Overlay config settings on top of default settings\n    - Overlay special/conditional settings (see `_configure_settings`)\n    \"\"\"\n    run_app(\n        project=\"nautobot\",\n        description=DESCRIPTION,\n        default_config_path=DEFAULT_CONFIG_PATH,\n        default_settings=DEFAULT_SETTINGS,\n        settings_initializer=generate_settings,\n        settings_envvar=SETTINGS_ENVVAR,\n        initializer=_configure_settings,  # Called after defaults\n    )\n\n\ndef generate_settings(config_template=CONFIG_TEMPLATE, **kwargs):\n    \"\"\"\n    This command is ran when `default_config_path` doesn't exist, or `init` is\n    ran and returns a string representing the default data to put into the\n    settings file.\n    \"\"\"\n    template_vars = {\n        \"secret_key\": get_random_secret_key(),\n        \"installation_metrics_enabled\": kwargs.get(\"installation_metrics_enabled\", True),\n    }\n\n    with open(config_template) as fh:\n        environment = Environment(loader=BaseLoader, keep_trailing_newline=True)\n        config = environment.from_string(fh.read())\n\n    return config.render(**template_vars)\n\n\ndef _configure_settings(config):\n    \"\"\"\n    Callback for processing conditional or special purpose settings.\n\n    Any specially prepared settings will be handled here, such as loading\n    plugins, enabling social auth, etc.\n\n    This is intended to be called by `run_app` and should not be invoked\n    directly.\n\n    :param config:\n        A dictionary of `config_path`, `project`, `settings`\n\n    Example::\n\n        {\n            'project': 'nautobot',\n            'config_path': '/path/to/nautobot_config.py',\n            'settings': <LazySettings \"nautobot_config\">\n        }\n    \"\"\"\n\n    settings = config[\"settings\"]\n\n    # Include the config path to the settings to align with builtin\n    # `settings.SETTINGS_MODULE`. Useful for debugging correct config path.\n    settings.SETTINGS_PATH = config[\"config_path\"]\n\n    #\n    # Storage directories\n    #\n    os.makedirs(settings.GIT_ROOT, exist_ok=True)\n    os.makedirs(settings.JOBS_ROOT, exist_ok=True)\n    os.makedirs(settings.MEDIA_ROOT, exist_ok=True)\n    os.makedirs(os.path.join(settings.MEDIA_ROOT, \"devicetype-images\"), exist_ok=True)\n    os.makedirs(os.path.join(settings.MEDIA_ROOT, \"image-attachments\"), exist_ok=True)\n    os.makedirs(os.path.join(settings.NAUTOBOT_UI_DIR, \"build\", \"static\"), exist_ok=True)\n    os.makedirs(settings.STATIC_ROOT, exist_ok=True)\n\n    #\n    # Databases\n    #\n\n    # If metrics are enabled and postgres is the backend, set the driver to the\n    # one provided by django-prometheus.\n    if settings.METRICS_ENABLED and \"postgres\" in settings.DATABASES[\"default\"][\"ENGINE\"]:\n        settings.DATABASES[\"default\"][\"ENGINE\"] = \"django_prometheus.db.backends.postgresql\"\n\n    # Create secondary db connection for job logging. This still writes to the default db, but because it's a separate\n    # connection, it allows allows us to \"escape\" from transaction.atomic() and ensure that job log entries are saved\n    # to the database even when the rest of the job transaction is rolled back.\n    settings.DATABASES[\"job_logs\"] = settings.DATABASES[\"default\"].copy()\n    # When running unit tests, treat it as a mirror of the default test DB, not a separate test DB of its own\n    settings.DATABASES[\"job_logs\"][\"TEST\"] = {\"MIRROR\": \"default\"}\n\n    #\n    # Media storage\n    #\n\n    if settings.STORAGE_BACKEND is not None:\n        settings.DEFAULT_FILE_STORAGE = settings.STORAGE_BACKEND\n\n        # django-storages\n        if settings.STORAGE_BACKEND.startswith(\"storages.\"):\n            try:\n                import storages.utils\n            except ModuleNotFoundError as e:\n                if getattr(e, \"name\") == \"storages\":\n                    raise ImproperlyConfigured(\n                        f\"STORAGE_BACKEND is set to {settings.STORAGE_BACKEND} but django-storages is not present. It \"\n                        f\"can be installed by running 'pip install django-storages'.\"\n                    )\n                raise e\n\n            # Monkey-patch django-storages to fetch settings from STORAGE_CONFIG or fall back to settings\n            def _setting(name, default=None):\n                if name in settings.STORAGE_CONFIG:\n                    return settings.STORAGE_CONFIG[name]\n                return getattr(settings, name, default)\n\n            storages.utils.setting = _setting\n\n    #\n    # Plugins\n    #\n\n    # Process the plugins and manipulate the specified config settings that are\n    # passed in.\n    load_plugins(settings)\n\n\nif __name__ == \"__main__\":\n    main()\n", "path": "nautobot/core/cli/__init__.py"}]}