Commit 0b4e03e8 by dcadams

Merge pull request #66 from edx/feature-dcadams-usermanagement

Feature user-management autoenroll
parents 1c348a94 7d961bae
...@@ -662,6 +662,7 @@ class CourseEnrollmentAllowed(models.Model): ...@@ -662,6 +662,7 @@ class CourseEnrollmentAllowed(models.Model):
""" """
email = models.CharField(max_length=255, db_index=True) email = models.CharField(max_length=255, db_index=True)
course_id = models.CharField(max_length=255, db_index=True) course_id = models.CharField(max_length=255, db_index=True)
auto_enroll = models.BooleanField(default=0)
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True) created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
......
...@@ -32,7 +32,7 @@ from student.models import (Registration, UserProfile, TestCenterUser, TestCente ...@@ -32,7 +32,7 @@ from student.models import (Registration, UserProfile, TestCenterUser, TestCente
TestCenterRegistration, TestCenterRegistrationForm, TestCenterRegistration, TestCenterRegistrationForm,
PendingNameChange, PendingEmailChange, PendingNameChange, PendingEmailChange,
CourseEnrollment, unique_id_for_user, CourseEnrollment, unique_id_for_user,
get_testcenter_registration) get_testcenter_registration, CourseEnrollmentAllowed)
from certificates.models import CertificateStatuses, certificate_status_for_student from certificates.models import CertificateStatuses, certificate_status_for_student
...@@ -264,7 +264,6 @@ def dashboard(request): ...@@ -264,7 +264,6 @@ def dashboard(request):
if not user.is_active: if not user.is_active:
message = render_to_string('registration/activate_account_notice.html', {'email': user.email}) message = render_to_string('registration/activate_account_notice.html', {'email': user.email})
# Global staff can see what courses errored on their dashboard # Global staff can see what courses errored on their dashboard
staff_access = False staff_access = False
errored_courses = {} errored_courses = {}
...@@ -355,7 +354,7 @@ def change_enrollment(request): ...@@ -355,7 +354,7 @@ def change_enrollment(request):
course = course_from_id(course_id) course = course_from_id(course_id)
except ItemNotFoundError: except ItemNotFoundError:
log.warning("User {0} tried to enroll in non-existent course {1}" log.warning("User {0} tried to enroll in non-existent course {1}"
.format(user.username, course_id)) .format(user.username, course_id))
return HttpResponseBadRequest("Course id is invalid") return HttpResponseBadRequest("Course id is invalid")
if not has_access(user, course, 'enroll'): if not has_access(user, course, 'enroll'):
...@@ -363,9 +362,9 @@ def change_enrollment(request): ...@@ -363,9 +362,9 @@ def change_enrollment(request):
org, course_num, run = course_id.split("/") org, course_num, run = course_id.split("/")
statsd.increment("common.student.enrollment", statsd.increment("common.student.enrollment",
tags=["org:{0}".format(org), tags=["org:{0}".format(org),
"course:{0}".format(course_num), "course:{0}".format(course_num),
"run:{0}".format(run)]) "run:{0}".format(run)])
try: try:
enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id)
...@@ -382,9 +381,9 @@ def change_enrollment(request): ...@@ -382,9 +381,9 @@ def change_enrollment(request):
org, course_num, run = course_id.split("/") org, course_num, run = course_id.split("/")
statsd.increment("common.student.unenrollment", statsd.increment("common.student.unenrollment",
tags=["org:{0}".format(org), tags=["org:{0}".format(org),
"course:{0}".format(course_num), "course:{0}".format(course_num),
"run:{0}".format(run)]) "run:{0}".format(run)])
return HttpResponse() return HttpResponse()
except CourseEnrollment.DoesNotExist: except CourseEnrollment.DoesNotExist:
...@@ -454,7 +453,6 @@ def login_user(request, error=""): ...@@ -454,7 +453,6 @@ def login_user(request, error=""):
expires_time = time.time() + max_age expires_time = time.time() + max_age
expires = cookie_date(expires_time) expires = cookie_date(expires_time)
response.set_cookie(settings.EDXMKTG_COOKIE_NAME, response.set_cookie(settings.EDXMKTG_COOKIE_NAME,
'true', max_age=max_age, 'true', max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
...@@ -515,8 +513,8 @@ def _do_create_account(post_vars): ...@@ -515,8 +513,8 @@ def _do_create_account(post_vars):
Note: this function is also used for creating test users. Note: this function is also used for creating test users.
""" """
user = User(username=post_vars['username'], user = User(username=post_vars['username'],
email=post_vars['email'], email=post_vars['email'],
is_active=False) is_active=False)
user.set_password(post_vars['password']) user.set_password(post_vars['password'])
registration = Registration() registration = Registration()
# TODO: Rearrange so that if part of the process fails, the whole process fails. # TODO: Rearrange so that if part of the process fails, the whole process fails.
...@@ -698,7 +696,6 @@ def create_account(request, post_override=None): ...@@ -698,7 +696,6 @@ def create_account(request, post_override=None):
expires_time = time.time() + max_age expires_time = time.time() + max_age
expires = cookie_date(expires_time) expires = cookie_date(expires_time)
response.set_cookie(settings.EDXMKTG_COOKIE_NAME, response.set_cookie(settings.EDXMKTG_COOKIE_NAME,
'true', max_age=max_age, 'true', max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
...@@ -708,7 +705,6 @@ def create_account(request, post_override=None): ...@@ -708,7 +705,6 @@ def create_account(request, post_override=None):
return response return response
def exam_registration_info(user, course): def exam_registration_info(user, course):
""" Returns a Registration object if the user is currently registered for a current """ Returns a Registration object if the user is currently registered for a current
exam of the course. Returns None if the user is not registered, or if there is no exam of the course. Returns None if the user is not registered, or if there is no
...@@ -849,7 +845,6 @@ def create_exam_registration(request, post_override=None): ...@@ -849,7 +845,6 @@ def create_exam_registration(request, post_override=None):
response_data['non_field_errors'] = form.non_field_errors() response_data['non_field_errors'] = form.non_field_errors()
return HttpResponse(json.dumps(response_data), mimetype="application/json") return HttpResponse(json.dumps(response_data), mimetype="application/json")
# only do the following if there is accommodation text to send, # only do the following if there is accommodation text to send,
# and a destination to which to send it. # and a destination to which to send it.
# TODO: still need to create the accommodation email templates # TODO: still need to create the accommodation email templates
...@@ -872,7 +867,6 @@ def create_exam_registration(request, post_override=None): ...@@ -872,7 +867,6 @@ def create_exam_registration(request, post_override=None):
# response_data['non_field_errors'] = [ 'Could not send accommodation e-mail.', ] # response_data['non_field_errors'] = [ 'Could not send accommodation e-mail.', ]
# return HttpResponse(json.dumps(response_data), mimetype="application/json") # return HttpResponse(json.dumps(response_data), mimetype="application/json")
js = {'success': True} js = {'success': True}
return HttpResponse(json.dumps(js), mimetype="application/json") return HttpResponse(json.dumps(js), mimetype="application/json")
...@@ -916,6 +910,16 @@ def activate_account(request, key): ...@@ -916,6 +910,16 @@ def activate_account(request, key):
if not r[0].user.is_active: if not r[0].user.is_active:
r[0].activate() r[0].activate()
already_active = False already_active = False
#Enroll student in any pending courses he/she may have if auto_enroll flag is set
student = User.objects.filter(id=r[0].user_id)
if student:
ceas = CourseEnrollmentAllowed.objects.filter(email=student[0].email)
for cea in ceas:
if cea.auto_enroll:
course_id = cea.course_id
enrollment, created = CourseEnrollment.objects.get_or_create(user_id=student[0].id, course_id=course_id)
resp = render_to_response("registration/activation_complete.html", {'user_logged_in': user_logged_in, 'already_active': already_active}) resp = render_to_response("registration/activation_complete.html", {'user_logged_in': user_logged_in, 'already_active': already_active})
return resp return resp
if len(r) == 0: if len(r) == 0:
......
'''
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.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
import xmodule.modulestore.django
from student.models import CourseEnrollment, CourseEnrollmentAllowed
from instructor.views import get_and_clean_student_list
@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE)
class TestInstructorEnrollsStudent(LoginEnrollmentTestCase):
'''
Check Enrollment/Unenrollment with/without auto-enrollment on activation
'''
def setUp(self):
self.full = modulestore().get_course("edX/full/6.002_Spring_2012")
self.toy = modulestore().get_course("edX/toy/2012_Fall")
#Create instructor and student accounts
self.instructor = 'instructor1@test.com'
self.student1 = 'student1@test.com'
self.student2 = 'student2@test.com'
self.password = 'foo'
self.create_account('it1', self.instructor, self.password)
self.create_account('st1', self.student1, self.password)
self.create_account('st2', self.student2, self.password)
self.activate_user(self.instructor)
self.activate_user(self.student1)
self.activate_user(self.student2)
def make_instructor(course):
group_name = _course_staff_group_name(course.location)
g = Group.objects.create(name=group_name)
g.user_set.add(get_user(self.instructor))
make_instructor(self.toy)
#Enroll Students
self.logout()
self.login(self.student1, self.password)
self.enroll(self.toy)
self.logout()
self.login(self.student2, self.password)
self.enroll(self.toy)
#Enroll Instructor
self.logout()
self.login(self.instructor, self.password)
self.enroll(self.toy)
def test_unenrollment(self):
'''
Do un-enrollment test
'''
course = self.toy
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'})
#Check the page output
self.assertContains(response, '<td>student1@test.com</td>')
self.assertContains(response, '<td>student2@test.com</td>')
self.assertContains(response, '<td>un-enrolled</td>')
#Check the enrollment table
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))
def test_enrollment_new_student_autoenroll_on(self):
'''
Do auto-enroll on test
'''
#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'})
#Check the page output
self.assertContains(response, '<td>test1_1@student.com</td>')
self.assertContains(response, '<td>test1_2@student.com</td>')
self.assertContains(response, '<td>user does not exist, enrollment allowed, pending with auto enrollment on</td>')
#Check the enrollmentallowed db entries
cea = CourseEnrollmentAllowed.objects.filter(email='test1_1@student.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)
self.assertEqual(1, 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))
#Create and activate student accounts with same email
self.student1 = 'test1_1@student.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.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')
ce = CourseEnrollment.objects.filter(course_id=course.id, user=user)
self.assertEqual(1, len(ce))
user = User.objects.get(email='test1_2@student.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
'''
#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'})
#Check the page output
self.assertContains(response, '<td>test2_1@student.com</td>')
self.assertContains(response, '<td>test2_2@student.com</td>')
self.assertContains(response, '<td>user does not exist, enrollment allowed, pending with auto enrollment off</td>')
#Check the enrollmentallowed db entries
cea = CourseEnrollmentAllowed.objects.filter(email='test2_1@student.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)
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))
#Create and activate student accounts with same email
self.student = 'test2_1@student.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.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')
ce = CourseEnrollment.objects.filter(course_id=course.id, user=user)
self.assertEqual(0, len(ce))
user = User.objects.get(email='test2_2@student.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 "
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'])
...@@ -296,9 +296,6 @@ function goto( mode) ...@@ -296,9 +296,6 @@ function goto( mode)
<p> <p>
<input type="submit" name="action" value="List enrolled students"> <input type="submit" name="action" value="List enrolled students">
<input type="submit" name="action" value="List students who may enroll but may not have yet signed up"> <input type="submit" name="action" value="List students who may enroll but may not have yet signed up">
<p>
Student Email: <input type="text" name="enstudent"> <input type="submit" name="action" value="Un-enroll student">
<input type="submit" name="action" value="Enroll student">
<hr width="40%" style="align:left"> <hr width="40%" style="align:left">
%if settings.MITX_FEATURES.get('REMOTE_GRADEBOOK_URL','') and instructor_access: %if settings.MITX_FEATURES.get('REMOTE_GRADEBOOK_URL','') and instructor_access:
...@@ -320,9 +317,13 @@ function goto( mode) ...@@ -320,9 +317,13 @@ function goto( mode)
%endif %endif
<p>Add students: enter emails, separated by new lines or commas;</p> <p>Enroll or un-enroll one or many students: enter emails, separated by new lines or commas;</p>
<textarea rows="6" cols="70" name="enroll_multiple"></textarea> <textarea rows="6" cols="70" name="multiple_students"></textarea>
<p>
<input type="checkbox" name="auto_enroll"> Auto-enroll students when they activate
<input type="submit" name="action" value="Enroll multiple students"> <input type="submit" name="action" value="Enroll multiple students">
<p>
<input type="submit" name="action" value="Unenroll multiple students">
%endif %endif
......
...@@ -267,8 +267,6 @@ if settings.COURSEWARE_ENABLED: ...@@ -267,8 +267,6 @@ if settings.COURSEWARE_ENABLED:
'instructor.views.gradebook', name='gradebook'), 'instructor.views.gradebook', name='gradebook'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/grade_summary$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/grade_summary$',
'instructor.views.grade_summary', name='grade_summary'), 'instructor.views.grade_summary', name='grade_summary'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/enroll_students$',
'instructor.views.enroll_students', name='enroll_students'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading$',
'open_ended_grading.views.staff_grading', name='staff_grading'), 'open_ended_grading.views.staff_grading', name='staff_grading'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/get_next$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/get_next$',
......
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