Source code for inginious.frontend.task_problems

# -*- coding: utf-8 -*-
#
# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
# more information about the licensing of this file.

""" Displayable problems """

from abc import ABCMeta, abstractmethod
from random import Random

from flask import render_template

from inginious.common.tasks_problems import Problem, CodeProblem, CodeSingleLineProblem, \
    MatchProblem, MultipleChoiceProblem, FileProblem,  _inspect_problem_types


from inginious.frontend.parsable_text import ParsableText


[docs] def inspect_displayable_problem_types(name: str) -> dict: """ Get the mapping of DisplayableProblem types available by inspecting a given module. :param name: The name of the module to inspect. :return: The mapping of problem name and problem class. """ raw = _inspect_problem_types(name, DisplayableProblem) return {pbl_name: pbl_cls for pbl_name, pbl_cls in raw.items() if pbl_name is not None}
[docs] def get_default_displayable_problem_types() -> dict: """ Get the mapping of default DisplayableProblem types available by inspecting the current module. :return: The mapping of problem name and problem class. """ return inspect_displayable_problem_types(__name__)
[docs] class DisplayableProblem(Problem, metaclass=ABCMeta): """Basic problem """
[docs] @classmethod @abstractmethod def get_type_name(cls, language): pass
[docs] def adapt_input_for_backend(self, input_data): """ Adapt the input from web.py for the inginious.backend """ return input_data
[docs] @abstractmethod def show_input(self, language, seed): """ get the html for this problem """ pass
[docs] @classmethod @abstractmethod def show_editbox(cls, key, language): """ get the edit box html for this problem """ pass
[docs] @classmethod @abstractmethod def show_editbox_templates(cls, key, language): return ""
[docs] class DisplayableCodeProblem(CodeProblem, DisplayableProblem): """ A basic class to display all BasicCodeProblem derivatives """ def __init__(self, problemid, content, translations, taskfs): super(DisplayableCodeProblem, self).__init__(problemid, content, translations, taskfs) self._first_line = content.get("offset", 1)
[docs] @classmethod def get_type_name(cls, language): return _("code")
[docs] def adapt_input_for_backend(self, input_data): return input_data
[docs] def show_input(self, language, seed): """ Show BasicCodeProblem and derivatives """ header = ParsableText(self.gettext(language,self._header), "rst") return render_template("tasks/code.html", inputId=self.get_id(), header=header, lines=8, first_line=self._first_line, maxChars=0, language=self._language, optional=self._optional, default=self._default)
[docs] @classmethod def show_editbox(cls, key, language): return render_template("course_admin/subproblems/code.html", key=key, multiline=True)
[docs] @classmethod def show_editbox_templates(cls, key, language): return ""
[docs] class DisplayableCodeSingleLineProblem(CodeSingleLineProblem, DisplayableProblem): """ A displayable single code line problem """ def __init__(self, problemid, content, translations, taskfs): super(DisplayableCodeSingleLineProblem, self).__init__(problemid, content, translations, taskfs)
[docs] def adapt_input_for_backend(self, input_data): return input_data
[docs] @classmethod def get_type_name(cls, language): return _("single-line code")
[docs] def show_input(self, language, seed): """ Show InputBox """ header = ParsableText(self.gettext(language, self._header), "rst") return render_template("tasks/single_line_code.html", inputId=self.get_id(), header=header, type="text", maxChars=0, optional=self._optional, default=self._default)
[docs] @classmethod def show_editbox(cls, key, language): return render_template("course_admin/subproblems/code.html", key=key, multiline=False)
[docs] @classmethod def show_editbox_templates(cls, key, language): return ""
[docs] class DisplayableFileProblem(FileProblem, DisplayableProblem): """ A displayable code problem """ def __init__(self, problemid, content, translations, taskfs): super(DisplayableFileProblem, self).__init__(problemid, content, translations, taskfs)
[docs] @classmethod def get_type_name(cls, language): return _("file upload")
[docs] def adapt_input_for_backend(self, input_data): try: input_data[self.get_id()] = {"filename": input_data[self.get_id()].filename, "value": input_data[self.get_id()].read()} except: input_data[self.get_id()] = {} return input_data
[docs] @classmethod def show_editbox(cls, key, language): return render_template("course_admin/subproblems/file.html", key=key)
[docs] def show_input(self, language, seed): """ Show FileBox """ header = ParsableText(self.gettext(language, self._header), "rst") return render_template("tasks/file.html", inputId=self.get_id(), header=header, max_size=self._max_size, allowed_exts=self._allowed_exts)
[docs] @classmethod def show_editbox_templates(cls, key, language): return ""
[docs] class DisplayableMultipleChoiceProblem(MultipleChoiceProblem, DisplayableProblem): """ A displayable multiple choice problem """ def __init__(self, problemid, content, translations, taskfs): super(DisplayableMultipleChoiceProblem, self).__init__(problemid, content, translations, taskfs)
[docs] @classmethod def get_type_name(cls, language): return _("multiple choice")
def _filter_choices(self, pre_shuffled_choices : list[dict]): choices = [] limit = self._limit or len(self._choices) valid_choices = [entry for entry in pre_shuffled_choices if entry['valid']] invalid_choices = [entry for entry in pre_shuffled_choices if not entry['valid']] if self._multiple: # take the valid choices and complete with invalid choices up to the limit limit = max(limit - len(valid_choices), 0) choices += valid_choices + invalid_choices[:limit] else: # Keep at least one valid entry choices += invalid_choices[:limit-1] limit = max(limit - len(choices), 0) choices += valid_choices[:limit] return choices
[docs] def show_input(self, language, seed): """ Show multiple choice problems """ rand = Random("{}#{}#{}".format(self.get_id(), language, seed)) # Ensure that the choices are random # we *do* need to copy the choices here random_order_choices = list(self._choices) if not self._unshuffle: rand.shuffle(random_order_choices) choices = self._filter_choices(random_order_choices) if not self._unshuffle: rand.shuffle(choices) else: choices = sorted(choices, key=lambda k: k['index']) header = ParsableText(self.gettext(language, self._header), "rst") return render_template( "tasks/multiple_choice.html", pid=self.get_id(), header=header, checkbox=self._multiple, choices=choices, func=lambda text: ParsableText(self.gettext(language, text) if text else "", "rst") )
[docs] @classmethod def show_editbox(cls, key, language): return render_template("course_admin/subproblems/multiple_choice.html", key=key)
[docs] @classmethod def show_editbox_templates(cls, key, language): return render_template("course_admin/subproblems/multiple_choice_templates.html", key=key)
[docs] class DisplayableMatchProblem(MatchProblem, DisplayableProblem): """ A displayable match problem """ def __init__(self, problemid, content, translations, taskfs): super(DisplayableMatchProblem, self).__init__(problemid, content, translations, taskfs)
[docs] @classmethod def get_type_name(cls, language): return _("match")
[docs] def show_input(self, language, seed): """ Show MatchProblem """ header = ParsableText(self.gettext(language, self._header), "rst") return render_template("tasks/match.html", inputId=self.get_id(), header=header)
[docs] @classmethod def show_editbox(cls, key, language): return render_template("course_admin/subproblems/match.html", key=key)
[docs] @classmethod def show_editbox_templates(cls, key, language): return ""