Commit 1622216b by Sarina Canelake

Merge pull request #3084 from edx/sarina/improve-bulk-beta-add

Improve the batch beta tester add feature
parents 7e3d661f bddae213
...@@ -33,7 +33,7 @@ from django_comment_common.models import ( ...@@ -33,7 +33,7 @@ from django_comment_common.models import (
) )
from courseware.models import StudentModule from courseware.models import StudentModule
from student.models import unique_id_for_user from student.models import unique_id_for_user, CourseEnrollment
import instructor_task.api import instructor_task.api
from instructor_task.api_helper import AlreadyRunningError from instructor_task.api_helper import AlreadyRunningError
from instructor_task.views import get_task_completion_info from instructor_task.views import get_task_completion_info
...@@ -204,7 +204,7 @@ def require_level(level): ...@@ -204,7 +204,7 @@ def require_level(level):
@ensure_csrf_cookie @ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff') @require_level('staff')
@require_query_params(action="enroll or unenroll", emails="stringified list of emails") @require_query_params(action="enroll or unenroll", identifiers="stringified list of emails and/or usernames")
def students_update_enrollment(request, course_id): def students_update_enrollment(request, course_id):
""" """
Enroll or unenroll students by email. Enroll or unenroll students by email.
...@@ -212,7 +212,7 @@ def students_update_enrollment(request, course_id): ...@@ -212,7 +212,7 @@ def students_update_enrollment(request, course_id):
Query Parameters: Query Parameters:
- action in ['enroll', 'unenroll'] - action in ['enroll', 'unenroll']
- emails is string containing a list of emails separated by anything split_input_list can handle. - identifiers is string containing a list of emails and/or usernames separated by anything split_input_list can handle.
- auto_enroll is a boolean (defaults to false) - auto_enroll is a boolean (defaults to false)
If auto_enroll is false, students will be allowed to enroll. If auto_enroll is false, students will be allowed to enroll.
If auto_enroll is true, students will be enrolled as soon as they register. If auto_enroll is true, students will be enrolled as soon as they register.
...@@ -244,8 +244,8 @@ def students_update_enrollment(request, course_id): ...@@ -244,8 +244,8 @@ def students_update_enrollment(request, course_id):
""" """
action = request.GET.get('action') action = request.GET.get('action')
emails_raw = request.GET.get('emails') identifiers_raw = request.GET.get('identifiers')
emails = _split_input_list(emails_raw) identifiers = _split_input_list(identifiers_raw)
auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True] auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True]
email_students = request.GET.get('email_students') in ['true', 'True', True] email_students = request.GET.get('email_students') in ['true', 'True', True]
...@@ -255,23 +255,23 @@ def students_update_enrollment(request, course_id): ...@@ -255,23 +255,23 @@ def students_update_enrollment(request, course_id):
email_params = get_email_params(course, auto_enroll) email_params = get_email_params(course, auto_enroll)
results = [] results = []
for email in emails: for identifier in identifiers:
# First try to get a user object from the identifer
user = None
email = None
try:
user = get_student_from_identifier(identifier)
except User.DoesNotExist:
email = identifier
else:
email = user.email
try: try:
# Use django.core.validators.validate_email to check email address # Use django.core.validators.validate_email to check email address
# validity (obviously, cannot check if email actually /exists/, # validity (obviously, cannot check if email actually /exists/,
# simply that it is plausibly valid) # simply that it is plausibly valid)
validate_email(email) validate_email(email) # Raises ValidationError if invalid
except ValidationError:
# Flag this email as an error if invalid, but continue checking
# the remaining in the list
results.append({
'email': email,
'error': True,
'invalidEmail': True,
})
continue
try:
if action == 'enroll': if action == 'enroll':
before, after = enroll_email(course_id, email, auto_enroll, email_students, email_params) before, after = enroll_email(course_id, email, auto_enroll, email_students, email_params)
elif action == 'unenroll': elif action == 'unenroll':
...@@ -281,20 +281,29 @@ def students_update_enrollment(request, course_id): ...@@ -281,20 +281,29 @@ def students_update_enrollment(request, course_id):
"Unrecognized action '{}'".format(action) "Unrecognized action '{}'".format(action)
)) ))
except ValidationError:
# Flag this email as an error if invalid, but continue checking
# the remaining in the list
results.append({ results.append({
'email': email, 'identifier': identifier,
'before': before.to_dict(), 'invalidIdentifier': True,
'after': after.to_dict(),
}) })
# catch and log any exceptions
# so that one error doesn't cause a 500.
except Exception as exc: # pylint: disable=W0703 except Exception as exc: # pylint: disable=W0703
# catch and log any exceptions
# so that one error doesn't cause a 500.
log.exception("Error while #{}ing student") log.exception("Error while #{}ing student")
log.exception(exc) log.exception(exc)
results.append({ results.append({
'email': email, 'identifier': identifier,
'error': True, 'error': True,
'invalidEmail': False, })
else:
results.append({
'identifier': identifier,
'before': before.to_dict(),
'after': after.to_dict(),
}) })
response_payload = { response_payload = {
...@@ -310,7 +319,7 @@ def students_update_enrollment(request, course_id): ...@@ -310,7 +319,7 @@ def students_update_enrollment(request, course_id):
@require_level('instructor') @require_level('instructor')
@common_exceptions_400 @common_exceptions_400
@require_query_params( @require_query_params(
emails="stringified list of emails", identifiers="stringified list of emails and/or usernames",
action="add or remove", action="add or remove",
) )
def bulk_beta_modify_access(request, course_id): def bulk_beta_modify_access(request, course_id):
...@@ -318,13 +327,15 @@ def bulk_beta_modify_access(request, course_id): ...@@ -318,13 +327,15 @@ def bulk_beta_modify_access(request, course_id):
Enroll or unenroll users in beta testing program. Enroll or unenroll users in beta testing program.
Query parameters: Query parameters:
- emails is string containing a list of emails separated by anything split_input_list can handle. - identifiers is string containing a list of emails and/or usernames separated by
anything split_input_list can handle.
- action is one of ['add', 'remove'] - action is one of ['add', 'remove']
""" """
action = request.GET.get('action') action = request.GET.get('action')
emails_raw = request.GET.get('emails') identifiers_raw = request.GET.get('identifiers')
emails = _split_input_list(emails_raw) identifiers = _split_input_list(identifiers_raw)
email_students = request.GET.get('email_students') in ['true', 'True', True] email_students = request.GET.get('email_students') in ['true', 'True', True]
auto_enroll = request.GET.get('auto_enroll') in ['true', 'True', True]
results = [] results = []
rolename = 'beta' rolename = 'beta'
course = get_course_by_id(course_id) course = get_course_by_id(course_id)
...@@ -333,11 +344,11 @@ def bulk_beta_modify_access(request, course_id): ...@@ -333,11 +344,11 @@ def bulk_beta_modify_access(request, course_id):
if email_students: if email_students:
email_params = get_email_params(course, auto_enroll=False) email_params = get_email_params(course, auto_enroll=False)
for email in emails: for identifier in identifiers:
try: try:
error = False error = False
user_does_not_exist = False user_does_not_exist = False
user = User.objects.get(email=email) user = get_student_from_identifier(identifier)
if action == 'add': if action == 'add':
allow_access(course, user, rolename) allow_access(course, user, rolename)
...@@ -360,10 +371,16 @@ def bulk_beta_modify_access(request, course_id): ...@@ -360,10 +371,16 @@ def bulk_beta_modify_access(request, course_id):
# If no exception thrown, see if we should send an email # If no exception thrown, see if we should send an email
if email_students: if email_students:
send_beta_role_email(action, user, email_params) send_beta_role_email(action, user, email_params)
# See if we should autoenroll the student
if auto_enroll:
# Check if student is already enrolled
if not CourseEnrollment.is_enrolled(user, course_id):
CourseEnrollment.enroll(user, course_id)
finally: finally:
# Tabulate the action result of this email address # Tabulate the action result of this email address
results.append({ results.append({
'email': email, 'identifier': identifier,
'error': error, 'error': error,
'userDoesNotExist': user_does_not_exist 'userDoesNotExist': user_does_not_exist
}) })
...@@ -543,8 +560,9 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=W06 ...@@ -543,8 +560,9 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=W06
student_data = analytics.basic.enrolled_students_features(course_id, query_features) student_data = analytics.basic.enrolled_students_features(course_id, query_features)
# Scrape the query features for i18n - can't translate here because it breaks further queries # Provide human-friendly and translatable names for these features. These names
# and how the coffeescript works. The actual translation will be done in data_download.coffee # will be displayed in the table generated in data_download.coffee. It is not (yet)
# used as the header row in the CSV, but could be in the future.
query_features_names = { query_features_names = {
'username': _('Username'), 'username': _('Username'),
'name': _('Name'), 'name': _('Name'),
...@@ -1086,7 +1104,7 @@ def update_forum_role_membership(request, course_id): ...@@ -1086,7 +1104,7 @@ def update_forum_role_membership(request, course_id):
target_is_instructor = has_access(user, course, 'instructor') target_is_instructor = has_access(user, course, 'instructor')
# cannot revoke instructor # cannot revoke instructor
if target_is_instructor and action == 'revoke' and rolename == FORUM_ROLE_ADMINISTRATOR: if target_is_instructor and action == 'revoke' and rolename == FORUM_ROLE_ADMINISTRATOR:
return HttpResponseBadRequest("Cannot revoke instructor forum admin privelages.") return HttpResponseBadRequest("Cannot revoke instructor forum admin privileges.")
try: try:
update_forum_role(course_id, user, rolename, action) update_forum_role(course_id, user, rolename, action)
......
...@@ -358,6 +358,11 @@ section.instructor-dashboard-content-2 { ...@@ -358,6 +358,11 @@ section.instructor-dashboard-content-2 {
display: block; display: block;
} }
label[for="auto-enroll-beta"]:hover + .auto-enroll-beta-hint {
width: 30%;
display: block;
}
label[for="email-students"]:hover + .email-students-hint { label[for="email-students"]:hover + .email-students-hint {
display: block; display: block;
......
...@@ -30,9 +30,10 @@ ...@@ -30,9 +30,10 @@
<div class="batch-enrollment"> <div class="batch-enrollment">
<h2> ${_("Batch Enrollment")} </h2> <h2> ${_("Batch Enrollment")} </h2>
<p> <p>
<label for="student-emails">${_("Enter email addresses separated by new lines or commas.")} <label for="student-ids">
${_("Enter email addresses and/or usernames separated by new lines or commas.")}
${_("You will not get notification for emails that bounce, so please double-check spelling.")} </label> ${_("You will not get notification for emails that bounce, so please double-check spelling.")} </label>
<textarea rows="6" name="student-emails" placeholder="${_("Email Addresses")}" spellcheck="false"></textarea> <textarea rows="6" name="student-ids" placeholder="${_("Email Addresses/Usernames")}" spellcheck="false"></textarea>
</p> </p>
<div class="enroll-option"> <div class="enroll-option">
...@@ -70,17 +71,30 @@ ...@@ -70,17 +71,30 @@
%if section_data['access']['instructor']: %if section_data['access']['instructor']:
<div class="batch-beta-testers"> <div class="batch-beta-testers">
<h2> ${_("Batch Beta Testers")} </h2> <h2> ${_("Batch Beta Tester Addition")} </h2>
<p> <p>
<label for="student-emails-for-beta"> <label for="student-ids-for-beta">
${_("Enter email addresses separated by new lines or commas.")}<br/> ${_("Enter email addresses and/or usernames 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)} ${_("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> </label>
<textarea rows="6" cols="50" name="student-emails-for-beta" placeholder="${_("Email Addresses")}" spellcheck="false"></textarea> <textarea rows="6" cols="50" name="student-ids-for-beta" placeholder="${_("Email Addresses/Usernames")}" spellcheck="false"></textarea>
</p> </p>
<div class="enroll-option"> <div class="enroll-option">
<input type="checkbox" name="auto-enroll" value="Auto-Enroll" checked="yes">
<label for="auto-enroll-beta">${_("Auto Enroll")}</label>
<div class="hint auto-enroll-beta-hint">
<span class="hint-caret"></span>
<p>
${_("If this option is <em>checked</em>, users who have not enrolled in your course will be automatically enrolled.")}
<br /><br />
${_("Checking this box has no effect if 'Remove beta testers' is selected.")}
</p>
</div>
</div>
<div class="enroll-option">
<input type="checkbox" name="email-students" value="Notify-students-by-email" checked="yes"> <input type="checkbox" name="email-students" value="Notify-students-by-email" checked="yes">
<label for="email-students-beta">${_("Notify users by email")}</label> <label for="email-students-beta">${_("Notify users by email")}</label>
<div class="hint email-students-beta-hint"> <div class="hint email-students-beta-hint">
......
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