diff options
-rw-r--r-- | online_test/settings.py | 3 | ||||
-rw-r--r-- | yaksh/forms.py | 17 | ||||
-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 | 65 | ||||
-rw-r--r-- | yaksh/urls.py | 1 | ||||
-rw-r--r-- | yaksh/views.py | 69 |
10 files changed, 290 insertions, 41 deletions
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/forms.py b/yaksh/forms.py index 3459be9..2740497 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -181,9 +181,13 @@ class QuizForm(forms.ModelForm): user = kwargs.pop('user') course_id = kwargs.pop('course') super(QuizForm, self).__init__(*args, **kwargs) - self.fields['prerequisite'] = forms.ModelChoiceField( - queryset=Quiz.objects.filter(course__id=course_id, - is_trial=False)) + + prerequisite_list = Quiz.objects.filter( + course__id=course_id, + is_trial=False + ).exclude(id=self.instance.id) + + self.fields['prerequisite'] = forms.ModelChoiceField(prerequisite_list) self.fields['prerequisite'].required = False self.fields['course'] = forms.ModelChoiceField( queryset=Course.objects.filter(id=course_id), empty_label=None) @@ -240,6 +244,13 @@ class QuizForm(forms.ModelForm): </p> """) + def clean_prerequisite(self): + prereq = self.cleaned_data['prerequisite'] + if prereq and prereq.prerequisite: + if prereq.prerequisite.id == self.instance.id: + raise forms.ValidationError("Please set another prerequisite quiz") + return prereq + class Meta: model = Quiz exclude = ["is_trial"] 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 ef222e2..f8a7c87 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -1195,6 +1195,7 @@ class TestAddTeacher(TestCase): username=self.user.username, password=self.user_plaintext_pass ) + teacher_id_list = [] for i in range(5): @@ -1833,6 +1834,70 @@ class TestCourseDetail(TestCase): 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): diff --git a/yaksh/urls.py b/yaksh/urls.py index 5270068..5058340 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -67,6 +67,7 @@ urlpatterns = [ name="enroll_user"), url(r'manage/enroll/rejected/(?P<course_id>\d+)/(?P<user_id>\d+)/$', views.enroll, {'was_rejected': True}), + 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+)/$', diff --git a/yaksh/views.py b/yaksh/views.py index f6adb2e..248a333 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 @@ -260,30 +260,24 @@ def add_quiz(request, course_id, quiz_id=None): if form.is_valid(): form.save() return my_redirect("/exam/manage/courses/") - else: - context["form"] = form - return my_render_to_response('yaksh/add_quiz.html', - context, - context_instance=ci) + else: quiz = Quiz.objects.get(id=quiz_id) form = QuizForm(request.POST, user=user, course=course_id, - instance=quiz) + instance=quiz + ) if form.is_valid(): form.save() - context["quiz_id"] = quiz_id return my_redirect("/exam/manage/courses/") + else: - if quiz_id is None: - form = QuizForm(course=course_id, user=user) - else: - quiz = Quiz.objects.get(id=quiz_id) - form = QuizForm(user=user,course=course_id, instance=quiz) - context["quiz_id"] = quiz_id - context["form"] = form - return my_render_to_response('yaksh/add_quiz.html', - context, - context_instance=ci) + quiz = Quiz.objects.get(id=quiz_id) if quiz_id else None + form = QuizForm(user=user,course=course_id, instance=quiz) + context["quiz_id"] = quiz_id + context["form"] = form + return my_render_to_response('yaksh/add_quiz.html', + context, + context_instance=ci) @login_required @email_verified @@ -739,6 +733,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) @@ -754,8 +781,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) |