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 exercise_ajax(request, course_key, exercise_key): ''' Receives an AJAX request for an exercise. ''' lang = request.GET.get('lang', None) try: (course, exercise, lang) = _get_course_exercise_lang(course_key, exercise_key, lang) 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) except (ConfigError, ImportError) as e: return _error_response(exc=e) # No need to control domain as valid submission_url is required to submit. response['Access-Control-Allow-Origin'] = '*' return response
def exercise_template(request, course_key, exercise_key, parameter=None): ''' Presents the exercise template. ''' lang = request.GET.get('lang', None) (course, exercise, lang) = _get_course_exercise_lang(course_key, exercise_key, lang) 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 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(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(request, course_key, exercise_key): ''' Presents the exercise and accepts answers to it. ''' post_url = request.GET.get('post_url', None) lang = request.POST.get('__grader_lang', None) or request.GET.get('lang', None) (course, exercise, lang) = _get_course_exercise_lang(course_key, exercise_key, lang) # Try to call the configured view. return import_named(course, exercise['view_type'])(request, course, exercise, post_url)
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) (course, exercise, lang) = _get_course_exercise_lang(course_key, exercise_key, lang) # Try to call the configured view. return import_named(course, exercise['view_type'])(request, course, exercise, post_url)
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 exercise_model(request, course_key, exercise_key, parameter=None): ''' Presents a model answer for an exercise. ''' lang = request.GET.get('lang', None) try: (course, exercise, lang) = _get_course_exercise_lang(course_key, exercise_key, lang) except ConfigError as e: return HttpResponse(str(e), content_type='text/plain') response = None path = None if 'model_files' in exercise and parameter: path = _find_file(exercise['model_files'], parameter) if path: try: with open(os.path.join(course['dir'], path)) as f: content = f.read() except FileNotFoundError as error: raise Http404(f'Model file "{parameter}" missing') from error except OSError as error: LOGGER.error(f'Error in reading the exercise model file "{path}".', exc_info=error) content = str(error) response = HttpResponse(content, content_type='text/plain') else: try: response = import_named(course, exercise['view_type'] + "Model")(request, course, exercise, parameter) except ImportError: pass except ConfigError as e: response = HttpResponse(str(e), content_type='text/plain') if response: return response else: raise Http404()
def exercise_model(request, course_key, exercise_key, parameter=None): ''' Presents a model answer for an exercise. ''' lang = request.GET.get('lang', None) (course, exercise, lang) = _get_course_exercise_lang(course_key, exercise_key, lang) response = None path = None 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: try: with open(os.path.join(course['dir'], path)) as f: content = f.read() except FileNotFoundError: pass else: response = HttpResponse(content, content_type='text/plain') else: try: response = import_named(course, exercise['view_type'] + "Model")(request, course, exercise, parameter) except ImportError: pass if response: return response else: 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.POST.get('__grader_lang', None) or request.GET.get('lang', None) (course, exercise, lang) = _get_course_exercise_lang(course_key, exercise_key, lang) # Try to call the configured view. try: return import_named(course, exercise['view_type'])(request, course, exercise, post_url) except ConfigError as error: return render(request, 'access/exercise_config_error.html', { 'course': course, 'exercise': exercise, 'config_error': str(error), 'result': { 'error': True, }, })
def exercise(request, course_key, exercise_key): ''' Presents the exercise and accepts answers to it. ''' try: if request.method == "GET": access_read_check_if_number(request, course_key) else: access_write_check_if_number(request, course_key) except PermissionDenied as e: SecurityLog.reject(request, f"EXERCISE-{request.method}", f"course_id={course_key}: {e}") raise SecurityLog.accept(request, f"EXERCISE-{request.method}", f"course_id={course_key}") post_url = request.GET.get('post_url', None) lang = request.POST.get('__grader_lang', None) or request.GET.get( 'lang', None) course = exercise = None try: (course, exercise, lang) = _get_course_exercise_lang(course_key, exercise_key, lang) # Try to call the configured view. return import_named(course, exercise['view_type'])(request, course, exercise, post_url) except (ConfigError, ImportError) as error: return render( request, 'access/exercise_config_error.html', { 'course': course, 'exercise': exercise, 'config_error': str(error), 'result': { 'error': True, }, })
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 _course_root(self, course_key): ''' Gets course dictionary root (meta and data). @type course_key: C{str} @param course_key: a course key @rtype: C{dict} @return: course root or None ''' # Try cached version. if course_key in self._courses: course_root = self._courses[course_key] try: if course_root["mtime"] >= os.path.getmtime(course_root["file"]): return course_root except OSError: pass LOGGER.debug('Loading course "%s"' % (course_key)) meta = read_meta(os.path.join(DIR, course_key, META)) try: f = self._get_config(os.path.join(self._conf_dir(DIR, course_key, meta), INDEX)) except ConfigError: return None t = os.path.getmtime(f) data = self._parse(f) if data is None: raise ConfigError('Failed to parse configuration file "%s"' % (f)) self._check_fields(f, data, ["name"]) data["key"] = course_key data["mtime"] = t data["dir"] = self._conf_dir(DIR, course_key, {}) if "language" in data: data["lang"] = data["language"] if "modules" in data: keys = [] config = {} def recurse_exercises(parent): if "children" in parent: for exercise_vars in parent["children"]: if "key" in exercise_vars: cfg = None if "config" in exercise_vars: cfg = exercise_vars["config"] elif "type" in exercise_vars and "exercise_types" in data \ and exercise_vars["type"] in data["exercise_types"] \ and "config" in data["exercise_types"][exercise_vars["type"]]: cfg = data["exercise_types"][exercise_vars["type"]]["config"] if cfg: keys.append(exercise_vars["key"]) config[exercise_vars["key"]] = cfg recurse_exercises(exercise_vars) for module in data["modules"]: recurse_exercises(module) data["exercises"] = keys data["config_files"] = config # Enable course configurable ecercise_loader function. exercise_loader = self._default_exercise_loader if "exercise_loader" in data: exercise_loader = import_named(data, data["exercise_loader"]) self._courses[course_key] = course_root = { "meta": meta, "file": f, "mtime": t, "ptime": time.time(), "data": data, "lang": data.get('lang', DEFAULT_LANG), "exercise_loader": exercise_loader, "exercises": {} } return course_root
def runactions(course, exercise, submission_dir): ''' Runs configured grading actions for an exercise submission. @type course: C{dict} @param course: a course configuration @type exercise: C{dict} @param exercise: an exercise configuration @type submission_dir: C{str} @param submission_dir: a submission directory where submitted files are stored @rtype: C{dict} @return: template = template name, result = points, max_points, tests ''' total_points = 0 max_points = 0 total_result = [] error = False has_appendixes = False # Try to run the grading actions. try: for action in exercise["actions"]: exgrader = None try: exgrader = import_named(course, action["type"]) except ImproperlyConfigured as e: raise ConfigError("Invalid action \"type\" in exercise configuration.", e) # Run the exercise grader action LOGGER.debug("Running action \"%s\"", action["type"]) r = exgrader(course, exercise, action, submission_dir) has_appendixes = has_appendixes or \ ("appendix" in r and r["appendix"]) # Configured template values. if "title" in action: r["title"] = action["title"] if "html" in action and action["html"]: r["html"] = True # Override with configured points. if "points" in action: r["max_points"] = action["points"] if r["stop"]: r["points"] = 0 else: r["points"] = action["points"] elif "max_points" in action: r["max_points"] = action["max_points"] if r["points"] > action["max_points"]: r["points"] = action["max_points"] # Sum total numbers. total_result.append(r) total_points += r["points"] if "max_points" in r: max_points += r["max_points"] if r["stop"]: if "expect_success" in action: error = action["expect_success"] break # Override with configured max points. if "max_points" in exercise: max_points = exercise["max_points"] # Check the points are in range. if total_points > max_points: total_points = max_points elif total_points < 0: total_points = 0 # Determine template. template = None if "feedback_template" in exercise: template = exercise["feedback_template"] else: template = "access/task_success.html" return { "template": template, "result": { "points": total_points, "max_points": max_points, "tests": total_result, "error": error, "has_appendixes": has_appendixes, } } finally: clean_submission_dir(submission_dir)
def runactions(course, exercise, submission_dir, user_ids="", submission_number=1): ''' Runs configured grading actions for an exercise submission. @type course: C{dict} @param course: a course configuration @type exercise: C{dict} @param exercise: an exercise configuration @type submission_dir: C{str} @param submission_dir: a submission directory where submitted files are stored @type user_ids: C{str} @param user_ids: user id(s) of the submitter(s) for personalized exercises @type submission_number: C{int} @param submission_number: ordinal number of the submission (parameter in the grader protocol) @rtype: C{dict} @return: template = template name, result = points, max_points, tests ''' total_points = 0 max_points = 0 total_result = [] error = False has_appendixes = False # Try to run the grading actions. try: for action in exercise["actions"]: exgrader = None try: exgrader = import_named(course, action["type"]) except ImproperlyConfigured as e: raise ConfigError( "Invalid action \"type\" in exercise configuration.", e) # Run the exercise grader action LOGGER.debug("Running action \"%s\"", action["type"]) if action["type"] == "grader.actions.prepare" or \ action["type"] == "grader.actions.store_user_files": r = exgrader(course, exercise, action, submission_dir, user_ids, submission_number) else: r = exgrader(course, exercise, action, submission_dir) has_appendixes = has_appendixes or \ ("appendix" in r and r["appendix"]) # Configured template values. if "title" in action: r["title"] = action["title"] if "html" in action and action["html"]: r["html"] = True # Override with configured points. if "points" in action: r["max_points"] = action["points"] if r["stop"]: r["points"] = 0 else: r["points"] = action["points"] elif "max_points" in action: r["max_points"] = action["max_points"] if r["points"] > action["max_points"]: r["points"] = action["max_points"] # Sum total numbers. total_result.append(r) total_points += r["points"] if "max_points" in r: max_points += r["max_points"] if r["stop"]: if "expect_success" in action: error = action["expect_success"] if not ("continue_after_error" in action and action["continue_after_error"]): break # skip the subsequent actions # Override with configured max points. if "max_points" in exercise: max_points = exercise["max_points"] # Check the points are in range. if total_points > max_points: total_points = max_points elif total_points < 0: total_points = 0 # Determine template. template = None if "feedback_template" in exercise: template = exercise["feedback_template"] else: template = "access/task_success.html" return { "template": template, "result": { "points": total_points, "max_points": max_points, "tests": total_result, "error": error, "has_appendixes": has_appendixes, } } finally: clean_submission_dir(submission_dir)
def _course_root(self, course_key): ''' Gets course dictionary root (meta and data). @type course_key: C{str} @param course_key: a course key @rtype: C{dict} @return: course root or None ''' # Try cached version. if course_key in self._courses: course_root = self._courses[course_key] try: if course_root["mtime"] >= os.path.getmtime(course_root["file"]): return course_root except OSError: pass LOGGER.debug('Loading course "%s"' % (course_key)) meta = read_meta(os.path.join(DIR, course_key, META)) try: f = self._get_config(os.path.join(self._conf_dir(DIR, course_key, meta), INDEX)) except ConfigError: return None t = os.path.getmtime(f) data = self._parse(f) if data is None: raise ConfigError('Failed to parse configuration file "%s"' % (f)) self._check_fields(f, data, ["name"]) data["key"] = course_key data["mtime"] = t data["dir"] = self._conf_dir(DIR, course_key, {}) if "static_url" not in data: data["static_url"] = "{}{}{}/".format( settings.STATIC_URL_HOST_INJECT, settings.STATIC_URL, course_key ) if "modules" in data: keys = [] config = {} def recurse_exercises(parent): if "children" in parent: for exercise_vars in parent["children"]: if "key" in exercise_vars: exercise_key = str(exercise_vars["key"]) cfg = None if "config" in exercise_vars: cfg = exercise_vars["config"] elif "type" in exercise_vars and "exercise_types" in data \ and exercise_vars["type"] in data["exercise_types"] \ and "config" in data["exercise_types"][exercise_vars["type"]]: cfg = data["exercise_types"][exercise_vars["type"]]["config"] if cfg: keys.append(exercise_key) config[exercise_key] = cfg recurse_exercises(exercise_vars) for module in data["modules"]: recurse_exercises(module) data["exercises"] = keys data["config_files"] = config # Enable course configurable ecercise_loader function. exercise_loader = self._default_exercise_loader if "exercise_loader" in data: exercise_loader = import_named(data, data["exercise_loader"]) self._courses[course_key] = course_root = { "meta": meta, "file": f, "mtime": t, "ptime": time.time(), "data": data, "lang": self._default_lang(data), "exercise_loader": exercise_loader, "exercises": {} } symbolic_link(DIR, data) return course_root