diff options
author | Prabhu Ramachandran | 2017-08-24 11:14:41 +0530 |
---|---|---|
committer | GitHub | 2017-08-24 11:14:41 +0530 |
commit | 13b087846f66f450ad1ed12fd534ae2b30cddbcf (patch) | |
tree | dda6c233926e2518d57c3a11d8b647409cb05618 /yaksh | |
parent | 5bf07392b04e1ad92b9b96dcaa932598d48be2b5 (diff) | |
parent | d6c4d44dca8520926fd781b87e35ecd7843e515c (diff) | |
download | online_test-13b087846f66f450ad1ed12fd534ae2b30cddbcf.tar.gz online_test-13b087846f66f450ad1ed12fd534ae2b30cddbcf.tar.bz2 online_test-13b087846f66f450ad1ed12fd534ae2b30cddbcf.zip |
Merge pull request #295 from adityacp/bulk_email
Send emails to students enrolled in a course
Diffstat (limited to 'yaksh')
-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/test_views.py | 65 | ||||
-rw-r--r-- | yaksh/urls.py | 1 | ||||
-rw-r--r-- | yaksh/views.py | 41 |
6 files changed, 254 insertions, 19 deletions
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/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 c10ba6a..db45a89 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 @@ -735,6 +735,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) @@ -750,8 +783,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) |