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 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 post_result(submission_url, course, exercise, template, result): ''' Posts grading result to the submission URL. @type submission_url: C{str} @param submission_url: a submission URL where grader should POST result @type course: C{dict} @param course: a course configuration @type exercise: C{dict} @param exercise: an exercise configuration @type template: C{str} @param template: a template name to use @type result: C{dict} @param result: additional results ''' html = template_to_str(course, exercise, None, template, result) # Make unicode results ascii. html = html.encode("ascii", "xmlcharrefreplace") data = { "max_points": result.get("max_points", 1), "points": result.get("points", 0), "feedback": html } if "error" in result and result["error"]: data["error"] = True if "grading_data" in result: data["grading_data"] = result["grading_data"] post_data(submission_url, data)
def resubmit_form(course, exercise, action, submission_dir): data = json.loads(read_submission_file(submission_dir, "data.json")) form = GradedForm(data, exercise=exercise) out = template_to_str(course, exercise, data.get("__aplus_post_url"), "access/task_resubmit.html", { "form": form, "instructions": action.get("instructions", False), }) return { "points": 0, "max_points": 0, "out": out, "err": "", "stop": False }
def create_more(self, configuration): ''' Creates more instructions by configuration. ''' more = "" if "more" in configuration: more += configuration["more"] if "include" in configuration: more += template_to_str(None, None, configuration["include"]) return more or None
def resubmit_form(course, exercise, action, submission_dir): data = json.loads(read_submission_file(submission_dir, "data.json")) form = GradedForm(data, exercise=exercise) out = template_to_str( course, exercise, data.get("__aplus_post_url"), "access/task_resubmit.html", { "form": form, "instructions": action.get("instructions", False), }) return {"points": 0, "max_points": 0, "out": out, "err": "", "stop": False}
def _acceptSubmission(request, course, exercise, post_url, sdir): ''' Queues the submission for grading. ''' # Backup synchronous grading. if not settings.CELERY_BROKER: LOGGER.warning("No queue configured") from grader.runactions import runactions r = runactions(course, exercise, sdir, get_uid(request), int(request.GET.get("ordinal_number", 1))) html = template_to_str(course, exercise, "", r["template"], r["result"]) return render_template(request, course, exercise, post_url, "access/async_accepted.html", { "synchronous": True, "accepted": True, "max_points": r["result"].get("max_points", 1), "points": r["result"].get("points", 0), "feedback": html, }) if "submission_url" in request.GET: surl = request.GET["submission_url"] surl_missing = False else: LOGGER.warning("submission_url missing from a request") surl = request.build_absolute_uri(reverse('test-result')) surl_missing = True # Queue grader. tasks.grade.delay(course["key"], exercise["key"], translation.get_language(), surl, sdir, get_uid(request), int(request.GET.get("ordinal_number", 1))) _acceptSubmission.counter += 1 qlen = queue_length() LOGGER.debug("Submission of %s/%s, queue counter %d, queue length %d", course["key"], exercise["key"], _acceptSubmission.counter, qlen) if qlen >= settings.QUEUE_ALERT_LENGTH: LOGGER.error("Queue alert, length: %d", qlen) return render_template(request, course, exercise, post_url, "access/async_accepted.html", { "accepted": True, "wait": True, "missing_url": surl_missing, "queue": qlen })
def handle(self, *args, **options): config = ConfigParser() # Check arguments. if len(args) < 1 or "/" not in args[0]: raise CommandError("Required arguments missing: course_key/exercise_key") course_key, exercise_key = args[0].split("/", 1) # Get exercise configuration. (course, exercise) = config.exercise_entry(course_key, exercise_key) if course is None: raise CommandError("Course not found for key: %s" % (course_key)) if exercise is None: raise CommandError("Exercise not found for key: %s/%s" % (course_key, exercise_key)) self.stdout.write('Exercise configuration retrieved.') # Check exercise type. if not "actions" in exercise: raise CommandError("Cannot grade: exercise does not configure asynchronous actions") # Create submission. sdir = create_submission_dir(course, exercise) if len(args) == 1: os.makedirs(sdir + "/user") for n in range(1, len(args)): name = args[n] # Copy individual files. if os.path.isfile(name): submit_path = submission_file_path(sdir, os.path.basename(name)) shutil.copy2(name, submit_path) # Copy a directory. elif os.path.isdir(name): if len(args) != 2: raise CommandError("Can only submit one directory or multiple files.") shutil.copytree(name, sdir + "/user", True) else: raise CommandError("Submit file not found: %s" % (name)) # Run actions. r = runactions(course, exercise, sdir) self.stdout.write("Response body:") self.stdout.write(template_to_str(course, exercise, "", r["template"], r["result"]))
def post_result(submission_url, course, exercise, template, result): ''' Posts grading result to the submission URL. @type submission_url: C{str} @param submission_url: a submission URL where grader should POST result @type course: C{dict} @param course: a course configuration @type exercise: C{dict} @param exercise: an exercise configuration @type template: C{str} @param template: a template name to use @type result: C{dict} @param result: additional results ''' html = template_to_str(course, exercise, None, template, result) # Make unicode results ascii. html = html.encode("ascii", "xmlcharrefreplace") data = { "max_points": result.get("max_points", 1), "points": result.get("points", 0), "feedback": html } if "error" in result and result["error"]: data["error"] = True if "grading_data" in result: data["grading_data"] = result["grading_data"] # Try to send send the result. try: r = requests.post(submission_url, data=data) if r.status_code != 200: r.raise_for_status() rsp = r.json() if not "success" in rsp or not rsp["success"]: LOGGER.error("Result POST to \"%s\" got unexpected response: %s", submission_url, rsp.body) except Exception: LOGGER.error("Failed to submit \"%s\"", submission_url)
def _acceptSubmission(request, course, exercise, post_url, sdir): ''' Queues the submission for grading. ''' uids = get_uid(request) attempt = int(request.GET.get("ordinal_number", 1)) container_flag = settings.CONTAINER_MODE and "container" in exercise # Backup synchronous grading. if not container_flag and not settings.CELERY_BROKER: LOGGER.warning("No queue configured") from grader.runactions import runactions r = runactions(course, exercise, sdir, uids, attempt) html = template_to_str(course, exercise, "", r["template"], r["result"]) return render_template( request, course, exercise, post_url, "access/async_accepted.html", { "synchronous": True, "accepted": True, "max_points": r["result"].get("max_points", 1), "points": r["result"].get("points", 0), "feedback": html, }) if "submission_url" in request.GET: surl = request.GET["submission_url"] surl_missing = False else: LOGGER.warning("submission_url missing from a request") surl = request.build_absolute_uri(reverse('test-result')) surl_missing = True _acceptSubmission.counter += 1 # Order container for grading. if container_flag: c = _requireContainer(exercise) course_extra = { "key": course["key"], "name": course["name"], } exercise_extra = { "key": exercise["key"], "title": exercise.get("title", None), } if exercise.get("personalized", False): exercise_extra["personalized_exercise"] \ = select_generated_exercise_instance(course, exercise, uids, attempt) if settings.ENABLE_PERSONAL_DIRECTORIES: exercise_extra["personal_directory"] \ = user_personal_directory_path(course, exercise, uids) sid = os.path.basename(sdir) write_submission_meta(sid, { "url": surl, "dir": sdir, }) r = invoke([ settings.CONTAINER_SCRIPT, sid, request.scheme + "://" + request.get_host(), c["image"], os.path.join(DIR, course["key"], c["mount"]), sdir, c["cmd"], json.dumps(course_extra), json.dumps(exercise_extra), ]) LOGGER.debug("Container order exit=%d out=%s err=%s", r["code"], r["out"], r["err"]) qlen = 1 # Queue in celery & rabbitmq for chroot sandbox actions. else: tasks.grade.delay(course["key"], exercise["key"], translation.get_language(), surl, sdir, uids, attempt) qlen = queue_length() LOGGER.debug("Submission of %s/%s, queue counter %d, queue length %d", course["key"], exercise["key"], _acceptSubmission.counter, qlen) if qlen >= settings.QUEUE_ALERT_LENGTH: LOGGER.error("Queue alert, length: %d", qlen) return render_template(request, course, exercise, post_url, "access/async_accepted.html", { "accepted": True, "wait": True, "missing_url": surl_missing, "queue": qlen })
def _acceptSubmission(request, course, exercise, post_url, sdir): ''' Queues the submission for grading. ''' uids = get_uid(request) attempt = int(request.GET.get("ordinal_number", 1)) container_flag = settings.CONTAINER_MODE and "container" in exercise # Backup synchronous grading. if not container_flag and not settings.CELERY_BROKER: LOGGER.warning("No queue configured") from grader.runactions import runactions r = runactions(course, exercise, sdir, uids, attempt) html = template_to_str(course, exercise, "", r["template"], r["result"]) return render_template(request, course, exercise, post_url, "access/async_accepted.html", { "synchronous": True, "accepted": True, "max_points": r["result"].get("max_points", 1), "points": r["result"].get("points", 0), "feedback": html, }) if "submission_url" in request.GET: surl = request.GET["submission_url"] surl_missing = False else: LOGGER.warning("submission_url missing from a request") surl = request.build_absolute_uri(reverse('test-result')) surl_missing = True _acceptSubmission.counter += 1 # Order container for grading. if container_flag: c = _requireContainer(exercise) course_extra = { "key": course["key"], "name": course["name"], } exercise_extra = { "key": exercise["key"], "title": exercise.get("title", None), "resources": c.get("resources", {}), # Unofficial param, implemented differently later "require_constant_environment": c.get("require_constant_environment", False) # Unofficial param, implemented differently later } if exercise.get("personalized", False): exercise_extra["personalized_exercise"] \ = select_generated_exercise_instance(course, exercise, uids, attempt) if settings.ENABLE_PERSONAL_DIRECTORIES: exercise_extra["personal_directory"] \ = user_personal_directory_path(course, exercise, uids) sid = os.path.basename(sdir) write_submission_meta(sid, { "url": surl, "dir": sdir, "course_key": course["key"], "exercise_key": exercise["key"], "lang": translation.get_language(), }) r = invoke([ settings.CONTAINER_SCRIPT, sid, request.scheme + "://" + request.get_host(), c["image"], os.path.join(DIR, course["key"], c["mount"]), sdir, c["cmd"], json.dumps(course_extra), json.dumps(exercise_extra), ]) LOGGER.debug("Container order exit=%d out=%s err=%s", r["code"], r["out"], r["err"]) qlen = 1 # Queue in celery & rabbitmq for chroot sandbox actions. else: tasks.grade.delay(course["key"], exercise["key"], translation.get_language(), surl, sdir, uids, attempt) qlen = queue_length() LOGGER.debug("Submission of %s/%s, queue counter %d, queue length %d", course["key"], exercise["key"], _acceptSubmission.counter, qlen) if qlen >= settings.QUEUE_ALERT_LENGTH: LOGGER.error("Queue alert, length: %d", qlen) return render_template(request, course, exercise, post_url, "access/async_accepted.html", { "accepted": True, "wait": True, "missing_url": surl_missing, "queue": qlen })