From 8b95f40cb626a7be629ee6da6e37ea389ceb379d Mon Sep 17 00:00:00 2001 From: prathamesh Date: Fri, 27 Mar 2020 13:03:36 +0530 Subject: Add tests for R evaluator and codemirror R mode --- yaksh/evaluator_tests/test_r_evaluation.py | 200 +++++++++++++++++++++ yaksh/r_code_evaluator.py | 5 +- yaksh/static/yaksh/js/codemirror/mode/r/index.html | 85 +++++++++ yaksh/static/yaksh/js/codemirror/mode/r/r.js | 164 +++++++++++++++++ yaksh/static/yaksh/js/requesthandler.js | 3 +- yaksh/templates/yaksh/question.html | 1 + 6 files changed, 455 insertions(+), 3 deletions(-) create mode 100644 yaksh/evaluator_tests/test_r_evaluation.py create mode 100644 yaksh/static/yaksh/js/codemirror/mode/r/index.html create mode 100644 yaksh/static/yaksh/js/codemirror/mode/r/r.js (limited to 'yaksh') diff --git a/yaksh/evaluator_tests/test_r_evaluation.py b/yaksh/evaluator_tests/test_r_evaluation.py new file mode 100644 index 0000000..d4272a5 --- /dev/null +++ b/yaksh/evaluator_tests/test_r_evaluation.py @@ -0,0 +1,200 @@ +from __future__ import unicode_literals +import unittest +from textwrap import dedent +import os +import tempfile +import shutil +from psutil import Process + +from yaksh.grader import Grader +from yaksh.settings import SERVER_TIMEOUT +from yaksh.evaluator_tests.test_python_evaluation import EvaluatorBaseTest + + +class RAssertionEvaluationTestCase(EvaluatorBaseTest): + def setUp(self): + self.tmp_file = os.path.join(tempfile.gettempdir(), 'test.txt') + with open(self.tmp_file, 'wb') as f: + f.write('2'.encode('ascii')) + tmp_in_dir_path = tempfile.mkdtemp() + self.in_dir = tmp_in_dir_path + self.test_case = dedent( + ''' + source("function.r") + check_empty = function(obj){ + stopifnot(is.null(obj) == FALSE) + } + check = function(input, output){ + stopifnot(input == output) + } + is_correct = function(){ + if (count == 3){ + quit("no", 31) + } + } + check_empty(odd_or_even(3)) + check(odd_or_even(6), "EVEN") + check(odd_or_even(1), "ODD") + check(odd_or_even(10), "EVEN") + check(odd_or_even(777), "ODD") + check(odd_or_even(778), "EVEN") + count = 3 + is_correct() + ''' + ) + self.test_case_data = [{"test_case": self.test_case, + "test_case_type": "standardtestcase", + "weight": 0.0 + }] + self.timeout_msg = ("Code took more than {0} seconds to run. " + "You probably have an infinite loop in" + " your code.").format(SERVER_TIMEOUT) + self.file_paths = None + + def tearDown(self): + os.remove(self.tmp_file) + shutil.rmtree(self.in_dir) + + def test_correct_answer(self): + # Given + user_answer = dedent( + ''' + odd_or_even <- function(n){ + if(n %% 2 == 0){ + return("EVEN") + } + return("ODD") + } + ''' + ) + kwargs = {'metadata': { + 'user_answer': user_answer, + 'file_paths': self.file_paths, + 'partial_grading': False, + 'language': 'r'}, + 'test_case_data': self.test_case_data, + } + + # When + grader = Grader(self.in_dir) + result = grader.evaluate(kwargs) + + # Then + self.assertTrue(result.get('success')) + + def test_incorrect_answer(self): + # Given + user_answer = dedent( + ''' + odd_or_even <- function(n){ + if(n %% 2 == 0){ + return("ODD") + } + return("EVEN") + } + ''' + ) + err = ['Error: input == output is not TRUE\nExecution halted\n'] + kwargs = { + 'metadata': { + 'user_answer': user_answer, + 'file_paths': self.file_paths, + 'partial_grading': False, + 'language': 'r' + }, 'test_case_data': self.test_case_data, + } + + # When + grader = Grader(self.in_dir) + result = grader.evaluate(kwargs) + errors = result.get('error') + # Then + self.assertFalse(result.get('success')) + self.assertEqual(errors, err) + + def test_error_code(self): + # Given + user_answer = dedent( + ''' + odd_or_even <- function(n){ + a + } + ''' + ) + kwargs = { + 'metadata': { + 'user_answer': user_answer, + 'file_paths': self.file_paths, + 'partial_grading': False, + 'language': 'r' + }, 'test_case_data': self.test_case_data, + } + + # When + grader = Grader(self.in_dir) + result = grader.evaluate(kwargs) + errors = result.get('error') + + # Then + self.assertFalse(result.get("success")) + self.assertIn("object 'a' not found", errors[0]) + + def test_empty_function(self): + # Given + user_answer = dedent( + ''' + odd_or_even <- function(n){ + } + ''' + ) + kwargs = { + 'metadata': { + 'user_answer': user_answer, + 'file_paths': self.file_paths, + 'partial_grading': False, + 'language': 'r' + }, 'test_case_data': self.test_case_data, + } + + # When + grader = Grader(self.in_dir) + result = grader.evaluate(kwargs) + errors = result.get('error') + + # Then + self.assertFalse(result.get("success")) + self.assertIn("Error: is.null(obj) == FALSE is not TRUE", errors[0]) + + def test_infinite_loop(self): + # Given + user_answer = dedent( + ''' + odd_or_even <- function(n){ + while(0 == 0){ + a <- 1 + } + } + ''' + ) + kwargs = { + 'metadata': { + 'user_answer': user_answer, + 'file_paths': self.file_paths, + 'partial_grading': False, + 'language': 'r' + }, 'test_case_data': self.test_case_data, + } + + # When + grader = Grader(self.in_dir) + result = grader.evaluate(kwargs) + + # Then + self.assertFalse(result.get("success")) + self.assert_correct_output(self.timeout_msg, + result.get("error")[0]["message"] + ) + parent_proc = Process(os.getpid()).children() + if parent_proc: + children_procs = Process(parent_proc[0].pid) + self.assertFalse(any(children_procs.children(recursive=True))) diff --git a/yaksh/r_code_evaluator.py b/yaksh/r_code_evaluator.py index ca4c94a..11bc970 100644 --- a/yaksh/r_code_evaluator.py +++ b/yaksh/r_code_evaluator.py @@ -49,7 +49,7 @@ class RCodeEvaluator(BaseEvaluator): # Throw message if there are commmands that terminates scilab add_err = "" if terminate_commands: - add_err = "Please do not use quit() in your\ + add_err = "Please do not use quit() q() in your\ code.\n Otherwise your code will not be evaluated\ correctly.\n" @@ -79,7 +79,8 @@ class RCodeEvaluator(BaseEvaluator): new_string = "" terminate_commands = False for line in string.splitlines(): - new_line = re.sub(r"quit.*$", "", line) + new_line = re.sub(r'quit(.*$)', "", line) + new_line = re.sub(r'q(.*$)', "", new_line) if line != new_line: terminate_commands = True new_string = new_string + '\n' + new_line diff --git a/yaksh/static/yaksh/js/codemirror/mode/r/index.html b/yaksh/static/yaksh/js/codemirror/mode/r/index.html new file mode 100644 index 0000000..6dd9634 --- /dev/null +++ b/yaksh/static/yaksh/js/codemirror/mode/r/index.html @@ -0,0 +1,85 @@ + + +CodeMirror: R mode + + + + + + + + + +
+

R mode

+
+ + +

MIME types defined: text/x-rsrc.

+ +

Development of the CodeMirror R mode was kindly sponsored + by Ubalo.

+ +
diff --git a/yaksh/static/yaksh/js/codemirror/mode/r/r.js b/yaksh/static/yaksh/js/codemirror/mode/r/r.js new file mode 100644 index 0000000..d41d1c5 --- /dev/null +++ b/yaksh/static/yaksh/js/codemirror/mode/r/r.js @@ -0,0 +1,164 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.registerHelper("wordChars", "r", /[\w.]/); + +CodeMirror.defineMode("r", function(config) { + function wordObj(str) { + var words = str.split(" "), res = {}; + for (var i = 0; i < words.length; ++i) res[words[i]] = true; + return res; + } + var atoms = wordObj("NULL NA Inf NaN NA_integer_ NA_real_ NA_complex_ NA_character_"); + var builtins = wordObj("list quote bquote eval return call parse deparse"); + var keywords = wordObj("if else repeat while function for in next break"); + var blockkeywords = wordObj("if else repeat while function for"); + var opChars = /[+\-*\/^<>=!&|~$:]/; + var curPunc; + + function tokenBase(stream, state) { + curPunc = null; + var ch = stream.next(); + if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } else if (ch == "0" && stream.eat("x")) { + stream.eatWhile(/[\da-f]/i); + return "number"; + } else if (ch == "." && stream.eat(/\d/)) { + stream.match(/\d*(?:e[+\-]?\d+)?/); + return "number"; + } else if (/\d/.test(ch)) { + stream.match(/\d*(?:\.\d+)?(?:e[+\-]\d+)?L?/); + return "number"; + } else if (ch == "'" || ch == '"') { + state.tokenize = tokenString(ch); + return "string"; + } else if (ch == "." && stream.match(/.[.\d]+/)) { + return "keyword"; + } else if (/[\w\.]/.test(ch) && ch != "_") { + stream.eatWhile(/[\w\.]/); + var word = stream.current(); + if (atoms.propertyIsEnumerable(word)) return "atom"; + if (keywords.propertyIsEnumerable(word)) { + // Block keywords start new blocks, except 'else if', which only starts + // one new block for the 'if', no block for the 'else'. + if (blockkeywords.propertyIsEnumerable(word) && + !stream.match(/\s*if(\s+|$)/, false)) + curPunc = "block"; + return "keyword"; + } + if (builtins.propertyIsEnumerable(word)) return "builtin"; + return "variable"; + } else if (ch == "%") { + if (stream.skipTo("%")) stream.next(); + return "variable-2"; + } else if (ch == "<" && stream.eat("-")) { + return "arrow"; + } else if (ch == "=" && state.ctx.argList) { + return "arg-is"; + } else if (opChars.test(ch)) { + if (ch == "$") return "dollar"; + stream.eatWhile(opChars); + return "operator"; + } else if (/[\(\){}\[\];]/.test(ch)) { + curPunc = ch; + if (ch == ";") return "semi"; + return null; + } else { + return null; + } + } + + function tokenString(quote) { + return function(stream, state) { + if (stream.eat("\\")) { + var ch = stream.next(); + if (ch == "x") stream.match(/^[a-f0-9]{2}/i); + else if ((ch == "u" || ch == "U") && stream.eat("{") && stream.skipTo("}")) stream.next(); + else if (ch == "u") stream.match(/^[a-f0-9]{4}/i); + else if (ch == "U") stream.match(/^[a-f0-9]{8}/i); + else if (/[0-7]/.test(ch)) stream.match(/^[0-7]{1,2}/); + return "string-2"; + } else { + var next; + while ((next = stream.next()) != null) { + if (next == quote) { state.tokenize = tokenBase; break; } + if (next == "\\") { stream.backUp(1); break; } + } + return "string"; + } + }; + } + + function push(state, type, stream) { + state.ctx = {type: type, + indent: state.indent, + align: null, + column: stream.column(), + prev: state.ctx}; + } + function pop(state) { + state.indent = state.ctx.indent; + state.ctx = state.ctx.prev; + } + + return { + startState: function() { + return {tokenize: tokenBase, + ctx: {type: "top", + indent: -config.indentUnit, + align: false}, + indent: 0, + afterIdent: false}; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (state.ctx.align == null) state.ctx.align = false; + state.indent = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + if (style != "comment" && state.ctx.align == null) state.ctx.align = true; + + var ctype = state.ctx.type; + if ((curPunc == ";" || curPunc == "{" || curPunc == "}") && ctype == "block") pop(state); + if (curPunc == "{") push(state, "}", stream); + else if (curPunc == "(") { + push(state, ")", stream); + if (state.afterIdent) state.ctx.argList = true; + } + else if (curPunc == "[") push(state, "]", stream); + else if (curPunc == "block") push(state, "block", stream); + else if (curPunc == ctype) pop(state); + state.afterIdent = style == "variable" || style == "keyword"; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), ctx = state.ctx, + closing = firstChar == ctx.type; + if (ctx.type == "block") return ctx.indent + (firstChar == "{" ? 0 : config.indentUnit); + else if (ctx.align) return ctx.column + (closing ? 0 : 1); + else return ctx.indent + (closing ? 0 : config.indentUnit); + }, + + lineComment: "#" + }; +}); + +CodeMirror.defineMIME("text/x-rsrc", "r"); + +}); diff --git a/yaksh/static/yaksh/js/requesthandler.js b/yaksh/static/yaksh/js/requesthandler.js index 7ccdef0..80b67fb 100644 --- a/yaksh/static/yaksh/js/requesthandler.js +++ b/yaksh/static/yaksh/js/requesthandler.js @@ -160,7 +160,8 @@ $(document).ready(function(){ 'cpp': 'text/x-c++src', 'java': 'text/x-java', 'bash': 'text/x-sh', - 'scilab': 'text/x-csrc' + 'scilab': 'text/x-csrc', + 'r':'text/x-rsrc', } // Code mirror Options diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html index 74343f8..92d591f 100644 --- a/yaksh/templates/yaksh/question.html +++ b/yaksh/templates/yaksh/question.html @@ -26,6 +26,7 @@ + {% endblock %} -{% block css %} - -{% endblock %} {% block content %}
diff --git a/yaksh/templates/yaksh/design_course_session.html b/yaksh/templates/yaksh/design_course_session.html index a15f4b1..88ecc16 100644 --- a/yaksh/templates/yaksh/design_course_session.html +++ b/yaksh/templates/yaksh/design_course_session.html @@ -68,16 +68,14 @@ Check Prerequisite Completion
- - What's This + What's This  Check Prerequisite Passing
- - What's This - + What's This  + diff --git a/yaksh/views.py b/yaksh/views.py index 9e81b2d..835e98a 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -2688,6 +2688,7 @@ def design_module(request, module_id, course_id=None): context['status'] = 'design' context['module_id'] = module_id context['course_id'] = course_id + context['module'] = learning_module return my_render_to_response(request, 'yaksh/add_module.html', context) -- cgit From 541f3ec154f7c484a2a9105565117cefa4e15b18 Mon Sep 17 00:00:00 2001 From: adityacp Date: Tue, 31 Mar 2020 14:46:19 +0530 Subject: Update the units and modules order on creation --- yaksh/models.py | 9 +++++++++ yaksh/test_views.py | 6 ++++++ yaksh/views.py | 23 +++++++++++++++++++---- 3 files changed, 34 insertions(+), 4 deletions(-) (limited to 'yaksh') diff --git a/yaksh/models.py b/yaksh/models.py index 12c902b..52a0414 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -841,6 +841,15 @@ class LearningModule(models.Model): write_templates_to_zip(zip_file, module_file_path, module_data, module_name, folder_name) + def get_unit_order(self, type, unit): + if type == "lesson": + order = self.get_learning_units().get( + type=type, lesson=unit).order + else: + order = self.get_learning_units().get( + type=type, quiz=unit).order + return order + def __str__(self): return self.name diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 6f28add..8f811c5 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -1103,6 +1103,12 @@ class TestAddQuiz(TestCase): is_exercise=True, description='demo exercise', creator=self.user ) + unit1 = LearningUnit.objects.create( + type="quiz", quiz=self.quiz, order=1) + unit2 = LearningUnit.objects.create( + type="quiz", quiz=self.exercise, order=2) + self.module.learning_unit.add(*[unit1.id, unit2.id]) + def tearDown(self): self.client.logout() self.user.delete() diff --git a/yaksh/views.py b/yaksh/views.py index 835e98a..2cd09ac 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -343,10 +343,14 @@ def add_quiz(request, course_id=None, module_id=None, quiz_id=None): form = QuizForm(request.POST, instance=quiz) if form.is_valid(): if quiz is None: + last_unit = module.get_learning_units().last() + order = last_unit.order + 1 if last_unit else 1 form.instance.creator = user + else: + order = module.get_unit_order("quiz", quiz) added_quiz = form.save() unit, created = LearningUnit.objects.get_or_create( - type="quiz", quiz=added_quiz, order=1 + type="quiz", quiz=added_quiz, order=order ) module.learning_unit.add(unit.id) messages.success(request, "Quiz saved successfully") @@ -386,7 +390,11 @@ def add_exercise(request, course_id=None, module_id=None, quiz_id=None): form = ExerciseForm(request.POST, instance=quiz) if form.is_valid(): if quiz is None: + last_unit = module.get_learning_units().last() + order = last_unit.order + 1 if last_unit else 1 form.instance.creator = user + else: + order = module.get_unit_order("quiz", quiz) quiz = form.save(commit=False) quiz.is_exercise = True quiz.time_between_attempts = 0 @@ -397,7 +405,7 @@ def add_exercise(request, course_id=None, module_id=None, quiz_id=None): quiz.pass_criteria = 0 quiz.save() unit, created = LearningUnit.objects.get_or_create( - type="quiz", quiz=quiz, order=1 + type="quiz", quiz=quiz, order=order ) module.learning_unit.add(unit.id) messages.success( @@ -2495,7 +2503,11 @@ def edit_lesson(request, course_id=None, module_id=None, lesson_id=None): lesson.remove_file() if lesson_form.is_valid(): if lesson is None: + last_unit = module.get_learning_units().last() + order = last_unit.order + 1 if last_unit else 1 lesson_form.instance.creator = user + else: + order = module.get_unit_order("lesson", lesson) lesson = lesson_form.save() lesson.html_data = get_html_text(lesson.description) lesson.save() @@ -2505,7 +2517,7 @@ def edit_lesson(request, course_id=None, module_id=None, lesson_id=None): lesson=lesson, file=les_file ) unit, created = LearningUnit.objects.get_or_create( - type="lesson", lesson=lesson, order=1 + type="lesson", lesson=lesson, order=order ) module.learning_unit.add(unit.id) messages.success( @@ -2622,7 +2634,7 @@ def design_module(request, module_id, course_id=None): order=order, quiz_id=learning_id, type=type) else: - unit = LearningUnit.objects.get_or_create( + unit, status = LearningUnit.objects.get_or_create( order=order, lesson_id=learning_id, type=type) to_add_list.append(unit) @@ -2714,7 +2726,10 @@ def add_module(request, course_id=None, module_id=None): module_form = LearningModuleForm(request.POST, instance=module) if module_form.is_valid(): if module is None: + last_module = course.get_learning_modules().last() module_form.instance.creator = user + if last_module: + module_form.instance.order = last_module.order + 1 module = module_form.save() module.html_data = get_html_text(module.description) module.save() -- cgit From e6cce5b0ca4d616b7ff55c66f5d94376974fcc4c Mon Sep 17 00:00:00 2001 From: adityacp Date: Tue, 31 Mar 2020 15:51:15 +0530 Subject: Change course_modules.html and views.py - Get distinct courses in student dashboard - Add condition to check if question paper exists for a quiz in course modules --- yaksh/templates/yaksh/course_modules.html | 8 +++++--- yaksh/views.py | 15 +++++++-------- 2 files changed, 12 insertions(+), 11 deletions(-) (limited to 'yaksh') diff --git a/yaksh/templates/yaksh/course_modules.html b/yaksh/templates/yaksh/course_modules.html index 214f8c7..dd7b68d 100644 --- a/yaksh/templates/yaksh/course_modules.html +++ b/yaksh/templates/yaksh/course_modules.html @@ -128,9 +128,11 @@ View {% else %} - - View - + {% if unit.quiz.questionpaper_set.get %} + + View + + {% endif %} {% endif %} diff --git a/yaksh/views.py b/yaksh/views.py index 2cd09ac..c72c4ff 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -176,15 +176,14 @@ def quizlist_user(request, enrolled=None, msg=None): courses = hidden_courses title = 'Search Results' else: - courses = list(Course.objects.filter( - active=True, is_trial=False, + enrolled_courses = user.students.filter(is_trial=False).order_by('-id') + remaining_courses = list(Course.objects.filter( + active=True, is_trial=False, hidden=False ).exclude( - ~Q(requests=user), ~Q(rejected=user), hidden=True - ).order_by('-id')) - enrolled_course = list( - user.students.filter(is_trial=False).order_by('-id') - ) - courses.extend(enrolled_course) + id__in=enrolled_courses.values_list("id", flat=True) + ).order_by('-id')) + courses = list(enrolled_courses) + courses.extend(remaining_courses) title = 'All Courses' for course in courses: -- cgit From 1efa4389e9d6e57315441d190da324101fd0c463 Mon Sep 17 00:00:00 2001 From: adityacp Date: Wed, 1 Apr 2020 09:03:23 +0530 Subject: Fix custom middleware according to django 3 --- yaksh/middleware/one_session_per_user.py | 15 +++++++-------- yaksh/middleware/user_time_zone.py | 11 ++++++++--- 2 files changed, 15 insertions(+), 11 deletions(-) (limited to 'yaksh') diff --git a/yaksh/middleware/one_session_per_user.py b/yaksh/middleware/one_session_per_user.py index 3b8d302..114c92b 100644 --- a/yaksh/middleware/one_session_per_user.py +++ b/yaksh/middleware/one_session_per_user.py @@ -25,14 +25,8 @@ class OneSessionPerUserMiddleware(object): self.get_response = get_response def __call__(self, request): - return self.get_response(request) - - def process_request(self, request): - """ - # Documentation: - # https://docs.djangoproject.com/en/1.5/topics/auth/customizing/ - #extending-the-existing-user-model - """ + # Code to be executed for each request before + # the view (and later middleware) are called. if isinstance(request.user, User): current_key = request.session.session_key if hasattr(request.user, 'concurrentuser'): @@ -46,3 +40,8 @@ class OneSessionPerUserMiddleware(object): concurrent_user=request.user, session_key=current_key, ) + + response = self.get_response(request) + # Code to be executed for each request/response after + # the view is called. + return response diff --git a/yaksh/middleware/user_time_zone.py b/yaksh/middleware/user_time_zone.py index 92035e8..8140851 100644 --- a/yaksh/middleware/user_time_zone.py +++ b/yaksh/middleware/user_time_zone.py @@ -12,12 +12,17 @@ class TimezoneMiddleware(object): self.get_response = get_response def __call__(self, request): - return self.get_response(request) - - def process_request(self, request): + # Code to be executed for each request before + # the view (and later middleware) are called. user = request.user user_tz = 'Asia/Kolkata' if hasattr(user, 'profile'): if user.profile.timezone: user_tz = user.profile.timezone timezone.activate(pytz.timezone(user_tz)) + + response = self.get_response(request) + + # Code to be executed for each request/response after + # the view is called. + return response -- cgit From 957222b17cd80859158df06ed4c8f2d3b83f2873 Mon Sep 17 00:00:00 2001 From: adityacp Date: Thu, 2 Apr 2020 11:05:27 +0530 Subject: Optimize code to add unit to module only on creation --- yaksh/views.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'yaksh') diff --git a/yaksh/views.py b/yaksh/views.py index c72c4ff..51f6e54 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -351,7 +351,8 @@ def add_quiz(request, course_id=None, module_id=None, quiz_id=None): unit, created = LearningUnit.objects.get_or_create( type="quiz", quiz=added_quiz, order=order ) - module.learning_unit.add(unit.id) + if created: + module.learning_unit.add(unit.id) messages.success(request, "Quiz saved successfully") return redirect( reverse("yaksh:edit_quiz", @@ -406,7 +407,8 @@ def add_exercise(request, course_id=None, module_id=None, quiz_id=None): unit, created = LearningUnit.objects.get_or_create( type="quiz", quiz=quiz, order=order ) - module.learning_unit.add(unit.id) + if created: + module.learning_unit.add(unit.id) messages.success( request, "{0} saved successfully".format(quiz.description) ) @@ -2518,7 +2520,8 @@ def edit_lesson(request, course_id=None, module_id=None, lesson_id=None): unit, created = LearningUnit.objects.get_or_create( type="lesson", lesson=lesson, order=order ) - module.learning_unit.add(unit.id) + if created: + module.learning_unit.add(unit.id) messages.success( request, "Saved {0} successfully".format(lesson.name) ) -- cgit From 2e360f7d5772c3059a42db8915530cde952a01c6 Mon Sep 17 00:00:00 2001 From: prathamesh Date: Thu, 2 Apr 2020 16:33:20 +0530 Subject: Show prettified R error messages and fix filter R language available in filter form. Values taken from models. R messages are prettified like python assertion messages. Text up to first colon in the R error message removed, as it is simply a filename or unwanted text. --- yaksh/evaluator_tests/test_r_evaluation.py | 9 +++++---- yaksh/forms.py | 24 +++--------------------- yaksh/r_code_evaluator.py | 14 ++++++++------ 3 files changed, 16 insertions(+), 31 deletions(-) (limited to 'yaksh') diff --git a/yaksh/evaluator_tests/test_r_evaluation.py b/yaksh/evaluator_tests/test_r_evaluation.py index b161dc9..b4b81ae 100644 --- a/yaksh/evaluator_tests/test_r_evaluation.py +++ b/yaksh/evaluator_tests/test_r_evaluation.py @@ -94,7 +94,7 @@ class RAssertionEvaluationTestCase(EvaluatorBaseTest): } ''' ) - err = ['Error: input == output is not TRUE\nExecution halted\n'] + err = 'input == output is not TRUE\nExecution halted\n' kwargs = {'metadata': { 'user_answer': user_answer, 'file_paths': self.file_paths, @@ -109,7 +109,7 @@ class RAssertionEvaluationTestCase(EvaluatorBaseTest): errors = result.get('error') # Then self.assertFalse(result.get('success')) - self.assertEqual(errors, err) + self.assertEqual(errors[0]['message'], err) def test_error_code(self): # Given @@ -135,7 +135,7 @@ class RAssertionEvaluationTestCase(EvaluatorBaseTest): # Then self.assertFalse(result.get("success")) - self.assertIn("object 'a' not found", errors[0]) + self.assertIn("object 'a' not found", errors[0]['message']) def test_empty_function(self): # Given @@ -160,7 +160,8 @@ class RAssertionEvaluationTestCase(EvaluatorBaseTest): # Then self.assertFalse(result.get("success")) - self.assertIn("Error: is.null(obj) == FALSE is not TRUE", errors[0]) + err = errors[0]['message'] + self.assertIn("is.null(obj) == FALSE is not TRUE", err) def test_infinite_loop(self): # Given diff --git a/yaksh/forms.py b/yaksh/forms.py index c0f40ea..52ef75d 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -1,7 +1,7 @@ from django import forms from yaksh.models import ( get_model_class, Profile, Quiz, Question, Course, QuestionPaper, Lesson, - LearningModule, TestCase + LearningModule, TestCase, languages, question_types ) from grades.models import GradingSystem from django.contrib.auth import authenticate @@ -17,27 +17,9 @@ from string import punctuation, digits import pytz from .send_emails import generate_activation_key -languages = ( - ("select", "Select Language"), - ("python", "Python"), - ("bash", "Bash"), - ("c", "C Language"), - ("cpp", "C++ Language"), - ("java", "Java Language"), - ("scilab", "Scilab"), -) +languages = (("select", "Select Language"),) + languages -question_types = ( - ("select", "Select Question Type"), - ("mcq", "Multiple Choice"), - ("mcc", "Multiple Correct Choices"), - ("code", "Code"), - ("upload", "Assignment Upload"), - ("integer", "Answer in Integer"), - ("string", "Answer in String"), - ("float", "Answer in Float"), - ("arrange", "Arrange in Correct Order"), -) +question_types = (("select", "Select Question Type"),) + question_types test_case_types = ( ("standardtestcase", "Standard Testcase"), diff --git a/yaksh/r_code_evaluator.py b/yaksh/r_code_evaluator.py index 11bc970..8eaeb38 100644 --- a/yaksh/r_code_evaluator.py +++ b/yaksh/r_code_evaluator.py @@ -7,6 +7,7 @@ import re # Local imports from .base_evaluator import BaseEvaluator from .file_utils import copy_files, delete_files +from .error_messages import prettify_exceptions class RCodeEvaluator(BaseEvaluator): @@ -49,9 +50,8 @@ class RCodeEvaluator(BaseEvaluator): # Throw message if there are commmands that terminates scilab add_err = "" if terminate_commands: - add_err = "Please do not use quit() q() in your\ - code.\n Otherwise your code will not be evaluated\ - correctly.\n" + add_err = "Please do not use quit() q() in your code.\ + \n Otherwise your code will not be evaluated.\n" cmd = 'Rscript main.r' ret = self._run_command(cmd, shell=True, stdout=subprocess.PIPE, @@ -66,10 +66,12 @@ class RCodeEvaluator(BaseEvaluator): success, err = True, None mark_fraction = 1.0 if self.partial_grading else 0.0 else: - err = add_err + stdout + err = stdout + add_err else: - err = add_err + stderr - + err = stderr + add_err + if err: + err = re.sub(r'.*?: ', '', err, count=1) + err = prettify_exceptions('Error', err) return success, err, mark_fraction def _remove_r_quit(self, string): -- cgit From fbeb3428426691c29d4dd3f38b2245f21b5673ef Mon Sep 17 00:00:00 2001 From: ankitjavalkar Date: Fri, 3 Apr 2020 11:11:41 +0530 Subject: Minor fixes based on feedback --- yaksh/templates/yaksh/add_question.html | 14 +------------- yaksh/templates/yaksh/showquestions.html | 2 +- 2 files changed, 2 insertions(+), 14 deletions(-) (limited to 'yaksh') diff --git a/yaksh/templates/yaksh/add_question.html b/yaksh/templates/yaksh/add_question.html index 342e8ae..0c846d0 100644 --- a/yaksh/templates/yaksh/add_question.html +++ b/yaksh/templates/yaksh/add_question.html @@ -74,18 +74,6 @@
- {% if question %} - Add Test Case: - - - - - {% endif %} {% if uploaded_files %}
@@ -157,7 +145,7 @@
- + Details 
diff --git a/yaksh/templates/yaksh/showquestions.html b/yaksh/templates/yaksh/showquestions.html index fdfcc60..e0cd529 100644 --- a/yaksh/templates/yaksh/showquestions.html +++ b/yaksh/templates/yaksh/showquestions.html @@ -122,7 +122,7 @@ - + -- cgit From 0291f6171fd2ba8cee681d1a9d1f8436c8680c58 Mon Sep 17 00:00:00 2001 From: adityacp Date: Sat, 4 Apr 2020 14:34:22 +0530 Subject: Update documentation with latest changes --- yaksh/documentation/conf.py | 6 +- yaksh/documentation/images/add_exercise.jpg | Bin 56803 -> 28485 bytes yaksh/documentation/images/add_lesson.jpg | Bin 74708 -> 39153 bytes yaksh/documentation/images/add_module.jpg | Bin 0 -> 31048 bytes yaksh/documentation/images/add_question.jpg | Bin 72155 -> 63387 bytes yaksh/documentation/images/add_quiz.jpg | Bin 110571 -> 73655 bytes .../images/bash_standard_testcase.jpg | Bin 25121 -> 20440 bytes .../images/course_details_features.jpg | Bin 104513 -> 46197 bytes yaksh/documentation/images/course_features.jpg | Bin 85643 -> 41292 bytes yaksh/documentation/images/course_modules.jpg | Bin 0 -> 57662 bytes .../documentation/images/cpp_standard_testcase.jpg | Bin 35558 -> 36713 bytes yaksh/documentation/images/create_course.jpg | Bin 83748 -> 48943 bytes yaksh/documentation/images/design_course.jpg | Bin 68198 -> 75429 bytes yaksh/documentation/images/design_module.jpg | Bin 75652 -> 55246 bytes .../documentation/images/design_questionpaper.jpg | Bin 89567 -> 62444 bytes yaksh/documentation/images/embed_video.jpg | Bin 15227 -> 11253 bytes yaksh/documentation/images/float_testcase.jpg | Bin 63320 -> 15697 bytes yaksh/documentation/images/hook_testcase.jpg | Bin 63401 -> 28047 bytes yaksh/documentation/images/integer_testcase.jpg | Bin 71280 -> 21616 bytes .../images/java_standard_testcase.jpg | Bin 33812 -> 39025 bytes yaksh/documentation/images/mcc_testcase.jpg | Bin 33782 -> 36265 bytes yaksh/documentation/images/mcq_testcase.jpg | Bin 30914 -> 36252 bytes yaksh/documentation/images/moderator_dashboard.jpg | Bin 99088 -> 42100 bytes .../images/python_standard_testcase.jpg | Bin 28411 -> 19746 bytes yaksh/documentation/images/questions.jpg | Bin 101605 -> 66138 bytes .../images/scilab_standard_testcase.jpg | Bin 0 -> 34651 bytes yaksh/documentation/images/stdio_testcase.jpg | Bin 29389 -> 20130 bytes yaksh/documentation/images/string_testcase.jpg | Bin 66369 -> 14758 bytes yaksh/documentation/images/view_lessons.jpg | Bin 61713 -> 0 bytes yaksh/documentation/images/view_modules.jpg | Bin 63372 -> 0 bytes yaksh/documentation/images/view_quizzes.jpg | Bin 72866 -> 0 bytes yaksh/documentation/installation.rst | 61 ++++---- yaksh/documentation/moderator_dashboard.rst | 15 ++ .../moderator_docs/creating_course.rst | 170 ++++++++++++--------- .../moderator_docs/creating_lessons_modules.rst | 81 +++++----- .../moderator_docs/creating_question.rst | 98 ++++++++---- .../documentation/moderator_docs/creating_quiz.rst | 109 +++++++------ .../moderator_docs/other_features.rst | 17 ++- 38 files changed, 336 insertions(+), 221 deletions(-) create mode 100644 yaksh/documentation/images/add_module.jpg create mode 100644 yaksh/documentation/images/course_modules.jpg create mode 100644 yaksh/documentation/images/scilab_standard_testcase.jpg delete mode 100644 yaksh/documentation/images/view_lessons.jpg delete mode 100644 yaksh/documentation/images/view_modules.jpg delete mode 100644 yaksh/documentation/images/view_quizzes.jpg (limited to 'yaksh') diff --git a/yaksh/documentation/conf.py b/yaksh/documentation/conf.py index 39481c7..627217f 100644 --- a/yaksh/documentation/conf.py +++ b/yaksh/documentation/conf.py @@ -59,7 +59,7 @@ master_doc = 'index' # General information about the project. project = u'Yaksh' -copyright = u'2018, FOSSEE' +copyright = u'2020, FOSSEE' author = u'FOSSEE' # The version info for the project you're documenting, acts as replacement for @@ -67,9 +67,9 @@ author = u'FOSSEE' # built documents. # # The short X.Y version. -version = u'0.7' +version = u'0.13' # The full version, including alpha/beta/rc tags. -release = u'Feb 2018' +release = u'April 2020' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/yaksh/documentation/images/add_exercise.jpg b/yaksh/documentation/images/add_exercise.jpg index 2512f1a..47e7c49 100644 Binary files a/yaksh/documentation/images/add_exercise.jpg and b/yaksh/documentation/images/add_exercise.jpg differ diff --git a/yaksh/documentation/images/add_lesson.jpg b/yaksh/documentation/images/add_lesson.jpg index 6de272c..819d035 100644 Binary files a/yaksh/documentation/images/add_lesson.jpg and b/yaksh/documentation/images/add_lesson.jpg differ diff --git a/yaksh/documentation/images/add_module.jpg b/yaksh/documentation/images/add_module.jpg new file mode 100644 index 0000000..5f3f890 Binary files /dev/null and b/yaksh/documentation/images/add_module.jpg differ diff --git a/yaksh/documentation/images/add_question.jpg b/yaksh/documentation/images/add_question.jpg index b9b5bc7..3a4c73e 100644 Binary files a/yaksh/documentation/images/add_question.jpg and b/yaksh/documentation/images/add_question.jpg differ diff --git a/yaksh/documentation/images/add_quiz.jpg b/yaksh/documentation/images/add_quiz.jpg index 3264684..d2e7fd8 100644 Binary files a/yaksh/documentation/images/add_quiz.jpg and b/yaksh/documentation/images/add_quiz.jpg differ diff --git a/yaksh/documentation/images/bash_standard_testcase.jpg b/yaksh/documentation/images/bash_standard_testcase.jpg index a017445..3bbbdea 100644 Binary files a/yaksh/documentation/images/bash_standard_testcase.jpg and b/yaksh/documentation/images/bash_standard_testcase.jpg differ diff --git a/yaksh/documentation/images/course_details_features.jpg b/yaksh/documentation/images/course_details_features.jpg index 63b4b2e..34ea117 100644 Binary files a/yaksh/documentation/images/course_details_features.jpg and b/yaksh/documentation/images/course_details_features.jpg differ diff --git a/yaksh/documentation/images/course_features.jpg b/yaksh/documentation/images/course_features.jpg index 2da356e..d911188 100644 Binary files a/yaksh/documentation/images/course_features.jpg and b/yaksh/documentation/images/course_features.jpg differ diff --git a/yaksh/documentation/images/course_modules.jpg b/yaksh/documentation/images/course_modules.jpg new file mode 100644 index 0000000..4c16eb3 Binary files /dev/null and b/yaksh/documentation/images/course_modules.jpg differ diff --git a/yaksh/documentation/images/cpp_standard_testcase.jpg b/yaksh/documentation/images/cpp_standard_testcase.jpg index cfb1d89..59b801f 100644 Binary files a/yaksh/documentation/images/cpp_standard_testcase.jpg and b/yaksh/documentation/images/cpp_standard_testcase.jpg differ diff --git a/yaksh/documentation/images/create_course.jpg b/yaksh/documentation/images/create_course.jpg index bcf1eff..61d8329 100644 Binary files a/yaksh/documentation/images/create_course.jpg and b/yaksh/documentation/images/create_course.jpg differ diff --git a/yaksh/documentation/images/design_course.jpg b/yaksh/documentation/images/design_course.jpg index 287ebea..b6701db 100644 Binary files a/yaksh/documentation/images/design_course.jpg and b/yaksh/documentation/images/design_course.jpg differ diff --git a/yaksh/documentation/images/design_module.jpg b/yaksh/documentation/images/design_module.jpg index eda8825..3757586 100644 Binary files a/yaksh/documentation/images/design_module.jpg and b/yaksh/documentation/images/design_module.jpg differ diff --git a/yaksh/documentation/images/design_questionpaper.jpg b/yaksh/documentation/images/design_questionpaper.jpg index 05da597..d748754 100644 Binary files a/yaksh/documentation/images/design_questionpaper.jpg and b/yaksh/documentation/images/design_questionpaper.jpg differ diff --git a/yaksh/documentation/images/embed_video.jpg b/yaksh/documentation/images/embed_video.jpg index 84d18a2..e441a31 100644 Binary files a/yaksh/documentation/images/embed_video.jpg and b/yaksh/documentation/images/embed_video.jpg differ diff --git a/yaksh/documentation/images/float_testcase.jpg b/yaksh/documentation/images/float_testcase.jpg index 70b8a9f..a5c9ab0 100644 Binary files a/yaksh/documentation/images/float_testcase.jpg and b/yaksh/documentation/images/float_testcase.jpg differ diff --git a/yaksh/documentation/images/hook_testcase.jpg b/yaksh/documentation/images/hook_testcase.jpg index 4d404f6..49650fc 100644 Binary files a/yaksh/documentation/images/hook_testcase.jpg and b/yaksh/documentation/images/hook_testcase.jpg differ diff --git a/yaksh/documentation/images/integer_testcase.jpg b/yaksh/documentation/images/integer_testcase.jpg index 58ff6be..8d32cfd 100644 Binary files a/yaksh/documentation/images/integer_testcase.jpg and b/yaksh/documentation/images/integer_testcase.jpg differ diff --git a/yaksh/documentation/images/java_standard_testcase.jpg b/yaksh/documentation/images/java_standard_testcase.jpg index c13f618..949c783 100644 Binary files a/yaksh/documentation/images/java_standard_testcase.jpg and b/yaksh/documentation/images/java_standard_testcase.jpg differ diff --git a/yaksh/documentation/images/mcc_testcase.jpg b/yaksh/documentation/images/mcc_testcase.jpg index 7101833..11b3b0b 100644 Binary files a/yaksh/documentation/images/mcc_testcase.jpg and b/yaksh/documentation/images/mcc_testcase.jpg differ diff --git a/yaksh/documentation/images/mcq_testcase.jpg b/yaksh/documentation/images/mcq_testcase.jpg index afb51ca..dc705df 100644 Binary files a/yaksh/documentation/images/mcq_testcase.jpg and b/yaksh/documentation/images/mcq_testcase.jpg differ diff --git a/yaksh/documentation/images/moderator_dashboard.jpg b/yaksh/documentation/images/moderator_dashboard.jpg index 739d221..4486083 100644 Binary files a/yaksh/documentation/images/moderator_dashboard.jpg and b/yaksh/documentation/images/moderator_dashboard.jpg differ diff --git a/yaksh/documentation/images/python_standard_testcase.jpg b/yaksh/documentation/images/python_standard_testcase.jpg index a4d2ac8..5536aa8 100644 Binary files a/yaksh/documentation/images/python_standard_testcase.jpg and b/yaksh/documentation/images/python_standard_testcase.jpg differ diff --git a/yaksh/documentation/images/questions.jpg b/yaksh/documentation/images/questions.jpg index 1935541..4afa888 100644 Binary files a/yaksh/documentation/images/questions.jpg and b/yaksh/documentation/images/questions.jpg differ diff --git a/yaksh/documentation/images/scilab_standard_testcase.jpg b/yaksh/documentation/images/scilab_standard_testcase.jpg new file mode 100644 index 0000000..f7598e3 Binary files /dev/null and b/yaksh/documentation/images/scilab_standard_testcase.jpg differ diff --git a/yaksh/documentation/images/stdio_testcase.jpg b/yaksh/documentation/images/stdio_testcase.jpg index 9a041ba..1526e6b 100644 Binary files a/yaksh/documentation/images/stdio_testcase.jpg and b/yaksh/documentation/images/stdio_testcase.jpg differ diff --git a/yaksh/documentation/images/string_testcase.jpg b/yaksh/documentation/images/string_testcase.jpg index 6cd2b72..5c6a1e8 100644 Binary files a/yaksh/documentation/images/string_testcase.jpg and b/yaksh/documentation/images/string_testcase.jpg differ diff --git a/yaksh/documentation/images/view_lessons.jpg b/yaksh/documentation/images/view_lessons.jpg deleted file mode 100644 index 4229afe..0000000 Binary files a/yaksh/documentation/images/view_lessons.jpg and /dev/null differ diff --git a/yaksh/documentation/images/view_modules.jpg b/yaksh/documentation/images/view_modules.jpg deleted file mode 100644 index 5b535d3..0000000 Binary files a/yaksh/documentation/images/view_modules.jpg and /dev/null differ diff --git a/yaksh/documentation/images/view_quizzes.jpg b/yaksh/documentation/images/view_quizzes.jpg deleted file mode 100644 index 43bb36f..0000000 Binary files a/yaksh/documentation/images/view_quizzes.jpg and /dev/null differ diff --git a/yaksh/documentation/installation.rst b/yaksh/documentation/installation.rst index 1c90997..e74a6f0 100644 --- a/yaksh/documentation/installation.rst +++ b/yaksh/documentation/installation.rst @@ -2,33 +2,50 @@ Installation ============ +Requirements +------------ + +Python 3.6, 3.7, 3.8 + +Django 3.0.3 + Installing Yaksh ---------------- +If Python 3.6 and above is not available in the system, then we recommend using +miniconda + +**Installing Miniconda** + +1. Download miniconda from https://docs.conda.io/en/latest/miniconda.html according to the OS version. + +2. Follow the installation instructions as given in https://conda.io/projects/conda/en/latest/user-guide/install/index.html#regular-installation + +3. Restart the Terminal. **Pre-Requisite** * Ensure `pip `_ is installed -**For installing Yaksh** +**Installing Yaksh** 1. **Clone the repository**:: - $ git clone https://github.com/FOSSEE/online_test.git + git clone https://github.com/FOSSEE/online_test.git 2. **Go to the online_test directory**:: - $ cd ./online_test + cd ./online_test - 3. **Install the dependencies** - - * For Python 2 use:: + 3. **Install the dependencies**: - $ pip install -r ./requirements/requirements-py2.txt + * Install Django and dependencies:: - * For Python 3 (recommended) use:: + pip install -r ./requirements/requirements-common.txt - $ pip install -r ./requirements/requirements-py3.txt + * Install Code Server dependencies:: + sudo pip3 install -r requirements/requirements-codeserver.txt Quick Start ----------- @@ -93,9 +110,7 @@ Production Deployment :: - pip install -r requirements/requirements-py2.txt # For Python 2 - - pip3 install -r requirements/requirements-py3.txt # For Python 3 + pip3 install -r requirements/requirements-py3.txt # For Python 3.x 3. Install MySql Server 4. Install Python MySql support @@ -157,9 +172,6 @@ Production Deployment :: - $ sudo python -m yaksh.code_server # For Python 2.x - - $ sudo python3 -m yaksh.code_server # For Python 3.x Put this in the background once it has started since this will not @@ -298,25 +310,14 @@ Production Deployment * **Additional commands available** - We provide several convenient commands for you to use: + * **create_moderator** : Use this command to make a user as moderator. - - load\_exam : load questions and a quiz from a python file. See - docs/sample\_questions.py + :: - - load\_questions\_xml : load questions from XML file, see - docs/sample\_questions.xml use of this is deprecated in favor of - load\_exam. + python manage.py create_moderator - - results2csv : Dump the quiz results into a CSV file for further - processing. - - - dump\_user\_data : Dump out relevalt user data for either all users - or specified users. - - For more information on these do this: + For more information on the command: :: - $ python manage.py help [command] - - where [command] is one of the above. + python manage.py help [command-name] diff --git a/yaksh/documentation/moderator_dashboard.rst b/yaksh/documentation/moderator_dashboard.rst index 4b5bfea..441106a 100644 --- a/yaksh/documentation/moderator_dashboard.rst +++ b/yaksh/documentation/moderator_dashboard.rst @@ -6,6 +6,21 @@ On logging in moderators see the following dashboard. .. image:: images/moderator_dashboard.jpg +There are two options available: + * **Add Course** + It allows to create a new course. + * **Create Demo Course** + It creates a demo course contaning sample lesson and quiz with questions. + +The dashboard contains all the courses. Each course provides two options + + * **Manage Course** + Click on this button to manage the course. See the Manage course section + in the :doc:`moderator_docs/creating_course` page for more details. + * **Details** + Clicking on the Details button shows all the quizzes in the course. + Click on the quiz link to monitor the quiz. + The following pages explain the various functions available for moderators diff --git a/yaksh/documentation/moderator_docs/creating_course.rst b/yaksh/documentation/moderator_docs/creating_course.rst index 5aaddf5..78e9907 100644 --- a/yaksh/documentation/moderator_docs/creating_course.rst +++ b/yaksh/documentation/moderator_docs/creating_course.rst @@ -23,11 +23,15 @@ Setting up a new course * **Code** If the course should be hidden and only accessible to students possessing the correct course code. * **Instructions** - Instructions for the course + Instructions for the course. * **Start Date and Time for enrollment of course** - If the enrollment of the course should be available only after a set date and time + If the enrollment of the course should be available only after a set date and time. * **End Date and Time for enrollment of course** - If the enrollment of the course should be available only before a set date and time + If the enrollment of the course should be available only before a set date and time. + * **Grading System** + Add a grading system to the course. + * **View Grade** + This field allows the student to view the grade if checked else grade is not visible to student. Features in Courses @@ -39,98 +43,114 @@ Features in Courses This page shows all the courses created by a moderator and all the courses allotted to a moderator. - The following features are available for courses + The following options are available in the courses page + * **My Courses** + Click to show all the courses created by you. + * **Add/Edit Course** + Click to add the details of a new course. + * **Add/View Grading Systems** + Add or view grading systems. More info on creating grading system + * **Search/Filter Courses** + Search the courses by name or filter the course with active and inactive status. * **Course Name** - Click on course name link to view all the enrolled, rejected and requested students list. Moderator can accept or reject the student. - * **Module Name** - Click to edit a module added to the course - * **Lesson or Quiz Name** - Click to edit a Lesson or Quiz added to the course - - In edit quiz you can also attempt the quiz in two modes - - * **God Mode** - In God mode you can attempt quiz without any time or eligibilty constraints. - * **User Mode** - In user mode you can attempt quiz the way normal users will attempt i.e. - - * Quiz will have the same duration as that of the original quiz. - * Quiz won't start if the course is inactive or the quiz time has expired. - * **Add Quizzes/Lessons for ** - Click to add/delete lessons or quizzes. - * **Design Course** - Click to add/delete modules of a course. - * **Add Teacher** - Click to add teachers for the course. The teachers can edit and modify only the specific course that are allotted to them. + Shows course name of all the created and allotted courses. + * **Edit Course** + Click this button to edit the corresponding course details. + * **Manage Course** + This provides more options for the course. For e.g. setting up modules, + lessons, quizzes, practice exercises, students enrollments etc. + * **Download** + This button provides two options. One is to download the course CSV containing student data, Other is to download entire course for offline viewing. * **Clone Course** Click to create a copy of a course along with its modules, lessons and quizzes. - * **Teachers added to the course** - This shows all the teachers added to a particular course. - * **Download CSV for the entire course** - This downloads the CSV file containing the performance of all students in every quiz for a given course. - * **Edit Course** - Click to edit the details of an existing course. - * **Deactivate/Activate Course** - Click to deactivate or activate the course. - * **My Courses** - Click to show all the courses created by you. - * **Allotted courses** - Click to view all the courses allotted to you. - * **Add New Course** - Click to open course form to create new course. - * **Add/View Quizzes** - Click to view all the quizzes created by you or add new quiz. - * **Add/View Lessons** - Click to view all the lessons created by you or add new lesson. - * **Add/View Modules** - Click to view all the modules created by you or add new module. - - -Design a Course ---------------- + * **Activate/Deactivate Course** + Toogle to activate or deactivate the course. - Clicking on **Design Course** will show the below page. - .. image:: ../images/design_course.jpg +Manage Course +-------------------------- - **Available Modules** contains all the modules that are not added to a course. + Click on the Manage course button to view the course details page. - To add a module to the course select the checkbox besides the desired module to be added and click **Add to course** button. + .. image:: ../images/course_details_features.jpg - **Chosen Modules** contains all the modules that are added to a course. + Following are the features for course details - - Following parameters can be changed while designing a course: + * **Enroll Students** + * **Upload Users** + Create and enroll users automatically by uploading a csv of the users. The mandatory fields for this csv are - **firstname, lastname, email**. Other fields like **username, password, institute, roll_no, department, remove** fields are optionals. + * **Requests** + This is a list of students who have requested to be enrolled in the course. Moderator can enroll or reject selected students. + * **Enrolled** + This is a list of students who have been enrolled in the course. Moderator can reject enrolled students. + * **Rejected** + This is a list of students who have been rejected for enrollment in a course. Moderator can enroll rejected students. + * **Course Modules** + Moderator can send mail to all enrolled students or selected students. - **Order** - Order in which modules are shown to a student. + .. image:: ../images/course_modules.jpg - To change a module's order change the value to a desired order in the textbox under **Order** column and click **Change order**. + * **Add Module** + Click on this button to add a module to the course. Fill the details + of the module and save it. - **Check Prerequisite** - Check if previous module is completed. Default value is **Yes**. - For e.g., Assuming a course contains modules **Demo Module** and **Python module** in the given order; a student has to first complete **Demo module** to attempt **Python Module** if the **Check Prerequisite** value for **Python Module** is checked **Yes**. + After creating a module for the course, following options are available: - **Currently** column shows the current value of **Check Prerequisite** which in this case is **Yes**. + * **Add Lesson** + Add lesson to the corresponding module. - Select the checkbox from **Change** column under **Check Prerequisite** and click **Change Prerequisite** button to change the value. + * **Add Quiz** + Add a graded quiz to the correspoding module. - To remove a module from the course select the checkbox beside every module and click **Remove from course** button. - + * **Add Exercise** + Add a ungraded practice exercise to the corresponding module. -Features in Course Details --------------------------- + * **Design Module** + This option allows you to change the order of the units added to + the module, check for prerequisites of the module and remove a unit from the module. + * **Design Course** + Clicking on **Design Course** will show the below page. - Click on a given course name to go to the course details page. + .. image:: ../images/design_course.jpg - .. image:: ../images/course_details_features.jpg + * **Available Modules** contains all the modules that are not added to a course. - Following are the features for course details - + To add a module to the course select the checkbox besides the desired module to be added and click **Add to course** button. + + * **Chosen Modules** contains all the modules that are added to a course. + + Following parameters can be changed while designing a course: + + * **Order** + Order in which modules are shown to a student. + + To change a module's order change the value to a desired order in the textbox under **Order** column and click **Change order**. + + * **Check Prerequisite Completion** + Check if previous module is completed. Default value is **Yes**. + + For e.g., Assuming a course contains modules **Demo Module** and **Trial for trial_course** in the given order; a student has to first complete **Demo module** to attempt **Trial for trial_course** if the **Check Prerequisite** value for **Trial for trial_course** is checked **Yes**. + + **Currently** column shows the current value of **Change Prerequisite Completion** which in this case is **Yes**. + + Select the checkbox from **Change** column under **Check Prerequisite Completion** and click **Change Prerequisite Completion** button to change the value. + + * **Check Prerequisite Passing** + Check if previous module is completed. Default value is **Yes**. This is similar to **Check Prerequisite Completion** except that it checks if all the quizzes in the module are passed or not. + + **Currently** column shows the current value of **Change Prerequisite Passing** which in this case is **Yes**. + + Select the checkbox from **Change** column under **Check Prerequisite Passing** and click **Change Prerequisite Passing** button to change the value. - * **Requests** - This is a list of students who have requested to be enrolled in the course. Moderator can enroll or reject selected students. - * **Enrolled** - This is a list of students who have been enrolled in the course. Moderator can reject enrolled students. - * **Rejected** - This is a list of students who have been rejected for enrollment in a course. Moderator can enroll rejected students. - * **Upload Users** - Create and enroll users automatically by uploading a csv of the users. The mandatory fields for this csv are - **firstname, lastname, email**. Other fields like **username, password, institute, roll_no, department, remove** fields are optionals. + * **Remove Module** + To remove a module from the course select the checkbox beside every module and click **Remove from course** button. + * **Course Progress** + It shows progress made by the students in the course. Moderator can also + download the course progress data. * **Send Mail** Moderator can send mail to all enrolled students or selected students. - * **View Course Status** - View students' progress through the course. + * **Add Teachers/TAs** + Moderator can search for the users by username, email, first name and last name to add as Teacher/TA to the course. + * **Current Teachers/TAs** + It shows all the added Teachers/TAs to the course. Added users can view and edit the course, modules, lessons and quizzes available in the course. diff --git a/yaksh/documentation/moderator_docs/creating_lessons_modules.rst b/yaksh/documentation/moderator_docs/creating_lessons_modules.rst index e057be0..b61789e 100644 --- a/yaksh/documentation/moderator_docs/creating_lessons_modules.rst +++ b/yaksh/documentation/moderator_docs/creating_lessons_modules.rst @@ -6,84 +6,89 @@ Lessons and Modules Courses can have lessons and quizzes encapsulated using a module. - * **What is a lesson?** - A lesson can be any markdown text with/or an embedded video of a particular topic. + * **What is a lesson?** + A lesson can be any markdown text with/or an embedded video of a particular topic. - * **What is a module?** - A Module is a collection of lessons and courses clubbed together by similar idea/content. A module can have its own description as a markdown text with/or an embedded video. + * **What is a module?** + A Module is a collection of lessons, quizzes and practice exercises clubbed together by similar idea/content. A module can have its own description as a markdown text with/or an embedded video. Setting up a Lesson ----------------------- - To create a new lesson or edit any existing lesson click on **Add/View Lessons** from courses page. + This page shows all the lessons created by you and added to a module. - .. image:: ../images/view_lessons.jpg + .. image:: ../images/course_modules.jpg - This page shows all the lessons created by you. + To create a new lesson click on **Add Lesson** button in the module. - Click on **Add new Lesson** to add new lesson. Click on the **lesson name** to edit a lesson. + .. image:: ../images/add_lesson.jpg - .. image:: ../images/add_lesson.jpg + * **Name** - Name of the lesson. + * **Description** - Description can be any markdown text or embedded video link. + * **Active** - Activate/Deactivate a lesson + * **Video File** - Upload a video file for the lesson + * **Lesson files** - Add files to the lesson which will be available for students to view and download. All the uploaded files will be shown below. - * **Name** - Name of the lesson. - * **Description** - Description can be any markdown text or embedded video link. - * **Active** - Activate/Deactivate a lesson - * **Lesson files** - Add files to the lesson which will be available for students to view and download. All the uploaded files will be shown below. + Click on **Save** to save a lesson. - Click on **Save** to save a lesson. + Click on **Preview Lesson Description** to preview lesson description. Markdown text from the description is converted to html and is displayed below. - Click on **Preview Lesson Description** to preview lesson description. Markdown text from the description is converted to html and is displayed below. + Select the checkbox beside each uploaded file and click on **Delete files** to remove files from the lesson. - Select the checkbox beside each uploaded file and click on **Delete files** to remove files from the lesson. + Click on **Embed Video Link** to embed a video. On clicking a pop-up will be shown. - Click on **Embed Video Link** to embed a video. On clicking a pop-up will be shown. + .. image:: ../images/embed_video.jpg - .. image:: ../images/embed_video.jpg - - Enter the url and click on **Submit** a html div is generated in the text area below. - Click on the button below the textarea to copy the textarea content. This html div can then be added in the lesson description. + Enter the url and click on **Submit** a html div is generated in the text area below. + Click on the button below the textarea to copy the textarea content. This html div can then be added in the lesson description. Setting up a Module ----------------------- - To create a new module or edit any existing module click on **Add/View Modules** from courses page. + To create a new module click on **Add Module** button from course modules + section of course details page. + + .. image:: ../images/add_module.jpg + + * **Name** - Name of the module. + * **Description** - Description can be any markdown text or embedded video link. - .. image:: ../images/view_modules.jpg + Click on **Save** to save a module. - This page shows all the modules created by you. + Click on **Preview Lesson Description** to preview lesson description. Markdown text from the description is converted to html and is displayed below. - Creating a new module or editing an existing module is similar to a lesson creation with a difference that a module has no option to upload files. + Click on **Embed Video Link** to embed a video. Design a Module --------------- - To add lessons or quizzes to a module click on **Add Quizzes/Lessons for **. + To add lessons or quizzes to a module click on **Design Module**. - .. image:: ../images/design_module.jpg + .. image:: ../images/design_module.jpg - **Available Lessons and quizzes** contains all the lessons and quizzes that are not added to a module. + **Available Lessons and quizzes** contains all the lessons and quizzes that are not added to a module. - To add a lesson or a quiz to the module select the checkbox beside every lesson or quiz and click **Add to Module** button. + To add a lesson or a quiz to the module select the checkbox beside every lesson or quiz and click **Add to Module** button. - **Chosen Lesson and quizzes** contains all the lessons and quizzes that are added to a module. + **Chosen Lesson and quizzes** contains all the lessons and quizzes that are added to a module. - A lesson or quiz added to a module becomes a unit. A unit has following parameters to change: + A lesson or quiz added to a module becomes a unit. A unit has following parameters to change: - **Order** - Order in which units are shown to a student. + **Order** - Order in which units are shown to a student. - To change a unit's order change the value in the textbox under **Order** column and click **Change order**. + To change a unit's order change the value in the textbox under **Order** column and click **Change order**. - **Check Prerequisite** - Check if previous unit is completed. Default value is **Yes**. - For e.g. A student has to first complete **Yaksh Demo quiz** to attempt **Demo Lesson** if the **Check Prerequisite** value for **Demo Lesson** is checked **Yes**. + **Check Prerequisite** - Check if previous unit is completed. Default value is **Yes**. + For e.g. A student has to first complete **Yaksh Demo quiz** to attempt **Demo Lesson** if the **Check Prerequisite** value for **Demo Lesson** is checked **Yes**. - **Currently** column shows the current value of **Check Prerequisite** which in this case is **Yes**. + **Currently** column shows the current value of **Check Prerequisite** which in this case is **Yes**. - Select the checkbox from **Change** column under **Check Prerequisite** and click **Change Prerequisite** button to change the value. + Select the checkbox from **Change** column under **Check Prerequisite** and click **Change Prerequisite** button to change the value. - To remove a lesson or a quiz from the module select the checkbox beside every lesson or quiz and click **Remove from Module** button. + To remove a lesson or a quiz from the module select the checkbox beside every lesson or quiz and click **Remove from Module** button. diff --git a/yaksh/documentation/moderator_docs/creating_question.rst b/yaksh/documentation/moderator_docs/creating_question.rst index 3e878ea..2c52628 100644 --- a/yaksh/documentation/moderator_docs/creating_question.rst +++ b/yaksh/documentation/moderator_docs/creating_question.rst @@ -23,9 +23,7 @@ Setting up questions * **Points** - Points is the marks for a question. - * **Description** - The actual question description in HTML format. - - .. note:: To add code snippets in questions please use html and
tags. + * **Description** - The actual question description. * **Tags** - Type of label or metadata tag making it easier to find specific type of questions. @@ -55,7 +53,9 @@ Setting up questions How to write Test cases ----------------------- - + After saving the question with the necessary details, you will be able to add + the test cases. A drop down **Add Test case** will be available to add the test case in the Test Cases section. + The following explains different methods to write test cases. * **Create Standard Test Case** @@ -200,10 +200,54 @@ How to write Test cases 2. Each argument should be separated by **space**. 3. This field can be left blank. + * **For Scilab** + .. image:: ../images/scilab_standard_testcase.jpg + :width: 80% + + Consider a Program to add two numbers. + The code in the Test case Field should be as follows: :: + + mode(-1) + exec("function.sci",-1); + i = 0 + p = add(3,5); + correct = (p == 8); + if correct then + i=i+1 + end + disp("Input submitted 3 and 5") + disp("Expected output 8 got " + string(p)) + p = add(22,-20); + correct = (p==2); + if correct then + i=i+1 + end + disp("Input submitted 22 and -20") + disp("Expected output 2 got " + string(p)) + p =add(91,0); + correct = (p==91); + if correct then + i=i+1 + end + disp("Input submitted 91 and 0") + disp("Expected output 91 got " + string(p)) + if i==3 then + exit(5); + else + exit(3); + end + + Assuming Students answer to be as below: :: + + funcprot(0) + function[c]=add(a,b) + c=a+b; + endfunction + - Check Delete Field if a test case is to be removed. + Check **Delete** Field if a test case is to be removed. - Finally click on Save to save the test case. + Finally click on **Save** to save the test case. * **Create Standard Input/Output Based Test Case** @@ -221,23 +265,7 @@ How to write Test cases Setting up Standard Input/Output Based questions is same for all languages. - * **Create MCQ or MCC Based Test Case** - - Select MCQ/MCC from Add Test Case field. - - Fig (a) showing MCQ based testcase - - .. image:: ../images/mcq_testcase.jpg - :width: 80% - - Fig (b) showing MCC based testcase - - .. image:: ../images/mcc_testcase.jpg - :width: 80% - - In Options Field type the option check the correct checkbox if the current option is correct and click on Save button to save each option. - - For MCC based question, check the correct checkbox for multiple correct options. + .. note:: Standard Input/Output Based questions is available only for the languages Python, C, C++, Java, Bash. * **Create Hook based Test Case** @@ -286,6 +314,24 @@ How to write Test cases .. image:: ../images/hook_testcase.jpg :width: 80% + * **Create MCQ or MCC Based Test Case** + + Select MCQ/MCC from Add Test Case field. + + Fig (a) showing MCQ based testcase + + .. image:: ../images/mcq_testcase.jpg + :width: 80% + + Fig (b) showing MCC based testcase + + .. image:: ../images/mcc_testcase.jpg + :width: 80% + + In Options Field type the option check the correct checkbox if the current option is correct and click on Save button to save each option. + + For MCC based question, check the correct checkbox for multiple correct options. + * **Create Integer Based Test Case** Select **Answer in Integer** from Type field. @@ -340,7 +386,7 @@ Features in Question * **Upload Questions** - Click on the **Upload and Download questions** tab in the + Click on the **Upload Questions** tab in the **Question Page**. One can upload Yaml file with extensions .yaml or .yml. Please note that you cannot upload files associated to a question. @@ -363,7 +409,7 @@ Features in Question Select questions from the list of question displayed on the Questions page. Click on Test selected button. This will take you to a quiz with the selected questions. - .. Note:: This will not create an actual quiz but a trial quiz. This quiz is hidden from the students and only for moderator to view. You can delete the quiz from moderator's dashboard. + .. Note:: This will not create an actual quiz but a trial quiz. This quiz is hidden from the students and only for moderator to view. * **Filter Questions** @@ -376,5 +422,5 @@ Features in Question 1. You can search the questions by tags added during question creation. 2. Click on the Available tags to view all the available tags. Select any tag from available tags and click **Search**. - 3. Enter the tag in the search bar and click on **Search** respective questions will be displayed. + 3. Enter the tag in the search bar and click on **Search Icon** respective questions will be displayed. diff --git a/yaksh/documentation/moderator_docs/creating_quiz.rst b/yaksh/documentation/moderator_docs/creating_quiz.rst index 8b93188..6c7cb93 100644 --- a/yaksh/documentation/moderator_docs/creating_quiz.rst +++ b/yaksh/documentation/moderator_docs/creating_quiz.rst @@ -4,85 +4,104 @@ Quizzes ======= -Quizzes are intrinsically associated with a course, hence to view and/or edit a quiz, we need to navigate to the courses page. +Quizzes are intrinsically associated with a course, hence to view and/or edit a quiz, we need to navigate to the course details page by clicking on **Manage Course** button. -Clicking on Add/View Quizzes from courses page will open the page as shown below +Clicking on **Course Modules** in the course details page will open the page as shown below -.. image:: ../images/view_quizzes.jpg +.. image:: ../images/course_modules.jpg -This page shows all the quizzes and exercise created. +This page shows all the modules with quizzes and exercises. Creating a Quiz ---------------- +--------------- - Click on **Add New Quiz** button to add a quiz. + Click on **Add Quiz** button to add a quiz. - .. image:: ../images/add_quiz.jpg - - .. note :: It is important to have created or uploaded questions before creating a quiz. + .. image:: ../images/add_quiz.jpg + + .. note :: It is important to have created or uploaded questions before creating a quiz. - * **Start Date and Time of quiz** - The date and time after which the quiz can be taken. - * **End Date and Time of quiz** - The date and time after which the quiz is deactivated and cannot be attempted. - * **Duration** - Duration of quiz to be written in minutes. - * **Active** - Check the checkbox to activate/deactivate quiz. - * **Description** - Description or name of the quiz. - * **Passing Percentage** - Minimum percentage required to pass the test. - * **Attempts allowed** - Number of attempts that a student can take of the current quiz. - * **Time Between Quiz Attempts in hours** - For a quiz with multiple attempts this value can be set so that student can attempt again after the specified time. - * **Instructions for students** - Additional instructions for students can be added. Some default instructions are already provided. - * **Allow student to view answer paper** - Click on this checkbox to allow student to view their answer paper. - * **Allow student to skip questions** - Click on this checkbox to allow/disallow student to skip questions for a quiz. Value defaults to allow skipping questions. - * **Weightage** - Every quiz will have weightage depending on which grades will be calculated. + * **Start Date and Time of quiz** - The date and time after which the quiz can be taken. + * **End Date and Time of quiz** - The date and time after which the quiz is deactivated and cannot be attempted. + * **Duration** - Duration of quiz to be written in minutes. + * **Active** - Check the checkbox to activate/deactivate quiz. + * **Description** - Description or name of the quiz. + * **Passing Percentage** - Minimum percentage required to pass the test. + * **Attempts allowed** - Number of attempts that a student can take of the current quiz. + * **Time Between Quiz Attempts in hours** - For a quiz with multiple attempts this value can be set so that student can attempt again after the specified time. + * **Instructions for students** - Additional instructions for students can be added. Some default instructions are already provided. + * **Allow student to view answer paper** - Click on this checkbox to allow student to view their answer paper. + * **Allow student to skip questions** - Click on this checkbox to allow/disallow student to skip questions for a quiz. Value defaults to allow skipping questions. + * **Weightage** - Every quiz will have weightage depending on which grades will be calculated. - Once a quiz parameters have been set click on **Save** button to save the quiz. + Once a quiz parameters have been set click on **Save** button to save the quiz. -To create a Question paper, Click on **Add** link located besides the created quiz. + To create a Question paper, Click on **Add Question Paper** link located + besides the created quiz. Creating a Exercise -------------------- +------------------- - Click on **Add New Exercise** button to add a exercise. + Click on **Add New Exercise** button to add a exercise. - .. image:: ../images/add_exercise.jpg + .. image:: ../images/add_exercise.jpg - Exercise is similar to quiz with a difference that exercise has infinite attempts and - infinite time. It also does not allow a student to skip the question. - Each question in an exercise can be timed i.e. time to solve a particular question. - Once the question time expires, question solution is shown to the student. + Exercise is similar to quiz with a difference that exercise has infinite attempts and + infinite time. It also does not allow a student to skip the question. + Each question in an exercise can be timed i.e. time to solve a particular question. + Once the question time expires, question solution is shown to the student. - All the parameters are set by default only below parameters can be changed. + All the parameters are set by default only below parameters can be changed. - * **Description** - Description or name of the exercise. - * **Allow student to view answer paper** - Click on this checkbox to allow student to view their answer paper. - * **Active** - Select the checkbox to activate/deactivate exercise. Default value is active. + * **Description** - Description or name of the exercise. + * **Allow student to view answer paper** - Click on this checkbox to allow student to view their answer paper. + * **Active** - Select the checkbox to activate/deactivate exercise. Default value is active. + + To create a Question paper, Click on **Add Question Paper** link located besides the created exercise. Designing Question Paper ------------------------ - .. image:: ../images/design_questionpaper.jpg + .. image:: ../images/design_questionpaper.jpg - A quiz/exercise can have fixed as well as random questions. Fixed questions are those question that are bound to appear for every student taking the quiz. In random questions a pool of questions is given and number of questions to be picked from the pool is set. Hence for different students, different questions from the pool will appear. + A quiz/exercise can have fixed as well as random questions. Fixed questions are those question that are bound to appear for every student taking the quiz. In random questions a pool of questions is given and number of questions to be picked from the pool is set. Hence for different students, different questions from the pool will appear. - To add questions to a questionpaper + To add questions to a questionpaper - * Select Question type and marks and a list of questions will be displayed will be in the **select questions to add** section. Do this for both fixed questions and random questions. - * After adding questions click on **Next** button to go to **Step 3 Finish**. - * Select shuffle paper if you want to jumble up the question sequence for every student and for every attempt. - * Click on save question paper to save it or preview question paper to preview it. + * Select Question type and marks and a list of questions will be displayed will be in the **select questions to add** section. Do this for both fixed questions and random questions. + * You can also search for questions using the tags added while creating the question. All the available tags are shown. + * After adding questions click on **Next** button to go to **Step 3 Finish**. + * Select **Shuffle questions' order for each student** if you want to jumble up the question sequence for every student and for every attempt. + * Select **Shuffle MCQ/MCC options for each student** if you want to jumble up the MCQ/MCC question options for every student and for every attempt. + * Click on **Save** to save it. Editing a Quiz/Exercise ----------------------- - Click on the quiz/exercise link to edit, change the parameters and click on Save. + Click on the quiz/exercise link to edit, change the parameters and click on Save. + Options Available once the Quiz is created. + + * **Preview Question Paper** + Click on the Preview Question Paper button located at the bottom of the + page to preview all the questions available in the question paper of the + quiz. + * **User Mode** + Attempt quiz the way student will attempt i.e. - + Quiz will have the same duration as that of the original quiz. + + Quiz won't start if the course is inactive or the quiz time has expired. + + * **God Mode** + Attempt quiz without any time or eligibilty constraints. Editing a QuestionPaper ----------------------- - Click on the Question Paper for a besides Quiz/Exercise and follow steps from Design Question Paper. + Click on the **Edit Question Paper** besides Quiz/Exercise and follow steps from Design Question Paper. - If the questions are already added to a Question Paper then they are shown in the - **Fixed Questions currently in the paper** section. + If the questions are already added to a Question Paper then they are shown in the + **Fixed Questions currently in the paper** section. diff --git a/yaksh/documentation/moderator_docs/other_features.rst b/yaksh/documentation/moderator_docs/other_features.rst index 0b39788..bbd60dc 100644 --- a/yaksh/documentation/moderator_docs/other_features.rst +++ b/yaksh/documentation/moderator_docs/other_features.rst @@ -7,17 +7,26 @@ Grade User Grade User is a feature of Yaksh to access students' answer papers for each quiz and grade them where necessary. + Clicking on the **Grade User** link from the nav bar will show all the courses. + + Click on the **Details** button to show the quizzes associated to a particular course. + + Click on the **Grade User** button next to the quiz name to view all the students who have attempted the quiz. + + Click on the student name to view their submissions. + Monitor ------- Monitor is a feature of Yaksh where the moderator can monitor a quiz and view statistics. -Trial Papers ------------- + Clicking on the **Monitor** link from the nav bar will show all the courses. + + Click on the **Details** button to show the quizzes associated to a particular course. - When a moderator attempts a quiz in User or God mode or tests questions, a trial answer paper is created. Moderator can check the answer paper. + Click on the **Monitor** button next to the quiz name to view all the students who are attempting the quiz. - .. note:: It is advisable to delete these trial answer papers. + Click on the student name to view their submissions. Grader ------ -- cgit From 4d56ce652b4b512080854191aae2925658a1c687 Mon Sep 17 00:00:00 2001 From: adityacp Date: Tue, 7 Apr 2020 14:16:20 +0530 Subject: Add R language standard testcase information --- yaksh/documentation/images/r_standard_testcase.jpg | Bin 0 -> 42217 bytes .../moderator_docs/creating_question.rst | 37 +++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 yaksh/documentation/images/r_standard_testcase.jpg (limited to 'yaksh') diff --git a/yaksh/documentation/images/r_standard_testcase.jpg b/yaksh/documentation/images/r_standard_testcase.jpg new file mode 100644 index 0000000..db6bf9b Binary files /dev/null and b/yaksh/documentation/images/r_standard_testcase.jpg differ diff --git a/yaksh/documentation/moderator_docs/creating_question.rst b/yaksh/documentation/moderator_docs/creating_question.rst index 2c52628..ea2d610 100644 --- a/yaksh/documentation/moderator_docs/creating_question.rst +++ b/yaksh/documentation/moderator_docs/creating_question.rst @@ -244,6 +244,43 @@ How to write Test cases c=a+b; endfunction + * **For R** + .. image:: ../images/r_standard_testcase.jpg + :width: 80% + + Consider a Program to print even or odd number. + The code in the Test case Field should be as follows: :: + + source("function.r") + check_empty = function(obj){ + stopifnot(is.null(obj) == FALSE) + } + check = function(input, output){ + stopifnot(input == output) + } + is_correct = function(){ + if (count == 3){ + quit("no", 31) + } + } + check_empty(odd_or_even(3)) + check(odd_or_even(6), "EVEN") + check(odd_or_even(1), "ODD") + check(odd_or_even(10), "EVEN") + check(odd_or_even(777), "ODD") + check(odd_or_even(778), "EVEN") + count = 3 + is_correct() + + Assuming Students answer to be as below: :: + + odd_or_even <- function(n){ + if(n %% 2 == 0){ + return("EVEN") + } + return("ODD") + } + Check **Delete** Field if a test case is to be removed. -- cgit From 8c907916799c756e658d61dec1b3f46903880097 Mon Sep 17 00:00:00 2001 From: adityacp Date: Tue, 7 Apr 2020 14:32:53 +0530 Subject: Release 0.14.0 related changes --- yaksh/migrations/0017_release_0_14_0.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 yaksh/migrations/0017_release_0_14_0.py (limited to 'yaksh') diff --git a/yaksh/migrations/0017_release_0_14_0.py b/yaksh/migrations/0017_release_0_14_0.py new file mode 100644 index 0000000..334ab8c --- /dev/null +++ b/yaksh/migrations/0017_release_0_14_0.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.3 on 2020-04-07 08:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('yaksh', '0016_release_0_12_0'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='timezone', + field=models.CharField(choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Currie', 'Australia/Currie'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Sydney', 'Australia/Sydney'), ('Canada/Atlantic', 'Canada/Atlantic'), ('Canada/Central', 'Canada/Central'), ('Canada/Eastern', 'Canada/Eastern'), ('Canada/Mountain', 'Canada/Mountain'), ('Canada/Newfoundland', 'Canada/Newfoundland'), ('Canada/Pacific', 'Canada/Pacific'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('GMT', 'GMT'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('US/Alaska', 'US/Alaska'), ('US/Arizona', 'US/Arizona'), ('US/Central', 'US/Central'), ('US/Eastern', 'US/Eastern'), ('US/Hawaii', 'US/Hawaii'), ('US/Mountain', 'US/Mountain'), ('US/Pacific', 'US/Pacific'), ('UTC', 'UTC')], default='UTC', max_length=64), + ), + ] -- cgit From ce3eb1dbbd924003489d01f4e98aba841cd803c0 Mon Sep 17 00:00:00 2001 From: adityacp Date: Wed, 8 Apr 2020 15:55:59 +0530 Subject: Change templates, views, forms, models - Allow to test, download and delete single question - Fix pagination for searching and filtering questions --- yaksh/forms.py | 15 +- yaksh/models.py | 2 +- yaksh/static/yaksh/js/question_filter.js | 47 ---- yaksh/static/yaksh/js/show_question.js | 17 +- yaksh/templates/yaksh/ajax_question_filter.html | 57 ----- yaksh/templates/yaksh/paginator.html | 6 +- yaksh/templates/yaksh/showquestions.html | 277 ++++++++++++------------ yaksh/urls.py | 10 +- yaksh/views.py | 169 +++++++++++---- 9 files changed, 305 insertions(+), 295 deletions(-) delete mode 100644 yaksh/static/yaksh/js/question_filter.js delete mode 100644 yaksh/templates/yaksh/ajax_question_filter.html (limited to 'yaksh') diff --git a/yaksh/forms.py b/yaksh/forms.py index 52ef75d..d2627d7 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -17,9 +17,9 @@ from string import punctuation, digits import pytz from .send_emails import generate_activation_key -languages = (("select", "Select Language"),) + languages +languages = (("", "Select Language"),) + languages -question_types = (("select", "Select Question Type"),) + question_types +question_types = (("", "Select Question Type"),) + question_types test_case_types = ( ("standardtestcase", "Standard Testcase"), @@ -357,11 +357,14 @@ class QuestionFilterForm(forms.Form): ) self.fields['marks'].required = False language = forms.CharField( - max_length=8, widget=forms.Select(choices=languages, - attrs={'class': 'custom-select'})) + max_length=8, widget=forms.Select( + choices=languages, attrs={'class': 'custom-select'}), + required=False + ) question_type = forms.CharField( - max_length=8, widget=forms.Select(choices=question_types, - attrs={'class': 'custom-select'}) + max_length=8, widget=forms.Select( + choices=question_types, attrs={'class': 'custom-select'}), + required=False ) diff --git a/yaksh/models.py b/yaksh/models.py index 52a0414..5d4d453 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -1385,7 +1385,7 @@ class Question(models.Model): testcases.append(case.get_field_value()) q_dict['testcase'] = testcases q_dict['files'] = file_names - q_dict['tags'] = [tags.tag.name for tags in q_dict['tags']] + q_dict['tags'] = [tag.name for tag in q_dict['tags']] questions_dict.append(q_dict) question._add_yaml_to_zip(zip_file, questions_dict) return zip_file_name diff --git a/yaksh/static/yaksh/js/question_filter.js b/yaksh/static/yaksh/js/question_filter.js deleted file mode 100644 index aa3a229..0000000 --- a/yaksh/static/yaksh/js/question_filter.js +++ /dev/null @@ -1,47 +0,0 @@ -$(document).ready(function(){ - $question_type = $("#id_question_type"); - $marks = $("#id_marks"); - $language = $("#id_language"); - - function question_filter() { - $.ajax({ - url: "/exam/ajax/questions/filter/", - type: "POST", - data: { - question_type: $question_type.val(), - marks: $marks.val(), - language: $language.val() - }, - dataType: "html", - success: function(output) { - var questions = $(output).filter("#questions").html(); - $("#filtered-questions").html(questions); - } - }); - } - - $question_type.change(function() { - question_filter() - }); - - $language.change(function() { - question_filter() - }); - - $marks.change(function() { - question_filter() - }); - - $("#checkall").change(function(){ - if($(this).prop("checked")) { - $("#filtered-questions input:checkbox").each(function(index, element) { - $(this).prop('checked', true); - }); - } - else { - $("#filtered-questions input:checkbox").each(function(index, element) { - $(this).prop('checked', false); - }); - } - }); -}); \ No newline at end of file diff --git a/yaksh/static/yaksh/js/show_question.js b/yaksh/static/yaksh/js/show_question.js index e6825a0..d7b6a44 100644 --- a/yaksh/static/yaksh/js/show_question.js +++ b/yaksh/static/yaksh/js/show_question.js @@ -47,7 +47,18 @@ function append_tag(tag){ tag_name.value = tag.value; } } -$(document).ready(function() - { - $("#questions-table").tablesorter({sortList: [[0,0], [4,0]]}); +$(document).ready(function() { + $("#questions-table").tablesorter({}); + $("#checkall").change(function(){ + if($(this).prop("checked")) { + $("#filtered-questions input:checkbox").each(function(index, element) { + $(this).prop('checked', true); + }); + } + else { + $("#filtered-questions input:checkbox").each(function(index, element) { + $(this).prop('checked', false); + }); + } }); +}); diff --git a/yaksh/templates/yaksh/ajax_question_filter.html b/yaksh/templates/yaksh/ajax_question_filter.html deleted file mode 100644 index 18f14ff..0000000 --- a/yaksh/templates/yaksh/ajax_question_filter.html +++ /dev/null @@ -1,57 +0,0 @@ -
- -
- -  Add Question - -

- {% if questions %} - {% include "yaksh/paginator.html" %} - -
- Select All -
-
    - - - - - - - - - - - - {% for question in questions %} - - - - - - - - {% endfor %} - -
    Select Summary Language Type Marks
    - - {{question.summary|capfirst}}{{question.language|capfirst}}{{question.type|capfirst}}{{question.points}}
    -
- {% include "yaksh/paginator.html" %} - {% endif %} -
diff --git a/yaksh/templates/yaksh/paginator.html b/yaksh/templates/yaksh/paginator.html index 5f0df7a..c634d5c 100644 --- a/yaksh/templates/yaksh/paginator.html +++ b/yaksh/templates/yaksh/paginator.html @@ -1,7 +1,7 @@
    {% if objects.has_previous %}
  • - + @@ -16,13 +16,13 @@ {{ n }}(current)
  • {% elif n > objects.number|add:'-5' and n < objects.number|add:'5' %} -
  • {{ n }}
  • +
  • {{ n }}
  • {% endif %} {% endfor %} {% if objects.has_next %}
  • - + diff --git a/yaksh/templates/yaksh/showquestions.html b/yaksh/templates/yaksh/showquestions.html index e0cd529..6f05a0b 100644 --- a/yaksh/templates/yaksh/showquestions.html +++ b/yaksh/templates/yaksh/showquestions.html @@ -7,154 +7,153 @@ {% block script %} - {% endblock %} {% block content %} -
    - - - -
    +
    +
    - -
    -
  • Yaml File -

    One can upload Yaml file with extensions .yaml or .yml. Please note - that you cannot upload files associated to a question. Yaml file can - have any name. -

    -
  • -
  • Zip File -

    One can also upload zip with the following zip structure -

    -
    -              .zip
    -              |-- .yaml or .yml
    -              |-- .yaml or .yml
    -              |-- folder1
    -              |   |-- Files required by questions
    -              |-- folder2
    -              |   |-- Files required by questions
    -            
    -
  • -

    - -
    -
    - {% csrf_token %} -
    - -  Download Template -

    -

    Or

    -
    -
    -
    - {{ upload_form }} - -
    -
    - -
    -
    -
    - -
    +
    + +
    + +
    +
    + {% csrf_token %} +
    + +  Download Template +

    +

    Or

    +
    +
    +
    + {{ upload_form }} + +
    +
    + +
    +
    +
    + +
    +
    -
    - + - -
    - {% if messages %} - {% for message in messages %} -
    - - {{ message }} -
    - {% endfor %} - {% endif %} -
    +
    + {% if messages %} + {% for message in messages %} +
    + + {{ message }} +
    + {% endfor %} + {% endif %}
    - -
    -

    Filters Questions:

    - + +
    -

    Or Search using Tags:

    - - {% csrf_token %} -
    -
    -
    + +

    Search using Tags:

    +
    +
    +
    -
    - Search Questions -
    - + - +
    -
    -
    - -
    -

    - + +
    +
    +
    +
    + + +  Clear +
    +
    + +
    +
    + {% csrf_token %}

     Add Question - {% if questions %} + {% if objects %}

    {% include "yaksh/paginator.html" %} @@ -163,24 +162,38 @@
    - + + + + + - {% for question in questions %} + {% for question in objects %} + + + + {% endfor %} @@ -191,23 +204,21 @@ {% else %}

    -

    No Questions created

    +

    No Questions found

    {% endif %}
    - {% if questions %} + {% if objects %} {% endif %}
    - - - + - + {% endblock %} diff --git a/yaksh/urls.py b/yaksh/urls.py index bdc3330..6085c51 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -124,7 +124,7 @@ urlpatterns = [ views.reject, {'was_enrolled': True}, name="reject_user"), url(r'manage/toggle_status/(?P\d+)/$', views.toggle_course_status, name="toggle_course_status"), - url(r'^ajax/questions/filter/$', views.ajax_questions_filter, + url(r'^questions/filter$', views.questions_filter, name="questions_filter"), url(r'^editprofile/$', views.edit_profile, name='edit_profile'), url(r'^viewprofile/$', views.view_profile, name='view_profile'), @@ -205,4 +205,12 @@ urlpatterns = [ views.course_teachers, name="course_teachers"), url(r'^manage/download/course/progress/(?P\d+)', views.download_course_progress, name="download_course_progress"), + url(r'^manage/question/download/(?P\d+)', + views.download_question, name="download_question"), + url(r'^manage/question/test/(?P\d+)', + views.test_question, name="test_question"), + url(r'^manage/question/delete/(?P\d+)', + views.delete_question, name="delete_question"), + url(r'^manage/search/questions', views.search_questions_by_tags, + name="search_questions_by_tags"), ] diff --git a/yaksh/views.py b/yaksh/views.py index 9efcbe9..33a8680 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -1329,36 +1329,6 @@ def monitor(request, quiz_id=None, course_id=None): return my_render_to_response(request, 'yaksh/monitor.html', context) -@csrf_exempt -def ajax_questions_filter(request): - """Ajax call made when filtering displayed questions.""" - - user = request.user - filter_dict = {"user_id": user.id, "active": True} - question_type = request.POST.get('question_type') - marks = request.POST.get('marks') - language = request.POST.get('language') - if question_type: - filter_dict['type'] = str(question_type) - - if marks: - filter_dict['points'] = marks - - if language: - filter_dict['language'] = str(language) - questions = Question.objects.get_queryset().filter( - **filter_dict).order_by('id') - paginator = Paginator(questions, 30) - page = request.GET.get('page') - questions = paginator.get_page(page) - return my_render_to_response( - request, 'yaksh/ajax_question_filter.html', { - 'questions': questions, - 'objects': questions - } - ) - - def _get_questions(user, question_type, marks): if question_type is None and marks is None: return None @@ -1385,12 +1355,16 @@ def _remove_already_present(questionpaper_id, questions): return questions -def _get_questions_from_tags(question_tags, user, active=True): +def _get_questions_from_tags(question_tags, user, active=True, questions=None): search_tags = [] for tags in question_tags: search_tags.extend(re.split('[; |, |\*|\n]', tags)) - return Question.objects.filter(tags__name__in=search_tags, - user=user, active=active).distinct() + if questions: + search = questions.filter(tags__name__in=search_tags) + else: + search = Question.objects.get_queryset().filter( + tags__name__in=search_tags, user=user, active=active).distinct() + return search @login_required @@ -1536,7 +1510,7 @@ def show_all_questions(request): raise Http404("You are not allowed to view this page !") questions = Question.objects.get_queryset().filter( - user_id=user.id, active=True).order_by('id') + user_id=user.id, active=True).order_by('-id') form = QuestionFilterForm(user=user) user_tags = questions.values_list('tags', flat=True).distinct() all_tags = Tag.objects.filter(id__in=user_tags) @@ -1544,11 +1518,8 @@ def show_all_questions(request): paginator = Paginator(questions, 30) page = request.GET.get('page') questions = paginator.get_page(page) - context['questions'] = questions context['objects'] = questions context['all_tags'] = all_tags - context['papers'] = [] - context['question'] = None context['form'] = form context['upload_form'] = upload_form @@ -1576,7 +1547,7 @@ def show_all_questions(request): questions = questions_file.read() message = ques.load_questions(questions, user) else: - message = "Please Upload a ZIP file" + message = "Please Upload a ZIP file or YAML file" if request.POST.get('download') == 'download': question_ids = request.POST.getlist('question') @@ -1600,19 +1571,129 @@ def show_all_questions(request): user, False, question_ids, None) trial_paper.update_total_marks() trial_paper.save() - return my_redirect("/exam/start/1/{0}/{1}/{2}".format( - trial_module.id, trial_paper.id, trial_course.id)) + return my_redirect(reverse("yaksh:start_quiz", + args=[1, trial_module.id, trial_paper.id, trial_course.id] + )) else: message = "Please select atleast one question to test" - if request.POST.get('question_tags'): - question_tags = request.POST.getlist("question_tags") - search_result = _get_questions_from_tags(question_tags, user) - context['questions'] = search_result messages.info(request, message) return my_render_to_response(request, 'yaksh/showquestions.html', context) +@login_required +@email_verified +def questions_filter(request): + """Filter questions by type, language or marks.""" + + user = request.user + if not is_moderator(user): + raise Http404('You are not allowed to view this page!') + + questions = Question.objects.get_queryset().filter( + user_id=user.id, active=True).order_by('-id') + form = QuestionFilterForm(user=user) + user_tags = questions.values_list('tags', flat=True).distinct() + all_tags = Tag.objects.filter(id__in=user_tags) + form = QuestionFilterForm(user=user) + upload_form = UploadFileForm() + filter_dict = {} + question_type = request.GET.get('question_type') + marks = request.GET.get('marks') + language = request.GET.get('language') + if question_type: + filter_dict['type'] = str(question_type) + if marks: + filter_dict['points'] = marks + if language: + filter_dict['language'] = str(language) + questions = questions.filter(**filter_dict).order_by('-id') + paginator = Paginator(questions, 30) + page = request.GET.get('page') + questions = paginator.get_page(page) + context = {'form': form, 'upload_form': upload_form, + 'all_tags': all_tags, 'objects': questions} + return my_render_to_response( + request, 'yaksh/showquestions.html', context + ) + + +@login_required +@email_verified +def delete_question(request, question_id): + user = request.user + if not is_moderator(user): + raise Http404("You are not allowed to view this page !") + + question = get_object_or_404(Question, pk=question_id) + question.active = False + question.save() + messages.success(request, "Deleted Question Successfully") + + return my_redirect(reverse("yaksh:show_questions")) + + +@login_required +@email_verified +def download_question(request, question_id): + user = request.user + if not is_moderator(user): + raise Http404("You are not allowed to view this page !") + + question = Question() + zip_file = question.dump_questions([question_id], user) + response = HttpResponse(content_type='application/zip') + response['Content-Disposition'] = dedent( + '''attachment; filename={0}_question.zip'''.format(user) + ) + zip_file.seek(0) + response.write(zip_file.read()) + return response + + +@login_required +@email_verified +def test_question(request, question_id): + user = request.user + if not is_moderator(user): + raise Http404("You are not allowed to view this page !") + + trial_paper, trial_course, trial_module = test_mode( + user, False, [question_id], None) + trial_paper.update_total_marks() + trial_paper.save() + return my_redirect( + reverse("yaksh:start_quiz", + args=[1, trial_module.id, trial_paper.id, trial_course.id] + ) + ) + +@login_required +@email_verified +def search_questions_by_tags(request): + user = request.user + if not is_moderator(user): + raise Http404("You are not allowed to view this page !") + + questions = Question.objects.get_queryset().filter( + user_id=user.id, active=True).order_by('-id') + form = QuestionFilterForm(user=user) + user_tags = questions.values_list('tags', flat=True).distinct() + all_tags = Tag.objects.filter(id__in=user_tags) + form = QuestionFilterForm(user=user) + upload_form = UploadFileForm() + question_tags = request.GET.getlist("question_tags") + questions = _get_questions_from_tags( + question_tags, user, questions=questions + ) + paginator = Paginator(questions, 30) + page = request.GET.get('page') + questions = paginator.get_page(page) + context = {'form': form, 'upload_form': upload_form, + 'all_tags': all_tags, 'objects': questions} + return my_render_to_response(request, 'yaksh/showquestions.html', context) + + @login_required @email_verified def user_data(request, user_id, questionpaper_id=None, course_id=None): -- cgit From 545bfa28925c0f74df8da6cec41de52ca1838175 Mon Sep 17 00:00:00 2001 From: adityacp Date: Wed, 8 Apr 2020 15:57:27 +0530 Subject: Fix and Add tests for questions view --- yaksh/test_views.py | 73 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 11 deletions(-) (limited to 'yaksh') diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 8f811c5..29a8542 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -4435,6 +4435,7 @@ class TestShowQuestions(TestCase): points=2.0, language="python", type="code", user=self.user, active=True ) + self.question.tags.add("question1") self.question1 = Question.objects.create( summary="Test_question2", description="Add two numbers", points=1.0, language="python", type="mcq", user=self.user, @@ -4504,7 +4505,7 @@ class TestShowQuestions(TestCase): ) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'yaksh/showquestions.html') - self.assertEqual(response.context['questions'][0], self.question) + self.assertEqual(response.context['objects'][0], self.question1) def test_download_questions(self): """ @@ -4655,13 +4656,13 @@ class TestShowQuestions(TestCase): ) trial_course = Course.objects.get(name="trial_course") trial_module = trial_course.learning_module.all()[0] - redirection_url = "/exam/start/1/{0}/{1}/{2}".format( + redirection_url = "/exam/start/1/{0}/{1}/{2}/".format( trial_module.id, trial_que_paper.id, trial_course.id ) self.assertEqual(response.status_code, 302) - self.assertRedirects(response, redirection_url, target_status_code=301) + self.assertRedirects(response, redirection_url, target_status_code=200) - def test_ajax_questions_filter(self): + def test_questions_filter(self): """ Check for filter questions based type, marks and language of a question @@ -4670,15 +4671,15 @@ class TestShowQuestions(TestCase): username=self.user.username, password=self.user_plaintext_pass ) - response = self.client.post( + response = self.client.get( reverse('yaksh:questions_filter'), data={'question_type': 'mcq', 'marks': '1.0', 'language': 'python' } ) self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'yaksh/ajax_question_filter.html') - self.assertEqual(response.context['questions'][0], self.question1) + self.assertTemplateUsed(response, 'yaksh/showquestions.html') + self.assertEqual(response.context['objects'][0], self.question1) def test_download_question_yaml_template(self): """ Test to check download question yaml template """ @@ -4724,13 +4725,63 @@ class TestShowQuestions(TestCase): password=self.user_plaintext_pass ) self.question.tags.add('code') - response = self.client.post( - reverse('yaksh:show_questions'), - data={'question_tags': ['code']} + response = self.client.get( + reverse('yaksh:search_questions_by_tags'), + data={'question_tags': ['question1']} ) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'yaksh/showquestions.html') - self.assertEqual(response.context['questions'][0], self.question) + self.assertEqual(response.context['objects'][0], self.question) + + def test_single_question_attempt(self): + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + response = self.client.get( + reverse('yaksh:test_question', args=[self.question.id]) + ) + trial_que_paper = QuestionPaper.objects.get( + quiz__description="trial_questions" + ) + trial_course = Course.objects.get(name="trial_course") + trial_module = trial_course.learning_module.all()[0] + redirection_url = "/exam/start/1/{0}/{1}/{2}/".format( + trial_module.id, trial_que_paper.id, trial_course.id + ) + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, redirection_url, target_status_code=200) + + def test_single_question_download(self): + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + response = self.client.get( + reverse('yaksh:download_question', args=[self.question.id]) + ) + file_name = "{0}_question.zip".format(self.user) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.get('Content-Disposition'), + "attachment; filename={0}".format(file_name)) + zip_file = string_io(response.content) + zipped_file = zipfile.ZipFile(zip_file, 'r') + self.assertIsNone(zipped_file.testzip()) + self.assertIn('questions_dump.yaml', zipped_file.namelist()) + zip_file.close() + zipped_file.close() + + def test_single_question_delete(self): + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + response = self.client.get( + reverse('yaksh:delete_question', args=[self.question.id]) + ) + self.assertEqual(response.status_code, 302) + updated_que = Question.objects.get(id=self.question.id) + self.assertFalse(updated_que.active) class TestShowStatistics(TestCase): -- cgit From 9cc5865251e896a6be54669ddc4c53c70eaef5ac Mon Sep 17 00:00:00 2001 From: adityacp Date: Sat, 11 Apr 2020 11:00:19 +0530 Subject: Fix tabs and show uploaded questions in questions page --- yaksh/templates/yaksh/showquestions.html | 142 +++++++++++++++---------------- yaksh/views.py | 28 +++--- 2 files changed, 85 insertions(+), 85 deletions(-) (limited to 'yaksh') diff --git a/yaksh/templates/yaksh/showquestions.html b/yaksh/templates/yaksh/showquestions.html index 6f05a0b..8f1bdaf 100644 --- a/yaksh/templates/yaksh/showquestions.html +++ b/yaksh/templates/yaksh/showquestions.html @@ -146,78 +146,78 @@ - -
    - {% csrf_token %} -
    + + {% csrf_token %} +
    +
    + +  Add Question + {% if objects %} +
    +
    + {% include "yaksh/paginator.html" %} +
    +
    Select All
    +
    +
    Select Sr No. Summary  Language  Type  Marks  TestDownloadDelete
    {{forloop.counter}} {{question.summary|capfirst}} {{question.language|capfirst}} {{question.type|capfirst}} {{question.points}} + + Test + + +  Download +  Delete
    + + + + + + + + + + + + + + + {% for question in objects %} + + + + + + + + + + + + {% endfor %} + +
    Select Sr No. Summary  Language  Type  Marks  TestDownloadDelete
    + + {{forloop.counter}}{{question.summary|capfirst}}{{question.language|capfirst}}{{question.type|capfirst}}{{question.points}} + + Test + + +  Download +  Delete
    +
    +
    + {% include "yaksh/paginator.html" %} + {% else %} +

    +
    +

    No Questions found

    +
    + {% endif %} +

    - -  Add Question - {% if objects %} -
    -
    - {% include "yaksh/paginator.html" %} -
    -
    Select All
    -
    - - - - - - - - - - - - - - - - {% for question in objects %} - - - - - - - - - - - - {% endfor %} - -
    Select Sr No. Summary  Language  Type  Marks  TestDownloadDelete
    - - {{forloop.counter}}{{question.summary|capfirst}}{{question.language|capfirst}}{{question.type|capfirst}}{{question.points}} - - Test - - -  Download -  Delete
    -
    -
    - {% include "yaksh/paginator.html" %} - {% else %} -

    -
    -

    No Questions found

    -
    - {% endif %} -
    -
    -
    - {% if objects %} - - - - {% endif %} -
    - +
    + {% if objects %} + + + + {% endif %} +
    + +
    {% endblock %} diff --git a/yaksh/views.py b/yaksh/views.py index 33a8680..336fdee 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -1509,20 +1509,6 @@ def show_all_questions(request): if not is_moderator(user): raise Http404("You are not allowed to view this page !") - questions = Question.objects.get_queryset().filter( - user_id=user.id, active=True).order_by('-id') - form = QuestionFilterForm(user=user) - user_tags = questions.values_list('tags', flat=True).distinct() - all_tags = Tag.objects.filter(id__in=user_tags) - upload_form = UploadFileForm() - paginator = Paginator(questions, 30) - page = request.GET.get('page') - questions = paginator.get_page(page) - context['objects'] = questions - context['all_tags'] = all_tags - context['form'] = form - context['upload_form'] = upload_form - if request.method == 'POST': if request.POST.get('delete') == 'delete': data = request.POST.getlist('question') @@ -1577,6 +1563,20 @@ def show_all_questions(request): else: message = "Please select atleast one question to test" + questions = Question.objects.get_queryset().filter( + user_id=user.id, active=True).order_by('-id') + form = QuestionFilterForm(user=user) + user_tags = questions.values_list('tags', flat=True).distinct() + all_tags = Tag.objects.filter(id__in=user_tags) + upload_form = UploadFileForm() + paginator = Paginator(questions, 30) + page = request.GET.get('page') + questions = paginator.get_page(page) + context['objects'] = questions + context['all_tags'] = all_tags + context['form'] = form + context['upload_form'] = upload_form + messages.info(request, message) return my_render_to_response(request, 'yaksh/showquestions.html', context) -- cgit From 2a9f81cb32acfd7a2efc18f58c4529b39ce4061b Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Sat, 11 Apr 2020 17:45:31 +0530 Subject: Discussion forum for a course --- yaksh/admin.py | 4 +- yaksh/models.py | 38 +++++++++++++++++ yaksh/templates/yaksh/course_forum.html | 68 ++++++++++++++++++++++++++++++ yaksh/templates/yaksh/course_modules.html | 1 + yaksh/templates/yaksh/thread_comments.html | 46 ++++++++++++++++++++ yaksh/urls.py | 2 + yaksh/views.py | 45 +++++++++++++++++++- 7 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 yaksh/templates/yaksh/course_forum.html create mode 100644 yaksh/templates/yaksh/thread_comments.html (limited to 'yaksh') diff --git a/yaksh/admin.py b/yaksh/admin.py index 9c36a98..4489ffc 100644 --- a/yaksh/admin.py +++ b/yaksh/admin.py @@ -1,7 +1,7 @@ from yaksh.models import Question, Quiz, QuestionPaper, Profile from yaksh.models import (TestCase, StandardTestCase, StdIOBasedTestCase, Course, AnswerPaper, CourseStatus, LearningModule, - Lesson + Lesson, Thread, Comment ) from django.contrib import admin @@ -48,6 +48,8 @@ class QuizAdmin(admin.ModelAdmin): admin.site.register(Profile, ProfileAdmin) admin.site.register(Question) admin.site.register(TestCase) +admin.site.register(Thread) +admin.site.register(Comment) admin.site.register(StandardTestCase) admin.site.register(StdIOBasedTestCase) admin.site.register(Course, CourseAdmin) diff --git a/yaksh/models.py b/yaksh/models.py index 52a0414..83c644a 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals, division from datetime import datetime, timedelta +import uuid import json import random import ruamel.yaml @@ -2633,3 +2634,40 @@ class TestCaseOrder(models.Model): order = models.TextField() ############################################################################## +class Thread(models.Model): + uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) + creator = models.ForeignKey(User, on_delete=models.CASCADE) + title = models.CharField(max_length=200) + description = models.TextField() + course = models.ForeignKey(Course, + on_delete=models.CASCADE, related_name='thread') + created_at = models.DateTimeField(auto_now_add=True) + modified_at = models.DateTimeField(auto_now=True) + # image = models.ImageField(upload_to='images/%y/%m/%d', blank=True) + + def __str__(self): + return self.title + + def get_last_comment(self): + return self.comment.last() + + def get_comments_count(self): + return self.comment.count() + +############################################################################## +class Comment(models.Model): + uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) + user = models.ForeignKey(User, on_delete=models.CASCADE) + thread = models.ForeignKey(Thread, + on_delete=models.CASCADE, + related_name='comment') + body = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) + modified_at = models.DateTimeField(auto_now=True) + active = models.BooleanField(default=True) #make it false if improper comment + # image = models.ImageField(upload_to='images/%y/%m/%d', blank=True) + + + def __str__(self): + return 'Comment by {0}: \n {1}'.format(self.user.username, + self.thread.title) diff --git a/yaksh/templates/yaksh/course_forum.html b/yaksh/templates/yaksh/course_forum.html new file mode 100644 index 0000000..b0c7024 --- /dev/null +++ b/yaksh/templates/yaksh/course_forum.html @@ -0,0 +1,68 @@ +{% extends "user.html" %} +{% load humanize %} +{% block title %} + {{course.name}}: Discussion Forum +{% endblock title %} +{% block content %} +
    +
    +

    {{course.name}}

    +
    Discussion Forum
    +
    +
    + +
    + + +
    +
    +
    +

    Threads:

    + {% if threads %} + {% for thread in threads %} +
    +
    + {{thread.title}} +

    {{thread.get_comments_count}}{% if thread.get_comments_count > 1 %} replies{% else %} reply{% endif %}

    +
    + +
    +
    + {% endfor %} + {% else %} + No discussion threads are there yet. Create one to start discussing. + {% endif %} +
    +
    +{% endblock content %} \ No newline at end of file diff --git a/yaksh/templates/yaksh/course_modules.html b/yaksh/templates/yaksh/course_modules.html index dd7b68d..b808562 100644 --- a/yaksh/templates/yaksh/course_modules.html +++ b/yaksh/templates/yaksh/course_modules.html @@ -7,6 +7,7 @@
    {{ course.name }} + Discussion Forum
    {% if course.view_grade %} diff --git a/yaksh/templates/yaksh/thread_comments.html b/yaksh/templates/yaksh/thread_comments.html new file mode 100644 index 0000000..245c363 --- /dev/null +++ b/yaksh/templates/yaksh/thread_comments.html @@ -0,0 +1,46 @@ +{% extends "user.html" %} + +{% block title %} + {{thread.title}} +{% endblock title %} + +{% block content %} +
    + Back to Threads +
    +
    + {{thread.title}} +
    + {{thread.creator.username}} {{thread.created_at}} +
    +
    +

    {{thread.description}}

    +
    +
    +
    +
    + {% if comments %} +
    Comments:
    + {% for comment in comments %} +
    +

    {{comment.body}}

    + by: {{comment.user.username}} . {{comment.created_at}} +
    +
    + {% endfor %} + {% else %} + No comments on this thread. + {% endif %} +
    +
    +
    +
    +
    + {% csrf_token %} + +
    + +
    +
    +
    +{% endblock content %} \ No newline at end of file diff --git a/yaksh/urls.py b/yaksh/urls.py index bdc3330..47cfad4 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -59,6 +59,8 @@ urlpatterns = [ views.get_next_unit, name='next_unit'), url(r'^course_modules/(?P\d+)/$', views.course_modules, name='course_modules'), + url(r'^forum/(?P\d+)/$', views.course_forum, name='course_forum'), + url(r'^forum/(?P\d+)/thread/(?P[0-9a-f-]+)/', views.thread_comments, name='thread_comments'), url(r'^manage/$', views.prof_manage, name='manage'), url(r'^manage/addquestion/$', views.add_question, name="add_question"), url(r'^manage/addquestion/(?P\d+)/$', views.add_question, diff --git a/yaksh/views.py b/yaksh/views.py index 9efcbe9..9350f0a 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -1,6 +1,6 @@ import os import csv -from django.http import HttpResponse, JsonResponse +from django.http import HttpResponse, JsonResponse, HttpResponseRedirect from django.contrib.auth import login, logout, authenticate from django.shortcuts import render, get_object_or_404, redirect from django.template import Context, Template @@ -37,7 +37,7 @@ from yaksh.models import ( QuestionPaper, QuestionSet, Quiz, Question, StandardTestCase, StdIOBasedTestCase, StringTestCase, TestCase, User, get_model_class, FIXTURES_DIR_PATH, MOD_GROUP_NAME, Lesson, LessonFile, - LearningUnit, LearningModule, CourseStatus, question_types + LearningUnit, LearningModule, CourseStatus, question_types, Thread, Comment ) from yaksh.forms import ( UserRegisterForm, UserLoginForm, QuizForm, QuestionForm, @@ -3191,3 +3191,44 @@ def download_course_progress(request, course_id): for student in stud_details: writer.writerow(student) return response + + +def course_forum(request, course_id): + user = request.user + course = get_object_or_404(Course, id=course_id) + threads = course.thread.all().order_by('modified_at') + if request.method == "POST": + title = request.POST['title'] + description = request.POST['description'] + if title and description: + new_thread = Thread.objects.create(title=title, + description=description, + creator=user, course=course) + new_thread.save() + return render(request, 'yaksh/thread_comments.html', { + 'thread': new_thread, + 'course': course, + 'user': user, + }) + return render(request, 'yaksh/course_forum.html', { + 'user': user, + 'course': course, + 'threads': threads + }) + + +def thread_comments(request, course_id, uuid): + thread = get_object_or_404(Thread, uid=uuid) + comments = thread.comment.filter(active=True) + if request.method == "POST": + comment = request.POST.get('comment') + if comment is not None: + new_comment = Comment.objects.create(thread=thread, + body=comment, + user=request.user) + new_comment.save() + return HttpResponseRedirect(request.path_info) + return render(request, 'yaksh/thread_comments.html', { + 'thread': thread, + 'comments': comments + }) \ No newline at end of file -- cgit From 9b7c8c7f6eb3e93f1bc28d118da165a7c600cbb3 Mon Sep 17 00:00:00 2001 From: adityacp Date: Sun, 12 Apr 2020 10:54:54 +0530 Subject: Fix search courses --- yaksh/templates/yaksh/courses.html | 11 ++++++----- yaksh/templates/yaksh/paginator.html | 7 ++++--- yaksh/test_views.py | 4 ++-- yaksh/views.py | 25 ++++++++++++------------- 4 files changed, 24 insertions(+), 23 deletions(-) (limited to 'yaksh') diff --git a/yaksh/templates/yaksh/courses.html b/yaksh/templates/yaksh/courses.html index a590f8e..151e1fb 100644 --- a/yaksh/templates/yaksh/courses.html +++ b/yaksh/templates/yaksh/courses.html @@ -41,8 +41,7 @@
    {% else %}
    -
    - {% csrf_token %} +

    Search/Filter Courses

    @@ -57,9 +56,11 @@

    - - - Clear Search + + +  Clear
    diff --git a/yaksh/templates/yaksh/paginator.html b/yaksh/templates/yaksh/paginator.html index c634d5c..e958519 100644 --- a/yaksh/templates/yaksh/paginator.html +++ b/yaksh/templates/yaksh/paginator.html @@ -1,7 +1,8 @@
      {% if objects.has_previous %}
    • - + @@ -16,13 +17,13 @@ {{ n }}(current)
    • {% elif n > objects.number|add:'-5' and n < objects.number|add:'5' %} -
    • {{ n }}
    • +
    • {{ n }}
    • {% endif %} {% endfor %} {% if objects.has_next %}
    • - + diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 29a8542..ef7c52f 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -2367,9 +2367,9 @@ class TestSearchFilters(TestCase): username=self.user1.username, password=self.user1_plaintext_pass ) - response = self.client.post( + response = self.client.get( reverse('yaksh:courses'), - data={'course_tags': 'demo', 'course_status': 'active'} + data={'search_tags': 'demo', 'search_status': 'active'} ) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'yaksh/courses.html') diff --git a/yaksh/views.py b/yaksh/views.py index 336fdee..1d7a8c6 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -1072,19 +1072,18 @@ def courses(request): form = SearchFilterForm() - if request.method == 'POST': - course_tags = request.POST.get('search_tags') - course_status = request.POST.get('search_status') - - if course_status == 'select' : - courses = courses.filter( - name__contains=course_tags) - elif course_status == 'active' : - courses = courses.filter( - name__contains=course_tags, active=True) - elif course_status == 'closed': - courses = courses.filter( - name__contains=course_tags, active=False) + course_tags = request.GET.get('search_tags') + course_status = request.GET.get('search_status') + + if course_status == 'select' and course_tags: + courses = courses.filter( + name__icontains=course_tags) + elif course_status == 'active' : + courses = courses.filter( + name__icontains=course_tags, active=True) + elif course_status == 'closed': + courses = courses.filter( + name__icontains=course_tags, active=False) paginator = Paginator(courses, 30) page = request.GET.get('page') -- cgit From 0e6c7d589114450d5cd1bc581ee1692c235f1a73 Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Mon, 13 Apr 2020 16:45:42 +0530 Subject: Add feature for uploading images --- yaksh/forms.py | 43 ++++++++++++++++++++++- yaksh/models.py | 46 +++++++++++++++---------- yaksh/static/yaksh/css/custom.css | 9 +++++ yaksh/templates/yaksh/course_forum.html | 14 ++++---- yaksh/templates/yaksh/thread_comments.html | 11 ++++-- yaksh/urls.py | 1 + yaksh/views.py | 55 ++++++++++++++++++------------ 7 files changed, 129 insertions(+), 50 deletions(-) (limited to 'yaksh') diff --git a/yaksh/forms.py b/yaksh/forms.py index 52ef75d..e66c898 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -1,7 +1,7 @@ from django import forms from yaksh.models import ( get_model_class, Profile, Quiz, Question, Course, QuestionPaper, Lesson, - LearningModule, TestCase, languages, question_types + LearningModule, TestCase, languages, question_types, Thread, Comment ) from grades.models import GradingSystem from django.contrib.auth import authenticate @@ -552,3 +552,44 @@ class TestcaseForm(forms.ModelForm): class Meta: model = TestCase fields = ["type"] + + +class ThreadForm(forms.ModelForm): + class Meta: + model = Thread + fields = ["title", "description", "image"] + widgets = { + 'title': forms.TextInput( + attrs = { + 'class': 'form-control' + } + ), + 'description': forms.Textarea( + attrs = { + 'class': 'form-control' + } + ), + 'image': forms.FileInput( + attrs = { + 'class': 'form-control-file' + } + ) + } + + +class CommentForm(forms.ModelForm): + class Meta: + model = Comment + fields = ["description", "image"] + widgets = { + 'description': forms.Textarea( + attrs = { + 'class': 'form-control' + } + ), + 'image': forms.FileInput( + attrs = { + 'class': 'form-control-file' + } + ) + } diff --git a/yaksh/models.py b/yaksh/models.py index 83c644a..f9878e4 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -11,6 +11,7 @@ from collections import Counter, defaultdict from django.db import models from django.contrib.auth.models import User, Group, Permission +from django.core.exceptions import ValidationError from django.contrib.contenttypes.models import ContentType from taggit.managers import TaggableManager from django.utils import timezone @@ -233,6 +234,18 @@ def render_template(template_path, data=None): return render +def validate_image(image): + file_size = image.file.size + limit_mb = 30 + if file_size > limit_mb * 1024 * 1024: + raise ValidationError("Max size of file is {0} MB".format(limit_mb)) + + +def get_image_dir(instance, filename): + return os.sep.join(( + 'thread_%s' % (instance), filename + )) + ############################################################################### class CourseManager(models.Manager): @@ -2634,16 +2647,21 @@ class TestCaseOrder(models.Model): order = models.TextField() ############################################################################## -class Thread(models.Model): +class ForumBase(models.Model): uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) creator = models.ForeignKey(User, on_delete=models.CASCADE) - title = models.CharField(max_length=200) description = models.TextField() - course = models.ForeignKey(Course, - on_delete=models.CASCADE, related_name='thread') created_at = models.DateTimeField(auto_now_add=True) modified_at = models.DateTimeField(auto_now=True) - # image = models.ImageField(upload_to='images/%y/%m/%d', blank=True) + image = models.ImageField(upload_to=get_image_dir, blank=True, + null=True, validators=[validate_image]) + active = models.BooleanField(default=True) + + +class Thread(ForumBase): + title = models.CharField(max_length=200) + course = models.ForeignKey(Course, + on_delete=models.CASCADE, related_name='thread') def __str__(self): return self.title @@ -2654,20 +2672,12 @@ class Thread(models.Model): def get_comments_count(self): return self.comment.count() -############################################################################## -class Comment(models.Model): - uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) - user = models.ForeignKey(User, on_delete=models.CASCADE) - thread = models.ForeignKey(Thread, + +class Comment(ForumBase): + thread_field = models.ForeignKey(Thread, on_delete=models.CASCADE, related_name='comment') - body = models.TextField() - created_at = models.DateTimeField(auto_now_add=True) - modified_at = models.DateTimeField(auto_now=True) - active = models.BooleanField(default=True) #make it false if improper comment - # image = models.ImageField(upload_to='images/%y/%m/%d', blank=True) - def __str__(self): - return 'Comment by {0}: \n {1}'.format(self.user.username, - self.thread.title) + return 'Comment by {0}: {1}'.format(self.creator.username, + self.thread_field.title) diff --git a/yaksh/static/yaksh/css/custom.css b/yaksh/static/yaksh/css/custom.css index 63ee455..d6b9bdc 100644 --- a/yaksh/static/yaksh/css/custom.css +++ b/yaksh/static/yaksh/css/custom.css @@ -97,3 +97,12 @@ body, .dropdown-menu { min-height: 100vh; transition: all 0.3s; } + +/* --------------------------------------------------- + Forum STYLE +----------------------------------------------------- */ + +.comment_image { + width: 350px; + height: 350px; +} \ No newline at end of file diff --git a/yaksh/templates/yaksh/course_forum.html b/yaksh/templates/yaksh/course_forum.html index b0c7024..4741ae0 100644 --- a/yaksh/templates/yaksh/course_forum.html +++ b/yaksh/templates/yaksh/course_forum.html @@ -23,13 +23,10 @@
      -
      +
      {% csrf_token %} - + {{form}}
      diff --git a/yaksh/urls.py b/yaksh/urls.py index 47cfad4..a8aa224 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -61,6 +61,7 @@ urlpatterns = [ views.course_modules, name='course_modules'), url(r'^forum/(?P\d+)/$', views.course_forum, name='course_forum'), url(r'^forum/(?P\d+)/thread/(?P[0-9a-f-]+)/', views.thread_comments, name='thread_comments'), + url(r'^forum/(?P\d+)/thread/(?P[0-9a-f-]+)/delete/', views.delete_thread, name='delete_thread'), url(r'^manage/$', views.prof_manage, name='manage'), url(r'^manage/addquestion/$', views.add_question, name="add_question"), url(r'^manage/addquestion/(?P\d+)/$', views.add_question, diff --git a/yaksh/views.py b/yaksh/views.py index 9350f0a..afa21df 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -44,7 +44,7 @@ from yaksh.forms import ( QuestionFilterForm, CourseForm, ProfileForm, UploadFileForm, FileForm, QuestionPaperForm, LessonForm, LessonFileForm, LearningModuleForm, ExerciseForm, TestcaseForm, - SearchFilterForm + SearchFilterForm, ThreadForm, CommentForm ) from yaksh.settings import SERVER_POOL_PORT, SERVER_HOST_NAME from .settings import URL_ROOT @@ -3193,42 +3193,53 @@ def download_course_progress(request, course_id): return response +@login_required +@email_verified def course_forum(request, course_id): user = request.user course = get_object_or_404(Course, id=course_id) - threads = course.thread.all().order_by('modified_at') + threads = course.thread.all().order_by('-modified_at') if request.method == "POST": - title = request.POST['title'] - description = request.POST['description'] - if title and description: - new_thread = Thread.objects.create(title=title, - description=description, - creator=user, course=course) + form = ThreadForm(request.POST, request.FILES) + if form.is_valid(): + new_thread = form.save(commit=False) + new_thread.creator = user + new_thread.course = course new_thread.save() - return render(request, 'yaksh/thread_comments.html', { - 'thread': new_thread, - 'course': course, - 'user': user, - }) + return redirect('yaksh:thread_comments', + course_id=course.id, uuid=new_thread.uid) + else: + form = ThreadForm() return render(request, 'yaksh/course_forum.html', { 'user': user, 'course': course, - 'threads': threads + 'threads': threads, + 'form': form }) +@login_required +@email_verified def thread_comments(request, course_id, uuid): thread = get_object_or_404(Thread, uid=uuid) comments = thread.comment.filter(active=True) + form = CommentForm() if request.method == "POST": - comment = request.POST.get('comment') - if comment is not None: - new_comment = Comment.objects.create(thread=thread, - body=comment, - user=request.user) + form = CommentForm(request.POST, request.FILES) + if form.is_valid(): + new_comment = form.save(commit=False) + new_comment.creator=request.user + new_comment.thread_field=thread new_comment.save() - return HttpResponseRedirect(request.path_info) + return redirect(request.path_info) return render(request, 'yaksh/thread_comments.html', { 'thread': thread, - 'comments': comments - }) \ No newline at end of file + 'comments': comments, + 'form': form + }) + + +@login_required +@email_verified +def delete_thread(request, course_id, uuid): + thread = get_object_or_404(Thread, uid=uuid) -- cgit From 2f9331717075b34534f2745706f57a98f7dce20d Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Mon, 13 Apr 2020 17:27:49 +0530 Subject: Add feature to hide thread or comments --- yaksh/models.py | 2 +- yaksh/templates/yaksh/course_forum.html | 4 +++- yaksh/templates/yaksh/thread_comments.html | 12 ++++++++++-- yaksh/urls.py | 6 ++++-- yaksh/views.py | 23 +++++++++++++++++++---- 5 files changed, 37 insertions(+), 10 deletions(-) (limited to 'yaksh') diff --git a/yaksh/models.py b/yaksh/models.py index f9878e4..d76d6aa 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -2670,7 +2670,7 @@ class Thread(ForumBase): return self.comment.last() def get_comments_count(self): - return self.comment.count() + return self.comment.filter(active=True).count() class Comment(ForumBase): diff --git a/yaksh/templates/yaksh/course_forum.html b/yaksh/templates/yaksh/course_forum.html index 4741ae0..41dbd7b 100644 --- a/yaksh/templates/yaksh/course_forum.html +++ b/yaksh/templates/yaksh/course_forum.html @@ -57,7 +57,9 @@ {% with thread.get_last_comment as last_comment %} {% if thread.creator.profile.is_moderator %} INSTRUCTOR CREATED {% endif %} Last Post by: {{last_comment.creator}} . {{last_comment.modified_at|naturaltime}} {% endwith %} - Delete + {% if user.profile.is_moderator %} + Delete + {% endif %}

      diff --git a/yaksh/templates/yaksh/thread_comments.html b/yaksh/templates/yaksh/thread_comments.html index ab0ade9..f614b7a 100644 --- a/yaksh/templates/yaksh/thread_comments.html +++ b/yaksh/templates/yaksh/thread_comments.html @@ -11,7 +11,12 @@
      {{thread.title}}
      - {{thread.creator.username}} {{thread.created_at}} + + {{thread.creator.username}} + {{thread.created_at}} + {% if user.profile.is_moderator %}Delete{% endif %} + +

      {{thread.description}}

      @@ -29,7 +34,10 @@ {% endif %}

      {{comment.description}}

      - by: {{comment.user.username}} . {{comment.created_at}} + + by: {{comment.creator.username}} . {{comment.created_at}} + {% if user.profile.is_moderator %}Delete{% endif %} +

      {% endfor %} diff --git a/yaksh/urls.py b/yaksh/urls.py index a8aa224..1b86ae8 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -60,8 +60,10 @@ urlpatterns = [ url(r'^course_modules/(?P\d+)/$', views.course_modules, name='course_modules'), url(r'^forum/(?P\d+)/$', views.course_forum, name='course_forum'), - url(r'^forum/(?P\d+)/thread/(?P[0-9a-f-]+)/', views.thread_comments, name='thread_comments'), - url(r'^forum/(?P\d+)/thread/(?P[0-9a-f-]+)/delete/', views.delete_thread, name='delete_thread'), + url(r'^forum/(?P\d+)/thread/(?P[0-9a-f-]+)/$', views.thread_comments, name='thread_comments'), + url(r'^forum/(?P\d+)/thread/(?P[0-9a-f-]+)/delete/', views.hide_thread, name='hide_thread'), + url(r'^forum/(?P\d+)/comment/(?P[0-9a-f-]+)/delete/', views.hide_comment, name='hide_comment'), + url(r'^manage/$', views.prof_manage, name='manage'), url(r'^manage/addquestion/$', views.add_question, name="add_question"), url(r'^manage/addquestion/(?P\d+)/$', views.add_question, diff --git a/yaksh/views.py b/yaksh/views.py index afa21df..0567f3d 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -3198,7 +3198,7 @@ def download_course_progress(request, course_id): def course_forum(request, course_id): user = request.user course = get_object_or_404(Course, id=course_id) - threads = course.thread.all().order_by('-modified_at') + threads = course.thread.filter(active=True).order_by('-modified_at') if request.method == "POST": form = ThreadForm(request.POST, request.FILES) if form.is_valid(): @@ -3214,13 +3214,15 @@ def course_forum(request, course_id): 'user': user, 'course': course, 'threads': threads, - 'form': form + 'form': form, + 'user': user }) @login_required @email_verified def thread_comments(request, course_id, uuid): + user = request.user thread = get_object_or_404(Thread, uid=uuid) comments = thread.comment.filter(active=True) form = CommentForm() @@ -3235,11 +3237,24 @@ def thread_comments(request, course_id, uuid): return render(request, 'yaksh/thread_comments.html', { 'thread': thread, 'comments': comments, - 'form': form + 'form': form, + 'user': user }) @login_required @email_verified -def delete_thread(request, course_id, uuid): +def hide_thread(request, course_id, uuid): thread = get_object_or_404(Thread, uid=uuid) + thread.comment.active = False + thread.active = False + thread.save() + return redirect('yaksh:course_forum', course_id) + + +def hide_comment(request, course_id, uuid): + comment = get_object_or_404(Comment, uid=uuid) + thread_uid = comment.thread_field.uid + comment.active = False + comment.save() + return redirect('yaksh:thread_comments', course_id, thread_uid) -- cgit From 508e0e78bb0bd3e8ebbad81e948f13de5c01b20f Mon Sep 17 00:00:00 2001 From: CruiseDevice Date: Tue, 14 Apr 2020 20:13:52 +0530 Subject: Change model name Thread to Post to avoid conflicts - Thread class from threading conflicts with the forum Thread model. - Tests for models and views. - PEP8 fix. --- yaksh/admin.py | 4 +- yaksh/forms.py | 20 +- yaksh/models.py | 21 ++- yaksh/templates/yaksh/course_forum.html | 32 ++-- yaksh/templates/yaksh/post_comments.html | 62 +++++++ yaksh/templates/yaksh/thread_comments.html | 59 ------ yaksh/test_models.py | 87 ++++++++- yaksh/test_views.py | 283 ++++++++++++++++++++++++++--- yaksh/urls.py | 17 +- yaksh/views.py | 52 +++--- 10 files changed, 479 insertions(+), 158 deletions(-) create mode 100644 yaksh/templates/yaksh/post_comments.html delete mode 100644 yaksh/templates/yaksh/thread_comments.html (limited to 'yaksh') diff --git a/yaksh/admin.py b/yaksh/admin.py index 4489ffc..3d3ba89 100644 --- a/yaksh/admin.py +++ b/yaksh/admin.py @@ -1,7 +1,7 @@ from yaksh.models import Question, Quiz, QuestionPaper, Profile from yaksh.models import (TestCase, StandardTestCase, StdIOBasedTestCase, Course, AnswerPaper, CourseStatus, LearningModule, - Lesson, Thread, Comment + Lesson, Post, Comment ) from django.contrib import admin @@ -48,7 +48,7 @@ class QuizAdmin(admin.ModelAdmin): admin.site.register(Profile, ProfileAdmin) admin.site.register(Question) admin.site.register(TestCase) -admin.site.register(Thread) +admin.site.register(Post) admin.site.register(Comment) admin.site.register(StandardTestCase) admin.site.register(StdIOBasedTestCase) diff --git a/yaksh/forms.py b/yaksh/forms.py index e66c898..819bb49 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -1,7 +1,7 @@ from django import forms from yaksh.models import ( get_model_class, Profile, Quiz, Question, Course, QuestionPaper, Lesson, - LearningModule, TestCase, languages, question_types, Thread, Comment + LearningModule, TestCase, languages, question_types, Post, Comment ) from grades.models import GradingSystem from django.contrib.auth import authenticate @@ -32,7 +32,7 @@ test_case_types = ( ) status_types = ( - ('select','Select Status'), + ('select', 'Select Status'), ('active', 'Active'), ('closed', 'Inactive'), ) @@ -369,7 +369,7 @@ class SearchFilterForm(forms.Form): search_tags = forms.CharField( label='Search Tags', widget=forms.TextInput(attrs={'placeholder': 'Search', - 'class': form_input_class,}), + 'class': form_input_class, }), required=False ) search_status = forms.CharField(max_length=16, widget=forms.Select( @@ -554,23 +554,23 @@ class TestcaseForm(forms.ModelForm): fields = ["type"] -class ThreadForm(forms.ModelForm): +class PostForm(forms.ModelForm): class Meta: - model = Thread + model = Post fields = ["title", "description", "image"] widgets = { 'title': forms.TextInput( - attrs = { + attrs={ 'class': 'form-control' } ), 'description': forms.Textarea( - attrs = { + attrs={ 'class': 'form-control' } ), 'image': forms.FileInput( - attrs = { + attrs={ 'class': 'form-control-file' } ) @@ -583,12 +583,12 @@ class CommentForm(forms.ModelForm): fields = ["description", "image"] widgets = { 'description': forms.Textarea( - attrs = { + attrs={ 'class': 'form-control' } ), 'image': forms.FileInput( - attrs = { + attrs={ 'class': 'form-control-file' } ) diff --git a/yaksh/models.py b/yaksh/models.py index d76d6aa..424e2f5 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -243,9 +243,10 @@ def validate_image(image): def get_image_dir(instance, filename): return os.sep.join(( - 'thread_%s' % (instance), filename + 'post_%s' % (instance), filename )) + ############################################################################### class CourseManager(models.Manager): @@ -1176,7 +1177,9 @@ class CourseStatus(models.Model): if self.is_course_complete(): self.calculate_percentage() if self.course.grading_system is None: - grading_system = GradingSystem.objects.get(name__contains='default') + grading_system = GradingSystem.objects.get( + name__contains='default' + ) else: grading_system = self.course.grading_system grade = grading_system.get_grade(self.percentage) @@ -2646,6 +2649,7 @@ class TestCaseOrder(models.Model): # Order of the test case for a question. order = models.TextField() + ############################################################################## class ForumBase(models.Model): uid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) @@ -2653,15 +2657,15 @@ class ForumBase(models.Model): description = models.TextField() created_at = models.DateTimeField(auto_now_add=True) modified_at = models.DateTimeField(auto_now=True) - image = models.ImageField(upload_to=get_image_dir, blank=True, + image = models.ImageField(upload_to=get_image_dir, blank=True, null=True, validators=[validate_image]) active = models.BooleanField(default=True) -class Thread(ForumBase): +class Post(ForumBase): title = models.CharField(max_length=200) course = models.ForeignKey(Course, - on_delete=models.CASCADE, related_name='thread') + on_delete=models.CASCADE, related_name='post') def __str__(self): return self.title @@ -2674,10 +2678,9 @@ class Thread(ForumBase): class Comment(ForumBase): - thread_field = models.ForeignKey(Thread, - on_delete=models.CASCADE, - related_name='comment') + post_field = models.ForeignKey(Post, on_delete=models.CASCADE, + related_name='comment') def __str__(self): return 'Comment by {0}: {1}'.format(self.creator.username, - self.thread_field.title) + self.post_field.title) diff --git a/yaksh/templates/yaksh/course_forum.html b/yaksh/templates/yaksh/course_forum.html index 41dbd7b..407296f 100644 --- a/yaksh/templates/yaksh/course_forum.html +++ b/yaksh/templates/yaksh/course_forum.html @@ -10,16 +10,16 @@
      Discussion Forum
      - +
      -
    • +