Commit 306ac482 by dcadams

Email on enroll/un-enroll actions

Optionally email students on enroll/un-enroll actions
by instructor from enrollment tab in LMS.
parent 2cd18dfa
...@@ -152,3 +152,5 @@ Common: Updated CodeJail. ...@@ -152,3 +152,5 @@ Common: Updated CodeJail.
Common: Allow setting of authentication session cookie name. Common: Allow setting of authentication session cookie name.
LMS: Option to email students when enroll/un-enroll them.
...@@ -44,7 +44,7 @@ class GroupFactory(sf.GroupFactory): ...@@ -44,7 +44,7 @@ class GroupFactory(sf.GroupFactory):
@world.absorb @world.absorb
class CourseEnrollmentAllowedFactory(sf.CourseEnrollmentAllowed): class CourseEnrollmentAllowedFactory(sf.CourseEnrollmentAllowedFactory):
""" """
Users allowed to enroll in the course outside of the usual window Users allowed to enroll in the course outside of the usual window
""" """
......
''' """
Unit tests for enrollment methods in views.py Unit tests for enrollment methods in views.py
''' """
from django.test.utils import override_settings from django.test.utils import override_settings
from django.contrib.auth.models import Group, User from django.contrib.auth.models import User
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from courseware.access import _course_staff_group_name from courseware.access import _course_staff_group_name
from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory
from student.tests.factories import UserFactory, CourseEnrollmentFactory, AdminFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE, LoginEnrollmentTestCase
from student.models import CourseEnrollment, CourseEnrollmentAllowed from student.models import CourseEnrollment, CourseEnrollmentAllowed
from instructor.views import get_and_clean_student_list from instructor.views import get_and_clean_student_list, send_mail_to_student
from django.core import mail
USER_COUNT = 4
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestInstructorEnrollsStudent(LoginEnrollmentTestCase): @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
''' class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase):
Check Enrollment/Unenrollment with/without auto-enrollment on activation """
''' Check Enrollment/Unenrollment with/without auto-enrollment on activation and with/without email notification
"""
def setUp(self): def setUp(self):
self.full = modulestore().get_course("edX/full/6.002_Spring_2012") instructor = AdminFactory.create()
self.toy = modulestore().get_course("edX/toy/2012_Fall") self.client.login(username=instructor.username, password='test')
#Create instructor and student accounts
self.instructor = 'instructor1@test.com'
self.student1 = 'student1@test.com'
self.student2 = 'student2@test.com'
self.password = 'foo'
self.create_account('it1', self.instructor, self.password)
self.create_account('st1', self.student1, self.password)
self.create_account('st2', self.student2, self.password)
self.activate_user(self.instructor)
self.activate_user(self.student1)
self.activate_user(self.student2)
def make_instructor(course): self.course = CourseFactory.create()
group_name = _course_staff_group_name(course.location)
g = Group.objects.create(name=group_name)
g.user_set.add(get_user(self.instructor))
make_instructor(self.toy) self.users = [
UserFactory.create(username="student%d" % i, email="student%d@test.com" % i)
for i in xrange(USER_COUNT)
]
#Enroll Students for user in self.users:
self.logout() CourseEnrollmentFactory.create(user=user, course_id=self.course.id)
self.login(self.student1, self.password)
self.enroll(self.toy)
self.logout() # Empty the test outbox
self.login(self.student2, self.password) mail.outbox = []
self.enroll(self.toy)
#Enroll Instructor def test_unenrollment_email_off(self):
self.logout() """
self.login(self.instructor, self.password) Do un-enrollment email off test
self.enroll(self.toy) """
def test_unenrollment(self): course = self.course
'''
Do un-enrollment test
'''
course = self.toy #Run the Un-enroll students command
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Unenroll multiple students', 'multiple_students': 'student1@test.com, student2@test.com'}) response = self.client.post(url, {'action': 'Unenroll multiple students', 'multiple_students': 'student0@test.com student1@test.com'})
#Check the page output #Check the page output
self.assertContains(response, '<td>student0@test.com</td>')
self.assertContains(response, '<td>student1@test.com</td>') self.assertContains(response, '<td>student1@test.com</td>')
self.assertContains(response, '<td>student2@test.com</td>')
self.assertContains(response, '<td>un-enrolled</td>') self.assertContains(response, '<td>un-enrolled</td>')
#Check the enrollment table #Check the enrollment table
user = User.objects.get(email='student1@test.com') user = User.objects.get(email='student0@test.com')
ce = CourseEnrollment.objects.filter(course_id=course.id, user=user) ce = CourseEnrollment.objects.filter(course_id=course.id, user=user)
self.assertEqual(0, len(ce)) self.assertEqual(0, len(ce))
user = User.objects.get(email='student2@test.com') user = User.objects.get(email='student1@test.com')
ce = CourseEnrollment.objects.filter(course_id=course.id, user=user) ce = CourseEnrollment.objects.filter(course_id=course.id, user=user)
self.assertEqual(0, len(ce)) self.assertEqual(0, len(ce))
def test_enrollment_new_student_autoenroll_on(self): #Check the outbox
''' self.assertEqual(len(mail.outbox), 0)
Do auto-enroll on test
''' def test_enrollment_new_student_autoenroll_on_email_off(self):
"""
Do auto-enroll on, email off test
"""
course = self.course
#Run the Enroll students command #Run the Enroll students command
course = self.toy
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'test1_1@student.com, test1_2@student.com', 'auto_enroll': 'on'}) response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student1_1@test.com, student1_2@test.com', 'auto_enroll': 'on'})
#Check the page output #Check the page output
self.assertContains(response, '<td>test1_1@student.com</td>') self.assertContains(response, '<td>student1_1@test.com</td>')
self.assertContains(response, '<td>test1_2@student.com</td>') self.assertContains(response, '<td>student1_2@test.com</td>')
self.assertContains(response, '<td>user does not exist, enrollment allowed, pending with auto enrollment on</td>') self.assertContains(response, '<td>user does not exist, enrollment allowed, pending with auto enrollment on</td>')
#Check the outbox
self.assertEqual(len(mail.outbox), 0)
#Check the enrollmentallowed db entries #Check the enrollmentallowed db entries
cea = CourseEnrollmentAllowed.objects.filter(email='test1_1@student.com', course_id=course.id) cea = CourseEnrollmentAllowed.objects.filter(email='student1_1@test.com', course_id=course.id)
self.assertEqual(1, cea[0].auto_enroll) self.assertEqual(1, cea[0].auto_enroll)
cea = CourseEnrollmentAllowed.objects.filter(email='test1_2@student.com', course_id=course.id) cea = CourseEnrollmentAllowed.objects.filter(email='student1_2@test.com', course_id=course.id)
self.assertEqual(1, cea[0].auto_enroll) self.assertEqual(1, cea[0].auto_enroll)
#Check there is no enrollment db entry other than for the setup instructor and students #Check there is no enrollment db entry other than for the other students
ce = CourseEnrollment.objects.filter(course_id=course.id) ce = CourseEnrollment.objects.filter(course_id=course.id)
self.assertEqual(3, len(ce)) self.assertEqual(4, len(ce))
#Create and activate student accounts with same email #Create and activate student accounts with same email
self.student1 = 'test1_1@student.com' self.student1 = 'student1_1@test.com'
self.password = 'bar' self.password = 'bar'
self.create_account('s1_1', self.student1, self.password) self.create_account('s1_1', self.student1, self.password)
self.activate_user(self.student1) self.activate_user(self.student1)
self.student2 = 'test1_2@student.com' self.student2 = 'student1_2@test.com'
self.create_account('s1_2', self.student2, self.password) self.create_account('s1_2', self.student2, self.password)
self.activate_user(self.student2) self.activate_user(self.student2)
#Check students are enrolled #Check students are enrolled
user = User.objects.get(email='test1_1@student.com') user = User.objects.get(email='student1_1@test.com')
ce = CourseEnrollment.objects.filter(course_id=course.id, user=user) ce = CourseEnrollment.objects.filter(course_id=course.id, user=user)
self.assertEqual(1, len(ce)) self.assertEqual(1, len(ce))
user = User.objects.get(email='test1_2@student.com') user = User.objects.get(email='student1_2@test.com')
ce = CourseEnrollment.objects.filter(course_id=course.id, user=user) ce = CourseEnrollment.objects.filter(course_id=course.id, user=user)
self.assertEqual(1, len(ce)) self.assertEqual(1, len(ce))
def test_enrollmemt_new_student_autoenroll_off(self): def test_repeat_enroll(self):
''' """
Do auto-enroll off test Try to enroll an already enrolled student
''' """
course = self.course
url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student0@test.com', 'auto_enroll': 'on'})
self.assertContains(response, '<td>student0@test.com</td>')
self.assertContains(response, '<td>already enrolled</td>')
def test_enrollmemt_new_student_autoenroll_off_email_off(self):
"""
Do auto-enroll off, email off test
"""
course = self.course
#Run the Enroll students command #Run the Enroll students command
course = self.toy
url = reverse('instructor_dashboard', kwargs={'course_id': course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'test2_1@student.com, test2_2@student.com'}) response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student2_1@test.com, student2_2@test.com'})
#Check the page output #Check the page output
self.assertContains(response, '<td>test2_1@student.com</td>') self.assertContains(response, '<td>student2_1@test.com</td>')
self.assertContains(response, '<td>test2_2@student.com</td>') self.assertContains(response, '<td>student2_2@test.com</td>')
self.assertContains(response, '<td>user does not exist, enrollment allowed, pending with auto enrollment off</td>') self.assertContains(response, '<td>user does not exist, enrollment allowed, pending with auto enrollment off</td>')
#Check the outbox
self.assertEqual(len(mail.outbox), 0)
#Check the enrollmentallowed db entries #Check the enrollmentallowed db entries
cea = CourseEnrollmentAllowed.objects.filter(email='test2_1@student.com', course_id=course.id) cea = CourseEnrollmentAllowed.objects.filter(email='student2_1@test.com', course_id=course.id)
self.assertEqual(0, cea[0].auto_enroll) self.assertEqual(0, cea[0].auto_enroll)
cea = CourseEnrollmentAllowed.objects.filter(email='test2_2@student.com', course_id=course.id) cea = CourseEnrollmentAllowed.objects.filter(email='student2_2@test.com', course_id=course.id)
self.assertEqual(0, cea[0].auto_enroll) self.assertEqual(0, cea[0].auto_enroll)
#Check there is no enrollment db entry other than for the setup instructor and students #Check there is no enrollment db entry other than for the setup instructor and students
ce = CourseEnrollment.objects.filter(course_id=course.id) ce = CourseEnrollment.objects.filter(course_id=course.id)
self.assertEqual(3, len(ce)) self.assertEqual(4, len(ce))
#Create and activate student accounts with same email #Create and activate student accounts with same email
self.student = 'test2_1@student.com' self.student = 'student2_1@test.com'
self.password = 'bar' self.password = 'bar'
self.create_account('s2_1', self.student, self.password) self.create_account('s2_1', self.student, self.password)
self.activate_user(self.student) self.activate_user(self.student)
self.student = 'test2_2@student.com' self.student = 'student2_2@test.com'
self.create_account('s2_2', self.student, self.password) self.create_account('s2_2', self.student, self.password)
self.activate_user(self.student) self.activate_user(self.student)
#Check students are not enrolled #Check students are not enrolled
user = User.objects.get(email='test2_1@student.com') user = User.objects.get(email='student2_1@test.com')
ce = CourseEnrollment.objects.filter(course_id=course.id, user=user) ce = CourseEnrollment.objects.filter(course_id=course.id, user=user)
self.assertEqual(0, len(ce)) self.assertEqual(0, len(ce))
user = User.objects.get(email='test2_2@student.com') user = User.objects.get(email='student2_2@test.com')
ce = CourseEnrollment.objects.filter(course_id=course.id, user=user) ce = CourseEnrollment.objects.filter(course_id=course.id, user=user)
self.assertEqual(0, len(ce)) self.assertEqual(0, len(ce))
def test_get_and_clean_student_list(self): def test_get_and_clean_student_list(self):
''' """
Clean user input test Clean user input test
''' """
string = "abc@test.com, def@test.com ghi@test.com \n \n jkl@test.com " string = "abc@test.com, def@test.com ghi@test.com \n \n jkl@test.com \n mno@test.com "
cleaned_string, cleaned_string_lc = get_and_clean_student_list(string) cleaned_string, cleaned_string_lc = get_and_clean_student_list(string)
self.assertEqual(cleaned_string, ['abc@test.com', 'def@test.com', 'ghi@test.com', 'jkl@test.com']) self.assertEqual(cleaned_string, ['abc@test.com', 'def@test.com', 'ghi@test.com', 'jkl@test.com', 'mno@test.com'])
def test_enrollment_email_on(self):
"""
Do email on enroll test
"""
course = self.course
#Create activated, but not enrolled, user
UserFactory.create(username="student3_0", email="student3_0@test.com")
url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Enroll multiple students', 'multiple_students': 'student3_0@test.com, student3_1@test.com, student3_2@test.com', 'auto_enroll': 'on', 'email_students': 'on'})
#Check the page output
self.assertContains(response, '<td>student3_0@test.com</td>')
self.assertContains(response, '<td>student3_1@test.com</td>')
self.assertContains(response, '<td>student3_2@test.com</td>')
self.assertContains(response, '<td>added, email sent</td>')
self.assertContains(response, '<td>user does not exist, enrollment allowed, pending with auto enrollment on, email sent</td>')
#Check the outbox
self.assertEqual(len(mail.outbox), 3)
self.assertEqual(mail.outbox[0].subject, 'You have been enrolled in MITx/999/Robot_Super_Course')
self.assertEqual(mail.outbox[1].subject, 'You have been invited to register for MITx/999/Robot_Super_Course')
self.assertEqual(mail.outbox[1].body, "Dear student,\n\nYou have been invited to join MITx/999/Robot_Super_Course at edx.org by a member of the course staff.\n\n" +
"To finish your registration, please visit https://edx.org/register and fill out the registration form.\n" +
"Once you have registered and activated your account, you will see MITx/999/Robot_Super_Course listed on your dashboard.\n\n" +
"----\nThis email was automatically sent from edx.org to student3_1@test.com")
def test_unenrollment_email_on(self):
"""
Do email on unenroll test
"""
course = self.course
#Create invited, but not registered, user
cea = CourseEnrollmentAllowed(email='student4_0@test.com', course_id=course.id)
cea.save()
url = reverse('instructor_dashboard', kwargs={'course_id': course.id})
response = self.client.post(url, {'action': 'Unenroll multiple students', 'multiple_students': 'student4_0@test.com, student2@test.com, student3@test.com', 'email_students': 'on'})
#Check the page output
self.assertContains(response, '<td>student2@test.com</td>')
self.assertContains(response, '<td>student3@test.com</td>')
self.assertContains(response, '<td>un-enrolled, email sent</td>')
#Check the outbox
self.assertEqual(len(mail.outbox), 3)
self.assertEqual(mail.outbox[0].subject, 'You have been un-enrolled from MITx/999/Robot_Super_Course')
self.assertEqual(mail.outbox[0].body, "Dear Student,\n\nYou have been un-enrolled from course MITx/999/Robot_Super_Course by a member of the course staff. " +
"Please disregard the invitation previously sent.\n\n" +
"----\nThis email was automatically sent from edx.org to student4_0@test.com")
self.assertEqual(mail.outbox[1].subject, 'You have been un-enrolled from MITx/999/Robot_Super_Course')
def test_send_mail_to_student(self):
"""
Do invalid mail template test
"""
d = {'message': 'message_type_that_doesn\'t_exist'}
send_mail_ret = send_mail_to_student('student0@test.com', d)
self.assertFalse(send_mail_ret)
...@@ -20,6 +20,8 @@ from django.http import HttpResponse ...@@ -20,6 +20,8 @@ from django.http import HttpResponse
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.mail import send_mail
import xmodule.graders as xmgraders import xmodule.graders as xmgraders
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
...@@ -45,6 +47,7 @@ from mitxmako.shortcuts import render_to_response ...@@ -45,6 +47,7 @@ from mitxmako.shortcuts import render_to_response
from psychometrics import psychoanalyze from psychometrics import psychoanalyze
from student.models import CourseEnrollment, CourseEnrollmentAllowed from student.models import CourseEnrollment, CourseEnrollmentAllowed
import track.views import track.views
from mitxmako.shortcuts import render_to_string
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -634,13 +637,15 @@ def instructor_dashboard(request, course_id): ...@@ -634,13 +637,15 @@ def instructor_dashboard(request, course_id):
students = request.POST.get('multiple_students', '') students = request.POST.get('multiple_students', '')
auto_enroll = bool(request.POST.get('auto_enroll')) auto_enroll = bool(request.POST.get('auto_enroll'))
ret = _do_enroll_students(course, course_id, students, auto_enroll=auto_enroll) email_students = bool(request.POST.get('email_students'))
ret = _do_enroll_students(course, course_id, students, auto_enroll=auto_enroll, email_students=email_students)
datatable = ret['datatable'] datatable = ret['datatable']
elif action == 'Unenroll multiple students': elif action == 'Unenroll multiple students':
students = request.POST.get('multiple_students', '') students = request.POST.get('multiple_students', '')
ret = _do_unenroll_students(course_id, students) email_students = bool(request.POST.get('email_students'))
ret = _do_unenroll_students(course_id, students, email_students=email_students)
datatable = ret['datatable'] datatable = ret['datatable']
elif action == 'List sections available in remote gradebook': elif action == 'List sections available in remote gradebook':
...@@ -1068,9 +1073,17 @@ def grade_summary(request, course_id): ...@@ -1068,9 +1073,17 @@ def grade_summary(request, course_id):
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# enrollment # enrollment
def _do_enroll_students(course, course_id, students, overload=False, auto_enroll=False): def _do_enroll_students(course, course_id, students, overload=False, auto_enroll=False, email_students=False):
"""Do the actual work of enrolling multiple students, presented as a string """
of emails separated by commas or returns""" Do the actual work of enrolling multiple students, presented as a string
of emails separated by commas or returns
`course` is course object
`course_id` id of course (a `str`)
`students` string of student emails separated by commas or returns (a `str`)
`overload` un-enrolls all existing students (a `boolean`)
`auto_enroll` is user input preference (a `boolean`)
`email_students` is user input preference (a `boolean`)
"""
new_students, new_students_lc = get_and_clean_student_list(students) new_students, new_students_lc = get_and_clean_student_list(students)
status = dict([x, 'unprocessed'] for x in new_students) status = dict([x, 'unprocessed'] for x in new_students)
...@@ -1088,12 +1101,22 @@ def _do_enroll_students(course, course_id, students, overload=False, auto_enroll ...@@ -1088,12 +1101,22 @@ def _do_enroll_students(course, course_id, students, overload=False, auto_enroll
status[cea.email] = 'removed from pending enrollment list' status[cea.email] = 'removed from pending enrollment list'
ceaset.delete() ceaset.delete()
if email_students:
registration_url = 'https://' + settings.SITE_NAME + reverse('student.views.register_user')
#Composition of email
d = {'site_name': settings.SITE_NAME,
'registration_url': registration_url,
'course_id': course_id,
'auto_enroll': auto_enroll,
'course_url': registration_url + '/courses/' + course_id,
}
for student in new_students: for student in new_students:
try: try:
user = User.objects.get(email=student) user = User.objects.get(email=student)
except User.DoesNotExist: except User.DoesNotExist:
#User not signed up yet, put in pending enrollment allowed table #Student not signed up yet, put in pending enrollment allowed table
cea = CourseEnrollmentAllowed.objects.filter(email=student, course_id=course_id) cea = CourseEnrollmentAllowed.objects.filter(email=student, course_id=course_id)
#If enrollmentallowed already exists, update auto_enroll flag to however it was set in UI #If enrollmentallowed already exists, update auto_enroll flag to however it was set in UI
...@@ -1104,18 +1127,42 @@ def _do_enroll_students(course, course_id, students, overload=False, auto_enroll ...@@ -1104,18 +1127,42 @@ def _do_enroll_students(course, course_id, students, overload=False, auto_enroll
status[student] = 'user does not exist, enrollment already allowed, pending with auto enrollment ' \ status[student] = 'user does not exist, enrollment already allowed, pending with auto enrollment ' \
+ ('on' if auto_enroll else 'off') + ('on' if auto_enroll else 'off')
continue continue
#EnrollmentAllowed doesn't exist so create it
cea = CourseEnrollmentAllowed(email=student, course_id=course_id, auto_enroll=auto_enroll) cea = CourseEnrollmentAllowed(email=student, course_id=course_id, auto_enroll=auto_enroll)
cea.save() cea.save()
status[student] = 'user does not exist, enrollment allowed, pending with auto enrollment ' + ('on' if auto_enroll else 'off')
status[student] = 'user does not exist, enrollment allowed, pending with auto enrollment ' \
+ ('on' if auto_enroll else 'off')
if email_students:
#User is allowed to enroll but has not signed up yet
d['email_address'] = student
d['message'] = 'allowed_enroll'
send_mail_ret = send_mail_to_student(student, d)
status[student] += (', email sent' if send_mail_ret else '')
continue continue
#Student has already registered
if CourseEnrollment.objects.filter(user=user, course_id=course_id): if CourseEnrollment.objects.filter(user=user, course_id=course_id):
status[student] = 'already enrolled' status[student] = 'already enrolled'
continue continue
try: try:
#Not enrolled yet
ce = CourseEnrollment(user=user, course_id=course_id) ce = CourseEnrollment(user=user, course_id=course_id)
ce.save() ce.save()
status[student] = 'added' status[student] = 'added'
if email_students:
#User enrolled for first time, populate dict with user specific info
d['email_address'] = student
d['first_name'] = user.first_name
d['last_name'] = user.last_name
d['message'] = 'enrolled_enroll'
send_mail_ret = send_mail_to_student(student, d)
status[student] += (', email sent' if send_mail_ret else '')
except: except:
status[student] = 'rejected' status[student] = 'rejected'
...@@ -1133,13 +1180,23 @@ def _do_enroll_students(course, course_id, students, overload=False, auto_enroll ...@@ -1133,13 +1180,23 @@ def _do_enroll_students(course, course_id, students, overload=False, auto_enroll
#Unenrollment #Unenrollment
def _do_unenroll_students(course_id, students): def _do_unenroll_students(course_id, students, email_students=False):
"""Do the actual work of un-enrolling multiple students, presented as a string """
of emails separated by commas or returns""" Do the actual work of un-enrolling multiple students, presented as a string
of emails separated by commas or returns
`course_id` is id of course (a `str`)
`students` is string of student emails separated by commas or returns (a `str`)
`email_students` is user input preference (a `boolean`)
"""
old_students, _ = get_and_clean_student_list(students) old_students, _ = get_and_clean_student_list(students)
status = dict([x, 'unprocessed'] for x in old_students) status = dict([x, 'unprocessed'] for x in old_students)
if email_students:
#Composition of email
d = {'site_name': settings.SITE_NAME,
'course_id': course_id}
for student in old_students: for student in old_students:
isok = False isok = False
...@@ -1153,6 +1210,14 @@ def _do_unenroll_students(course_id, students): ...@@ -1153,6 +1210,14 @@ def _do_unenroll_students(course_id, students):
try: try:
user = User.objects.get(email=student) user = User.objects.get(email=student)
except User.DoesNotExist: except User.DoesNotExist:
if isok and email_students:
#User was allowed to join but had not signed up yet
d['email_address'] = student
d['message'] = 'allowed_unenroll'
send_mail_ret = send_mail_to_student(student, d)
status[student] += (', email sent' if send_mail_ret else '')
continue continue
ce = CourseEnrollment.objects.filter(user=user, course_id=course_id) ce = CourseEnrollment.objects.filter(user=user, course_id=course_id)
...@@ -1161,6 +1226,15 @@ def _do_unenroll_students(course_id, students): ...@@ -1161,6 +1226,15 @@ def _do_unenroll_students(course_id, students):
try: try:
ce[0].delete() ce[0].delete()
status[student] = "un-enrolled" status[student] = "un-enrolled"
if email_students:
#User was enrolled
d['email_address'] = student
d['first_name'] = user.first_name
d['last_name'] = user.last_name
d['message'] = 'enrolled_unenroll'
send_mail_ret = send_mail_to_student(student, d)
status[student] += (', email sent' if send_mail_ret else '')
except Exception: except Exception:
if not isok: if not isok:
status[student] = "Error! Failed to un-enroll" status[student] = "Error! Failed to un-enroll"
...@@ -1173,13 +1247,48 @@ def _do_unenroll_students(course_id, students): ...@@ -1173,13 +1247,48 @@ def _do_unenroll_students(course_id, students):
return data return data
def send_mail_to_student(student, param_dict):
"""
Construct the email using templates and then send it.
`student` is the student's email address (a `str`),
`param_dict` is a `dict` with keys [
`site_name`: name given to edX instance (a `str`)
`registration_url`: url for registration (a `str`)
`course_id`: id of course (a `str`)
`auto_enroll`: user input option (a `str`)
`course_url`: url of course (a `str`)
`email_address`: email of student (a `str`)
`first_name`: student first name (a `str`)
`last_name`: student last name (a `str`)
`message`: type of email to send and template to use (a `str`)
]
Returns a boolean indicating whether the email was sent successfully.
"""
EMAIL_TEMPLATE_DICT = {'allowed_enroll': ('emails/enroll_email_allowedsubject.txt', 'emails/enroll_email_allowedmessage.txt'),
'enrolled_enroll': ('emails/enroll_email_enrolledsubject.txt', 'emails/enroll_email_enrolledmessage.txt'),
'allowed_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_allowedmessage.txt'),
'enrolled_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_enrolledmessage.txt')}
subject_template, message_template = EMAIL_TEMPLATE_DICT.get(param_dict['message'], (None, None))
if subject_template is not None and message_template is not None:
subject = render_to_string(subject_template, param_dict)
message = render_to_string(message_template, param_dict)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [student], fail_silently=False)
return True
else:
return False
def get_and_clean_student_list(students): def get_and_clean_student_list(students):
""" """
Separate out individual student email from the comma, or space separated string. Separate out individual student email from the comma, or space separated string.
`students` is string of student emails separated by commas or returns (a `str`)
In: Returns:
students: string coming from the input text area
Return:
students: list of cleaned student emails students: list of cleaned student emails
students_lc: list of lower case cleaned student emails students_lc: list of lower case cleaned student emails
""" """
......
...@@ -382,6 +382,8 @@ function goto( mode) ...@@ -382,6 +382,8 @@ function goto( mode)
<p>Enroll or un-enroll one or many students: enter emails, separated by new lines or commas;</p> <p>Enroll or un-enroll one or many students: enter emails, separated by new lines or commas;</p>
<textarea rows="6" cols="70" name="multiple_students"></textarea> <textarea rows="6" cols="70" name="multiple_students"></textarea>
<p> <p>
<input type="checkbox" name="email_students"> Notify students by email
<p>
<input type="checkbox" name="auto_enroll"> Auto-enroll students when they activate <input type="checkbox" name="auto_enroll"> Auto-enroll students when they activate
<input type="submit" name="action" value="Enroll multiple students"> <input type="submit" name="action" value="Enroll multiple students">
<p> <p>
......
Dear student,
You have been invited to join ${course_id} at ${site_name} by a member of the course staff.
To finish your registration, please visit ${registration_url} and fill out the registration form.
% if auto_enroll:
Once you have registered and activated your account, you will see ${course_id} listed on your dashboard.
% else:
Once you have registered and activated your account, visit ${course_url} to join the course.
% endif
----
This email was automatically sent from ${site_name} to ${email_address}
\ No newline at end of file
You have been invited to register for ${course_id}
\ No newline at end of file
Dear ${first_name} ${last_name}
You have been enrolled in ${course_id} at ${site_name} by a member of the course staff. The course should now appear on your ${site_name} dashboard.
To start accessing course materials, please visit ${course_url}
----
This email was automatically sent from ${site_name} to ${first_name} ${last_name}
\ No newline at end of file
You have been enrolled in ${course_id}
\ No newline at end of file
Dear Student,
You have been un-enrolled from course ${course_id} by a member of the course staff. Please disregard the invitation previously sent.
----
This email was automatically sent from ${site_name} to ${email_address}
\ No newline at end of file
Dear ${first_name} ${last_name}
You have been un-enrolled in ${course_id} at ${site_name} by a member of the course staff. The course will no longer appear on your ${site_name} dashboard.
Your other courses have not been affected.
----
This email was automatically sent from ${site_name} to ${first_name} ${last_name}
\ No newline at end of file
You have been un-enrolled from ${course_id}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment