summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--yaksh/send_emails.py32
-rw-r--r--yaksh/static/yaksh/js/course.js45
-rw-r--r--yaksh/templates/yaksh/course_detail.html94
-rw-r--r--yaksh/test_views.py66
-rw-r--r--yaksh/urls.py3
-rw-r--r--yaksh/views.py42
6 files changed, 265 insertions, 17 deletions
diff --git a/yaksh/send_emails.py b/yaksh/send_emails.py
index 24215dd..4465ce9 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 = "Yaksh"
+ 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..74ad37c 100644
--- a/yaksh/static/yaksh/js/course.js
+++ b/yaksh/static/yaksh/js/course.js
@@ -35,4 +35,49 @@ $(".reject").change( function(){
});
}
});
+
+$(function() {
+ $('textarea#email_body').froalaEditor({
+ heightMin: 100,
+ heightMax: 200
+ })
+ });
+
+var status;
+var btn_name;
+
+$("#send_mail").click(function(){
+ btn_name = "send_mail";
+ var subject = $("#subject").val();
+ var body = $('#email_body').val();
+ if (subject == '' || body == ''){
+ status = false;
+ $("#error_msg").html("Please enter email details");
+ $("#dialog").dialog();
+ }
+ else{
+ status = true;
+ }
+});
+
+$('#send_mail_form').submit(function(eventObj) {
+ if (btn_name == 'send_mail'){
+ if(status == false){
+ return false;
+ }
+ }
+ var selected = [];
+ $('#reject input:checked').each(function() {
+ selected.push($(this).attr('value'));
+ });
+ if(selected.length > 0){
+ return true;
+ }
+ else{
+ $("#error_msg").html("Please select atleast one user");
+ $( "#dialog" ).dialog();
+ return false;
+ }
+});
+
});
diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html
index cd4144f..676d7f3 100644
--- a/yaksh/templates/yaksh/course_detail.html
+++ b/yaksh/templates/yaksh/course_detail.html
@@ -6,6 +6,14 @@
{% 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="//code.jquery.com/ui/1.9.1/themes/base/jquery-ui.css">
+<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css" rel="stylesheet" type="text/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 +21,13 @@
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
+ {% if msg == '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 +36,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 msg == 'mail' %}
+ <div id="enrolled-students">
+ <center><b><u>Send Mails to Students</u></b></center><br>
+ {% if course.get_enrolled %}
+ <input type="checkbox" class="reject"/>&nbsp;<font size="2">Select all</font>
+ <div id="reject">
+ <form action="{{URL_ROOT}}/exam/manage/send_mail/{{ course.id }}/" method="post" id="send_mail_form">
+ {% csrf_token %}
+ <table class="table table-striped">
+ <th></th>
+ <th></th>
+ <th>Full Name</th>
+ <th>Email</th>
+ <th>Roll Number</th>
+ <th>Institute</th>
+ <th>Department</th>
+ {% for enrolled in course.get_enrolled %}
+ <tr>
+ <td><input type="checkbox" name="check" value="{{ enrolled.id }}"></td>
+ <td>{{ forloop.counter }}.</td>
+ <td> {{ enrolled.get_full_name|title }} </td>
+ <td> {{enrolled.email}}</td>
+ <td> {{enrolled.profile.roll_number}}</td>
+ <td> {{enrolled.profile.institute}}</td>
+ <td> {{enrolled.profile.department}}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ <br>
+ <input type="text" name="subject" id="subject" placeholder="Email Subject">
+ <br><br>
+ <textarea name="body" id="email_body"></textarea><br>
+ Attachments: <input type="file" name="email_attach" multiple="">
+ <br>
+ <button class="btn btn-success" type="submit" name='send_mail' value='send_mail' id="send_mail">
+ Send Mail to Selected Students</button>
+ </div>
+ {% endif %}
+ </form>
+ </div>
+ {% else %}
<div id="student-requests">
<center><b><u>Requests</u></b></center><br>
{% if course.get_requests %}
<input type="checkbox" class="checkall"/>&nbsp;<font size="2">Select all</font>
<div id="enroll-all">
+ <form action="{{URL_ROOT}}/exam/manage/enroll/{{ course.id }}/" method="post">
+ {% csrf_token %}
<table class="table table-striped">
<th></th>
<th></th>
@@ -43,9 +109,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 +140,8 @@
{% if course.get_enrolled %}
<input type="checkbox" class="reject"/>&nbsp;<font size="2">Select all</font>
<div id="reject">
+ <form action="{{URL_ROOT}}/exam/manage/enrolled/reject/{{ course.id }}/" method="post" id="reject-form">
+ {% csrf_token %}
<table class="table table-striped">
<th></th>
<th></th>
@@ -86,9 +152,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 +163,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 +179,8 @@
{% if course.get_rejected %}
<input type="checkbox" class="enroll"/>&nbsp;<font size="2">Select all</font>
<div id="enroll">
+ <form action="{{URL_ROOT}}/exam/manage/enroll/rejected/{{ course.id }}/" method="post">
+ {% csrf_token %}
<table class="table table-striped">
<th></th>
<th></th>
@@ -123,9 +190,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 +214,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 37e5ce4..caec9d4 100644
--- a/yaksh/test_views.py
+++ b/yaksh/test_views.py
@@ -8,6 +8,7 @@ from django.test import TestCase
from django.test import Client
from django.utils import timezone
from django.core import mail
+from django.core.files.uploadedfile import SimpleUploadedFile
from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\
QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\
@@ -1072,6 +1073,71 @@ class TestCourseDetail(TestCase):
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")
+ response = self.client.post(reverse('yaksh:send_mail',
+ kwargs={'course_id': self.user1_course.id}),
+ data={'send_mail': 'send_mail', 'email_attach': [attachment],
+ 'subject': 'test_bulk_mail', 'body': 'Test_Mail',
+ 'check': user_ids}
+ )
+ 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(response.status_code, 200)
+ self.assertEqual(response.context['course'], self.user1_course)
+
+ # Test if no users are selected
+ post_response = self.client.post(reverse('yaksh:send_mail',
+ kwargs={'course_id': self.user1_course.id}),
+ data={'check': []}
+ )
+ self.assertIn('select', post_response.context['message'])
+
+
class TestEnrollRequest(TestCase):
def setUp(self):
self.client = Client()
diff --git a/yaksh/urls.py b/yaksh/urls.py
index e4676d3..068df7a 100644
--- a/yaksh/urls.py
+++ b/yaksh/urls.py
@@ -69,6 +69,7 @@ urlpatterns = [
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/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),
@@ -79,7 +80,7 @@ urlpatterns = [
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'),
diff --git a/yaksh/views.py b/yaksh/views.py
index 7db0366..9785230 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
@@ -757,6 +757,40 @@ def enroll(request, course_id, user_id=None, was_rejected=False):
course.enroll(was_rejected, *users)
return course_detail(request, course_id)
+@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 not user_ids:
+ message = "Please select atleast one User"
+ return my_render_to_response('yaksh/course_detail.html',
+ {'course': course, "message": message},
+ context_instance=ci)
+
+ if request.POST.get('send_mail') == 'send_mail':
+ users = User.objects.filter(id__in=user_ids)
+ recipients = [user.email for user 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)
+
+ return my_render_to_response('yaksh/course_detail.html',
+ {'course': course, "message": message,
+ 'msg': 'mail'},
+ context_instance=ci)
@login_required
@email_verified
@@ -775,8 +809,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)