Source code for fairdm.apps
"""Application config for the FairDM framework app."""
from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules
# Imported at module level, not in `ready()`, so the navigation exists regardless of
# which optional apps are installed. `fairdm.menus` imports no models.
from fairdm import menus as _menus # noqa: F401
# Imported at module level so the checks also run under `manage.py check --deploy`
# when the production-configuration guard in `ready()` is skipped.
from fairdm.conf import checks as conf_checks # noqa: F401
#: Environments that ship a non-production override module. Every other name,
#: including typos and the empty string, composes the production baseline and is
#: checked as production.
NON_PRODUCTION_ENVIRONMENTS = frozenset({"development"})
[docs]
class FairDMConfig(AppConfig):
"""Wire the FairDM framework into Django's startup."""
name = "fairdm"
[docs]
def resolved_environment(self) -> str:
"""Return the environment ``fairdm.setup()`` resolved, defaulting to ``production``.
Returns:
The value of the ``DJANGO_ENV`` setting.
"""
from django.conf import settings
return getattr(settings, "DJANGO_ENV", "production")
[docs]
def import_models(self) -> None:
"""Re-check the parler language settings before any model imports parler."""
# A portal may narrow LANGUAGES after setup() returns. parler validates during
# this model-import phase, so `ready()` is too late to give FairDM's named error.
self._check_parler_languages()
return super().import_models()
def _check_parler_languages(self) -> None:
"""Raise when ``PARLER_LANGUAGES`` disagrees with ``LANGUAGES``."""
from django.conf import settings
from fairdm.conf.checks import raise_on_parler_languages_mismatch
raise_on_parler_languages_mismatch(
getattr(settings, "LANGUAGES", []),
getattr(settings, "PARLER_LANGUAGES", {}),
getattr(settings, "PARLER_DEFAULT_LANGUAGE_CODE", None),
)
[docs]
def ready(self) -> None:
"""Discover portal config and plugin modules and install FairDM's startup hooks."""
autodiscover_modules("config")
autodiscover_modules("plugins")
from django_filters import compat
# Stops django-filter rendering through crispy forms.
compat.is_crispy = lambda: False
self._install_quantity_formatter()
self._connect_portal_roles_reconciliation()
self._check_production_configuration()
return super().ready()
def _install_quantity_formatter(self) -> None:
"""Install the framework's quantity formatter on the shared pint unit registry."""
# Done here rather than in the template tag module, which Django imports only
# once a template loads it, so values formatted elsewhere would miss it.
from fairdm.templatetags.fairdm import MyFormatter, ureg
ureg.formatter = MyFormatter(registry=ureg)
def _connect_portal_roles_reconciliation(self) -> None:
"""Reconcile the four portal roles after every ``migrate`` run."""
# Connected with no sender: `fairdm` precedes the apps whose permissions the
# roles need, so its own `post_migrate` would fire before those exist.
from django.db.models.signals import post_migrate
post_migrate.connect(
self._reconcile_portal_roles,
dispatch_uid="fairdm.reconcile_portal_roles",
)
@staticmethod
def _reconcile_portal_roles(**kwargs) -> None:
"""Install the shipped portal roles."""
from fairdm.portal_roles import PortalRoles
PortalRoles.reconcile()
def _check_production_configuration(self) -> None:
"""Refuse to boot on the production baseline when a critical check fails.
The gate is the composed settings, not an exact match on ``production``, so an
unrecognised ``DJANGO_ENV`` (``prod``, the empty string) is checked as production.
Every failure is named in one error.
Raises:
SystemCheckError: A production-critical deployment check reported a serious issue.
"""
if self.resolved_environment() in NON_PRODUCTION_ENVIRONMENTS:
return
from django.core.checks.registry import registry
from django.core.management.base import SystemCheckError
from fairdm.conf.checks import DeployTags
errors = [
issue
for issue in registry.run_checks(
tags=[DeployTags.production_critical],
include_deployment_checks=True,
)
if issue.is_serious()
]
if errors:
raise SystemCheckError(
"FairDM production configuration is invalid:\n\n"
+ "\n\n".join(str(error) for error in errors)
)