diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1997d1d1952a973e133f25dffd4a0238c35d2091..b1fa8cc75fed438d03a01b4244b4be725ce18884 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,16 @@ and this project adheres to `Semantic Versioning`_. Unreleased ---------- +Added +~~~~~ + +* Plan version can be explicitly selected (defaulting to newest) + +Fixed +~~~~~ + +* Import now only imports one plan version + `2.1`_ - 2022-01-13 ------------------- diff --git a/README.rst b/README.rst index 8352cbaf25df4810553075417fac68b9191efa92..d1f6c454c66b982c298bb41668b42f11b308aff0 100644 --- a/README.rst +++ b/README.rst @@ -32,7 +32,7 @@ Licence Copyright © 2018, 2019, 2020, 2021 Jonathan Weth <dev@jonathanweth.de> Copyright © 2018, 2019 Frank Poetzsch-Heffter <p-h@katharineum.de> - Copyright © 2019, 2020, 2021 Dominik George <dominik.george@teckids.org> + Copyright © 2019, 2020, 2021, 2022 Dominik George <dominik.george@teckids.org> Copyright © 2019, 2020 Tom Teichler <tom.teichler@teckids.org> Copyright © 2019 Julian Leucker <leuckeju@katharineum.de> Copyright © 2019 mirabilos <thorsten.glaser@teckids.org> diff --git a/aleksis/apps/untis/apps.py b/aleksis/apps/untis/apps.py index 4205a066700754edf9189e3eab8b3299821ff469..27a0a264b2efadb292aeb33c885126a2005088f7 100644 --- a/aleksis/apps/untis/apps.py +++ b/aleksis/apps/untis/apps.py @@ -12,7 +12,7 @@ class UntisConfig(AppConfig): copyright_info = ( ([2018, 2019, 2020, 2021], "Jonathan Weth", "dev@jonathanweth.de"), ([2018, 2019], "Frank Poetzsch-Heffter", "p-h@katharineum.de"), - ([2019, 2020, 2021], "Dominik George", "dominik.george@teckids.org"), + ([2019, 2020, 2021, 2022], "Dominik George", "dominik.george@teckids.org"), ([2019, 2020], "Tom Teichler", "tom.teichler@teckids.org"), ([2019], "Julian Leucker", "leuckeju@katharineum.de"), ([2019], "mirabilos", "thorsten.glaser@teckids.org"), diff --git a/aleksis/apps/untis/commands.py b/aleksis/apps/untis/commands.py index ce6b17a86b07347468f9a737da16a0868fe4cb48..ae85233596d18b6f63f6c1f9e4434e6709e6ffd6 100644 --- a/aleksis/apps/untis/commands.py +++ b/aleksis/apps/untis/commands.py @@ -27,15 +27,15 @@ class ImportCommand: return None @classmethod - def run(cls, background: bool = False): + def run(cls, background: bool = False, version: Optional[int] = None): """Run the import command (foreground/background).""" if background: from .tasks import TASKS task = TASKS[cls.task_name] - task.delay() + task.delay(version=version) else: - _untis_import_mysql(cls.get_terms()) + _untis_import_mysql(cls.get_terms(), version=version) class CurrentImportCommand(ImportCommand): diff --git a/aleksis/apps/untis/management/commands/untis_import_mysql.py b/aleksis/apps/untis/management/commands/untis_import_mysql.py index a53a9e61b5c6e88fbb45b704d931434e7b51fd60..1aa6bdd6e55a72faebba81bf90dfcfd461256ca5 100644 --- a/aleksis/apps/untis/management/commands/untis_import_mysql.py +++ b/aleksis/apps/untis/management/commands/untis_import_mysql.py @@ -13,8 +13,13 @@ class Command(BaseCommand): action="store_true", help="Run import job in background using Celery", ) + parser.add_argument( + "--plan-version", + help="Select explicit Untis plan version", + ) def handle(self, *args, **options): command = COMMANDS_BY_NAME[options["command"]] background = options["background"] - command.run(background=background) + version = options.get("plan_version", None) + command.run(background=background, version=version) diff --git a/aleksis/apps/untis/tasks.py b/aleksis/apps/untis/tasks.py index 6da6405c5ad00f37dd90a046bf23121993dceb94..3e45b18e2e16bdb7600597c87de7571ee7c514c9 100644 --- a/aleksis/apps/untis/tasks.py +++ b/aleksis/apps/untis/tasks.py @@ -6,8 +6,8 @@ TASKS = {} for import_command in ImportCommand.__subclasses__(): @app.task(name=import_command.task_name, bind=True) - def _task(self): + def _task(self, *args, **kwargs): import_command = COMMANDS_BY_TASK_NAME[self.name] - import_command.run() + import_command.run(*args, **kwargs) TASKS[import_command.task_name] = _task diff --git a/aleksis/apps/untis/util/mysql/importers/terms.py b/aleksis/apps/untis/util/mysql/importers/terms.py index e8b662008230d5b5e74e6dc70a9cbfd543bbc59f..092a9f9d990e7056461257afc74cb3c8560ad66b 100644 --- a/aleksis/apps/untis/util/mysql/importers/terms.py +++ b/aleksis/apps/untis/util/mysql/importers/terms.py @@ -2,7 +2,7 @@ import logging from datetime import date from typing import Dict, Optional -from django.db.models import QuerySet +from django.db.models import Max, OuterRef, QuerySet, Subquery from django.utils import timezone from tqdm import tqdm @@ -49,12 +49,31 @@ logger = logging.getLogger(__name__) def import_terms( qs: Optional[QuerySet] = None, + version: Optional[int] = None, ) -> Dict[int, chronos_models.ValidityRange]: """Import terms and school years as validity ranges and school terms.""" ranges_ref = {} if not isinstance(qs, QuerySet): - qs = run_using(mysql_models.Terms.objects).all() + qs = run_using(mysql_models.Terms.objects) + + if version is None: + # Select newest version per term / validity range + sub_qs = ( + run_using(mysql_models.Terms.objects) + .filter( + school_id=OuterRef("school_id"), + schoolyear_id=OuterRef("schoolyear_id"), + term_id=OuterRef("term_id"), + ) + .values("school_id", "schoolyear_id", "term_id") + .annotate(max_version=Max("version_id")) + .values("max_version") + ) + qs = qs.filter(version_id=Subquery(sub_qs)) + else: + # Select passed version + qs = qs.filter(version_id=version) school_terms = {} for term in tqdm(qs, desc="Import terms (as validity ranges)", **TQDM_DEFAULTS): diff --git a/aleksis/apps/untis/util/mysql/main.py b/aleksis/apps/untis/util/mysql/main.py index c372de002f96f8d194d2d71326d24b4ddf7891ad..b3e6ca510ced6f8cb0bb1c13803537422501fdaa 100644 --- a/aleksis/apps/untis/util/mysql/main.py +++ b/aleksis/apps/untis/util/mysql/main.py @@ -25,9 +25,9 @@ from .importers.lessons import import_lessons from .importers.substitutions import import_substitutions -def untis_import_mysql(terms: Optional[QuerySet] = None): +def untis_import_mysql(terms: Optional[QuerySet] = None, version: Optional[int] = None): # School terms and validity ranges - validity_ref = import_terms(terms) + validity_ref = import_terms(terms, version=version) for validity_range in tqdm( validity_ref.values(), desc="Import data for terms", **TQDM_DEFAULTS diff --git a/docs/admin/10_features.rst b/docs/admin/10_features.rst index 0674261d8b81c6924f32458acfc17f91bf6807ec..8d6e0c2a73d58c43ff5960cc3674b2ef12149650 100644 --- a/docs/admin/10_features.rst +++ b/docs/admin/10_features.rst @@ -14,7 +14,7 @@ Supported Untis features ------------------------ Not all features of Untis are supported in AlekSIS. The following -information form Untis can be imported into AlekSIS: +information from Untis can be imported into AlekSIS: * Terms * Holidays @@ -26,6 +26,9 @@ information form Untis can be imported into AlekSIS: * Substitutions, extra lessons, cancellations * Events +The Untis integration supports the versioning features of Untis. By default, +the most recent version of each object is imported. + Currently, the following features are known not to be supported: * Students, student groups, student choices diff --git a/docs/admin/20_configuration.rst b/docs/admin/20_configuration.rst index 7703e31eee1c1b922b3bc2585a2be051482cb59d..9984da36cbdd7c181b4263f00c51f8d1da779a39 100644 --- a/docs/admin/20_configuration.rst +++ b/docs/admin/20_configuration.rst @@ -92,7 +92,9 @@ is also already imported when it becomes reelvant. In general, all tasks will do nothing if there is no matching Untis term. -To use these tasks, you have to add them as periodic tasks. +To use these tasks, you have to add them as periodic tasks. By default, they will +import the most recent plan version from Untis. To select a specific version (i.e. +to import an older snapshot), you can pass the ``version`` argument in the tasks. How existing data is matched ----------------------------