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")
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()
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)
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()
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()
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)
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")
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)
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
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
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
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()
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)
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)