summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--yaksh/forms.py2
-rw-r--r--yaksh/models.py26
-rw-r--r--yaksh/templates/yaksh/add_exercise.html46
-rw-r--r--yaksh/templates/yaksh/add_quiz.html43
-rw-r--r--yaksh/test_views.py159
-rw-r--r--yaksh/urls.py2
-rw-r--r--yaksh/views.py36
7 files changed, 242 insertions, 72 deletions
diff --git a/yaksh/forms.py b/yaksh/forms.py
index 8399bc9..9fd2eaa 100644
--- a/yaksh/forms.py
+++ b/yaksh/forms.py
@@ -176,7 +176,7 @@ class UserLoginForm(forms.Form):
class ExerciseForm(forms.ModelForm):
class Meta:
model = Quiz
- fields = ['description', 'view_answerpaper']
+ fields = ['description', 'view_answerpaper', 'active']
class QuizForm(forms.ModelForm):
diff --git a/yaksh/models.py b/yaksh/models.py
index f0d1b2e..1e45851 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -1196,13 +1196,14 @@ class QuestionPaper(models.Model):
return ans_paper
- def _is_attempt_allowed(self, user):
+ def _is_attempt_allowed(self, user, course_id):
attempts = AnswerPaper.objects.get_total_attempt(questionpaper=self,
- user=user)
+ user=user,
+ course_id=course_id)
return attempts != self.quiz.attempts_allowed
def can_attempt_now(self, user, course_id):
- if self._is_attempt_allowed(user):
+ if self._is_attempt_allowed(user, course_id):
last_attempt = AnswerPaper.objects.get_user_last_attempt(
user=user, questionpaper=self, course_id=course_id
)
@@ -1280,10 +1281,11 @@ class QuestionSet(models.Model):
###############################################################################
class AnswerPaperManager(models.Manager):
- def get_all_questions(self, questionpaper_id, attempt_number,
+ def get_all_questions(self, questionpaper_id, attempt_number, course_id,
status='completed'):
''' Return a dict of question id as key and count as value'''
papers = self.filter(question_paper_id=questionpaper_id,
+ course_id=course_id,
attempt_number=attempt_number, status=status)
all_questions = list()
questions = list()
@@ -1294,9 +1296,10 @@ class AnswerPaperManager(models.Manager):
return Counter(questions)
def get_all_questions_answered(self, questionpaper_id, attempt_number,
- status='completed'):
+ course_id, status='completed'):
''' Return a dict of answered question id as key and count as value'''
papers = self.filter(question_paper_id=questionpaper_id,
+ course_id=course_id,
attempt_number=attempt_number, status=status)
questions_answered = list()
for paper in papers:
@@ -1334,15 +1337,17 @@ class AnswerPaperManager(models.Manager):
).count()
def get_question_statistics(self, questionpaper_id, attempt_number,
- status='completed'):
+ course_id, status='completed'):
''' Return dict with question object as key and list as value
The list contains two value, first the number of times a question
was answered correctly, and second the number of times a question
appeared in a quiz'''
question_stats = {}
questions_answered = self.get_all_questions_answered(questionpaper_id,
- attempt_number)
- questions = self.get_all_questions(questionpaper_id, attempt_number)
+ attempt_number,
+ course_id)
+ questions = self.get_all_questions(questionpaper_id, attempt_number,
+ course_id)
all_questions = Question.objects.filter(
id__in=set(questions),
active=True
@@ -1391,8 +1396,9 @@ class AnswerPaperManager(models.Manager):
def get_user_answerpapers(self, user):
return self.filter(user=user)
- def get_total_attempt(self, questionpaper, user):
- return self.filter(question_paper=questionpaper, user=user).count()
+ def get_total_attempt(self, questionpaper, user, course_id):
+ return self.filter(question_paper=questionpaper, user=user,
+ course_id=course_id).count()
def get_users_for_questionpaper(self, questionpaper_id, course_id):
return self._get_answerpapers_for_quiz(questionpaper_id, course_id,
diff --git a/yaksh/templates/yaksh/add_exercise.html b/yaksh/templates/yaksh/add_exercise.html
index dac35d4..77e3ee8 100644
--- a/yaksh/templates/yaksh/add_exercise.html
+++ b/yaksh/templates/yaksh/add_exercise.html
@@ -14,17 +14,41 @@
{% block content %}
<form name=frm id=frm action="" method="post" >
- {% csrf_token %}
- <center>
- <table class="span1 table">
- {{ form.as_table }}
- </table>
- <br/><br/>
- </center>
+ {% csrf_token %}
+ <center>
+ <table class="span1 table">
+ {{ form.as_table }}
+ </table>
+ <br/><br/>
+ </center>
+ <center><button class="btn" type="submit" id="submit" name="save_exercise"> Save
+ </button>
- <center><button class="btn" type="submit" id="submit" name="questionpaper"> Save
- </button>
-
- <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/courses/");'>Cancel</button> </center>
+ <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/courses/");'>Cancel</button> </center>
</form>
+{% if exercise and course_id %}
+ {% if exercise.questionpaper_set.get.id %}
+ <center>
+ <h4>You can check the quiz by attempting it in the following modes:</h4>
+ <a href="{{URL_ROOT}}/exam/manage/designquestionpaper/{{ exercise.id }}/{{exercise.questionpaper_set.get.id}}/{{course_id}}" class="btn btn-primary">View Question Paper</a>
+ <button class="btn" type="button" name="button" onClick='usermode("{{URL_ROOT}}/exam/manage/usermode/{{exercise.id}}/{{course_id}}/");'>User Mode</button>
+
+ <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/godmode/{{exercise.id}}/{{course_id}}/");'>
+ God Mode</button>
+ <a data-toggle="collapse" data-target="#help">
+ <span class="glyphicon glyphicon-info-sign">Help</span></a>
+ <div id="help" class="collapse">
+ <br/>
+ <ul>
+ <li><b>User Mode:</b> Attempt quiz the way normal users will attempt i.e. -
+ <ul>
+ <li><i>Quiz will have the same duration as that of the original quiz.</li>
+ <li>Quiz won't start if the course is inactive or the quiz time has expired.</li>
+ <li>You will be notified about quiz prerequisites.(You can still attempt the quiz though)</i></li>
+ </ul>
+ </p>
+ <li> <b>God Mode:</b> Attempt quiz without any time or eligibilty constraints.</p>
+ </div>
+ {% endif %}
+{% endif %}
{% endblock %}
diff --git a/yaksh/templates/yaksh/add_quiz.html b/yaksh/templates/yaksh/add_quiz.html
index d3705e3..684f804 100644
--- a/yaksh/templates/yaksh/add_quiz.html
+++ b/yaksh/templates/yaksh/add_quiz.html
@@ -35,31 +35,34 @@
</form>
<br>
-{% if quiz_id and course_id %}
+{% if quiz and course_id %}
+ {% if quiz.questionpaper_set.get.id %}
<center>
<h4>You can check the quiz by attempting it in the following modes:</h4>
- <button class="btn" type="button" name="button" onClick='usermode("{{URL_ROOT}}/exam/manage/usermode/{{quiz_id}}/{{course_id}}/");'>User Mode</button>
-
- <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/godmode/{{quiz_id}}/{{course_id}}/");'>
- God Mode</button>
+ <a href="{{URL_ROOT}}/exam/manage/designquestionpaper/{{ quiz.id }}/{{quiz.questionpaper_set.get.id}}/{{course_id}}" class="btn btn-primary">View Question Paper</a>
+ <button class="btn" type="button" name="button" onClick='usermode("{{URL_ROOT}}/exam/manage/usermode/{{quiz.id}}/{{course_id}}/");'>User Mode</button>
+
+ <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/godmode/{{quiz.id}}/{{course_id}}/");'>
+ God Mode</button>
<a data-toggle="collapse" data-target="#help">
<span class="glyphicon glyphicon-info-sign">Help</span></a>
- <div id="help" class="collapse">
- <br/>
- <ul>
- <li><b>User Mode:</b> Attempt quiz the way normal users will attempt i.e. -
- <ul>
- <li><i>Quiz will have the same duration as that of the original quiz.</li>
- <li>Quiz won't start if the course is inactive or the quiz time has expired.</li>
- <li>You will be notified about quiz prerequisites.(You can still attempt the quiz though)</i></li>
- </ul>
- </p>
- <li> <b>God Mode:</b> Attempt quiz without any time or eligibilty constraints.</p>
- </div>
- {% endif %}
- <style type="text/css">
+ <div id="help" class="collapse">
+ <br/>
+ <ul>
+ <li><b>User Mode:</b> Attempt quiz the way normal users will attempt i.e. -
+ <ul>
+ <li><i>Quiz will have the same duration as that of the original quiz.</li>
+ <li>Quiz won't start if the course is inactive or the quiz time has expired.</li>
+ <li>You will be notified about quiz prerequisites.(You can still attempt the quiz though)</i></li>
+ </ul>
+ </p>
+ <li> <b>God Mode:</b> Attempt quiz without any time or eligibilty constraints.</p>
+ </div>
+ {% endif %}
+{% endif %}
+<style type="text/css">
#rendered_text{
width: 550px;
}
- </style>
+</style>
{% endblock %}
diff --git a/yaksh/test_views.py b/yaksh/test_views.py
index ff2b5a7..9be8d13 100644
--- a/yaksh/test_views.py
+++ b/yaksh/test_views.py
@@ -1230,6 +1230,7 @@ class TestAddQuiz(TestCase):
kwargs={'quiz_id': self.exercise.id}),
data={
'description': 'updated demo exercise',
+ 'active': True
}
)
@@ -1257,6 +1258,7 @@ class TestAddQuiz(TestCase):
response = self.client.post(reverse('yaksh:add_exercise'),
data={
'description': "Demo Exercise",
+ 'active': True
}
)
quiz_list = Quiz.objects.all().order_by('-id')
@@ -1916,6 +1918,25 @@ class TestAddCourse(TestCase):
timezone='UTC'
)
+ # Create a teacher
+ self.teacher_plaintext_pass = 'demo_teacher'
+ self.teacher = User.objects.create_user(
+ username='demo_teacher',
+ password=self.teacher_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.teacher,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
# Create Student
self.student_plaintext_pass = 'demo_student'
self.student = User.objects.create_user(
@@ -1928,10 +1949,13 @@ class TestAddCourse(TestCase):
# Add to moderator group
self.mod_group.user_set.add(self.user)
+ self.mod_group.user_set.add(self.teacher)
self.course = Course.objects.create(name="Python Course",
enrollment="Enroll Request", creator=self.user)
+ self.course.teachers.add(self.teacher)
+
self.pre_req_quiz = Quiz.objects.create(
start_date_time=datetime(2014, 2, 1, 5, 8, 15, 0, tzone),
end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone),
@@ -2021,6 +2045,33 @@ class TestAddCourse(TestCase):
self.assertRedirects(response, '/exam/manage/courses',
target_status_code=301)
+ def test_add_course_teacher_cannot_be_creator(self):
+ """
+ Teacher editing the course should not become creator
+ """
+ self.client.login(
+ username=self.teacher.username,
+ password=self.teacher_plaintext_pass
+ )
+
+ response = self.client.post(reverse('yaksh:edit_course',
+ kwargs={"course_id": self.course.id}),
+ data={'name': 'Teacher_course',
+ 'active': True,
+ 'enrollment': 'open',
+ 'start_enroll_time': '2016-01-10 09:00:15',
+ 'end_enroll_time': '2016-01-15 09:00:15',
+ }
+ )
+ updated_course = Course.objects.get(id=self.course.id)
+ self.assertEqual(updated_course.name, 'Teacher_course')
+ self.assertEqual(updated_course.enrollment, 'open')
+ self.assertEqual(updated_course.active, True)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(updated_course.creator, self.user)
+ self.assertRedirects(response, '/exam/manage/courses',
+ target_status_code=301)
+
class TestCourseDetail(TestCase):
def setUp(self):
@@ -3989,8 +4040,27 @@ class TestQuestionPaper(TestCase):
timezone='UTC'
)
+ self.teacher_plaintext_pass = 'demo_teacher'
+ self.teacher = User.objects.create_user(
+ username='demo_teacher',
+ password=self.teacher_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.teacher,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
# Add to moderator group
self.mod_group.user_set.add(self.user)
+ self.mod_group.user_set.add(self.teacher)
self.course = Course.objects.create(
name="Python Course",
@@ -4005,6 +4075,15 @@ class TestQuestionPaper(TestCase):
creator=self.user
)
+ self.demo_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),
+ duration=30, active=True, instructions="Demo Instructions",
+ attempts_allowed=-1, time_between_attempts=0,
+ description='demo quiz 2', pass_criteria=40,
+ creator=self.user
+ )
+
self.learning_unit = LearningUnit.objects.create(
order=1, type="quiz", quiz=self.quiz)
self.learning_module = LearningModule.objects.create(
@@ -4026,8 +4105,6 @@ class TestQuestionPaper(TestCase):
)
self.mcq_based_testcase.save()
- ordered_questions = str(self.question_mcq.id)
-
# Mcc Question
self.question_mcc = Question.objects.create(
summary="Test_mcc_question", description="Test MCC",
@@ -4041,8 +4118,6 @@ class TestQuestionPaper(TestCase):
)
self.mcc_based_testcase.save()
- ordered_questions = ordered_questions + str(self.question_mcc.id)
-
# Integer Question
self.question_int = Question.objects.create(
summary="Test_mcc_question", description="Test MCC",
@@ -4055,8 +4130,6 @@ class TestQuestionPaper(TestCase):
)
self.int_based_testcase.save()
- ordered_questions = ordered_questions + str(self.question_int.id)
-
# String Question
self.question_str = Question.objects.create(
summary="Test_mcc_question", description="Test MCC",
@@ -4083,17 +4156,19 @@ class TestQuestionPaper(TestCase):
)
self.float_based_testcase.save()
- ordered_questions = ordered_questions + str(self.question_float.id)
-
- questions_list = [self.question_mcq, self.question_mcc,
- self.question_int, self.question_str,
- self.question_float]
-
+ self.questions_list = [self.question_mcq, self.question_mcc,
+ self.question_int, self.question_str,
+ self.question_float]
+ questions_order = ",".join([
+ str(self.question_mcq.id), str(self.question_mcc.id),
+ str(self.question_int.id), str(self.question_str.id),
+ str(self.question_float.id)
+ ])
self.question_paper = QuestionPaper.objects.create(
quiz=self.quiz,
- total_marks=5.0, fixed_question_order=ordered_questions
+ total_marks=5.0, fixed_question_order=questions_order
)
- self.question_paper.fixed_questions.add(*questions_list)
+ self.question_paper.fixed_questions.add(*self.questions_list)
self.answerpaper = AnswerPaper.objects.create(
user=self.user, question_paper=self.question_paper,
attempt_number=1,
@@ -4102,12 +4177,14 @@ class TestQuestionPaper(TestCase):
user_ip="127.0.0.1", status="inprogress", passed=False,
percent=0, marks_obtained=0, course=self.course
)
- self.answerpaper.questions.add(*questions_list)
+ self.answerpaper.questions.add(*self.questions_list)
def tearDown(self):
self.client.logout()
self.user.delete()
+ self.teacher.delete()
self.quiz.delete()
+ self.demo_quiz.delete()
self.course.delete()
self.answerpaper.delete()
self.question_mcq.delete()
@@ -4381,6 +4458,58 @@ class TestQuestionPaper(TestCase):
wrong_answer_paper = AnswerPaper.objects.get(id=self.answerpaper.id)
self.assertEqual(wrong_answer_paper.marks_obtained, 0)
+ def test_design_questionpaper(self):
+ """ Test design Question Paper """
+
+ # Should fail if Question paper is not the one which is associated
+ # with a quiz
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+
+ response = self.client.get(
+ reverse('yaksh:designquestionpaper',
+ kwargs={"quiz_id": self.demo_quiz.id,
+ "questionpaper_id": self.question_paper.id}))
+ self.assertEqual(response.status_code, 404)
+
+ self.client.login(
+ username=self.teacher.username,
+ password=self.teacher_plaintext_pass
+ )
+
+ # Should not allow teacher to view question paper
+ response = self.client.get(
+ reverse('yaksh:designquestionpaper',
+ kwargs={"quiz_id": self.quiz.id,
+ "questionpaper_id": self.question_paper.id}))
+
+ self.assertEqual(response.status_code, 404)
+
+ # Should not allow teacher to view question paper
+ response = self.client.get(
+ reverse('yaksh:designquestionpaper',
+ kwargs={"quiz_id": self.quiz.id,
+ "course_id": self.course.id,
+ "questionpaper_id": self.question_paper.id}))
+
+ self.assertEqual(response.status_code, 404)
+
+ # Should allow course teacher to view question paper
+ # Add teacher to the course
+ self.course.teachers.add(self.teacher)
+ response = self.client.get(
+ reverse('yaksh:designquestionpaper',
+ kwargs={"quiz_id": self.quiz.id,
+ "course_id": self.course.id,
+ "questionpaper_id": self.question_paper.id}))
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/design_questionpaper.html')
+ self.assertEqual(response.context['fixed_questions'],
+ self.questions_list)
+ self.assertEqual(response.context['qpaper'], self.question_paper)
+
class TestLearningModule(TestCase):
def setUp(self):
diff --git a/yaksh/urls.py b/yaksh/urls.py
index 7eca5af..08c2091 100644
--- a/yaksh/urls.py
+++ b/yaksh/urls.py
@@ -78,6 +78,8 @@ urlpatterns = [
name='design_questionpaper'),
url(r'^manage/designquestionpaper/(?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+)/$',
+ views.design_questionpaper, name='designquestionpaper'),
url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$',
views.show_statistics, name="show_statistics"),
url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<attempt_number>\d+)/(?P<course_id>\d+)/$',
diff --git a/yaksh/views.py b/yaksh/views.py
index 637b35c..2bc5dfe 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -320,10 +320,9 @@ def add_quiz(request, quiz_id=None, course_id=None):
return my_redirect("/exam/manage/courses/")
else:
- quiz = Quiz.objects.get(id=quiz_id) if quiz_id else None
form = QuizForm(instance=quiz)
- context["quiz_id"] = quiz_id
context["course_id"] = course_id
+ context["quiz"] = quiz
context["form"] = form
return my_render_to_response(
'yaksh/add_quiz.html', context, context_instance=ci
@@ -346,7 +345,7 @@ def add_exercise(request, quiz_id=None, course_id=None):
if course_id:
course = get_object_or_404(Course, pk=course_id)
if not course.is_creator(user) and not course.is_teacher(user):
- raise Http404('This quiz does not belong to you')
+ raise Http404('This Course does not belong to you')
context = {}
if request.method == "POST":
@@ -370,9 +369,8 @@ def add_exercise(request, quiz_id=None, course_id=None):
return my_redirect("/exam/manage/courses/")
else:
- quiz = Quiz.objects.get(id=quiz_id) if quiz_id else None
form = ExerciseForm(instance=quiz)
- context["quiz_id"] = quiz_id
+ context["exercise"] = quiz
context["course_id"] = course_id
context["form"] = form
return my_render_to_response(
@@ -941,14 +939,14 @@ def add_course(request, course_id=None):
raise Http404("You are not allowed to view this course")
else:
course = None
-
if not is_moderator(user):
raise Http404('You are not allowed to view this page')
if request.method == 'POST':
form = CourseForm(request.POST, instance=course)
if form.is_valid():
new_course = form.save(commit=False)
- new_course.creator = user
+ if course_id is None:
+ new_course.creator = user
new_course.save()
return my_redirect('/exam/manage/courses')
else:
@@ -1167,7 +1165,7 @@ def show_statistics(request, questionpaper_id, attempt_number=None,
course_id):
return my_redirect('/exam/manage/')
question_stats = AnswerPaper.objects.get_question_statistics(
- questionpaper_id, attempt_number
+ questionpaper_id, attempt_number, course_id
)
context = {'question_stats': question_stats, 'quiz': quiz,
'questionpaper_id': questionpaper_id,
@@ -1301,14 +1299,21 @@ def _remove_already_present(questionpaper_id, questions):
@login_required
@email_verified
-def design_questionpaper(request, quiz_id, questionpaper_id=None):
+def design_questionpaper(request, quiz_id, questionpaper_id=None,
+ course_id=None):
user = request.user
if not is_moderator(user):
raise Http404('You are not allowed to view this page!')
- quiz = Quiz.objects.get(id=quiz_id)
- if not quiz.creator == user:
- raise Http404('This course does not belong to you')
+ if quiz_id:
+ quiz = get_object_or_404(Quiz, pk=quiz_id)
+ if quiz.creator != user and not course_id:
+ raise Http404('This quiz does not belong to you')
+ if course_id:
+ course = get_object_or_404(Course, pk=course_id)
+ if not course.is_creator(user) and not course.is_teacher(user):
+ raise Http404('This Course does not belong to you')
+
filter_form = QuestionFilterForm(user=user)
questions = None
marks = None
@@ -1316,7 +1321,8 @@ def design_questionpaper(request, quiz_id, questionpaper_id=None):
if questionpaper_id is None:
question_paper = QuestionPaper.objects.get_or_create(quiz_id=quiz_id)[0]
else:
- question_paper = get_object_or_404(QuestionPaper, id=questionpaper_id)
+ question_paper = get_object_or_404(QuestionPaper, id=questionpaper_id,
+ quiz_id=quiz_id)
qpaper_form = QuestionPaperForm(instance=question_paper)
if request.method == 'POST':
@@ -1533,9 +1539,9 @@ def download_quiz_csv(request, course_id, quiz_id):
questions = question_paper.get_question_bank()
answerpapers = AnswerPaper.objects.filter(question_paper=question_paper,
- attempt_number=attempt_number)
+ attempt_number=attempt_number, course_id=course_id)
if not answerpapers:
- return monitor(request, quiz_id)
+ return monitor(request, quiz_id, course_id)
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="{0}-{1}-attempt{2}.csv"'.format(
course.name.replace('.', ''), quiz.description.replace('.', ''),