"""Custom loader for loading structure of programming challenges."""

import os
from django.db import transaction
from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError
from utils.TranslatableModelLoader import TranslatableModelLoader
from topics.models import ProgrammingChallengeLanguage, ProgrammingChallengeDifficulty


class ProgrammingChallengesStructureLoader(TranslatableModelLoader):
    """Custom loader for loading structure of programming challenges."""

    @transaction.atomic
    def load(self):
        """Load the content for structure of programming challenges.

        Raises:
            MissingRequiredFieldError: when no object can be found with the matching
                attribute.
        """
        structure = self.load_yaml_file(self.structure_file_path)

        prog_languages = structure.get("languages", None)
        difficulty_levels = structure.get("difficulties", None)
        if None in [prog_languages, difficulty_levels]:
            raise MissingRequiredFieldError(
                self.structure_file_path,
                ["lanugages", "difficulties"],
                "Programming Challenge Structure"
            )

        # Add "-languages" to the structure filename
        prog_languages_translation_filename = "{}-languages.yaml".format(
            os.path.splitext(self.structure_filename)[0]
        )
        prog_languages_translations = self.get_yaml_translations(
            prog_languages_translation_filename,
            required_slugs=prog_languages.keys(),
            required_fields=["name"]
        )

        for (prog_language, prog_language_data) in prog_languages.items():

            if prog_language_data is None:
                raise MissingRequiredFieldError(
                    self.structure_file_path,
                    ["number"],
                    "Programming Challenge Language"
                )

            # Check for required fields
            prog_language_number = prog_language_data.get("number", None)
            if prog_language_number is None:
                raise MissingRequiredFieldError(
                    self.structure_file_path,
                    ["number"],
                    "Programming Challenge Language"
                )

            # Check if icon is given
            if "icon" in prog_language_data:
                prog_language_icon = prog_language_data["icon"]
            else:
                prog_language_icon = None

            new_prog_language = ProgrammingChallengeLanguage(
                slug=prog_language,
                number=prog_language_number,
                icon=prog_language_icon
            )

            translations = prog_languages_translations.get(prog_language, dict())
            self.populate_translations(new_prog_language, translations)
            self.mark_translation_availability(new_prog_language, required_fields=["name"])
            new_prog_language.save()

            self.log("Added programming language: {}".format(new_prog_language.__str__()))

        # Add "-languages" to the structure filename
        difficulties_translation_filename = "{}-difficulties.yaml".format(
            os.path.splitext(self.structure_filename)[0]
        )
        difficulties_translations = self.get_yaml_translations(
            difficulties_translation_filename,
            required_slugs=difficulty_levels,
            required_fields=["name"],
        )

        for difficulty in difficulty_levels:

            new_difficulty = ProgrammingChallengeDifficulty(
                level=difficulty,
            )

            translations = difficulties_translations.get(difficulty, dict())
            self.populate_translations(new_difficulty, translations)
            self.mark_translation_availability(new_difficulty, required_fields=["name"])
            new_difficulty.save()

            self.log("Added programming difficulty level: {}".format(new_difficulty.__str__()))

        self.log("")

csunplugged/topics/management/commands/_ProgrammingChallengesStructureLoader.py
"""Custom loader for loading structure of programming challenges."""

import os
from django.db import transaction
from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError
from utils.TranslatableModelLoader import TranslatableModelLoader
from topics.models import ProgrammingChallengeLanguage, ProgrammingChallengeDifficulty


class ProgrammingChallengesStructureLoader(TranslatableModelLoader):
    """Custom loader for loading structure of programming challenges."""

    @transaction.atomic
    def load(self):
        """Load the content for structure of programming challenges.

        Raises:
            MissingRequiredFieldError: when no object can be found with the matching
                attribute.
        """
        structure = self.load_yaml_file(self.structure_file_path)

        prog_languages = structure.get("languages", None)
        difficulty_levels = structure.get("difficulties", None)
        if None in [prog_languages, difficulty_levels]:
            raise MissingRequiredFieldError(
                self.structure_file_path,
                ["lanugages", "difficulties"],
                "Programming Challenge Structure"
            )

        # Add "-languages" to the structure filename
        prog_languages_translation_filename = "{}-languages.yaml".format(
            os.path.splitext(self.structure_filename)[0]
        )
        prog_languages_translations = self.get_yaml_translations(
            prog_languages_translation_filename,
            required_slugs=prog_languages.keys(),
            required_fields=["name"]
        )

        for (prog_language, prog_language_data) in prog_languages.items():

            if prog_language_data is None:
                raise MissingRequiredFieldError(
                    self.structure_file_path,
                    ["number"],
                    "Programming Challenge Language"
                )

            # Check for required fields
            prog_language_number = prog_language_data.get("number", None)
            if prog_language_number is None:
                raise MissingRequiredFieldError(
                    self.structure_file_path,
                    ["number"],
                    "Programming Challenge Language"
                )

            # Check if icon is given
            if "icon" in prog_language_data:
                prog_language_icon = prog_language_data["icon"]
            else:
                prog_language_icon = None

            new_prog_language = ProgrammingChallengeLanguage(
                slug=prog_language,
                number=prog_language_number,
                icon=prog_language_icon
            )

            translations = prog_languages_translations.get(prog_language, dict())
            self.populate_translations(new_prog_language, translations)
            self.mark_translation_availability(new_prog_language, required_fields=["name"])
            new_prog_language.save()

            self.log("Added programming language: {}".format(new_prog_language.__str__()))

        # Add "-languages" to the structure filename
        difficulties_translation_filename = "{}-difficulties.yaml".format(
            os.path.splitext(self.structure_filename)[0]
        )
        difficulties_translations = self.get_yaml_translations(
            difficulties_translation_filename,
            required_slugs=difficulty_levels,
            required_fields=["name"],
        )

        for difficulty in difficulty_levels:

            new_difficulty = ProgrammingChallengeDifficulty(
                level=int(difficulty),
            )

            translations = difficulties_translations.get(difficulty, dict())
            self.populate_translations(new_difficulty, translations)
            self.mark_translation_availability(new_difficulty, required_fields=["name"])
            new_difficulty.save()

            self.log("Added programming difficulty level: {}".format(new_difficulty.__str__()))

        self.log("")

csunplugged/topics/management/commands/_ProgrammingChallengesStructureLoader.py
diff --git a/csunplugged/topics/content/en/programming-challenges-structure-difficulties.yaml b/csunplugged/topics/content/en/programming-challenges-structure-difficulties.yaml
index 5cf9b30dc..2e0eb18a5 100644
--- a/csunplugged/topics/content/en/programming-challenges-structure-difficulties.yaml
+++ b/csunplugged/topics/content/en/programming-challenges-structure-difficulties.yaml
@@ -1,8 +1,8 @@
-0:
+"0":
   name: Try it out
-1:
+"1":
   name: Beginner
-2:
+"2":
   name: Growing experience
-3:
+"3":
   name: Ready to expand
diff --git a/csunplugged/topics/content/structure/programming-challenges-structure.yaml b/csunplugged/topics/content/structure/programming-challenges-structure.yaml
index 9c01fe7ae..6a9507299 100644
--- a/csunplugged/topics/content/structure/programming-challenges-structure.yaml
+++ b/csunplugged/topics/content/structure/programming-challenges-structure.yaml
@@ -9,7 +9,7 @@ languages:
         icon: img/python-logo.png
 
 difficulties:
-  - 0
-  - 1
-  - 2
-  - 3
+  - "0"
+  - "1"
+  - "2"
+  - "3"
diff --git a/csunplugged/topics/management/commands/_ProgrammingChallengesStructureLoader.py b/csunplugged/topics/management/commands/_ProgrammingChallengesStructureLoader.py
index 99c6c0f53..7339188b7 100644
--- a/csunplugged/topics/management/commands/_ProgrammingChallengesStructureLoader.py
+++ b/csunplugged/topics/management/commands/_ProgrammingChallengesStructureLoader.py
@@ -89,7 +89,7 @@ def load(self):
         for difficulty in difficulty_levels:
 
             new_difficulty = ProgrammingChallengeDifficulty(
-                level=difficulty,
+                level=int(difficulty),
             )
 
             translations = difficulties_translations.get(difficulty, dict())

Workaround Crowdin bug where integer yaml keys are not preserved
When downloading in-context localisation files, integer keys in YAML files are not preserved. This is only an issue in the file `topics/content/en/programming-challenges-structure-difficulties.yaml`, which uses the difficulty number as the key.

As a work around, we can use the string value of the integer as the key, i.e. `"0"` instead of `0`
