diff --git a/inginious/frontend/common/static/js/task.js b/inginious/frontend/common/static/js/task.js index a246b47e..a0ad5e48 100644 --- a/inginious/frontend/common/static/js/task.js +++ b/inginious/frontend/common/static/js/task.js @@ -1006,7 +1006,10 @@ function convertInginiousLanguageToCodemirror(inginiousLanguage) { "c11": "c", "python2": "python", "python3": "python", - "ruby": "ruby" + "ruby": "ruby", + "GNU C++": "cpp", + "GNU C++11" : "cpp", + "GNU C++14" : "cpp" }; return languages[inginiousLanguage]; @@ -1133,7 +1136,13 @@ var lintServerUrl = "http://localhost:4567/"; function lintCode(language, problemId, callback){ if(language == "plain") language = getLanguageForProblemId(problemId); - var editor = getEditorForProblemId(problemId); + + language = convertInginiousLanguageToCodemirror(language) + + + var editor = getEditorForProblemId(problemId); + + var code = editor.getValue(); var apiUrl = lintServerUrl + language; callback = callback || getCallbackForLanguage(language, editor); diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/__init__.py b/inginious/frontend/webapp/plugins/rubric_scoring/__init__.py new file mode 100644 index 00000000..20ee5f45 --- /dev/null +++ b/inginious/frontend/webapp/plugins/rubric_scoring/__init__.py @@ -0,0 +1,34 @@ +# -*- 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. + +""" A demo plugin that adds a page """ +from . import pages + + +def init(plugin_manager, _, _2, _3): + """ Init the plugin """ + + plugin_manager.add_page(r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring', pages.CourseTaskListPage) + plugin_manager.add_page(r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring/task/([a-z0-9A-Z\-_]+)', pages.TaskListSubmissionPage) + plugin_manager.add_page(r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring/task/([a-z0-9A-Z\-_]+)/submission/([a-z0-9A-Z\-_]+)', + pages.SubmissionRubricPage) + + + + plugin_manager.add_page(r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring_temp', pages.CourseTaskListPageTemp) + plugin_manager.add_page(r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring_temp/task/([a-z0-9A-Z\-_]+)', + pages.TaskListSubmissionPageTemp) + plugin_manager.add_page( + r'/admin/([a-z0-9A-Z\-_]+)/rubric_scoring_temp/task/([a-z0-9A-Z\-_]+)/submission/([a-z0-9A-Z\-_]+)', + pages.SubmissionRubricPageTemp) + + + + plugin_manager.add_hook('course_admin_menu', pages.rubric_course_admin_menu_hook) + plugin_manager.add_hook('course_admin_menu', pages.rubric_course_admin_menu_hook_temp) + + + + diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/pages.py b/inginious/frontend/webapp/plugins/rubric_scoring/pages.py new file mode 100644 index 00000000..cf87af0c --- /dev/null +++ b/inginious/frontend/webapp/plugins/rubric_scoring/pages.py @@ -0,0 +1,471 @@ +import web +import posixpath +import urllib +import os +import collections +import inginious.frontend.webapp.pages.api._api_page as api +from .rubric_wdo import RubricWdo +from bson.objectid import ObjectId +from pymongo import MongoClient +import gridfs +import bson + +from inginious.frontend.webapp.pages.api._api_page import APIAuthenticatedPage +from inginious.frontend.webapp.pages.utils import INGIniousAuthPage, INGIniousPage +from inginious.frontend.webapp.pages.course_admin.utils import INGIniousAdminPage +from inginious.common.filesystems.local import LocalFSProvider +from inginious.common.course_factory import CourseNotFoundException, CourseUnreadableException, InvalidNameException + +from collections import OrderedDict + + +_BASE_RENDERER_PATH = 'frontend/webapp/plugins/rubric_scoring' +_BASE_RENDERER_PATH_TEMP = 'frontend/webapp/plugins/rubric_scoring_temp' + +_BASE_STATIC_FOLDER = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static') + +def rubric_course_admin_menu_hook_temp(course): + return "rubric_scoring_temp", ' Rubric Scoring Temp' + +def rubric_course_admin_menu_hook(course): + return "rubric_scoring", ' Rubric Scoring' + + +#listado de task +class CourseTaskListPage(INGIniousAdminPage): + """ List informations about all tasks """ + + def GET_AUTH(self, courseid): # pylint: disable=arguments-differ + """ GET request """ + course, _ = self.get_course_and_check_rights(courseid) + return self.page(course) + + def POST_AUTH(self, courseid): # pylint: disable=arguments-differ + """ POST request """ + course, _ = self.get_course_and_check_rights(courseid) + data = web.input(task=[]) + + if "task" in data: + # Change tasks order + for index, taskid in enumerate(data["task"]): + try: + task = self.task_factory.get_task_descriptor_content(courseid, taskid) + task["order"] = index + self.task_factory.update_task_descriptor_content(courseid, taskid, task) + except: + pass + + return self.page(course) + + def submission_url_generator(self, taskid): + """ Generates a submission url """ + return "?format=taskid%2Fusername&tasks=" + taskid + + def page(self, course): + """ Get all data and display the page """ + url = 'rubric_scoring' + data = list(self.database.user_tasks.aggregate( + [ + { + "$match": + { + "courseid": course.get_id(), + "username": {"$in": self.user_manager.get_course_registered_users(course, False)} + } + }, + { + "$group": + { + "_id": "$taskid", + "viewed": {"$sum": 1}, + "attempted": {"$sum": {"$cond": [{"$ne": ["$tried", 0]}, 1, 0]}}, + "attempts": {"$sum": "$tried"}, + "succeeded": {"$sum": {"$cond": ["$succeeded", 1, 0]}} + } + } + ])) + + # Load tasks and verify exceptions + files = self.task_factory.get_readable_tasks(course) + output = {} + errors = [] + for task in files: + try: + output[task] = course.get_task(task) + except Exception as inst: + errors.append({"taskid": task, "error": str(inst)}) + tasks = OrderedDict(sorted(list(output.items()), key=lambda t: (t[1].get_order(), t[1].get_id()))) + + # Now load additional informations + result = OrderedDict() + for taskid in tasks: + result[taskid] = {"name": tasks[taskid].get_name(), "viewed": 0, "attempted": 0, "attempts": 0, "succeeded": 0, + "url": self.submission_url_generator(taskid)} + + for entry in data: + if entry["_id"] in result: + result[entry["_id"]]["viewed"] = entry["viewed"] + result[entry["_id"]]["attempted"] = entry["attempted"] + result[entry["_id"]]["attempts"] = entry["attempts"] + result[entry["_id"]]["succeeded"] = entry["succeeded"] + + return self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).task_list(course, result, errors, url) + + +#Listado de submissions +class TaskListSubmissionPage(INGIniousAdminPage): + def GET_AUTH(self, course_id, task_id): + + course, task = self.get_course_and_check_rights(course_id, task_id) + + + self.template_helper.add_javascript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.3.6/papaparse.min.js") + self.template_helper.add_javascript("https://cdn.plot.ly/plotly-1.30.0.min.js") + self.template_helper.add_javascript("https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js") + + return self.page(course, task_id, task) + + + def page(self, course, task_id, task ): + """ Get all data and display the page """ + #print(self.database.collection_names()) + + url = 'rubric_scoring' + + result = list(self.database.submissions.aggregate( + + [ + { + "$match": + { + "courseid": course.get_id(), + "taskid": task_id, + "username": {"$in": self.user_manager.get_course_registered_users(course, False)}, + + } + }, + { + "$project": { + + "taskid": 1, + "result": 1, + "submitted_on": 1, + "username": 1, + "custom": 1 + + } + } + + ])) + + #print("result", result) + data = OrderedDict() + + for entry in result: + data[entry["_id"]] = {"taskid": entry["taskid"], "result": entry["result"], "_id":entry["_id"], + "username":entry["username"], "date": entry["submitted_on"]} + + if "rubric_score" not in entry["custom"]: + data[entry["_id"]]["rubric_score"] = "not grade" + + else: + data[entry["_id"]]["rubric_score"] = entry["custom"]["rubric_score"] + + + #print ("data -> ",data) + return ( + self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).task_admin_rubric( + course,data, task, url) + ) + + + +class SubmissionRubricPage(INGIniousAdminPage): + + def POST_AUTH(self, course_id, task_id, submission_id): + """ POST request """ + course, task = self.get_course_and_check_rights(course_id, task_id) + data = web.input() + + self.database.submissions.update( + {"_id": ObjectId(submission_id)}, + {"$set": {"custom.rubric_score": data["grade"]} + }) + + return self.page(course, task, submission_id) + + + + def GET_AUTH(self, course_id, task_id, submission_id): + #print("-->", course_id, task_id, submission_id) + course, task = self.get_course_and_check_rights(course_id, task_id) + + self.template_helper.add_javascript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.3.6/papaparse.min.js") + self.template_helper.add_javascript("https://cdn.plot.ly/plotly-1.30.0.min.js") + self.template_helper.add_javascript("https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js") + self.template_helper.add_css("static/css/rubric_scoring.css") + + return self.page(course, task, submission_id) + + def page(self, course, task, submission_id): + #print (submission_id, type(submission_id)) + #print ("////", course) + + + # TODO: verificar que exista exactamente un elemento. TOMAR MEDIDAS PREVENTIVAS EN CASO CONTRARIO + problem_id = task.get_problems()[0].get_id() + + + + submission = self.submission_manager.get_submission( submission_id, user_check=False) + #print (submission) + submission_input = self.submission_manager.get_input_from_submission(submission) + print ("submission_code", submission_input) + print ("------------") + + language = submission_input['input'][problem_id + '/language'] + data = { + "url": 'rubric_scoring', + + "memory": "not memory", + "test_passed": "no test casses", + "verdict": "verdict" + + } + + rubric_wdo = RubricWdo("/home/lina/Documents/Rubrica.xlsx") + + return ( + self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).submission_rubric( + course, task, submission_input, problem_id, rubric_wdo, data, language) + ) + + +#listado de task +class CourseTaskListPageTemp(INGIniousAdminPage): + """ List informations about all tasks """ + + def GET_AUTH(self, courseid): # pylint: disable=arguments-differ + """ GET request """ + course, _ = self.get_course_and_check_rights(courseid) + return self.page(course) + + def POST_AUTH(self, courseid): # pylint: disable=arguments-differ + """ POST request """ + course, _ = self.get_course_and_check_rights(courseid) + data = web.input(task=[]) + + if "task" in data: + # Change tasks order + for index, taskid in enumerate(data["task"]): + try: + task = self.task_factory.get_task_descriptor_content(courseid, taskid) + task["order"] = index + self.task_factory.update_task_descriptor_content(courseid, taskid, task) + except: + pass + + return self.page(course) + + def submission_url_generator(self, taskid): + """ Generates a submission url """ + return "?format=taskid%2Fusername&tasks=" + taskid + + def page(self, course): + """ Get all data and display the page """ + url = 'rubric_scoring_temp' + + data = list(self.database.user_tasks.aggregate( + [ + { + "$match": + { + "courseid": course.get_id(), + "username": {"$in": self.user_manager.get_course_registered_users(course, False)} + } + }, + { + "$group": + { + "_id": "$taskid", + "viewed": {"$sum": 1}, + "attempted": {"$sum": {"$cond": [{"$ne": ["$tried", 0]}, 1, 0]}}, + "attempts": {"$sum": "$tried"}, + "succeeded": {"$sum": {"$cond": ["$succeeded", 1, 0]}} + } + } + ])) + + # Load tasks and verify exceptions + files = self.task_factory.get_readable_tasks(course) + output = {} + errors = [] + for task in files: + try: + output[task] = course.get_task(task) + except Exception as inst: + errors.append({"taskid": task, "error": str(inst)}) + tasks = OrderedDict(sorted(list(output.items()), key=lambda t: (t[1].get_order(), t[1].get_id()))) + + # Now load additional informations + result = OrderedDict() + for taskid in tasks: + result[taskid] = {"name": tasks[taskid].get_name(), "viewed": 0, "attempted": 0, "attempts": 0, "succeeded": 0, + "url": self.submission_url_generator(taskid)} + + for entry in data: + if entry["_id"] in result: + result[entry["_id"]]["viewed"] = entry["viewed"] + result[entry["_id"]]["attempted"] = entry["attempted"] + result[entry["_id"]]["attempts"] = entry["attempts"] + result[entry["_id"]]["succeeded"] = entry["succeeded"] + + return self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).task_list(course, result, errors, url) + + +#Listado de submissions +class TaskListSubmissionPageTemp(INGIniousAdminPage): + def GET_AUTH(self, course_id, task_id): + + course, task = self.get_course_and_check_rights(course_id, task_id) + + + self.template_helper.add_javascript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.3.6/papaparse.min.js") + self.template_helper.add_javascript("https://cdn.plot.ly/plotly-1.30.0.min.js") + self.template_helper.add_javascript("https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js") + + return self.page(course, task_id, task) + + + def page(self, course, task_id, task ): + """ Get all data and display the page """ + #print(self.database.collection_names()) + print ("here", course.get_id(), task_id) + url = 'rubric_scoring_temp' + + result = list(self.database.submissions_rubric.aggregate( + + [ + { + "$match": + { + "courseid": course.get_id(), + "taskid": task_id, + "username": {"$in": self.user_manager.get_course_registered_users(course, False)}, + + } + }, + { + "$project": { + + "taskid": 1, + "veredict": 1, + "submitted_on": 1, + "username": 1, + "custom": 1 + + } + } + + ])) + + print("result", result) + data = OrderedDict() + + for entry in result: + data[entry["_id"]] = {"taskid": entry["taskid"], "result": entry["veredict"], "_id":entry["_id"], + "username":entry["username"], "date": entry["submitted_on"]} + + if "rubric_score" not in entry["custom"]: + data[entry["_id"]]["rubric_score"] = "not grade" + + else: + data[entry["_id"]]["rubric_score"] = entry["custom"]["rubric_score"] + + + #print ("data -> ",data) + return ( + self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).task_admin_rubric( + course,data, task, url) + ) + + + +class SubmissionRubricPageTemp(INGIniousAdminPage): + + client = MongoClient() + database = client['INGInious'] + fs = gridfs.GridFS(database) + + def POST_AUTH(self, course_id, task_id, submission_id): + """ POST request """ + course, task = self.get_course_and_check_rights(course_id, task_id) + data = web.input() + print ("data->",data) + self.database.submissions_rubric.update( + {"_id": ObjectId(submission_id)}, + {"$set": {"custom.rubric_score": data["grade"]} + }) + + return self.page(course, task, submission_id) + + def GET_AUTH(self, course_id, task_id, submission_id): + #print("-->", course_id, task_id, submission_id) + course, task = self.get_course_and_check_rights(course_id, task_id) + + self.template_helper.add_javascript("https://cdnjs.cloudflare.com/ajax/libs/PapaParse/4.3.6/papaparse.min.js") + self.template_helper.add_javascript("https://cdn.plot.ly/plotly-1.30.0.min.js") + self.template_helper.add_javascript("https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js") + self.template_helper.add_css("static/css/rubric_scoring.css") + + return self.page(course, task, submission_id) + + def get_submission(self, submissionid): + """ Get a submission from the database """ + sub = self.database.submissions_rubric.find_one({'_id': ObjectId(submissionid)}) + + return sub + + + + def page(self, course, task, submission_id): + #print (submission_id, type(submission_id)) + #print ("////", course) + + + # TODO: verificar que exista exactamente un elemento. TOMAR MEDIDAS PREVENTIVAS EN CASO CONTRARIO + problem_id = task.get_problems()[0].get_id() + + + + submission = self.get_submission( submission_id) + + #print ("submission ->>>>", submission) + + #print (submission) + submission_input = self.submission_manager.get_input_from_submission(submission) + print ("submission_code", submission_input) + print ("------------") + + language = submission_input['language'] + + + data = { + "url" : 'rubric_scoring_temp', + + "memory": submission_input['memory'], + "test_passed": submission_input['test_passed'], + "verdict" : submission_input['veredict'] + + } + + rubric_wdo = RubricWdo("/home/lina/Documents/Rubrica.xlsx") + + + + + return ( + self.template_helper.get_custom_renderer(_BASE_RENDERER_PATH).submission_rubric( + course, task, submission_input, problem_id, rubric_wdo, data, language) + ) + diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/rubric_wdo.py b/inginious/frontend/webapp/plugins/rubric_scoring/rubric_wdo.py new file mode 100644 index 00000000..bc5453b9 --- /dev/null +++ b/inginious/frontend/webapp/plugins/rubric_scoring/rubric_wdo.py @@ -0,0 +1,27 @@ +import pandas as pd + +class RubricWdo(): + + + def __init__(self, source): + if (source != None and len(source) > 0): + self.data = self.read_data(source) + else: + self.data = self.load_data() + + #read data from source + def read_data(self, source): + return pd.read_excel(source, index_col=0, header=0) + + def load_data(self): + self.grades = ["grade 1", "grade 2", "grade 3"] + self.categories = ["cat 1", "cat 2", "cat 3"] + data_matrix = [["1","2","3"],["4","5","6"],["7","8","9"]] + data = pd.DataFrame(data_matrix, columns=self.grades, index=self.categories) + + return data + + + + + diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/static/css/rubric_scoring.css b/inginious/frontend/webapp/plugins/rubric_scoring/static/css/rubric_scoring.css new file mode 100644 index 00000000..4ae89aa1 --- /dev/null +++ b/inginious/frontend/webapp/plugins/rubric_scoring/static/css/rubric_scoring.css @@ -0,0 +1,7 @@ +.table th.table_rubric +{ + padding: 0; + border: none; + + background-color: red; +} \ No newline at end of file diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/submission_rubric.html b/inginious/frontend/webapp/plugins/rubric_scoring/submission_rubric.html new file mode 100644 index 00000000..0796fb18 --- /dev/null +++ b/inginious/frontend/webapp/plugins/rubric_scoring/submission_rubric.html @@ -0,0 +1,258 @@ +$def with (course, task_id, submission_code, problem_id, rubrica, data, language) + +$var title: $:course.get_name() - Rubric Scoring + +$def NavbarF(): + + +$var Navbar: $:NavbarF() + + +

Rubric scoring for Task: $task_id.get_id()

+ + + + + +
+
+

Source code of the submission

+
+
+
+
+

+ $course.get_name() + +

+
+
+
+

print hi baker

+ +
+ + +
+
+ +
+ +
+ +
+ +
+ +
+ +

General Information

+ + + + + + + + + + + + + + + + + +
Author(s)$submission_code['username'][0]
Task + $task_id.get_id() +
Language + $language +
Submitted on $submission_code['submitted_on']
+
+
+
+
+ +

Information

+ + + + + + + + + + + + + + +
Memory$data['memory']
Test Casses + $data['test_passed'] +
Verdict + $data['verdict'] +
+
+ +
+ +
+ +

Scoring

+ + + +
+ + +
+ +
+ + + Grade + +
+
+
+ + +
+ + + + + +
+ +
+ + + + + $for header in rubrica.data.columns.values: + + + + + + $for index,row in rubrica.data.iterrows(): + + + $for zeld in rubrica.data.columns.values: + + + + + +
$header
$index $row[zeld]
+
+ + +
+ + +
+ + + + + + + + + diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/task_admin_rubric.html b/inginious/frontend/webapp/plugins/rubric_scoring/task_admin_rubric.html new file mode 100644 index 00000000..27579e4b --- /dev/null +++ b/inginious/frontend/webapp/plugins/rubric_scoring/task_admin_rubric.html @@ -0,0 +1,45 @@ +$def with (course, data, task, url) + +$var title: $:course.get_name() - Rubric Scoring + +$def NavbarF(): + + +$var Navbar: $:NavbarF() + +

Rubric scoring for Task: $task.get_id()

+
+ + + + + + + + + + + + + + $for taskid in data: + + + + + + + + + + +
# submission id# verdict# task# username# date# rubric scoring
+ $data[taskid]["_id"] + + $data[taskid]["result"]$data[taskid]["taskid"]$data[taskid]["username"][0]$data[taskid]["date"]$data[taskid]["rubric_score"]
+
+ + diff --git a/inginious/frontend/webapp/plugins/rubric_scoring/task_list.html b/inginious/frontend/webapp/plugins/rubric_scoring/task_list.html new file mode 100644 index 00000000..e27bf622 --- /dev/null +++ b/inginious/frontend/webapp/plugins/rubric_scoring/task_list.html @@ -0,0 +1,105 @@ +$def with (course,data,errors, url) + +$# +$# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for +$# more information about the licensing of this file. +$# + +$var title: $:course.get_name() + +$var Column: $:template_helper.call('course_admin_menu',course=course,current='tasks') +$ is_admin = user_manager.has_admin_rights_on_course(course) + +$def NavbarF(): + +$var Navbar: $:NavbarF() + +
+

Tasks +
+
+ + +
+
+

+ + + + + + + + + + + + + + + $for taskid in data: + + + + + + + + + +
task name# student viewed# student attempted# student succeeded# attempts +
+ + +
+
+ $data[taskid]["name"] + + $data[taskid]["viewed"]$data[taskid]["attempted"]$data[taskid]["succeeded"]$data[taskid]["attempts"] + +
+
+ + +$if len(errors) != 0: +

Errors while loading tasks

+ + + + + + $for task in errors: + + + + +
task iderror
$task['taskid']
$task['error']
+ + \ No newline at end of file diff --git a/inginious/frontend/webapp/static/css/INGInious.less b/inginious/frontend/webapp/static/css/INGInious.less index 89f1841c..5155c2ed 100644 --- a/inginious/frontend/webapp/static/css/INGInious.less +++ b/inginious/frontend/webapp/static/css/INGInious.less @@ -207,7 +207,7 @@ html, body min-height: 100%; height: auto !important; height: 100%; - margin: 0 auto -81px; + margin: 0 auto 0; } #footer, .footer_push