summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.coveragerc3
-rw-r--r--online_test/settings.py3
-rw-r--r--yaksh/admin.py6
-rw-r--r--yaksh/models.py8
-rw-r--r--yaksh/send_emails.py32
-rw-r--r--yaksh/static/yaksh/js/course.js31
-rw-r--r--yaksh/templates/yaksh/course_detail.html103
-rw-r--r--yaksh/templates/yaksh/login.html8
-rw-r--r--yaksh/templates/yaksh/moderator_dashboard.html2
-rw-r--r--yaksh/test_views.py1621
-rw-r--r--yaksh/urls.py67
-rw-r--r--yaksh/views.py77
12 files changed, 1861 insertions, 100 deletions
diff --git a/.coveragerc b/.coveragerc
index 4ac9a45..3b868e8 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -4,6 +4,9 @@ source = .
omit =
*tests*
*migrations*
+ yaksh/test_views.py
+ yaksh/test_models.py
+ *management*
[report]
exclude_lines =
diff --git a/online_test/settings.py b/online_test/settings.py
index 4ca2967..90cce9d 100644
--- a/online_test/settings.py
+++ b/online_test/settings.py
@@ -113,7 +113,8 @@ EMAIL_HOST_USER = 'email_host_user'
EMAIL_HOST_PASSWORD = 'email_host_password'
-# Set EMAIL_BACKEND to 'django.core.mail.backends.smtp.EmailBackend' in production
+# Set EMAIL_BACKEND to 'django.core.mail.backends.smtp.EmailBackend'
+# in production
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
# SENDER_EMAIL, REPLY_EMAIL, PRODUCTION_URL, IS_DEVELOPMENT are used in email
diff --git a/yaksh/admin.py b/yaksh/admin.py
index 79d4930..199fb56 100644
--- a/yaksh/admin.py
+++ b/yaksh/admin.py
@@ -1,4 +1,4 @@
-from yaksh.models import Question, Quiz, QuestionPaper
+from yaksh.models import Question, Quiz, QuestionPaper, Profile
from yaksh.models import TestCase, StandardTestCase, StdIOBasedTestCase, Course, AnswerPaper
from django.contrib import admin
@@ -8,7 +8,11 @@ class AnswerPaperAdmin(admin.ModelAdmin):
search_fields = ['user__first_name', 'user__last_name','user__username',
"question_paper__quiz__description","user_ip" ]
+class ProfileAdmin(admin.ModelAdmin):
+ search_fields = ['user__first_name', 'user__last_name','user__username',
+ "roll_number", "institute","department"]
+admin.site.register(Profile, ProfileAdmin)
admin.site.register(Question)
admin.site.register(TestCase)
admin.site.register(StandardTestCase)
diff --git a/yaksh/models.py b/yaksh/models.py
index 79732cc..87e6260 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -78,9 +78,10 @@ test_status = (
def get_assignment_dir(instance, filename):
+ upload_dir = instance.question_paper.quiz.description.replace(" ", "_")
return os.sep.join((
- instance.question_paper.quiz.description, instance.user.username,
- str(instance.assignmentQuestion.id), filename
+ upload_dir, instance.user.username, str(instance.assignmentQuestion.id),
+ filename
))
@@ -305,6 +306,9 @@ class Profile(models.Model):
os.chmod(user_dir, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
return user_dir
+ def __str__(self):
+ return '%s' % (self.user.get_full_name())
+
###############################################################################
class Question(models.Model):
diff --git a/yaksh/send_emails.py b/yaksh/send_emails.py
index 24215dd..ae49f23 100644
--- a/yaksh/send_emails.py
+++ b/yaksh/send_emails.py
@@ -7,11 +7,14 @@ from string import digits, punctuation
import hashlib
from textwrap import dedent
import smtplib
+import os
# Django imports
from django.utils.crypto import get_random_string
from django.conf import settings
-from django.core.mail import send_mass_mail, send_mail
+from django.core.mail import EmailMultiAlternatives, send_mail
+from django.core.files.storage import default_storage
+from django.core.files.base import ContentFile
def generate_activation_key(username):
@@ -57,3 +60,30 @@ def send_user_mail(user_mail, key):
success = False
return success, msg
+
+def send_bulk_mail(subject, email_body, recipients, attachments):
+ try:
+ text_msg = ""
+ msg = EmailMultiAlternatives(subject, text_msg, settings.SENDER_EMAIL,
+ recipients
+ )
+ msg.attach_alternative(email_body, "text/html")
+ if attachments:
+ for file in attachments:
+ path = default_storage.save('attachments/'+file.name,
+ ContentFile(file.read())
+ )
+ msg.attach_file(os.sep.join((settings.MEDIA_ROOT, path)),
+ mimetype="text/html"
+ )
+ default_storage.delete(path)
+ msg.send()
+
+ message = "Email Sent Successfully"
+
+ except Exception as exc_msg:
+ message = """Error: {0}. Please check email address.\
+ If email address is correct then
+ Please contact {1}.""".format(exc_msg, settings.REPLY_EMAIL)
+
+ return message
diff --git a/yaksh/static/yaksh/js/course.js b/yaksh/static/yaksh/js/course.js
index 5b79e68..8fb2773 100644
--- a/yaksh/static/yaksh/js/course.js
+++ b/yaksh/static/yaksh/js/course.js
@@ -35,4 +35,35 @@ $(".reject").change( function(){
});
}
});
+
+$(function() {
+ $('textarea#email_body').froalaEditor({
+ heightMin: 200,
+ heightMax: 200
+ })
+ });
+
+$("#send_mail").click(function(){
+ var subject = $("#subject").val();
+ var body = $('#email_body').val();
+ var status = false;
+ var selected = [];
+ $('#reject input:checked').each(function() {
+ selected.push($(this).attr('value'));
+ });
+
+ if (subject == '' || body == ''){
+ $("#error_msg").html("Please enter mail details");
+ $("#dialog").dialog();
+ }
+ else if (selected.length == 0){
+ $("#error_msg").html("Please select atleast one user");
+ $("#dialog").dialog();
+ }
+ else {
+ status = true;
+ }
+ return status;
+});
+
});
diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html
index cd4144f..bcada42 100644
--- a/yaksh/templates/yaksh/course_detail.html
+++ b/yaksh/templates/yaksh/course_detail.html
@@ -6,6 +6,13 @@
{% block script %}
<script language="JavaScript" type="text/javascript" src="{{ URL_ROOT }}/static/yaksh/js/course.js"></script>
+<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/2.5.1/js/froala_editor.min.js"></script>
+<script src="https://code.jquery.com/ui/1.9.1/jquery-ui.js"></script>
+{% endblock %}
+{% block css %}
+<link rel="stylesheet" href="https://code.jquery.com/ui/1.9.1/themes/base/jquery-ui.css">
+<link href="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/2.5.1/css/froala_editor.min.css" rel="stylesheet" type="text/css" />
+<link href="https://cdnjs.cloudflare.com/ajax/libs/froala-editor/2.5.1/css/froala_style.min.css" rel="stylesheet" type="text/css" />
{% endblock %}
{% block content %}
<br/>
@@ -13,9 +20,17 @@
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
- <li><a href="#student-requests" id="request"> Requested Students </a></li>
- <li><a href="#enrolled-students" id="enroll-students"> Enrolled Students </a></li>
- <li><a href="#rejected-students" id="reject-students"> Rejected Students </a></li>
+ {% if state == 'mail'%}
+ <li><a href="{{URL_ROOT}}/exam/manage/course_detail/{{course.id}}/">
+ Go to Course Details</a></li>
+ {% else %}
+ <li><a href="#student-requests" id="request">
+ Requested Students </a></li>
+ <li><a href="#enrolled-students" id="enroll-students">
+ Enrolled Students </a></li>
+ <li><a href="#rejected-students" id="reject-students">
+ Rejected Students </a></li>
+ {% endif %}
<li>
<a href="{{URL_ROOT}}/exam/manage/toggle_status/{{ course.id }}/">
{% if course.active %}Deactivate Course {% else %} Activate Course {% endif %}</a>
@@ -24,16 +39,70 @@
<a href="{{URL_ROOT}}/exam/manage/duplicate_course/{{ course.id }}/">
Clone Course</a>
</li>
+ <li>
+ <a href="{{URL_ROOT}}/exam/manage/send_mail/{{ course.id }}/">
+ Send Mail</a>
+ </li>
</ul>
</div>
</div>
<div class="col-md-9 col-md-offset-2 main">
<div class="row">
+ {% if message %}
+ <div class="alert alert-warning" role="alert">
+ <center>
+ <strong> {{ message }} </strong>
+ </center>
+ </div>
+ {% endif %}
+ {% if state == 'mail' %}
+ <div id="enrolled-students">
+ <center><b><u>Send Mails to Students</u></b></center><br>
+ {% if course.get_enrolled %}
+ <input type="checkbox" class="reject"/>&nbsp;<font size="2">Select all</font>
+ <div id="reject">
+ <form action="{{URL_ROOT}}/exam/manage/send_mail/{{ course.id }}/" method="post" id="send_mail_form">
+ {% csrf_token %}
+ <table class="table table-striped">
+ <th></th>
+ <th></th>
+ <th>Full Name</th>
+ <th>Email</th>
+ <th>Roll Number</th>
+ <th>Institute</th>
+ <th>Department</th>
+ {% for enrolled in course.get_enrolled %}
+ <tr>
+ <td><input type="checkbox" name="check" value="{{ enrolled.id }}"></td>
+ <td>{{ forloop.counter }}.</td>
+ <td> {{ enrolled.get_full_name|title }} </td>
+ <td> {{enrolled.email}}</td>
+ <td> {{enrolled.profile.roll_number}}</td>
+ <td> {{enrolled.profile.institute}}</td>
+ <td> {{enrolled.profile.department}}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ <br>
+ <textarea name="subject" id="subject" placeholder="Email Subject" cols="50"></textarea>
+ <br><br>
+ <textarea name="body" id="email_body"></textarea><br>
+ Attachments: <input type="file" name="email_attach" multiple="">
+ <br>
+ <button class="btn btn-success" type="submit" name='send_mail' value='send_mail' id="send_mail">
+ Send Mail to Selected Students</button>
+ </div>
+ {% endif %}
+ </form>
+ </div>
+ {% else %}
<div id="student-requests">
<center><b><u>Requests</u></b></center><br>
{% if course.get_requests %}
<input type="checkbox" class="checkall"/>&nbsp;<font size="2">Select all</font>
<div id="enroll-all">
+ <form action="{{URL_ROOT}}/exam/manage/enroll/{{ course.id }}/" method="post">
+ {% csrf_token %}
<table class="table table-striped">
<th></th>
<th></th>
@@ -43,9 +112,7 @@
<th>Institute</th>
<th>Department</th>
<th>Enroll/Reject</th>
- <form action="{{URL_ROOT}}/exam/manage/enroll/{{ course.id }}/" method="post">
- {% csrf_token %}
- {% for request in course.get_requests %}
+ {% for request in course.get_requests %}
<tr>
<td><input type="checkbox" name="check" value="{{ request.id }}"></td>
<td>{{ forloop.counter }}.</td>
@@ -76,6 +143,8 @@
{% if course.get_enrolled %}
<input type="checkbox" class="reject"/>&nbsp;<font size="2">Select all</font>
<div id="reject">
+ <form action="{{URL_ROOT}}/exam/manage/enrolled/reject/{{ course.id }}/" method="post" id="reject-form">
+ {% csrf_token %}
<table class="table table-striped">
<th></th>
<th></th>
@@ -86,9 +155,7 @@
<th>Department</th>
<th>Reject</th>
{% for enrolled in course.get_enrolled %}
- <form action="{{URL_ROOT}}/exam/manage/enrolled/reject/{{ course.id }}/" method="post">
- {% csrf_token %}
- <tr>
+ <tr>
<td><input type="checkbox" name="check" value="{{ enrolled.id }}"></td>
<td>{{ forloop.counter }}.</td>
<td> {{ enrolled.get_full_name|title }} </td>
@@ -99,11 +166,12 @@
<td><a class="btn btn-danger"
href="{{URL_ROOT}}/exam/manage/enrolled/reject/{{ course.id }}/{{ enrolled.id }}/">
Reject </a>
- </td>
- </tr>
+ </td>
+ </tr>
{% endfor %}
</table>
- <button class="btn btn-danger" type="submit" name='reject' value='reject'>Reject Selected</button>
+ <button class="btn btn-danger" type="submit" name='reject' value='reject'>
+ Reject Selected</button>
</div>
{% endif %}
</form>
@@ -114,6 +182,8 @@
{% if course.get_rejected %}
<input type="checkbox" class="enroll"/>&nbsp;<font size="2">Select all</font>
<div id="enroll">
+ <form action="{{URL_ROOT}}/exam/manage/enroll/rejected/{{ course.id }}/" method="post">
+ {% csrf_token %}
<table class="table table-striped">
<th></th>
<th></th>
@@ -123,9 +193,7 @@
<th>Institute</th>
<th>Department</th>
<th>Enroll</th>
- {% for rejected in course.get_rejected %}
- <form action="{{URL_ROOT}}/exam/manage/enroll/rejected/{{ course.id }}/" method="post">
- {% csrf_token %}
+ {% for rejected in course.get_rejected %}
<tr>
<td><input type="checkbox" name="check" value="{{ rejected.id }}"></td>
<td>{{ forloop.counter }}.</td>
@@ -149,6 +217,11 @@
{% endif %}
</form>
</div>
+ {% endif %}
</div>
</div>
+<!-- Dialog to display error message -->
+<div id="dialog" title="Alert">
+ <p id="error_msg"></p>
+</div>
{% endblock %}
diff --git a/yaksh/templates/yaksh/login.html b/yaksh/templates/yaksh/login.html
index e4b5933..f40b12f 100644
--- a/yaksh/templates/yaksh/login.html
+++ b/yaksh/templates/yaksh/login.html
@@ -36,6 +36,14 @@
<li>Scales to over 500+ simultaneous users.</li>
</ul>
</p>
+ <br/>
+ <p><b>Fork us at:</b>
+ <a class = "btn btn-social-icon btn-github"
+ href ="https://github.com/fossee/online_test">
+
+ <span class="fa fa-github" style="font-size:48px"></span>
+ </p>
+ </a>
</div>
</div>
diff --git a/yaksh/templates/yaksh/moderator_dashboard.html b/yaksh/templates/yaksh/moderator_dashboard.html
index faccffe..c61675d 100644
--- a/yaksh/templates/yaksh/moderator_dashboard.html
+++ b/yaksh/templates/yaksh/moderator_dashboard.html
@@ -20,7 +20,7 @@
{{ paper.quiz.course.name }}
</td>
<td>
- <a href="{{URL_ROOT}}/exam/manage/monitor/{{paper.id}}/">{{ paper.quiz.description }}</a>
+ <a href="{{URL_ROOT}}/exam/manage/monitor/{{ paper.quiz.id }}/">{{ paper.quiz.description }}</a>
</td>
<td>
{{ answer_papers|length }} user(s)
diff --git a/yaksh/test_views.py b/yaksh/test_views.py
index 37e5ce4..f8a7c87 100644
--- a/yaksh/test_views.py
+++ b/yaksh/test_views.py
@@ -1,6 +1,15 @@
from datetime import datetime
import pytz
import os
+import json
+try:
+ from StringIO import StringIO as string_io
+except ImportError:
+ from io import BytesIO as string_io
+import zipfile
+import shutil
+from textwrap import dedent
+
from django.contrib.auth.models import Group
from django.contrib.auth import authenticate
from django.core.urlresolvers import reverse
@@ -8,10 +17,40 @@ from django.test import TestCase
from django.test import Client
from django.utils import timezone
from django.core import mail
+from django.conf import settings
+from django.core.files.uploadedfile import SimpleUploadedFile
from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\
- QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\
- StdIOBasedTestCase, has_profile
+ QuestionSet, AnswerPaper, Answer, Course, StandardTestCase, has_profile,\
+ AssignmentUpload, FileUpload
+
+
+class TestUserRegistration(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ def tearDown(self):
+ self.registered_user.delete()
+
+ def test_register_user_post(self):
+ response = self.client.post(reverse('yaksh:register'),
+ data={'username': 'register_user',
+ 'email':'register_user@mail.com', 'password': 'reg_user',
+ 'confirm_password': 'reg_user', 'first_name': 'user1_f_name',
+ 'last_name': 'user1_l_name', 'roll_number': '1',
+ 'institute': 'demo_institute', 'department': 'demo_dept',
+ 'position': 'student', 'timezone': pytz.utc.zone
+ }
+ )
+ self.registered_user = User.objects.get(username='register_user')
+ self.assertEqual(self.registered_user.email, 'register_user@mail.com')
+ self.assertEqual(self.registered_user.first_name, 'user1_f_name')
+ self.assertEqual(self.registered_user.last_name, 'user1_l_name')
+ self.assertEqual(self.registered_user.profile.roll_number, '1')
+ self.assertEqual(self.registered_user.profile.institute, 'demo_institute')
+ self.assertEqual(self.registered_user.profile.department, 'demo_dept')
+ self.assertEqual(self.registered_user.profile.position, 'student')
+ self.assertEqual(self.registered_user.profile.timezone, 'UTC')
class TestProfile(TestCase):
@@ -187,6 +226,646 @@ class TestProfile(TestCase):
self.assertTemplateUsed(response, 'yaksh/activation_status.html')
+class TestStudentDashboard(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ # student
+ self.student_plaintext_pass = 'student'
+ self.student = User.objects.create_user(
+ username='student',
+ password=self.student_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='student@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.student,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='student',
+ timezone='UTC'
+ )
+
+ # moderator
+ self.user_plaintext_pass = 'demo'
+ self.user = User.objects.create_user(
+ username='demo_user',
+ password=self.user_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
+ self.course = Course.objects.create(name="Python Course",
+ enrollment="Enroll Request", creator=self.user)
+
+ self.hidden_course = Course.objects.create(name="Hidden Course",
+ enrollment="Enroll Request", creator=self.user, code="hide",
+ hidden=True)
+
+ def tearDown(self):
+ self.client.logout()
+ self.user.delete()
+ self.course.delete()
+
+ def test_student_dashboard_denies_anonymous_user(self):
+ """
+ Check student dashboard denies anonymous user
+ """
+ response = self.client.get(reverse('yaksh:quizlist_user'),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ redirection_url = '/exam/login/?next=/exam/quizzes/'
+ self.assertRedirects(response, redirection_url)
+
+ def test_student_dashboard_all_courses_get(self):
+ """
+ Check student dashboard for all non hidden courses
+ """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:quizlist_user'),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/quizzes_user.html")
+ self.assertEqual(response.context['title'], 'All Courses')
+ self.assertEqual(response.context['courses'][0], self.course)
+
+ def test_student_dashboard_enrolled_courses_get(self):
+ """
+ Check student dashboard for all courses in which student is
+ enrolled
+ """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ self.course.students.add(self.student)
+ response = self.client.get(reverse('yaksh:quizlist_user',
+ kwargs={'enrolled': "enrolled"}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/quizzes_user.html")
+ self.assertEqual(response.context['title'], 'Enrolled Courses')
+ self.assertEqual(response.context['courses'][0], self.course)
+
+ def test_student_dashboard_hidden_courses_post(self):
+ """
+ Get courses for student based on the course code
+ """
+
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.post(reverse('yaksh:quizlist_user'),
+ data={'course_code': 'hide'}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/quizzes_user.html")
+ self.assertEqual(response.context['title'], 'Search')
+ self.assertEqual(response.context['courses'][0], self.hidden_course)
+
+
+class TestMonitor(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ self.mod_group = Group.objects.create(name='moderator')
+ tzone = pytz.timezone('UTC')
+ # Create Moderator with profile
+ self.user_plaintext_pass = 'demo'
+ self.user = User.objects.create_user(
+ username='demo_user',
+ password=self.user_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user,
+ 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(
+ username='demo_student',
+ password=self.student_plaintext_pass,
+ first_name='student_first_name',
+ last_name='student_last_name',
+ email='demo_student@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.student,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
+ # Add to moderator group
+ self.mod_group.user_set.add(self.user)
+
+ self.course = Course.objects.create(name="Python Course",
+ enrollment="Open Enrollment", creator=self.user)
+
+ 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),
+ duration=30, active=True, instructions="Demo Instructions",
+ attempts_allowed=-1, time_between_attempts=0,
+ description='demo quiz', pass_criteria=40,
+ language='Python', course=self.course
+ )
+
+ self.question = Question.objects.create(
+ summary="Test_question", description="Add two numbers",
+ points=1.0, language="python", type="code", user=self.user
+ )
+
+ self.question_paper = QuestionPaper.objects.create(quiz=self.quiz,
+ total_marks=1.0, fixed_question_order=str(self.question)
+ )
+ self.question_paper.fixed_questions.add(self.question)
+ user_answer = "def add(a, b)\n\treturn a+b"
+ self.new_answer = Answer(question=self.question, answer=user_answer,
+ correct=True, error=json.dumps([]))
+ self.new_answer.save()
+ self.answerpaper = AnswerPaper.objects.create(
+ user=self.student, question_paper=self.question_paper,
+ attempt_number=1,
+ start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
+ end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
+ user_ip="127.0.0.1", status="completed", passed=True,
+ percent=1, marks_obtained=1
+ )
+ self.answerpaper.answers.add(self.new_answer)
+ self.answerpaper.questions_answered.add(self.question)
+
+ def tearDown(self):
+ self.client.logout()
+ self.user.delete()
+ self.student.delete()
+ self.quiz.delete()
+ self.course.delete()
+ self.answerpaper.delete()
+ self.question.delete()
+ self.question_paper.delete()
+ self.new_answer.delete()
+
+ def test_monitor_denies_student(self):
+ """
+ Check Monitor denies student
+ """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:monitor'),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 404)
+
+ def test_monitor_display_quizzes(self):
+ """
+ Check all the available quizzes in monitor
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:monitor'),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/monitor.html")
+ self.assertEqual(response.context['course_details'][0], self.course)
+ self.assertEqual(response.context['msg'], "Monitor")
+
+ def test_monitor_display_quiz_results(self):
+ """
+ Check all the quiz results in monitor
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:monitor',
+ kwargs={'quiz_id': self.quiz.id}),
+ follow=True
+ )
+
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/monitor.html")
+ self.assertEqual(response.context['msg'], "Quiz Results")
+ self.assertEqual(response.context['papers'][0], self.answerpaper)
+ self.assertEqual(response.context['latest_attempts'][0], self.answerpaper)
+
+ def test_get_quiz_user_data(self):
+ """
+ Check for getting user data for a quiz
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:user_data',
+ kwargs={'user_id':self.student.id,
+ 'questionpaper_id': self.question_paper.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/user_data.html')
+ self.assertEqual(response.context['data']['papers'][0], self.answerpaper)
+ self.assertEqual(response.context['data']['profile'], self.student.profile)
+ self.assertEqual(response.context['data']['user'], self.student)
+ self.assertEqual(response.context['data']['questionpaperid'],
+ str(self.question_paper.id))
+
+class TestGradeUser(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ self.mod_group = Group.objects.create(name='moderator')
+ tzone = pytz.timezone('UTC')
+ # Create Moderator with profile
+ self.user_plaintext_pass = 'demo'
+ self.user = User.objects.create_user(
+ username='demo_user',
+ password=self.user_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user,
+ 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(
+ username='demo_student',
+ password=self.student_plaintext_pass,
+ first_name='student_first_name',
+ last_name='student_last_name',
+ email='demo_student@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.student,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
+ # Add to moderator group
+ self.mod_group.user_set.add(self.user)
+
+ self.course = Course.objects.create(name="Python Course",
+ enrollment="Open Enrollment", creator=self.user)
+
+ 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),
+ duration=30, active=True, instructions="Demo Instructions",
+ attempts_allowed=-1, time_between_attempts=0,
+ description='demo quiz', pass_criteria=40,
+ language='Python', course=self.course
+ )
+
+ self.question = Question.objects.create(
+ summary="Test_question", description="Add two numbers",
+ points=1.0, language="python", type="code", user=self.user
+ )
+
+ self.question_paper = QuestionPaper.objects.create(quiz=self.quiz,
+ total_marks=1.0, fixed_question_order=str(self.question.id)
+ )
+ self.question_paper.fixed_questions.add(self.question)
+ user_answer = "def add(a, b)\n\treturn a+b"
+ self.new_answer = Answer(question=self.question, answer=user_answer,
+ correct=True, error=json.dumps([]), marks=0.5)
+ self.new_answer.save()
+ self.answerpaper = AnswerPaper.objects.create(
+ user=self.student, question_paper=self.question_paper,
+ attempt_number=1,
+ start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
+ end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
+ user_ip="127.0.0.1", status="completed", passed=True,
+ marks_obtained=0.5
+ )
+ self.answerpaper.answers.add(self.new_answer)
+ self.answerpaper.questions_answered.add(self.question)
+ self.answerpaper.questions.add(self.question)
+
+ def tearDown(self):
+ self.client.logout()
+ self.user.delete()
+ self.student.delete()
+ self.quiz.delete()
+ self.course.delete()
+ self.answerpaper.delete()
+ self.question.delete()
+ self.question_paper.delete()
+ self.new_answer.delete()
+
+ def test_grade_user_denies_student(self):
+ """
+ Check Grade User denies student
+ """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:grade_user'),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 404)
+
+ def test_grade_user_display_quizzes(self):
+ """
+ Check all the available quizzes in grade user
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:grade_user'),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/grade_user.html")
+ self.assertEqual(response.context['course_details'][0], self.course)
+
+ def test_grade_user_get_quiz_users(self):
+ """
+ Check all the available users in quiz in grade user
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:grade_user',
+ kwargs={"quiz_id": self.quiz.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/grade_user.html")
+ self.assertEqual(response.context['users'][0]['user__first_name'],
+ self.student.first_name)
+ self.assertEqual(response.context['quiz'], self.quiz)
+ self.assertFalse(response.context['has_quiz_assignments'])
+
+ def test_grade_user_get_quiz_user_data(self):
+ """
+ Check student attempts and answers
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:grade_user',
+ kwargs={"quiz_id": self.quiz.id,
+ "user_id": self.student.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/grade_user.html")
+ self.assertFalse(response.context['has_user_assignments'])
+ self.assertEqual(response.context['quiz_id'], str(self.quiz.id))
+ self.assertEqual(response.context['user_id'], str(self.student.id))
+ self.assertEqual(response.context['attempts'][0], self.answerpaper)
+
+ def test_grade_user_update_user_marks(self):
+ """
+ Check update marks of student
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ self.client.get(reverse('yaksh:grade_user',
+ kwargs={"quiz_id": self.quiz.id,
+ "user_id": self.student.id}),
+ follow=True
+ )
+ question_marks = "q{0}_marks".format(self.question.id)
+ response = self.client.post(reverse('yaksh:grade_user',
+ kwargs={"quiz_id": self.quiz.id,
+ "user_id": self.student.id,
+ "attempt_number": self.answerpaper.attempt_number}),
+ data={question_marks: 1.0}
+ )
+
+ updated_ans_paper = AnswerPaper.objects.get(user=self.student,
+ question_paper=self.question_paper,
+ attempt_number=self.answerpaper.attempt_number
+ )
+ updated_ans = Answer.objects.get(question=self.question)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/grade_user.html")
+ self.assertEqual(updated_ans.marks, 1.0)
+ self.assertEqual(updated_ans_paper.marks_obtained, 1.0)
+
+
+class TestDownloadAssignment(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ self.mod_group = Group.objects.create(name='moderator')
+ tzone = pytz.timezone('UTC')
+ # Create Moderator with profile
+ self.user_plaintext_pass = 'demo'
+ self.user = User.objects.create_user(
+ username='demo_user',
+ password=self.user_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
+ # Add to moderator group
+ self.mod_group.user_set.add(self.user)
+ # Create Student 1
+ self.student1_plaintext_pass = 'demo_student1'
+ self.student1 = User.objects.create_user(
+ username='demo_student1',
+ password=self.student1_plaintext_pass,
+ first_name='student1_first_name',
+ last_name='student1_last_name',
+ email='demo_student1@test.com'
+ )
+
+ # Create Student 2
+ self.student2_plaintext_pass = 'demo_student2'
+ self.student2 = User.objects.create_user(
+ username='demo_student2',
+ password=self.student2_plaintext_pass,
+ first_name='student2_first_name',
+ last_name='student2_last_name',
+ email='demo_student2@test.com'
+ )
+
+ self.course = Course.objects.create(name="Python Course",
+ enrollment="Enroll Request", creator=self.user)
+
+ 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),
+ duration=30, active=True, instructions="Demo Instructions",
+ attempts_allowed=-1, time_between_attempts=0,
+ description='demo_quiz', pass_criteria=40,
+ language='Python', course=self.course
+ )
+
+ self.question = Question.objects.create(
+ summary="Test_question", description="Assignment Upload",
+ points=1.0, language="python", type="upload", user=self.user
+ )
+
+ self.question_paper = QuestionPaper.objects.create(quiz=self.quiz,
+ total_marks=1.0, fixed_question_order=str(self.question.id)
+ )
+ self.question_paper.fixed_questions.add(self.question)
+
+ # create assignment file
+ assignment_file1 = SimpleUploadedFile("file1.txt", b"Test")
+ assignment_file2 = SimpleUploadedFile("file2.txt", b"Test")
+ assignment_file3 = SimpleUploadedFile("file3.txt", b"Test")
+ self.assignment1 = AssignmentUpload.objects.create(user=self.student1,
+ assignmentQuestion=self.question,
+ assignmentFile=assignment_file1,
+ question_paper=self.question_paper
+ )
+ self.assignment2 = AssignmentUpload.objects.create(user=self.student2,
+ assignmentQuestion=self.question,
+ assignmentFile=assignment_file2,
+ question_paper=self.question_paper
+ )
+
+ def tearDown(self):
+ self.client.logout()
+ self.user.delete()
+ self.student1.delete()
+ self.student2.delete()
+ self.assignment1.delete()
+ self.assignment2.delete()
+ self.quiz.delete()
+ self.course.delete()
+ dir_name = self.quiz.description.replace(" ", "_")
+ file_path = os.sep.join((settings.MEDIA_ROOT, dir_name))
+ if os.path.exists(file_path):
+ shutil.rmtree(file_path)
+
+ def test_download_assignment_denies_student(self):
+ """
+ Check download assignment denies student
+ """
+ self.client.login(
+ username=self.student1.username,
+ password=self.student1_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:download_quiz_assignment',
+ kwargs={'quiz_id': self.quiz.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 404)
+
+ def test_download_assignment_per_quiz(self):
+ """
+ Check for download assignments per quiz
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:download_quiz_assignment',
+ kwargs={'quiz_id': self.quiz.id}),
+ follow=True
+ )
+ file_name = "{0}_Assignment_files.zip".format(self.quiz.description)
+ file_name = file_name.replace(" ", "_")
+ 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('file1.txt', zipped_file.namelist()[0])
+ self.assertIn('file2.txt', zipped_file.namelist()[1])
+ zip_file.close()
+ zipped_file.close()
+
+ def test_download_assignment_per_user(self):
+ """
+ Check for download assignments per quiz
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:download_user_assignment',
+ kwargs={'quiz_id': self.quiz.id,
+ 'question_id': self.question.id,
+ 'user_id': self.student2.id
+ }),
+ follow=True
+ )
+ file_name = "{0}.zip".format(self.student2.get_full_name())
+ file_name = file_name.replace(" ", "_")
+ 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('file2.txt', zipped_file.namelist()[0])
+ zip_file.close()
+ zipped_file.close()
+
+
class TestAddQuiz(TestCase):
def setUp(self):
self.client = Client()
@@ -354,7 +1033,8 @@ class TestAddQuiz(TestCase):
)
tzone = pytz.timezone('UTC')
- response = self.client.post(reverse('yaksh:add_quiz', kwargs={"course_id": self.course.id}),
+ response = self.client.post(reverse('yaksh:add_quiz',
+ kwargs={"course_id": self.course.id}),
data={
'start_date_time': '2016-01-10 09:00:15',
'end_date_time': '2016-01-15 09:00:15',
@@ -515,6 +1195,7 @@ class TestAddTeacher(TestCase):
username=self.user.username,
password=self.user_plaintext_pass
)
+
teacher_id_list = []
for i in range(5):
@@ -934,8 +1615,7 @@ class TestAddCourse(TestCase):
'end_enroll_time': '2016-01-15 09:00:15',
}
)
- course_list = Course.objects.all().order_by('-id')
- new_course = course_list[0]
+ new_course = Course.objects.latest('created_on')
self.assertEqual(new_course.name, 'new_demo_course_1')
self.assertEqual(new_course.enrollment, 'open')
self.assertEqual(new_course.active, True)
@@ -995,6 +1675,14 @@ class TestCourseDetail(TestCase):
last_name='student_last_name',
email='demo_student@test.com'
)
+ self.student1_plaintext_pass = 'demo_student1'
+ self.student1 = User.objects.create_user(
+ username='demo_student1',
+ password=self.student1_plaintext_pass,
+ first_name='student_first_name',
+ last_name='student_last_name',
+ email='demo_student1@test.com'
+ )
# Add to moderator group
self.mod_group.user_set.add(self.user1)
@@ -1020,7 +1708,7 @@ class TestCourseDetail(TestCase):
follow=True
)
redirect_destination = ('/exam/login/?next=/exam/'
- 'manage/course_detail/1/')
+ 'manage/course_detail/{0}/'.format(self.user1_course.id))
self.assertRedirects(response, redirect_destination)
def test_course_detail_denies_non_moderator(self):
@@ -1071,6 +1759,146 @@ class TestCourseDetail(TestCase):
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'yaksh/course_detail.html')
+ def test_student_course_enroll_get(self):
+ """
+ Enroll student in a course using get request
+ """
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:enroll_user',
+ kwargs={'course_id': self.user1_course.id,
+ 'user_id': self.student.id})
+ )
+ enrolled_student = self.user1_course.students.all()
+ self.assertEqual(response.status_code, 200)
+ self.assertSequenceEqual([self.student], enrolled_student)
+
+ def test_student_course_enroll_post(self):
+ """
+ Enroll student in a course using post request
+ """
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ response = self.client.post(reverse('yaksh:enroll_users',
+ kwargs={'course_id': self.user1_course.id}),
+ data={'check': self.student1.id}
+ )
+ enrolled_student = self.user1_course.students.all()
+ self.assertEqual(response.status_code, 200)
+ self.assertSequenceEqual([self.student1], enrolled_student)
+
+ def test_student_course_reject_get(self):
+ """
+ Reject student in a course using get request
+ """
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:reject_user',
+ kwargs={'course_id': self.user1_course.id,
+ 'user_id': self.student.id})
+ )
+ enrolled_student = self.user1_course.rejected.all()
+ self.assertEqual(response.status_code, 200)
+ self.assertSequenceEqual([self.student], enrolled_student)
+
+ def test_student_course_reject_post(self):
+ """
+ Reject student in a course using post request
+ """
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ response = self.client.post(reverse('yaksh:reject_users',
+ kwargs={'course_id': self.user1_course.id}),
+ data={'check': self.student1.id}
+ )
+ enrolled_student = self.user1_course.rejected.all()
+ self.assertEqual(response.status_code, 200)
+ self.assertSequenceEqual([self.student1], enrolled_student)
+
+ def test_toggle_course_status_get(self):
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ response = self.client.post(reverse('yaksh:toggle_course_status',
+ kwargs={'course_id': self.user1_course.id})
+ )
+ self.assertEqual(response.status_code, 200)
+ course = Course.objects.get(name="Python Course")
+ self.assertFalse(course.active)
+ self.assertEqual(self.user1_course, response.context['course'])
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/course_detail.html')
+
+ def test_send_mail_to_course_students(self):
+ """ Check if bulk mail is sent to multiple students enrolled in a course
+ """
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ self.student2 = User.objects.create_user(
+ username='demo_student2',
+ password=self.student_plaintext_pass,
+ first_name='student_first_name',
+ last_name='student_last_name',
+ email='demo_student2@test.com'
+ )
+ self.student3 = User.objects.create_user(
+ username='demo_student3',
+ password=self.student_plaintext_pass,
+ first_name='student_first_name',
+ last_name='student_last_name',
+ email='demo_student3@test.com'
+ )
+ self.student4 = User.objects.create_user(
+ username='demo_student4',
+ password=self.student_plaintext_pass,
+ first_name='student_first_name',
+ last_name='student_last_name',
+ email='demo_student4@test.com'
+ )
+ user_ids = [self.student.id, self.student2.id, self.student3.id,
+ self.student4.id]
+ user_emails = [self.student.email, self.student2.email,
+ self.student3.email, self.student4.email]
+
+ self.user1_course.students.add(*user_ids)
+ attachment = SimpleUploadedFile("file.txt", b"Test")
+ email_data = {
+ 'send_mail': 'send_mail', 'email_attach': [attachment],
+ 'subject': 'test_bulk_mail', 'body': 'Test_Mail',
+ 'check': user_ids
+ }
+ self.client.post(reverse(
+ 'yaksh:send_mail', kwargs={'course_id': self.user1_course.id}),
+ data=email_data
+ )
+ attachment_file = mail.outbox[0].attachments[0][0]
+ subject = mail.outbox[0].subject
+ body = mail.outbox[0].alternatives[0][0]
+ recipients = mail.outbox[0].recipients()
+ self.assertEqual(attachment_file, "file.txt")
+ self.assertEqual(subject, "test_bulk_mail")
+ self.assertEqual(body, "Test_Mail")
+ self.assertSequenceEqual(recipients, user_emails)
+
+ # Test for get request in send mail
+ get_response = self.client.get(reverse(
+ 'yaksh:send_mail', kwargs={'course_id': self.user1_course.id})
+ )
+ self.assertEqual(get_response.status_code, 200)
+ self.assertEqual(get_response.context['course'], self.user1_course)
+ self.assertEqual(get_response.context['state'], 'mail')
+
class TestEnrollRequest(TestCase):
def setUp(self):
@@ -1193,7 +2021,7 @@ class TestViewAnswerPaper(TestCase):
email='demo@test.com'
)
- self.user1 = User.objects.get(pk=1)
+ self.user1 = User.objects.get(username="demo_user1")
self.course = Course.objects.create(name="Python Course",
enrollment="Enroll Request",
@@ -1210,7 +2038,7 @@ class TestViewAnswerPaper(TestCase):
self.question_paper.fixed_questions.add(self.question)
self.question_paper.save()
- AnswerPaper.objects.create(user_id=3,
+ self.ans_paper = AnswerPaper.objects.create(user_id=3,
attempt_number=1, question_paper=self.question_paper,
start_time=timezone.now(), user_ip='101.0.0.1',
end_time=timezone.now()+timezone.timedelta(minutes=20))
@@ -1240,7 +2068,7 @@ class TestViewAnswerPaper(TestCase):
def test_cannot_view(self):
# Given, enrolled user tries to view when not permitted by moderator
- user2 = User.objects.get(pk=2)
+ user2 = User.objects.get(username="demo_user2")
self.course.students.add(user2)
self.course.save()
self.quiz.view_answerpaper = False
@@ -1260,12 +2088,12 @@ class TestViewAnswerPaper(TestCase):
# Then
self.assertRedirects(response, '/exam/quizzes/')
- def test_can_view(self):
+ def test_can_view_answerpaper(self):
# Given, user enrolled and can view
- user3 = User.objects.get(pk=3)
+ user3 = User.objects.get(username="demo_user3")
self.course.students.add(user3)
self.course.save()
- answerpaper = AnswerPaper.objects.get(pk=1)
+ answerpaper = AnswerPaper.objects.get(pk=self.ans_paper.id)
self.quiz.view_answerpaper = True
self.quiz.save()
self.client.login(
@@ -1300,7 +2128,7 @@ class TestViewAnswerPaper(TestCase):
def test_view_when_not_enrolled(self):
# Given, user tries to view when not enrolled in the course
- user2 = User.objects.get(pk=2)
+ user2 = User.objects.get(username="demo_user2")
self.client.login(
username=user2.username,
password=self.plaintext_pass
@@ -1534,7 +2362,10 @@ class TestGrader(TestCase):
def test_regrade_denies_anonymous(self):
# Given
- redirect_destination = ('/exam/login/?next=/exam/manage/regrade/answerpaper/1/1/1/')
+ redirect_destination = dedent('''\
+ /exam/login/?next=/exam/manage/regrade/answerpaper/{}/{}/{}/'''.format(
+ self.course.id, self.question.id, self.answerpaper.id)
+ )
# When
response = self.client.get(reverse('yaksh:regrade',
@@ -1686,3 +2517,765 @@ class TestPasswordReset(TestCase):
# Finally
self.client.logout()
+
+
+class TestModeratorDashboard(TestCase):
+ def setUp(self):
+ self.client = Client()
+ tzone = pytz.timezone("utc")
+ self.mod_group = Group.objects.create(name='moderator')
+ # student
+ self.student_plaintext_pass = 'student'
+ self.student = User.objects.create_user(
+ username='student',
+ password=self.student_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='student@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.student,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='student',
+ timezone='UTC'
+ )
+
+ # moderator
+ self.user_plaintext_pass = 'demo'
+ self.user = User.objects.create_user(
+ username='demo_user',
+ password=self.user_plaintext_pass,
+ first_name='user_first_name',
+ last_name='user_last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+ self.mod_group.user_set.add(self.user)
+ self.course = Course.objects.create(name="Python Course",
+ enrollment="Enroll Request", creator=self.user)
+
+ 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),
+ duration=30, active=True, instructions="Demo Instructions",
+ attempts_allowed=-1, time_between_attempts=0,
+ description='demo quiz', pass_criteria=40,
+ language='Python', course=self.course
+ )
+
+ self.question = Question.objects.create(
+ summary="Test_question", description="Add two numbers",
+ points=1.0, language="python", type="code", user=self.user
+ )
+
+ self.question_paper = QuestionPaper.objects.create(quiz=self.quiz,
+ total_marks=1.0, fixed_question_order=str(self.question.id)
+ )
+ self.question_paper.fixed_questions.add(self.question)
+
+ # student answerpaper
+ user_answer = "def add(a, b)\n\treturn a+b"
+ self.new_answer = Answer(question=self.question, answer=user_answer,
+ correct=True, error=json.dumps([]), marks=0.5)
+ self.new_answer.save()
+ self.answerpaper = AnswerPaper.objects.create(
+ user=self.student, question_paper=self.question_paper,
+ attempt_number=1,
+ start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
+ end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
+ user_ip="127.0.0.1", status="completed", passed=True,
+ marks_obtained=0.5
+ )
+ self.answerpaper.answers.add(self.new_answer)
+ self.answerpaper.questions_answered.add(self.question)
+ self.answerpaper.questions.add(self.question)
+
+ # moderator trial answerpaper
+ self.trial_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='trial quiz', pass_criteria=40,
+ language='Python', course=self.course, is_trial=True
+ )
+
+ self.trial_question_paper = QuestionPaper.objects.create(
+ quiz=self.trial_quiz,
+ total_marks=1.0, fixed_question_order=str(self.question.id)
+ )
+ self.trial_question_paper.fixed_questions.add(self.question)
+
+ self.new_answer1 = Answer(question=self.question, answer=user_answer,
+ correct=True, error=json.dumps([]), marks=0.5)
+ self.new_answer1.save()
+ self.trial_answerpaper = AnswerPaper.objects.create(
+ user=self.user, question_paper=self.trial_question_paper,
+ attempt_number=1,
+ start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
+ end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
+ user_ip="127.0.0.1", status="completed", passed=True,
+ marks_obtained=0.5
+ )
+ self.trial_answerpaper.answers.add(self.new_answer1)
+ self.trial_answerpaper.questions_answered.add(self.question)
+ self.trial_answerpaper.questions.add(self.question)
+
+ def tearDown(self):
+ self.client.logout()
+ self.user.delete()
+ self.quiz.delete()
+ self.question_paper.delete()
+ self.answerpaper.delete()
+ self.new_answer.delete()
+
+ def test_moderator_dashboard_denies_student(self):
+ """
+ Check moderator dashboard denies student
+ """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:manage'),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertRedirects(response, '/exam/quizzes/')
+
+ def test_moderator_dashboard_get_all_quizzes(self):
+ """
+ Check moderator dashboard to get all the moderator created quizzes
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+
+ response = self.client.get(reverse('yaksh:manage'),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/moderator_dashboard.html")
+ self.assertEqual(response.context['trial_paper'][0], self.trial_answerpaper)
+ paper, answer_papers, users_passed, users_failed =\
+ response.context['users_per_paper'][0]
+ self.assertEqual(paper, self.question_paper)
+ self.assertEqual(answer_papers[0], self.answerpaper)
+ self.assertEqual(users_passed, 1)
+ self.assertEqual(users_failed, 0)
+
+ def test_moderator_dashboard_delete_trial_papers(self):
+ """
+ Check moderator dashboard to delete trial papers
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ self.course.is_trial=True
+ self.course.save()
+ response = self.client.post(reverse('yaksh:manage'),
+ data={'delete_paper': [self.trial_answerpaper.id]}
+ )
+
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/moderator_dashboard.html")
+ updated_answerpaper = AnswerPaper.objects.filter(user=self.user)
+ updated_quiz = Quiz.objects.filter(
+ description=self.trial_question_paper.quiz.description
+ )
+ updated_course = Course.objects.filter(
+ name=self.trial_question_paper.quiz.course.name)
+ self.assertSequenceEqual(updated_answerpaper, [])
+ self.assertSequenceEqual(updated_quiz, [])
+ self.assertSequenceEqual(updated_course, [])
+
+class TestUserLogin(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ # Create Moderator with profile
+ self.user1_plaintext_pass = 'demo1'
+ self.user1 = User.objects.create_user(
+ username='demo_user1',
+ password=self.user1_plaintext_pass,
+ first_name='user1_first_name',
+ last_name='user1_last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user1,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
+ def tearDown(self):
+ self.client.logout()
+ settings.IS_DEVELOPMENT = True
+ self.user1.delete()
+
+ def test_successful_user_login(self):
+ """
+ Check if user is successfully logged in
+ """
+ response = self.client.post(reverse('yaksh:login'),
+ data={'username': self.user1.username,
+ 'password': self.user1_plaintext_pass}
+ )
+ self.assertEqual(response.status_code, 302)
+ self.assertRedirects(response, '/exam/quizzes/')
+
+ def test_unsuccessful_user_login(self):
+ """
+ Check for failed login attempt for incorrect username/password
+ """
+ response = self.client.post(reverse('yaksh:login'),
+ data={'username': self.user1.username,
+ 'password': "demo"}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/login.html')
+
+ def test_email_verified_decorator_for_user_login(self):
+ """
+ Check email verified decorator to check for user login
+ """
+ settings.IS_DEVELOPMENT = False
+ response = self.client.post(reverse('yaksh:login'),
+ data={'username': self.user1.username,
+ 'password': self.user1_plaintext_pass}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "yaksh/activation_status.html")
+
+
+class TestDownloadcsv(TestCase):
+ def setUp(self):
+ self.client = Client()
+ tzone = pytz.timezone("utc")
+ self.mod_group = Group.objects.create(name='moderator')
+ # student
+ self.student_plaintext_pass = 'student'
+ self.student = User.objects.create_user(
+ username='student',
+ password=self.student_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='student@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.student,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='student',
+ timezone='UTC'
+ )
+
+ # moderator
+ self.user_plaintext_pass = 'demo'
+ self.user = User.objects.create_user(
+ username='demo_user',
+ password=self.user_plaintext_pass,
+ first_name='user_first_name',
+ last_name='user_last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+ self.mod_group.user_set.add(self.user)
+ self.course = Course.objects.create(name="Python Course",
+ enrollment="Enroll Request", creator=self.user)
+
+ 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),
+ duration=30, active=True, instructions="Demo Instructions",
+ attempts_allowed=-1, time_between_attempts=0,
+ description='demo quiz', pass_criteria=40,
+ language='Python', course=self.course
+ )
+
+ self.question = Question.objects.create(
+ summary="Test_question", description="Add two numbers",
+ points=1.0, language="python", type="code", user=self.user
+ )
+
+ self.question_paper = QuestionPaper.objects.create(quiz=self.quiz,
+ total_marks=1.0, fixed_question_order=str(self.question.id)
+ )
+ self.question_paper.fixed_questions.add(self.question)
+
+ # student answerpaper
+ user_answer = "def add(a, b)\n\treturn a+b"
+ self.new_answer = Answer(question=self.question, answer=user_answer,
+ correct=True, error=json.dumps([]), marks=0.5)
+ self.new_answer.save()
+ self.answerpaper = AnswerPaper.objects.create(
+ user=self.student, question_paper=self.question_paper,
+ attempt_number=1,
+ start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
+ end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
+ user_ip="127.0.0.1", status="completed", passed=True,
+ marks_obtained=0.5
+ )
+ self.answerpaper.answers.add(self.new_answer)
+ self.answerpaper.questions_answered.add(self.question)
+ self.answerpaper.questions.add(self.question)
+
+ def tearDown(self):
+ self.client.logout()
+ self.user.delete()
+ self.student.delete()
+ self.quiz.delete()
+ self.course.delete()
+
+ def test_download_csv_denies_student(self):
+ """
+ Check download csv denies student
+ """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:download_csv',
+ kwargs={"questionpaper_id": self.question_paper.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 404)
+
+ def test_download_course_csv_denies_student(self):
+ """
+ Check download course csv denies student
+ """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:download_course_csv',
+ kwargs={"course_id": self.course.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 404)
+
+ def test_download_csv_denies_non_course_creator(self):
+ """
+ Check download csv denies non course creator
+ """
+ self.mod_group.user_set.add(self.student)
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:download_csv',
+ kwargs={"questionpaper_id": self.question_paper.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 404)
+
+ def test_download_course_csv_denies_non_course_creator(self):
+ """
+ Check download course csv denies non course creator
+ """
+ self.mod_group.user_set.add(self.student)
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:download_course_csv',
+ kwargs={"course_id": self.course.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 404)
+
+ def test_download_course_csv(self):
+ """
+ Check for csv result of a course
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:download_course_csv',
+ kwargs={'course_id': self.course.id}),
+ follow=True
+ )
+ file_name = "{0}.csv".format(self.course.name.lower())
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.get('Content-Disposition'),
+ 'attachment; filename="{0}"'.format(file_name))
+
+ def test_download_quiz_csv(self):
+ """
+ Check for csv result of a quiz
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:download_csv',
+ kwargs={'questionpaper_id': self.question_paper.id}),
+ follow=True
+ )
+ file_name = "{0}.csv".format(self.quiz.description)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.get('Content-Disposition'),
+ 'attachment; filename="{0}"'.format(file_name))
+
+
+class TestShowQuestions(TestCase):
+ def setUp(self):
+ self.client = Client()
+ tzone = pytz.timezone("utc")
+ self.mod_group = Group.objects.create(name='moderator')
+ # student
+ self.student_plaintext_pass = 'student'
+ self.student = User.objects.create_user(
+ username='student',
+ password=self.student_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='student@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.student,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='student',
+ timezone='UTC'
+ )
+
+ # moderator
+ self.user_plaintext_pass = 'demo'
+ self.user = User.objects.create_user(
+ username='demo_user',
+ password=self.user_plaintext_pass,
+ first_name='user_first_name',
+ last_name='user_last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+ self.mod_group.user_set.add(self.user)
+ self.question = Question.objects.create(
+ summary="Test_question1", description="Add two numbers",
+ points=2.0, language="python", type="code", user=self.user,
+ active=True
+ )
+ self.question1 = Question.objects.create(
+ summary="Test_question2", description="Add two numbers",
+ points=1.0, language="python", type="mcq", user=self.user,
+ active=True
+ )
+
+ def test_show_questions_denies_student(self):
+ """
+ Check show questions denies student
+ """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:show_questions'),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 404)
+
+ def test_show_all_questions(self):
+ """
+ Check if all the user created questions are shown
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:show_questions'),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/showquestions.html')
+ self.assertEqual(response.context['questions'][0], self.question)
+
+ def test_download_questions(self):
+ """
+ Check for downloading questions zip file
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.post(reverse('yaksh:show_questions'),
+ data={'question': [self.question.id],
+ 'download': 'download'}
+ )
+ file_name = "{0}_questions.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.json', zipped_file.namelist())
+ zip_file.close()
+ zipped_file.close()
+
+ response = self.client.post(reverse('yaksh:show_questions'),
+ data={'question': [],
+ 'download': 'download'}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/showquestions.html')
+ self.assertIn("download", response.context['msg'])
+
+
+ def test_upload_questions(self):
+ """
+ Check for uploading questions zip file
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ ques_file = os.path.join(settings.FIXTURE_DIRS, "demo_questions.zip")
+ f = open(ques_file, 'rb')
+ questions_file = SimpleUploadedFile(ques_file, f.read(),
+ content_type="application/zip")
+ response = self.client.post(reverse('yaksh:show_questions'),
+ data={'file': questions_file,
+ 'upload': 'upload'}
+ )
+ uploaded_ques = Question.objects.filter(active=True,
+ summary="Yaksh Demo Question",
+ user=self.user).count()
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/showquestions.html')
+ self.assertEqual(uploaded_ques, 3)
+ f.close()
+ dummy_file = SimpleUploadedFile("test.txt", b"test")
+ response = self.client.post(reverse('yaksh:show_questions'),
+ data={'file': dummy_file,
+ 'upload': 'upload'}
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/showquestions.html')
+ self.assertIn("ZIP file", response.context['message'])
+
+ def test_attempt_questions(self):
+ """
+ Check for testing questions
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.post(reverse('yaksh:show_questions'),
+ data={'question': [self.question.id],
+ 'test': 'test'}
+ )
+ trial_que_paper = QuestionPaper.objects.get(
+ quiz__description="trial_questions"
+ )
+ redirection_url = "/exam/start/1/{}".format(trial_que_paper.id)
+ self.assertEqual(response.status_code, 302)
+ self.assertRedirects(response, redirection_url, target_status_code=301)
+
+ def test_ajax_questions_filter(self):
+ """
+ Check for filter questions based type, marks and
+ language of a question
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.post(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)
+
+
+class TestShowStatistics(TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ self.mod_group = Group.objects.create(name='moderator')
+ tzone = pytz.timezone('UTC')
+ # Create Moderator with profile
+ self.user_plaintext_pass = 'demo'
+ self.user = User.objects.create_user(
+ username='demo_user',
+ password=self.user_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user,
+ 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(
+ username='demo_student',
+ password=self.student_plaintext_pass,
+ first_name='student_first_name',
+ last_name='student_last_name',
+ email='demo_student@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.student,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
+ # Add to moderator group
+ self.mod_group.user_set.add(self.user)
+
+ self.course = Course.objects.create(name="Python Course",
+ enrollment="Open Enrollment", creator=self.user)
+
+ 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),
+ duration=30, active=True, instructions="Demo Instructions",
+ attempts_allowed=-1, time_between_attempts=0,
+ description='demo quiz', pass_criteria=40,
+ language='Python', course=self.course
+ )
+
+ self.question = Question.objects.create(
+ summary="Test_question", description="Add two numbers",
+ points=1.0, language="python", type="code", user=self.user
+ )
+
+ self.question_paper = QuestionPaper.objects.create(quiz=self.quiz,
+ total_marks=1.0, fixed_question_order=str(self.question)
+ )
+ self.question_paper.fixed_questions.add(self.question)
+ user_answer = "def add(a, b)\n\treturn a+b"
+ self.new_answer = Answer(question=self.question, answer=user_answer,
+ correct=True, error=json.dumps([]))
+ self.new_answer.save()
+ self.answerpaper = AnswerPaper.objects.create(
+ user=self.student, question_paper=self.question_paper,
+ attempt_number=1,
+ start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
+ end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
+ user_ip="127.0.0.1", status="completed", passed=True,
+ percent=1, marks_obtained=1
+ )
+ self.answerpaper.answers.add(self.new_answer)
+ self.answerpaper.questions_answered.add(self.question)
+ self.answerpaper.questions.add(self.question)
+
+ def tearDown(self):
+ self.client.logout()
+ self.user.delete()
+ self.student.delete()
+ self.quiz.delete()
+ self.course.delete()
+ self.answerpaper.delete()
+ self.question.delete()
+ self.question_paper.delete()
+ self.new_answer.delete()
+
+ def test_show_statistics_denies_student(self):
+ """
+ Check show statistics denies student
+ """
+ self.client.login(
+ username=self.student.username,
+ password=self.student_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:show_statistics',
+ kwargs={"questionpaper_id": self.question_paper.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 404)
+
+ def test_show_statistics_for_student(self):
+ """
+ Check for student statistics
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:show_statistics',
+ kwargs={'questionpaper_id': self.question_paper.id}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/statistics_question.html')
+ self.assertEqual(response.context['quiz'], self.quiz)
+ self.assertEqual(response.context['attempts'][0],
+ self.answerpaper.attempt_number)
+ self.assertEqual(response.context['questionpaper_id'],
+ str(self.question_paper.id))
+
+ def test_show_statistics_for_student_per_attempt(self):
+ """
+ Check for student statistics per attempt
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+ response = self.client.get(reverse('yaksh:show_statistics',
+ kwargs={'questionpaper_id': self.question_paper.id,
+ 'attempt_number': self.answerpaper.attempt_number}),
+ follow=True
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'yaksh/statistics_question.html')
+ self.assertSequenceEqual(response.context['question_stats'][self.question],
+ [1, 1])
+ self.assertEqual(response.context['attempts'][0], 1)
+ self.assertEqual(response.context['total'], 1)
diff --git a/yaksh/urls.py b/yaksh/urls.py
index e4676d3..5058340 100644
--- a/yaksh/urls.py
+++ b/yaksh/urls.py
@@ -18,7 +18,7 @@ urlpatterns = [
url(r'^complete/$', views.complete),
url(r'^complete/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$',\
views.complete),
- url(r'^register/$', views.user_register),
+ url(r'^register/$', views.user_register, name="register"),
url(r'^(?P<q_id>\d+)/check/$', views.check),
url(r'^(?P<q_id>\d+)/check/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$',\
views.check),
@@ -29,62 +29,67 @@ urlpatterns = [
url(r'^enroll_request/(?P<course_id>\d+)/$', views.enroll_request, name='enroll_request'),
url(r'^self_enroll/(?P<course_id>\d+)/$', views.self_enroll, name='self_enroll'),
url(r'^view_answerpaper/(?P<questionpaper_id>\d+)/$', views.view_answerpaper, name='view_answerpaper'),
- url(r'^download/user_assignment/(?P<question_id>\d+)/(?P<user_id>\d+)/(?P<quiz_id>\d+)$',
- views.download_assignment_file, name="download_user_assignment"),
url(r'^manage/$', views.prof_manage, name='manage'),
- url(r'^manage/addquestion/$', views.add_question),
- url(r'^manage/addquestion/(?P<question_id>\d+)/$', views.add_question),
+ 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/(?P<course_id>\d+)/$', views.add_quiz, name='add_quiz'),
url(r'^manage/addquiz/(?P<course_id>\d+)/(?P<quiz_id>\d+)/$', views.add_quiz, name='edit_quiz'),
- url(r'^manage/gradeuser/$', views.grade_user),
- url(r'^manage/gradeuser/(?P<quiz_id>\d+)/$',views.grade_user),
- url(r'^manage/gradeuser/(?P<quiz_id>\d+)/(?P<user_id>\d+)/$',views.grade_user),
- url(r'^manage/gradeuser/(?P<quiz_id>\d+)/(?P<user_id>\d+)/(?P<attempt_number>\d+)/$',views.grade_user),
- url(r'^manage/questions/$', views.show_all_questions),
- url(r'^manage/monitor/$', views.monitor),
- url(r'^manage/showquestionpapers/$', views.show_all_questionpapers),
- url(r'^manage/showquestionpapers/(?P<questionpaper_id>\d+)/$',\
- views.show_all_questionpapers),
- url(r'^manage/monitor/(?P<quiz_id>\d+)/$', views.monitor),
+ url(r'^manage/gradeuser/$', views.grade_user, name="grade_user"),
+ url(r'^manage/gradeuser/(?P<quiz_id>\d+)/$',views.grade_user, name="grade_user"),
+ url(r'^manage/gradeuser/(?P<quiz_id>\d+)/(?P<user_id>\d+)/$',
+ views.grade_user, name="grade_user"),
+ url(r'^manage/gradeuser/(?P<quiz_id>\d+)/(?P<user_id>\d+)/(?P<attempt_number>\d+)/$',
+ views.grade_user, name="grade_user"),
+ url(r'^manage/questions/$', views.show_all_questions, name="show_questions"),
+ url(r'^manage/monitor/$', views.monitor, name="monitor"),
+ url(r'^manage/monitor/(?P<quiz_id>\d+)/$', views.monitor, name="monitor"),
url(r'^manage/user_data/(?P<user_id>\d+)/(?P<questionpaper_id>\d+)/$',
- views.user_data),
+ 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='design_questionpaper'),
url(r'^manage/designquestionpaper/(?P<quiz_id>\d+)/(?P<questionpaper_id>\d+)/$',
views.design_questionpaper, name='designquestionpaper'),
url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/$',
- views.show_statistics),
+ views.show_statistics, name="show_statistics"),
url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<attempt_number>\d+)/$',
- views.show_statistics),
+ views.show_statistics, name="show_statistics"),
url(r'^manage/monitor/download_csv/(?P<questionpaper_id>\d+)/$',
- views.download_csv),
+ views.download_csv, name="download_csv"),
url(r'^manage/duplicate_course/(?P<course_id>\d+)/$', views.duplicate_course,
name='duplicate_course'),
url(r'manage/courses/$', views.courses, name='courses'),
url(r'manage/add_course/$', views.add_course, name='add_course'),
url(r'manage/edit_course/(?P<course_id>\d+)$', views.add_course, name='edit_course'),
url(r'manage/course_detail/(?P<course_id>\d+)/$', views.course_detail, name='course_detail'),
- url(r'manage/enroll/(?P<course_id>\d+)/(?P<user_id>\d+)/$', views.enroll),
+ url(r'manage/enroll/(?P<course_id>\d+)/(?P<user_id>\d+)/$', views.enroll,
+ name="enroll_user"),
url(r'manage/enroll/rejected/(?P<course_id>\d+)/(?P<user_id>\d+)/$',
views.enroll, {'was_rejected': True}),
- url(r'manage/reject/(?P<course_id>\d+)/(?P<user_id>\d+)/$', views.reject),
+ url(r'manage/send_mail/(?P<course_id>\d+)/$', views.send_mail, name="send_mail"),
+ url(r'manage/reject/(?P<course_id>\d+)/(?P<user_id>\d+)/$', views.reject,
+ name="reject_user"),
url(r'manage/enrolled/reject/(?P<course_id>\d+)/(?P<user_id>\d+)/$',
- views.reject, {'was_enrolled': True}),
- url(r'manage/toggle_status/(?P<course_id>\d+)/$', views.toggle_course_status),
- url(r'^ajax/questions/filter/$', views.ajax_questions_filter),
+ views.reject, {'was_enrolled': True}, name="reject_user"),
+ url(r'manage/toggle_status/(?P<course_id>\d+)/$', views.toggle_course_status,
+ name="toggle_course_status"),
+ url(r'^ajax/questions/filter/$', views.ajax_questions_filter,
+ name="questions_filter"),
url(r'^editprofile/$', views.edit_profile, name='edit_profile'),
url(r'^viewprofile/$', views.view_profile, name='view_profile'),
- url(r'^manage/enroll/(?P<course_id>\d+)/$', views.enroll),
+ url(r'^manage/enroll/(?P<course_id>\d+)/$', views.enroll, name="enroll_users"),
url(r'manage/enroll/rejected/(?P<course_id>\d+)/$',
views.enroll, {'was_rejected': True}),
url(r'manage/enrolled/reject/(?P<course_id>\d+)/$',
- views.reject, {'was_enrolled': True}),
+ views.reject, {'was_enrolled': True}, name="reject_users"),
url(r'^manage/searchteacher/(?P<course_id>\d+)/$', views.search_teacher),
url(r'^manage/addteacher/(?P<course_id>\d+)/$', views.add_teacher, name='add_teacher'),
url(r'^manage/remove_teachers/(?P<course_id>\d+)/$', views.remove_teachers, name='remove_teacher'),
- url(r'^manage/download_questions/$', views.show_all_questions),
- url(r'^manage/upload_questions/$', views.show_all_questions),
+ url(r'^manage/download_questions/$', views.show_all_questions,
+ name="show_questions"),
+ url(r'^manage/upload_questions/$', views.show_all_questions,
+ name="show_questions"),
url(r'^manage/grader/$', views.grader, name='grader'),
url(r'^manage/regrade/question/(?P<course_id>\d+)/(?P<question_id>\d+)/$',
views.regrade, name='regrade'),
@@ -97,9 +102,9 @@ urlpatterns = [
url(r'^manage/(?P<mode>[\w\-]+)/(?P<quiz_id>\d+)/$', views.test_quiz),
url(r'^manage/create_demo_course/$', views.create_demo_course),
url(r'^manage/courses/download_course_csv/(?P<course_id>\d+)/$',
- views.download_course_csv),
+ views.download_course_csv, name="download_course_csv"),
url(r'^manage/download/user_assignment/(?P<question_id>\d+)/(?P<user_id>\d+)/(?P<quiz_id>\d+)/$',
- views.download_assignment_file),
+ views.download_assignment_file, name="download_user_assignment"),
url(r'^manage/download/quiz_assignments/(?P<quiz_id>\d+)/$',
- views.download_assignment_file)
+ views.download_assignment_file, name="download_quiz_assignment")
]
diff --git a/yaksh/views.py b/yaksh/views.py
index 8746a57..7f907aa 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -42,7 +42,7 @@ from yaksh.forms import UserRegisterForm, UserLoginForm, QuizForm,\
from .settings import URL_ROOT
from yaksh.models import AssignmentUpload
from .file_utils import extract_files
-from .send_emails import send_user_mail, generate_activation_key
+from .send_emails import send_user_mail, generate_activation_key, send_bulk_mail
from .decorators import email_verified
@@ -275,31 +275,6 @@ def add_quiz(request, course_id, quiz_id=None):
context,
context_instance=ci)
-
-@login_required
-@email_verified
-def show_all_questionpapers(request, questionpaper_id=None):
- user = request.user
- ci = RequestContext(request)
- if not user.is_authenticated() or not is_moderator(user):
- raise Http404('You are not allowed to view this page!')
-
- if questionpaper_id is None:
- qu_papers = QuestionPaper.objects.filter(is_trial=False)
- context = {'papers': qu_papers}
- return my_render_to_response('yaksh/showquestionpapers.html', context,
- context_instance=ci)
- else:
- qu_papers = QuestionPaper.objects.get(id=questionpaper_id)
- quiz = qu_papers.quiz
- fixed_questions = qu_papers.get_ordered_questions()
- random_questions = qu_papers.random_questions.all()
- context = {'quiz': quiz, 'fixed_questions': fixed_questions,
- 'random_questions': random_questions}
- return my_render_to_response('yaksh/editquestionpaper.html', context,
- context_instance=ci)
-
-
@login_required
@email_verified
def prof_manage(request, msg=None):
@@ -754,6 +729,39 @@ def enroll(request, course_id, user_id=None, was_rejected=False):
@login_required
@email_verified
+def send_mail(request, course_id, user_id=None):
+ user = request.user
+ ci = RequestContext(request)
+ if not is_moderator(user):
+ raise Http404('You are not allowed to view this page')
+
+ 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')
+
+ message = None
+ if request.method == 'POST':
+ user_ids = request.POST.getlist('check')
+ if request.POST.get('send_mail') == 'send_mail':
+ users = User.objects.filter(id__in=user_ids)
+ recipients = [student.email for student in users]
+ email_body = request.POST.get('body')
+ subject = request.POST.get('subject')
+ attachments = request.FILES.getlist('email_attach')
+ message = send_bulk_mail(
+ subject, email_body, recipients, attachments
+ )
+ context = {
+ 'course': course, 'message': message,
+ 'state': 'mail'
+ }
+ return my_render_to_response(
+ 'yaksh/course_detail.html', context, context_instance=ci
+ )
+
+
+@login_required
+@email_verified
def reject(request, course_id, user_id=None, was_enrolled=False):
user = request.user
ci = RequestContext(request)
@@ -769,8 +777,10 @@ def reject(request, course_id, user_id=None, was_enrolled=False):
else:
reject_ids = [user_id]
if not reject_ids:
- return my_render_to_response('yaksh/course_detail.html', {'course': course},
- context_instance=ci)
+ message = "Please select atleast one User"
+ return my_render_to_response('yaksh/course_detail.html',
+ {'course': course, "message": message},
+ context_instance=ci)
users = User.objects.filter(id__in=reject_ids)
course.reject(was_enrolled, *users)
return course_detail(request, course_id)
@@ -887,7 +897,6 @@ def ajax_questions_filter(request):
if language != "select":
filter_dict['language'] = str(language)
-
questions = list(Question.objects.filter(**filter_dict))
return my_render_to_response('yaksh/ajax_question_filter.html',
@@ -1047,8 +1056,8 @@ def show_all_questions(request):
question = Question()
zip_file = question.dump_questions(question_ids, user)
response = HttpResponse(content_type='application/zip')
- response['Content-Disposition'] = '''attachment;\
- filename={0}_questions.zip'''.format(user)
+ response['Content-Disposition'] = dedent(\
+ '''attachment; filename={0}_questions.zip'''.format(user))
zip_file.seek(0)
response.write(zip_file.read())
return response
@@ -1090,7 +1099,6 @@ def user_data(request, user_id, questionpaper_id=None):
return my_render_to_response('yaksh/user_data.html', context,
context_instance=RequestContext(request))
-
@login_required
@email_verified
def download_csv(request, questionpaper_id):
@@ -1570,6 +1578,8 @@ def update_email(request):
@email_verified
def download_assignment_file(request, quiz_id, question_id=None, user_id=None):
user = request.user
+ if not is_moderator(user):
+ raise Http404("You are not allowed to view this page")
qp = QuestionPaper.objects.get(quiz_id=quiz_id)
assignment_files, file_name = AssignmentUpload.objects.get_assignments(qp,
question_id,
@@ -1588,8 +1598,7 @@ def download_assignment_file(request, quiz_id, question_id=None, user_id=None):
zip_file.close()
zipfile_name.seek(0)
response = HttpResponse(content_type='application/zip')
- response['Content-Disposition'] = '''attachment;\
- filename={0}.zip'''.format(
+ response['Content-Disposition'] = 'attachment; filename={0}.zip'.format(
file_name.replace(" ", "_")
)
response.write(zipfile_name.read())