From 306ac482102970a3946e1e3736a6f5f01bb4143f Mon Sep 17 00:00:00 2001 From: dcadams Date: Wed, 26 Jun 2013 14:01:07 -0700 Subject: [PATCH] Email on enroll/un-enroll actions Optionally email students on enroll/un-enroll actions by instructor from enrollment tab in LMS. --- CHANGELOG.rst | 2 + common/djangoapps/terrain/factories.py | 2 +- .../instructor/tests/test_enrollment.py | 249 ++++++++++++------ lms/djangoapps/instructor/views.py | 137 +++++++++- .../courseware/instructor_dashboard.html | 2 + .../emails/enroll_email_allowedmessage.txt | 13 + .../emails/enroll_email_allowedsubject.txt | 1 + .../emails/enroll_email_enrolledmessage.txt | 8 + .../emails/enroll_email_enrolledsubject.txt | 1 + .../emails/unenroll_email_allowedmessage.txt | 6 + .../emails/unenroll_email_enrolledmessage.txt | 8 + .../emails/unenroll_email_subject.txt | 1 + 12 files changed, 330 insertions(+), 100 deletions(-) create mode 100644 lms/templates/emails/enroll_email_allowedmessage.txt create mode 100644 lms/templates/emails/enroll_email_allowedsubject.txt create mode 100644 lms/templates/emails/enroll_email_enrolledmessage.txt create mode 100644 lms/templates/emails/enroll_email_enrolledsubject.txt create mode 100644 lms/templates/emails/unenroll_email_allowedmessage.txt create mode 100644 lms/templates/emails/unenroll_email_enrolledmessage.txt create mode 100644 lms/templates/emails/unenroll_email_subject.txt diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4fea30a5c5..8481f3a707 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -152,3 +152,5 @@ Common: Updated CodeJail. Common: Allow setting of authentication session cookie name. +LMS: Option to email students when enroll/un-enroll them. + diff --git a/common/djangoapps/terrain/factories.py b/common/djangoapps/terrain/factories.py index decce42368..2ed78aaa9f 100644 --- a/common/djangoapps/terrain/factories.py +++ b/common/djangoapps/terrain/factories.py @@ -44,7 +44,7 @@ class GroupFactory(sf.GroupFactory): @world.absorb -class CourseEnrollmentAllowedFactory(sf.CourseEnrollmentAllowed): +class CourseEnrollmentAllowedFactory(sf.CourseEnrollmentAllowedFactory): """ Users allowed to enroll in the course outside of the usual window """ diff --git a/lms/djangoapps/instructor/tests/test_enrollment.py b/lms/djangoapps/instructor/tests/test_enrollment.py index 3ce82b700b..3b5bdc2ce9 100644 --- a/lms/djangoapps/instructor/tests/test_enrollment.py +++ b/lms/djangoapps/instructor/tests/test_enrollment.py @@ -1,177 +1,256 @@ -''' +""" Unit tests for enrollment methods in views.py -''' +""" 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 courseware.access import _course_staff_group_name from courseware.tests.tests import LoginEnrollmentTestCase, TEST_DATA_XML_MODULESTORE, get_user 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 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): - ''' - Check Enrollment/Unenrollment with/without auto-enrollment on activation - ''' +@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) +class TestInstructorEnrollsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase): + """ + Check Enrollment/Unenrollment with/without auto-enrollment on activation and with/without email notification + """ def setUp(self): - self.full = modulestore().get_course("edX/full/6.002_Spring_2012") - self.toy = modulestore().get_course("edX/toy/2012_Fall") + instructor = AdminFactory.create() + 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) + self.course = CourseFactory.create() - def make_instructor(course): - group_name = _course_staff_group_name(course.location) - g = Group.objects.create(name=group_name) - g.user_set.add(get_user(self.instructor)) + self.users = [ + UserFactory.create(username="student%d" % i, email="student%d@test.com" % i) + for i in xrange(USER_COUNT) + ] - make_instructor(self.toy) + for user in self.users: + CourseEnrollmentFactory.create(user=user, course_id=self.course.id) - #Enroll Students - self.logout() - self.login(self.student1, self.password) - self.enroll(self.toy) + # Empty the test outbox + mail.outbox = [] - self.logout() - self.login(self.student2, self.password) - self.enroll(self.toy) + def test_unenrollment_email_off(self): + """ + Do un-enrollment email off test + """ - #Enroll Instructor - self.logout() - self.login(self.instructor, self.password) - self.enroll(self.toy) + course = self.course - def test_unenrollment(self): - ''' - Do un-enrollment test - ''' - - course = self.toy + #Run the Un-enroll students command 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, 'student0@test.com') self.assertContains(response, 'student1@test.com') - self.assertContains(response, 'student2@test.com') self.assertContains(response, 'un-enrolled') #Check the enrollment table + user = User.objects.get(email='student0@test.com') + ce = CourseEnrollment.objects.filter(course_id=course.id, user=user) + self.assertEqual(0, len(ce)) + user = User.objects.get(email='student1@test.com') ce = CourseEnrollment.objects.filter(course_id=course.id, user=user) self.assertEqual(0, len(ce)) - user = User.objects.get(email='student2@test.com') - ce = CourseEnrollment.objects.filter(course_id=course.id, user=user) - self.assertEqual(0, len(ce)) + #Check the outbox + self.assertEqual(len(mail.outbox), 0) - def test_enrollment_new_student_autoenroll_on(self): - ''' - 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 - course = self.toy 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 - self.assertContains(response, 'test1_1@student.com') - self.assertContains(response, 'test1_2@student.com') + self.assertContains(response, 'student1_1@test.com') + self.assertContains(response, 'student1_2@test.com') self.assertContains(response, 'user does not exist, enrollment allowed, pending with auto enrollment on') + #Check the outbox + self.assertEqual(len(mail.outbox), 0) + #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) - 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) - #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) - self.assertEqual(3, len(ce)) + self.assertEqual(4, len(ce)) #Create and activate student accounts with same email - self.student1 = 'test1_1@student.com' + self.student1 = 'student1_1@test.com' self.password = 'bar' self.create_account('s1_1', self.student1, self.password) 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.activate_user(self.student2) #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) 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) self.assertEqual(1, len(ce)) - def test_enrollmemt_new_student_autoenroll_off(self): - ''' - Do auto-enroll off test - ''' + def test_repeat_enroll(self): + """ + 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, 'student0@test.com') + self.assertContains(response, 'already enrolled') + + 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 - course = self.toy 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 - self.assertContains(response, 'test2_1@student.com') - self.assertContains(response, 'test2_2@student.com') + self.assertContains(response, 'student2_1@test.com') + self.assertContains(response, 'student2_2@test.com') self.assertContains(response, 'user does not exist, enrollment allowed, pending with auto enrollment off') + #Check the outbox + self.assertEqual(len(mail.outbox), 0) + #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) - 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) #Check there is no enrollment db entry other than for the setup instructor and students 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 - self.student = 'test2_1@student.com' + self.student = 'student2_1@test.com' self.password = 'bar' self.create_account('s2_1', self.student, self.password) 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.activate_user(self.student) #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) 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) self.assertEqual(0, len(ce)) def test_get_and_clean_student_list(self): - ''' + """ 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) - 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, 'student3_0@test.com') + self.assertContains(response, 'student3_1@test.com') + self.assertContains(response, 'student3_2@test.com') + self.assertContains(response, 'added, email sent') + self.assertContains(response, 'user does not exist, enrollment allowed, pending with auto enrollment on, email sent') + + #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, 'student2@test.com') + self.assertContains(response, 'student3@test.com') + self.assertContains(response, 'un-enrolled, email sent') + + #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) diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index e9fff63698..ea96901bca 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -20,6 +20,8 @@ from django.http import HttpResponse from django_future.csrf import ensure_csrf_cookie from django.views.decorators.cache import cache_control from django.core.urlresolvers import reverse +from django.core.mail import send_mail + import xmodule.graders as xmgraders from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError @@ -45,6 +47,7 @@ from mitxmako.shortcuts import render_to_response from psychometrics import psychoanalyze from student.models import CourseEnrollment, CourseEnrollmentAllowed import track.views +from mitxmako.shortcuts import render_to_string log = logging.getLogger(__name__) @@ -634,13 +637,15 @@ def instructor_dashboard(request, course_id): students = request.POST.get('multiple_students', '') 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'] elif action == 'Unenroll 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'] elif action == 'List sections available in remote gradebook': @@ -1068,9 +1073,17 @@ def grade_summary(request, course_id): #----------------------------------------------------------------------------- # enrollment -def _do_enroll_students(course, course_id, students, overload=False, auto_enroll=False): - """Do the actual work of enrolling multiple students, presented as a string - of emails separated by commas or returns""" +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 + `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) 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 status[cea.email] = 'removed from pending enrollment list' 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: try: user = User.objects.get(email=student) 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) #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 status[student] = 'user does not exist, enrollment already allowed, pending with auto enrollment ' \ + ('on' if auto_enroll else 'off') continue + + #EnrollmentAllowed doesn't exist so create it cea = CourseEnrollmentAllowed(email=student, course_id=course_id, auto_enroll=auto_enroll) 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 + #Student has already registered if CourseEnrollment.objects.filter(user=user, course_id=course_id): status[student] = 'already enrolled' continue + try: + #Not enrolled yet ce = CourseEnrollment(user=user, course_id=course_id) ce.save() 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: status[student] = 'rejected' @@ -1133,13 +1180,23 @@ def _do_enroll_students(course, course_id, students, overload=False, auto_enroll #Unenrollment -def _do_unenroll_students(course_id, students): - """Do the actual work of un-enrolling multiple students, presented as a string - of emails separated by commas or returns""" +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 + `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) 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: isok = False @@ -1153,6 +1210,14 @@ def _do_unenroll_students(course_id, students): try: user = User.objects.get(email=student) 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 ce = CourseEnrollment.objects.filter(user=user, course_id=course_id) @@ -1161,6 +1226,15 @@ def _do_unenroll_students(course_id, students): try: ce[0].delete() 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: if not isok: status[student] = "Error! Failed to un-enroll" @@ -1173,13 +1247,48 @@ def _do_unenroll_students(course_id, students): 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): """ Separate out individual student email from the comma, or space separated string. - - In: - students: string coming from the input text area - Return: + `students` is string of student emails separated by commas or returns (a `str`) + Returns: students: list of cleaned student emails students_lc: list of lower case cleaned student emails """ diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html index d541962906..bc49cda427 100644 --- a/lms/templates/courseware/instructor_dashboard.html +++ b/lms/templates/courseware/instructor_dashboard.html @@ -382,6 +382,8 @@ function goto( mode)

Enroll or un-enroll one or many students: enter emails, separated by new lines or commas;

+ Notify students by email +

Auto-enroll students when they activate

diff --git a/lms/templates/emails/enroll_email_allowedmessage.txt b/lms/templates/emails/enroll_email_allowedmessage.txt new file mode 100644 index 0000000000..eab347166e --- /dev/null +++ b/lms/templates/emails/enroll_email_allowedmessage.txt @@ -0,0 +1,13 @@ +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 diff --git a/lms/templates/emails/enroll_email_allowedsubject.txt b/lms/templates/emails/enroll_email_allowedsubject.txt new file mode 100644 index 0000000000..41da60d1db --- /dev/null +++ b/lms/templates/emails/enroll_email_allowedsubject.txt @@ -0,0 +1 @@ +You have been invited to register for ${course_id} \ No newline at end of file diff --git a/lms/templates/emails/enroll_email_enrolledmessage.txt b/lms/templates/emails/enroll_email_enrolledmessage.txt new file mode 100644 index 0000000000..8e8f24efed --- /dev/null +++ b/lms/templates/emails/enroll_email_enrolledmessage.txt @@ -0,0 +1,8 @@ +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 diff --git a/lms/templates/emails/enroll_email_enrolledsubject.txt b/lms/templates/emails/enroll_email_enrolledsubject.txt new file mode 100644 index 0000000000..db897a3299 --- /dev/null +++ b/lms/templates/emails/enroll_email_enrolledsubject.txt @@ -0,0 +1 @@ +You have been enrolled in ${course_id} \ No newline at end of file diff --git a/lms/templates/emails/unenroll_email_allowedmessage.txt b/lms/templates/emails/unenroll_email_allowedmessage.txt new file mode 100644 index 0000000000..9bd0bd3cfd --- /dev/null +++ b/lms/templates/emails/unenroll_email_allowedmessage.txt @@ -0,0 +1,6 @@ +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 diff --git a/lms/templates/emails/unenroll_email_enrolledmessage.txt b/lms/templates/emails/unenroll_email_enrolledmessage.txt new file mode 100644 index 0000000000..8a7f9f996e --- /dev/null +++ b/lms/templates/emails/unenroll_email_enrolledmessage.txt @@ -0,0 +1,8 @@ +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 diff --git a/lms/templates/emails/unenroll_email_subject.txt b/lms/templates/emails/unenroll_email_subject.txt new file mode 100644 index 0000000000..f79218ff22 --- /dev/null +++ b/lms/templates/emails/unenroll_email_subject.txt @@ -0,0 +1 @@ +You have been un-enrolled from ${course_id} \ No newline at end of file