Commit aac28d08 by sjang92

Merge pull request #74 from Stanford-Online/sjang92/origin/enrollment-email

Sjang92/origin/enrollment email
parents 44129cc4 496eff88
...@@ -154,4 +154,6 @@ Abdallah Nassif <abdoosh00@gmail.com> ...@@ -154,4 +154,6 @@ Abdallah Nassif <abdoosh00@gmail.com>
Johnny Brown <johnnybrown7@gmail.com> Johnny Brown <johnnybrown7@gmail.com>
Ben McMorran <bmcmorran@edx.org> Ben McMorran <bmcmorran@edx.org>
Mat Peterson <mpeterson@edx.org> Mat Peterson <mpeterson@edx.org>
Tim Babych <tim.babych@gmail.com> Tim Babych <tim.babych@gmail.com>
\ No newline at end of file Se Won Jang <swjang@stanford.edu>
...@@ -42,6 +42,8 @@ class CourseDetailsTestCase(CourseTestCase): ...@@ -42,6 +42,8 @@ class CourseDetailsTestCase(CourseTestCase):
self.assertIsNone(details.syllabus, "syllabus somehow initialized" + str(details.syllabus)) self.assertIsNone(details.syllabus, "syllabus somehow initialized" + str(details.syllabus))
self.assertIsNone(details.intro_video, "intro_video somehow initialized" + str(details.intro_video)) self.assertIsNone(details.intro_video, "intro_video somehow initialized" + str(details.intro_video))
self.assertIsNone(details.effort, "effort somehow initialized" + str(details.effort)) self.assertIsNone(details.effort, "effort somehow initialized" + str(details.effort))
self.assertFalse(details.enable_enrollment_email, "Enrollment Email should be initialized as false")
self.assertTrue(details.enable_default_enrollment_email, "Default Template option for enrollment email should be initialized as true")
def test_encoder(self): def test_encoder(self):
details = CourseDetails.fetch(self.course.id) details = CourseDetails.fetch(self.course.id)
......
...@@ -24,10 +24,16 @@ class CourseDetails(object): ...@@ -24,10 +24,16 @@ class CourseDetails(object):
self.syllabus = None # a pdf file asset self.syllabus = None # a pdf file asset
self.short_description = "" self.short_description = ""
self.overview = "" # html to render as the overview self.overview = "" # html to render as the overview
self.pre_enrollment_email = "" # html to render as the pre-enrollment email
self.post_enrollment_email = "" # html to render as the post-enrollment email
self.pre_enrollment_email_subject = "" # header of the pre_enrollment_email
self.post_enrollment_email_subject = "" # header of the post_enrollment_email
self.intro_video = None # a video pointer self.intro_video = None # a video pointer
self.effort = None # int hours/week self.effort = None # int hours/week
self.course_image_name = "" self.course_image_name = ""
self.course_image_asset_path = "" # URL of the course image self.course_image_asset_path = "" # URL of the course image
self.enable_enrollment_email = False
self.enable_default_enrollment_email = True
@classmethod @classmethod
def fetch(cls, course_key): def fetch(cls, course_key):
...@@ -43,6 +49,8 @@ class CourseDetails(object): ...@@ -43,6 +49,8 @@ class CourseDetails(object):
course_details.enrollment_end = descriptor.enrollment_end course_details.enrollment_end = descriptor.enrollment_end
course_details.course_image_name = descriptor.course_image course_details.course_image_name = descriptor.course_image
course_details.course_image_asset_path = course_image_url(descriptor) course_details.course_image_asset_path = course_image_url(descriptor)
course_details.enable_enrollment_email = descriptor.enable_enrollment_email
course_details.enable_default_enrollment_email = descriptor.enable_default_enrollment_email
temploc = course_key.make_usage_key('about', 'syllabus') temploc = course_key.make_usage_key('about', 'syllabus')
try: try:
...@@ -61,6 +69,28 @@ class CourseDetails(object): ...@@ -61,6 +69,28 @@ class CourseDetails(object):
course_details.overview = modulestore().get_item(temploc).data course_details.overview = modulestore().get_item(temploc).data
except ItemNotFoundError: except ItemNotFoundError:
pass pass
temploc = course_key.make_usage_key('about', 'pre_enrollment_email_subject')
try:
course_details.pre_enrollment_email_subject = modulestore().get_item(temploc).data
except ItemNotFoundError:
pass
temploc = course_key.make_usage_key('about', 'post_enrollment_email_subject')
try:
course_details.post_enrollment_email_subject = modulestore().get_item(temploc).data
except ItemNotFoundError:
pass
temploc = course_key.make_usage_key('about', 'pre_enrollment_email')
try:
course_details.pre_enrollment_email = modulestore().get_item(temploc).data
except ItemNotFoundError:
pass
temploc = course_key.make_usage_key('about', 'post_enrollment_email')
try:
course_details.post_enrollment_email = modulestore().get_item(temploc).data
except ItemNotFoundError:
pass
temploc = course_key.make_usage_key('about', 'effort') temploc = course_key.make_usage_key('about', 'effort')
try: try:
...@@ -110,6 +140,16 @@ class CourseDetails(object): ...@@ -110,6 +140,16 @@ class CourseDetails(object):
# into the model is nasty, convert the JSON Date to a Python date, which is what the # into the model is nasty, convert the JSON Date to a Python date, which is what the
# setter expects as input. # setter expects as input.
date = Date() date = Date()
# Added to allow admins to enable/disable default enrollment emails
if 'enable_default_enrollment_email' in jsondict:
descriptor.enable_default_enrollment_email = jsondict['enable_default_enrollment_email']
dirty = True
# Added to allow admins to enable/disable enrollment emails
if 'enable_enrollment_email' in jsondict:
descriptor.enable_enrollment_email = jsondict['enable_enrollment_email']
dirty=True
if 'start_date' in jsondict: if 'start_date' in jsondict:
converted = date.from_json(jsondict['start_date']) converted = date.from_json(jsondict['start_date'])
...@@ -155,7 +195,9 @@ class CourseDetails(object): ...@@ -155,7 +195,9 @@ class CourseDetails(object):
# NOTE: below auto writes to the db w/o verifying that any of the fields actually changed # NOTE: below auto writes to the db w/o verifying that any of the fields actually changed
# to make faster, could compare against db or could have client send over a list of which fields changed. # to make faster, could compare against db or could have client send over a list of which fields changed.
for about_type in ['syllabus', 'overview', 'effort', 'short_description']: for about_type in ['syllabus', 'overview', 'effort', 'short_description',
'pre_enrollment_email', 'post_enrollment_email', 'pre_enrollment_email_subject',
'post_enrollment_email_subject']:
cls.update_about_item(course_key, about_type, jsondict[about_type], descriptor, user) cls.update_about_item(course_key, about_type, jsondict[about_type], descriptor, user)
recomposed_video_tag = CourseDetails.recompose_video_tag(jsondict['intro_video']) recomposed_video_tag = CourseDetails.recompose_video_tag(jsondict['intro_video'])
......
...@@ -5,17 +5,18 @@ var CourseDetails = Backbone.Model.extend({ ...@@ -5,17 +5,18 @@ var CourseDetails = Backbone.Model.extend({
org : '', org : '',
course_id: '', course_id: '',
run: '', run: '',
start_date: null, // maps to 'start' start_date: null, // maps to 'start'
end_date: null, // maps to 'end' end_date: null, // maps to 'end'
enrollment_start: null, enrollment_start: null,
enrollment_end: null, enrollment_end: null,
syllabus: null, syllabus: null,
short_description: "", short_description: "",
overview: "", overview: "",
intro_video: null, intro_video: null,
effort: null, // an int or null, effort: null, // an int or null,
course_image_name: '', // the filename course_image_name: '', // the filename
course_image_asset_path: '' // the full URL (/c4x/org/course/num/asset/filename) course_image_asset_path: '', // the full URL (/c4x/org/course/num/asset/filename)
enable_enrollment_email: false
}, },
// When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset) // When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset)
......
...@@ -12,6 +12,10 @@ var DetailsView = ValidatingView.extend({ ...@@ -12,6 +12,10 @@ var DetailsView = ValidatingView.extend({
"change textarea" : "updateModel", "change textarea" : "updateModel",
'click .remove-course-introduction-video' : "removeVideo", 'click .remove-course-introduction-video' : "removeVideo",
'focus #course-overview' : "codeMirrorize", 'focus #course-overview' : "codeMirrorize",
'click #enable-enrollment-email' : "toggleEnrollmentEmails",
'click #enable-default-enrollment-email' : "toggleDefaultEnrollmentEmails",
'focus #pre-enrollment-email' : "codeMirrorize",
'focus #post-enrollment-email' : "codeMirrorize",
'mouseover #timezone' : "updateTime", 'mouseover #timezone' : "updateTime",
// would love to move to a general superclass, but event hashes don't inherit in backbone :-( // would love to move to a general superclass, but event hashes don't inherit in backbone :-(
'focus :input' : "inputFocus", 'focus :input' : "inputFocus",
...@@ -40,6 +44,21 @@ var DetailsView = ValidatingView.extend({ ...@@ -40,6 +44,21 @@ var DetailsView = ValidatingView.extend({
this.listenTo(this.model, 'invalid', this.handleValidationError); this.listenTo(this.model, 'invalid', this.handleValidationError);
this.listenTo(this.model, 'change', this.showNotificationBar); this.listenTo(this.model, 'change', this.showNotificationBar);
this.selectorToField = _.invert(this.fieldToSelectorMap); this.selectorToField = _.invert(this.fieldToSelectorMap);
/* Memoize html elements for enrollment emails */
this.pre_enrollment_email_elem = this.$el.find('#' + this.fieldToSelectorMap['pre_enrollment_email']);
this.pre_enrollment_email_subject_elem = this.$el.find('#' + this.fieldToSelectorMap['pre_enrollment_email_subject']);
this.pre_enrollment_email_field = this.$el.find('#field-pre-enrollment-email');
this.pre_enrollment_email_subject_field = this.$el.find('#field-pre-enrollment-email-subject');
this.post_enrollment_email_elem = this.$el.find('#' + this.fieldToSelectorMap['post_enrollment_email']);
this.post_enrollment_email_subject_elem = this.$el.find('#' + this.fieldToSelectorMap['post_enrollment_email_subject']);
this.post_enrollment_email_field = this.$el.find('#field-post-enrollment-email');
this.post_enrollment_email_subject_field = this.$el.find('#field-post-enrollment-email-subject');
this.enable_enrollment_email_box = this.$el.find('#' + this.fieldToSelectorMap['enable_enrollment_email'])[0];
this.enable_default_enrollment_email_box = this.$el.find('#' + this.fieldToSelectorMap['enable_default_enrollment_email'])[0];
}, },
render: function() { render: function() {
...@@ -51,6 +70,31 @@ var DetailsView = ValidatingView.extend({ ...@@ -51,6 +70,31 @@ var DetailsView = ValidatingView.extend({
this.$el.find('#' + this.fieldToSelectorMap['overview']).val(this.model.get('overview')); this.$el.find('#' + this.fieldToSelectorMap['overview']).val(this.model.get('overview'));
this.codeMirrorize(null, $('#course-overview')[0]); this.codeMirrorize(null, $('#course-overview')[0]);
this.pre_enrollment_email_subject_elem.val(this.model.get('pre_enrollment_email_subject'));
this.post_enrollment_email_subject_elem.val(this.model.get('post_enrollment_email_subject'));
this.pre_enrollment_email_elem.val(this.model.get('pre_enrollment_email'));
this.codeMirrorize(null, $('#pre-enrollment-email')[0]);
this.post_enrollment_email_elem.val(this.model.get('post_enrollment_email'));
this.codeMirrorize(null, $('#post-enrollment-email')[0]);
this.enable_enrollment_email_box.checked = this.model.get('enable_enrollment_email');
this.enable_default_enrollment_email_box.checked = this.model.get('enable_default_enrollment_email');
if (this.model.get('enable_enrollment_email')) {
this.pre_enrollment_email_field.show();
this.post_enrollment_email_field.show();
this.pre_enrollment_email_subject_field.show();
this.post_enrollment_email_subject_field.show();
} else {
this.pre_enrollment_email_field.hide();
this.post_enrollment_email_field.hide();
this.pre_enrollment_email_subject_field.hide();
this.post_enrollment_email_subject_field.hide();
}
this.$el.find('#' + this.fieldToSelectorMap['short_description']).val(this.model.get('short_description')); this.$el.find('#' + this.fieldToSelectorMap['short_description']).val(this.model.get('short_description'));
this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample()); this.$el.find('.current-course-introduction-video iframe').attr('src', this.model.videosourceSample());
...@@ -74,10 +118,16 @@ var DetailsView = ValidatingView.extend({ ...@@ -74,10 +118,16 @@ var DetailsView = ValidatingView.extend({
'enrollment_start' : 'enrollment-start', 'enrollment_start' : 'enrollment-start',
'enrollment_end' : 'enrollment-end', 'enrollment_end' : 'enrollment-end',
'overview' : 'course-overview', 'overview' : 'course-overview',
'pre_enrollment_email' : 'pre-enrollment-email',
'post_enrollment_email' : 'post-enrollment-email',
'short_description' : 'course-short-description', 'short_description' : 'course-short-description',
'intro_video' : 'course-introduction-video', 'intro_video' : 'course-introduction-video',
'effort' : "course-effort", 'effort' : "course-effort",
'course_image_asset_path': 'course-image-url' 'course_image_asset_path': 'course-image-url',
'enable_enrollment_email': 'enable-enrollment-email',
'pre_enrollment_email_subject' :'pre-enrollment-email-subject',
'post_enrollment_email_subject':'post-enrollment-email-subject',
'enable_default_enrollment_email':'enable-default-enrollment-email'
}, },
updateTime : function(e) { updateTime : function(e) {
...@@ -152,6 +202,12 @@ var DetailsView = ValidatingView.extend({ ...@@ -152,6 +202,12 @@ var DetailsView = ValidatingView.extend({
case 'course-effort': case 'course-effort':
this.setField(event); this.setField(event);
break; break;
case 'pre-enrollment-email-subject':
this.setField(event);
break;
case 'post-enrollment-email-subject':
this.setField(event);
break;
case 'course-short-description': case 'course-short-description':
this.setField(event); this.setField(event);
break; break;
...@@ -185,6 +241,41 @@ var DetailsView = ValidatingView.extend({ ...@@ -185,6 +241,41 @@ var DetailsView = ValidatingView.extend({
this.$el.find('.remove-course-introduction-video').hide(); this.$el.find('.remove-course-introduction-video').hide();
} }
}, },
toggleEnrollmentEmails: function(event) {
var isChecked = this.enable_enrollment_email_box.checked;
if(isChecked) {
this.pre_enrollment_email_field.show();
this.post_enrollment_email_field.show();
this.pre_enrollment_email_subject_field.show();
this.post_enrollment_email_subject_field.show();
} else {
this.pre_enrollment_email_field.hide();
this.post_enrollment_email_field.hide();
this.pre_enrollment_email_subject_field.hide();
this.post_enrollment_email_subject_field.hide();
/* If enrollment email sending option is turned off, default email option should be turned off as well. */
this.enable_default_enrollment_email_box.checked = false;
this.setAndValidate(this.selectorToField['enable-default-enrollment-email'], false);
}
var field = this.selectorToField['enable-enrollment-email'];
if (this.model.get(field) != isChecked) {
this.setAndValidate(field, isChecked);
}
},
toggleDefaultEnrollmentEmails: function(event) {
var isChecked = this.enable_default_enrollment_email_box.checked;
var field = this.selectorToField['enable-default-enrollment-email'];
if (this.model.get(field) != isChecked) {
this.setAndValidate(field, isChecked);
}
},
codeMirrors : {}, codeMirrors : {},
codeMirrorize: function (e, forcedTarget) { codeMirrorize: function (e, forcedTarget) {
var thisTarget; var thisTarget;
......
...@@ -202,6 +202,23 @@ ...@@ -202,6 +202,23 @@
display: inline-block; display: inline-block;
} }
} }
.list-actions {
padding-top: ($baseline/2);
.action-primary {
@include blue-button();
@extend %t-action3;
font-weight: 600;
[class^="icon-"] {
@extend %t-icon5;
display: inline-block;
vertical-align: middle;
margin-top: -3px;
}
}
}
} }
.field-group { .field-group {
...@@ -404,6 +421,32 @@ ...@@ -404,6 +421,32 @@
} }
} }
// specific fields - pre-enrollment email
#field-pre-enrollment-email {
#pre-enrollment-email {
height: ($baseline*20);
}
//adds back in CodeMirror border removed due to Unit page styling of component editors
.CodeMirror {
border: 1px solid $gray-l2;
}
}
// specific fields - post-enrollment email
#field-post-enrollment-email {
#post-enrollment-email {
height: ($baseline*20);
}
//adds back in CodeMirror border removed due to Unit page styling of component editors
.CodeMirror {
border: 1px solid $gray-l2;
}
}
// specific fields - video // specific fields - video
#field-course-introduction-video { #field-course-introduction-video {
......
...@@ -205,7 +205,7 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s ...@@ -205,7 +205,7 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s
<ol class="list-input"> <ol class="list-input">
% if short_description_editable: % if short_description_editable:
<li class="field text" id="field-course-short-description"> <li class="field text" id="field-course-short-description">
<label for="course-overview">${_("Course Short Description")}</label> <label for="course-short-description">${_("Course Short Description")}</label>
<textarea class="text" id="course-short-description"></textarea> <textarea class="text" id="course-short-description"></textarea>
<span class="tip tip-stacked">${_("Appears on the course catalog page when students roll over the course name. Limit to ~150 characters")}</span> <span class="tip tip-stacked">${_("Appears on the course catalog page when students roll over the course name. Limit to ~150 characters")}</span>
</li> </li>
...@@ -225,6 +225,53 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s ...@@ -225,6 +225,53 @@ require(["domReady!", "jquery", "js/models/settings/course_details", "js/views/s
</li> </li>
% endif % endif
<li class="field text" id="field-enable-enrollment-email">
<label for="course-email">${_("Course Enrollment Email")}</label>
<input type="checkbox" id="enable-enrollment-email"/>
<label for="enable-enrollment-email">${_("Enable enrollment emails")}</label>
</li>
<li class="field text" id="field-enable-default-enrollment-email">
<input type="checkbox" id="enable-default-enrollment-email"/>
<label for="enable-enrollment-email">${_("Use default enrollment emails")}</label>
</li>
<li class="field text" id="field-pre-enrollment-email-subject">
<label for="pre-enrollment-email-subject">${_("Subject of the Email sent to students who enroll before the course starts")}</label>
<textarea class = 'text' id="pre-enrollment-email-subject"></textarea>
</li>
<li class="field text" id="field-pre-enrollment-email">
<label for="pre-enrollment-email">${_("Email sent to students who enroll before the course starts")}</label>
<textarea class="tinymce text-editor" id="pre-enrollment-email"></textarea>
<span class="tip tip-stacked">${_("This email will be sent to any student who enrolls in the course before its start date")}</span>
<ul class="list-actions">
<li class="action-item">
<a title="${_('Send me a copy of this via email')}"
href="" class="action action-primary">
${_("Send me a test email")}</a>
</li>
</ul>
</li>
<li class="field text" id="field-post-enrollment-email-subject">
<label for="post-enrollment-email-subject">${_("Subject of the Email sent to students who enroll after the course starts")}</label>
<textarea class = 'text' id="post-enrollment-email-subject"></textarea>
</li>
<li class="field text" id="field-post-enrollment-email">
<label for="post-enrollment-email">${_("Email sent to students who enroll after the course starts")}</label>
<textarea class="tinymce text-editor" id="post-enrollment-email"></textarea>
<span class="tip tip-stacked">${_("This email will be sent to any student who enrolls in the course after its start date")}</span>
<ul class="list-actions">
<li class="action-item">
<a title="${_('Send me a copy of this via email')}"
href="" class="action action-primary">
${_("Send me a test email")}</a>
</li>
</ul>
</li>
<li class="field image" id="field-course-image"> <li class="field image" id="field-course-image">
<label>${_("Course Image")}</label> <label>${_("Course Image")}</label>
<div class="current current-course-image"> <div class="current current-course-image">
......
...@@ -791,6 +791,9 @@ class CourseEnrollment(models.Model): ...@@ -791,6 +791,9 @@ class CourseEnrollment(models.Model):
may include "audit", "verified_id", etc. Please don't use it may include "audit", "verified_id", etc. Please don't use it
until we have these mapped out. until we have these mapped out.
'should_send_email' is a boolean that specifies if a course enrollment
email should be sent to the given user.
It is expected that this method is called from a method which has already It is expected that this method is called from a method which has already
verified the user authentication and access. verified the user authentication and access.
""" """
......
...@@ -3,7 +3,7 @@ import django.db ...@@ -3,7 +3,7 @@ import django.db
import unittest import unittest
from student.tests.factories import UserFactory, RegistrationFactory, PendingEmailChangeFactory from student.tests.factories import UserFactory, RegistrationFactory, PendingEmailChangeFactory
from student.views import reactivation_email_for_user, change_email_request, confirm_email_change from student.views import reactivation_email_for_user, change_email_request, confirm_email_change, notify_enrollment_by_email
from student.models import UserProfile, PendingEmailChange from student.models import UserProfile, PendingEmailChange
from django.contrib.auth.models import User, AnonymousUser from django.contrib.auth.models import User, AnonymousUser
from django.test import TestCase, TransactionTestCase from django.test import TestCase, TransactionTestCase
...@@ -14,7 +14,8 @@ from django.conf import settings ...@@ -14,7 +14,8 @@ from django.conf import settings
from edxmako.shortcuts import render_to_string from edxmako.shortcuts import render_to_string
from util.request import safe_get_host from util.request import safe_get_host
from textwrap import dedent from textwrap import dedent
from microsite_configuration import microsite
from xmodule.modulestore.tests.factories import CourseFactory
class TestException(Exception): class TestException(Exception):
"""Exception used for testing that nothing will catch explicitly""" """Exception used for testing that nothing will catch explicitly"""
...@@ -58,6 +59,48 @@ class EmailTestMixin(object): ...@@ -58,6 +59,48 @@ class EmailTestMixin(object):
settings.ALLOWED_HOSTS.append(hostname) settings.ALLOWED_HOSTS.append(hostname)
self.addCleanup(settings.ALLOWED_HOSTS.pop) self.addCleanup(settings.ALLOWED_HOSTS.pop)
class EnrollmentEmailTests(TestCase):
""" Test senging automated emails to users upon course enrollment. """
def setUp(self):
# Test Contstants
COURSE_SLUG = "100"
COURSE_NAME = "test_course"
COURSE_ORG = "EDX"
self.user = UserFactory.create(username="tester", email="tester@gmail.com", password="test")
self.course = CourseFactory.create(org=COURSE_ORG, display_name=COURSE_NAME, number=COURSE_SLUG)
self.assertIsNotNone(self.course)
self.request = RequestFactory().post('random_url')
self.request.user = self.user
def send_enrollment_email(self):
""" Send enrollment email to the user and return the Json response data. """
return json.loads(notify_enrollment_by_email(self.course, self.user, self.request).content)
def test_disabled_email_case(self):
""" Make sure emails don't fire when enable_enrollment_email setting is disabled. """
self.course.enable_enrollment_email = False
email_result = self.send_enrollment_email()
self.assertIn('email_did_fire', email_result)
self.assertFalse(email_result['email_did_fire'])
def test_custom_enrollment_email_sent(self):
""" Test sending of enrollment emails when enable_default_enrollment_email setting is disabled. """
self.course.enable_enrollment_email = True
self.course.enable_default_enrollment_email = False
email_result = self.send_enrollment_email()
self.assertNotIn('email_did_fire', email_result)
self.assertIn('is_success', email_result)
def test_default_enrollment_email_sent(self):
""" Test sending of enrollment emails when enable_default_enrollment_email setting is enabled. """
self.course.enable_enrollment_email = True
self.course.enable_default_enrollment_email = True
email_result = self.send_enrollment_email()
self.assertNotIn('email_did_fire', email_result)
self.assertIn('is_success', email_result)
@patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True)) @patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True))
@patch('django.contrib.auth.models.User.email_user') @patch('django.contrib.auth.models.User.email_user')
......
...@@ -8,6 +8,8 @@ import uuid ...@@ -8,6 +8,8 @@ import uuid
import time import time
from collections import defaultdict from collections import defaultdict
from pytz import UTC from pytz import UTC
from pytz import timezone
import json
from django.conf import settings from django.conf import settings
from django.contrib.auth import logout, authenticate, login from django.contrib.auth import logout, authenticate, login
...@@ -57,7 +59,7 @@ from xmodule.modulestore import ModuleStoreEnum ...@@ -57,7 +59,7 @@ from xmodule.modulestore import ModuleStoreEnum
from collections import namedtuple from collections import namedtuple
from courseware.courses import get_courses, sort_by_announcement from courseware.courses import get_courses, sort_by_announcement, get_course_about_section
from courseware.access import has_access from courseware.access import has_access
from courseware.models import CoursePreference from courseware.models import CoursePreference
...@@ -682,7 +684,11 @@ def change_enrollment(request): ...@@ -682,7 +684,11 @@ def change_enrollment(request):
current_mode = available_modes[0] current_mode = available_modes[0]
CourseEnrollment.enroll(user, course.id, mode=current_mode.slug) CourseEnrollment.enroll(user, course.id, mode=current_mode.slug)
# notify the user of the enrollment via email
enrollment_email_result = json.loads(notify_enrollment_by_email(course, user, request).content)
if ('is_success' in enrollment_email_result and not enrollment_email_result['is_success']):
return HttpResponseBadRequest(_(enrollment_email_result['error']))
return HttpResponse() return HttpResponse()
elif action == "add_to_cart": elif action == "add_to_cart":
...@@ -706,6 +712,52 @@ def change_enrollment(request): ...@@ -706,6 +712,52 @@ def change_enrollment(request):
else: else:
return HttpResponseBadRequest(_("Enrollment action is invalid")) return HttpResponseBadRequest(_("Enrollment action is invalid"))
def notify_enrollment_by_email(course, user, request):
"""
Updates the user about the course enrollment by email.
If the Course has already started, use post_enrollment_email
If the Course has not yet started, use pre_enrollment_email
"""
if (not (settings.FEATURES.get('AUTOMATIC_AUTH_FOR_TESTING')) and course.enable_enrollment_email):
from_address = microsite.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
try:
if (not course.enable_default_enrollment_email):
# Check if the course has already started and set subject & message accordingly
if (course.has_started):
subject = get_course_about_section(course, 'post_enrollment_email_subject')
message = get_course_about_section(course, 'post_enrollment_email')
else:
subject = get_course_about_section(course, 'pre_enrollment_email_subject')
message = get_course_about_section(course, 'pre_enrollment_email')
else:
# If not default, use the default emailing template
course_url = reverse('info', args=(course.id.to_deprecated_string(),))
context = {
'course':course,
'full_name':user.profile.name,
'site_name': microsite.get_value('SITE_NAME', settings.SITE_NAME),
'course_url':request.build_absolute_uri(course_url),
}
subject = render_to_string('emails/enroll_email_enrolledsubject.txt', context)
message = render_to_string('emails/enroll_email_enrolledmessage.txt', context)
subject = ''.join(subject.splitlines())
user.email_user(subject, message, from_address)
except Exception:
log.error('unable to send course enrollment verification email to user from "{from_address}"'.format(
from_address=from_address), exc_info = True)
return JsonResponse({"is_success": False, "error": _("Could not send enrollment email to the user"),})
return JsonResponse({"is_success": True, "subject": subject, "message": message})
else:
return JsonResponse({"email_did_fire": False})
def _check_can_enroll_in_course(user, course_key, access_type="enroll"): def _check_can_enroll_in_course(user, course_key, access_type="enroll"):
""" """
......
...@@ -169,6 +169,8 @@ class CourseFields(object): ...@@ -169,6 +169,8 @@ class CourseFields(object):
default=[], scope=Scope.content) default=[], scope=Scope.content)
wiki_slug = String(help="Slug that points to the wiki for this course", scope=Scope.content) wiki_slug = String(help="Slug that points to the wiki for this course", scope=Scope.content)
enable_enrollment_email = Boolean(help="Whether to send notification email upon enrollment or not", default=False, scope=Scope.settings)
enable_default_enrollment_email = Boolean(help="Whether to use default enrollment email for enrollment notification", default=True, scope=Scope.settings)
enrollment_start = Date(help="Date that enrollment for this class is opened", scope=Scope.settings) enrollment_start = Date(help="Date that enrollment for this class is opened", scope=Scope.settings)
enrollment_end = Date(help="Date that enrollment for this class is closed", scope=Scope.settings) enrollment_end = Date(help="Date that enrollment for this class is closed", scope=Scope.settings)
start = Date(help="Start time when this module is visible", start = Date(help="Start time when this module is visible",
......
...@@ -162,6 +162,10 @@ def get_course_about_section(course, section_key): ...@@ -162,6 +162,10 @@ def get_course_about_section(course, section_key):
- faq - faq
- more_info - more_info
- ocw_links - ocw_links
- pre_enrollment_email
- post_enrollment_email
- pre_enrollment_email_subject
- post_enrollment_email_subject
""" """
# Many of these are stored as html files instead of some semantic # Many of these are stored as html files instead of some semantic
...@@ -173,7 +177,9 @@ def get_course_about_section(course, section_key): ...@@ -173,7 +177,9 @@ def get_course_about_section(course, section_key):
'course_staff_short', 'course_staff_extended', 'course_staff_short', 'course_staff_extended',
'requirements', 'syllabus', 'textbook', 'faq', 'more_info', 'requirements', 'syllabus', 'textbook', 'faq', 'more_info',
'number', 'instructors', 'overview', 'number', 'instructors', 'overview',
'effort', 'end_date', 'prerequisites', 'ocw_links']: 'effort', 'end_date', 'prerequisites', 'ocw_links',
'pre_enrollment_email', 'post_enrollment_email',
'pre_enrollment_email_subject', 'post_enrollment_email_subject']:
try: try:
......
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