Skip to content

Commit

Permalink
[frontend] Categories managed by the Task Dispenser (#855)
Browse files Browse the repository at this point in the history
  • Loading branch information
maleclercq committed Jul 29, 2022
1 parent 4b64864 commit ae95c5e
Show file tree
Hide file tree
Showing 24 changed files with 165 additions and 100 deletions.
4 changes: 2 additions & 2 deletions inginious/frontend/pages/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def show_page(self, course):
course_grade = course.get_task_dispenser().get_course_grade(username)

# Get tag list
tag_list = course.get_tags()
categories = set(course.get_task_dispenser().get_all_categories())

# Get user info
user_info = self.user_manager.get_user_info(username)
Expand All @@ -91,4 +91,4 @@ def show_page(self, course):
submissions=last_submissions,
tasks_data=tasks_data,
grade=course_grade,
tag_filter_list=tag_list)
category_filter_list=categories)
6 changes: 3 additions & 3 deletions inginious/frontend/pages/course_admin/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def GET_AUTH(self, courseid): # pylint: disable=arguments-differ
user_input["users"] = flask.request.args.getlist("users")
user_input["audiences"] = flask.request.args.getlist("audiences")
user_input["tasks"] = flask.request.args.getlist("tasks")
user_input["org_tags"] = flask.request.args.getlist("org_tags")
user_input["org_categories"] = flask.request.args.getlist("org_categories")
params = self.get_input_params(user_input, course, 500)

return self.page(course, params)
Expand All @@ -175,7 +175,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
user_input["users"] = flask.request.form.getlist("users")
user_input["audiences"] = flask.request.form.getlist("audiences")
user_input["tasks"] = flask.request.form.getlist("tasks")
user_input["org_tags"] = flask.request.form.getlist("org_tags")
user_input["org_categories"] = flask.request.form.getlist("org_categories")
params = self.get_input_params(user_input, course, 500)

return self.page(course, params)
Expand All @@ -202,7 +202,7 @@ def page(self, course, params):
users, tutored_users, audiences, tutored_audiences, tasks, limit = self.get_course_params(course, params)

filter, best_submissions_list = self.get_submissions_filter(course, only_tasks=params["tasks"],
only_tasks_with_categories=params["org_tags"],
only_tasks_with_categories=params["org_categories"],
only_users=params["users"],
only_audiences=params["audiences"],
grade_between=[
Expand Down
6 changes: 3 additions & 3 deletions inginious/frontend/pages/course_admin/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def POST_AUTH(self, courseid): # pylint: disable=arguments-differ
user_input["users"] = flask.request.form.getlist("users")
user_input["audiences"] = flask.request.form.getlist("audiences")
user_input["tasks"] = flask.request.form.getlist("tasks")
user_input["org_tags"] = flask.request.form.getlist("org_tasks")
user_input["org_categories"] = flask.request.form.getlist("org_categories")

if "replay_submission" in user_input:
# Replay a unique submission
Expand Down Expand Up @@ -91,7 +91,7 @@ def GET_AUTH(self, courseid): # pylint: disable=arguments-differ
user_input["users"] = flask.request.args.getlist("users")
user_input["audiences"] = flask.request.args.getlist("audiences")
user_input["tasks"] = flask.request.args.getlist("tasks")
user_input["org_tags"] = flask.request.args.getlist("org_tasks")
user_input["org_categories"] = flask.request.args.getlist("org_categories")

if "download_submission" in user_input:
submission = self.database.submissions.find_one({"_id": ObjectId(user_input["download_submission"]),
Expand Down Expand Up @@ -144,7 +144,7 @@ def submissions_from_user_input(self, course, user_input, msgs, page=None, limit
skip = (page-1) * limit

return self.get_selected_submissions(course, only_tasks=user_input["tasks"],
only_tasks_with_categories=user_input["org_tags"],
only_tasks_with_categories=user_input["org_categories"],
only_users=user_input["users"],
only_audiences=user_input["audiences"],
grade_between=[
Expand Down
7 changes: 0 additions & 7 deletions inginious/frontend/pages/course_admin/task_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,6 @@ def POST_AUTH(self, courseid, taskid): # pylint: disable=arguments-differ
data["problems"] = OrderedDict([(key, self.parse_problem(val))
for key, val in sorted(iter(problems.items()), key=lambda x: int(x[1]['@order']))])

# Categories
course_tags = course.get_tags()
data['categories'] = [cat for cat in map(str.strip, data['categories'].split(',')) if cat]
for category in data['categories']:
if category not in course_tags:
return json.dumps({"status": "error", "message": _("Unknown category tag.")})

# Task environment parameters
data["environment_parameters"] = environment_parameters

Expand Down
7 changes: 3 additions & 4 deletions inginious/frontend/pages/course_admin/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,8 @@ def get_input_params(self, user_input, course, limit=50):
# Sanitise tags
if not user_input.get("tasks", []):
user_input["tasks"] = []
if len(user_input.get("org_tags", [])) == 1 and "," in user_input["org_tags"][0]:
user_input["org_tags"] = user_input["org_tags"][0].split(',')
user_input["org_tags"] = [org_tag for org_tag in user_input["org_tags"] if org_tag in course.get_tags()]
if len(user_input.get("org_categories", [])) == 1 and "," in user_input["org_categories"][0]:
user_input["org_categories"] = user_input["org_categories"][0].split(',')

# Sanitise grade
if "grade_min" in user_input:
Expand Down Expand Up @@ -186,7 +185,7 @@ def get_submissions_filter(self, course,
elif only_tasks_with_categories:
only_tasks_with_categories = set(only_tasks_with_categories)
more_tasks = {taskid for taskid, task in course.get_tasks().items() if
only_tasks_with_categories.intersection(task.get_categories())}
only_tasks_with_categories.intersection(course.get_task_dispenser().get_categories(taskid))}
if only_tasks:
self._validate_list(only_tasks)
more_tasks.intersection_update(only_tasks)
Expand Down
5 changes: 2 additions & 3 deletions inginious/frontend/pages/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,8 @@ def GET(self, courseid, taskid, is_LTI):

# Visible tags
course_tags = course.get_tags()
task_categories = task.get_categories()
visible_tags = [course_tags[category] for category in task_categories if
course_tags[category].is_visible_for_student() or self.user_manager.has_staff_rights_on_course(course)]
visible_tags = [tags for _,tags in course_tags.items() if
tags.is_visible_for_student() or self.user_manager.has_staff_rights_on_course(course)]

# Problem dict
pdict = {problem.get_id(): problem.get_type() for problem in task.get_problems()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,8 @@
</div>

<div id="tags_of_task" style="display:none;">
{% for category in task.get_categories() %}
{% set tag = tag_list[category] %}
{% if tag.is_visible_for_student() or user_manager.has_staff_rights_on_course(course) %}
<div id="tag" data-tag-name="{{tag.get_id()}}"></div>
{% endif %}
{% for category in course.get_task_dispenser().get_categories(task.get_id()) %}
<div id="tag" data-tag-name="{{ category }}"></div>
{% endfor %}
</div>
</a>
Expand Down
1 change: 1 addition & 0 deletions inginious/frontend/static/js/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,7 @@ function updateMainTags(data){
if(elem.attr('class') == "badge alert-danger"){
elem.show();
}else{
elem.show();
elem.attr('class', 'badge alert-success')
}
}
Expand Down
12 changes: 12 additions & 0 deletions inginious/frontend/static/js/task_dispensers.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ function dispenser_util_get_sections_list(element) {

structure["no_stored_submissions"] = dispenser_util_get_no_stored_submissions(tasks_id);
structure["evaluation_mode"] = dispenser_util_get_evaluation_mode(tasks_id);
structure["categories"] = dispenser_util_get_categories(tasks_id);
} else if ($(this).hasClass("sections_list")) {
structure["sections_list"] = dispenser_util_get_sections_list(content);
}
Expand Down Expand Up @@ -439,6 +440,17 @@ function dispenser_util_get_evaluation_mode(tasks_id){
return evaluation_mode;
}

function dispenser_util_get_categories(tasks_id){
const categories = {};
$(".categories").each(function(){
var taskid = this.id;
if(taskid in tasks_id && this.value !== ""){
categories[taskid] = this.value.split(",");
}
});
return categories;
}

function dispenser_util_get_tasks_list(element) {
const tasks_list = {};
element.children(".task").each(function (index) {
Expand Down
10 changes: 10 additions & 0 deletions inginious/frontend/task_dispensers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ def get_evaluation_mode(self, taskid):
"""Returns the evaluation mode specified by the administrator"""
pass

@abstractmethod
def get_categories(self,taskid):
"""Returns the categories specified for the taskid by the administrator"""
pass

@abstractmethod
def get_all_categories(self):
"""Returns the categories specified by the administrator"""
pass

@abstractmethod
def get_course_grade(self, username):
"""Returns the current grade of the course for a specific user"""
Expand Down
27 changes: 27 additions & 0 deletions inginious/frontend/task_dispensers/combinatory_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,33 @@ def get_evaluation_mode(self,taskid):
except:
return "best"

def get_categories(self,taskid):
"""Returns the categories specified for the taskid by the administrator"""
try:
struct = self._data.to_structure()
for elem in struct:
categories = self._data.get_value_rec(taskid,elem,"categories")
if categories is not None:
return categories
return []
except:
return []

def get_all_categories(self):
"""Returns the categories specified by the administrator"""
tasks = self._data.get_tasks()
all_categories = []
for task in tasks:
try:
struct = self._data.to_structure()
for elem in struct:
categories = self._data.get_value_rec(task,elem,"categories")
if categories is not None:
all_categories += categories
except:
return all_categories
return all_categories

def get_course_grade(self, username):
""" Returns the grade of a user for the current course"""
task_list = self.get_user_task_list([username])[username]
Expand Down
27 changes: 27 additions & 0 deletions inginious/frontend/task_dispensers/toc.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,33 @@ def get_evaluation_mode(self,taskid):
except:
return "best"

def get_categories(self,taskid):
"""Returns the categories specified for the taskid by the administrator"""
try:
struct = self._toc.to_structure()
for elem in struct:
categories = self._toc.get_value_rec(taskid,elem,"categories")
if categories is not None:
return categories
return []
except:
return []

def get_all_categories(self):
"""Returns the categories specified by the administrator"""
tasks = self._toc.get_tasks()
all_categories = []
for task in tasks:
try:
struct = self._toc.to_structure()
for elem in struct:
categories = self._toc.get_value_rec(task,elem,"categories")
if categories is not None:
all_categories += categories
except:
return all_categories
return all_categories

def get_course_grade(self, username):
""" Returns the grade of a user for the current course"""
task_list = self.get_user_task_list([username])[username]
Expand Down
26 changes: 16 additions & 10 deletions inginious/frontend/task_dispensers/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,30 +206,34 @@ def __init__(self, structure):
for taskid,weight in structure["weights"].items():
if not (type(weight) == float or type(weight) == int):
raise InvalidTocException( ("The weight value must be a numeric >= 0 for the task: " + str(taskid)) )
elif weight >= 0:
elif weight < 0:
raise InvalidTocException( ("The weight value must be a numeric >= 0 for the task: " + str(taskid)) )
else:
if taskid in structure['tasks_list']:
self._weights[taskid] = weight
else:
raise InvalidTocException( ("The weight value must be a numeric >= 0 for the task: " + str(taskid)) )

self._no_stored_submissions = {}
if "no_stored_submissions" in structure:
for taskid,no_stored_submissions in structure["no_stored_submissions"].items():
if not type(no_stored_submissions) == int:
raise InvalidTocException( ("The store submission must be an integer > 1 for the task: " + str(taskid)) )
elif no_stored_submissions >= 0:
if taskid in structure['tasks_list']:
self._no_stored_submissions = structure["no_stored_submissions"]
else:
elif no_stored_submissions < 0:
raise InvalidTocException( ("The store submission must be an integer > 1 for the task: " + str(taskid)) )
self._no_stored_submissions = structure["no_stored_submissions"]

self._evaluation_mode = {}
if "evaluation_mode" in structure:
for taskid,evaluation_mode in structure["evaluation_mode"].items():
if evaluation_mode != "best" and evaluation_mode != "last":
raise InvalidTocException( ("The evaluation mode must be either best or last for the task: '" + str(taskid)) +"' but is " + str(evaluation_mode) )
else:
self._evaluation_mode = structure["evaluation_mode"]
self._evaluation_mode = structure["evaluation_mode"]

self._categories = {}
if "categories" in structure:
for taskid,categorie in structure["categories"].items():
if "" in categorie:
raise InvalidTocException( ("The categorie must have a name for the task: '" + str(taskid)) +"' but is " + str(categorie) )
self._categories = structure["categories"]

def is_terminal(self):
return True
Expand Down Expand Up @@ -273,7 +277,9 @@ def to_structure(self, rank):
:return: The structure in YAML format
"""
return {"id": self._id, "rank": rank, "title": self._title,
"tasks_list": {taskid: rank for rank, taskid in enumerate(self._task_list)}, "weights": self._weights, "no_stored_submissions": self._no_stored_submissions, "evaluation_mode": self._evaluation_mode}
"tasks_list": {taskid: rank for rank, taskid in enumerate(self._task_list)},
"weights": self._weights, "no_stored_submissions": self._no_stored_submissions,
"evaluation_mode": self._evaluation_mode, "categories": self._categories }


def check_toc(toc):
Expand Down
7 changes: 0 additions & 7 deletions inginious/frontend/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,6 @@ def __init__(self, course, taskid, content, filesystem, plugin_manager, task_pro
# Regenerate input random
self._regenerate_input_random = bool(self._data.get("regenerate_input_random", False))

# Category tags
self._categories = self._data.get("categories", [])

def get_translation_obj(self, language):
return self._translations.get(language, gettext.NullTranslations())

Expand Down Expand Up @@ -239,10 +236,6 @@ def adapt_input_for_backend(self, input_data):
for problem in self._problems:
input_data = problem.adapt_input_for_backend(input_data)
return input_data

def get_categories(self):
""" Returns the tags id associated to the task """
return [category for category in self._categories if category in self._course.get_tags()]

def get_number_input_random(self):
""" Return the number of random inputs """
Expand Down
12 changes: 5 additions & 7 deletions inginious/frontend/templates/course.html
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,12 @@ <h3>{{ _("Enroll in the course") }}</h3>
<h2>{{ course.get_name(user_manager.session_language()) }}</h2>
</div>

{%if tag_filter_list %}
{%if category_filter_list %}
<div class="col-md-4 p-3">
<select class="form-control input-sm" style="height: 22px; padding: 2px 5px; font-size: 12px; line-height: 1.5;" id="tag_filter" onchange="filter_tag(this)">
<option value=""> {{ _("Filter tasks by tags") }}</option>
{% for key, tag in tag_filter_list.items() %}
{% if tag.get_type() in [0, 2] and tag.is_visible_for_student() %}
<option value="{{ tag.get_id() }}">{{ tag.get_name(user_manager.session_language()) }}</option>
{% endif %}
<option value=""> {{ _("Filter tasks by category") }}</option>
{% for category in category_filter_list %}
<option value="{{ category }}">{{ category }}</option>
{% endfor %}
</select>
</div>
Expand Down Expand Up @@ -175,7 +173,7 @@ <h2>{{ course.get_name(user_manager.session_language()) }}</h2>
}
</style>

{{ course.get_task_dispenser().render(template_helper, course, tasks_data, tag_filter_list) | safe }}
{{ course.get_task_dispenser().render(template_helper, course, tasks_data, category_filter_list) | safe }}

{% include "unregister_modal.html" %}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,6 @@
<input type="text" class="form-control" id="contact-url" name="contact_url" placeholder="{{_('Contact link')}}" value="{{task_data.get('contact_url','')}}"/>
</div>
</div>
<div class="form-group row">
<label for="categories" class="col-sm-2 control-label">{{_("Categories")}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="categories" name="categories" placeholder="{{ _('Tag ids, separated by commas') }}"
value="{{ task_data.get('categories', []) | join(',') }}">
</div>
</div>
<div class="form-group row">
<label for="groups" class="col-sm-2 control-label">{{_("Submission mode")}}</label>
<div class="col-sm-10">
Expand Down
2 changes: 0 additions & 2 deletions inginious/frontend/templates/course_admin/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,6 @@ <h2>{{_("Course settings")}}</h2>
<select disabled class="form-control" name="tags[NEW][type]">
<option value="0" TYPE_REPLACE_0>{{_("Skill")}}</option>
<option value="1" TYPE_REPLACE_1>{{_("Misconception")}}</option>
<option value="2" TYPE_REPLACE_2>{{_("Category")}}</option>
</select>
</td>
</tr>
Expand All @@ -420,7 +419,6 @@ <h2>{{_("Course settings")}}</h2>
<select class="form-control" name="tags[{{loop.index}}][type]">
<option value="0" {% if type|int == 0 %} selected="selected" {% endif %}>{{_("Skill")}}</option>
<option value="1" {% if type|int == 1 %} selected="selected" {% endif %}>{{_("Misconception")}}</option>
<option value="2" {% if type|int == 2 %} selected="selected" {% endif %}>{{_("Category")}}</option>
</select>
</td>
</tr>
Expand Down

0 comments on commit ae95c5e

Please sign in to comment.