diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py index aaee34bc1439fb045626ec07768f7b9555c67ef7..5b87a04501275c8941d01277b6afedc7f40e8656 100644 --- a/aleksis/core/apps.py +++ b/aleksis/core/apps.py @@ -1,8 +1,5 @@ -from django.apps import AppConfig, apps - -from constance.signals import config_updated - from .signals import clean_scss +from .util.apps import AppConfig class CoreConfig(AppConfig): @@ -10,5 +7,7 @@ class CoreConfig(AppConfig): verbose_name = "AlekSIS — The Free School Information System" def ready(self) -> None: + super().ready() + + def config_updated(self, *args, **kwargs) -> None: clean_scss() - config_updated.connect(clean_scss) diff --git a/aleksis/core/util/apps.py b/aleksis/core/util/apps.py index f98649cdc261b21443cfc1696af2453c119b6f79..bf12d1af56f654e3ea49ef1e79134a4ac689ff34 100644 --- a/aleksis/core/util/apps.py +++ b/aleksis/core/util/apps.py @@ -1,6 +1,10 @@ from importlib import import_module +from typing import Any, Optional import django.apps +from django.db.models.signals import post_migrate, pre_migrate + +from constance.signals import config_updated class AppConfig(django.apps.AppConfig): @@ -17,3 +21,64 @@ class AppConfig(django.apps.AppConfig): except ImportError: # ImportErrors are non-fatal because model extensions are optional. pass + + # Register default listeners + pre_migrate.connect(self.pre_migrate, sender=self) + post_migrate.connect(self.post_migrate, sender=self) + config_updated.connect(self.config_updated) + + # Getting an app ready means it should look at its config once + self.config_updated() + + def config_updated( + self, + key: Optional[str] = "", + old_value: Optional[Any] = None, + new_value: Optional[Any] = None, + **kwargs + ) -> None: + """ Called on every app instance if a Constance config chagnes, and once on startup + + By default, it does nothing. + """ + pass + + def pre_migrate( + self, + app_config: django.apps.AppConfig, + verbosity: int, + interactive: bool, + using: str, + plan: List[Tuple], + apps: django.apps.Apps, + ) -> None: + """ Called on every app instance before its models are migrated + + By default, it does nothing. + """ + pass + + def post_migrate( + self, + app_config: django.apps.AppConfig, + verbosity: int, + interactive: bool, + using: str, + plan: List[Tuple], + apps: django.apps.Apps, + ) -> None: + """ Called on every app instance after its models have been migrated + + By default, asks all models to do maintenance on their default data. + """ + self._maintain_default_data() + + def _maintain_default_data(self): + if not self.models_module: + # This app does not have any models, so bail out early + return + + for model in self.get_models(): + if hasattr(model, "maintain_default_data"): + # Method implemented by each model object; can be left out + model.maintain_default_data()