diff options
-rw-r--r-- | .coveragerc | 3 | ||||
-rw-r--r-- | online_test/settings.py | 3 | ||||
-rw-r--r-- | yaksh/admin.py | 6 | ||||
-rw-r--r-- | yaksh/models.py | 8 | ||||
-rw-r--r-- | yaksh/send_emails.py | 32 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/course.js | 31 | ||||
-rw-r--r-- | yaksh/templates/yaksh/course_detail.html | 103 | ||||
-rw-r--r-- | yaksh/templates/yaksh/login.html | 8 | ||||
-rw-r--r-- | yaksh/templates/yaksh/moderator_dashboard.html | 2 | ||||
-rw-r--r-- | yaksh/test_views.py | 1621 | ||||
-rw-r--r-- | yaksh/urls.py | 67 | ||||
-rw-r--r-- | yaksh/views.py | 77 |
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"/> <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"/> <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"/> <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"/> <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()) |