Exemple #1
0
def container_post(request):
    '''
    Proxies the grading result from inside container to A+
    '''
    sid = request.POST.get("sid", None)
    if not sid:
        return HttpResponseForbidden("Missing sid")

    meta = read_and_remove_submission_meta(sid)
    if meta is None:
        return HttpResponseForbidden("Invalid sid")
    #clean_submission_dir(meta["dir"])

    data = {
        "points": int(request.POST.get("points", 0)),
        "max_points": int(request.POST.get("max_points", 1)),
    }
    for key in ["error", "grading_data"]:
        if key in request.POST:
            data[key] = request.POST[key]
    if "error" in data and data["error"].lower() in ("no", "false"):
        del data["error"]

    feedback = request.POST.get("feedback", "")
    # Fetch the corresponding exercise entry from the config.
    lang = meta["lang"]
    (course, exercise) = config.exercise_entry(meta["course_key"],
                                               meta["exercise_key"],
                                               lang=lang)
    if "feedback_template" in exercise:
        # replace the feedback with a rendered feedback template if the exercise is configured to do so
        # it is not feasible to support all of the old feedback template variables that runactions.py
        # used to have since the grading actions are not configured in the exercise YAML file anymore
        required_fields = {'points', 'max_points', 'error', 'out'}
        result = MonitoredDict({
            "points": data["points"],
            "max_points": data["max_points"],
            "out": feedback,
            "error": data.get("error", False),
            "title": exercise.get("title", ""),
        })
        translation.activate(lang)
        feedback = template_to_str(course,
                                   exercise,
                                   None,
                                   exercise["feedback_template"],
                                   result=result)
        if result.accessed.isdisjoint(required_fields):
            alert = template_to_str(
                course, exercise, None,
                "access/feedback_template_did_not_use_result_alert.html")
            feedback = alert + feedback
        # Make unicode results ascii.
        feedback = feedback.encode("ascii", "xmlcharrefreplace")

    data["feedback"] = feedback

    if not post_data(meta["url"], data):
        raise IOError("Failed to deliver results")
    return HttpResponse("Ok")
Exemple #2
0
def generated_exercise_file(request, course_key, exercise_key,
                            exercise_instance, filename):
    '''
    Delivers a generated file of the exercise instance.
    '''
    # Fetch the corresponding exercise entry from the config.
    (course, exercise) = config.exercise_entry(course_key, exercise_key)
    if course is None or exercise is None:
        raise Http404()
    if "generated_files" in exercise:
        import magic
        for gen_file_conf in exercise["generated_files"]:
            if gen_file_conf["file"] == filename:
                if gen_file_conf.get("allow_download", False):
                    file_content = read_generated_exercise_file(
                        course, exercise, exercise_instance, filename)
                    response = HttpResponse(file_content,
                                            content_type=magic.from_buffer(
                                                file_content, mime=True))
                    response[
                        'Content-Disposition'] = 'attachment; filename="{}"'.format(
                            filename)
                    return response
                else:
                    # hide file existence with 404
                    raise Http404()
    raise Http404()
Exemple #3
0
def exercise(request, course_key, exercise_key):
    '''
    Presents the exercise and accepts answers to it.
    '''
    post_url = request.GET.get('post_url', None)
    lang = request.GET.get('lang', None)

    # Fetch the corresponding exercise entry from the config.
    (course, exercise) = config.exercise_entry(course_key,
                                               exercise_key,
                                               lang=lang)
    if course is None or exercise is None:
        raise Http404()

    # Exercise language.
    if not lang:
        if "lang" in course:
            lang = course["lang"]
        else:
            lang = "en"
    translation.activate(lang)

    # Try to call the configured view.
    return import_named(course, exercise['view_type'])(request, course,
                                                       exercise, post_url)
Exemple #4
0
def exercise_model(request, course_key, exercise_key, parameter=None):
    '''
    Presents a model answer for an exercise.
    '''
    (course, exercise) = config.exercise_entry(course_key, exercise_key)
    if course is None or exercise is None:
        raise Http404()
    response = None

    try:
        response = import_named(course, exercise['view_type'] + "Model")(
            request, course, exercise, parameter)
    except ImportError:
        pass

    if 'model_files' in exercise:
        def find_name(paths, name):
            models = [(path,path.split('/')[-1]) for path in paths]
            for path,name in models:
                if name == parameter:
                    return path
            return None
        path = find_name(exercise['model_files'], parameter)
        if path:
            with open(os.path.join(course['dir'], path)) as f:
                content = f.read()
            response = HttpResponse(content, content_type='text/plain')

    if response:
        return response
    else:
        raise Http404()
Exemple #5
0
def exercise_template(request, course_key, exercise_key, parameter=None):
    '''
    Presents the exercise template.
    '''
    (course, exercise) = config.exercise_entry(course_key, exercise_key)
    if course is None or exercise is None:
        raise Http404()
    response = None

    path = None
    if 'template_files' in exercise:

        def find_name(paths, name):
            templates = [(path, path.split('/')[-1]) for path in paths]
            for path, name in templates:
                if name == parameter:
                    return path
            return None

        path = find_name(exercise['template_files'], parameter)
    if path:
        with open(os.path.join(course['dir'], path)) as f:
            content = f.read()
        response = HttpResponse(content, content_type='text/plain')
    else:
        try:
            response = import_named(course, exercise['view_type'] +
                                    "Template")(request, course, exercise,
                                                parameter)
        except ImportError:
            pass
    if response:
        return response
    else:
        raise Http404()
Exemple #6
0
def _get_course_exercise_lang(course_key, exercise_key, lang_code):
    (course, exercise) = config.exercise_entry(course_key, exercise_key, lang=lang_code)
    if course is None or exercise is None:
        raise Http404()
    if not lang_code:
        lang_code = course.get('lang', DEFAULT_LANG)
    translation.activate(lang_code)
    return (course, exercise, lang_code)
Exemple #7
0
def _get_course_exercise_lang(course_key, exercise_key, lang_code):
    (course, exercise) = config.exercise_entry(course_key,
                                               exercise_key,
                                               lang=lang_code)
    if course is None or exercise is None:
        raise Http404()
    if not lang_code:
        lang_code = course.get('lang', DEFAULT_LANG)
    translation.activate(lang_code)
    return (course, exercise, lang_code)
Exemple #8
0
def container_post(request):
    '''
    Proxies the grading result from inside container to A+
    '''
    sid = request.POST.get("sid", None)
    if not sid:
        return HttpResponseForbidden("Missing sid")

    meta = read_and_remove_submission_meta(sid)
    if meta is None:
        return HttpResponseForbidden("Invalid sid")
    #clean_submission_dir(meta["dir"])


    data = {
        "points": int(request.POST.get("points", 0)),
        "max_points": int(request.POST.get("max_points", 1)),
    }
    for key in ["error", "grading_data"]:
        if key in request.POST:
            data[key] = request.POST[key]
    if "error" in data and data["error"].lower() in ("no", "false"):
        del data["error"]

    feedback = request.POST.get("feedback", "")
    # Fetch the corresponding exercise entry from the config.
    lang = meta["lang"]
    (course, exercise) = config.exercise_entry(meta["course_key"], meta["exercise_key"], lang=lang)
    if "feedback_template" in exercise:
        # replace the feedback with a rendered feedback template if the exercise is configured to do so
        # it is not feasible to support all of the old feedback template variables that runactions.py
        # used to have since the grading actions are not configured in the exercise YAML file anymore
        required_fields = { 'points', 'max_points', 'error', 'out' }
        result = MonitoredDict({
            "points": data["points"],
            "max_points": data["max_points"],
            "out": feedback,
            "error": data.get("error", False),
            "title": exercise.get("title", ""),
        })
        translation.activate(lang)
        feedback = template_to_str(course, exercise, None, exercise["feedback_template"], result=result)
        if result.accessed.isdisjoint(required_fields):
            alert = template_to_str(
                course, exercise, None,
                "access/feedback_template_did_not_use_result_alert.html")
            feedback = alert + feedback
        # Make unicode results ascii.
        feedback = feedback.encode("ascii", "xmlcharrefreplace")

    data["feedback"] = feedback

    if not post_data(meta["url"], data):
        raise IOError("Failed to deliver results")
    return HttpResponse("Ok")
Exemple #9
0
def _get_course_exercise_lang(course_key, exercise_key, lang_code):
    # Keep only "en" from "en-gb" if the long language format is used.
    if lang_code:
        lang_code = lang_code[:2]
    (course, exercise) = config.exercise_entry(course_key, exercise_key, lang=lang_code)
    if course is None or exercise is None:
        raise Http404()
    if not lang_code:
        lang_code = course.get('lang', DEFAULT_LANG)
    translation.activate(lang_code)
    return (course, exercise, lang_code)
Exemple #10
0
 def children_recursion(parent):
     if not "children" in parent:
         return []
     result = []
     for o in [o for o in parent["children"] if "key" in o]:
         of = _type_dict(o, course.get("exercise_types", {}))
         if "config" in of:
             _, exercise = config.exercise_entry(course["key"], str(of["key"]), '_root')
             of = export.exercise(request, course, exercise, of)
         elif "static_content" in of:
             of = export.chapter(request, course, of)
         of["children"] = children_recursion(o)
         result.append(of)
     return result
Exemple #11
0
 def children_recursion(parent):
     if not "children" in parent:
         return []
     result = []
     for o in [o for o in parent["children"] if "key" in o]:
         of = _type_dict(o, course.get("exercise_types", {}))
         if "config" in of:
             _, exercise = config.exercise_entry(course["key"], str(of["key"]), '_root')
             of = export.exercise(request, course, exercise, of)
         elif "static_content" in of:
             of = export.chapter(request, course, of)
         of["children"] = children_recursion(o)
         result.append(of)
     return result
Exemple #12
0
def exercise_ajax(request, course_key, exercise_key):
    '''
    Receives an AJAX request for an exercise.
    '''
    (course, exercise) = config.exercise_entry(course_key, exercise_key)
    if course is None or exercise is None or 'ajax_type' not in exercise:
        raise Http404()
    # jQuery does not send "requested with" on cross domain requests
    #if not request.is_ajax():
    #    return HttpResponse('Method not allowed', status=405)

    response = import_named(course, exercise['ajax_type'])(request, course,
                                                           exercise)

    # No need to control domain as valid submission_url is required to submit.
    response['Access-Control-Allow-Origin'] = '*'
    return response
Exemple #13
0
def exercise_ajax(request, course_key, exercise_key):
    '''
    Receives an AJAX request for an exercise.
    '''
    (course, exercise) = config.exercise_entry(course_key, exercise_key)
    if course is None or exercise is None or 'ajax_type' not in exercise:
        raise Http404()
    # jQuery does not send "requested with" on cross domain requests
    #if not request.is_ajax():
    #    return HttpResponse('Method not allowed', status=405)

    response = import_named(course, exercise['ajax_type'])(
        request, course, exercise)

    # No need to control domain as valid submission_url is required to submit.
    response['Access-Control-Allow-Origin'] = '*'
    return response
Exemple #14
0
def generated_exercise_file(request, course_key, exercise_key, exercise_instance, filename):
    '''
    Delivers a generated file of the exercise instance.
    '''
    # Fetch the corresponding exercise entry from the config.
    (course, exercise) = config.exercise_entry(course_key, exercise_key)
    if course is None or exercise is None:
        raise Http404()
    if "generated_files" in exercise:
        import magic
        for gen_file_conf in exercise["generated_files"]:
            if gen_file_conf["file"] == filename:
                if "allow_download" in gen_file_conf and gen_file_conf["allow_download"]:
                    file_content = read_generated_exercise_file(course, exercise,
                                                                exercise_instance, filename)
                    response = HttpResponse(file_content,
                                            content_type=magic.from_buffer(file_content, mime=True))
                    response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
                    return response
                else:
                    # hide file existence with 404
                    raise Http404()
    raise Http404()
Exemple #15
0
def exercise(request, course_key, exercise_key):
    '''
    Presents the exercise and accepts answers to it.
    '''
    post_url = request.GET.get('post_url', None)
    lang = request.GET.get('lang', None)

    # Fetch the corresponding exercise entry from the config.
    (course, exercise) = config.exercise_entry(course_key, exercise_key, lang=lang)
    if course is None or exercise is None:
        raise Http404()

    # Exercise language.
    if not lang:
        if "lang" in course:
            lang = course["lang"]
        else:
            lang = "en"
    translation.activate(lang)

    # Try to call the configured view.
    return import_named(course, exercise['view_type'])(
        request, course, exercise, post_url)
Exemple #16
0
def configure(request):
    '''
    Configure a course according to the gitmanager protocol.
    '''
    if request.method != "POST":
        return HttpResponse(status=405)

    if request.POST.get("publish"):
        return publish(request)

    if "exercises" not in request.POST or "course_id" not in request.POST:
        return HttpResponse("Missing exercises or course_id", status=400)

    try:
        exercises = json.loads(request.POST["exercises"])
    except (JSONDecodeError, ValueError) as e:
        LOGGER.info(f"Invalid exercises field: {e}")
        return HttpResponse(f"Invalid exercises field: {e}", status=400)

    course_id = request.POST["course_id"]
    try:
        access_write_check(request, course_id)
    except PermissionDenied as e:
        SecurityLog.reject(request, f"CONFIGURE",
                           f"course_id={course_id}: {e}")
        raise
    except ValueError as e:
        LOGGER.info(f"Invalid course_id field: {e}")
        return HttpResponse(f"Invalid course_id field: {e}", status=400)

    SecurityLog.accept(request, f"CONFIGURE", f"course_id={course_id}")

    root_dir = Path(settings.COURSE_STORE)
    course_path = root_dir / course_id
    if course_path.exists():
        try:
            rmtree(course_path)
        except OSError:
            LOGGER.exception("Failed to remove old stored course files")
            return HttpResponse("Failed to remove old stored course files",
                                status=500)

    course_files_path = course_path / EXTERNAL_FILES_DIR
    course_exercises_path = course_path / EXTERNAL_EXERCISES_DIR
    version_id_path = root_dir / (course_id + ".version")
    course_files_path.mkdir(parents=True, exist_ok=True)
    course_exercises_path.mkdir(parents=True, exist_ok=True)

    if "files" in request.FILES:
        tar_file = request.FILES["files"].file
        tarh = TarFile(fileobj=tar_file)
        tarh.extractall(course_files_path)

    course_config = {
        "name": course_id,
        "exercises": [ex["key"] for ex in exercises],
        "exercise_loader": "access.config._ext_exercise_loader",
    }

    try:
        with open(course_path / "index.json", "w") as f:
            json.dump(course_config, f)

        for info in exercises:
            with open(course_exercises_path / (info["key"] + ".json"),
                      "w") as f:
                json.dump(info["config"], f)
    except OSError as e:
        LOGGER.exception("Failed to dump configuration JSONs to files")
        return HttpResponse("Failed to dump configuration JSONs to files: {e}",
                            status=500)

    if "version_id" in request.POST:
        try:
            with open(version_id_path, "w") as f:
                f.write(request.POST["version_id"])
        except OSError as e:
            LOGGER.exception("Failed to write version id file")
            return HttpResponse("Failed to write version id file: {e}",
                                status=500)
    elif version_id_path.exists():
        try:
            rm_path(version_id_path)
        except OSError as e:
            LOGGER.exception("Failed to remove version id file")
            return HttpResponse("Failed to remove version id file: {e}",
                                status=500)

    course_config = config._course_root_from_root_dir(course_id, root_dir)

    defaults = {}
    for info in exercises:
        of = info["spec"]
        if info.get("config"):
            of["config"] = info["key"] + ".json"
            course, exercise = config.exercise_entry(course_config,
                                                     info["key"], "_root")
            of = export.exercise(request, course, exercise, of)
        defaults[of["key"]] = of

    return JsonResponse(defaults)