diff options
27 files changed, 807 insertions, 449 deletions
diff --git a/.travis.yml b/.travis.yml index a2cf266..892898e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ services: before_install: - sudo apt-get update -qq - sudo apt-get install -y scilab + - sudo apt-get install -y r-base - export DISPLAY=:99.0 # command to install dependencies diff --git a/grades/templates/add_grades.html b/grades/templates/add_grades.html index f53a337..198eb4b 100644 --- a/grades/templates/add_grades.html +++ b/grades/templates/add_grades.html @@ -17,22 +17,10 @@ Add/Edit Course </a> </li> - <li class="nav-item dropdown hide"> - <a class="nav-link dropdown-toggle active" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="true">More</a> - <div class="dropdown-menu hide" x-placement="bottom-start" style="position: absolute; transform: translate3d(0px, 37px, 0px); top: 0px; left: 0px; will-change: transform;"> - <a class="dropdown-item" href="{% url 'yaksh:show_all_quizzes' %}"> - View Quizzes - </a> - <a class="dropdown-item" href="{% url 'yaksh:show_all_lessons' %}"> - View Lessons - </a> - <a class="dropdown-item" href="{% url 'yaksh:show_all_modules' %}"> - View Modules - </a> - <a href="{% url 'grades:grading_systems'%}" class="dropdown-item active" > - View Grading Systems + <li class="nav-item"> + <a href="{% url 'grades:grading_systems'%}" class="active nav-link" > + Add/View Grading Systems </a> - </div> </li> </ul> </div> diff --git a/grades/templates/grading_systems.html b/grades/templates/grading_systems.html index 88adfa0..46b3d30 100644 --- a/grades/templates/grading_systems.html +++ b/grades/templates/grading_systems.html @@ -16,22 +16,10 @@ Add/Edit Course </a> </li> - <li class="nav-item dropdown hide"> - <a class="nav-link dropdown-toggle active" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="true">More</a> - <div class="dropdown-menu hide" x-placement="bottom-start" style="position: absolute; transform: translate3d(0px, 37px, 0px); top: 0px; left: 0px; will-change: transform;"> - <a class="dropdown-item" href="{% url 'yaksh:show_all_quizzes' %}"> - View Quizzes - </a> - <a class="dropdown-item" href="{% url 'yaksh:show_all_lessons' %}"> - View Lessons - </a> - <a class="dropdown-item" href="{% url 'yaksh:show_all_modules' %}"> - View Modules - </a> - <a href="{% url 'grades:grading_systems'%}" class="dropdown-item active" > - View Grading Systems + <li class="nav-item"> + <a href="{% url 'grades:grading_systems'%}" class="active nav-link" > + Add/View Grading Systems </a> - </div> </li> </ul> </div> diff --git a/yaksh/evaluator_tests/test_r_evaluation.py b/yaksh/evaluator_tests/test_r_evaluation.py new file mode 100644 index 0000000..b161dc9 --- /dev/null +++ b/yaksh/evaluator_tests/test_r_evaluation.py @@ -0,0 +1,196 @@ +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/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 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/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 @@ +<!doctype html> + +<title>CodeMirror: R mode</title> +<meta charset="utf-8"/> +<link rel=stylesheet href="../../doc/docs.css"> + +<link rel="stylesheet" href="../../lib/codemirror.css"> +<script src="../../lib/codemirror.js"></script> +<script src="r.js"></script> +<style> + .CodeMirror { border-top: 1px solid silver; border-bottom: 1px solid silver; } + .cm-s-default span.cm-semi { color: blue; font-weight: bold; } + .cm-s-default span.cm-dollar { color: orange; font-weight: bold; } + .cm-s-default span.cm-arrow { color: brown; } + .cm-s-default span.cm-arg-is { color: brown; } + </style> +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a> + + <ul> + <li><a href="../../index.html">Home</a> + <li><a href="../../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a href="../index.html">Language modes</a> + <li><a class=active href="#">R</a> + </ul> +</div> + +<article> +<h2>R mode</h2> +<form><textarea id="code" name="code"> +# Code from http://www.mayin.org/ajayshah/KB/R/ + +# FIRST LEARN ABOUT LISTS -- +X = list(height=5.4, weight=54) +print("Use default printing --") +print(X) +print("Accessing individual elements --") +cat("Your height is ", X$height, " and your weight is ", X$weight, "\n") + +# FUNCTIONS -- +square <- function(x) { + return(x*x) +} +cat("The square of 3 is ", square(3), "\n") + + # default value of the arg is set to 5. +cube <- function(x=5) { + return(x*x*x); +} +cat("Calling cube with 2 : ", cube(2), "\n") # will give 2^3 +cat("Calling cube : ", cube(), "\n") # will default to 5^3. + +# LEARN ABOUT FUNCTIONS THAT RETURN MULTIPLE OBJECTS -- +powers <- function(x) { + parcel = list(x2=x*x, x3=x*x*x, x4=x*x*x*x); + return(parcel); +} + +X = powers(3); +print("Showing powers of 3 --"); print(X); + +# WRITING THIS COMPACTLY (4 lines instead of 7) + +powerful <- function(x) { + return(list(x2=x*x, x3=x*x*x, x4=x*x*x*x)); +} +print("Showing powers of 3 --"); print(powerful(3)); + +# In R, the last expression in a function is, by default, what is +# returned. So you could equally just say: +powerful <- function(x) {list(x2=x*x, x3=x*x*x, x4=x*x*x*x)} +</textarea></form> + <script> + var editor = CodeMirror.fromTextArea(document.getElementById("code"), {}); + </script> + + <p><strong>MIME types defined:</strong> <code>text/x-rsrc</code>.</p> + + <p>Development of the CodeMirror R mode was kindly sponsored + by <a href="https://twitter.com/ubalo">Ubalo</a>.</p> + + </article> 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/design_course.js b/yaksh/static/yaksh/js/design_course.js index dbff9fd..4e2dc9d 100644 --- a/yaksh/static/yaksh/js/design_course.js +++ b/yaksh/static/yaksh/js/design_course.js @@ -20,7 +20,11 @@ $(document).ready(function(){ $(this).append('<input type="hidden" name="ordered_list" value='+order_list+'>'); return true; }); - var msg = "Check Prerequisite is set to Yes by default \n" + - "To change, select the Change checkbox and Click Change Prerequisite button \n"; - $("#prereq_msg").attr("title", msg); + var completion_msg = "This will check if the previous module is completed " + + "before viewing the next module." + $("#prereq_msg").attr("title", completion_msg); + $("#prereq_msg").tooltip(); + var completion_msg = "This will check if the previous module is completed " + + "before viewing the next module based on quiz passing status." + $("#prereq_passing_msg").attr("title", completion_msg); });
\ No newline at end of file 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/add_course.html b/yaksh/templates/yaksh/add_course.html index 97c6f56..0072a95 100644 --- a/yaksh/templates/yaksh/add_course.html +++ b/yaksh/templates/yaksh/add_course.html @@ -26,22 +26,10 @@ Add/Edit Course </a> </li> - <li class="nav-item dropdown hide"> - <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="true">More</a> - <div class="dropdown-menu hide" x-placement="bottom-start" style="position: absolute; transform: translate3d(0px, 37px, 0px); top: 0px; left: 0px; will-change: transform;"> - <a class="dropdown-item" href="{% url 'yaksh:show_all_quizzes' %}"> - View Quizzes - </a> - <a class="dropdown-item" href="{% url 'yaksh:show_all_lessons' %}"> - View Lessons - </a> - <a class="dropdown-item" href="{% url 'yaksh:show_all_modules' %}"> - View Modules - </a> - <a href="{% url 'grades:grading_systems'%}" class="dropdown-item" > - View Grading Systems + <li class="nav-item"> + <a href="{% url 'grades:grading_systems'%}" class="nav-link" > + Add/View Grading Systems </a> - </div> </li> </ul> </div> diff --git a/yaksh/templates/yaksh/add_exercise.html b/yaksh/templates/yaksh/add_exercise.html index d3d9068..542d1c4 100644 --- a/yaksh/templates/yaksh/add_exercise.html +++ b/yaksh/templates/yaksh/add_exercise.html @@ -23,17 +23,9 @@ </div> {% endfor %} {% endif %} - {% if course_id %} - <a class="btn btn-primary" href="{% url 'yaksh:get_course_modules' course_id %}"> - <i class="fa fa-arrow-left"></i> - Back - </a> - {% else %} - <a class="btn btn-primary" href="{% url 'yaksh:show_all_quizzes' %}"> - <i class="fa fa-arrow-left"></i> - Back - </a> - {% endif %} + <a class="btn btn-primary" href="{% url 'yaksh:get_course_modules' course_id %}"> + <i class="fa fa-arrow-left"></i> Back + </a> <br><br> <form name=frm id=frm action="" method="post" > {% csrf_token %} diff --git a/yaksh/templates/yaksh/add_lesson.html b/yaksh/templates/yaksh/add_lesson.html index 99fc31a..b984db0 100644 --- a/yaksh/templates/yaksh/add_lesson.html +++ b/yaksh/templates/yaksh/add_lesson.html @@ -26,17 +26,9 @@ <div class="container"> <div class="row justify-content-center form-group"> <div class="col-md-9 col-md-offset-4"> - {% if course_id %} <a class="btn btn-primary" href="{% url 'yaksh:get_course_modules' course_id %}"> - <i class="fa fa-arrow-left"></i> - Back + <i class="fa fa-arrow-left"></i> Back </a> - {% else %} - <a class="btn btn-primary" href="{% url 'yaksh:show_all_lessons' %}"> - <i class="fa fa-arrow-left"></i> - Back - </a> - {% endif %} <br> {% if messages %} <br> diff --git a/yaksh/templates/yaksh/add_module.html b/yaksh/templates/yaksh/add_module.html index edbfaa2..262c009 100644 --- a/yaksh/templates/yaksh/add_module.html +++ b/yaksh/templates/yaksh/add_module.html @@ -110,7 +110,7 @@ <!-- Add learning Units --> {% if status == "design" %} <div class="container"> -<center><h3><u>Add/Edit Learning Units</h3></u></center> +<center><h2><u>{{module.name}}</u></h2></center> {% if course_id %} <form action="{% url 'yaksh:design_module' module_id course_id %}" method="POST" id="design_course_form"> {% else %} @@ -161,8 +161,7 @@ <th width="25%" colspan="2">Check Prerequisite <br> <a href="#" data-toggle="tooltip" id="prereq_msg"> - <span class="glyphicon glyphicon-question-sign"> - </span> What's This + What's This <i class="fa fa-question-circle"></i> </a> </th> </tr> diff --git a/yaksh/templates/yaksh/add_quiz.html b/yaksh/templates/yaksh/add_quiz.html index 5497eeb..55e3bd6 100644 --- a/yaksh/templates/yaksh/add_quiz.html +++ b/yaksh/templates/yaksh/add_quiz.html @@ -35,17 +35,9 @@ </div> {% endfor %} {% endif %} - {% if course_id %} - <a class="btn btn-primary" href="{% url 'yaksh:get_course_modules' course_id %}"> - <i class="fa fa-arrow-left"></i> - Back - </a> - {% else %} - <a class="btn btn-primary" href="{% url 'yaksh:show_all_quizzes' %}"> - <i class="fa fa-arrow-left"></i> - Back - </a> - {% endif %} + <a class="btn btn-primary" href="{% url 'yaksh:get_course_modules' course_id %}"> + <i class="fa fa-arrow-left"></i> Back + </a> <br><br> <form name=frm id=frm action="" method="post" > {% csrf_token %} diff --git a/yaksh/templates/yaksh/course_added_modules.html b/yaksh/templates/yaksh/course_added_modules.html index c70eb7a..2d194b9 100644 --- a/yaksh/templates/yaksh/course_added_modules.html +++ b/yaksh/templates/yaksh/course_added_modules.html @@ -1,46 +1,113 @@ {% if is_modules %} {% block pagetitle %} <center> <h3>Course Modules</h3> </center> {% endblock %} + <a href="{% url 'yaksh:add_module' course.id %}" class="btn btn-primary btn-lg"> + <i class="fa fa-plus-circle"></i> Add Module + </a> + <br><br> {% if modules %} - <table class="table table-responsive"> - <tr> - <th>Module</th> - <th>Module Design</th> - <th>Lessons/Quizzes</th> - </tr> + <center> + <div class="alert alert-dismissible alert-info"> + <strong> + For additional module settings, Click on Design Module + </strong> + </div> + </center> {% for module in modules %} - <tr> - <td> - <a href="{% url 'yaksh:edit_module' module.id course.id %}"> - {{module.name}}</a> - </td> - <td> - <a href="{% url 'yaksh:design_module' module.id course.id %}"> - Add Quizzes/Lessons for {{module.name}} - </a> - </td> - <td> - {% for unit in module.get_learning_units %} - <ul class="inputs-list"> - <li> - {% if unit.type == "quiz" %} - {% if unit.quiz.is_exercise %} - <a href="{% url 'yaksh:edit_exercise' unit.quiz.id course.id %}"> - {{unit.quiz.description}}</a> + <div class="card"> + <div class="card-header"> + <a href="{% url 'yaksh:edit_module' course.id module.id %}"> + <i class="fa fa-edit"></i> {{module.name}} + </a> + </div> + <div class="card-body"> + <div class="row"> + <div class="col"> + <a href="{% url 'yaksh:edit_lesson' course.id module.id %}" class="btn btn-info"> + <i class="fa fa-plus-circle"></i> Add Lesson + </a> + </div> + <div class="col"> + <a href="{% url 'yaksh:add_quiz' course.id module.id %}" class="btn btn-success"> + <i class="fa fa-plus-circle"></i> Add Quiz + </a> + </div> + <div class="col"> + <a href="{% url 'yaksh:add_exercise' course.id module.id %}" class="btn btn-dark"> + <i class="fa fa-plus-circle"></i> Add Exercise + </a> + </div> + <div class="col"> + <a href="{% url 'yaksh:design_module' module.id course.id %}" class="btn btn-secondary"> + Design Module + </a> + </div> + </div> + <br> + {% with module.get_learning_units as units %} + {% if units %} + <p><b><u>Lessons/Quizzes/Exercise</u></b><p> + <table class="table table-responsive-sm"> + {% for unit in units %} + <tr> + <td> + {% if unit.type == "quiz" %} + {% if unit.quiz.is_exercise %} + <a href="{% url 'yaksh:edit_exercise' course.id module.id unit.quiz.id %}"> + {{unit.quiz.description}}</a> + {% else %} + <a href="{% url 'yaksh:edit_quiz' course.id module.id unit.quiz.id %}"> + {{unit.quiz.description}}</a> + {% endif %} + {% else %} + <a href="{% url 'yaksh:edit_lesson' course.id module.id unit.lesson.id %}"> + {{unit.lesson.name}}</a> + {% endif %} + </td> + <td> + {% if unit.type == "quiz" %} + {% with unit.quiz as quiz %} + {% if quiz.questionpaper_set.get.id %} + <a href="{% url 'yaksh:designquestionpaper' course.id quiz.id quiz.questionpaper_set.get.id %}" class="btn btn-primary"> + <i class="fa fa-edit"></i> + Edit Question Paper + </a> + {% else %} + <a href="{% url 'yaksh:designquestionpaper' course.id quiz.id %}" class="btn btn-success"> + <i class="fa fa-plus-circle"></i> + Add Question Paper + </a> + {% endif %} + {% endwith %} + {% else %} + ------- + {% endif %} + </td> + <td> + {% if unit.type == "quiz" %} + {% if unit.quiz.is_exercise %} + Exercise + {% else %} + Quiz + {% endif %} + {% else %} + Lesson + {% endif %} + </td> + </tr> + {% endfor %} + </table> {% else %} - <a href="{% url 'yaksh:edit_quiz' unit.quiz.id course.id %}"> - {{unit.quiz.description}}</a> + <center> + <span class="badge badge-warning"> + <big>No lesson/quiz added</big> + </span> + </center> {% endif %} - {% else %} - <a href="{% url 'yaksh:edit_lesson' unit.lesson.id course.id %}"> - {{unit.lesson.name}}</a> - {% endif %} - </li> - </ul> - {% endfor %} - </td> - </tr> - {% endfor %} <!-- end for modules --> - </table> + {% endwith %} + </div> + </div> + <br> + {% endfor %} {% else %} <center> <span class="badge badge-warning"><big>No learning modules</big></span> diff --git a/yaksh/templates/yaksh/course_detail_options.html b/yaksh/templates/yaksh/course_detail_options.html index 6f9a711..90662d6 100644 --- a/yaksh/templates/yaksh/course_detail_options.html +++ b/yaksh/templates/yaksh/course_detail_options.html @@ -5,28 +5,28 @@ </a> </li> <li class="nav-item"> - <a href="{% url 'yaksh:course_students' course.id %}" id="enroll-students" class="nav-link list-group-item {% if is_students %} active {% endif %}" title="View the course requested, rejected and added students" data-placement="top" data-toggle="tooltip"> + <a href="{% url 'yaksh:course_students' course.id %}" id="enroll-students" class="nav-link list-group-item {% if is_students %} active {% endif %}" title="View the course requested, rejected and enrolled students" data-placement="top" data-toggle="tooltip"> Enroll Students </a> </li> <li class="nav-item"> - <a href="{% url 'yaksh:send_mail' course.id %}" class="nav-link list-group-item {% if is_mail %} active {% endif %}" title="Send mail to course students" data-placement="top" data-toggle="tooltip"> - Send Mail + <a class="nav-link list-group-item {% if is_modules %} active {% endif %}" href="{% url 'yaksh:get_course_modules' course.id %}" title="View modules added to the course" data-placement="top" data-toggle="tooltip"> + Course Modules </a> </li> <li class="nav-item"> - <a href="{% url 'yaksh:course_status' course.id %}" class="nav-link list-group-item {% if is_progress %} active {% endif %}" title="View Students course progress" data-placement="top" data-toggle="tooltip"> - Course Progress + <a class="nav-link list-group-item {% if is_design_course %} active {% endif %}" href="{% url 'yaksh:design_course' course.id %}" title="Additional course settings" data-placement="top" data-toggle="tooltip"> + Design Course </a> </li> <li class="nav-item"> - <a class="nav-link list-group-item {% if is_design_course %} active {% endif %}" href="{% url 'yaksh:design_course' course.id %}" title="Add modules to this course" data-placement="top" data-toggle="tooltip"> - Design Course + <a href="{% url 'yaksh:course_status' course.id %}" class="nav-link list-group-item {% if is_progress %} active {% endif %}" title="View Students course progress" data-placement="top" data-toggle="tooltip"> + Course Progress </a> </li> <li class="nav-item"> - <a class="nav-link list-group-item {% if is_modules %} active {% endif %}" href="{% url 'yaksh:get_course_modules' course.id %}" title="View modules added to the course" data-placement="top" data-toggle="tooltip"> - Course Modules + <a href="{% url 'yaksh:send_mail' course.id %}" class="nav-link list-group-item {% if is_mail %} active {% endif %}" title="Send mail to course students" data-placement="top" data-toggle="tooltip"> + Send Mail </a> </li> <li class="nav-item"> 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 </a> {% else %} - <a href="{% url 'yaksh:start_quiz' unit.quiz.questionpaper_set.get.id module.id course.id %}" class="btn btn-outline-info"> - View - </a> + {% if unit.quiz.questionpaper_set.get %} + <a href="{% url 'yaksh:start_quiz' unit.quiz.questionpaper_set.get.id module.id course.id %}" class="btn btn-outline-info"> + View + </a> + {% endif %} {% endif %} </td> <td> diff --git a/yaksh/templates/yaksh/courses.html b/yaksh/templates/yaksh/courses.html index 084d0f6..a590f8e 100644 --- a/yaksh/templates/yaksh/courses.html +++ b/yaksh/templates/yaksh/courses.html @@ -8,14 +8,6 @@ </script> {% endblock %} -{% block css %} -<style> - .test + .tooltip.top > .tooltip-inner { - padding: 15px; - font-size: 12px; - } -</style> -{% endblock %} {% block content %} <div class="container-fluid"> <div class="container"> @@ -32,22 +24,10 @@ Add/Edit Course </a> </li> - <li class="nav-item dropdown hide"> - <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="true">More</a> - <div class="dropdown-menu hide" x-placement="bottom-start" style="position: absolute; transform: translate3d(0px, 37px, 0px); top: 0px; left: 0px; will-change: transform;"> - <a class="dropdown-item" href="{% url 'yaksh:show_all_quizzes' %}"> - Add/View Quizzes - </a> - <a class="dropdown-item" href="{% url 'yaksh:show_all_lessons' %}"> - Add/View Lessons - </a> - <a class="dropdown-item" href="{% url 'yaksh:show_all_modules' %}"> - Add/View Modules - </a> - <a href="{% url 'grades:grading_systems'%}" class="dropdown-item" > + <li class="nav-item"> + <a href="{% url 'grades:grading_systems'%}" class="nav-link" > Add/View Grading Systems </a> - </div> </li> </ul> </div> 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 @@ <th width="25%" colspan="2">Check Prerequisite Completion <br> <a href="#" data-toggle="tooltip" id="prereq_msg"> - <span class="glyphicon glyphicon-question-sign"> - </span> What's This + What's This <i class="fa fa-question-circle"></i> </a> </th> <th width="25%" colspan="2">Check Prerequisite Passing <br> <a href="#" data-toggle="tooltip" id="prereq_passing_msg"> - <span class="glyphicon glyphicon-question-sign"> - </span> What's This - </a> + What's This <i class="fa fa-question-circle"></i> + </a> </th> </tr> <tr> diff --git a/yaksh/templates/yaksh/design_questionpaper.html b/yaksh/templates/yaksh/design_questionpaper.html index 6e916a3..ffbdf5f 100644 --- a/yaksh/templates/yaksh/design_questionpaper.html +++ b/yaksh/templates/yaksh/design_questionpaper.html @@ -17,17 +17,10 @@ {% block content %} <div class="container"> <input type=hidden id="url_root" value={{ URL_ROOT }}> -{% if course_id %} - <form action="{% url 'yaksh:designquestionpaper' qpaper.quiz.id qpaper.id course_id %}" method="POST" id="design_q"> + <form action="{% url 'yaksh:designquestionpaper' course_id qpaper.quiz.id qpaper.id %}" method="POST" id="design_q"> <a href="{% url 'yaksh:get_course_modules' course_id %}" class="btn btn-primary"> <i class="fa fa-arrow-left"></i> Back </a> -{% else %} - <form action="{% url 'yaksh:designquestionpaper' qpaper.quiz.id qpaper.id %}" method="POST" id="design_q"> - <a href="{% url 'yaksh:show_all_quizzes' %}" class="btn btn-primary"> - <i class="fa fa-arrow-left"></i> Back - </a> -{% endif %} {% csrf_token %} <input type=hidden name="is_active" id="is_active" value="{{ state }}"> <center><b>Manual mode to design the {{lang}} Question Paper</center><br> 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 @@ <script src="{% static 'yaksh/js/codemirror/mode/python/python.js' %}"></script> <script src="{% static 'yaksh/js/codemirror/mode/clike/clike.js' %}"></script> <script src="{% static 'yaksh/js/codemirror/mode/shell/shell.js' %}"></script> +<script src="{% static 'yaksh/js/codemirror/mode/r/r.js' %}"></script> <script type="text/javascript" src="{% static 'yaksh/js/mathjax/MathJax.js' %}?config=TeX-MML-AM_CHTML"></script> <script src="{% static 'yaksh/js/jquery-sortable.js' %}"></script> <script> diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 569d4d7..8f811c5 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -1083,6 +1083,10 @@ class TestAddQuiz(TestCase): enrollment="Enroll Request", creator=self.user ) + self.module = LearningModule.objects.create( + name="My test module", creator=self.user, description="Test" + ) + self.quiz = Quiz.objects.create( start_date_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone), end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone), @@ -1099,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() @@ -1112,10 +1122,17 @@ class TestAddQuiz(TestCase): """ If not logged in redirect to login page """ - response = self.client.get(reverse('yaksh:add_quiz'), - follow=True - ) - redirect_destination = '/exam/login/?next=/exam/manage/addquiz/' + response = self.client.get( + reverse('yaksh:add_quiz', + kwargs={'course_id': self.course.id, + 'module_id': self.module.id}), + follow=True + ) + redirect_destination = ( + '/exam/login/?next=/exam/manage/addquiz/{0}/{1}/'.format( + self.course.id, self.module.id + ) + ) self.assertRedirects(response, redirect_destination) def test_add_quiz_denies_non_moderator(self): @@ -1126,9 +1143,12 @@ class TestAddQuiz(TestCase): username=self.student.username, password=self.student_plaintext_pass ) - response = self.client.get(reverse('yaksh:add_quiz'), - follow=True - ) + response = self.client.get( + reverse('yaksh:add_quiz', + kwargs={'course_id': self.course.id, + 'module_id': self.module.id}), + follow=True + ) self.assertEqual(response.status_code, 404) def test_add_quiz_get(self): @@ -1139,8 +1159,12 @@ class TestAddQuiz(TestCase): username=self.user.username, password=self.user_plaintext_pass ) - response = self.client.get(reverse('yaksh:add_quiz') - ) + response = self.client.get( + reverse('yaksh:add_quiz', + kwargs={'course_id': self.course.id, + 'module_id': self.module.id} + ) + ) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'yaksh/add_quiz.html') self.assertIsNotNone(response.context['form']) @@ -1156,7 +1180,9 @@ class TestAddQuiz(TestCase): tzone = pytz.timezone('UTC') response = self.client.post( reverse('yaksh:edit_quiz', - kwargs={'quiz_id': self.quiz.id}), + kwargs={'course_id': self.course.id, + 'module_id': self.module.id, + 'quiz_id': self.quiz.id}), data={ 'start_date_time': '2016-01-10 09:00:15', 'end_date_time': '2016-01-15 09:00:15', @@ -1198,7 +1224,9 @@ class TestAddQuiz(TestCase): tzone = pytz.timezone('UTC') response = self.client.post( - reverse('yaksh:add_quiz'), + reverse('yaksh:add_quiz', + kwargs={'course_id': self.course.id, + 'module_id': self.module.id}), data={ 'start_date_time': '2016-01-10 09:00:15', 'end_date_time': '2016-01-15 09:00:15', @@ -1233,11 +1261,18 @@ class TestAddQuiz(TestCase): """ If not logged in redirect to login page """ - response = self.client.get(reverse('yaksh:add_exercise'), - follow=True - ) - redirect_destination = '/exam/login/?next=/exam/manage/add_exercise/' - self.assertRedirects(response, redirect_destination) + response = self.client.get( + reverse('yaksh:add_exercise', + kwargs={'course_id': self.course.id, + 'module_id': self.module.id}), + follow=True + ) + redirect = ( + '/exam/login/?next=/exam/manage/add_exercise/{0}/{1}/'.format( + self.course.id, self.module.id + ) + ) + self.assertRedirects(response, redirect) def test_add_exercise_denies_non_moderator(self): """ @@ -1247,9 +1282,12 @@ class TestAddQuiz(TestCase): username=self.student.username, password=self.student_plaintext_pass ) - response = self.client.get(reverse('yaksh:add_exercise'), - follow=True - ) + response = self.client.get( + reverse('yaksh:add_exercise', + kwargs={'course_id': self.course.id, + 'module_id': self.module.id}), + follow=True + ) self.assertEqual(response.status_code, 404) def test_add_exercise_get(self): @@ -1260,8 +1298,11 @@ class TestAddQuiz(TestCase): username=self.user.username, password=self.user_plaintext_pass ) - response = self.client.get(reverse('yaksh:add_exercise') - ) + response = self.client.get( + reverse('yaksh:add_exercise', + kwargs={'course_id': self.course.id, + 'module_id': self.module.id}) + ) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'yaksh/add_exercise.html') self.assertIsNotNone(response.context['form']) @@ -1276,7 +1317,9 @@ class TestAddQuiz(TestCase): ) response = self.client.post( reverse('yaksh:edit_exercise', - kwargs={'quiz_id': self.exercise.id}), + kwargs={'course_id': self.course.id, + 'module_id': self.module.id, + 'quiz_id': self.exercise.id}), data={ 'description': 'updated demo exercise', 'active': True @@ -1301,7 +1344,9 @@ class TestAddQuiz(TestCase): password=self.user_plaintext_pass ) response = self.client.post( - reverse('yaksh:add_exercise'), + reverse('yaksh:add_exercise', + kwargs={'course_id': self.course.id, + 'module_id': self.module.id}), data={ 'description': "Demo Exercise", 'active': True @@ -1317,19 +1362,6 @@ class TestAddQuiz(TestCase): self.assertEqual(new_exercise.pass_criteria, 0) self.assertTrue(new_exercise.is_exercise) - def test_show_all_quizzes(self): - self.client.login( - username=self.user.username, - password=self.user_plaintext_pass - ) - response = self.client.get( - reverse('yaksh:show_all_quizzes'), - follow=True - ) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.context['quizzes'][0], self.quiz) - self.assertTemplateUsed(response, "yaksh/quizzes.html") - class TestAddAsModerator(TestCase): def setUp(self): @@ -2344,51 +2376,6 @@ class TestSearchFilters(TestCase): self.assertIsNotNone(response.context['form']) self.assertIn(self.user1_course1, response.context['courses']) - def test_quizzes_search_filter(self): - """ Test to check if quizzes are obtained with tags and status """ - self.client.login( - username=self.user1.username, - password=self.user1_plaintext_pass - ) - response = self.client.post( - reverse('yaksh:show_all_quizzes'), - data={'quiz_tags': 'demo', 'quiz_status': 'active'} - ) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'yaksh/quizzes.html') - self.assertIsNotNone(response.context['form']) - self.assertIn(self.quiz1, response.context['quizzes']) - - def test_lessons_search_filter(self): - """ Test to check if lessons are obtained with tags and status """ - self.client.login( - username=self.user1.username, - password=self.user1_plaintext_pass - ) - response = self.client.post( - reverse('yaksh:show_all_lessons'), - data={'lesson_tags': 'demo', 'lesson_status': 'active'} - ) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'yaksh/lessons.html') - self.assertIsNotNone(response.context['form']) - self.assertIn(self.lesson1, response.context['lessons']) - - def test_learning_modules_search_filter(self): - """ Test to check if learning modules are obtained with tags and status """ - self.client.login( - username=self.user1.username, - password=self.user1_plaintext_pass - ) - response = self.client.post( - reverse('yaksh:show_all_modules'), - data={'module_tags': 'demo', 'module_status': 'active'} - ) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, 'yaksh/modules.html') - self.assertIsNotNone(response.context['form']) - self.assertIn(self.learning_module1, response.context['modules']) - class TestAddCourse(TestCase): def setUp(self): @@ -5491,14 +5478,17 @@ class TestQuestionPaper(TestCase): response = self.client.get( reverse('yaksh:designquestionpaper', - kwargs={"quiz_id": self.demo_quiz.id, - "questionpaper_id": self.question_paper.id})) + kwargs={ + "course_id": self.course.id, + "quiz_id": self.demo_quiz.id, + "questionpaper_id": self.question_paper.id})) self.assertEqual(response.status_code, 404) # Design question paper for a quiz response = self.client.post( reverse('yaksh:designquestionpaper', - kwargs={"quiz_id": self.quiz_without_qp.id}), + kwargs={"course_id": self.course.id, + "quiz_id": self.quiz_without_qp.id}), data={"marks": "1.0", "question_type": "code"}) self.assertEqual(response.status_code, 200) self.assertIsNotNone(response.context['questions']) @@ -5511,7 +5501,8 @@ class TestQuestionPaper(TestCase): response = self.client.get( reverse('yaksh:designquestionpaper', - kwargs={"quiz_id": self.demo_quiz.id, + kwargs={"course_id": self.course.id, + "quiz_id": self.demo_quiz.id, "questionpaper_id": self.question_paper.id})) self.assertEqual(response.status_code, 404) @@ -5523,7 +5514,8 @@ class TestQuestionPaper(TestCase): # Should not allow teacher to view question paper response = self.client.get( reverse('yaksh:designquestionpaper', - kwargs={"quiz_id": self.quiz.id, + kwargs={"course_id": self.course.id, + "quiz_id": self.quiz.id, "questionpaper_id": self.question_paper.id})) self.assertEqual(response.status_code, 404) @@ -5792,14 +5784,14 @@ class TestLearningModule(TestCase): ) # Student tries to add learning module - response = self.client.post(reverse('yaksh:add_module'), - data={"name": "test module1", - "description": "my test1", - "Save": "Save"}) + response = self.client.post( + reverse('yaksh:add_module', kwargs={"course_id": self.course.id}), + data={"name": "test module1", + "description": "my test1", + "Save": "Save"}) self.assertEqual(response.status_code, 404) # Student tries to view learning modules - response = self.client.get(reverse('yaksh:show_all_modules')) self.assertEqual(response.status_code, 404) def test_add_new_module(self): @@ -5810,10 +5802,11 @@ class TestLearningModule(TestCase): ) # Test add new module - response = self.client.post(reverse('yaksh:add_module'), - data={"name": "test module1", - "description": "my test1", - "Save": "Save"}) + response = self.client.post( + reverse('yaksh:add_module', kwargs={"course_id": self.course.id}), + data={"name": "test module1", + "description": "my test1", + "Save": "Save"}) self.assertEqual(response.status_code, 200) learning_module = LearningModule.objects.get(name="test module1") @@ -5833,7 +5826,8 @@ class TestLearningModule(TestCase): # Test add new module response = self.client.post( reverse('yaksh:edit_module', - kwargs={"module_id": self.learning_module.id}), + kwargs={"course_id": self.course.id, + "module_id": self.learning_module.id}), data={"name": "test module2", "description": "my test2", "Save": "Save"}) @@ -5846,17 +5840,6 @@ class TestLearningModule(TestCase): self.assertEqual(learning_module.html_data, Markdown().convert("my test2")) - def test_show_all_modules(self): - """Try to get all learning modules""" - self.client.login( - username=self.user.username, - password=self.user_plaintext_pass - ) - response = self.client.get(reverse('yaksh:show_all_modules')) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.context['modules'][0], - self.learning_module) - def test_teacher_can_edit_module(self): """ Check if teacher can edit the module """ self.client.login( @@ -6178,7 +6161,8 @@ class TestLessons(TestCase): response = self.client.get( reverse('yaksh:edit_lesson', kwargs={"lesson_id": self.lesson.id, - "course_id": self.course.id})) + "course_id": self.course.id, + "module_id": self.learning_module.id})) self.assertEqual(response.status_code, 404) def test_teacher_can_edit_lesson(self): @@ -6192,7 +6176,8 @@ class TestLessons(TestCase): response = self.client.post( reverse('yaksh:edit_lesson', kwargs={"lesson_id": self.lesson.id, - "course_id": self.course.id}), + "course_id": self.course.id, + "module_id": self.learning_module.id}), data={"name": "updated lesson", "description": "updated description", "Lesson_files": dummy_file, @@ -6201,7 +6186,7 @@ class TestLessons(TestCase): ) # Teacher edits existing lesson and adds file - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 302) updated_lesson = Lesson.objects.get(name="updated lesson") self.assertEqual(updated_lesson.description, "updated description") self.assertEqual(updated_lesson.creator, self.user) @@ -6216,7 +6201,8 @@ class TestLessons(TestCase): response = self.client.post( reverse('yaksh:edit_lesson', kwargs={"lesson_id": self.lesson.id, - "course_id": self.course.id}), + "course_id": self.course.id, + "module_id": self.learning_module.id}), data={"delete_files": [str(lesson_files.id)], "Delete": "Delete"} ) @@ -6287,17 +6273,6 @@ class TestLessons(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.context["msg"], err_msg) - def test_show_all_lessons(self): - """ Moderator should be able to see all created lessons""" - self.client.login( - username=self.user.username, - password=self.user_plaintext_pass - ) - response = self.client.get(reverse('yaksh:show_all_lessons')) - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, "yaksh/lessons.html") - self.assertEqual(response.context["lessons"][0], self.lesson) - def test_preview_lesson_description(self): """ Test preview lesson description converted from md to html""" self.client.login( diff --git a/yaksh/urls.py b/yaksh/urls.py index b53d335..bdc3330 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -63,15 +63,13 @@ urlpatterns = [ url(r'^manage/addquestion/$', views.add_question, name="add_question"), url(r'^manage/addquestion/(?P<question_id>\d+)/$', views.add_question, name="add_question"), - url(r'^manage/addquiz/$', views.add_quiz, name='add_quiz'), - url(r'^manage/add_exercise/$', views.add_exercise, name='add_exercise'), - url(r'^manage/add_exercise/(?P<quiz_id>\d+)/$', views.add_exercise, - name='edit_exercise'), - url(r'^manage/add_exercise/(?P<quiz_id>\d+)/(?P<course_id>\d+)/$', + url(r'^manage/add_exercise/(?P<course_id>\d+)/(?P<module_id>\d+)/$', + views.add_exercise, name='add_exercise'), + url(r'^manage/add_exercise/(?P<course_id>\d+)/(?P<module_id>\d+)/(?P<quiz_id>\d+)$', views.add_exercise, name='edit_exercise'), - url(r'^manage/addquiz/(?P<quiz_id>\d+)/$', - views.add_quiz, name='edit_quiz'), - url(r'^manage/addquiz/(?P<quiz_id>\d+)/(?P<course_id>\d+)$', + url(r'^manage/addquiz/(?P<course_id>\d+)/(?P<module_id>\d+)/$', + views.add_quiz, name='add_quiz'), + url(r'^manage/addquiz/(?P<course_id>\d+)/(?P<module_id>\d+)/(?P<quiz_id>\d+)$', views.add_quiz, name='edit_quiz'), url(r'^manage/gradeuser/$', views.grade_user, name="grade_user"), url(r'^manage/gradeuser/(?P<quiz_id>\d+)/(?P<course_id>\d+)/$', @@ -91,13 +89,10 @@ urlpatterns = [ '(?P<course_id>\d+)/$', views.user_data, name="user_data"), url(r'^manage/user_data/(?P<user_id>\d+)/$', views.user_data), - url(r'^manage/quiz/designquestionpaper/(?P<quiz_id>\d+)/$', - views.design_questionpaper, name='designquestionpaper'), - url(r'^manage/designquestionpaper/(?P<quiz_id>\d+)/' + url(r'^manage/designquestionpaper/(?P<course_id>\d+)/(?P<quiz_id>\d+)/' '(?P<questionpaper_id>\d+)/$', views.design_questionpaper, name='designquestionpaper'), - url(r'^manage/designquestionpaper/(?P<quiz_id>\d+)/' - '(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$', + url(r'^manage/designquestionpaper/(?P<course_id>\d+)/(?P<quiz_id>\d+)/$', views.design_questionpaper, name='designquestionpaper'), url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/' '(?P<course_id>\d+)/$', @@ -176,29 +171,19 @@ urlpatterns = [ views.download_yaml_template, name="download_yaml_template"), url(r'^manage/download_sample_csv/', views.download_sample_csv, name="download_sample_csv"), - url(r'^manage/courses/edit_lesson/$', + url(r'^manage/courses/edit_lesson/(?P<course_id>\d+)/(?P<module_id>\d+)/$', views.edit_lesson, name="edit_lesson"), - url(r'^manage/courses/edit_lesson/(?P<lesson_id>\d+)/$', - views.edit_lesson, name="edit_lesson"), - url(r'^manage/courses/edit_lesson/(?P<lesson_id>\d+)/(?P<course_id>\d+)/$', + url(r'^manage/courses/edit_lesson/(?P<course_id>\d+)/(?P<module_id>\d+)/(?P<lesson_id>\d+)/$', views.edit_lesson, name="edit_lesson"), url(r'^manage/courses/designmodule/(?P<module_id>\d+)/$', views.design_module, name="design_module"), url(r'^manage/courses/designmodule/(?P<module_id>\d+)/' '(?P<course_id>\d+)/$', views.design_module, name="design_module"), - url(r'^manage/courses/all_quizzes/$', - views.show_all_quizzes, name="show_all_quizzes"), - url(r'^manage/courses/all_lessons/$', - views.show_all_lessons, name="show_all_lessons"), url(r'^manage/courses/lesson/preview/$', views.preview_html_text, name="preview_html_text"), - url(r'^manage/courses/all_learning_module/$', - views.show_all_modules, name="show_all_modules"), - url(r'^manage/courses/add_module/$', + url(r'^manage/courses/add_module/(?P<course_id>\d+)/$', views.add_module, name="add_module"), - url(r'^manage/courses/add_module/(?P<module_id>\d+)/$', - views.add_module, name="edit_module"), - url(r'^manage/courses/add_module/(?P<module_id>\d+)/(?P<course_id>\d+)/$', + url(r'^manage/courses/add_module/(?P<course_id>\d+)/(?P<module_id>\d+)/$', views.add_module, name="edit_module"), url(r'^manage/courses/designcourse/(?P<course_id>\d+)/$', views.design_course, name="design_course"), diff --git a/yaksh/views.py b/yaksh/views.py index 873c227..9efcbe9 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -177,15 +177,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: @@ -320,7 +319,7 @@ def add_question(request, question_id=None): @login_required @email_verified -def add_quiz(request, quiz_id=None, course_id=None): +def add_quiz(request, course_id=None, module_id=None, quiz_id=None): """To add a new quiz in the database. Create a new quiz and store it.""" user = request.user @@ -332,6 +331,8 @@ def add_quiz(request, quiz_id=None, course_id=None): raise Http404('This quiz does not belong to you') else: quiz = None + if module_id: + module = get_object_or_404(LearningModule, id=module_id) if course_id: course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): @@ -342,9 +343,22 @@ def add_quiz(request, quiz_id=None, course_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 - form.save() + 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=order + ) + if created: + module.learning_unit.add(unit.id) messages.success(request, "Quiz saved successfully") + return redirect( + reverse("yaksh:edit_quiz", + args=[course_id, module_id, added_quiz.id]) + ) else: form = QuizForm(instance=quiz) context["course_id"] = course_id @@ -355,7 +369,7 @@ def add_quiz(request, quiz_id=None, course_id=None): @login_required @email_verified -def add_exercise(request, quiz_id=None, course_id=None): +def add_exercise(request, course_id=None, module_id=None, quiz_id=None): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this course !') @@ -365,6 +379,8 @@ def add_exercise(request, quiz_id=None, course_id=None): raise Http404('This quiz does not belong to you') else: quiz = None + if module_id: + module = get_object_or_404(LearningModule, id=module_id) if course_id: course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): @@ -375,7 +391,11 @@ def add_exercise(request, quiz_id=None, course_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 @@ -385,9 +405,18 @@ def add_exercise(request, quiz_id=None, course_id=None): quiz.duration = 1000 quiz.pass_criteria = 0 quiz.save() + unit, created = LearningUnit.objects.get_or_create( + type="quiz", quiz=quiz, order=order + ) + if created: + module.learning_unit.add(unit.id) messages.success( request, "{0} saved successfully".format(quiz.description) ) + return redirect( + reverse("yaksh:edit_exercise", + args=[course_id, module_id, quiz.id]) + ) else: form = ExerciseForm(instance=quiz) context["exercise"] = quiz @@ -1366,8 +1395,7 @@ def _get_questions_from_tags(question_tags, user, active=True): @login_required @email_verified -def design_questionpaper(request, quiz_id, questionpaper_id=None, - course_id=None): +def design_questionpaper(request, course_id, quiz_id, questionpaper_id=None): user = request.user que_tags = Question.objects.filter( active=True, user=user).values_list('tags', flat=True).distinct() @@ -2445,7 +2473,7 @@ def download_yaml_template(request): @login_required @email_verified -def edit_lesson(request, lesson_id=None, course_id=None): +def edit_lesson(request, course_id=None, module_id=None, lesson_id=None): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') @@ -2455,13 +2483,13 @@ def edit_lesson(request, lesson_id=None, course_id=None): raise Http404('This Lesson does not belong to you') else: lesson = None + if module_id: + module = get_object_or_404(LearningModule, id=module_id) if course_id: course = get_object_or_404(Course, id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This Lesson does not belong to you') - redirect_url = reverse("yaksh:get_course_modules", args=[course_id]) - else: - redirect_url = reverse("yaksh:show_all_lessons") + context = {} if request.method == "POST": if "Save" in request.POST: @@ -2477,7 +2505,11 @@ def edit_lesson(request, lesson_id=None, course_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() @@ -2486,9 +2518,18 @@ def edit_lesson(request, lesson_id=None, course_id=None): LessonFile.objects.get_or_create( lesson=lesson, file=les_file ) + unit, created = LearningUnit.objects.get_or_create( + type="lesson", lesson=lesson, order=order + ) + if created: + module.learning_unit.add(unit.id) messages.success( request, "Saved {0} successfully".format(lesson.name) ) + return redirect( + reverse("yaksh:edit_lesson", + args=[course_id, module_id, lesson.id]) + ) else: context['lesson_form'] = lesson_form context['error'] = lesson_form["video_file"].errors @@ -2592,14 +2633,14 @@ def design_module(request, module_id, course_id=None): for order, value in enumerate(add_values, start_val): learning_id, type = value.split(":") if type == "quiz": - learning_unit = LearningUnit.objects.create( + unit, status = LearningUnit.objects.get_or_create( order=order, quiz_id=learning_id, type=type) else: - learning_unit = LearningUnit.objects.create( + unit, status = LearningUnit.objects.get_or_create( order=order, lesson_id=learning_id, type=type) - to_add_list.append(learning_unit) + to_add_list.append(unit) learning_module.learning_unit.add(*to_add_list) messages.success(request, "Lesson/Quiz added successfully") else: @@ -2662,21 +2703,20 @@ 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) @login_required @email_verified -def add_module(request, module_id=None, course_id=None): +def add_module(request, course_id=None, module_id=None): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') - redirect_url = reverse("yaksh:show_all_modules") if course_id: course = Course.objects.get(id=course_id) if not course.is_creator(user) and not course.is_teacher(user): raise Http404('This course does not belong to you') - redirect_url = reverse("yaksh:get_course_modules", args=[course_id]) if module_id: module = LearningModule.objects.get(id=module_id) if not module.creator == user and not course_id: @@ -2689,10 +2729,14 @@ def add_module(request, module_id=None, course_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() + course.learning_module.add(module.id) messages.success( request, "Saved {0} successfully".format(module.name) @@ -2709,99 +2753,6 @@ def add_module(request, module_id=None, course_id=None): @login_required @email_verified -def show_all_quizzes(request): - user = request.user - if not is_moderator(user): - raise Http404('You are not allowed to view this page!') - quizzes = Quiz.objects.filter(creator=user, is_trial=False) - - form = SearchFilterForm() - - if request.method == 'POST': - quiz_tags = request.POST.get('search_tags') - quiz_status = request.POST.get('search_status') - - if quiz_status == 'select' : - quizzes = quizzes.filter( - description__contains=quiz_tags) - elif quiz_status == 'active' : - quizzes = quizzes.filter( - description__contains=quiz_tags, active=True) - elif quiz_status == 'closed': - quizzes = quizzes.filter( - description__contains=quiz_tags, active=False) - quizzes_found = quizzes.count() - - context = {"quizzes": quizzes, "form": form, - "quizzes_found": quizzes_found} - return my_render_to_response(request, 'yaksh/quizzes.html', context) - - -@login_required -@email_verified -def show_all_lessons(request): - user = request.user - if not is_moderator(user): - raise Http404('You are not allowed to view this page!') - lessons = Lesson.objects.filter(creator=user) - - form = SearchFilterForm() - - if request.method == 'POST': - lesson_tags = request.POST.get('search_tags') - lesson_status = request.POST.get('search_status') - - if lesson_status == 'select' : - lessons = lessons.filter( - description__contains=lesson_tags) - elif lesson_status == 'active' : - lessons = lessons.filter( - description__contains=lesson_tags, active=True) - elif lesson_status == 'closed': - lessons = lessons.filter( - description__contains=lesson_tags, active=False) - lessons_found = lessons.count() - - context = {"lessons": lessons, "form": form, - "lessons_found": lessons_found} - return my_render_to_response(request, 'yaksh/lessons.html', context) - - -@login_required -@email_verified -def show_all_modules(request): - user = request.user - if not is_moderator(user): - raise Http404('You are not allowed to view this page!') - learning_modules = LearningModule.objects.filter( - creator=user, is_trial=False) - - form = SearchFilterForm() - - if request.method == 'POST': - module_tags = request.POST.get('search_tags') - module_status = request.POST.get('search_status') - - if module_status == 'select' : - learning_modules = learning_modules.filter( - name__contains=module_tags) - elif module_status == 'active' : - learning_modules = learning_modules.filter( - name__contains=module_tags, active=True) - elif module_status == 'closed': - learning_modules = learning_modules.filter( - name__contains=module_tags, active=False) - learning_modules_found = learning_modules.count() - - context = {"modules": learning_modules, "form": form, - "modules_found": learning_modules_found} - return my_render_to_response( - request, 'yaksh/modules.html', context - ) - - -@login_required -@email_verified def preview_html_text(request): user = request.user if not is_moderator(user): @@ -2886,10 +2837,12 @@ def design_course(request, course_id): else: start_val = 1 for order, value in enumerate(add_values, start_val): - learning_module = LearningModule.objects.get(id=int(value)) - learning_module.order = order - learning_module.save() - to_add_list.append(learning_module) + module, created = LearningModule.objects.get_or_create( + id=int(value) + ) + module.order = order + module.save() + to_add_list.append(module) course.learning_module.add(*to_add_list) messages.success(request, "Modules added successfully") else: |