From 48d3c34fbe1c6361cf3dcb210c3f019d26448cdc Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Fri, 15 Dec 2023 23:17:10 +0100 Subject: [PATCH 01/21] Create migration test command --- .../commands/migrate_objectives_to_content.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 server/books/management/commands/migrate_objectives_to_content.py diff --git a/server/books/management/commands/migrate_objectives_to_content.py b/server/books/management/commands/migrate_objectives_to_content.py new file mode 100644 index 00000000..bb6e9f54 --- /dev/null +++ b/server/books/management/commands/migrate_objectives_to_content.py @@ -0,0 +1,79 @@ +import json + +from django.core.management import BaseCommand + +from books.blocks import TextBlock +from books.categorize_modules import categorize_modules, delete_unused_levels, uncategorize_modules, \ + delete_unused_categories +from books.categorize_modules import create_default_levels_and_categories +from books.models import Module +from books.models import ContentBlock, Chapter + +from books.models import ContentBlock + + +class Command(BaseCommand): + def handle(self, *args, **options): + ContentBlock.objects.filter(title__startswith="TESTOBJECTIVE").delete() + Chapter.objects.filter(title__startswith="TESTOBJECTIVE").delete() + ContentBlock.objects.filter(title__startswith="XXX").delete() + Chapter.objects.filter(title__startswith="XXX").delete() + + for module in Module.objects.filter(title="Politische Streitfragen"): + print(f"Module: {module}") + chapter = create_chapter_from_objective_group(module) + + for objective_group in module.objective_groups.all(): + print(f" Objective group: {objective_group}") + print(" Default objectives:") + content_block = create_content_block_from_objective(objective_group, chapter) + + default_objectives = objective_group.objectives.filter(owner__isnull=True) + content_block = create_text_in_content_block(default_objectives, content_block) + + for objective in default_objectives: + print(f" Objective: {objective} {objective.owner}") + + custom_objectives = objective_group.objectives.filter(owner__isnull=False) + create_text_in_content_block(custom_objectives, content_block) + + print(" Custom objectives:") + for objective in custom_objectives: + print(f" Objective: {objective} {objective.owner}") + + print("") + + + +def create_chapter_from_objective_group(module): + chapter = Chapter(title=f"XXX Leistungsziele") + # + first_sibling = module.get_first_child() + if first_sibling is not None: + first_sibling.add_sibling(instance=chapter, pos='left') + + chapter.save_revision().publish() + chapter.save() + return chapter + +def create_content_block_from_objective(objective_group, chapter): + + content_block = ContentBlock( + title=f"XXX {objective_group.title}", + type="normal" + ) + chapter.add_child(instance=content_block) + + return content_block + +def create_text_in_content_block( objectives, content_block): + print(content_block.contents) + texts = [{'type': 'text_block', + 'value': { + 'text': f"- {objective.text}" + }} for objective in objectives] + content_block.contents = json.dumps(texts) + content_block.save_revision().publish() + return content_block + + From 568564a64c07fe5a1b1d0ec4c4bddff07e66c897 Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Mon, 18 Dec 2023 15:34:56 +0100 Subject: [PATCH 02/21] Add User created content --- .../commands/migrate_objectives_to_content.py | 70 ++++++++++++++----- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/server/books/management/commands/migrate_objectives_to_content.py b/server/books/management/commands/migrate_objectives_to_content.py index bb6e9f54..814c85da 100644 --- a/server/books/management/commands/migrate_objectives_to_content.py +++ b/server/books/management/commands/migrate_objectives_to_content.py @@ -8,8 +8,9 @@ from books.categorize_modules import categorize_modules, delete_unused_levels, u from books.categorize_modules import create_default_levels_and_categories from books.models import Module from books.models import ContentBlock, Chapter - +from django.db.models import Avg, Count, Min, Sum from books.models import ContentBlock +from users.models import SchoolClass class Command(BaseCommand): @@ -26,28 +27,56 @@ class Command(BaseCommand): for objective_group in module.objective_groups.all(): print(f" Objective group: {objective_group}") print(" Default objectives:") - content_block = create_content_block_from_objective(objective_group, chapter) + + # create "Verlagsinhalt Lernziele" default_objectives = objective_group.objectives.filter(owner__isnull=True) - content_block = create_text_in_content_block(default_objectives, content_block) + + default_content_block = create_content_block_from_objective(objective_group, chapter) + create_text_in_content_block(default_objectives, default_content_block) for objective in default_objectives: print(f" Objective: {objective} {objective.owner}") + # Create "Benutzerdefinierte Lernziele" custom_objectives = objective_group.objectives.filter(owner__isnull=False) - create_text_in_content_block(custom_objectives, content_block) - print(" Custom objectives:") + custom_objectives_by_owner = {} + for objective in custom_objectives: - print(f" Objective: {objective} {objective.owner}") + owner = objective.owner + owners_school_classes = owner.school_classes.all() - print("") + if owner not in custom_objectives_by_owner: + custom_objectives_by_owner[owner] = [] + custom_objectives_by_owner[owner].append(objective) + + for owner, owner_objectives in custom_objectives_by_owner.items(): + print(f" Owner: {owner}") + print(f" Objectives: ") + default_objectives = objective_group.objectives.filter(owner__isnull=True) + owner_objectives.extend(default_objectives) + + for objective in owner_objectives: + print(f" Objective: {objective} {objective.owner}") + # + custom_content_block = create_content_block_from_objective(objective_group, chapter, owner=owner) + create_text_in_content_block(owner_objectives, custom_content_block) + + owners_school_classes = owner.school_classes.all() + for school_class in owners_school_classes: + custom_content_block.visible_for.add(school_class.id) + + if objective.is_hidden_for_class(school_class): + custom_content_block.hidden_for.add(school_class.id) + + custom_content_block.save_revision().publish() def create_chapter_from_objective_group(module): - chapter = Chapter(title=f"XXX Leistungsziele") - # + chapter = Chapter(title=f"XXX Lernziele") + first_sibling = module.get_first_child() if first_sibling is not None: first_sibling.add_sibling(instance=chapter, pos='left') @@ -56,24 +85,27 @@ def create_chapter_from_objective_group(module): chapter.save() return chapter -def create_content_block_from_objective(objective_group, chapter): +def create_content_block_from_objective(objective_group, chapter, owner=None): content_block = ContentBlock( - title=f"XXX {objective_group.title}", - type="normal" + title=f"XXX {objective_group.get_title_display()}", + type="normal", + owner=owner, + user_created=owner is not None, ) - chapter.add_child(instance=content_block) + chapter.add_child(instance=content_block) return content_block -def create_text_in_content_block( objectives, content_block): + +def create_text_in_content_block(objectives, content_block): print(content_block.contents) + objective_li = [f"
  • ---{objective.owner}--- {objective.text}
  • " for objective in objectives if objective.text] + texts = [{'type': 'text_block', - 'value': { - 'text': f"- {objective.text}" - }} for objective in objectives] + 'value': { + 'text': f"" + }}] content_block.contents = json.dumps(texts) content_block.save_revision().publish() return content_block - - From 2446b525962b9a84ff7e87b278dc26c2677deb08 Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Mon, 18 Dec 2023 16:28:12 +0100 Subject: [PATCH 03/21] Working. But too many content blocks... todo remove duplicates. --- .../commands/migrate_objectives_to_content.py | 97 ++++++++++++------- 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/server/books/management/commands/migrate_objectives_to_content.py b/server/books/management/commands/migrate_objectives_to_content.py index 814c85da..4970f3cd 100644 --- a/server/books/management/commands/migrate_objectives_to_content.py +++ b/server/books/management/commands/migrate_objectives_to_content.py @@ -25,54 +25,83 @@ class Command(BaseCommand): chapter = create_chapter_from_objective_group(module) for objective_group in module.objective_groups.all(): - print(f" Objective group: {objective_group}") - print(" Default objectives:") + default_content_block = create_default_content(objective_group, chapter) - # create "Verlagsinhalt Lernziele" - - default_objectives = objective_group.objectives.filter(owner__isnull=True) - - default_content_block = create_content_block_from_objective(objective_group, chapter) - create_text_in_content_block(default_objectives, default_content_block) - - for objective in default_objectives: - print(f" Objective: {objective} {objective.owner}") # Create "Benutzerdefinierte Lernziele" - custom_objectives = objective_group.objectives.filter(owner__isnull=False) - custom_objectives_by_owner = {} + custom_objectives_by_owner = get_objectives_by_owner(objective_group) - for objective in custom_objectives: - owner = objective.owner - owners_school_classes = owner.school_classes.all() - - if owner not in custom_objectives_by_owner: - custom_objectives_by_owner[owner] = [] - - custom_objectives_by_owner[owner].append(objective) for owner, owner_objectives in custom_objectives_by_owner.items(): print(f" Owner: {owner}") print(f" Objectives: ") - default_objectives = objective_group.objectives.filter(owner__isnull=True) - owner_objectives.extend(default_objectives) - for objective in owner_objectives: - print(f" Objective: {objective} {objective.owner}") - # - custom_content_block = create_content_block_from_objective(objective_group, chapter, owner=owner) - create_text_in_content_block(owner_objectives, custom_content_block) + default_objectives = list(objective_group.objectives.filter(owner__isnull=True)) - owners_school_classes = owner.school_classes.all() - for school_class in owners_school_classes: - custom_content_block.visible_for.add(school_class.id) + visible_default_objectives_by_class = remove_hidden_objectives(default_objectives, owner) - if objective.is_hidden_for_class(school_class): - custom_content_block.hidden_for.add(school_class.id) + for school_class in visible_default_objectives_by_class.keys(): + print(f" School class: {school_class}") + school_class_objectives = visible_default_objectives_by_class[school_class] + owner_objectives - custom_content_block.save_revision().publish() + for objective in school_class_objectives: + print(f" Objective: {objective} {objective.owner}") + custom_content_block = create_content_block_from_objective(objective_group, chapter, + owner=owner) + create_text_in_content_block(school_class_objectives, custom_content_block) + custom_content_block.visible_for.add(school_class) + + if custom_content_block: + default_content_block.hidden_for.add(school_class) + + + + + + +def create_default_content(objective_group, chapter): + print(f" Objective group: {objective_group}") + print(" Default objectives:") + + # create "Verlagsinhalt Lernziele" + + default_objectives = objective_group.objectives.filter(owner__isnull=True) + + default_content_block = create_content_block_from_objective(objective_group, chapter) + create_text_in_content_block(default_objectives, default_content_block) + + for objective in default_objectives: + print(f" Objective: {objective} {objective.owner}") + + return default_content_block + + +def remove_hidden_objectives(objectives, user): + school_classes = user.school_classes.all() + visible_objectives = {} + for school_class in school_classes: + if school_class not in visible_objectives: + visible_objectives[school_class] = [] + objectives_vis = [objective for objective in objectives if not objective.is_hidden_for_class(school_class)] + visible_objectives[school_class].extend(objectives_vis) + return visible_objectives + + +def get_objectives_by_owner(objective_group): + custom_objectives = objective_group.objectives.filter(owner__isnull=False) + custom_objectives_by_owner = {} + + for objective in custom_objectives: + owner = objective.owner + + if owner not in custom_objectives_by_owner: + custom_objectives_by_owner[owner] = [] + + custom_objectives_by_owner[owner].append(objective) + + return custom_objectives_by_owner def create_chapter_from_objective_group(module): chapter = Chapter(title=f"XXX Lernziele") From 6069c47c5eab9c537449e2a3e35d5a4a03e2ea97 Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Mon, 18 Dec 2023 17:02:41 +0100 Subject: [PATCH 04/21] Refactoring --- .../commands/migrate_objectives_to_content.py | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/server/books/management/commands/migrate_objectives_to_content.py b/server/books/management/commands/migrate_objectives_to_content.py index 4970f3cd..22d3a505 100644 --- a/server/books/management/commands/migrate_objectives_to_content.py +++ b/server/books/management/commands/migrate_objectives_to_content.py @@ -20,6 +20,8 @@ class Command(BaseCommand): ContentBlock.objects.filter(title__startswith="XXX").delete() Chapter.objects.filter(title__startswith="XXX").delete() + createed_content_blocks = 0 + for module in Module.objects.filter(title="Politische Streitfragen"): print(f"Module: {module}") chapter = create_chapter_from_objective_group(module) @@ -27,38 +29,42 @@ class Command(BaseCommand): for objective_group in module.objective_groups.all(): default_content_block = create_default_content(objective_group, chapter) - # Create "Benutzerdefinierte Lernziele" - custom_objectives_by_owner = get_objectives_by_owner(objective_group) - for owner, owner_objectives in custom_objectives_by_owner.items(): print(f" Owner: {owner}") print(f" Objectives: ") default_objectives = list(objective_group.objectives.filter(owner__isnull=True)) - visible_default_objectives_by_class = remove_hidden_objectives(default_objectives, owner) + contentblocks_by_merged_objectives_ids = {} for school_class in visible_default_objectives_by_class.keys(): print(f" School class: {school_class}") - school_class_objectives = visible_default_objectives_by_class[school_class] + owner_objectives + merged_objectives = visible_default_objectives_by_class[school_class] + owner_objectives + merged_objectives_ids = tuple(objective.id for objective in merged_objectives) - for objective in school_class_objectives: - print(f" Objective: {objective} {objective.owner}") - custom_content_block = create_content_block_from_objective(objective_group, chapter, - owner=owner) - - create_text_in_content_block(school_class_objectives, custom_content_block) - custom_content_block.visible_for.add(school_class) + # Create content block if that set of objectives has not been created yet + if merged_objectives_ids not in contentblocks_by_merged_objectives_ids: + for objective in merged_objectives: + print(f" Objective: {objective} {objective.owner}") + custom_content_block = create_content_block_from_objective(objective_group, chapter, + owner=owner) + contentblocks_by_merged_objectives_ids[merged_objectives_ids] = custom_content_block + create_text_in_content_block(merged_objectives, custom_content_block) + createed_content_blocks += 1 + else: + print(f" Objective: Reuse content block") + # set visibility + current_content_block = contentblocks_by_merged_objectives_ids[merged_objectives_ids] + current_content_block.visible_for.add(school_class) + # hide default objectives content if custom content exists if custom_content_block: default_content_block.hidden_for.add(school_class) - - - + print(f"Created {createed_content_blocks} content blocks") def create_default_content(objective_group, chapter): @@ -103,6 +109,7 @@ def get_objectives_by_owner(objective_group): return custom_objectives_by_owner + def create_chapter_from_objective_group(module): chapter = Chapter(title=f"XXX Lernziele") From c88447ebb6f6eafde1cf1fe69e8fbb829197b163 Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Tue, 19 Dec 2023 10:25:05 +0100 Subject: [PATCH 05/21] Add Error Handling --- .../commands/migrate_objectives_to_content.py | 98 ++++++++++--------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/server/books/management/commands/migrate_objectives_to_content.py b/server/books/management/commands/migrate_objectives_to_content.py index 22d3a505..f721e39b 100644 --- a/server/books/management/commands/migrate_objectives_to_content.py +++ b/server/books/management/commands/migrate_objectives_to_content.py @@ -1,18 +1,13 @@ import json +from logging import getLogger from django.core.management import BaseCommand -from books.blocks import TextBlock -from books.categorize_modules import categorize_modules, delete_unused_levels, uncategorize_modules, \ - delete_unused_categories -from books.categorize_modules import create_default_levels_and_categories -from books.models import Module -from books.models import ContentBlock, Chapter -from django.db.models import Avg, Count, Min, Sum +from books.models import Chapter from books.models import ContentBlock -from users.models import SchoolClass - +from books.models import Module +logger = getLogger(__name__) class Command(BaseCommand): def handle(self, *args, **options): ContentBlock.objects.filter(title__startswith="TESTOBJECTIVE").delete() @@ -22,49 +17,61 @@ class Command(BaseCommand): createed_content_blocks = 0 - for module in Module.objects.filter(title="Politische Streitfragen"): - print(f"Module: {module}") - chapter = create_chapter_from_objective_group(module) + failed_modules = [] - for objective_group in module.objective_groups.all(): - default_content_block = create_default_content(objective_group, chapter) + for module in Module.objects.all(): + try: + chapter = create_chapter_from_objective_group(module) - # Create "Benutzerdefinierte Lernziele" - custom_objectives_by_owner = get_objectives_by_owner(objective_group) + for objective_group in module.objective_groups.all(): + default_content_block = create_default_content(objective_group, chapter) - for owner, owner_objectives in custom_objectives_by_owner.items(): - print(f" Owner: {owner}") - print(f" Objectives: ") + # Create "Benutzerdefinierte Lernziele" + custom_objectives_by_owner = get_objectives_by_owner(objective_group) - default_objectives = list(objective_group.objectives.filter(owner__isnull=True)) - visible_default_objectives_by_class = remove_hidden_objectives(default_objectives, owner) - contentblocks_by_merged_objectives_ids = {} + for owner, owner_objectives in custom_objectives_by_owner.items(): + print(f" Owner: {owner}") + print(f" Objectives: ") - for school_class in visible_default_objectives_by_class.keys(): - print(f" School class: {school_class}") - merged_objectives = visible_default_objectives_by_class[school_class] + owner_objectives - merged_objectives_ids = tuple(objective.id for objective in merged_objectives) + default_objectives = list(objective_group.objectives.filter(owner__isnull=True)) + visible_default_objectives_by_class = remove_hidden_objectives(default_objectives, owner) + contentblocks_by_merged_objectives_ids = {} - # Create content block if that set of objectives has not been created yet - if merged_objectives_ids not in contentblocks_by_merged_objectives_ids: - for objective in merged_objectives: - print(f" Objective: {objective} {objective.owner}") - custom_content_block = create_content_block_from_objective(objective_group, chapter, - owner=owner) - contentblocks_by_merged_objectives_ids[merged_objectives_ids] = custom_content_block - create_text_in_content_block(merged_objectives, custom_content_block) - createed_content_blocks += 1 - else: - print(f" Objective: Reuse content block") + for school_class in visible_default_objectives_by_class.keys(): + print(f" School class: {school_class}") + merged_objectives = visible_default_objectives_by_class[school_class] + owner_objectives + merged_objectives_ids = tuple(objective.id for objective in merged_objectives) - # set visibility - current_content_block = contentblocks_by_merged_objectives_ids[merged_objectives_ids] - current_content_block.visible_for.add(school_class) - # hide default objectives content if custom content exists - if custom_content_block: - default_content_block.hidden_for.add(school_class) + # Create content block if that set of objectives has not been created yet + if merged_objectives_ids not in contentblocks_by_merged_objectives_ids: + for objective in merged_objectives: + print(f" Objective: {objective} {objective.owner}") + pass + custom_content_block = create_content_block_from_objective(objective_group, chapter, + owner=owner) + contentblocks_by_merged_objectives_ids[merged_objectives_ids] = custom_content_block + create_text_in_content_block(merged_objectives, custom_content_block) + createed_content_blocks += 1 + else: + print(f" Objective: Reuse content block") + pass + # set visibility + current_content_block = contentblocks_by_merged_objectives_ids[merged_objectives_ids] + current_content_block.visible_for.add(school_class) + # hide default objectives content if custom content exists + if custom_content_block: + default_content_block.hidden_for.add(school_class) + except Exception as e: + print(e) + print(f"Error with module {module}") + logger.error(e) + failed_modules.append(module) print(f"Created {createed_content_blocks} content blocks") + print(f"Failed modules: {len(failed_modules)}") + + for module in failed_modules: + print(f"Faile module: {module}") def create_default_content(objective_group, chapter): @@ -73,13 +80,14 @@ def create_default_content(objective_group, chapter): # create "Verlagsinhalt Lernziele" - default_objectives = objective_group.objectives.filter(owner__isnull=True) + default_objectives = objective_group.objectives.filter(owner__isnull=True).order_by('id') default_content_block = create_content_block_from_objective(objective_group, chapter) create_text_in_content_block(default_objectives, default_content_block) for objective in default_objectives: print(f" Objective: {objective} {objective.owner}") + pass return default_content_block @@ -136,7 +144,7 @@ def create_content_block_from_objective(objective_group, chapter, owner=None): def create_text_in_content_block(objectives, content_block): print(content_block.contents) - objective_li = [f"
  • ---{objective.owner}--- {objective.text}
  • " for objective in objectives if objective.text] + objective_li = [f"
  • {objective.text} -{objective.owner}-
  • " for objective in objectives if objective.text] texts = [{'type': 'text_block', 'value': { From 3dcfb92dbfc63622f635aa587b6cb8e3e2afbfdf Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Tue, 19 Dec 2023 16:01:29 +0100 Subject: [PATCH 06/21] Add analyze snapshots command --- .../management/commands/analyze_snapshots.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 server/books/management/commands/analyze_snapshots.py diff --git a/server/books/management/commands/analyze_snapshots.py b/server/books/management/commands/analyze_snapshots.py new file mode 100644 index 00000000..c97497bd --- /dev/null +++ b/server/books/management/commands/analyze_snapshots.py @@ -0,0 +1,20 @@ +from django.core.management import BaseCommand +from django.db.models import Count +from django.db.models.functions import ExtractYear +from books.models import Snapshot + + +# Query to group by creator's email, count the snapshots, and order by the count + +class Command(BaseCommand): + def handle(self, *args, **options): + snapshots_grouped = (Snapshot.objects + .annotate(year=ExtractYear('created')) + .values('year', 'creator__email') + .annotate(count=Count('id')) + .order_by('year', '-count')) # Order by year and then by count (descending) + + # To access the results + for snapshot in snapshots_grouped: + modified_email = snapshot['creator__email'].split('@')[0] + '@skillbox.ch' + print(f"Year: {snapshot['year']}, Creator Email: {modified_email}, Count: {snapshot['count']}") From 1908f11370cac8b3149a4acf4f5fa18a5a77f9b8 Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Thu, 25 Jan 2024 11:06:33 +0100 Subject: [PATCH 07/21] Add migrate objective snapshot command --- .../commands/migrate_objective_snapshots.py | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 server/books/management/commands/migrate_objective_snapshots.py diff --git a/server/books/management/commands/migrate_objective_snapshots.py b/server/books/management/commands/migrate_objective_snapshots.py new file mode 100644 index 00000000..edd5f370 --- /dev/null +++ b/server/books/management/commands/migrate_objective_snapshots.py @@ -0,0 +1,108 @@ + + +import json +from logging import getLogger + +from django.core.management import BaseCommand + +from books.management.commands.migrate_objectives_to_content import create_chapter_from_objective_group, \ + create_content_block_from_objective, create_text_in_content_block +from books.models import Chapter, ObjectiveGroupSnapshot, ContentBlockSnapshot, Snapshot +from books.models import ContentBlock +from books.models import Module +from objectives.models import Objective, ObjectiveSnapshot, ObjectiveGroup + +logger = getLogger(__name__) +class Command(BaseCommand): + def handle(self, *args, **options): + prefix = "SNAP " + ContentBlock.objects.filter(title__startswith=prefix).delete() + Chapter.objects.filter(title__startswith=prefix).delete() + + + + analyze() + + createed_content_blocks = 0 + + failed_modules = [] + + visible_objectives_by_ids = {} + + for module in Module.objects.filter(): + # try: + + snapshots = Snapshot.objects.filter(module=module) + module_snapshot_by_id = {} + #chapter = create_chapter_from_objective_group(module) + + + for snapshot in snapshots: + # hier objective snapshots entfernt werden. die werden nicht mehr gebraucht + # for objective_group_snapshot in self.objective_groups.through.objects.all(): + # if objective_group_snapshot.hidden: + # objective_group_snapshot.objective_group.hidden_for.add(selected_class) + default_objectives = Objective.objects.filter(group__module=module, owner__isnull=True) + visible_objectives = list(default_objectives) + visible_objectives_ids = [objective.id for objective in visible_objectives] + + for hidden_objective in snapshot.hidden_objectives.all(): + visible_objectives = [objective for objective in visible_objectives if hidden_objective.id not in visible_objectives_ids] + for custom_objective_snapshot in snapshot.custom_objectives.all(): + visible_objectives.append(custom_objective_snapshot) + + # filter for unique texts in objectives + # TODO: I don't know why this is necessary + objectives_by_texts = {} + for objective in visible_objectives: + if objective.text not in objectives_by_texts: + objectives_by_texts[objective.text] = objective + visible_objectives = list(objectives_by_texts.values()) + + if visible_objectives: + # make comvinations of objectives unique, this prevents generatino of many duplicated content blocks + visible_objectives_hash = hash([objective.text for objective in visible_objectives].__str__()) + + visible_objectives_by_ids[visible_objectives_hash] = visible_objectives + + + for objectives in visible_objectives_by_ids.values(): + print("") + for objective in objectives: + print(f" Objective: {objective.group} {objective} {objective.owner}") + print("-") + + print(f" visible_objectives_by_ids: {len(visible_objectives_by_ids.items())}") + + + # create custom content blocks with the objectives + createed_content_blocks = 0 + for objectives in visible_objectives_by_ids.values(): + module = objectives[0].group.module + chapter = create_chapter_from_objective_group(module, prefix="SNAP ") + + # Owner des custom blocks festlegen + custom_content_block = create_content_block_snapshot_from_objective(objectives[0].group, chapter, + owner=None, prefix="SNAP ") + create_text_in_content_block(objectives, custom_content_block) + createed_content_blocks += 1 + + print(f"created_content_blocks: {createed_content_blocks}") + + +def analyze(): + print(f""" + OjectiveGroups: {ObjectiveGroup.objects.count()} + Objectives: {Objective.objects.count()} + + ObjectiveGroupSnapshots: {ObjectiveGroupSnapshot.objects.count()} + ObjectivesSnapshots: {ObjectiveSnapshot.objects.count()} + + + ObjectiveGroups: {ObjectiveGroup.objects.filter(objectivegroupsnapshot__isnull=True).count()} + Objectives: {Objective.objects.filter(objectivesnapshot__isnull=True).count()} + + Snapshot: {Snapshot.objects.count()} + """) + + From 9f16573b9217b004333c168436350035fc40c90a Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Mon, 29 Jan 2024 16:31:38 +0100 Subject: [PATCH 08/21] Fix bug in content creation --- .../commands/migrate_objective_snapshots.py | 16 +- .../commands/migrate_objectives_to_content.py | 201 +++++++++++++----- server/books/models/snapshot.py | 19 +- server/test_objectives_migrations.sh | 0 4 files changed, 176 insertions(+), 60 deletions(-) create mode 100644 server/test_objectives_migrations.sh diff --git a/server/books/management/commands/migrate_objective_snapshots.py b/server/books/management/commands/migrate_objective_snapshots.py index edd5f370..d5be6197 100644 --- a/server/books/management/commands/migrate_objective_snapshots.py +++ b/server/books/management/commands/migrate_objective_snapshots.py @@ -6,8 +6,9 @@ from logging import getLogger from django.core.management import BaseCommand from books.management.commands.migrate_objectives_to_content import create_chapter_from_objective_group, \ - create_content_block_from_objective, create_text_in_content_block -from books.models import Chapter, ObjectiveGroupSnapshot, ContentBlockSnapshot, Snapshot + create_content_block_from_objective, create_text_in_content_block, create_content_block_snapshot_from_objective, \ + create_chapter_snapshot_from_objective_group +from books.models import Chapter, ObjectiveGroupSnapshot, ContentBlockSnapshot, Snapshot, ChapterSnapshot from books.models import ContentBlock from books.models import Module from objectives.models import Objective, ObjectiveSnapshot, ObjectiveGroup @@ -18,6 +19,8 @@ class Command(BaseCommand): prefix = "SNAP " ContentBlock.objects.filter(title__startswith=prefix).delete() Chapter.objects.filter(title__startswith=prefix).delete() + ContentBlockSnapshot.objects.filter(title__startswith=prefix).delete() + ChapterSnapshot.objects.filter(chapter__title__startswith=prefix).delete() @@ -78,11 +81,16 @@ class Command(BaseCommand): # create custom content blocks with the objectives createed_content_blocks = 0 for objectives in visible_objectives_by_ids.values(): + snapshot = objectives[0].group.module.snapshots.first() module = objectives[0].group.module - chapter = create_chapter_from_objective_group(module, prefix="SNAP ") + # does that work? + chapter = module.get_first_child() + if "Lernziele" not in chapter.title: + raise Exception("Chapter does not contain 'Lernziele'") + #chapter = create_chapter_snapshot_from_objective_group(module, snapshot, prefix="SNAP ") # Owner des custom blocks festlegen - custom_content_block = create_content_block_snapshot_from_objective(objectives[0].group, chapter, + custom_content_block = create_content_block_snapshot_from_objective(objectives[0].group, chapter, snapshot, owner=None, prefix="SNAP ") create_text_in_content_block(objectives, custom_content_block) createed_content_blocks += 1 diff --git a/server/books/management/commands/migrate_objectives_to_content.py b/server/books/management/commands/migrate_objectives_to_content.py index f721e39b..ec351a23 100644 --- a/server/books/management/commands/migrate_objectives_to_content.py +++ b/server/books/management/commands/migrate_objectives_to_content.py @@ -2,72 +2,116 @@ import json from logging import getLogger from django.core.management import BaseCommand +from django.core.exceptions import ValidationError -from books.models import Chapter +from books.models import Chapter, ObjectiveGroupSnapshot, Snapshot, ContentBlockSnapshot, ChapterSnapshot from books.models import ContentBlock from books.models import Module +from objectives.models import ObjectiveSnapshot, Objective, ObjectiveGroup logger = getLogger(__name__) + + class Command(BaseCommand): def handle(self, *args, **options): ContentBlock.objects.filter(title__startswith="TESTOBJECTIVE").delete() + ContentBlock.objects.filter(title__startswith="CUSTOM").delete() Chapter.objects.filter(title__startswith="TESTOBJECTIVE").delete() ContentBlock.objects.filter(title__startswith="XXX").delete() Chapter.objects.filter(title__startswith="XXX").delete() - createed_content_blocks = 0 + created_content_blocks = 0 failed_modules = [] - for module in Module.objects.all(): + # This dict stores all content blocks that have been created for a set of objectives + # In this way we can reuse content blocks for the same set of objectives + created_default_content_blocks = {} + + for module in Module.objects.filter(title="Politik mitbestimmen"): try: chapter = create_chapter_from_objective_group(module) for objective_group in module.objective_groups.all(): - default_content_block = create_default_content(objective_group, chapter) + default_objectives = list(objective_group.objectives.filter(owner__isnull=True, ) + .exclude(objectivesnapshot__isnull=False).order_by('order')) + + default_objectives_ids = tuple(objective.id for objective in default_objectives) + + # Create "Verlagsinhalte" content block if it does not exist yet + if default_objectives_ids not in created_default_content_blocks: + default_content_block = create_default_content(objective_group, chapter) + created_default_content_blocks[default_objectives_ids] = default_content_block + else: + default_content_block = created_default_content_blocks[default_objectives_ids] - # Create "Benutzerdefinierte Lernziele" custom_objectives_by_owner = get_objectives_by_owner(objective_group) - for owner, owner_objectives in custom_objectives_by_owner.items(): - print(f" Owner: {owner}") - print(f" Objectives: ") - - default_objectives = list(objective_group.objectives.filter(owner__isnull=True)) - visible_default_objectives_by_class = remove_hidden_objectives(default_objectives, owner) + if default_objectives or custom_objectives_by_owner: contentblocks_by_merged_objectives_ids = {} - for school_class in visible_default_objectives_by_class.keys(): - print(f" School class: {school_class}") - merged_objectives = visible_default_objectives_by_class[school_class] + owner_objectives - merged_objectives_ids = tuple(objective.id for objective in merged_objectives) + for owner, owner_objectives in custom_objectives_by_owner.items(): + print(f"Owner: {owner}") + print(f" Objectives: ") - # Create content block if that set of objectives has not been created yet - if merged_objectives_ids not in contentblocks_by_merged_objectives_ids: - for objective in merged_objectives: - print(f" Objective: {objective} {objective.owner}") + visible_default_objectives_by_class = remove_hidden_objectives(default_objectives, owner) + + for school_class in visible_default_objectives_by_class.keys(): + custom_content_block = None + + print(f" School class: {school_class}") + # merge "Verlagsinhalte" and "benutzerdefinierte Inhalte" + merged_objectives = visible_default_objectives_by_class[school_class] + owner_objectives + merged_objectives_ids = tuple(objective.id for objective in merged_objectives) + + + # Create content block if that set of objectives has not been created yet + if merged_objectives_ids not in contentblocks_by_merged_objectives_ids: + for objective in merged_objectives: + print(f" Objective: {objective} {objective.owner}") + + if merged_objectives_ids: + custom_content_block = create_content_block_from_objective(objective_group, chapter, + owner=owner, prefix=f"CUSTOM {owner.username} ") + contentblocks_by_merged_objectives_ids[merged_objectives_ids] = custom_content_block + create_text_in_content_block(merged_objectives, custom_content_block) + created_content_blocks += 1 + else: + print(f" Objective: Reuse content block") + custom_content_block = contentblocks_by_merged_objectives_ids[merged_objectives_ids] + + # set visibility + # hide default objectives if custom objectives exist + current_block = contentblocks_by_merged_objectives_ids.get(merged_objectives_ids) + if current_block: + # default_content_block.hidden_for.add(school_class) + # default_content_block.save_revision().publish() + # default_content_block.save() + # created_default_content_blocks[default_objectives_ids] = default_content_block pass - custom_content_block = create_content_block_from_objective(objective_group, chapter, - owner=owner) - contentblocks_by_merged_objectives_ids[merged_objectives_ids] = custom_content_block - create_text_in_content_block(merged_objectives, custom_content_block) - createed_content_blocks += 1 - else: - print(f" Objective: Reuse content block") - pass + current_block.visible_for.add(school_class) + current_block.save_revision().publish() + current_block.save() - # set visibility - current_content_block = contentblocks_by_merged_objectives_ids[merged_objectives_ids] - current_content_block.visible_for.add(school_class) - # hide default objectives content if custom content exists - if custom_content_block: - default_content_block.hidden_for.add(school_class) - except Exception as e: - print(e) + if custom_content_block: + pass + # default_content_block.hidden_for.add(school_class) + # default_content_block.save() + + # + # custom_content_block.visible_for.add(school_class) + # custom_content_block.save() + + + + + + except ValidationError as e: print(f"Error with module {module}") logger.error(e) failed_modules.append(module) - print(f"Created {createed_content_blocks} content blocks") + + print(f"Created {created_content_blocks} content blocks") print(f"Failed modules: {len(failed_modules)}") for module in failed_modules: @@ -75,19 +119,17 @@ class Command(BaseCommand): def create_default_content(objective_group, chapter): + """Create Verlagsinhalt Lernziele""" print(f" Objective group: {objective_group}") print(" Default objectives:") - # create "Verlagsinhalt Lernziele" + default_objectives = list(objective_group.objectives.filter(owner__isnull=True, objectivesnapshot__isnull=True).order_by('id')) - default_objectives = objective_group.objectives.filter(owner__isnull=True).order_by('id') - - default_content_block = create_content_block_from_objective(objective_group, chapter) + default_content_block = create_content_block_from_objective(objective_group, chapter, prefix="XXX YYY ") create_text_in_content_block(default_objectives, default_content_block) for objective in default_objectives: print(f" Objective: {objective} {objective.owner}") - pass return default_content_block @@ -103,8 +145,9 @@ def remove_hidden_objectives(objectives, user): return visible_objectives -def get_objectives_by_owner(objective_group): - custom_objectives = objective_group.objectives.filter(owner__isnull=False) +def get_objectives_by_owner(objective_group, exclude_snapshots=True): + custom_objectives = objective_group.objectives.filter(owner__isnull=False).exclude( + objectivesnapshot__isnull=False).order_by('order') custom_objectives_by_owner = {} for objective in custom_objectives: @@ -115,11 +158,20 @@ def get_objectives_by_owner(objective_group): custom_objectives_by_owner[owner].append(objective) + # add owners with hidden default objectives to custom objectives, needed for further processing + hidden_default_objectives = list( + objective_group.objectives.filter(owner__isnull=True, objectivesnapshot__isnull=True, hidden_for__isnull=False)) + for hidden_default_objective in hidden_default_objectives: + for school_class in hidden_default_objective.hidden_for.all(): + for teacher in school_class.get_teachers(): + if teacher not in custom_objectives_by_owner: + custom_objectives_by_owner[teacher] = [] + return custom_objectives_by_owner -def create_chapter_from_objective_group(module): - chapter = Chapter(title=f"XXX Lernziele") +def create_chapter_from_objective_group(module, prefix="XXX "): + chapter = Chapter(title=f"{prefix}Lernziele") first_sibling = module.get_first_child() if first_sibling is not None: @@ -130,26 +182,77 @@ def create_chapter_from_objective_group(module): return chapter -def create_content_block_from_objective(objective_group, chapter, owner=None): +def create_chapter_snapshot_from_objective_group(module, snapshot, prefix="XXX "): + chapter = Chapter.objects.filter(parent=module, title=f"{prefix}Lernziele").first() + chapter_snapshot = ChapterSnapshot(title=f"{prefix}Lernziele", snapshot=snapshot) + + first_sibling = module.get_first_child() + if first_sibling is not None: + first_sibling.add_sibling(instance=chapter_snapshot, pos='left') + return chapter + + +def create_content_block_from_objective(objective_group, chapter, owner=None, prefix="XXX "): content_block = ContentBlock( - title=f"XXX {objective_group.get_title_display()}", + title=f"{prefix}{objective_group.get_title_display()}", type="normal", owner=owner, user_created=owner is not None, + original_creator=owner ) - chapter.add_child(instance=content_block) return content_block +def create_content_block_snapshot_from_objective(objective_group, chapter, snapshot, owner=None, prefix="XXX "): + content_block_snapshot = ContentBlockSnapshot( + title=f"{prefix}{objective_group.get_title_display()}", + type="normal", + owner=owner, + user_created=owner is not None, + snapshot=snapshot + ) + + chapter.add_child(instance=content_block_snapshot) + return content_block_snapshot + + def create_text_in_content_block(objectives, content_block): - print(content_block.contents) + objectives = list(objectives) + objective_texts = set([objective.text for objective in objectives]) + objective_li = [f"
  • {objective.text} -{objective.owner}-
  • " for objective in objectives if objective.text] texts = [{'type': 'text_block', 'value': { 'text': f"
      {''.join(str(i) for i in objective_li)}
    " }}] + content_block.contents = json.dumps(texts) content_block.save_revision().publish() return content_block + + +def analyze(): + print(f""" + OjectiveGroups: {ObjectiveGroup.objects.count()} + Objectives: {Objective.objects.count()} + + ObjectiveGroupSnapshots: {ObjectiveGroupSnapshot.objects.count()} + ObjectivesSnapshots: {ObjectiveSnapshot.objects.filter(hidden=True).count()} + + + ObjectiveGroups: {ObjectiveGroup.objects.filter(objectivegroupsnapshot__isnull=True).count()} + Objectives: {Objective.objects.filter(objectivesnapshot__isnull=True).count()} + + Snapshot: {Snapshot.objects.count()} + """) + + +def clean_snapshots(): + emails = ["mia.teacher", "simone.gerber", "pascal.sigg", "dario.aebersold", "steph-teacher"] + for snapshot in Snapshot.objects.all(): + for email in emails: + if email in snapshot.creator.email: + print(f"Deleting snapshot {email}") + snapshot.delete() diff --git a/server/books/models/snapshot.py b/server/books/models/snapshot.py index cef603bb..ed851708 100644 --- a/server/books/models/snapshot.py +++ b/server/books/models/snapshot.py @@ -54,9 +54,11 @@ class SnapshotManager(models.Manager): description_hidden=chapter.description_hidden_for.filter(id=school_class.id).exists() ) base_qs = ContentBlock.get_by_parent(chapter).filter(contentblocksnapshot__isnull=True) + # Verlagsinhalte for content_block in base_qs.filter(user_created=False): if content_block.hidden_for.filter(id=school_class.id).exists(): snapshot.hidden_content_blocks.add(content_block) + # Benutzerdefinierte Inhalte for content_block in base_qs.filter(Q(user_created=True) & Q(owner=user)): new_content_block = ContentBlockSnapshot( hidden=content_block.is_hidden_for_class(school_class), @@ -78,9 +80,11 @@ class SnapshotManager(models.Manager): hidden=objective_group.hidden_for.filter(id=school_class.id).exists(), ) base_qs = objective_group.objectives.filter(objectivesnapshot__isnull=True) + # Verlagslernziele for objective in base_qs.filter(owner__isnull=True): if objective.hidden_for.filter(id=school_class.id).exists(): snapshot.hidden_objectives.add(objective) + # Benutzerdefinierte Lernziele for objective in base_qs.filter(owner=user): ObjectiveSnapshot.objects.create( hidden=objective.is_hidden_for_class(school_class=school_class), @@ -152,10 +156,11 @@ class Snapshot(models.Model): chapter.title_hidden_for.add(selected_class) if chapter_snapshot.description_hidden: chapter.description_hidden_for.add(selected_class) - for objective_group_snapshot in self.objective_groups.through.objects.all(): - if objective_group_snapshot.hidden: - objective_group_snapshot.objective_group.hidden_for.add(selected_class) - for objective in self.hidden_objectives.all(): - objective.hidden_for.add(selected_class) - for custom_objective in self.custom_objectives.all(): - custom_objective.to_regular_objective(owner=user, school_class=selected_class) + # hier objective snapshots entfernt werden. die werden nicht mehr gebraucht + # for objective_group_snapshot in self.objective_groups.through.objects.all(): + # if objective_group_snapshot.hidden: + # objective_group_snapshot.objective_group.hidden_for.add(selected_class) + # for objective in self.hidden_objectives.all(): + # objective.hidden_for.add(selected_class) + # for custom_objective in self.custom_objectives.all(): + # custom_objective.to_regular_objective(owner=user, school_class=selected_class) diff --git a/server/test_objectives_migrations.sh b/server/test_objectives_migrations.sh new file mode 100644 index 00000000..e69de29b From eef536b8010d863a167a9f4c6c1f7c477ad6e6af Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Mon, 29 Jan 2024 17:17:52 +0100 Subject: [PATCH 09/21] Fix visibiility of custom and default objectives --- .../commands/migrate_objectives_to_content.py | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/server/books/management/commands/migrate_objectives_to_content.py b/server/books/management/commands/migrate_objectives_to_content.py index ec351a23..f43cc757 100644 --- a/server/books/management/commands/migrate_objectives_to_content.py +++ b/server/books/management/commands/migrate_objectives_to_content.py @@ -64,46 +64,42 @@ class Command(BaseCommand): merged_objectives = visible_default_objectives_by_class[school_class] + owner_objectives merged_objectives_ids = tuple(objective.id for objective in merged_objectives) + if merged_objectives_ids == default_objectives_ids: + print(f" Objective: Reuse default content block") + # custom_content_block = default_content_block # Create content block if that set of objectives has not been created yet - if merged_objectives_ids not in contentblocks_by_merged_objectives_ids: + if merged_objectives_ids not in contentblocks_by_merged_objectives_ids and merged_objectives_ids != default_objectives_ids: for objective in merged_objectives: print(f" Objective: {objective} {objective.owner}") if merged_objectives_ids: - custom_content_block = create_content_block_from_objective(objective_group, chapter, - owner=owner, prefix=f"CUSTOM {owner.username} ") - contentblocks_by_merged_objectives_ids[merged_objectives_ids] = custom_content_block + custom_content_block = create_content_block_from_objective(objective_group, + chapter, + owner=owner, + prefix=f"CUSTOM {owner.username} ") + contentblocks_by_merged_objectives_ids[ + merged_objectives_ids] = custom_content_block create_text_in_content_block(merged_objectives, custom_content_block) created_content_blocks += 1 else: - print(f" Objective: Reuse content block") - custom_content_block = contentblocks_by_merged_objectives_ids[merged_objectives_ids] + if merged_objectives_ids != default_objectives_ids: + print(f" Objective: Reuse content block") + custom_content_block = contentblocks_by_merged_objectives_ids[ + merged_objectives_ids] + else: + print(f" Objective: Reuse default content block") # set visibility # hide default objectives if custom objectives exist - current_block = contentblocks_by_merged_objectives_ids.get(merged_objectives_ids) - if current_block: - # default_content_block.hidden_for.add(school_class) - # default_content_block.save_revision().publish() - # default_content_block.save() - # created_default_content_blocks[default_objectives_ids] = default_content_block - pass - current_block.visible_for.add(school_class) - current_block.save_revision().publish() - current_block.save() - if custom_content_block: - pass - # default_content_block.hidden_for.add(school_class) - # default_content_block.save() - - # - # custom_content_block.visible_for.add(school_class) - # custom_content_block.save() - - + default_content_block.hidden_for.add(school_class) + default_content_block.save_revision().publish() + default_content_block.save() + custom_content_block.visible_for.add(school_class) + custom_content_block.save_revision().publish() + custom_content_block.save() except ValidationError as e: @@ -123,7 +119,8 @@ def create_default_content(objective_group, chapter): print(f" Objective group: {objective_group}") print(" Default objectives:") - default_objectives = list(objective_group.objectives.filter(owner__isnull=True, objectivesnapshot__isnull=True).order_by('id')) + default_objectives = list( + objective_group.objectives.filter(owner__isnull=True, objectivesnapshot__isnull=True).order_by('id')) default_content_block = create_content_block_from_objective(objective_group, chapter, prefix="XXX YYY ") create_text_in_content_block(default_objectives, default_content_block) From 2b7d8eeda3ad65aba2b05e273e18dcd96c889e38 Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Thu, 1 Feb 2024 14:30:22 +0100 Subject: [PATCH 10/21] Add intermediate backups to migrations --- .../management/commands/analyze_snapshots.py | 18 +- .../commands/migrate_objective_snapshots.py | 215 ++++++++++++------ .../commands/migrate_objectives_to_content.py | 42 +++- .../commands/reset_all_passwords.py | 26 ++- server/test_objectives_migrations.sh | 8 + 5 files changed, 233 insertions(+), 76 deletions(-) mode change 100644 => 100755 server/test_objectives_migrations.sh diff --git a/server/books/management/commands/analyze_snapshots.py b/server/books/management/commands/analyze_snapshots.py index c97497bd..e6050a44 100644 --- a/server/books/management/commands/analyze_snapshots.py +++ b/server/books/management/commands/analyze_snapshots.py @@ -1,7 +1,8 @@ from django.core.management import BaseCommand from django.db.models import Count from django.db.models.functions import ExtractYear -from books.models import Snapshot +from books.models import Snapshot, ObjectiveGroupSnapshot +from objectives.models import ObjectiveSnapshot # Query to group by creator's email, count the snapshots, and order by the count @@ -18,3 +19,18 @@ class Command(BaseCommand): for snapshot in snapshots_grouped: modified_email = snapshot['creator__email'].split('@')[0] + '@skillbox.ch' print(f"Year: {snapshot['year']}, Creator Email: {modified_email}, Count: {snapshot['count']}") + + hidden_objectives_counter = 0 + custom_objectives_counter = 0 + + for snapshot in Snapshot.objects.all(): + if snapshot.hidden_objectives.count() > 0: + hidden_objectives_counter += 1 + + if snapshot.custom_objectives.count() > 0: + custom_objectives_counter += 1 + + print(f"Hidden objectives: {hidden_objectives_counter}") + print(f"Custom objectives: {custom_objectives_counter}") + print(f"ObjectiveGroupSnapshot objectives: {ObjectiveGroupSnapshot.objects.count()}") + print(f"ObjectiveSnapshot objectives: {ObjectiveSnapshot.objects.count()}") diff --git a/server/books/management/commands/migrate_objective_snapshots.py b/server/books/management/commands/migrate_objective_snapshots.py index d5be6197..4e369db6 100644 --- a/server/books/management/commands/migrate_objective_snapshots.py +++ b/server/books/management/commands/migrate_objective_snapshots.py @@ -1,5 +1,3 @@ - - import json from logging import getLogger @@ -7,110 +5,199 @@ from django.core.management import BaseCommand from books.management.commands.migrate_objectives_to_content import create_chapter_from_objective_group, \ create_content_block_from_objective, create_text_in_content_block, create_content_block_snapshot_from_objective, \ - create_chapter_snapshot_from_objective_group + create_chapter_snapshot_from_objective_group, create_content_block_contents from books.models import Chapter, ObjectiveGroupSnapshot, ContentBlockSnapshot, Snapshot, ChapterSnapshot from books.models import ContentBlock from books.models import Module from objectives.models import Objective, ObjectiveSnapshot, ObjectiveGroup logger = getLogger(__name__) + + class Command(BaseCommand): def handle(self, *args, **options): + """ + This command must be run after the migration of objectives to content blocks. + Migrate objective snapshots to content blocks + - Verlagsinhalte - deafult content blocks are referced in the snapshot by foreign key + + Man muss unterscheiden zwischen, snapshots die nur Verlagslernziele sichtbar und unsichtbar machen. + + Und solchen die auch benutzerdefinierte Lernziele sichtbar und unsichtbar machen. + + Es gibt keine custom objective groups! + + Es gibt keine hidden custom objective_groups + + Case1: + - 100% Verlagslernziele, 100% Verlagsgruppe, nicht neue content blocks erstellen, nichts hidden... migration muss nichts machen. + + Case2: + - 100% Verlagslernziele, 100% Verlagsgruppe, hidden gruppe, nur verlagsinhalt gruppe verstecken + + Case3: + - Bestende gruppe mit benutzerdefinierten lernzielen, - custom content_block snapshot erstellen. + - Einzelne verslagslernziele sind versteckt. - custom content_block snapshot erstellen. + + --- user created setzen bei custom content snapshot. + + """ prefix = "SNAP " ContentBlock.objects.filter(title__startswith=prefix).delete() Chapter.objects.filter(title__startswith=prefix).delete() ContentBlockSnapshot.objects.filter(title__startswith=prefix).delete() ChapterSnapshot.objects.filter(chapter__title__startswith=prefix).delete() - - analyze() + count = 0 + + case1_count = 0 + case2_count = 0 + case3_count = 0 createed_content_blocks = 0 - - failed_modules = [] - visible_objectives_by_ids = {} - for module in Module.objects.filter(): - # try: + for module in Module.objects.all(): # .filter(title__contains="Politik mitbestimmen"): + for snapshot in Snapshot.objects.filter(module=module): + for objective_group_snapshot in snapshot.objective_groups.through.objects.filter(objective_group__module=module,snapshot=snapshot): + header = f"{count} {module.title:50} {objective_group_snapshot.objective_group.get_title_display():25} {str(snapshot.creator):40} {objective_group_snapshot.hidden} " + count += 1 + objective_group = objective_group_snapshot.objective_group - snapshots = Snapshot.objects.filter(module=module) - module_snapshot_by_id = {} - #chapter = create_chapter_from_objective_group(module) + hidden_default_objectives = snapshot.hidden_objectives.filter( + group=objective_group) + + visible_custom_objectives = snapshot.custom_objectives.filter(snapshot=snapshot, hidden=False, + group=objective_group_snapshot.objective_group) + group_is_hidden = objective_group_snapshot.hidden - for snapshot in snapshots: - # hier objective snapshots entfernt werden. die werden nicht mehr gebraucht - # for objective_group_snapshot in self.objective_groups.through.objects.all(): - # if objective_group_snapshot.hidden: - # objective_group_snapshot.objective_group.hidden_for.add(selected_class) - default_objectives = Objective.objects.filter(group__module=module, owner__isnull=True) - visible_objectives = list(default_objectives) - visible_objectives_ids = [objective.id for objective in visible_objectives] + info = f"{hidden_default_objectives.count()} {visible_custom_objectives.count()}" - for hidden_objective in snapshot.hidden_objectives.all(): - visible_objectives = [objective for objective in visible_objectives if hidden_objective.id not in visible_objectives_ids] - for custom_objective_snapshot in snapshot.custom_objectives.all(): - visible_objectives.append(custom_objective_snapshot) + if (not hidden_default_objectives and not visible_custom_objectives and not group_is_hidden): + # print(f"{info} Case 1 - skip") + case1_count += 1 + break - # filter for unique texts in objectives - # TODO: I don't know why this is necessary - objectives_by_texts = {} - for objective in visible_objectives: - if objective.text not in objectives_by_texts: - objectives_by_texts[objective.text] = objective - visible_objectives = list(objectives_by_texts.values()) + if not hidden_default_objectives and not visible_custom_objectives and group_is_hidden: + print(header + f"Case 2 - {info} hide default content group") + case2_count += 1 + content_block = get_content_block_by_objective_group(objective_group_snapshot, + module, False, None) + snapshot.hidden_content_blocks.add(content_block.id) + snapshot.save() - if visible_objectives: - # make comvinations of objectives unique, this prevents generatino of many duplicated content blocks - visible_objectives_hash = hash([objective.text for objective in visible_objectives].__str__()) + if hidden_default_objectives or visible_custom_objectives: + print(header + f"Case 3 - {info} create custom content blocks") + case3_count += 1 - visible_objectives_by_ids[visible_objectives_hash] = visible_objectives + default_objectives = Objective.objects.filter(group=objective_group, + group__module=module, + owner__isnull=True) + visible_default_objectives = [objective for objective in default_objectives if objective.id not in hidden_default_objectives.values_list("id", flat=True)] + + # Benutzerdefinierte Lernziele + visible_custom_objectives = list(snapshot.custom_objectives.filter(hidden=False)) + + visible_objectives = visible_default_objectives + visible_custom_objectives + + # filter for unique texts in objectives + # TODO: I don't know why there are duplicated objectives + objectives_by_texts = {} + for objective in visible_objectives: + if objective.text not in objectives_by_texts: + objectives_by_texts[objective.text] = objective + visible_objectives = list(objectives_by_texts.values()) + + if visible_objectives: + # make combinations of objectives unique by text, this prevents generation of many duplicated content blocks + visible_objectives_hash = hash( + [objective.text for objective in visible_objectives].__str__()) + + visible_objectives_by_ids[visible_objectives_hash] = visible_objectives + + for objectives in visible_objectives_by_ids.values(): + print("") + for objective in objectives: + print(f" Objective: {objective.group} {objective} {objective.owner}") + print("-") + + print(f" visible_objectives_by_ids: {len(visible_objectives_by_ids.items())}") + + # create custom content blocks with the objectives + created_content_blocks = 0 + for objectives in visible_objectives_by_ids.values(): + chapter = module.get_first_child() + if "Lernziele" not in chapter.title: + raise Exception(f"Chapter does not contain 'Lernziele' first title is {chapter.title}") + # + # Owner des custom blocks festlegen + custom_content_block_snapshot = create_content_block_snapshot_from_objective( + objectives[0].group, chapter, snapshot, + owner=snapshot.creator, + prefix="SNAP ") + create_text_in_content_block(objectives, custom_content_block_snapshot, get_or_create=True) + created_content_blocks += 1 + snapshot.save() + print() + print(f"Skipped {case1_count} Case 1") + print(f"Hidden default content groups {case2_count} Case 2") + print(f"Created new content {case3_count} Case 3") - for objectives in visible_objectives_by_ids.values(): - print("") - for objective in objectives: - print(f" Objective: {objective.group} {objective} {objective.owner}") - print("-") +def get_content_block_by_objective_group(objective_group_snapshot, module, user_created: bool, user): + # TODO: review qustion, is it necessary to filter for module + content_block = ContentBlock.objects.filter( + title__contains=objective_group_snapshot.objective_group.get_title_display(), + user_created=user_created, owner=user).descendant_of(module) + if content_block.count() > 1: + return content_block.first() - print(f" visible_objectives_by_ids: {len(visible_objectives_by_ids.items())}") + return content_block.first() - # create custom content blocks with the objectives - createed_content_blocks = 0 - for objectives in visible_objectives_by_ids.values(): - snapshot = objectives[0].group.module.snapshots.first() - module = objectives[0].group.module - # does that work? - chapter = module.get_first_child() - if "Lernziele" not in chapter.title: - raise Exception("Chapter does not contain 'Lernziele'") - #chapter = create_chapter_snapshot_from_objective_group(module, snapshot, prefix="SNAP ") +def get_visible_default_objectives(): + default_objectives = Objective.objects.filter(group=ObjectiveGroup, group__module=module, owner__isnull=True) + visible_objectives = list(default_objectives) + visible_objectives_ids = [objective.id for objective in visible_objectives] - # Owner des custom blocks festlegen - custom_content_block = create_content_block_snapshot_from_objective(objectives[0].group, chapter, snapshot, - owner=None, prefix="SNAP ") - create_text_in_content_block(objectives, custom_content_block) - createed_content_blocks += 1 + # Verlags Lernziele + for hidden_objective in snapshot.hidden_objectives.all(): + visible_objectives = [objective for objective in visible_objectives if + hidden_objective.id not in visible_objectives_ids] + return visible_objectives - print(f"created_content_blocks: {createed_content_blocks}") + +def get_content_block_by_objectives(objectives, module, user_created=False, user=None): + contents = create_content_block_contents(objectives) + + content_block_qs = ContentBlock.objects.filter( + owner=user, + user_created=user_created, + contents=contents) + if content_block_qs.exists(): + return content_block_qs.first() + else: + raise Exception("Content block does not exist ") + + +# + +# print(f"created_content_blocks: {createed_content_blocks}") def analyze(): print(f""" OjectiveGroups: {ObjectiveGroup.objects.count()} Objectives: {Objective.objects.count()} - + ObjectiveGroupSnapshots: {ObjectiveGroupSnapshot.objects.count()} ObjectivesSnapshots: {ObjectiveSnapshot.objects.count()} - - + + ObjectiveGroups: {ObjectiveGroup.objects.filter(objectivegroupsnapshot__isnull=True).count()} Objectives: {Objective.objects.filter(objectivesnapshot__isnull=True).count()} - + Snapshot: {Snapshot.objects.count()} """) - - diff --git a/server/books/management/commands/migrate_objectives_to_content.py b/server/books/management/commands/migrate_objectives_to_content.py index f43cc757..89815d37 100644 --- a/server/books/management/commands/migrate_objectives_to_content.py +++ b/server/books/management/commands/migrate_objectives_to_content.py @@ -11,7 +11,6 @@ from objectives.models import ObjectiveSnapshot, Objective, ObjectiveGroup logger = getLogger(__name__) - class Command(BaseCommand): def handle(self, *args, **options): ContentBlock.objects.filter(title__startswith="TESTOBJECTIVE").delete() @@ -28,11 +27,11 @@ class Command(BaseCommand): # In this way we can reuse content blocks for the same set of objectives created_default_content_blocks = {} - for module in Module.objects.filter(title="Politik mitbestimmen"): + for module in Module.objects.all(): try: chapter = create_chapter_from_objective_group(module) - for objective_group in module.objective_groups.all(): + for objective_group in module.objective_groups.all().order_by('title'): default_objectives = list(objective_group.objectives.filter(owner__isnull=True, ) .exclude(objectivesnapshot__isnull=False).order_by('order')) @@ -50,6 +49,9 @@ class Command(BaseCommand): if default_objectives or custom_objectives_by_owner: contentblocks_by_merged_objectives_ids = {} + # cor custom objectives iterate over owners, + # - one ownsers custom objectives must not be changed by another owner + # - visibility is set per class for owner, owner_objectives in custom_objectives_by_owner.items(): print(f"Owner: {owner}") print(f" Objectives: ") @@ -101,6 +103,11 @@ class Command(BaseCommand): custom_content_block.save_revision().publish() custom_content_block.save() + if objective_group.hidden_for.filter(id=school_class.id).exists(): + default_content_block.hidden_for.add(school_class) + default_content_block.save_revision().publish() + default_content_block.save() + except ValidationError as e: print(f"Error with module {module}") @@ -214,10 +221,8 @@ def create_content_block_snapshot_from_objective(objective_group, chapter, snaps return content_block_snapshot -def create_text_in_content_block(objectives, content_block): +def create_text_in_content_block(objectives, content_block, get_or_create=False): objectives = list(objectives) - objective_texts = set([objective.text for objective in objectives]) - objective_li = [f"
  • {objective.text} -{objective.owner}-
  • " for objective in objectives if objective.text] texts = [{'type': 'text_block', @@ -225,10 +230,30 @@ def create_text_in_content_block(objectives, content_block): 'text': f"
      {''.join(str(i) for i in objective_li)}
    " }}] - content_block.contents = json.dumps(texts) + content_block.contents = create_content_block_contents(objectives) + + if get_or_create: + content_block_qs = ContentBlock.objects.filter(title=content_block.title, + owner=content_block.owner, + user_created=content_block.user_created, + contents=content_block.contents) + if content_block_qs.exists(): + content_block = content_block_qs.first() + content_block.save_revision().publish() return content_block +def create_content_block_contents(objectives): + objectives = list(objectives) + objective_li = [f"
  • {objective.text} -{objective.owner}-
  • " for objective in objectives if objective.text] + + texts = [{'type': 'text_block', + 'value': { + 'text': f"
      {''.join(str(i) for i in objective_li)}
    " + }}] + + contents = json.dumps(texts) + return contents def analyze(): print(f""" @@ -247,9 +272,10 @@ def analyze(): def clean_snapshots(): + """ utility function to clean up snapshots """ emails = ["mia.teacher", "simone.gerber", "pascal.sigg", "dario.aebersold", "steph-teacher"] for snapshot in Snapshot.objects.all(): for email in emails: if email in snapshot.creator.email: - print(f"Deleting snapshot {email}") + print(f"Deleting snapshot of user {email}") snapshot.delete() diff --git a/server/core/management/commands/reset_all_passwords.py b/server/core/management/commands/reset_all_passwords.py index 886a9266..26557519 100644 --- a/server/core/management/commands/reset_all_passwords.py +++ b/server/core/management/commands/reset_all_passwords.py @@ -1,3 +1,6 @@ +from concurrent.futures import ThreadPoolExecutor +from datetime import timedelta, datetime + from django.contrib.auth import get_user_model from django.core.management import BaseCommand @@ -12,6 +15,23 @@ class Command(BaseCommand): if result == 'YES': users = get_user_model().objects.all() - for user in users: - user.set_password('test') - user.save() + + with ThreadPoolExecutor(max_workers=10) as executor: + executor.map(process_user, users) + + +def process_user(usr): + # replace domain with skillbox.ch + usr.email = usr.email.split('@')[0] + '@skillbox.ch' + usr.username = usr.email + print(usr.email) + + # make license valid for 1 year + now = datetime.now() + + if usr.license_expiry_date: + usr.license_expiry_date = now + timedelta(days=365) + + # set password to test + usr.set_password('test') + usr.save() diff --git a/server/test_objectives_migrations.sh b/server/test_objectives_migrations.sh old mode 100644 new mode 100755 index e69de29b..90e17887 --- a/server/test_objectives_migrations.sh +++ b/server/test_objectives_migrations.sh @@ -0,0 +1,8 @@ +pg_restore --verbose --clean --no-acl --no-owner -h localhost -U skillbox -d skillbox latest.dump +python manage.py migrate +python manage.py reset_all_passwords +pg_dump -Fc --no-acl -h localhost -U skillbox skillbox > latest-anonymized.dump +python manage.py migrate_objectives_to_content +pg_dump -Fc --no-acl -h localhost -U skillbox skillbox > latest-migrated-objectives.dump +#python manage.py migrate_objective_snapshots +#pg_dump -Fc --no-acl -h localhost -U skillbox skillbox > latest-migrated-objectives-and-snapshots.dump From b046eae52b18401cba9ae7678148e5240c9afe7a Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Thu, 1 Feb 2024 14:31:18 +0100 Subject: [PATCH 11/21] Gitignore all dump files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 67ac19ec..9c640d03 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ server/test-reports/ *.prod.sql latest.dump* dump*.sql +*.dump From a765d7874f84468e30051bbcf4b6b58c26a32435 Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Thu, 1 Feb 2024 16:23:35 +0100 Subject: [PATCH 12/21] Remove test for objectives snapshot --- server/books/tests/test_snapshots.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/server/books/tests/test_snapshots.py b/server/books/tests/test_snapshots.py index aeda62c7..896fbaac 100644 --- a/server/books/tests/test_snapshots.py +++ b/server/books/tests/test_snapshots.py @@ -110,25 +110,6 @@ class CreateSnapshotTestCase(SkillboxTestCase): self.assertTrue( school_class_name in [school_class['name'] for school_class in custom_content_block.get('visibleFor')]) - - objectives = module['objectiveGroups'][0]['objectives'] - - self.assertEqual(len(objectives), 4) - - hidden_objective = [objective for objective in objectives if - objective['text'] == self.hidden_objective.text][0] - custom_objective = [objective for objective in objectives if - objective['text'] == self.custom_objective.text][0] - - # check if hidden objective is hidden for this school class - self.assertTrue( - school_class_name in [school_class['name'] for school_class in - hidden_objective.get('hiddenFor')]) - # check if the custom objective is visible for this school class - self.assertTrue( - school_class_name in [school_class['name'] for school_class in - custom_objective.get('visibleFor')]) - return module def _compare_content_blocks(self, content_blocks): From 1d5cf2cf05d24e4f80ad7c69b9b1f9829f2e3af8 Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Mon, 5 Feb 2024 09:58:46 +0100 Subject: [PATCH 13/21] Add comment how to restore the backup file --- server/test_objectives_migrations.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/test_objectives_migrations.sh b/server/test_objectives_migrations.sh index 90e17887..a206a701 100755 --- a/server/test_objectives_migrations.sh +++ b/server/test_objectives_migrations.sh @@ -6,3 +6,7 @@ python manage.py migrate_objectives_to_content pg_dump -Fc --no-acl -h localhost -U skillbox skillbox > latest-migrated-objectives.dump #python manage.py migrate_objective_snapshots #pg_dump -Fc --no-acl -h localhost -U skillbox skillbox > latest-migrated-objectives-and-snapshots.dump + + +# Use this command to restore the database from the dump: +#pg_restore --verbose --clean --no-acl --no-owner -h localhost -U skillbox -d skillbox latest-migrated-objectives.dump From e338f2e2adb6174b56059a087c94f598e8641733 Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Mon, 5 Feb 2024 10:45:19 +0100 Subject: [PATCH 14/21] Use fast hasher when DEBUG settings is true --- server/core/settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/core/settings.py b/server/core/settings.py index e7546779..a610c84b 100644 --- a/server/core/settings.py +++ b/server/core/settings.py @@ -158,6 +158,9 @@ AUTH_USER_MODEL = "users.User" WEAK_PASSWORDS = DEBUG if WEAK_PASSWORDS: AUTH_PASSWORD_VALIDATORS = [] + PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.MD5PasswordHasher", + ] else: AUTH_PASSWORD_VALIDATORS = [ { From d3b51b10068b423724ae7535fb64ceec3e321aac Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Mon, 5 Feb 2024 10:46:18 +0100 Subject: [PATCH 15/21] Fix several bugs migrate_objective snapshots command --- .../commands/migrate_objective_snapshots.py | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/server/books/management/commands/migrate_objective_snapshots.py b/server/books/management/commands/migrate_objective_snapshots.py index 4e369db6..46d3fb0f 100644 --- a/server/books/management/commands/migrate_objective_snapshots.py +++ b/server/books/management/commands/migrate_objective_snapshots.py @@ -57,10 +57,18 @@ class Command(BaseCommand): createed_content_blocks = 0 visible_objectives_by_ids = {} + snapshot_counter = 0 for module in Module.objects.all(): # .filter(title__contains="Politik mitbestimmen"): for snapshot in Snapshot.objects.filter(module=module): - for objective_group_snapshot in snapshot.objective_groups.through.objects.filter(objective_group__module=module,snapshot=snapshot): + group_counter = snapshot.objective_groups.through.objects.filter(objective_group__module=module, + snapshot=snapshot).count() + print( + f"{snapshot_counter} Snapshot id: {snapshot.id} Module: {module.title} {group_counter} groups {snapshot.creator} {snapshot.title}") + snapshot_counter += 1 + + for objective_group_snapshot in snapshot.objective_groups.through.objects.filter( + objective_group__module=module, snapshot=snapshot): header = f"{count} {module.title:50} {objective_group_snapshot.objective_group.get_title_display():25} {str(snapshot.creator):40} {objective_group_snapshot.hidden} " count += 1 objective_group = objective_group_snapshot.objective_group @@ -72,7 +80,6 @@ class Command(BaseCommand): group=objective_group_snapshot.objective_group) group_is_hidden = objective_group_snapshot.hidden - info = f"{hidden_default_objectives.count()} {visible_custom_objectives.count()}" if (not hidden_default_objectives and not visible_custom_objectives and not group_is_hidden): @@ -91,11 +98,14 @@ class Command(BaseCommand): if hidden_default_objectives or visible_custom_objectives: print(header + f"Case 3 - {info} create custom content blocks") case3_count += 1 - + # Verlags Lernziele default_objectives = Objective.objects.filter(group=objective_group, group__module=module, - owner__isnull=True) - visible_default_objectives = [objective for objective in default_objectives if objective.id not in hidden_default_objectives.values_list("id", flat=True)] + owner__isnull=True, + objectivesnapshot__isnull=True) + visible_default_objectives = [objective for objective in default_objectives if + objective.id not in hidden_default_objectives.values_list("id", + flat=True)] # Benutzerdefinierte Lernziele visible_custom_objectives = list(snapshot.custom_objectives.filter(hidden=False)) @@ -120,26 +130,27 @@ class Command(BaseCommand): for objectives in visible_objectives_by_ids.values(): print("") for objective in objectives: - print(f" Objective: {objective.group} {objective} {objective.owner}") + print(f" Objective: {objective.group} {objective} owner:{objective.owner}") print("-") print(f" visible_objectives_by_ids: {len(visible_objectives_by_ids.items())}") # create custom content blocks with the objectives created_content_blocks = 0 - for objectives in visible_objectives_by_ids.values(): - chapter = module.get_first_child() - if "Lernziele" not in chapter.title: - raise Exception(f"Chapter does not contain 'Lernziele' first title is {chapter.title}") - # - # Owner des custom blocks festlegen - custom_content_block_snapshot = create_content_block_snapshot_from_objective( - objectives[0].group, chapter, snapshot, - owner=snapshot.creator, - prefix="SNAP ") - create_text_in_content_block(objectives, custom_content_block_snapshot, get_or_create=True) - created_content_blocks += 1 - snapshot.save() + chapter = module.get_first_child() + if "Lernziele" not in chapter.title: + raise Exception(f"Chapter does not contain 'Lernziele' first title is {chapter.title}") + # + # Owner des custom blocks festlegen + custom_content_block_snapshot = create_content_block_snapshot_from_objective( + objective_group, chapter, snapshot, + owner=snapshot.creator, + prefix="SNAP ") + # + objectives = list(visible_objectives_by_ids.values())[0] + create_text_in_content_block(objectives, custom_content_block_snapshot, get_or_create=True) + created_content_blocks += 1 + snapshot.save() print() print(f"Skipped {case1_count} Case 1") print(f"Hidden default content groups {case2_count} Case 2") From 106bad4c52c4f1db3da021dfa27df11ab3e4b97a Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Mon, 5 Feb 2024 10:51:07 +0100 Subject: [PATCH 16/21] Fix linting error --- client/src/components/modules/Module.vue | 233 ++++++++++++----------- 1 file changed, 119 insertions(+), 114 deletions(-) diff --git a/client/src/components/modules/Module.vue b/client/src/components/modules/Module.vue index 29c67e1c..aeef77ed 100644 --- a/client/src/components/modules/Module.vue +++ b/client/src/components/modules/Module.vue @@ -5,19 +5,20 @@ v-if="module.id" >
    +

    + {{ module.metaTitle }} +

    -

    - {{ module.metaTitle }} -

    - -
    +
    -

    From 0631b428679300738bf2e358d1a147b55832e935 Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Tue, 6 Feb 2024 14:54:57 +0100 Subject: [PATCH 17/21] Add tests for objectives migration --- .../src/components/modules/SnapshotHeader.vue | 2 - .../commands/migrate_objective_snapshots.py | 212 ++++++++++-------- .../commands/migrate_objectives_to_content.py | 31 ++- server/books/models/snapshot.py | 8 - server/books/tests/queries.py | 4 +- .../books/tests/test_objectives_migration.py | 111 +++++++++ .../books/tests/test_snapshots_migration.py | 195 ++++++++++++++++ 7 files changed, 446 insertions(+), 117 deletions(-) create mode 100644 server/books/tests/test_objectives_migration.py create mode 100644 server/books/tests/test_snapshots_migration.py diff --git a/client/src/components/modules/SnapshotHeader.vue b/client/src/components/modules/SnapshotHeader.vue index ed5a0ca9..43e221cc 100644 --- a/client/src/components/modules/SnapshotHeader.vue +++ b/client/src/components/modules/SnapshotHeader.vue @@ -9,8 +9,6 @@

    In diesem Snapshot sind {{ changesCount }} Anpassungen gespeichert:

      -
    • {{ hiddenObjectives }} Lernziele wurden ausgeblendet
    • -
    • {{ newObjectives }} Lernziele wurde erfasst
    • {{ hiddenContentBlocks }} Inhaltsblöcke wurden ausgeblendet
    • {{ newContentBlocks }} Inhaltsblock wurde erfasst
    diff --git a/server/books/management/commands/migrate_objective_snapshots.py b/server/books/management/commands/migrate_objective_snapshots.py index 46d3fb0f..9049466c 100644 --- a/server/books/management/commands/migrate_objective_snapshots.py +++ b/server/books/management/commands/migrate_objective_snapshots.py @@ -49,112 +49,124 @@ class Command(BaseCommand): ChapterSnapshot.objects.filter(chapter__title__startswith=prefix).delete() analyze() - count = 0 - case1_count = 0 - case2_count = 0 - case3_count = 0 + migrate_snapshots() - createed_content_blocks = 0 - visible_objectives_by_ids = {} - snapshot_counter = 0 - for module in Module.objects.all(): # .filter(title__contains="Politik mitbestimmen"): - for snapshot in Snapshot.objects.filter(module=module): - group_counter = snapshot.objective_groups.through.objects.filter(objective_group__module=module, - snapshot=snapshot).count() - print( - f"{snapshot_counter} Snapshot id: {snapshot.id} Module: {module.title} {group_counter} groups {snapshot.creator} {snapshot.title}") - snapshot_counter += 1 +def migrate_snapshots(): + count = 0 - for objective_group_snapshot in snapshot.objective_groups.through.objects.filter( - objective_group__module=module, snapshot=snapshot): - header = f"{count} {module.title:50} {objective_group_snapshot.objective_group.get_title_display():25} {str(snapshot.creator):40} {objective_group_snapshot.hidden} " - count += 1 - objective_group = objective_group_snapshot.objective_group + case1_count = 0 + case2_count = 0 + case3_count = 0 - hidden_default_objectives = snapshot.hidden_objectives.filter( - group=objective_group) + createed_content_blocks = 0 + visible_objectives_by_ids = {} + snapshot_counter = 0 - visible_custom_objectives = snapshot.custom_objectives.filter(snapshot=snapshot, hidden=False, - group=objective_group_snapshot.objective_group) - group_is_hidden = objective_group_snapshot.hidden + for module in Module.objects.all(): + for snapshot in Snapshot.objects.filter(module=module): + group_counter = snapshot.objective_groups.through.objects.filter(objective_group__module=module, + snapshot=snapshot).count() + print( + f"{snapshot_counter} Snapshot id: {snapshot.id} Module: {module.title} {group_counter} groups {snapshot.creator} {snapshot.title}") + snapshot_counter += 1 - info = f"{hidden_default_objectives.count()} {visible_custom_objectives.count()}" + for objective_group_snapshot in snapshot.objective_groups.through.objects.filter( + objective_group__module=module, snapshot=snapshot): + header = f"{count} {module.title:50} {objective_group_snapshot.objective_group.get_title_display():25} {str(snapshot.creator):40} {objective_group_snapshot.hidden} " + count += 1 + objective_group = objective_group_snapshot.objective_group - if (not hidden_default_objectives and not visible_custom_objectives and not group_is_hidden): - # print(f"{info} Case 1 - skip") - case1_count += 1 - break + hidden_default_objectives = snapshot.hidden_objectives.filter( + group=objective_group) - if not hidden_default_objectives and not visible_custom_objectives and group_is_hidden: - print(header + f"Case 2 - {info} hide default content group") - case2_count += 1 - content_block = get_content_block_by_objective_group(objective_group_snapshot, - module, False, None) - snapshot.hidden_content_blocks.add(content_block.id) - snapshot.save() + visible_custom_objectives = snapshot.custom_objectives.filter(snapshot=snapshot, hidden=False, + group=objective_group_snapshot.objective_group) + group_is_hidden = objective_group_snapshot.hidden - if hidden_default_objectives or visible_custom_objectives: - print(header + f"Case 3 - {info} create custom content blocks") - case3_count += 1 - # Verlags Lernziele - default_objectives = Objective.objects.filter(group=objective_group, - group__module=module, - owner__isnull=True, - objectivesnapshot__isnull=True) - visible_default_objectives = [objective for objective in default_objectives if - objective.id not in hidden_default_objectives.values_list("id", - flat=True)] + info = f"{hidden_default_objectives.count()} {visible_custom_objectives.count()}" - # Benutzerdefinierte Lernziele - visible_custom_objectives = list(snapshot.custom_objectives.filter(hidden=False)) + if (not hidden_default_objectives and not visible_custom_objectives and not group_is_hidden): + # print(f"{info} Case 1 - skip") + case1_count += 1 + break - visible_objectives = visible_default_objectives + visible_custom_objectives + if not hidden_default_objectives and not visible_custom_objectives and group_is_hidden: + print(header + f"Case 2 - {info} hide default content group") + case2_count += 1 + content_block = get_content_block_by_objective_group(objective_group_snapshot, + module, False, None) + snapshot.hidden_content_blocks.add(content_block.id) + snapshot.save() - # filter for unique texts in objectives - # TODO: I don't know why there are duplicated objectives - objectives_by_texts = {} - for objective in visible_objectives: - if objective.text not in objectives_by_texts: - objectives_by_texts[objective.text] = objective - visible_objectives = list(objectives_by_texts.values()) + if hidden_default_objectives or visible_custom_objectives: + print(header + f"Case 3 - {info} create custom content blocks") + case3_count += 1 + # Verlags Lernziele + default_objectives = Objective.objects.filter(group=objective_group, + group__module=module, + owner__isnull=True, + objectivesnapshot__isnull=True) + visible_default_objectives = [objective for objective in default_objectives if + objective.id not in hidden_default_objectives.values_list("id", + flat=True)] - if visible_objectives: - # make combinations of objectives unique by text, this prevents generation of many duplicated content blocks - visible_objectives_hash = hash( - [objective.text for objective in visible_objectives].__str__()) + # Benutzerdefinierte Lernziele + visible_custom_objectives = list(snapshot.custom_objectives.filter(hidden=False)) - visible_objectives_by_ids[visible_objectives_hash] = visible_objectives + visible_objectives = visible_default_objectives + visible_custom_objectives - for objectives in visible_objectives_by_ids.values(): - print("") - for objective in objectives: - print(f" Objective: {objective.group} {objective} owner:{objective.owner}") - print("-") + # filter for unique texts in objectives + # TODO: I don't know why there are duplicated objectives + objectives_by_texts = {} + for objective in visible_objectives: + if objective.text not in objectives_by_texts: + objectives_by_texts[objective.text] = objective + visible_objectives = list(objectives_by_texts.values()) - print(f" visible_objectives_by_ids: {len(visible_objectives_by_ids.items())}") + if visible_objectives: + # make combinations of objectives unique by text, this prevents generation of many duplicated content blocks + visible_objectives_hash = hash( + [objective.text for objective in visible_objectives].__str__()) - # create custom content blocks with the objectives - created_content_blocks = 0 - chapter = module.get_first_child() - if "Lernziele" not in chapter.title: - raise Exception(f"Chapter does not contain 'Lernziele' first title is {chapter.title}") - # - # Owner des custom blocks festlegen - custom_content_block_snapshot = create_content_block_snapshot_from_objective( - objective_group, chapter, snapshot, - owner=snapshot.creator, - prefix="SNAP ") - # + visible_objectives_by_ids[visible_objectives_hash] = visible_objectives + + for objectives in visible_objectives_by_ids.values(): + print("") + for objective in objectives: + print(f" Objective: {objective.group} {objective} owner:{objective.owner}") + print("-") + + print(f" visible_objectives_by_ids: {len(visible_objectives_by_ids.items())}") + + # create custom content blocks with the objectives + created_content_blocks = 0 + chapter = module.get_first_child() + if "Lernziele" not in chapter.title: + raise Exception(f"Chapter does not contain 'Lernziele' first title is {chapter.title}") + # + # Owner des custom blocks festlegen + custom_content_block_snapshot = create_content_block_snapshot_from_objective( + objective_group, chapter, snapshot, + owner=snapshot.creator, + prefix="SNAP ") + # + # Hide default content block for this objective group, since custom content block is created + default_content_block = get_default_content_block(objective_group_snapshot, module) + if default_content_block: + print(f"Default content block: {default_content_block.title}") + snapshot.hidden_content_blocks.add(default_content_block.id) + + if list(visible_objectives_by_ids.values()): objectives = list(visible_objectives_by_ids.values())[0] create_text_in_content_block(objectives, custom_content_block_snapshot, get_or_create=True) created_content_blocks += 1 - snapshot.save() - print() - print(f"Skipped {case1_count} Case 1") - print(f"Hidden default content groups {case2_count} Case 2") - print(f"Created new content {case3_count} Case 3") + snapshot.save() + print() + print(f"Skipped {case1_count} Case 1") + print(f"Hidden default content groups {case2_count} Case 2") + print(f"Created new content {case3_count} Case 3") def get_content_block_by_objective_group(objective_group_snapshot, module, user_created: bool, user): @@ -168,7 +180,32 @@ def get_content_block_by_objective_group(objective_group_snapshot, module, user_ return content_block.first() +def get_default_content_block(objective_group_snapshot, module): + default_objectives = Objective.objects.filter(group=objective_group_snapshot.objective_group, + group__module=module, + owner__isnull=True, + objectivesnapshot__isnull=True) + + default_content_block_contents = create_content_block_contents(default_objectives) + + contents = json.loads(default_content_block_contents) + contents[0].get("value").get("text") + chapter = Chapter.objects.filter(title__contains="Lernziele").descendant_of(module) + text_contents = contents[0].get("value").get("text") + group_title = objective_group_snapshot.objective_group.get_title_display() + content_blocks = ContentBlock.objects.filter(user_created=False).descendant_of(chapter.first()) + + for content_block in content_blocks: + print(content_block.title, group_title) + if group_title in content_block.title: + return content_block + + if not content_block.exists(): + raise Exception("Content block does not exist ") + + def get_visible_default_objectives(): + # TODO: Objective Group geht so nicth default_objectives = Objective.objects.filter(group=ObjectiveGroup, group__module=module, owner__isnull=True) visible_objectives = list(default_objectives) visible_objectives_ids = [objective.id for objective in visible_objectives] @@ -193,11 +230,6 @@ def get_content_block_by_objectives(objectives, module, user_created=False, user raise Exception("Content block does not exist ") -# - -# print(f"created_content_blocks: {createed_content_blocks}") - - def analyze(): print(f""" OjectiveGroups: {ObjectiveGroup.objects.count()} diff --git a/server/books/management/commands/migrate_objectives_to_content.py b/server/books/management/commands/migrate_objectives_to_content.py index 89815d37..0b828fd1 100644 --- a/server/books/management/commands/migrate_objectives_to_content.py +++ b/server/books/management/commands/migrate_objectives_to_content.py @@ -19,6 +19,9 @@ class Command(BaseCommand): ContentBlock.objects.filter(title__startswith="XXX").delete() Chapter.objects.filter(title__startswith="XXX").delete() + migrate_objectives_to_content() + +def migrate_objectives_to_content(): created_content_blocks = 0 failed_modules = [] @@ -63,7 +66,9 @@ class Command(BaseCommand): print(f" School class: {school_class}") # merge "Verlagsinhalte" and "benutzerdefinierte Inhalte" - merged_objectives = visible_default_objectives_by_class[school_class] + owner_objectives + visible_owner_objectives = [objective for objective in owner_objectives if not objective.is_hidden_for_class(school_class)] + + merged_objectives = visible_default_objectives_by_class[school_class] + visible_owner_objectives merged_objectives_ids = tuple(objective.id for objective in merged_objectives) if merged_objectives_ids == default_objectives_ids: @@ -73,7 +78,7 @@ class Command(BaseCommand): # Create content block if that set of objectives has not been created yet if merged_objectives_ids not in contentblocks_by_merged_objectives_ids and merged_objectives_ids != default_objectives_ids: for objective in merged_objectives: - print(f" Objective: {objective} {objective.owner}") + print(f" Objective: {objective} {objective.owner}") if merged_objectives_ids: custom_content_block = create_content_block_from_objective(objective_group, @@ -165,6 +170,7 @@ def get_objectives_by_owner(objective_group, exclude_snapshots=True): # add owners with hidden default objectives to custom objectives, needed for further processing hidden_default_objectives = list( objective_group.objectives.filter(owner__isnull=True, objectivesnapshot__isnull=True, hidden_for__isnull=False)) + for hidden_default_objective in hidden_default_objectives: for school_class in hidden_default_objective.hidden_for.all(): for teacher in school_class.get_teachers(): @@ -175,7 +181,7 @@ def get_objectives_by_owner(objective_group, exclude_snapshots=True): def create_chapter_from_objective_group(module, prefix="XXX "): - chapter = Chapter(title=f"{prefix}Lernziele") + chapter = Chapter(title=f"Lernziele") first_sibling = module.get_first_child() if first_sibling is not None: @@ -187,8 +193,8 @@ def create_chapter_from_objective_group(module, prefix="XXX "): def create_chapter_snapshot_from_objective_group(module, snapshot, prefix="XXX "): - chapter = Chapter.objects.filter(parent=module, title=f"{prefix}Lernziele").first() - chapter_snapshot = ChapterSnapshot(title=f"{prefix}Lernziele", snapshot=snapshot) + chapter = Chapter.objects.filter(parent=module, title=f"Lernziele").first() + chapter_snapshot = ChapterSnapshot(title=f"Lernziele", snapshot=snapshot) first_sibling = module.get_first_child() if first_sibling is not None: @@ -198,7 +204,7 @@ def create_chapter_snapshot_from_objective_group(module, snapshot, prefix="XXX " def create_content_block_from_objective(objective_group, chapter, owner=None, prefix="XXX "): content_block = ContentBlock( - title=f"{prefix}{objective_group.get_title_display()}", + title=f"{objective_group.get_title_display()}", type="normal", owner=owner, user_created=owner is not None, @@ -210,7 +216,7 @@ def create_content_block_from_objective(objective_group, chapter, owner=None, pr def create_content_block_snapshot_from_objective(objective_group, chapter, snapshot, owner=None, prefix="XXX "): content_block_snapshot = ContentBlockSnapshot( - title=f"{prefix}{objective_group.get_title_display()}", + title=f"{objective_group.get_title_display()}", type="normal", owner=owner, user_created=owner is not None, @@ -223,13 +229,6 @@ def create_content_block_snapshot_from_objective(objective_group, chapter, snaps def create_text_in_content_block(objectives, content_block, get_or_create=False): objectives = list(objectives) - objective_li = [f"
  • {objective.text} -{objective.owner}-
  • " for objective in objectives if objective.text] - - texts = [{'type': 'text_block', - 'value': { - 'text': f"
      {''.join(str(i) for i in objective_li)}
    " - }}] - content_block.contents = create_content_block_contents(objectives) if get_or_create: @@ -245,11 +244,11 @@ def create_text_in_content_block(objectives, content_block, get_or_create=False) def create_content_block_contents(objectives): objectives = list(objectives) - objective_li = [f"
  • {objective.text} -{objective.owner}-
  • " for objective in objectives if objective.text] + objective_li = [f"
  • {objective.text}
  • " for objective in objectives if objective.text] texts = [{'type': 'text_block', 'value': { - 'text': f"
      {''.join(str(i) for i in objective_li)}
    " + 'text': f"
      {''.join(str(i) for i in objective_li)}
    " }}] contents = json.dumps(texts) diff --git a/server/books/models/snapshot.py b/server/books/models/snapshot.py index ed851708..3d2adfa3 100644 --- a/server/books/models/snapshot.py +++ b/server/books/models/snapshot.py @@ -156,11 +156,3 @@ class Snapshot(models.Model): chapter.title_hidden_for.add(selected_class) if chapter_snapshot.description_hidden: chapter.description_hidden_for.add(selected_class) - # hier objective snapshots entfernt werden. die werden nicht mehr gebraucht - # for objective_group_snapshot in self.objective_groups.through.objects.all(): - # if objective_group_snapshot.hidden: - # objective_group_snapshot.objective_group.hidden_for.add(selected_class) - # for objective in self.hidden_objectives.all(): - # objective.hidden_for.add(selected_class) - # for custom_objective in self.custom_objectives.all(): - # custom_objective.to_regular_objective(owner=user, school_class=selected_class) diff --git a/server/books/tests/queries.py b/server/books/tests/queries.py index ccfc63a3..e500bdd4 100644 --- a/server/books/tests/queries.py +++ b/server/books/tests/queries.py @@ -22,6 +22,7 @@ query ModulesQuery($slug: String, $id: ID) { contentBlocks { id title + userCreated originalCreator { id fullName @@ -32,7 +33,8 @@ query ModulesQuery($slug: String, $id: ID) { } hiddenFor { name - } + } + contents } } } diff --git a/server/books/tests/test_objectives_migration.py b/server/books/tests/test_objectives_migration.py new file mode 100644 index 00000000..785d15bc --- /dev/null +++ b/server/books/tests/test_objectives_migration.py @@ -0,0 +1,111 @@ +from django.test import RequestFactory +from graphene.test import Client +from graphql_relay import to_global_id, from_global_id + +from api.schema import schema +from api.utils import get_object +from books.factories import ModuleFactory, ChapterFactory, ContentBlockFactory +from books.management.commands.migrate_objective_snapshots import migrate_snapshots +from books.management.commands.migrate_objectives_to_content import migrate_objectives_to_content +from books.models import Snapshot, ChapterSnapshot +from books.tests.queries import MODULE_QUERY, SNAPSHOT_MODULE_QUERY, CREATE_SNAPSHOT_MUTATION, APPLY_SNAPSHOT_MUTATION, \ + MODULE_SNAPSHOTS_QUERY, SHARE_SNAPSHOT_MUTATION, UPDATE_SNAPSHOT_MUTATION, DELETE_SNAPSHOT_MUTATION +from core.tests.base_test import SkillboxTestCase +from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory +from users.factories import SchoolClassFactory +from users.models import User, SchoolClass + + +class TestObjectivesMigration(SkillboxTestCase): + @property + def graphene_client(self): + return self.get_client() + + def setUp(self): + self.createDefault() + self.client = self.get_client() + # teacher will create snapshot + self.slug = 'some-module' + + self.module = ModuleFactory(slug=self.slug) + self.skillbox_class = SchoolClass.objects.get(name='skillbox') + self.teacher2 = User.objects.get(username='teacher2') + self.second_class_name = 'second_class' + self.second_class = SchoolClass.objects.get(name=self.second_class_name) + self.admin = User.objects.get(username='admin') + + # we make a snapshot S of the module M + # snapshot S looks like module M for school class X + # module M has a chapter + self.chapter = ChapterFactory(parent=self.module, slug='some-chapter', owner=self.admin) + ChapterFactory(parent=self.module, slug='some-other-chapter', owner=self.admin) + + objective_group = ObjectiveGroupFactory(module=self.module, title='Gesellschaft') + second_objective_group = ObjectiveGroupFactory(module=self.module, title='Sprache & Kommunikation') + + self.visible_objective = ObjectiveFactory(text='visible-objective', group=objective_group) + self.hidden_objective = ObjectiveFactory(text='hidden-objective', group=objective_group) + self.custom_objective = ObjectiveFactory(text='custom-objective', group=objective_group, owner=self.teacher) + self.custom_hidden_objective = ObjectiveFactory(text='custom-hidden-objective', group=objective_group, + owner=self.teacher) + + self.visible_objective = ObjectiveFactory(text='objective1', group=second_objective_group) + + self.hidden_objective.hidden_for.add(self.skillbox_class) + self.hidden_objective.save() + + self.custom_objective.visible_for.add(self.skillbox_class) + self.custom_objective.save() + + second_objective_group.hidden_for.add(self.skillbox_class) + second_objective_group.save() + + self.custom_hidden_objective.visible_for.remove(self.skillbox_class) + + migrate_objectives_to_content() + + def test_objectives_migration_hidden_default_content(self): + result = self.client.execute(MODULE_QUERY, variables={ + 'slug': self.module.slug + }) + module = result.data['module'] + chapter1 = module['chapters'][0] + default_content, _, _ = chapter1['contentBlocks'] + + # default content block (Verlagsinhalte) exists but is hidden (since one objective is hidden for this class) + self.assertEqual(default_content['title'], 'Gesellschaft') + self.assertEqual(default_content['originalCreator'], None) + self.assertEqual(default_content['contents'][0]['value']['text'], + '
    • visible-objective
    • hidden-objective
    ') + self.assertEqual(default_content['hiddenFor'], [{'name': 'skillbox'}]) + + def test_objectives_migration_hidden_default_content_creates_custom_content(self): + result = self.client.execute(MODULE_QUERY, variables={ + 'slug': self.module.slug + }) + module = result.data['module'] + chapter1 = module['chapters'][0] + _, custom, _ = chapter1['contentBlocks'] + + # default content block (Verlagsinhalte) exists but is hidden (since one objective is hidden for this class) + self.assertEqual(custom['title'], 'Gesellschaft') + self.assertTrue(custom['originalCreator'] is not None) + self.assertEqual(custom['hiddenFor'], []) + self.assertEqual(custom['visibleFor'], [{'name': 'skillbox'}]) + self.assertEqual(custom['contents'][0]['value']['text'], + '
    • visible-objective
    • custom-objective
    ') + + def test_objectives_migration_hidden_default_content_creates_hidden_content_block(self): + result = self.client.execute(MODULE_QUERY, variables={ + 'slug': self.module.slug + }) + module = result.data['module'] + chapter1 = module['chapters'][0] + _, _, hidden_custom = chapter1['contentBlocks'] + + # default content block (Verlagsinhalte) exists but is hidden (since one objective is hidden for this class) + self.assertEqual(hidden_custom['title'], 'Sprache & Kommunikation') + self.assertTrue(hidden_custom['originalCreator'] is None) + self.assertEqual(hidden_custom['hiddenFor'], []) + self.assertEqual(hidden_custom['visibleFor'], []) + self.assertEqual(hidden_custom['contents'][0]['value']['text'], '
    • objective1
    ') diff --git a/server/books/tests/test_snapshots_migration.py b/server/books/tests/test_snapshots_migration.py new file mode 100644 index 00000000..492920a0 --- /dev/null +++ b/server/books/tests/test_snapshots_migration.py @@ -0,0 +1,195 @@ +from django.test import RequestFactory +from graphene.test import Client +from graphql_relay import to_global_id, from_global_id + +from api.schema import schema +from api.utils import get_object +from books.factories import ModuleFactory, ChapterFactory, ContentBlockFactory +from books.management.commands.migrate_objective_snapshots import migrate_snapshots +from books.management.commands.migrate_objectives_to_content import migrate_objectives_to_content +from books.models import Snapshot, ChapterSnapshot, ContentBlock +from books.tests.queries import MODULE_QUERY, SNAPSHOT_MODULE_QUERY, CREATE_SNAPSHOT_MUTATION, APPLY_SNAPSHOT_MUTATION, \ + MODULE_SNAPSHOTS_QUERY, SHARE_SNAPSHOT_MUTATION, UPDATE_SNAPSHOT_MUTATION, DELETE_SNAPSHOT_MUTATION +from core.tests.base_test import SkillboxTestCase +from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory +from users.factories import SchoolClassFactory +from users.models import User, SchoolClass + + +class TestSnapshotMigration(SkillboxTestCase): + @property + def graphene_client(self): + return self.get_client() + + def setUp(self): + self.createDefault() + self.client = self.get_client() + # teacher will create snapshot + self.slug = 'some-module' + + self.module = ModuleFactory(slug=self.slug) + self.skillbox_class = SchoolClass.objects.get(name='skillbox') + self.teacher2 = User.objects.get(username='teacher2') + self.second_class_name = 'second_class' + self.second_class = SchoolClass.objects.get(name=self.second_class_name) + self.admin = User.objects.get(username='admin') + + # we make a snapshot S of the module M + # snapshot S looks like module M for school class X + # module M has a chapter + self.chapter = ChapterFactory(parent=self.module, slug='some-chapter', owner=self.admin) + ChapterFactory(parent=self.module, slug='some-other-chapter', owner=self.admin) + + objective_group = ObjectiveGroupFactory(module=self.module, title='Gesellschaft') + second_objective_group = ObjectiveGroupFactory(module=self.module, title='Sprache & Kommunikation') + + self.visible_objective = ObjectiveFactory(text='visible-objective', group=objective_group) + self.visible_objective_2 = ObjectiveFactory(text='hidden-objective', group=objective_group) + self.custom_objective = ObjectiveFactory(text='custom-objective', group=objective_group, owner=self.teacher) + self.custom_hidden_objective = ObjectiveFactory(text='custom-hidden-objective', group=objective_group, + owner=self.teacher) + + self.visible_objective = ObjectiveFactory(text='objective1', group=second_objective_group) + + self.custom_objective.visible_for.add(self.skillbox_class) + self.custom_objective.save() + + second_objective_group.hidden_for.add(self.skillbox_class) + second_objective_group.save() + + self.custom_hidden_objective.visible_for.remove(self.skillbox_class) + + self.snapshot1 = Snapshot.objects.create_snapshot(self.module, self.skillbox_class, self.teacher) + + migrate_objectives_to_content() + + migrate_snapshots() + + # Change visibility of objectives resp. content blocks, hide all + + for content_block in ContentBlock.objects.all().descendant_of(self.chapter): + if content_block.owner is None: + content_block.hidden_for.add(self.skillbox_class) + else: + content_block.visible_for.remove(self.skillbox_class) + content_block.save() + + def test_snapshot_migration_dfault_content_pre_apply(self): + result = self.client.execute(MODULE_QUERY, variables={ + 'slug': self.module.slug + }) + module = result.data['module'] + chapter1 = module['chapters'][0] + default_content, _, _ = chapter1['contentBlocks'] + + # default content block (Verlagsinhalte) exists but is hidden (since one objective is hidden for this class) + self.assertEqual(default_content['title'], 'Gesellschaft') + self.assertEqual(default_content['originalCreator'], None) + self.assertEqual(default_content['contents'][0]['value']['text'], + '
    • visible-objective
    • hidden-objective
    ') + self.assertEqual(default_content['hiddenFor'], [{'name': 'skillbox'}]) + + def test_snapshot_migration_hidden_default_content_creates_custom_content_pre_apply(self): + result = self.client.execute(MODULE_QUERY, variables={ + 'slug': self.module.slug + }) + module = result.data['module'] + chapter1 = module['chapters'][0] + _, custom, _ = chapter1['contentBlocks'] + + self.assertEqual(custom['title'], 'Gesellschaft') + self.assertTrue(custom['originalCreator'] is not None) + self.assertEqual(custom['hiddenFor'], []) + self.assertEqual(custom['visibleFor'], []) + self.assertEqual(custom['contents'][0]['value']['text'], + '
    • visible-objective
    • hidden-objective
    • custom-objective
    ') + + def test_snapshot_migration_hidden_content_block_pre_apply(self): + result = self.client.execute(MODULE_QUERY, variables={ + 'slug': self.module.slug + }) + module = result.data['module'] + chapter1 = module['chapters'][0] + _, _, hidden_custom = chapter1['contentBlocks'] + + # default content block (Verlagsinhalte) exists but is hidden (since one objective is hidden for this class) + self.assertEqual(hidden_custom['title'], 'Sprache & Kommunikation') + self.assertTrue(hidden_custom['originalCreator'] is None) + self.assertEqual(hidden_custom['hiddenFor'], [{'name': 'skillbox'}]) + self.assertEqual(hidden_custom['visibleFor'], []) + self.assertEqual(hidden_custom['contents'][0]['value']['text'], '
    • objective1
    ') + + def test_snapshot_migration_default_apply_snapshot(self): + # apply snapshot + self.snapshot1.apply(self.teacher, self.school_class) + result = self.client.execute(MODULE_QUERY, variables={ + 'slug': self.module.slug + }) + module = result.data['module'] + chapter1 = module['chapters'][0] + default_content, _, _, _ = chapter1['contentBlocks'] + + # default content block (Verlagsinhalte) exists but is hidden (since one objective is hidden for this class) + self.assertEqual(default_content['title'], 'Gesellschaft') + self.assertFalse(default_content['userCreated']) + self.assertEqual(default_content['originalCreator'], None) + self.assertEqual(default_content['contents'][0]['value']['text'], + '
    • visible-objective
    • hidden-objective
    ') + self.assertEqual(default_content['hiddenFor'], [{'name': 'skillbox'}]) + + def test_snapshot_migration_hidden_custom_content_apply_snapshot(self): + # custom content from bevore the snapshot must be hidden (visible for nobody) + self.snapshot1.apply(self.teacher, self.school_class) + + result = self.client.execute(MODULE_QUERY, variables={ + 'slug': self.module.slug + }) + module = result.data['module'] + chapter1 = module['chapters'][0] + _, custom, _, _ = chapter1['contentBlocks'] + + self.assertEqual(custom['title'], 'Gesellschaft') + self.assertTrue(custom['userCreated']) + self.assertTrue(custom['originalCreator'] is not None) + self.assertEqual(custom['hiddenFor'], []) + self.assertEqual(custom['visibleFor'], []) + self.assertEqual(custom['contents'][0]['value']['text'], + '
    • visible-objective
    • hidden-objective
    • custom-objective
    ') + + def test_snapshot_migration_hidden_content_block_apply_snapshot_2(self): + # custom content from bevore the snapshot must be hidden (visible for nobody) + self.snapshot1.apply(self.teacher, self.school_class) + + result = self.client.execute(MODULE_QUERY, variables={ + 'slug': self.module.slug + }) + module = result.data['module'] + chapter1 = module['chapters'][0] + _, _, hidden_custom, _ = chapter1['contentBlocks'] + + # default content block (Verlagsinhalte) exists but is hidden (since one objective is hidden for this class) + self.assertEqual(hidden_custom['title'], 'Sprache & Kommunikation') + self.assertFalse(hidden_custom['userCreated']) + self.assertTrue(hidden_custom['originalCreator'] is None) + self.assertEqual(hidden_custom['hiddenFor'], [{'name': 'skillbox'}]) + self.assertEqual(hidden_custom['visibleFor'], []) + self.assertEqual(hidden_custom['contents'][0]['value']['text'], '
    • objective1
    ') + + def test_snapshot_migration_visible_content_snapshot_new_content(self): + # the applicaiton of a snapshot hides old custom content, and creates new custom content for the visible stuff + self.snapshot1.apply(self.teacher, self.school_class) + + result = self.client.execute(MODULE_QUERY, variables={ + 'slug': self.module.slug + }) + module = result.data['module'] + chapter1 = module['chapters'][0] + _, _, _, new_content_block = chapter1['contentBlocks'] + + self.assertEqual(new_content_block['title'], 'Gesellschaft') + self.assertTrue(new_content_block['userCreated']) + self.assertTrue(new_content_block['originalCreator'] is None) + self.assertEqual(new_content_block['hiddenFor'], []) + self.assertEqual(new_content_block['visibleFor'], [{'name': 'skillbox'}]) + self.assertEqual(new_content_block['contents'][0]['value']['text'], + '
    • visible-objective
    • hidden-objective
    • custom-objective
    ') From fa68f68a6ea87c977eed5d762d7982b5f8380e6c Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Wed, 7 Feb 2024 11:15:34 +0100 Subject: [PATCH 18/21] Resolve comments from pull request --- .../commands/migrate_objective_snapshots.py | 57 ++++++------------- .../commands/migrate_objectives_to_content.py | 26 ++++----- 2 files changed, 31 insertions(+), 52 deletions(-) diff --git a/server/books/management/commands/migrate_objective_snapshots.py b/server/books/management/commands/migrate_objective_snapshots.py index 9049466c..5f053bd5 100644 --- a/server/books/management/commands/migrate_objective_snapshots.py +++ b/server/books/management/commands/migrate_objective_snapshots.py @@ -3,9 +3,9 @@ from logging import getLogger from django.core.management import BaseCommand -from books.management.commands.migrate_objectives_to_content import create_chapter_from_objective_group, \ - create_content_block_from_objective, create_text_in_content_block, create_content_block_snapshot_from_objective, \ - create_chapter_snapshot_from_objective_group, create_content_block_contents +from books.management.commands.migrate_objectives_to_content import create_text_in_content_block, \ + create_content_block_snapshot_from_objective, \ + create_content_block_contents from books.models import Chapter, ObjectiveGroupSnapshot, ContentBlockSnapshot, Snapshot, ChapterSnapshot from books.models import ContentBlock from books.models import Module @@ -103,15 +103,9 @@ def migrate_snapshots(): if hidden_default_objectives or visible_custom_objectives: print(header + f"Case 3 - {info} create custom content blocks") case3_count += 1 - # Verlags Lernziele - default_objectives = Objective.objects.filter(group=objective_group, - group__module=module, - owner__isnull=True, - objectivesnapshot__isnull=True) - visible_default_objectives = [objective for objective in default_objectives if - objective.id not in hidden_default_objectives.values_list("id", - flat=True)] + # Verlags Lernziele + visible_default_objectives = get_visible_default_objectives(objective_group, module, snapshot) # Benutzerdefinierte Lernziele visible_custom_objectives = list(snapshot.custom_objectives.filter(hidden=False)) @@ -145,13 +139,12 @@ def migrate_snapshots(): chapter = module.get_first_child() if "Lernziele" not in chapter.title: raise Exception(f"Chapter does not contain 'Lernziele' first title is {chapter.title}") - # + # Owner des custom blocks festlegen custom_content_block_snapshot = create_content_block_snapshot_from_objective( objective_group, chapter, snapshot, - owner=snapshot.creator, - prefix="SNAP ") - # + owner=snapshot.creator) + # Hide default content block for this objective group, since custom content block is created default_content_block = get_default_content_block(objective_group_snapshot, module) if default_content_block: @@ -204,30 +197,16 @@ def get_default_content_block(objective_group_snapshot, module): raise Exception("Content block does not exist ") -def get_visible_default_objectives(): - # TODO: Objective Group geht so nicth - default_objectives = Objective.objects.filter(group=ObjectiveGroup, group__module=module, owner__isnull=True) - visible_objectives = list(default_objectives) - visible_objectives_ids = [objective.id for objective in visible_objectives] - - # Verlags Lernziele - for hidden_objective in snapshot.hidden_objectives.all(): - visible_objectives = [objective for objective in visible_objectives if - hidden_objective.id not in visible_objectives_ids] - return visible_objectives - - -def get_content_block_by_objectives(objectives, module, user_created=False, user=None): - contents = create_content_block_contents(objectives) - - content_block_qs = ContentBlock.objects.filter( - owner=user, - user_created=user_created, - contents=contents) - if content_block_qs.exists(): - return content_block_qs.first() - else: - raise Exception("Content block does not exist ") +def get_visible_default_objectives(objective_group, module, snapshot): + default_objectives = Objective.objects.filter(group=objective_group, + group__module=module, + owner__isnull=True, + objectivesnapshot__isnull=True) + hidden_default_objectives = snapshot.hidden_objectives.filter(group=objective_group) + visible_default_objectives = [objective for objective in default_objectives if + objective.id not in hidden_default_objectives.values_list("id", + flat=True)] + return visible_default_objectives def analyze(): diff --git a/server/books/management/commands/migrate_objectives_to_content.py b/server/books/management/commands/migrate_objectives_to_content.py index 0b828fd1..9f5df387 100644 --- a/server/books/management/commands/migrate_objectives_to_content.py +++ b/server/books/management/commands/migrate_objectives_to_content.py @@ -59,24 +59,25 @@ def migrate_objectives_to_content(): print(f"Owner: {owner}") print(f" Objectives: ") - visible_default_objectives_by_class = remove_hidden_objectives(default_objectives, owner) + visible_default_objectives_by_class = filter_visible_objectives_by_class(default_objectives, owner) - for school_class in visible_default_objectives_by_class.keys(): + for school_class, default_objectives_for_class in visible_default_objectives_by_class.items(): custom_content_block = None print(f" School class: {school_class}") # merge "Verlagsinhalte" and "benutzerdefinierte Inhalte" visible_owner_objectives = [objective for objective in owner_objectives if not objective.is_hidden_for_class(school_class)] - merged_objectives = visible_default_objectives_by_class[school_class] + visible_owner_objectives + merged_objectives = default_objectives_for_class + visible_owner_objectives merged_objectives_ids = tuple(objective.id for objective in merged_objectives) + is_default_content = merged_objectives_ids == default_objectives_ids - if merged_objectives_ids == default_objectives_ids: + if is_default_content: print(f" Objective: Reuse default content block") # custom_content_block = default_content_block # Create content block if that set of objectives has not been created yet - if merged_objectives_ids not in contentblocks_by_merged_objectives_ids and merged_objectives_ids != default_objectives_ids: + if merged_objectives_ids not in contentblocks_by_merged_objectives_ids and not is_default_content: for objective in merged_objectives: print(f" Objective: {objective} {objective.owner}") @@ -84,13 +85,13 @@ def migrate_objectives_to_content(): custom_content_block = create_content_block_from_objective(objective_group, chapter, owner=owner, - prefix=f"CUSTOM {owner.username} ") + ) contentblocks_by_merged_objectives_ids[ merged_objectives_ids] = custom_content_block create_text_in_content_block(merged_objectives, custom_content_block) created_content_blocks += 1 else: - if merged_objectives_ids != default_objectives_ids: + if not is_default_content: print(f" Objective: Reuse content block") custom_content_block = contentblocks_by_merged_objectives_ids[ merged_objectives_ids] @@ -134,7 +135,7 @@ def create_default_content(objective_group, chapter): default_objectives = list( objective_group.objectives.filter(owner__isnull=True, objectivesnapshot__isnull=True).order_by('id')) - default_content_block = create_content_block_from_objective(objective_group, chapter, prefix="XXX YYY ") + default_content_block = create_content_block_from_objective(objective_group, chapter) create_text_in_content_block(default_objectives, default_content_block) for objective in default_objectives: @@ -143,7 +144,7 @@ def create_default_content(objective_group, chapter): return default_content_block -def remove_hidden_objectives(objectives, user): +def filter_visible_objectives_by_class(objectives, user): school_classes = user.school_classes.all() visible_objectives = {} for school_class in school_classes: @@ -155,8 +156,7 @@ def remove_hidden_objectives(objectives, user): def get_objectives_by_owner(objective_group, exclude_snapshots=True): - custom_objectives = objective_group.objectives.filter(owner__isnull=False).exclude( - objectivesnapshot__isnull=False).order_by('order') + custom_objectives = objective_group.objectives.filter(owner__isnull=False, objectivesnapshot__isnull=True).order_by('order') custom_objectives_by_owner = {} for objective in custom_objectives: @@ -202,7 +202,7 @@ def create_chapter_snapshot_from_objective_group(module, snapshot, prefix="XXX " return chapter -def create_content_block_from_objective(objective_group, chapter, owner=None, prefix="XXX "): +def create_content_block_from_objective(objective_group, chapter, owner=None): content_block = ContentBlock( title=f"{objective_group.get_title_display()}", type="normal", @@ -214,7 +214,7 @@ def create_content_block_from_objective(objective_group, chapter, owner=None, pr return content_block -def create_content_block_snapshot_from_objective(objective_group, chapter, snapshot, owner=None, prefix="XXX "): +def create_content_block_snapshot_from_objective(objective_group, chapter, snapshot, owner=None): content_block_snapshot = ContentBlockSnapshot( title=f"{objective_group.get_title_display()}", type="normal", From ce7ab55cbabde75e050de0e7974eac30fe70dcc9 Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Wed, 7 Feb 2024 12:04:22 +0100 Subject: [PATCH 19/21] Fix visiblity of old objectives --- client/src/components/modules/Module.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/components/modules/Module.vue b/client/src/components/modules/Module.vue index aeef77ed..e44285e1 100644 --- a/client/src/components/modules/Module.vue +++ b/client/src/components/modules/Module.vue @@ -67,7 +67,7 @@
    + Date: Wed, 7 Feb 2024 14:58:06 +0100 Subject: [PATCH 20/21] Fix problem with duplicate usernames --- .../commands/migrate_objective_snapshots.py | 2 +- .../management/commands/reset_all_passwords.py | 14 ++++++++++---- server/test_objectives_migrations.sh | 6 +++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/server/books/management/commands/migrate_objective_snapshots.py b/server/books/management/commands/migrate_objective_snapshots.py index 5f053bd5..ad3e3f97 100644 --- a/server/books/management/commands/migrate_objective_snapshots.py +++ b/server/books/management/commands/migrate_objective_snapshots.py @@ -149,7 +149,7 @@ def migrate_snapshots(): default_content_block = get_default_content_block(objective_group_snapshot, module) if default_content_block: print(f"Default content block: {default_content_block.title}") - snapshot.hidden_content_blocks.add(default_content_block.id) + snapshot.hidden_content_blocks.add(default_content_block.id) if list(visible_objectives_by_ids.values()): objectives = list(visible_objectives_by_ids.values())[0] diff --git a/server/core/management/commands/reset_all_passwords.py b/server/core/management/commands/reset_all_passwords.py index 26557519..f198fdc4 100644 --- a/server/core/management/commands/reset_all_passwords.py +++ b/server/core/management/commands/reset_all_passwords.py @@ -12,17 +12,23 @@ class Command(BaseCommand): self.stdout.write("If so, type \"YES\"") result = input() + user_model = get_user_model() if result == 'YES': - users = get_user_model().objects.all() + users = user_model.objects.all().order_by('email') - with ThreadPoolExecutor(max_workers=10) as executor: + with ThreadPoolExecutor(max_workers=5) as executor: executor.map(process_user, users) def process_user(usr): - # replace domain with skillbox.ch - usr.email = usr.email.split('@')[0] + '@skillbox.ch' + # replace domain with id.skillbox.ch,to ensure unique emails + usr.email = usr.email.split('@')[0] + f'@skillbox.ch' + user_model = get_user_model() + + if user_model.objects.filter(email=usr.email).exists(): + usr.email = usr.email.split('@')[0] + f'@{usr.id}.skillbox.ch' + usr.username = usr.email print(usr.email) diff --git a/server/test_objectives_migrations.sh b/server/test_objectives_migrations.sh index a206a701..6a24a1d8 100755 --- a/server/test_objectives_migrations.sh +++ b/server/test_objectives_migrations.sh @@ -4,9 +4,9 @@ python manage.py reset_all_passwords pg_dump -Fc --no-acl -h localhost -U skillbox skillbox > latest-anonymized.dump python manage.py migrate_objectives_to_content pg_dump -Fc --no-acl -h localhost -U skillbox skillbox > latest-migrated-objectives.dump -#python manage.py migrate_objective_snapshots -#pg_dump -Fc --no-acl -h localhost -U skillbox skillbox > latest-migrated-objectives-and-snapshots.dump +python manage.py migrate_objective_snapshots +pg_dump -Fc --no-acl -h localhost -U skillbox skillbox > latest-migrated-objectives-and-snapshots.dump -# Use this command to restore the database from the dump: +#Use this command to restore the database from the dump: #pg_restore --verbose --clean --no-acl --no-owner -h localhost -U skillbox -d skillbox latest-migrated-objectives.dump From 95cf48717c25f166a02b37de5c4508b2d6978d4d Mon Sep 17 00:00:00 2001 From: Lorenz Padberg Date: Tue, 13 Feb 2024 11:15:54 +0100 Subject: [PATCH 21/21] Move show-objectives to computed properties --- client/src/components/modules/Module.vue | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/client/src/components/modules/Module.vue b/client/src/components/modules/Module.vue index e44285e1..ddf87536 100644 --- a/client/src/components/modules/Module.vue +++ b/client/src/components/modules/Module.vue @@ -67,7 +67,7 @@
    -