Merge pull request #66 from edx/feature-dcadams-usermanagement
Feature user-management autoenroll
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
# -*- 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 'CourseEnrollmentAllowed.auto_enroll'
|
||||
db.add_column('student_courseenrollmentallowed', 'auto_enroll',
|
||||
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'CourseEnrollmentAllowed.auto_enroll'
|
||||
db.delete_column('student_courseenrollmentallowed', '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'})
|
||||
},
|
||||
'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'},
|
||||
'auto_enroll': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'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.testcenterregistration': {
|
||||
'Meta': {'object_name': 'TestCenterRegistration'},
|
||||
'accommodation_code': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}),
|
||||
'accommodation_request': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '1024', 'blank': 'True'}),
|
||||
'authorization_id': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'client_authorization_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'db_index': 'True'}),
|
||||
'confirmed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
|
||||
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'eligibility_appointment_date_first': ('django.db.models.fields.DateField', [], {'db_index': 'True'}),
|
||||
'eligibility_appointment_date_last': ('django.db.models.fields.DateField', [], {'db_index': 'True'}),
|
||||
'exam_series_code': ('django.db.models.fields.CharField', [], {'max_length': '15', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'processed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'testcenter_user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['student.TestCenterUser']"}),
|
||||
'updated_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'upload_error_message': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
|
||||
'upload_status': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}),
|
||||
'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'user_updated_at': ('django.db.models.fields.DateTimeField', [], {'db_index': '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', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}),
|
||||
'company_name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '50', 'blank': 'True'}),
|
||||
'confirmed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': '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'}),
|
||||
'processed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': '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'}),
|
||||
'upload_error_message': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}),
|
||||
'upload_status': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '20', 'blank': 'True'}),
|
||||
'uploaded_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': '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'"},
|
||||
'allow_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'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']
|
||||
@@ -662,6 +662,7 @@ class CourseEnrollmentAllowed(models.Model):
|
||||
"""
|
||||
email = 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)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ from student.models import (Registration, UserProfile, TestCenterUser, TestCente
|
||||
TestCenterRegistration, TestCenterRegistrationForm,
|
||||
PendingNameChange, PendingEmailChange,
|
||||
CourseEnrollment, unique_id_for_user,
|
||||
get_testcenter_registration)
|
||||
get_testcenter_registration, CourseEnrollmentAllowed)
|
||||
|
||||
from certificates.models import CertificateStatuses, certificate_status_for_student
|
||||
|
||||
@@ -264,7 +264,6 @@ def dashboard(request):
|
||||
if not user.is_active:
|
||||
message = render_to_string('registration/activate_account_notice.html', {'email': user.email})
|
||||
|
||||
|
||||
# Global staff can see what courses errored on their dashboard
|
||||
staff_access = False
|
||||
errored_courses = {}
|
||||
@@ -355,7 +354,7 @@ def change_enrollment(request):
|
||||
course = course_from_id(course_id)
|
||||
except ItemNotFoundError:
|
||||
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")
|
||||
|
||||
if not has_access(user, course, 'enroll'):
|
||||
@@ -363,9 +362,9 @@ def change_enrollment(request):
|
||||
|
||||
org, course_num, run = course_id.split("/")
|
||||
statsd.increment("common.student.enrollment",
|
||||
tags=["org:{0}".format(org),
|
||||
"course:{0}".format(course_num),
|
||||
"run:{0}".format(run)])
|
||||
tags=["org:{0}".format(org),
|
||||
"course:{0}".format(course_num),
|
||||
"run:{0}".format(run)])
|
||||
|
||||
try:
|
||||
enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id)
|
||||
@@ -382,9 +381,9 @@ def change_enrollment(request):
|
||||
|
||||
org, course_num, run = course_id.split("/")
|
||||
statsd.increment("common.student.unenrollment",
|
||||
tags=["org:{0}".format(org),
|
||||
"course:{0}".format(course_num),
|
||||
"run:{0}".format(run)])
|
||||
tags=["org:{0}".format(org),
|
||||
"course:{0}".format(course_num),
|
||||
"run:{0}".format(run)])
|
||||
|
||||
return HttpResponse()
|
||||
except CourseEnrollment.DoesNotExist:
|
||||
@@ -454,7 +453,6 @@ def login_user(request, error=""):
|
||||
expires_time = time.time() + max_age
|
||||
expires = cookie_date(expires_time)
|
||||
|
||||
|
||||
response.set_cookie(settings.EDXMKTG_COOKIE_NAME,
|
||||
'true', max_age=max_age,
|
||||
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
|
||||
@@ -515,8 +513,8 @@ def _do_create_account(post_vars):
|
||||
Note: this function is also used for creating test users.
|
||||
"""
|
||||
user = User(username=post_vars['username'],
|
||||
email=post_vars['email'],
|
||||
is_active=False)
|
||||
email=post_vars['email'],
|
||||
is_active=False)
|
||||
user.set_password(post_vars['password'])
|
||||
registration = Registration()
|
||||
# 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):
|
||||
expires_time = time.time() + max_age
|
||||
expires = cookie_date(expires_time)
|
||||
|
||||
|
||||
response.set_cookie(settings.EDXMKTG_COOKIE_NAME,
|
||||
'true', max_age=max_age,
|
||||
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
|
||||
@@ -708,7 +705,6 @@ def create_account(request, post_override=None):
|
||||
return response
|
||||
|
||||
|
||||
|
||||
def exam_registration_info(user, course):
|
||||
""" 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
|
||||
@@ -849,7 +845,6 @@ def create_exam_registration(request, post_override=None):
|
||||
response_data['non_field_errors'] = form.non_field_errors()
|
||||
return HttpResponse(json.dumps(response_data), mimetype="application/json")
|
||||
|
||||
|
||||
# only do the following if there is accommodation text to send,
|
||||
# and a destination to which to send it.
|
||||
# TODO: still need to create the accommodation email templates
|
||||
@@ -872,7 +867,6 @@ def create_exam_registration(request, post_override=None):
|
||||
# response_data['non_field_errors'] = [ 'Could not send accommodation e-mail.', ]
|
||||
# return HttpResponse(json.dumps(response_data), mimetype="application/json")
|
||||
|
||||
|
||||
js = {'success': True}
|
||||
return HttpResponse(json.dumps(js), mimetype="application/json")
|
||||
|
||||
@@ -916,6 +910,16 @@ def activate_account(request, key):
|
||||
if not r[0].user.is_active:
|
||||
r[0].activate()
|
||||
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})
|
||||
return resp
|
||||
if len(r) == 0:
|
||||
|
||||
178
lms/djangoapps/instructor/tests/test_enrollment.py
Normal file
178
lms/djangoapps/instructor/tests/test_enrollment.py
Normal file
@@ -0,0 +1,178 @@
|
||||
'''
|
||||
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'])
|
||||
@@ -227,13 +227,13 @@ def instructor_dashboard(request, course_id):
|
||||
if student_to_reset is not None:
|
||||
# find the module in question
|
||||
if '/' not in problem_to_reset: # allow state of modules other than problem to be reset
|
||||
problem_to_reset = "problem/" + problem_to_reset # but problem is the default
|
||||
problem_to_reset = "problem/" + problem_to_reset # but problem is the default
|
||||
try:
|
||||
(org, course_name, _) = course_id.split("/")
|
||||
module_state_key = "i4x://" + org + "/" + course_name + "/" + problem_to_reset
|
||||
module_to_reset = StudentModule.objects.get(student_id=student_to_reset.id,
|
||||
course_id=course_id,
|
||||
module_state_key=module_state_key)
|
||||
course_id=course_id,
|
||||
module_state_key=module_state_key)
|
||||
msg += "Found module to reset. "
|
||||
except Exception:
|
||||
msg += "<font color='red'>Couldn't find module with that urlname. </font>"
|
||||
@@ -257,19 +257,18 @@ def instructor_dashboard(request, course_id):
|
||||
module_to_reset.state = json.dumps(problem_state)
|
||||
module_to_reset.save()
|
||||
track.views.server_track(request,
|
||||
'{instructor} reset attempts from {old_attempts} to 0 for {student} on problem {problem} in {course}'.format(
|
||||
old_attempts=old_number_of_attempts,
|
||||
student=student_to_reset,
|
||||
problem=module_to_reset.module_state_key,
|
||||
instructor=request.user,
|
||||
course=course_id),
|
||||
{},
|
||||
page='idashboard')
|
||||
'{instructor} reset attempts from {old_attempts} to 0 for {student} on problem {problem} in {course}'.format(
|
||||
old_attempts=old_number_of_attempts,
|
||||
student=student_to_reset,
|
||||
problem=module_to_reset.module_state_key,
|
||||
instructor=request.user,
|
||||
course=course_id),
|
||||
{},
|
||||
page='idashboard')
|
||||
msg += "<font color='green'>Module state successfully reset!</font>"
|
||||
except:
|
||||
msg += "<font color='red'>Couldn't reset module state. </font>"
|
||||
|
||||
|
||||
elif "Get link to student's progress page" in action:
|
||||
unique_student_identifier = request.POST.get('unique_student_identifier', '')
|
||||
try:
|
||||
@@ -279,12 +278,12 @@ def instructor_dashboard(request, course_id):
|
||||
student_to_reset = User.objects.get(username=unique_student_identifier)
|
||||
progress_url = reverse('student_progress', kwargs={'course_id': course_id, 'student_id': student_to_reset.id})
|
||||
track.views.server_track(request,
|
||||
'{instructor} requested progress page for {student} in {course}'.format(
|
||||
student=student_to_reset,
|
||||
instructor=request.user,
|
||||
course=course_id),
|
||||
{},
|
||||
page='idashboard')
|
||||
'{instructor} requested progress page for {student} in {course}'.format(
|
||||
student=student_to_reset,
|
||||
instructor=request.user,
|
||||
course=course_id),
|
||||
{},
|
||||
page='idashboard')
|
||||
msg += "<a href='{0}' target='_blank'> Progress page for username: {1} with email address: {2}</a>.".format(progress_url, student_to_reset.username, student_to_reset.email)
|
||||
except:
|
||||
msg += "<font color='red'>Couldn't find student with that username. </font>"
|
||||
@@ -312,6 +311,7 @@ def instructor_dashboard(request, course_id):
|
||||
msg2, rg_stud_data = _do_remote_gradebook(request.user, course, 'get-membership')
|
||||
datatable = {'header': ['Student email', 'Match?']}
|
||||
rg_students = [x['email'] for x in rg_stud_data['retdata']]
|
||||
|
||||
def domatch(x):
|
||||
return 'yes' if x.email in rg_students else 'No'
|
||||
datatable['data'] = [[x.email, domatch(x)] for x in stud_data['students']]
|
||||
@@ -347,7 +347,6 @@ def instructor_dashboard(request, course_id):
|
||||
msg2, _ = _do_remote_gradebook(request.user, course, 'post-grades', files=files)
|
||||
msg += msg2
|
||||
|
||||
|
||||
#----------------------------------------
|
||||
# Admin
|
||||
|
||||
@@ -413,6 +412,7 @@ def instructor_dashboard(request, course_id):
|
||||
profkeys = ['name', 'language', 'location', 'year_of_birth', 'gender', 'level_of_education',
|
||||
'mailing_address', 'goals']
|
||||
datatable = {'header': ['username', 'email'] + profkeys}
|
||||
|
||||
def getdat(u):
|
||||
p = u.profile
|
||||
return [u.username, u.email] + [getattr(p, x, '') for x in profkeys]
|
||||
@@ -421,9 +421,8 @@ def instructor_dashboard(request, course_id):
|
||||
datatable['title'] = 'Student profile data for course %s' % course_id
|
||||
return return_csv('profiledata_%s.csv' % course_id, datatable)
|
||||
|
||||
|
||||
elif 'Download CSV of all responses to problem' in action:
|
||||
problem_to_dump = request.POST.get('problem_to_dump','')
|
||||
problem_to_dump = request.POST.get('problem_to_dump', '')
|
||||
|
||||
if problem_to_dump[-4:] == ".xml":
|
||||
problem_to_dump = problem_to_dump[:-4]
|
||||
@@ -441,7 +440,7 @@ def instructor_dashboard(request, course_id):
|
||||
|
||||
if smdat:
|
||||
datatable = {'header': ['username', 'state']}
|
||||
datatable['data'] = [ [x.student.username, x.state] for x in smdat ]
|
||||
datatable['data'] = [[x.student.username, x.state] for x in smdat]
|
||||
datatable['title'] = 'Student state for problem %s' % problem_to_dump
|
||||
return return_csv('student_state_from_%s.csv' % problem_to_dump, datatable)
|
||||
|
||||
@@ -478,7 +477,6 @@ def instructor_dashboard(request, course_id):
|
||||
msg += _list_course_forum_members(course_id, rolename, datatable)
|
||||
track.views.server_track(request, 'list-{0}'.format(rolename), {}, page='idashboard')
|
||||
|
||||
|
||||
elif action == 'Remove forum admin':
|
||||
uname = request.POST['forumadmin']
|
||||
msg += _update_forum_role_membership(uname, course, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_REMOVE)
|
||||
@@ -536,35 +534,17 @@ def instructor_dashboard(request, course_id):
|
||||
datatable['data'] = [[x.email] for x in ceaset]
|
||||
datatable['title'] = action
|
||||
|
||||
elif action == 'Enroll student':
|
||||
|
||||
student = request.POST.get('enstudent', '')
|
||||
ret = _do_enroll_students(course, course_id, student)
|
||||
datatable = ret['datatable']
|
||||
|
||||
elif action == 'Un-enroll student':
|
||||
|
||||
student = request.POST.get('enstudent', '')
|
||||
datatable = {}
|
||||
isok = False
|
||||
cea = CourseEnrollmentAllowed.objects.filter(course_id=course_id, email=student)
|
||||
if cea:
|
||||
cea.delete()
|
||||
msg += "Un-enrolled student with email '%s'" % student
|
||||
isok = True
|
||||
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:
|
||||
if not isok:
|
||||
msg += "Error! Failed to un-enroll student with email '%s'\n" % student
|
||||
msg += str(err) + '\n'
|
||||
|
||||
elif action == 'Enroll multiple students':
|
||||
|
||||
students = request.POST.get('enroll_multiple', '')
|
||||
ret = _do_enroll_students(course, course_id, students)
|
||||
students = request.POST.get('multiple_students', '')
|
||||
auto_enroll = bool(request.POST.get('auto_enroll'))
|
||||
ret = _do_enroll_students(course, course_id, students, auto_enroll=auto_enroll)
|
||||
datatable = ret['datatable']
|
||||
|
||||
elif action == 'Unenroll multiple students':
|
||||
|
||||
students = request.POST.get('multiple_students', '')
|
||||
ret = _do_unenroll_students(course_id, students)
|
||||
datatable = ret['datatable']
|
||||
|
||||
elif action == 'List sections available in remote gradebook':
|
||||
@@ -586,7 +566,6 @@ def instructor_dashboard(request, course_id):
|
||||
ret = _do_enroll_students(course, course_id, students, overload=overload)
|
||||
datatable = ret['datatable']
|
||||
|
||||
|
||||
#----------------------------------------
|
||||
# psychometrics
|
||||
|
||||
@@ -606,9 +585,9 @@ def instructor_dashboard(request, course_id):
|
||||
logs and swallows errors.
|
||||
"""
|
||||
url = settings.ANALYTICS_SERVER_URL + \
|
||||
"get?aname={}&course_id={}&apikey={}".format(analytics_name,
|
||||
course_id,
|
||||
settings.ANALYTICS_API_KEY)
|
||||
"get?aname={}&course_id={}&apikey={}".format(analytics_name,
|
||||
course_id,
|
||||
settings.ANALYTICS_API_KEY)
|
||||
try:
|
||||
res = requests.get(url)
|
||||
except Exception:
|
||||
@@ -667,7 +646,7 @@ def instructor_dashboard(request, course_id):
|
||||
'cohorts_ajax_url': reverse('cohorts', kwargs={'course_id': course_id}),
|
||||
|
||||
'analytics_results': analytics_results,
|
||||
}
|
||||
}
|
||||
|
||||
return render_to_response('courseware/instructor_dashboard.html', context)
|
||||
|
||||
@@ -830,7 +809,7 @@ def _add_or_remove_user_group(request, username_or_email, group, group_title, ev
|
||||
action = "Added" if do_add else "Removed"
|
||||
prep = "to" if do_add else "from"
|
||||
msg = '<font color="green">{action} {0} {prep} {1} group = {2}</font>'.format(user, group_title, group.name,
|
||||
action=action, prep=prep)
|
||||
action=action, prep=prep)
|
||||
if do_add:
|
||||
user.groups.add(group)
|
||||
else:
|
||||
@@ -956,7 +935,7 @@ def gradebook(request, course_id):
|
||||
'grade_summary': student_grades(student, request, course),
|
||||
'realname': student.profile.name,
|
||||
}
|
||||
for student in enrolled_students]
|
||||
for student in enrolled_students]
|
||||
|
||||
return render_to_response('courseware/gradebook.html', {
|
||||
'students': student_info,
|
||||
@@ -982,17 +961,11 @@ def grade_summary(request, course_id):
|
||||
#-----------------------------------------------------------------------------
|
||||
# enrollment
|
||||
|
||||
def _do_enroll_students(course, course_id, students, overload=False):
|
||||
def _do_enroll_students(course, course_id, students, overload=False, auto_enroll=False):
|
||||
"""Do the actual work of enrolling multiple students, presented as a string
|
||||
of emails separated by commas or returns"""
|
||||
|
||||
new_students = split_by_comma_and_whitespace(students)
|
||||
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('')
|
||||
|
||||
new_students, new_students_lc = get_and_clean_student_list(students)
|
||||
status = dict([x, 'unprocessed'] for x in new_students)
|
||||
|
||||
if overload: # delete all but staff
|
||||
@@ -1012,27 +985,35 @@ def _do_enroll_students(course, course_id, students, overload=False):
|
||||
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'
|
||||
|
||||
#User not signed up yet, put in pending enrollment allowed table
|
||||
cea = CourseEnrollmentAllowed.objects.filter(email=student, course_id=course_id)
|
||||
|
||||
#If enrollmentallowed already exists, update auto_enroll flag to however it was set in UI
|
||||
#Will be 0 or 1 records as there is a unique key on email + course_id
|
||||
if cea:
|
||||
cea[0].auto_enroll = auto_enroll
|
||||
cea[0].save()
|
||||
status[student] = 'user does not exist, enrollment already allowed, pending with auto enrollment ' \
|
||||
+ ('on' if auto_enroll else 'off')
|
||||
continue
|
||||
cea = CourseEnrollmentAllowed(email=student, course_id=course_id)
|
||||
cea = CourseEnrollmentAllowed(email=student, course_id=course_id, auto_enroll=auto_enroll)
|
||||
cea.save()
|
||||
status[student] = 'user does not exist, enrollment allowed, pending'
|
||||
status[student] = 'user does not exist, enrollment allowed, pending with auto enrollment ' + ('on' if auto_enroll else 'off')
|
||||
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()
|
||||
ce = CourseEnrollment(user=user, course_id=course_id)
|
||||
ce.save()
|
||||
status[student] = 'added'
|
||||
except:
|
||||
status[student] = 'rejected'
|
||||
|
||||
datatable = {'header': ['StudentEmail', 'action']}
|
||||
datatable['data'] = [[x, status[x]] for x in status]
|
||||
datatable['data'] = [[x, status[x]] for x in sorted(status)]
|
||||
datatable['title'] = 'Enrollment of students'
|
||||
|
||||
def sf(stat):
|
||||
@@ -1044,39 +1025,69 @@ def _do_enroll_students(course, course_id, students, overload=False):
|
||||
return data
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
def enroll_students(request, course_id):
|
||||
"""Allows a staff member to enroll students in a course.
|
||||
#Unenrollment
|
||||
def _do_unenroll_students(course_id, students):
|
||||
"""Do the actual work of un-enrolling multiple students, presented as a string
|
||||
of emails separated by commas or returns"""
|
||||
|
||||
This is a short-term hack for Berkeley courses launching fall
|
||||
2012. In the long term, we would like functionality like this, but
|
||||
we would like both the instructor and the student to agree. Right
|
||||
now, this allows any instructor to add students to their course,
|
||||
which we do not want.
|
||||
old_students, old_students_lc = get_and_clean_student_list(students)
|
||||
status = dict([x, 'unprocessed'] for x in old_students)
|
||||
|
||||
It is poorly written and poorly tested, but it's designed to be
|
||||
stripped out.
|
||||
for student in old_students:
|
||||
|
||||
isok = False
|
||||
cea = CourseEnrollmentAllowed.objects.filter(course_id=course_id, email=student)
|
||||
#Will be 0 or 1 records as there is a unique key on email + course_id
|
||||
if cea:
|
||||
cea[0].delete()
|
||||
status[student] = "un-enrolled"
|
||||
isok = True
|
||||
|
||||
try:
|
||||
user = User.objects.get(email=student)
|
||||
except User.DoesNotExist:
|
||||
continue
|
||||
|
||||
ce = CourseEnrollment.objects.filter(user=user, course_id=course_id)
|
||||
#Will be 0 or 1 records as there is a unique key on user + course_id
|
||||
if ce:
|
||||
try:
|
||||
ce[0].delete()
|
||||
status[student] = "un-enrolled"
|
||||
except Exception as err:
|
||||
if not isok:
|
||||
status[student] = "Error! Failed to un-enroll"
|
||||
|
||||
datatable = {'header': ['StudentEmail', 'action']}
|
||||
datatable['data'] = [[x, status[x]] for x in sorted(status)]
|
||||
datatable['title'] = 'Un-enrollment of students'
|
||||
|
||||
data = dict(datatable=datatable)
|
||||
return data
|
||||
|
||||
|
||||
def get_and_clean_student_list(students):
|
||||
"""
|
||||
Separate out individual student email from the comma, or space separated string.
|
||||
|
||||
In:
|
||||
students: string coming from the input text area
|
||||
Return:
|
||||
students: list of cleaned student emails
|
||||
students_lc: list of lower case cleaned student emails
|
||||
"""
|
||||
|
||||
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)]
|
||||
|
||||
new_students = request.POST.get('new_students')
|
||||
ret = _do_enroll_students(course, course_id, new_students)
|
||||
added_students = ret['added']
|
||||
rejected_students = ret['rejected']
|
||||
|
||||
return render_to_response("enroll_students.html", {'course': course_id,
|
||||
'existing_students': existing_students,
|
||||
'added_students': added_students,
|
||||
'rejected_students': rejected_students,
|
||||
'debug': new_students})
|
||||
students = split_by_comma_and_whitespace(students)
|
||||
students = [str(s.strip()) for s in students]
|
||||
students = [s for s in students if s != '']
|
||||
students_lc = [x.lower() for x in students]
|
||||
|
||||
return students, students_lc
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# answer distribution
|
||||
|
||||
|
||||
def get_answers_distribution(request, course_id):
|
||||
"""
|
||||
Get the distribution of answers for all graded problems in the course.
|
||||
@@ -1168,5 +1179,5 @@ def dump_grading_context(course):
|
||||
msg += " %s (format=%s, Assignment=%s%s)\n" % (s.display_name, format, aname, notes)
|
||||
msg += "all descriptors:\n"
|
||||
msg += "length=%d\n" % len(gc['all_descriptors'])
|
||||
msg = '<pre>%s</pre>' % msg.replace('<','<')
|
||||
msg = '<pre>%s</pre>' % msg.replace('<', '<')
|
||||
return msg
|
||||
|
||||
@@ -296,9 +296,6 @@ function goto( mode)
|
||||
<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">
|
||||
<hr width="40%" style="align:left">
|
||||
|
||||
%if settings.MITX_FEATURES.get('REMOTE_GRADEBOOK_URL','') and instructor_access:
|
||||
@@ -320,9 +317,13 @@ function goto( mode)
|
||||
|
||||
%endif
|
||||
|
||||
<p>Add students: enter emails, separated by new lines or commas;</p>
|
||||
<textarea rows="6" cols="70" name="enroll_multiple"></textarea>
|
||||
<p>Enroll or un-enroll one or many students: enter emails, separated by new lines or commas;</p>
|
||||
<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">
|
||||
<p>
|
||||
<input type="submit" name="action" value="Unenroll multiple students">
|
||||
|
||||
%endif
|
||||
|
||||
|
||||
@@ -267,8 +267,6 @@ if settings.COURSEWARE_ENABLED:
|
||||
'instructor.views.gradebook', name='gradebook'),
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/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$',
|
||||
'open_ended_grading.views.staff_grading', name='staff_grading'),
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/get_next$',
|
||||
|
||||
Reference in New Issue
Block a user