Commit 200493a5 by ichuang

instructor dashboard upgrade - add enrollment management

parent e97469ae
# -*- 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 model 'CourseEnrollmentAllowed'
db.create_table('student_courseenrollmentallowed', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('email', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('course_id', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, null=True, db_index=True, blank=True)),
))
db.send_create_signal('student', ['CourseEnrollmentAllowed'])
# Adding unique constraint on 'CourseEnrollmentAllowed', fields ['email', 'course_id']
db.create_unique('student_courseenrollmentallowed', ['email', 'course_id'])
def backwards(self, orm):
# Removing unique constraint on 'CourseEnrollmentAllowed', fields ['email', 'course_id']
db.delete_unique('student_courseenrollmentallowed', ['email', 'course_id'])
# Deleting model 'CourseEnrollmentAllowed'
db.delete_table('student_courseenrollmentallowed')
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'})
},
'student.courseenrollment': {
'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'student.courseenrollmentallowed': {
'Meta': {'unique_together': "(('email', 'course_id'),)", 'object_name': 'CourseEnrollmentAllowed'},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'student.pendingemailchange': {
'Meta': {'object_name': 'PendingEmailChange'},
'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'student.pendingnamechange': {
'Meta': {'object_name': 'PendingNameChange'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'student.registration': {
'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"},
'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'student.testcenteruser': {
'Meta': {'object_name': 'TestCenterUser'},
'address_1': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'address_2': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
'address_3': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
'candidate_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_index': 'True'}),
'city': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
'client_candidate_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'company_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'country': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
'extension': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '8', 'blank': 'True'}),
'fax': ('django.db.models.fields.CharField', [], {'max_length': '35', 'blank': 'True'}),
'fax_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'middle_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'phone': ('django.db.models.fields.CharField', [], {'max_length': '35'}),
'phone_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}),
'postal_code': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '16', 'blank': 'True'}),
'salutation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'state': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}),
'suffix': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'unique': 'True'}),
'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'})
},
'student.userprofile': {
'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"},
'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}),
'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}),
'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}),
'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}),
'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
},
'student.usertestgroup': {
'Meta': {'object_name': 'UserTestGroup'},
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'})
}
}
complete_apps = ['student']
\ No newline at end of file
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
ASKBOT_AUTH_USER_COLUMNS = (
'website',
'about',
'gold',
'email_isvalid',
'real_name',
'location',
'reputation',
'gravatar',
'bronze',
'last_seen',
'silver',
'questions_per_page',
'new_response_count',
'seen_response_count',
)
class Migration(SchemaMigration):
def forwards(self, orm):
"Kill the askbot"
# For MySQL, we're batching the alters together for performance reasons
if db.backend_name == 'mysql':
drops = ["drop `{0}`".format(col) for col in ASKBOT_AUTH_USER_COLUMNS]
statement = "alter table `auth_user` {0};".format(", ".join(drops))
db.execute(statement)
else:
for column in ASKBOT_AUTH_USER_COLUMNS:
db.delete_column('auth_user', column)
def backwards(self, orm):
raise RuntimeError("Cannot reverse this migration: there's no going back to Askbot.")
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'})
},
'student.courseenrollment': {
'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'student.pendingemailchange': {
'Meta': {'object_name': 'PendingEmailChange'},
'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'student.pendingnamechange': {
'Meta': {'object_name': 'PendingNameChange'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'student.registration': {
'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"},
'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
},
'student.testcenteruser': {
'Meta': {'object_name': 'TestCenterUser'},
'address_1': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
'address_2': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
'address_3': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
'candidate_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_index': 'True'}),
'city': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
'client_candidate_id': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'company_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'country': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}),
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
'extension': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '8', 'blank': 'True'}),
'fax': ('django.db.models.fields.CharField', [], {'max_length': '35', 'blank': 'True'}),
'fax_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
'middle_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'phone': ('django.db.models.fields.CharField', [], {'max_length': '35'}),
'phone_country_code': ('django.db.models.fields.CharField', [], {'max_length': '3', 'db_index': 'True'}),
'postal_code': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '16', 'blank': 'True'}),
'salutation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
'state': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}),
'suffix': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'unique': 'True'}),
'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'})
},
'student.userprofile': {
'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"},
'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}),
'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}),
'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}),
'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}),
'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
},
'student.usertestgroup': {
'Meta': {'object_name': 'UserTestGroup'},
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'})
}
}
complete_apps = ['student']
...@@ -262,6 +262,24 @@ class CourseEnrollment(models.Model): ...@@ -262,6 +262,24 @@ class CourseEnrollment(models.Model):
return "[CourseEnrollment] %s: %s (%s)" % (self.user, self.course_id, self.created) return "[CourseEnrollment] %s: %s (%s)" % (self.user, self.course_id, self.created)
class CourseEnrollmentAllowed(models.Model):
"""
Table of users (specified by email address strings) who are allowed to enroll in a specified course.
The user may or may not (yet) exist. Enrollment by users listed in this table is allowed
even if the enrollment time window is past.
"""
email = models.CharField(max_length=255, db_index=True)
course_id = models.CharField(max_length=255, db_index=True)
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
class Meta:
unique_together = (('email', 'course_id'), )
def __unicode__(self):
return "[CourseEnrollmentAllowed] %s: %s (%s)" % (self.email, self.course_id, self.created)
@receiver(post_save, sender=CourseEnrollment) @receiver(post_save, sender=CourseEnrollment)
def assign_default_role(sender, instance, **kwargs): def assign_default_role(sender, instance, **kwargs):
if instance.user.is_staff: if instance.user.is_staff:
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
from collections import defaultdict from collections import defaultdict
import csv import csv
import json
import logging import logging
import os import os
import requests
import urllib import urllib
from django.conf import settings from django.conf import settings
...@@ -20,7 +22,7 @@ from courseware.courses import get_course_with_access ...@@ -20,7 +22,7 @@ from courseware.courses import get_course_with_access
from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA
from django_comment_client.utils import has_forum_access from django_comment_client.utils import has_forum_access
from psychometrics import psychoanalyze from psychometrics import psychoanalyze
from student.models import CourseEnrollment from student.models import CourseEnrollment, CourseEnrollmentAllowed
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
...@@ -125,7 +127,7 @@ def instructor_dashboard(request, course_id): ...@@ -125,7 +127,7 @@ def instructor_dashboard(request, course_id):
except Exception as err: except Exception as err:
msg += '<br/><p>Error: {0}</p>'.format(escape(err)) msg += '<br/><p>Error: {0}</p>'.format(escape(err))
if action == 'Dump list of enrolled students': if action == 'Dump list of enrolled students' or action=='List enrolled students':
log.debug(action) log.debug(action)
datatable = get_student_grade_summary_data(request, course, course_id, get_grades=False) datatable = get_student_grade_summary_data(request, course, course_id, get_grades=False)
datatable['title'] = 'List of students enrolled in {0}'.format(course_id) datatable['title'] = 'List of students enrolled in {0}'.format(course_id)
...@@ -258,6 +260,70 @@ def instructor_dashboard(request, course_id): ...@@ -258,6 +260,70 @@ def instructor_dashboard(request, course_id):
{}, page='idashboard') {}, page='idashboard')
#---------------------------------------- #----------------------------------------
# enrollment
elif action == 'List students who may enroll but may not have yet signed up':
ceaset = CourseEnrollmentAllowed.objects.filter(course_id=course_id)
datatable = {'header': ['StudentEmail']}
datatable['data'] = [[x.email] for x in ceaset]
datatable['title'] = action
elif action == 'Enroll student':
student = request.POST.get('enstudent','')
datatable = {}
try:
nce = CourseEnrollment(user=User.objects.get(email=student), course_id=course_id)
nce.save()
msg += "Enrolled student with email '%s'" % student
except Exception as err:
msg += "Error! Failed to enroll student with email '%s'\n" % student
msg += str(err) + '\n'
elif action == 'Un-enroll student':
student = request.POST.get('enstudent','')
datatable = {}
try:
nce = CourseEnrollment.objects.get(user=User.objects.get(email=student), course_id=course_id)
nce.delete()
msg += "Un-enrolled student with email '%s'" % student
except Exception as err:
msg += "Error! Failed to un-enroll student with email '%s'\n" % student
msg += str(err) + '\n'
elif action == 'Un-enroll ALL students':
ret = _do_enroll_students(course, course_id, '', overload=True)
datatable = ret['datatable']
elif action == 'Enroll multiple students':
students = request.POST.get('enroll_multiple','')
ret = _do_enroll_students(course, course_id, students)
datatable = ret['datatable']
elif action == 'List sections available in remote gradebook':
msg2, datatable = _do_remote_gradebook(course, 'get-sections')
msg += msg2
elif action in ['List students in section in remote gradebook',
'Overload enrollment list using remote gradebook',
'Merge enrollment list with remote gradebook']:
section = request.POST.get('gradebook_section','')
msg2, datatable = _do_remote_gradebook(course, 'get-membership', dict(section=section) )
msg += msg2
if not 'List' in action:
students = ','.join([x['email'] for x in datatable['retdata']])
overload = 'Overload' in action
ret = _do_enroll_students(course, course_id, students, overload=overload)
datatable = ret['datatable']
#----------------------------------------
# psychometrics # psychometrics
elif action == 'Generate Histogram and IRT Plot': elif action == 'Generate Histogram and IRT Plot':
...@@ -270,9 +336,9 @@ def instructor_dashboard(request, course_id): ...@@ -270,9 +336,9 @@ def instructor_dashboard(request, course_id):
problems = psychoanalyze.problems_with_psychometric_data(course_id) problems = psychoanalyze.problems_with_psychometric_data(course_id)
#---------------------------------------- #----------------------------------------
# context for rendering # context for rendering
context = {'course': course, context = {'course': course,
'staff_access': True, 'staff_access': True,
'admin_access': request.user.is_staff, 'admin_access': request.user.is_staff,
...@@ -285,16 +351,65 @@ def instructor_dashboard(request, course_id): ...@@ -285,16 +351,65 @@ def instructor_dashboard(request, course_id):
'plots': plots, # psychometrics 'plots': plots, # psychometrics
'course_errors': modulestore().get_item_errors(course.location), 'course_errors': modulestore().get_item_errors(course.location),
'djangopid' : os.getpid(), 'djangopid' : os.getpid(),
'mitx_version' : getattr(settings,'MITX_VERSION_STRING','')
} }
return render_to_response('courseware/instructor_dashboard.html', context) return render_to_response('courseware/instructor_dashboard.html', context)
def _do_remote_gradebook(course, action, args=None):
'''
Perform remote gradebook action. Returns msg, datatable.
'''
rg = course.metadata.get('remote_gradebook','')
if not rg:
msg = "No remote gradebook defined in course metadata"
return msg, {}
rgurl = settings.MITX_FEATURES.get('REMOTE_GRADEBOOK_URL','')
if not rgurl:
msg = "No remote gradebook url defined in settings.MITX_FEATURES"
return msg, {}
rgname = rg.get('name','')
if not rgname:
msg = "No gradebook name defined in course remote_gradebook metadata"
return msg, {}
if args is None:
args = {}
data = dict(submit=action, gradebook=rgname)
data.update(args)
try:
resp = requests.post(rgurl, data=data, verify=False)
retdict = json.loads(resp.content)
except Exception as err:
msg = "Failed to communicate with gradebook server at %s<br/>" % rgurl
msg += "Error: %s" % err
msg += "<br/>resp=%s" % resp.content
msg += "<br/>data=%s" % data
return msg, {}
msg = '<pre>%s</pre>' % retdict['msg'].replace('\n','<br/>')
retdata = retdict['data']
if retdata:
datatable = {'header': retdata[0].keys()}
datatable['data'] = [x.values() for x in retdata]
datatable['title'] = 'Remote gradebook response for %s' % action
datatable['retdata'] = retdata
else:
datatable = {}
return msg, datatable
def _list_course_forum_members(course_id, rolename, datatable): def _list_course_forum_members(course_id, rolename, datatable):
''' '''
Fills in datatable with forum membership information, for a given role, Fills in datatable with forum membership information, for a given role,
so that it will be displayed on instructor dashboard. so that it will be displayed on instructor dashboard.
course_ID = course's ID string course_ID = the ID string for a course
rolename = one of "Administrator", "Moderator", "Community TA" rolename = one of "Administrator", "Moderator", "Community TA"
Returns message status string to append to displayed message, if role is unknown. Returns message status string to append to displayed message, if role is unknown.
...@@ -455,6 +570,68 @@ def grade_summary(request, course_id): ...@@ -455,6 +570,68 @@ def grade_summary(request, course_id):
return render_to_response('courseware/grade_summary.html', context) return render_to_response('courseware/grade_summary.html', context)
def _do_enroll_students(course, course_id, students, overload=False):
"""Do the actual work of enrolling multiple students, presented as a string
of emails separated by commas or returns"""
ns = [x.split('\n') for x in students.split(',')]
new_students = [item for sublist in ns for item in sublist]
new_students = [str(s.strip()) for s in new_students]
new_students_lc = [x.lower() for x in new_students]
if '' in new_students:
new_students.remove('')
status = dict([x,'unprocessed'] for x in new_students)
if overload: # delete all but staff
todelete = CourseEnrollment.objects.filter(course_id=course_id)
for ce in todelete:
if not has_access(ce.user, course, 'staff') and ce.user.email.lower() not in new_students_lc:
status[ce.user.email] = 'deleted'
ce.delete()
else:
status[ce.user.email] = 'is staff'
ceaset = CourseEnrollmentAllowed.objects.filter(course_id=course_id)
for cea in ceaset:
status[cea.email] = 'removed from pending enrollment list'
ceaset.delete()
for student in new_students:
try:
user=User.objects.get(email=student)
except User.DoesNotExist:
# user not signed up yet, put in pending enrollment allowed table
if CourseEnrollmentAllowed.objects.filter(email=student, course_id=course_id):
status[student] = 'user does not exist, enrollment already allowed, pending'
continue
cea = CourseEnrollmentAllowed(email=student, course_id=course_id)
cea.save()
status[student] = 'user does not exist, enrollment allowed, pending'
continue
if CourseEnrollment.objects.filter(user=user, course_id=course_id):
status[student] = 'already enrolled'
continue
try:
nce = CourseEnrollment(user=user, course_id=course_id)
nce.save()
status[student] = 'added'
except:
status[student] = 'rejected'
datatable = {'header': ['StudentEmail', 'action']}
datatable['data'] = [[x, status[x]] for x in status]
datatable['title'] = 'Enrollment of students'
def sf(stat): return [x for x in status if status[x]==stat]
data = dict(added=sf('added'), rejected=sf('rejected')+sf('exists'),
deleted=sf('deleted'), datatable=datatable)
return data
@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)
def enroll_students(request, course_id): def enroll_students(request, course_id):
...@@ -473,22 +650,10 @@ def enroll_students(request, course_id): ...@@ -473,22 +650,10 @@ def enroll_students(request, course_id):
course = get_course_with_access(request.user, course_id, 'staff') course = get_course_with_access(request.user, course_id, 'staff')
existing_students = [ce.user.email for ce in CourseEnrollment.objects.filter(course_id=course_id)] existing_students = [ce.user.email for ce in CourseEnrollment.objects.filter(course_id=course_id)]
if 'new_students' in request.POST: new_students = request.POST.get('new_students')
new_students = request.POST['new_students'].split('\n') ret = _do_enroll_students(course, course_id, new_students)
else: added_students = ret['added']
new_students = [] rejected_students = ret['rejected']
new_students = [s.strip() for s in new_students]
added_students = []
rejected_students = []
for student in new_students:
try:
nce = CourseEnrollment(user=User.objects.get(email=student), course_id=course_id)
nce.save()
added_students.append(student)
except:
rejected_students.append(student)
return render_to_response("enroll_students.html", {'course': course_id, return render_to_response("enroll_students.html", {'course': course_id,
'existing_students': existing_students, 'existing_students': existing_students,
......
...@@ -102,6 +102,10 @@ SUBDOMAIN_BRANDING = { ...@@ -102,6 +102,10 @@ SUBDOMAIN_BRANDING = {
COMMENTS_SERVICE_KEY = "PUT_YOUR_API_KEY_HERE" COMMENTS_SERVICE_KEY = "PUT_YOUR_API_KEY_HERE"
################################# mitx revision string #####################
MITX_VERSION_STRING = os.popen('cd %s; git describe' % REPO_ROOT).read().strip()
################################# Staff grading config ##################### ################################# Staff grading config #####################
STAFF_GRADING_INTERFACE = { STAFF_GRADING_INTERFACE = {
......
...@@ -57,10 +57,13 @@ function goto( mode) ...@@ -57,10 +57,13 @@ function goto( mode)
<a href="#" onclick="goto('Psychometrics');" class="${modeflag.get('Psychometrics')}">Psychometrics</a> | <a href="#" onclick="goto('Psychometrics');" class="${modeflag.get('Psychometrics')}">Psychometrics</a> |
%endif %endif
<a href="#" onclick="goto('Admin');" class="${modeflag.get('Admin')}">Admin</a> | <a href="#" onclick="goto('Admin');" class="${modeflag.get('Admin')}">Admin</a> |
<a href="#" onclick="goto('Forum Admin');" class="${modeflag.get('Forum Admin')}">Forum Admin</a> ] <a href="#" onclick="goto('Forum Admin');" class="${modeflag.get('Forum Admin')}">Forum Admin</a> |
<a href="#" onclick="goto('Enrollment');" class="${modeflag.get('Enrollment')}">Enrollment</a>
]
</h2> </h2>
<div style="text-align:right" id="djangopid">${djangopid}</div> <div style="text-align:right"><span id="djangopid">${djangopid}</span>
| <span id="mitxver">${mitx_version}</span></div>
<form name="idashform" method="POST"> <form name="idashform" method="POST">
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }"> <input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
...@@ -163,10 +166,52 @@ function goto( mode) ...@@ -163,10 +166,52 @@ function goto( mode)
%endif %endif
%endif %endif
##-----------------------------------------------------------------------------
%if modeflag.get('Enrollment'):
<hr width="40%" style="align:left">
<p>
<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">
<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">
<input type="submit" name="action" value="Un-enroll ALL students">
<hr width="40%" style="align:left">
%if settings.MITX_FEATURES.get('REMOTE_GRADEBOOK_URL','') and instructor_access:
<%
rg = course.metadata.get('remote_gradebook',{})
%>
<p>Pull enrollment from remote gradebook</p>
<ul>
<li>Gradebook name: <input type="text" name="gradebook_name" size=40 value="${rg.get('name','')}"></li>
<li>Section: <input type="text" name="gradebook_section" size=40 value="${rg.get('section','')}"></li>
</ul>
<input type="submit" name="action" value="List sections available in remote gradebook">
<input type="submit" name="action" value="List students in section in remote gradebook">
<input type="submit" name="action" value="Overload enrollment list using remote gradebook">
<input type="submit" name="action" value="Merge enrollment list with remote gradebook">
<hr width="40%" style="align:left">
%endif
<p>Add students: enter emails, separated by returns or commas;</p>
<textarea rows="6" cols="70" name="enroll_multiple"></textarea>
<input type="submit" name="action" value="Enroll multiple students">
%endif
##-----------------------------------------------------------------------------
</form> </form>
##----------------------------------------------------------------------------- ##-----------------------------------------------------------------------------
%if modeflag.get('Psychometrics') is None: ##-----------------------------------------------------------------------------
%if datatable and modeflag.get('Psychometrics') is None:
<br/> <br/>
<br/> <br/>
......
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