Make course ids and usage ids opaque to LMS and Studio [partial commit]
This commit updates common/djangoapps. These keys are now objects with a limited interface, and the particular internal representation is managed by the data storage layer (the modulestore). For the LMS, there should be no outward-facing changes to the system. The keys are, for now, a change to internal representation only. For Studio, the new serialized form of the keys is used in urls, to allow for further migration in the future. Co-Author: Andy Armstrong <andya@edx.org> Co-Author: Christina Roberts <christina@edx.org> Co-Author: David Baumgold <db@edx.org> Co-Author: Diana Huang <dkh@edx.org> Co-Author: Don Mitchell <dmitchell@edx.org> Co-Author: Julia Hansbrough <julia@edx.org> Co-Author: Nimisha Asthagiri <nasthagiri@edx.org> Co-Author: Sarina Canelake <sarina@edx.org> [LMS-2370]
This commit is contained in:
@@ -35,7 +35,7 @@ def has_access(user, role):
|
||||
return True
|
||||
# if not, then check inferred permissions
|
||||
if (isinstance(role, (CourseStaffRole, CourseBetaTesterRole)) and
|
||||
CourseInstructorRole(role.location).has_user(user)):
|
||||
CourseInstructorRole(role.course_key).has_user(user)):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -81,6 +81,6 @@ def _check_caller_authority(caller, role):
|
||||
if isinstance(role, (GlobalStaff, CourseCreatorRole)):
|
||||
raise PermissionDenied
|
||||
elif isinstance(role, CourseRole): # instructors can change the roles w/in their course
|
||||
if not has_access(caller, CourseInstructorRole(role.location)):
|
||||
if not has_access(caller, CourseInstructorRole(role.course_key)):
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class Command(BaseCommand):
|
||||
for student in students:
|
||||
csv_writer.writerow((
|
||||
student.id,
|
||||
anonymous_id_for_user(student, ''),
|
||||
anonymous_id_for_user(student, None),
|
||||
anonymous_id_for_user(student, course_id)
|
||||
))
|
||||
except IOError:
|
||||
|
||||
@@ -5,6 +5,9 @@ from django.contrib.auth.models import User
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import translation
|
||||
|
||||
from opaque_keys import InvalidKeyError
|
||||
from xmodule.modulestore.keys import CourseKey
|
||||
from xmodule.modulestore.locations import SlashSeparatedCourseKey
|
||||
from student.models import CourseEnrollment, Registration, create_comments_service_user
|
||||
from student.views import _do_create_account, AccountValidationError
|
||||
from track.management.tracked_command import TrackedCommand
|
||||
@@ -68,6 +71,15 @@ class Command(TrackedCommand):
|
||||
if not name:
|
||||
name = options['email'].split('@')[0]
|
||||
|
||||
# parse out the course into a coursekey
|
||||
if options['course']:
|
||||
try:
|
||||
course = CourseKey.from_string(options['course'])
|
||||
# if it's not a new-style course key, parse it from an old-style
|
||||
# course key
|
||||
except InvalidKeyError:
|
||||
course = SlashSeparatedCourseKey.from_deprecated_string(options['course'])
|
||||
|
||||
post_data = {
|
||||
'username': username,
|
||||
'email': options['email'],
|
||||
@@ -93,5 +105,5 @@ class Command(TrackedCommand):
|
||||
print e.message
|
||||
user = User.objects.get(email=options['email'])
|
||||
if options['course']:
|
||||
CourseEnrollment.enroll(user, options['course'], mode=options['mode'])
|
||||
CourseEnrollment.enroll(user, course, mode=options['mode'])
|
||||
translation.deactivate()
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
# -*- 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 'CourseAccessRole'
|
||||
db.create_table('student_courseaccessrole', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
|
||||
('org', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=64, blank=True)),
|
||||
('course_id', self.gf('xmodule_django.models.CourseKeyField')(db_index=True, max_length=255, blank=True)),
|
||||
('role', self.gf('django.db.models.fields.CharField')(max_length=64, db_index=True)),
|
||||
))
|
||||
db.send_create_signal('student', ['CourseAccessRole'])
|
||||
|
||||
# Adding unique constraint on 'CourseAccessRole', fields ['user', 'org', 'course_id', 'role']
|
||||
db.create_unique('student_courseaccessrole', ['user_id', 'org', 'course_id', 'role'])
|
||||
|
||||
|
||||
# Changing field 'AnonymousUserId.course_id'
|
||||
db.alter_column('student_anonymoususerid', 'course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=255))
|
||||
|
||||
# Changing field 'CourseEnrollment.course_id'
|
||||
db.alter_column('student_courseenrollment', 'course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=255))
|
||||
|
||||
# Changing field 'CourseEnrollmentAllowed.course_id'
|
||||
db.alter_column('student_courseenrollmentallowed', 'course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=255))
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'CourseAccessRole', fields ['user', 'org', 'course_id', 'role']
|
||||
db.delete_unique('student_courseaccessrole', ['user_id', 'org', 'course_id', 'role'])
|
||||
|
||||
# Deleting model 'CourseAccessRole'
|
||||
db.delete_table('student_courseaccessrole')
|
||||
|
||||
|
||||
# Changing field 'AnonymousUserId.course_id'
|
||||
db.alter_column('student_anonymoususerid', 'course_id', self.gf('django.db.models.fields.CharField')(max_length=255))
|
||||
|
||||
# Changing field 'CourseEnrollment.course_id'
|
||||
db.alter_column('student_courseenrollment', 'course_id', self.gf('django.db.models.fields.CharField')(max_length=255))
|
||||
|
||||
# Changing field 'CourseEnrollmentAllowed.course_id'
|
||||
db.alter_column('student_courseenrollmentallowed', 'course_id', self.gf('django.db.models.fields.CharField')(max_length=255))
|
||||
|
||||
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.anonymoususerid': {
|
||||
'Meta': {'object_name': 'AnonymousUserId'},
|
||||
'anonymous_user_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.courseaccessrole': {
|
||||
'Meta': {'unique_together': "(('user', 'org', 'course_id', 'role'),)", 'object_name': 'CourseAccessRole'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'org': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'blank': 'True'}),
|
||||
'role': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.courseenrollment': {
|
||||
'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'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'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}),
|
||||
'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': ('xmodule_django.models.CourseKeyField', [], {'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.loginfailures': {
|
||||
'Meta': {'object_name': 'LoginFailures'},
|
||||
'failure_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'lockout_until': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.passwordhistory': {
|
||||
'Meta': {'object_name': 'PasswordHistory'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'time_set': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'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.userprofile': {
|
||||
'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"},
|
||||
'allow_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'city': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'null': 'True', 'blank': '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.userstanding': {
|
||||
'Meta': {'object_name': 'UserStanding'},
|
||||
'account_status': ('django.db.models.fields.CharField', [], {'max_length': '31', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'standing_last_changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'standing'", 'unique': 'True', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'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']
|
||||
@@ -5,12 +5,12 @@ import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from xmodule.modulestore import Location
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
from student.roles import CourseInstructorRole, CourseStaffRole, CourseCreatorRole
|
||||
from student.tests.factories import AdminFactory
|
||||
from student.auth import has_access, add_users, remove_users
|
||||
from xmodule.modulestore.locations import SlashSeparatedCourseKey
|
||||
|
||||
|
||||
class CreatorGroupTest(TestCase):
|
||||
@@ -137,54 +137,54 @@ class CourseGroupTest(TestCase):
|
||||
self.global_admin = AdminFactory()
|
||||
self.creator = User.objects.create_user('testcreator', 'testcreator+courses@edx.org', 'foo')
|
||||
self.staff = User.objects.create_user('teststaff', 'teststaff+courses@edx.org', 'foo')
|
||||
self.location = Location('i4x', 'mitX', '101', 'course', 'test')
|
||||
self.course_key = SlashSeparatedCourseKey('mitX', '101', 'test')
|
||||
|
||||
def test_add_user_to_course_group(self):
|
||||
"""
|
||||
Tests adding user to course group (happy path).
|
||||
"""
|
||||
# Create groups for a new course (and assign instructor role to the creator).
|
||||
self.assertFalse(has_access(self.creator, CourseInstructorRole(self.location)))
|
||||
add_users(self.global_admin, CourseInstructorRole(self.location), self.creator)
|
||||
add_users(self.global_admin, CourseStaffRole(self.location), self.creator)
|
||||
self.assertTrue(has_access(self.creator, CourseInstructorRole(self.location)))
|
||||
self.assertFalse(has_access(self.creator, CourseInstructorRole(self.course_key)))
|
||||
add_users(self.global_admin, CourseInstructorRole(self.course_key), self.creator)
|
||||
add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator)
|
||||
self.assertTrue(has_access(self.creator, CourseInstructorRole(self.course_key)))
|
||||
|
||||
# Add another user to the staff role.
|
||||
self.assertFalse(has_access(self.staff, CourseStaffRole(self.location)))
|
||||
add_users(self.creator, CourseStaffRole(self.location), self.staff)
|
||||
self.assertTrue(has_access(self.staff, CourseStaffRole(self.location)))
|
||||
self.assertFalse(has_access(self.staff, CourseStaffRole(self.course_key)))
|
||||
add_users(self.creator, CourseStaffRole(self.course_key), self.staff)
|
||||
self.assertTrue(has_access(self.staff, CourseStaffRole(self.course_key)))
|
||||
|
||||
def test_add_user_to_course_group_permission_denied(self):
|
||||
"""
|
||||
Verifies PermissionDenied if caller of add_user_to_course_group is not instructor role.
|
||||
"""
|
||||
add_users(self.global_admin, CourseInstructorRole(self.location), self.creator)
|
||||
add_users(self.global_admin, CourseStaffRole(self.location), self.creator)
|
||||
add_users(self.global_admin, CourseInstructorRole(self.course_key), self.creator)
|
||||
add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator)
|
||||
with self.assertRaises(PermissionDenied):
|
||||
add_users(self.staff, CourseStaffRole(self.location), self.staff)
|
||||
add_users(self.staff, CourseStaffRole(self.course_key), self.staff)
|
||||
|
||||
def test_remove_user_from_course_group(self):
|
||||
"""
|
||||
Tests removing user from course group (happy path).
|
||||
"""
|
||||
add_users(self.global_admin, CourseInstructorRole(self.location), self.creator)
|
||||
add_users(self.global_admin, CourseStaffRole(self.location), self.creator)
|
||||
add_users(self.global_admin, CourseInstructorRole(self.course_key), self.creator)
|
||||
add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator)
|
||||
|
||||
add_users(self.creator, CourseStaffRole(self.location), self.staff)
|
||||
self.assertTrue(has_access(self.staff, CourseStaffRole(self.location)))
|
||||
add_users(self.creator, CourseStaffRole(self.course_key), self.staff)
|
||||
self.assertTrue(has_access(self.staff, CourseStaffRole(self.course_key)))
|
||||
|
||||
remove_users(self.creator, CourseStaffRole(self.location), self.staff)
|
||||
self.assertFalse(has_access(self.staff, CourseStaffRole(self.location)))
|
||||
remove_users(self.creator, CourseStaffRole(self.course_key), self.staff)
|
||||
self.assertFalse(has_access(self.staff, CourseStaffRole(self.course_key)))
|
||||
|
||||
remove_users(self.creator, CourseInstructorRole(self.location), self.creator)
|
||||
self.assertFalse(has_access(self.creator, CourseInstructorRole(self.location)))
|
||||
remove_users(self.creator, CourseInstructorRole(self.course_key), self.creator)
|
||||
self.assertFalse(has_access(self.creator, CourseInstructorRole(self.course_key)))
|
||||
|
||||
def test_remove_user_from_course_group_permission_denied(self):
|
||||
"""
|
||||
Verifies PermissionDenied if caller of remove_user_from_course_group is not instructor role.
|
||||
"""
|
||||
add_users(self.global_admin, CourseInstructorRole(self.location), self.creator)
|
||||
add_users(self.global_admin, CourseInstructorRole(self.course_key), self.creator)
|
||||
another_staff = User.objects.create_user('another', 'teststaff+anothercourses@edx.org', 'foo')
|
||||
add_users(self.global_admin, CourseStaffRole(self.location), self.creator, self.staff, another_staff)
|
||||
add_users(self.global_admin, CourseStaffRole(self.course_key), self.creator, self.staff, another_staff)
|
||||
with self.assertRaises(PermissionDenied):
|
||||
remove_users(self.staff, CourseStaffRole(self.location), another_staff)
|
||||
remove_users(self.staff, CourseStaffRole(self.course_key), another_staff)
|
||||
|
||||
@@ -6,6 +6,7 @@ from django_comment_common.models import (
|
||||
from django_comment_common.utils import seed_permissions_roles
|
||||
from student.models import CourseEnrollment, UserProfile
|
||||
from util.testing import UrlResetMixin
|
||||
from xmodule.modulestore.locations import SlashSeparatedCourseKey
|
||||
from mock import patch
|
||||
|
||||
|
||||
@@ -23,6 +24,8 @@ class AutoAuthEnabledTestCase(UrlResetMixin, TestCase):
|
||||
super(AutoAuthEnabledTestCase, self).setUp()
|
||||
self.url = '/auto_auth'
|
||||
self.client = Client()
|
||||
self.course_id = 'edX/Test101/2014_Spring'
|
||||
self.course_key = SlashSeparatedCourseKey.from_deprecated_string(self.course_id)
|
||||
|
||||
def test_create_user(self):
|
||||
"""
|
||||
@@ -83,43 +86,39 @@ class AutoAuthEnabledTestCase(UrlResetMixin, TestCase):
|
||||
def test_course_enrollment(self):
|
||||
|
||||
# Create a user and enroll in a course
|
||||
course_id = "edX/Test101/2014_Spring"
|
||||
self._auto_auth(username='test', course_id=course_id)
|
||||
self._auto_auth(username='test', course_id=self.course_id)
|
||||
|
||||
# Check that a course enrollment was created for the user
|
||||
self.assertEqual(CourseEnrollment.objects.count(), 1)
|
||||
enrollment = CourseEnrollment.objects.get(course_id=course_id)
|
||||
enrollment = CourseEnrollment.objects.get(course_id=self.course_key)
|
||||
self.assertEqual(enrollment.user.username, "test")
|
||||
|
||||
def test_double_enrollment(self):
|
||||
|
||||
# Create a user and enroll in a course
|
||||
course_id = "edX/Test101/2014_Spring"
|
||||
self._auto_auth(username='test', course_id=course_id)
|
||||
self._auto_auth(username='test', course_id=self.course_id)
|
||||
|
||||
# Make the same call again, re-enrolling the student in the same course
|
||||
self._auto_auth(username='test', course_id=course_id)
|
||||
self._auto_auth(username='test', course_id=self.course_id)
|
||||
|
||||
# Check that only one course enrollment was created for the user
|
||||
self.assertEqual(CourseEnrollment.objects.count(), 1)
|
||||
enrollment = CourseEnrollment.objects.get(course_id=course_id)
|
||||
enrollment = CourseEnrollment.objects.get(course_id=self.course_key)
|
||||
self.assertEqual(enrollment.user.username, "test")
|
||||
|
||||
def test_set_roles(self):
|
||||
|
||||
course_id = "edX/Test101/2014_Spring"
|
||||
seed_permissions_roles(course_id)
|
||||
course_roles = dict((r.name, r) for r in Role.objects.filter(course_id=course_id))
|
||||
seed_permissions_roles(self.course_key)
|
||||
course_roles = dict((r.name, r) for r in Role.objects.filter(course_id=self.course_key))
|
||||
self.assertEqual(len(course_roles), 4) # sanity check
|
||||
|
||||
# Student role is assigned by default on course enrollment.
|
||||
self._auto_auth(username='a_student', course_id=course_id)
|
||||
self._auto_auth(username='a_student', course_id=self.course_id)
|
||||
user = User.objects.get(username='a_student')
|
||||
user_roles = user.roles.all()
|
||||
self.assertEqual(len(user_roles), 1)
|
||||
self.assertEqual(user_roles[0], course_roles[FORUM_ROLE_STUDENT])
|
||||
|
||||
self._auto_auth(username='a_moderator', course_id=course_id, roles='Moderator')
|
||||
self._auto_auth(username='a_moderator', course_id=self.course_id, roles='Moderator')
|
||||
user = User.objects.get(username='a_moderator')
|
||||
user_roles = user.roles.all()
|
||||
self.assertEqual(
|
||||
@@ -128,7 +127,7 @@ class AutoAuthEnabledTestCase(UrlResetMixin, TestCase):
|
||||
course_roles[FORUM_ROLE_MODERATOR]]))
|
||||
|
||||
# check multiple roles work.
|
||||
self._auto_auth(username='an_admin', course_id=course_id,
|
||||
self._auto_auth(username='an_admin', course_id=self.course_id,
|
||||
roles='{},{}'.format(FORUM_ROLE_MODERATOR, FORUM_ROLE_ADMINISTRATOR))
|
||||
user = User.objects.get(username='an_admin')
|
||||
user_roles = user.roles.all()
|
||||
|
||||
@@ -15,6 +15,7 @@ from student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
|
||||
from xmodule.modulestore.locations import SlashSeparatedCourseKey
|
||||
|
||||
from bulk_email.models import CourseAuthorization
|
||||
|
||||
@@ -100,7 +101,10 @@ class TestStudentDashboardEmailViewXMLBacked(ModuleStoreTestCase):
|
||||
|
||||
# Create student account
|
||||
student = UserFactory.create()
|
||||
CourseEnrollmentFactory.create(user=student, course_id=self.course_name)
|
||||
CourseEnrollmentFactory.create(
|
||||
user=student,
|
||||
course_id=SlashSeparatedCourseKey.from_deprecated_string(self.course_name)
|
||||
)
|
||||
self.client.login(username=student.username, password="test")
|
||||
|
||||
try:
|
||||
|
||||
@@ -20,6 +20,7 @@ from xmodule.modulestore.inheritance import own_metadata
|
||||
from xmodule.modulestore.django import editable_modulestore
|
||||
|
||||
from external_auth.models import ExternalAuthMap
|
||||
from xmodule.modulestore.locations import SlashSeparatedCourseKey
|
||||
|
||||
TEST_DATA_MIXED_MODULESTORE = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {})
|
||||
|
||||
@@ -275,7 +276,10 @@ class UtilFnTest(TestCase):
|
||||
COURSE_ID = u'org/num/run' # pylint: disable=C0103
|
||||
COURSE_URL = u'/courses/{}/otherstuff'.format(COURSE_ID) # pylint: disable=C0103
|
||||
NON_COURSE_URL = u'/blahblah' # pylint: disable=C0103
|
||||
self.assertEqual(_parse_course_id_from_string(COURSE_URL), COURSE_ID)
|
||||
self.assertEqual(
|
||||
_parse_course_id_from_string(COURSE_URL),
|
||||
SlashSeparatedCourseKey.from_deprecated_string(COURSE_ID)
|
||||
)
|
||||
self.assertIsNone(_parse_course_id_from_string(NON_COURSE_URL))
|
||||
|
||||
|
||||
@@ -320,7 +324,7 @@ class ExternalAuthShibTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests the _get_course_enrollment_domain utility function
|
||||
"""
|
||||
self.assertIsNone(_get_course_enrollment_domain("I/DONT/EXIST"))
|
||||
self.assertIsNone(_get_course_enrollment_domain(SlashSeparatedCourseKey("I", "DONT", "EXIST")))
|
||||
self.assertIsNone(_get_course_enrollment_domain(self.course.id))
|
||||
self.assertEqual(self.shib_course.enrollment_domain, _get_course_enrollment_domain(self.shib_course.id))
|
||||
|
||||
@@ -340,7 +344,7 @@ class ExternalAuthShibTest(ModuleStoreTestCase):
|
||||
Tests the redirects when visiting course-specific URL with @login_required.
|
||||
Should vary by course depending on its enrollment_domain
|
||||
"""
|
||||
TARGET_URL = reverse('courseware', args=[self.course.id]) # pylint: disable=C0103
|
||||
TARGET_URL = reverse('courseware', args=[self.course.id.to_deprecated_string()]) # pylint: disable=C0103
|
||||
noshib_response = self.client.get(TARGET_URL, follow=True)
|
||||
self.assertEqual(noshib_response.redirect_chain[-1],
|
||||
('http://testserver/accounts/login?next={url}'.format(url=TARGET_URL), 302))
|
||||
@@ -348,7 +352,7 @@ class ExternalAuthShibTest(ModuleStoreTestCase):
|
||||
.format(platform_name=settings.PLATFORM_NAME)))
|
||||
self.assertEqual(noshib_response.status_code, 200)
|
||||
|
||||
TARGET_URL_SHIB = reverse('courseware', args=[self.shib_course.id]) # pylint: disable=C0103
|
||||
TARGET_URL_SHIB = reverse('courseware', args=[self.shib_course.id.to_deprecated_string()]) # pylint: disable=C0103
|
||||
shib_response = self.client.get(**{'path': TARGET_URL_SHIB,
|
||||
'follow': True,
|
||||
'REMOTE_USER': self.extauth.external_id,
|
||||
|
||||
@@ -53,7 +53,7 @@ class UserStandingTest(TestCase):
|
||||
try:
|
||||
self.some_url = reverse('dashboard')
|
||||
except NoReverseMatch:
|
||||
self.some_url = '/course'
|
||||
self.some_url = '/course/'
|
||||
|
||||
# since it's only possible to disable accounts from lms, we're going
|
||||
# to skip tests for cms
|
||||
|
||||
@@ -55,6 +55,7 @@ from dark_lang.models import DarkLangConfig
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.locations import SlashSeparatedCourseKey
|
||||
from xmodule.modulestore import XML_MODULESTORE_TYPE, Location
|
||||
|
||||
from collections import namedtuple
|
||||
@@ -132,8 +133,7 @@ def index(request, extra_context={}, user=AnonymousUser()):
|
||||
|
||||
def course_from_id(course_id):
|
||||
"""Return the CourseDescriptor corresponding to this course_id"""
|
||||
course_loc = CourseDescriptor.id_to_location(course_id)
|
||||
return modulestore().get_instance(course_id, course_loc)
|
||||
return modulestore().get_course(course_id)
|
||||
|
||||
day_pattern = re.compile(r'\s\d+,\s')
|
||||
multimonth_pattern = re.compile(r'\s?\-\s?\S+\s')
|
||||
@@ -269,8 +269,8 @@ def get_course_enrollment_pairs(user, course_org_filter, org_filter_out_set):
|
||||
a student's dashboard.
|
||||
"""
|
||||
for enrollment in CourseEnrollment.enrollments_for_user(user):
|
||||
try:
|
||||
course = course_from_id(enrollment.course_id)
|
||||
course = course_from_id(enrollment.course_id)
|
||||
if course:
|
||||
|
||||
# if we are in a Microsite, then filter out anything that is not
|
||||
# attributed (by ORG) to that Microsite
|
||||
@@ -282,7 +282,7 @@ def get_course_enrollment_pairs(user, course_org_filter, org_filter_out_set):
|
||||
continue
|
||||
|
||||
yield (course, enrollment)
|
||||
except ItemNotFoundError:
|
||||
else:
|
||||
log.error("User {0} enrolled in non-existent course {1}"
|
||||
.format(user.username, enrollment.course_id))
|
||||
|
||||
@@ -478,13 +478,13 @@ def dashboard(request):
|
||||
# Global staff can see what courses errored on their dashboard
|
||||
staff_access = False
|
||||
errored_courses = {}
|
||||
if has_access(user, 'global', 'staff'):
|
||||
if has_access(user, 'staff', 'global'):
|
||||
# Show any courses that errored on load
|
||||
staff_access = True
|
||||
errored_courses = modulestore().get_errored_courses()
|
||||
|
||||
show_courseware_links_for = frozenset(course.id for course, _enrollment in course_enrollment_pairs
|
||||
if has_access(request.user, course, 'load'))
|
||||
if has_access(request.user, 'load', course))
|
||||
|
||||
course_modes = {course.id: complete_course_mode_info(course.id, enrollment) for course, enrollment in course_enrollment_pairs}
|
||||
cert_statuses = {course.id: cert_info(request.user, course) for course, _enrollment in course_enrollment_pairs}
|
||||
@@ -617,10 +617,11 @@ def change_enrollment(request):
|
||||
user = request.user
|
||||
|
||||
action = request.POST.get("enrollment_action")
|
||||
course_id = request.POST.get("course_id")
|
||||
if course_id is None:
|
||||
if 'course_id' not in request.POST:
|
||||
return HttpResponseBadRequest(_("Course id not specified"))
|
||||
|
||||
course_id = SlashSeparatedCourseKey.from_deprecated_string(request.POST.get("course_id"))
|
||||
|
||||
if not user.is_authenticated():
|
||||
return HttpResponseForbidden()
|
||||
|
||||
@@ -634,7 +635,7 @@ def change_enrollment(request):
|
||||
.format(user.username, course_id))
|
||||
return HttpResponseBadRequest(_("Course id is invalid"))
|
||||
|
||||
if not has_access(user, course, 'enroll'):
|
||||
if not has_access(user, 'enroll', course):
|
||||
return HttpResponseBadRequest(_("Enrollment is closed"))
|
||||
|
||||
# see if we have already filled up all allowed enrollments
|
||||
@@ -648,7 +649,7 @@ def change_enrollment(request):
|
||||
available_modes = CourseMode.modes_for_course(course_id)
|
||||
if len(available_modes) > 1:
|
||||
return HttpResponse(
|
||||
reverse("course_modes_choose", kwargs={'course_id': course_id})
|
||||
reverse("course_modes_choose", kwargs={'course_id': course_id.to_deprecated_string()})
|
||||
)
|
||||
|
||||
current_mode = available_modes[0]
|
||||
@@ -664,7 +665,7 @@ def change_enrollment(request):
|
||||
# the user to the shopping cart page always, where they can reasonably discern the status of their cart,
|
||||
# whether things got added, etc
|
||||
|
||||
shoppingcart.views.add_course_to_cart(request, course_id)
|
||||
shoppingcart.views.add_course_to_cart(request, course_id.to_deprecated_string())
|
||||
return HttpResponse(
|
||||
reverse("shoppingcart.views.show_cart")
|
||||
)
|
||||
@@ -686,7 +687,7 @@ def _parse_course_id_from_string(input_str):
|
||||
"""
|
||||
m_obj = re.match(r'^/courses/(?P<course_id>[^/]+/[^/]+/[^/]+)', input_str)
|
||||
if m_obj:
|
||||
return m_obj.group('course_id')
|
||||
return SlashSeparatedCourseKey.from_deprecated_string(m_obj.group('course_id'))
|
||||
return None
|
||||
|
||||
|
||||
@@ -696,12 +697,12 @@ def _get_course_enrollment_domain(course_id):
|
||||
@param course_id:
|
||||
@return:
|
||||
"""
|
||||
try:
|
||||
course = course_from_id(course_id)
|
||||
return course.enrollment_domain
|
||||
except ItemNotFoundError:
|
||||
course = course_from_id(course_id)
|
||||
if course is None:
|
||||
return None
|
||||
|
||||
return course.enrollment_domain
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def accounts_login(request):
|
||||
@@ -1378,6 +1379,9 @@ def auto_auth(request):
|
||||
full_name = request.GET.get('full_name', username)
|
||||
is_staff = request.GET.get('staff', None)
|
||||
course_id = request.GET.get('course_id', None)
|
||||
course_key = None
|
||||
if course_id:
|
||||
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||
role_names = [v.strip() for v in request.GET.get('roles', '').split(',') if v.strip()]
|
||||
|
||||
# Get or create the user object
|
||||
@@ -1413,12 +1417,12 @@ def auto_auth(request):
|
||||
reg.save()
|
||||
|
||||
# Enroll the user in a course
|
||||
if course_id is not None:
|
||||
CourseEnrollment.enroll(user, course_id)
|
||||
if course_key is not None:
|
||||
CourseEnrollment.enroll(user, course_key)
|
||||
|
||||
# Apply the roles
|
||||
for role_name in role_names:
|
||||
role = Role.objects.get(name=role_name, course_id=course_id)
|
||||
role = Role.objects.get(name=role_name, course_id=course_key)
|
||||
user.roles.add(role)
|
||||
|
||||
# Log in as the user
|
||||
@@ -1865,15 +1869,16 @@ def change_email_settings(request):
|
||||
user = request.user
|
||||
|
||||
course_id = request.POST.get("course_id")
|
||||
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||
receive_emails = request.POST.get("receive_emails")
|
||||
if receive_emails:
|
||||
optout_object = Optout.objects.filter(user=user, course_id=course_id)
|
||||
optout_object = Optout.objects.filter(user=user, course_id=course_key)
|
||||
if optout_object:
|
||||
optout_object.delete()
|
||||
log.info(u"User {0} ({1}) opted in to receive emails from course {2}".format(user.username, user.email, course_id))
|
||||
track.views.server_track(request, "change-email-settings", {"receive_emails": "yes", "course": course_id}, page='dashboard')
|
||||
else:
|
||||
Optout.objects.get_or_create(user=user, course_id=course_id)
|
||||
Optout.objects.get_or_create(user=user, course_id=course_key)
|
||||
log.info(u"User {0} ({1}) opted out of receiving emails from course {2}".format(user.username, user.email, course_id))
|
||||
track.views.server_track(request, "change-email-settings", {"receive_emails": "no", "course": course_id}, page='dashboard')
|
||||
|
||||
@@ -1889,7 +1894,7 @@ def token(request):
|
||||
the token was issued. This will be stored with the user along with
|
||||
the id for identification purposes in the backend.
|
||||
'''
|
||||
course_id = request.GET.get("course_id")
|
||||
course_id = SlashSeparatedCourseKey.from_deprecated_string(request.GET.get("course_id"))
|
||||
course = course_from_id(course_id)
|
||||
dtnow = datetime.datetime.now()
|
||||
dtutcnow = datetime.datetime.utcnow()
|
||||
|
||||
Reference in New Issue
Block a user