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:
Calen Pennington
2014-04-30 10:17:43 -04:00
parent 7852906ce0
commit e2bfcf2a36
42 changed files with 603 additions and 330 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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()

View File

@@ -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']

View File

@@ -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)

View File

@@ -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()

View File

@@ -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:

View File

@@ -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,

View File

@@ -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

View File

@@ -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()