Commit d4a1e99a by cewing

MIT: CCX. Implement auto-enroll for CCX students

Based loosely on course enrollment model

Ensure that registered users are 'active' when they are enrolled in a POC

Respect the 'auto enroll' and 'email students' checkboxes in the UI

Add an auto_enroll flag to the PocFutureMembership model so we can automatically enroll non-users when they have registered and activated their account.

Build a future enrollment using the auto_enroll value from the request so we can ensure that non-existent users can be auto-enrolled

Ensure that any user added by way of the one-at-a-time UI is automatically auto-enrolled

Update tests with email sending to use the flag from the request

Provide api on the PocMembership object to auto-enroll a newly active member in this poc.

This method will delete the passed PocFutureMembership object and will automatically enroll the user in the POC named in that future membership as well as the MOOC from which it was created

Conditionally activate poc memberships that are pending when a new registree first activates their account.
parent 2b4da4d8
...@@ -1806,6 +1806,16 @@ def activate_account(request, key): ...@@ -1806,6 +1806,16 @@ def activate_account(request, key):
if cea.auto_enroll: if cea.auto_enroll:
CourseEnrollment.enroll(student[0], cea.course_id) CourseEnrollment.enroll(student[0], cea.course_id)
# enroll student in any pending POCs he/she may have if auto_enroll flag is set
if settings.FEATURES.get('PERSONAL_ONLINE_COURSES'):
from pocs.models import PocMembership, PocFutureMembership
pfms = PocFutureMembership.objects.filter(
email=student[0].email
)
for pfm in pfms:
if pfm.auto_enroll:
PocMembership.auto_enroll(student[0], pfm)
resp = render_to_response( resp = render_to_response(
"registration/activation_complete.html", "registration/activation_complete.html",
{ {
......
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'PocFutureMembership.auto_enroll'
db.add_column('pocs_pocfuturemembership', 'auto_enroll',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
def backwards(self, orm):
# Deleting field 'PocFutureMembership.auto_enroll'
db.delete_column('pocs_pocfuturemembership', 'auto_enroll')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'pocs.personalonlinecourse': {
'Meta': {'object_name': 'PersonalOnlineCourse'},
'coach': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
'display_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'pocs.pocfieldoverride': {
'Meta': {'unique_together': "(('poc', 'location', 'field'),)", 'object_name': 'PocFieldOverride'},
'field': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'location': ('xmodule_django.models.LocationKeyField', [], {'max_length': '255', 'db_index': 'True'}),
'poc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pocs.PersonalOnlineCourse']"}),
'value': ('django.db.models.fields.TextField', [], {'default': "'null'"})
},
'pocs.pocfuturemembership': {
'Meta': {'object_name': 'PocFutureMembership'},
'auto_enroll': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'poc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pocs.PersonalOnlineCourse']"})
},
'pocs.pocmembership': {
'Meta': {'object_name': 'PocMembership'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'poc': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['pocs.PersonalOnlineCourse']"}),
'student': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
}
}
complete_apps = ['pocs']
\ No newline at end of file
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from student.models import CourseEnrollment, AlreadyEnrolledError
from xmodule_django.models import CourseKeyField, LocationKeyField from xmodule_django.models import CourseKeyField, LocationKeyField
...@@ -21,6 +22,20 @@ class PocMembership(models.Model): ...@@ -21,6 +22,20 @@ class PocMembership(models.Model):
student = models.ForeignKey(User, db_index=True) student = models.ForeignKey(User, db_index=True)
active = models.BooleanField(default=False) active = models.BooleanField(default=False)
@classmethod
def auto_enroll(cls, student=None, future_membership=None):
assert student is not None and future_membership is not None
membership = cls(
poc=future_membership.poc, student=student, active=True
)
try:
CourseEnrollment.enroll(student, future_membership.poc.course_id)
except AlreadyEnrolledError:
pass
else:
membership.save()
future_membership.delete()
class PocFutureMembership(models.Model): class PocFutureMembership(models.Model):
""" """
...@@ -28,6 +43,7 @@ class PocFutureMembership(models.Model): ...@@ -28,6 +43,7 @@ class PocFutureMembership(models.Model):
""" """
poc = models.ForeignKey(PersonalOnlineCourse, db_index=True) poc = models.ForeignKey(PersonalOnlineCourse, db_index=True)
email = models.CharField(max_length=255) email = models.CharField(max_length=255)
auto_enroll = models.BooleanField(default=0)
class PocFieldOverride(models.Model): class PocFieldOverride(models.Model):
......
...@@ -231,6 +231,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -231,6 +231,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
data = { data = {
'enrollment-button': 'Enroll', 'enrollment-button': 'Enroll',
'student-ids': u','.join([student.email, ]), 'student-ids': u','.join([student.email, ]),
'email-students': 'Notify-students-by-email',
} }
response = self.client.post(url, data=data, follow=True) response = self.client.post(url, data=data, follow=True)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
...@@ -263,6 +264,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -263,6 +264,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
data = { data = {
'enrollment-button': 'Unenroll', 'enrollment-button': 'Unenroll',
'student-ids': u','.join([student.email, ]), 'student-ids': u','.join([student.email, ]),
'email-students': 'Notify-students-by-email',
} }
response = self.client.post(url, data=data, follow=True) response = self.client.post(url, data=data, follow=True)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
...@@ -292,6 +294,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -292,6 +294,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
data = { data = {
'enrollment-button': 'Enroll', 'enrollment-button': 'Enroll',
'student-ids': u','.join([test_email, ]), 'student-ids': u','.join([test_email, ]),
'email-students': 'Notify-students-by-email',
} }
response = self.client.post(url, data=data, follow=True) response = self.client.post(url, data=data, follow=True)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
...@@ -323,6 +326,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -323,6 +326,7 @@ class TestCoachDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
data = { data = {
'enrollment-button': 'Unenroll', 'enrollment-button': 'Unenroll',
'student-ids': u','.join([test_email, ]), 'student-ids': u','.join([test_email, ]),
'email-students': 'Notify-students-by-email',
} }
response = self.client.post(url, data=data, follow=True) response = self.client.post(url, data=data, follow=True)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
......
...@@ -65,7 +65,14 @@ def enroll_email(poc, student_email, auto_enroll=False, email_students=False, em ...@@ -65,7 +65,14 @@ def enroll_email(poc, student_email, auto_enroll=False, email_students=False, em
if previous_state.user: if previous_state.user:
if not previous_state.in_poc: if not previous_state.in_poc:
user = User.objects.get(email=student_email) user = User.objects.get(email=student_email)
membership = PocMembership(poc=poc, student=user) membership = PocMembership(
poc=poc, student=user, active=True
)
membership.save()
elif auto_enroll:
# activate existing memberships
membership = PocMembership.objects.get(student=user, poc=poc)
membership.active = True
membership.save() membership.save()
if email_students: if email_students:
email_params['message'] = 'enrolled_enroll' email_params['message'] = 'enrolled_enroll'
...@@ -73,7 +80,9 @@ def enroll_email(poc, student_email, auto_enroll=False, email_students=False, em ...@@ -73,7 +80,9 @@ def enroll_email(poc, student_email, auto_enroll=False, email_students=False, em
email_params['full_name'] = previous_state.full_name email_params['full_name'] = previous_state.full_name
send_mail_to_student(student_email, email_params) send_mail_to_student(student_email, email_params)
else: else:
membership = PocFutureMembership(poc=poc, email=student_email) membership = PocFutureMembership(
poc=poc, auto_enroll=auto_enroll, email=student_email
)
membership.save() membership.save()
if email_students: if email_students:
email_params['message'] = 'allowed_enroll' email_params['message'] = 'allowed_enroll'
......
...@@ -308,6 +308,8 @@ def poc_invite(request, course): ...@@ -308,6 +308,8 @@ def poc_invite(request, course):
action = request.POST.get('enrollment-button') action = request.POST.get('enrollment-button')
identifiers_raw = request.POST.get('student-ids') identifiers_raw = request.POST.get('student-ids')
identifiers = _split_input_list(identifiers_raw) identifiers = _split_input_list(identifiers_raw)
auto_enroll = True if 'auto-enroll' in request.POST else False
email_students = True if 'email-students' in request.POST else False
for identifier in identifiers: for identifier in identifiers:
user = None user = None
email = None email = None
...@@ -320,9 +322,14 @@ def poc_invite(request, course): ...@@ -320,9 +322,14 @@ def poc_invite(request, course):
try: try:
validate_email(email) validate_email(email)
if action == 'Enroll': if action == 'Enroll':
enroll_email(poc, email, email_students=True) enroll_email(
poc,
email,
auto_enroll=auto_enroll,
email_students=email_students
)
if action == "Unenroll": if action == "Unenroll":
unenroll_email(poc, email, email_students=True) unenroll_email(poc, email, email_students=email_students)
except ValidationError: except ValidationError:
pass # maybe log this? pass # maybe log this?
url = reverse('poc_coach_dashboard', kwargs={'course_id': course.id}) url = reverse('poc_coach_dashboard', kwargs={'course_id': course.id})
...@@ -350,7 +357,8 @@ def poc_student_management(request, course): ...@@ -350,7 +357,8 @@ def poc_student_management(request, course):
validate_email(email) validate_email(email)
if action == 'add': if action == 'add':
# by decree, no emails sent to students added this way # by decree, no emails sent to students added this way
enroll_email(poc, email, email_students=False) # by decree, any students added this way are auto_enrolled
enroll_email(poc, email, auto_enroll=True, email_students=False)
elif action == 'revoke': elif action == 'revoke':
unenroll_email(poc, email, email_students=False) unenroll_email(poc, email, email_students=False)
except ValidationError: except ValidationError:
......
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