Commit 8f24a5fd by Sarina Canelake

Batch add/remove beta testers on beta dash

LMS-1287
parent d7c9491f
......@@ -140,6 +140,30 @@ def unenroll_email(course_id, student_email, email_students=False, email_params=
return previous_state, after_state
def send_beta_role_email(action, user, email_params):
"""
Send an email to a user added or removed as a beta tester.
`action` is one of 'add' or 'remove'
`user` is the User affected
`email_params` parameters used while parsing email templates (a `dict`).
"""
if action == 'add':
email_params['message'] = 'add_beta_tester'
email_params['email_address'] = user.email
email_params['full_name'] = user.profile.name
elif action == 'remove':
email_params['message'] = 'remove_beta_tester'
email_params['email_address'] = user.email
email_params['full_name'] = user.profile.name
else:
raise ValueError("Unexpected action received '{}' - expected 'add' or 'remove'".format(action))
send_mail_to_student(user.email, email_params)
def reset_student_attempts(course_id, student, module_state_key, delete_module=False):
"""
Reset student attempts for a problem. Optionally deletes all student state for the specified problem.
......@@ -257,7 +281,15 @@ def send_mail_to_student(student, param_dict):
'enrolled_unenroll': (
'emails/unenroll_email_subject.txt',
'emails/unenroll_email_enrolledmessage.txt'
)
),
'add_beta_tester': (
'emails/add_beta_tester_email_subject.txt',
'emails/add_beta_tester_email_message.txt'
),
'remove_beta_tester': (
'emails/remove_beta_tester_email_subject.txt',
'emails/remove_beta_tester_email_message.txt'
),
}
subject_template, message_template = email_template_dict.get(message_type, (None, None))
......
# -*- coding: utf-8 -*-
"""
Unit tests for instructor.api methods.
"""
......@@ -23,7 +24,8 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from courseware.tests.helpers import LoginEnrollmentTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from student.tests.factories import UserFactory
from courseware.tests.factories import StaffFactory, InstructorFactory
from courseware.tests.factories import StaffFactory, InstructorFactory, BetaTesterFactory
from student.roles import CourseBetaTesterRole
from student.models import CourseEnrollment, CourseEnrollmentAllowed
from courseware.models import StudentModule
......@@ -135,6 +137,7 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
]
# Endpoints that only Instructors can access
self.instructor_level_endpoints = [
('bulk_beta_modify_access', {'emails': 'foo@example.org', 'action': 'add'}),
('modify_access', {'unique_student_identifier': self.user.email, 'rolename': 'beta', 'action': 'allow'}),
('list_course_role_members', {'rolename': 'beta'}),
('rescore_problem', {'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.user.email}),
......@@ -607,6 +610,193 @@ class TestInstructorAPIEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class TestInstructorAPIBulkBetaEnrollment(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
Test bulk beta modify access endpoint.
"""
def setUp(self):
self.course = CourseFactory.create()
self.instructor = InstructorFactory(course=self.course.location)
self.client.login(username=self.instructor.username, password='test')
self.beta_tester = BetaTesterFactory(course=self.course.location)
CourseEnrollment.enroll(
self.beta_tester,
self.course.id
)
self.notenrolled_student = UserFactory(username='NotEnrolledStudent')
self.notregistered_email = 'robot-not-an-email-yet@robot.org'
self.assertEqual(User.objects.filter(email=self.notregistered_email).count(), 0)
# uncomment to enable enable printing of large diffs
# from failed assertions in the event of a test failure.
# (comment because pylint C0103)
# self.maxDiff = None
def test_missing_params(self):
""" Test missing all query parameters. """
url = reverse('bulk_beta_modify_access', kwargs={'course_id': self.course.id})
response = self.client.get(url)
self.assertEqual(response.status_code, 400)
def test_bad_action(self):
""" Test with an invalid action. """
action = 'robot-not-an-action'
url = reverse('bulk_beta_modify_access', kwargs={'course_id': self.course.id})
response = self.client.get(url, {'emails': self.beta_tester.email, 'action': action})
self.assertEqual(response.status_code, 400)
def test_add_notenrolled(self):
url = reverse('bulk_beta_modify_access', kwargs={'course_id': self.course.id})
response = self.client.get(url, {'emails': self.notenrolled_student.email, 'action': 'add', 'email_students': False})
self.assertEqual(response.status_code, 200)
self.assertTrue(CourseBetaTesterRole(self.course.location).has_user(self.notenrolled_student))
# test the response data
expected = {
"action": "add",
"results": [
{
"email": self.notenrolled_student.email,
"error": False,
"userDoesNotExist": False
}
]
}
res_json = json.loads(response.content)
self.assertEqual(res_json, expected)
# Check the outbox
self.assertEqual(len(mail.outbox), 0)
def test_add_notenrolled_with_email(self):
url = reverse('bulk_beta_modify_access', kwargs={'course_id': self.course.id})
response = self.client.get(url, {'emails': self.notenrolled_student.email, 'action': 'add', 'email_students': True})
self.assertEqual(response.status_code, 200)
self.assertTrue(CourseBetaTesterRole(self.course.location).has_user(self.notenrolled_student))
# test the response data
expected = {
"action": "add",
"results": [
{
"email": self.notenrolled_student.email,
"error": False,
"userDoesNotExist": False
}
]
}
res_json = json.loads(response.content)
self.assertEqual(res_json, expected)
# Check the outbox
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(
mail.outbox[0].subject,
'You have been invited to a beta test for Robot Super Course'
)
self.assertEqual(
mail.outbox[0].body,
u"Dear {0}\n\nYou have been invited to be a beta tester "
"for Robot Super Course at edx.org by a member of the course staff.\n\n"
"Visit https://edx.org/courses/MITx/999/Robot_Super_Course/about to join "
"the course and begin the beta test.\n\n----\n"
"This email was automatically sent from edx.org to {1}".format(
self.notenrolled_student.profile.name,
self.notenrolled_student.email
)
)
def test_enroll_with_email_not_registered(self):
# User doesn't exist
url = reverse('bulk_beta_modify_access', kwargs={'course_id': self.course.id})
response = self.client.get(url, {'emails': self.notregistered_email, 'action': 'add', 'email_students': True})
self.assertEqual(response.status_code, 200)
# test the response data
expected = {
"action": "add",
"results": [
{
"email": self.notregistered_email,
"error": True,
"userDoesNotExist": True
}
]
}
res_json = json.loads(response.content)
self.assertEqual(res_json, expected)
# Check the outbox
self.assertEqual(len(mail.outbox), 0)
def test_remove_without_email(self):
url = reverse('bulk_beta_modify_access', kwargs={'course_id': self.course.id})
response = self.client.get(url, {'emails': self.beta_tester.email, 'action': 'remove', 'email_students': False})
self.assertEqual(response.status_code, 200)
self.assertFalse(CourseBetaTesterRole(self.course.location).has_user(self.beta_tester))
# test the response data
expected = {
"action": "remove",
"results": [
{
"email": self.beta_tester.email,
"error": False,
"userDoesNotExist": False
}
]
}
res_json = json.loads(response.content)
self.assertEqual(res_json, expected)
# Check the outbox
self.assertEqual(len(mail.outbox), 0)
def test_remove_with_email(self):
url = reverse('bulk_beta_modify_access', kwargs={'course_id': self.course.id})
response = self.client.get(url, {'emails': self.beta_tester.email, 'action': 'remove', 'email_students': True})
self.assertEqual(response.status_code, 200)
self.assertFalse(CourseBetaTesterRole(self.course.location).has_user(self.beta_tester))
# test the response data
expected = {
"action": "remove",
"results": [
{
"email": self.beta_tester.email,
"error": False,
"userDoesNotExist": False
}
]
}
res_json = json.loads(response.content)
self.assertEqual(res_json, expected)
# Check the outbox
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(
mail.outbox[0].subject,
'You have been removed from a beta test for Robot Super Course'
)
self.assertEqual(
mail.outbox[0].body,
"Dear {full_name}\n\nYou have been removed as a beta tester for "
"Robot Super Course at edx.org by a member of the course staff. "
"The course will remain on your dashboard, but you will no longer "
"be part of the beta testing group.\n\n"
"Your other courses have not been affected.\n\n----\n"
"This email was automatically sent from edx.org to {email_address}".format(
full_name=self.beta_tester.profile.name,
email_address=self.beta_tester.email
)
)
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class TestInstructorAPILevelsAccess(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
Test endpoints whereby instructors can change permissions
......
......@@ -9,9 +9,13 @@ from django.test import TestCase
from student.tests.factories import UserFactory
from student.models import CourseEnrollment, CourseEnrollmentAllowed
from instructor.enrollment import (EmailEnrollmentState,
enroll_email, unenroll_email,
reset_student_attempts)
from instructor.enrollment import (
EmailEnrollmentState,
enroll_email,
reset_student_attempts,
send_beta_role_email,
unenroll_email
)
class TestSettableEnrollmentState(TestCase):
......@@ -365,3 +369,19 @@ class SettableEnrollmentState(EmailEnrollmentState):
return EnrollmentObjects(email, None, None, cea)
else:
return EnrollmentObjects(email, None, None, None)
class TestSendBetaRoleEmail(TestCase):
"""
Test edge cases for `send_beta_role_email`
"""
def setUp(self):
self.user = UserFactory.create()
self.email_params = {'course': 'Robot Super Course'}
def test_bad_action(self):
bad_action = 'beta_tester'
error_msg = "Unexpected action received '{}' - expected 'add' or 'remove'".format(bad_action)
with self.assertRaisesRegexp(ValueError, error_msg):
send_beta_role_email(bad_action, self.user, self.email_params)
......@@ -37,7 +37,12 @@ from instructor_task.api_helper import AlreadyRunningError
from instructor_task.views import get_task_completion_info
from instructor_task.models import ReportStore
import instructor.enrollment as enrollment
from instructor.enrollment import enroll_email, unenroll_email, get_email_params
from instructor.enrollment import (
enroll_email,
get_email_params,
send_beta_role_email,
unenroll_email
)
from instructor.access import list_with_level, allow_access, revoke_access, update_forum_role
import analytics.basic
import analytics.distributions
......@@ -286,16 +291,32 @@ def students_update_enrollment(request, course_id):
emails="stringified list of emails",
action="add or remove",
)
def bulk_modify_access(request, course_id):
def bulk_beta_modify_access(request, course_id):
"""
Enroll or unenroll users in beta testing program.
Query parameters:
- emails is string containing a list of emails separated by anything split_input_list can handle.
- action is one of ['add', 'remove']
"""
action = request.GET.get('action')
emails_raw = request.GET.get('emails')
emails = _split_input_list(emails_raw)
email_students = request.GET.get('email_students') in ['true', 'True', True]
results = []
rolename = 'beta'
course = get_course_by_id(course_id)
email_params = {}
if email_students:
email_params = get_email_params(course, auto_enroll=False)
for email in emails:
try:
error = False
user_does_not_exist = False
user = User.objects.get(email=email)
if action == 'add':
allow_access(course, user, rolename)
elif action == 'remove':
......@@ -304,19 +325,25 @@ def bulk_modify_access(request, course_id):
return HttpResponseBadRequest(strip_tags(
"Unrecognized action '{}'".format(action)
))
results.append({
'email': email,
'error': False,
})
# catch and log any exceptions
except User.DoesNotExist:
error = True
user_does_not_exist = True
# catch and log any unexpected exceptions
# so that one error doesn't cause a 500.
except Exception as exc: # pylint: disable=W0703
except Exception as exc: # pylint: disable=broad-except
log.exception("Error while #{}ing student")
log.exception(exc)
error = True
else:
# If no exception thrown, see if we should send an email
if email_students:
send_beta_role_email(action, user, email_params)
finally:
# Tabulate the action result of this email address
results.append({
'email': email,
'error': True,
'error': error,
'userDoesNotExist': user_does_not_exist
})
response_payload = {
......
......@@ -11,8 +11,8 @@ urlpatterns = patterns('', # nopep8
'instructor.views.api.list_course_role_members', name="list_course_role_members"),
url(r'^modify_access$',
'instructor.views.api.modify_access', name="modify_access"),
url(r'^bulk_modify_access$',
'instructor.views.api.bulk_modify_access', name="bulk_modify_access"),
url(r'^bulk_beta_modify_access$',
'instructor.views.api.bulk_beta_modify_access', name="bulk_beta_modify_access"),
url(r'^get_grading_config$',
'instructor.views.api.get_grading_config', name="get_grading_config"),
url(r'^get_students_features(?P<csv>/csv)?$',
......
......@@ -146,7 +146,7 @@ def _section_membership(course_id, access):
'access': access,
'enroll_button_url': reverse('students_update_enrollment', kwargs={'course_id': course_id}),
'unenroll_button_url': reverse('students_update_enrollment', kwargs={'course_id': course_id}),
'modify_beta_testers_button_url': reverse('bulk_modify_access', kwargs={'course_id': course_id}),
'modify_beta_testers_button_url': reverse('bulk_beta_modify_access', kwargs={'course_id': course_id}),
'list_course_role_members_url': reverse('list_course_role_members', kwargs={'course_id': course_id}),
'modify_access_url': reverse('modify_access', kwargs={'course_id': course_id}),
'list_forum_members_url': reverse('list_forum_members', kwargs={'course_id': course_id}),
......
......@@ -165,15 +165,15 @@ class BetaTesterBulkAddition
# gather elements
@$emails_input = @$container.find("textarea[name='student-emails-for-beta']")
@$btn_beta_testers = @$container.find("input[name='beta-testers']")
# @$checkbox_emailstudents = @$container.find("input[name='email-students']")
@$checkbox_emailstudents = @$container.find("input[name='email-students']")
@$task_response = @$container.find(".request-response")
@$request_response_error = @$container.find(".request-response-error")
# click handlers
@$btn_beta_testers.click =>
# emailStudents = @$checkbox_emailstudents.is(':checked')
emailStudents = @$checkbox_emailstudents.is(':checked')
send_data =
action: $(event.target).data('action')
action: $(event.target).data('action') # 'add' or 'remove'
emails: @$emails_input.val()
email_students: emailStudents
......@@ -182,28 +182,29 @@ class BetaTesterBulkAddition
url: @$btn_beta_testers.data 'endpoint'
data: send_data
success: (data) => @display_response data
# error: std_ajax_err => @fail_with_error "Error enrolling/unenrolling students."
# success: (data) => @display_response data
error: std_ajax_err => @fail_with_error gettext "Error adding/removing user(s) as beta tester(s)."
# fail_with_error: (msg) ->
# console.warn msg
# @$task_response.empty()
# @$request_response_error.empty()
# @$request_response_error.text msg
fail_with_error: (msg) ->
console.warn msg
@$task_response.empty()
@$request_response_error.empty()
@$request_response_error.text msg
display_response: (data_from_server) ->
@$task_response.empty()
# @$request_response_error.empty()
@$request_response_error.empty()
errors = []
sucesses = []
successes = []
no_users = []
for student_results in data_from_server.results
if student_results.error
if student_results.userDoesNotExist
no_users.push student_results
else if student_results.error
errors.push student_results
else
sucesses.push student_results
successes.push student_results
console.log(sr.email for sr in sucesses)
console.log(sr.email for sr in successes)
render_list = (label, emails) =>
task_res_section = $ '<div/>', class: 'request-res-section'
......@@ -216,8 +217,26 @@ class BetaTesterBulkAddition
@$task_response.append task_res_section
render_list gettext("these students were added as beta testers"), (sr.email for sr in sucesses)
render_list gettext("these students were not added as beta testers"), (sr.email for sr in errors)
if successes.length and data_from_server.action is 'add'
`// Translators: A list of users appears after this sentence`
render_list gettext("These user(s) were successfully added as beta tester(s):"), (sr.email for sr in successes)
if successes.length and data_from_server.action is 'remove'
`// Translators: A list of users appears after this sentence`
render_list gettext("These user(s) were successfully removed as beta tester(s):"), (sr.email for sr in successes)
if errors.length and data_from_server.action is 'add'
`// Translators: A list of users appears after this sentence`
render_list gettext("These user(s) were not added as beta tester(s):"), (sr.email for sr in errors)
if errors.length and data_from_server.action is 'remove'
`// Translators: A list of users appears after this sentence`
render_list gettext("These user(s) were not removed as beta tester(s):"), (sr.email for sr in errors)
if no_users.length
no_users.push gettext("Users must create and activate their account before they can be promoted to beta tester.")
`// Translators: A list of email addresses appears after this sentence`
render_list gettext("Could not find users associated with the following email addresses:"), (sr.email for sr in no_users)
# Wrapper for the batch enrollment subsection.
# This object handles buttons, success and failure reporting,
......@@ -246,7 +265,7 @@ class BatchEnrollment
url: $(event.target).data 'endpoint'
data: send_data
success: (data) => @display_response data
error: std_ajax_err => @fail_with_error gettext "Error enrolling/unenrolling students."
error: std_ajax_err => @fail_with_error gettext "Error enrolling/unenrolling user(s)."
fail_with_error: (msg) ->
......@@ -349,49 +368,49 @@ class BatchEnrollment
render_list errors_label, (sr.email for sr in errors)
if enrolled.length and emailStudents
render_list gettext("Successfully enrolled and sent email to the following students:"), (sr.email for sr in enrolled)
render_list gettext("Successfully enrolled and sent email to the following user(s):"), (sr.email for sr in enrolled)
if enrolled.length and not emailStudents
`// Translators: A list of students appears after this sentence.`
render_list gettext("Successfully enrolled the following students:"), (sr.email for sr in enrolled)
`// Translators: A list of users appears after this sentence`
render_list gettext("Successfully enrolled the following user(s):"), (sr.email for sr in enrolled)
# Student hasn't registered so we allow them to enroll
if allowed.length and emailStudents
`// Translators: A list of students appears after this sentence.`
render_list gettext("Successfully sent enrollment emails to the following students. They will be allowed to enroll once they register:"),
`// Translators: A list of users appears after this sentence`
render_list gettext("Successfully sent enrollment emails to the following user(s). They will be allowed to enroll once they register:"),
(sr.email for sr in allowed)
# Student hasn't registered so we allow them to enroll
if allowed.length and not emailStudents
`// Translators: A list of students appears after this sentence.`
render_list gettext("These students will be allowed to enroll once they register:"),
`// Translators: A list of users appears after this sentence`
render_list gettext("These user(s) will be allowed to enroll once they register:"),
(sr.email for sr in allowed)
# Student hasn't registered so we allow them to enroll with autoenroll
if autoenrolled.length and emailStudents
`// Translators: A list of students appears after this sentence.`
render_list gettext("Successfully sent enrollment emails to the following students. They will be enrolled once they register:"),
`// Translators: A list of users appears after this sentence`
render_list gettext("Successfully sent enrollment emails to the following user(s). They will be enrolled once they register:"),
(sr.email for sr in autoenrolled)
# Student hasn't registered so we allow them to enroll with autoenroll
if autoenrolled.length and not emailStudents
`// Translators: A list of students appears after this sentence.`
render_list gettext("These students will be enrolled once they register:"),
`// Translators: A list of users appears after this sentence`
render_list gettext("These user(s) will be enrolled once they register:"),
(sr.email for sr in autoenrolled)
if notenrolled.length and emailStudents
`// Translators: A list of students appears after this sentence.`
render_list gettext("Emails successfully sent. The following students are no longer enrolled in the course:"),
`// Translators: A list of users appears after this sentence`
render_list gettext("Emails successfully sent. The following user(s) are no longer enrolled in the course:"),
(sr.email for sr in notenrolled)
if notenrolled.length and not emailStudents
`// Translators: A list of students appears after this sentence.`
render_list gettext("The following students are no longer enrolled in the course:"),
`// Translators: A list of users appears after this sentence`
render_list gettext("The following user(s) are no longer enrolled in the course:"),
(sr.email for sr in notenrolled)
if notunenrolled.length
`// Translators: A list of students appears after this sentence.`
render_list gettext("These students were not affliliated with the course so could not be unenrolled:"),
`// Translators: A list of users appears after this sentence`
render_list gettext("These user(s) were not affliliated with the course so could not be unenrolled:"),
(sr.email for sr in notunenrolled)
# Wrapper for auth list subsection.
......
......@@ -323,7 +323,7 @@ section.instructor-dashboard-content-2 {
}
.enroll-option {
margin-bottom: ($baseline/2);
margin: ($baseline/2) 0;
position: relative;
label {
......@@ -339,6 +339,7 @@ section.instructor-dashboard-content-2 {
padding: ($baseline/2);
width: 50%;
background-color: $light-gray;
box-shadow: 2px 2px 3px $shadow;
.hint-caret {
display: block;
......@@ -362,6 +363,11 @@ section.instructor-dashboard-content-2 {
display: block;
}
label[for="email-students-beta"]:hover + .email-students-beta-hint {
width: 30%;
display: block;
}
.enroll-actions {
margin-top: $baseline;
}
......
<%! from django.utils.translation import ugettext as _ %>
${_("Dear {full_name}").format(full_name=full_name)}
${_("You have been invited to be a beta tester for {course_name} at {site_name} by a "
"member of the course staff.").format(
course_name=course.display_name_with_default,
site_name=site_name
)}
${_("Visit {course_about_url} to join the course and begin the beta test.").format(course_about_url=course_about_url)}
----
${_("This email was automatically sent from {site_name} to "
"{email_address}").format(
site_name=site_name, email_address=email_address
)}
<%! from django.utils.translation import ugettext as _ %>
${_("You have been invited to a beta test for {course_name}").format(
course_name=course.display_name_with_default
)}
<%! from django.utils.translation import ugettext as _ %>
${_("Dear {full_name}").format(full_name=full_name)}
${_("You have been removed as a beta tester for {course_name} at {site_name} by a "
"member of the course staff. The course will remain on your dashboard, but "
"you will no longer be part of the beta testing group.").format(
course_name=course.display_name_with_default,
site_name=site_name
)}
${_("Your other courses have not been affected.")}
----
${_("This email was automatically sent from {site_name} to "
"{email_address}").format(
site_name=site_name, email_address=email_address
)}
<%! from django.utils.translation import ugettext as _ %>
${_("You have been removed from a beta test for {course_name}").format(
course_name=course.display_name_with_default
)}
......@@ -30,29 +30,29 @@
<div class="batch-enrollment">
<h2> ${_("Batch Enrollment")} </h2>
<p>
<label for="student-emails">${_("Enter student emails separated by new lines or commas.")} </label>
<textarea rows="6" name="student-emails" placeholder="${_("Student Emails")}" spellcheck="false"></textarea>
<label for="student-emails">${_("Enter email addresses separated by new lines or commas.")} </label>
<textarea rows="6" name="student-emails" placeholder="${_("Email Addresses")}" spellcheck="false"></textarea>
</p>
<div class="enroll-option">
<input type="checkbox" name="auto-enroll" value="Auto-Enroll" style="margin-top: 1em;">
<input type="checkbox" name="auto-enroll" value="Auto-Enroll">
<label for="auto-enroll">${_("Auto Enroll")}</label>
<div class="hint auto-enroll-hint">
<span class="hint-caret"></span>
<p> ${_("If auto enroll is <em>checked</em>, students who have not yet registered for edX will be automatically enrolled.")}
${_("If auto enroll is left <em>unchecked</em>, students who have not yet registered for edX will not be enrolled, but will be allowed to enroll.")}</p>
<p> ${_("If this option is <em>checked</em>, users who have not yet registered for {platform_name} will be automatically enrolled.").format(platform_name=settings.PLATFORM_NAME)}
${_("If this option is left <em>unchecked</em>, users who have not yet registered for {platform_name} will not be enrolled, but will be allowed to enroll once they make an account.").format(platform_name=settings.PLATFORM_NAME)}</p>
</div>
</div>
<div class="enroll-option">
<input type="checkbox" name="email-students" value="Notify-students-by-email">
<label for="email-students">${_("Notify students by email")}</label>
<label for="email-students">${_("Notify users by email")}</label>
<div class="hint email-students-hint">
<span class="hint-caret"></span>
<p> ${_("If email students is <em>checked</em> students will receive an email notification.")}</p>
<p> ${_("If this option is <em>checked</em>, users will receive an email notification.")}</p>
</div>
</div>
<div>
<input type="button" name="enrollment-button" class="enrollment-button" value="${_("Enroll")}" data-endpoint="${ section_data['enroll_button_url'] }" data-action="enroll" >
<input type="button" name="enrollment-button" class="enrollment-button" value="${_("Unenroll")}" data-endpoint="${ section_data['unenroll_button_url'] }" data-action="unenroll" >
......@@ -63,23 +63,28 @@
%if section_data['access']['instructor']:
<div class="batch-beta-testers">
<h2> ${_("Add Beta Testers")} </h2>
<p> ${_("Enter student emails separated by new lines or commas.")} </p>
<textarea rows="6" cols="50" name="student-emails-for-beta" placeholder="${_("Student Emails")}" spellcheck="false"></textarea>
<br>
<div>
<h2> ${_("Batch Beta Testers")} </h2>
<p>
<label for="student-emails-for-beta">
${_("Enter email addresses separated by new lines or commas.")}<br/>
${_("Note: Users must have an activated {platform_name} account before they can be enrolled as a beta tester.").format(platform_name=settings.PLATFORM_NAME)}
</label>
<textarea rows="6" cols="50" name="student-emails-for-beta" placeholder="${_("Email addresses")}" spellcheck="false"></textarea>
</p>
<div class="enroll-option">
<input type="checkbox" name="email-students" value="Notify-students-by-email">
<label for="email-students">${_("Notify students by email")}</label>
<div class="email-students-hint">
<p> ${_("If email students is <em>checked</em> students will receive an email notification.")}
</p>
<label for="email-students-beta">${_("Notify users by email")}</label>
<div class="hint email-students-beta-hint">
<span class="hint-caret"></span>
<p> ${_("If this option is <em>checked</em>, users will receive an email notification.")}</p>
</div>
</div>
<div>
<input type="button" name="beta-testers" class="" value="${_("Add students as beta testers")}" data-endpoint="${ section_data['modify_beta_testers_button_url'] }" data-action="add" >
<input type="button" name="beta-testers" class="" value="${_("Remove students as beta testers")}" data-endpoint="${ section_data['modify_beta_testers_button_url'] }" data-action="remove" >
<input type="button" name="beta-testers" class="enrollment-button" value="${_("Add beta tester(s)")}" data-endpoint="${ section_data['modify_beta_testers_button_url'] }" data-action="add" >
<input type="button" name="beta-testers" class="enrollment-button" value="${_("Remove beta tester(s)")}" data-endpoint="${ section_data['modify_beta_testers_button_url'] }" data-action="remove" >
</div>
<div class="request-response"></div>
......
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