Commit 9811926d by Calen Pennington

Make course ids and usage ids opaque to LMS and Studio [partial commit]

This commit updates lms/djangoapps/courseware.

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]
parent 7852906c
...@@ -43,7 +43,7 @@ def log_in(username='robot', password='test', email='robot@edx.org', name="Robot ...@@ -43,7 +43,7 @@ def log_in(username='robot', password='test', email='robot@edx.org', name="Robot
@world.absorb @world.absorb
def register_by_course_id(course_id, username='robot', password='test', is_staff=False): def register_by_course_key(course_key, username='robot', password='test', is_staff=False):
create_user(username, password) create_user(username, password)
user = User.objects.get(username=username) user = User.objects.get(username=username)
# Note: this flag makes the user global staff - that is, an edX employee - not a course staff. # Note: this flag makes the user global staff - that is, an edX employee - not a course staff.
...@@ -51,17 +51,17 @@ def register_by_course_id(course_id, username='robot', password='test', is_staff ...@@ -51,17 +51,17 @@ def register_by_course_id(course_id, username='robot', password='test', is_staff
if is_staff: if is_staff:
user.is_staff = True user.is_staff = True
user.save() user.save()
CourseEnrollment.enroll(user, course_id) CourseEnrollment.enroll(user, course_key)
@world.absorb @world.absorb
def enroll_user(user, course_id): def enroll_user(user, course_key):
# Activate user # Activate user
registration = world.RegistrationFactory(user=user) registration = world.RegistrationFactory(user=user)
registration.register(user) registration.register(user)
registration.activate() registration.activate()
# Enroll them in the course # Enroll them in the course
CourseEnrollment.enroll(user, course_id) CourseEnrollment.enroll(user, course_key)
@world.absorb @world.absorb
......
...@@ -21,6 +21,8 @@ from .course_helpers import * ...@@ -21,6 +21,8 @@ from .course_helpers import *
from .ui_helpers import * from .ui_helpers import *
from nose.tools import assert_equals # pylint: disable=E0611 from nose.tools import assert_equals # pylint: disable=E0611
from xmodule.modulestore.locations import SlashSeparatedCourseKey
from logging import getLogger from logging import getLogger
logger = getLogger(__name__) logger = getLogger(__name__)
...@@ -110,7 +112,8 @@ def i_am_not_logged_in(step): ...@@ -110,7 +112,8 @@ def i_am_not_logged_in(step):
@step('I am staff for course "([^"]*)"$') @step('I am staff for course "([^"]*)"$')
def i_am_staff_for_course_by_id(step, course_id): def i_am_staff_for_course_by_id(step, course_id):
world.register_by_course_id(course_id, True) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
world.register_by_course_key(course_key, True)
@step(r'click (?:the|a) link (?:called|with the text) "([^"]*)"$') @step(r'click (?:the|a) link (?:called|with the text) "([^"]*)"$')
......
...@@ -34,7 +34,7 @@ def debug(*args, **kwargs): ...@@ -34,7 +34,7 @@ def debug(*args, **kwargs):
log.debug(*args, **kwargs) log.debug(*args, **kwargs)
def has_access(user, obj, action, course_context=None): def has_access(user, action, obj, course_key=None):
""" """
Check whether a user has the access to do action on obj. Handles any magic Check whether a user has the access to do action on obj. Handles any magic
switching based on various settings. switching based on various settings.
...@@ -55,7 +55,7 @@ def has_access(user, obj, action, course_context=None): ...@@ -55,7 +55,7 @@ def has_access(user, obj, action, course_context=None):
actions depend on the obj type, but include e.g. 'enroll' for courses. See the actions depend on the obj type, but include e.g. 'enroll' for courses. See the
type-specific functions below for the known actions for that type. type-specific functions below for the known actions for that type.
course_context: A course_id specifying which course run this access is for. course_key: A course_key specifying which course run this access is for.
Required when accessing anything other than a CourseDescriptor, 'global', Required when accessing anything other than a CourseDescriptor, 'global',
or a location with category 'course' or a location with category 'course'
...@@ -69,23 +69,23 @@ def has_access(user, obj, action, course_context=None): ...@@ -69,23 +69,23 @@ def has_access(user, obj, action, course_context=None):
# delegate the work to type-specific functions. # delegate the work to type-specific functions.
# (start with more specific types, then get more general) # (start with more specific types, then get more general)
if isinstance(obj, CourseDescriptor): if isinstance(obj, CourseDescriptor):
return _has_access_course_desc(user, obj, action) return _has_access_course_desc(user, action, obj)
if isinstance(obj, ErrorDescriptor): if isinstance(obj, ErrorDescriptor):
return _has_access_error_desc(user, obj, action, course_context) return _has_access_error_desc(user, action, obj, course_key)
if isinstance(obj, XModule): if isinstance(obj, XModule):
return _has_access_xmodule(user, obj, action, course_context) return _has_access_xmodule(user, action, obj, course_key)
# NOTE: any descriptor access checkers need to go above this # NOTE: any descriptor access checkers need to go above this
if isinstance(obj, XBlock): if isinstance(obj, XBlock):
return _has_access_descriptor(user, obj, action, course_context) return _has_access_descriptor(user, action, obj, course_key)
if isinstance(obj, Location): if isinstance(obj, Location):
return _has_access_location(user, obj, action, course_context) return _has_access_location(user, action, obj, course_key)
if isinstance(obj, basestring): if isinstance(obj, basestring):
return _has_access_string(user, obj, action, course_context) return _has_access_string(user, action, obj, course_key)
# Passing an unknown object here is a coding error, so rather than # Passing an unknown object here is a coding error, so rather than
# returning a default, complain. # returning a default, complain.
...@@ -94,7 +94,7 @@ def has_access(user, obj, action, course_context=None): ...@@ -94,7 +94,7 @@ def has_access(user, obj, action, course_context=None):
# ================ Implementation helpers ================================ # ================ Implementation helpers ================================
def _has_access_course_desc(user, course, action): def _has_access_course_desc(user, action, course):
""" """
Check if user has access to a course descriptor. Check if user has access to a course descriptor.
...@@ -114,16 +114,19 @@ def _has_access_course_desc(user, course, action): ...@@ -114,16 +114,19 @@ def _has_access_course_desc(user, course, action):
NOTE: this is not checking whether user is actually enrolled in the course. NOTE: this is not checking whether user is actually enrolled in the course.
""" """
# delegate to generic descriptor check to check start dates # delegate to generic descriptor check to check start dates
return _has_access_descriptor(user, course, 'load') return _has_access_descriptor(user, 'load', course, course.id)
def can_load_forum(): def can_load_forum():
""" """
Can this user access the forums in this course? Can this user access the forums in this course?
""" """
return (can_load() and \ return (
(CourseEnrollment.is_enrolled(user, course.id) or \ can_load() and
_has_staff_access_to_descriptor(user, course) (
)) CourseEnrollment.is_enrolled(user, course.id) or
_has_staff_access_to_descriptor(user, course, course.id)
)
)
def can_enroll(): def can_enroll():
""" """
...@@ -158,13 +161,16 @@ def _has_access_course_desc(user, course, action): ...@@ -158,13 +161,16 @@ def _has_access_course_desc(user, course, action):
debug("Allow: in enrollment period") debug("Allow: in enrollment period")
return True return True
# if user is in CourseEnrollmentAllowed with right course_id then can also enroll # if user is in CourseEnrollmentAllowed with right course key then can also enroll
# (note that course.id actually points to a CourseKey)
# (the filter call uses course_id= since that's the legacy database schema)
# (sorry that it's confusing :( )
if user is not None and user.is_authenticated() and CourseEnrollmentAllowed: if user is not None and user.is_authenticated() and CourseEnrollmentAllowed:
if CourseEnrollmentAllowed.objects.filter(email=user.email, course_id=course.id): if CourseEnrollmentAllowed.objects.filter(email=user.email, course_id=course.id):
return True return True
# otherwise, need staff access # otherwise, need staff access
return _has_staff_access_to_descriptor(user, course) return _has_staff_access_to_descriptor(user, course, course.id)
def see_exists(): def see_exists():
""" """
...@@ -184,7 +190,7 @@ def _has_access_course_desc(user, course, action): ...@@ -184,7 +190,7 @@ def _has_access_course_desc(user, course, action):
if course.ispublic: if course.ispublic:
debug("Allow: ACCESS_REQUIRE_STAFF_FOR_COURSE and ispublic") debug("Allow: ACCESS_REQUIRE_STAFF_FOR_COURSE and ispublic")
return True return True
return _has_staff_access_to_descriptor(user, course) return _has_staff_access_to_descriptor(user, course, course.id)
return can_enroll() or can_load() return can_enroll() or can_load()
...@@ -193,14 +199,14 @@ def _has_access_course_desc(user, course, action): ...@@ -193,14 +199,14 @@ def _has_access_course_desc(user, course, action):
'load_forum': can_load_forum, 'load_forum': can_load_forum,
'enroll': can_enroll, 'enroll': can_enroll,
'see_exists': see_exists, 'see_exists': see_exists,
'staff': lambda: _has_staff_access_to_descriptor(user, course), 'staff': lambda: _has_staff_access_to_descriptor(user, course, course.id),
'instructor': lambda: _has_instructor_access_to_descriptor(user, course), 'instructor': lambda: _has_instructor_access_to_descriptor(user, course, course.id),
} }
return _dispatch(checkers, action, user, course) return _dispatch(checkers, action, user, course)
def _has_access_error_desc(user, descriptor, action, course_context): def _has_access_error_desc(user, action, descriptor, course_key):
""" """
Only staff should see error descriptors. Only staff should see error descriptors.
...@@ -209,7 +215,7 @@ def _has_access_error_desc(user, descriptor, action, course_context): ...@@ -209,7 +215,7 @@ def _has_access_error_desc(user, descriptor, action, course_context):
'staff' -- staff access to descriptor. 'staff' -- staff access to descriptor.
""" """
def check_for_staff(): def check_for_staff():
return _has_staff_access_to_descriptor(user, descriptor, course_context) return _has_staff_access_to_descriptor(user, descriptor, course_key)
checkers = { checkers = {
'load': check_for_staff, 'load': check_for_staff,
...@@ -219,7 +225,7 @@ def _has_access_error_desc(user, descriptor, action, course_context): ...@@ -219,7 +225,7 @@ def _has_access_error_desc(user, descriptor, action, course_context):
return _dispatch(checkers, action, user, descriptor) return _dispatch(checkers, action, user, descriptor)
def _has_access_descriptor(user, descriptor, action, course_context=None): def _has_access_descriptor(user, action, descriptor, course_key=None):
""" """
Check if user has access to this descriptor. Check if user has access to this descriptor.
...@@ -249,14 +255,14 @@ def _has_access_descriptor(user, descriptor, action, course_context=None): ...@@ -249,14 +255,14 @@ def _has_access_descriptor(user, descriptor, action, course_context=None):
effective_start = _adjust_start_date_for_beta_testers( effective_start = _adjust_start_date_for_beta_testers(
user, user,
descriptor, descriptor,
course_context=course_context course_key=course_key
) )
if now > effective_start: if now > effective_start:
# after start date, everyone can see it # after start date, everyone can see it
debug("Allow: now > effective start date") debug("Allow: now > effective start date")
return True return True
# otherwise, need staff access # otherwise, need staff access
return _has_staff_access_to_descriptor(user, descriptor, course_context) return _has_staff_access_to_descriptor(user, descriptor, course_key)
# No start date, so can always load. # No start date, so can always load.
debug("Allow: no start date") debug("Allow: no start date")
...@@ -264,13 +270,13 @@ def _has_access_descriptor(user, descriptor, action, course_context=None): ...@@ -264,13 +270,13 @@ def _has_access_descriptor(user, descriptor, action, course_context=None):
checkers = { checkers = {
'load': can_load, 'load': can_load,
'staff': lambda: _has_staff_access_to_descriptor(user, descriptor, course_context) 'staff': lambda: _has_staff_access_to_descriptor(user, descriptor, course_key)
} }
return _dispatch(checkers, action, user, descriptor) return _dispatch(checkers, action, user, descriptor)
def _has_access_xmodule(user, xmodule, action, course_context): def _has_access_xmodule(user, action, xmodule, course_key):
""" """
Check if user has access to this xmodule. Check if user has access to this xmodule.
...@@ -278,10 +284,10 @@ def _has_access_xmodule(user, xmodule, action, course_context): ...@@ -278,10 +284,10 @@ def _has_access_xmodule(user, xmodule, action, course_context):
- same as the valid actions for xmodule.descriptor - same as the valid actions for xmodule.descriptor
""" """
# Delegate to the descriptor # Delegate to the descriptor
return has_access(user, xmodule.descriptor, action, course_context) return has_access(user, action, xmodule.descriptor, course_key)
def _has_access_location(user, location, action, course_context): def _has_access_location(user, action, location, course_key):
""" """
Check if user has access to this location. Check if user has access to this location.
...@@ -295,13 +301,13 @@ def _has_access_location(user, location, action, course_context): ...@@ -295,13 +301,13 @@ def _has_access_location(user, location, action, course_context):
And in general, prefer checking access on loaded items, rather than locations. And in general, prefer checking access on loaded items, rather than locations.
""" """
checkers = { checkers = {
'staff': lambda: _has_staff_access_to_location(user, location, course_context) 'staff': lambda: _has_staff_access_to_location(user, location, course_key)
} }
return _dispatch(checkers, action, user, location) return _dispatch(checkers, action, user, location)
def _has_access_string(user, perm, action, course_context): def _has_access_string(user, action, perm, course_key):
""" """
Check if user has certain special access, specified as string. Valid strings: Check if user has certain special access, specified as string. Valid strings:
...@@ -338,7 +344,7 @@ def _dispatch(table, action, user, obj): ...@@ -338,7 +344,7 @@ def _dispatch(table, action, user, obj):
debug("%s user %s, object %s, action %s", debug("%s user %s, object %s, action %s",
'ALLOWED' if result else 'DENIED', 'ALLOWED' if result else 'DENIED',
user, user,
obj.location.url() if isinstance(obj, XBlock) else str(obj)[:60], obj.location.to_deprecated_string() if isinstance(obj, XBlock) else str(obj),
action) action)
return result return result
...@@ -346,7 +352,7 @@ def _dispatch(table, action, user, obj): ...@@ -346,7 +352,7 @@ def _dispatch(table, action, user, obj):
type(obj), action)) type(obj), action))
def _adjust_start_date_for_beta_testers(user, descriptor, course_context=None): def _adjust_start_date_for_beta_testers(user, descriptor, course_key=None): # pylint: disable=invalid-name
""" """
If user is in a beta test group, adjust the start date by the appropriate number of If user is in a beta test group, adjust the start date by the appropriate number of
days. days.
...@@ -373,7 +379,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor, course_context=None): ...@@ -373,7 +379,7 @@ def _adjust_start_date_for_beta_testers(user, descriptor, course_context=None):
# bail early if no beta testing is set up # bail early if no beta testing is set up
return descriptor.start return descriptor.start
if CourseBetaTesterRole(descriptor.location, course_context=course_context).has_user(user): if CourseBetaTesterRole(course_key).has_user(user):
debug("Adjust start time: user in beta role for %s", descriptor) debug("Adjust start time: user in beta role for %s", descriptor)
delta = timedelta(descriptor.days_early_for_beta) delta = timedelta(descriptor.days_early_for_beta)
effective = descriptor.start - delta effective = descriptor.start - delta
...@@ -382,15 +388,15 @@ def _adjust_start_date_for_beta_testers(user, descriptor, course_context=None): ...@@ -382,15 +388,15 @@ def _adjust_start_date_for_beta_testers(user, descriptor, course_context=None):
return descriptor.start return descriptor.start
def _has_instructor_access_to_location(user, location, course_context=None): def _has_instructor_access_to_location(user, location, course_key=None): # pylint: disable=invalid-name
return _has_access_to_location(user, location, 'instructor', course_context) return _has_access_to_location(user, 'instructor', location, course_key)
def _has_staff_access_to_location(user, location, course_context=None): def _has_staff_access_to_location(user, location, course_key=None):
return _has_access_to_location(user, location, 'staff', course_context) return _has_access_to_location(user, 'staff', location, course_key)
def _has_access_to_location(user, location, access_level, course_context): def _has_access_to_location(user, access_level, location, course_key):
''' '''
Returns True if the given user has access_level (= staff or Returns True if the given user has access_level (= staff or
instructor) access to a location. For now this is equivalent to instructor) access to a location. For now this is equivalent to
...@@ -398,7 +404,7 @@ def _has_access_to_location(user, location, access_level, course_context): ...@@ -398,7 +404,7 @@ def _has_access_to_location(user, location, access_level, course_context):
This means that user is in the staff_* group or instructor_* group, or is an overall admin. This means that user is in the staff_* group or instructor_* group, or is an overall admin.
TODO (vshnayder): this needs to be changed to allow per-course_id permissions, not per-course TODO (vshnayder): this needs to be changed to allow per-course_key permissions, not per-course
(e.g. staff in 2012 is different from 2013, but maybe some people always have access) (e.g. staff in 2012 is different from 2013, but maybe some people always have access)
course is a string: the course field of the location being accessed. course is a string: the course field of the location being accessed.
...@@ -422,8 +428,8 @@ def _has_access_to_location(user, location, access_level, course_context): ...@@ -422,8 +428,8 @@ def _has_access_to_location(user, location, access_level, course_context):
return False return False
staff_access = ( staff_access = (
CourseStaffRole(location, course_context).has_user(user) or CourseStaffRole(course_key).has_user(user) or
OrgStaffRole(location).has_user(user) OrgStaffRole(course_key.org).has_user(user)
) )
if staff_access and access_level == 'staff': if staff_access and access_level == 'staff':
...@@ -431,8 +437,8 @@ def _has_access_to_location(user, location, access_level, course_context): ...@@ -431,8 +437,8 @@ def _has_access_to_location(user, location, access_level, course_context):
return True return True
instructor_access = ( instructor_access = (
CourseInstructorRole(location, course_context).has_user(user) or CourseInstructorRole(course_key).has_user(user) or
OrgInstructorRole(location).has_user(user) OrgInstructorRole(course_key.org).has_user(user)
) )
if instructor_access and access_level in ('staff', 'instructor'): if instructor_access and access_level in ('staff', 'instructor'):
...@@ -443,42 +449,43 @@ def _has_access_to_location(user, location, access_level, course_context): ...@@ -443,42 +449,43 @@ def _has_access_to_location(user, location, access_level, course_context):
return False return False
def _has_staff_access_to_course_id(user, course_id): # TODO Please change this function signature to _has_staff_access_to_course_key at next opportunity!
"""Helper method that takes a course_id instead of a course name""" def _has_staff_access_to_course_id(user, course_key):
loc = CourseDescriptor.id_to_location(course_id) """Helper method that takes a course_key instead of a course name"""
return _has_staff_access_to_location(user, loc, course_id) loc = CourseDescriptor.id_to_location(course_key)
return _has_staff_access_to_location(user, loc, course_key)
def _has_instructor_access_to_descriptor(user, descriptor, course_context=None): def _has_instructor_access_to_descriptor(user, descriptor, course_key): # pylint: disable=invalid-name
"""Helper method that checks whether the user has staff access to """Helper method that checks whether the user has staff access to
the course of the location. the course of the location.
descriptor: something that has a location attribute descriptor: something that has a location attribute
""" """
return _has_instructor_access_to_location(user, descriptor.location, course_context) return _has_instructor_access_to_location(user, descriptor.location, course_key)
def _has_staff_access_to_descriptor(user, descriptor, course_context=None): def _has_staff_access_to_descriptor(user, descriptor, course_key):
"""Helper method that checks whether the user has staff access to """Helper method that checks whether the user has staff access to
the course of the location. the course of the location.
descriptor: something that has a location attribute descriptor: something that has a location attribute
""" """
return _has_staff_access_to_location(user, descriptor.location, course_context) return _has_staff_access_to_location(user, descriptor.location, course_key)
def get_user_role(user, course_id): def get_user_role(user, course_key):
""" """
Return corresponding string if user has staff, instructor or student Return corresponding string if user has staff, instructor or student
course role in LMS. course role in LMS.
""" """
from courseware.courses import get_course from courseware.courses import get_course
course = get_course(course_id) course = get_course(course_key)
if is_masquerading_as_student(user): if is_masquerading_as_student(user):
return 'student' return 'student'
elif has_access(user, course, 'instructor'): elif has_access(user, 'instructor', course):
return 'instructor' return 'instructor'
elif has_access(user, course, 'staff'): elif has_access(user, 'staff', course):
return 'staff' return 'staff'
else: else:
return 'student' return 'student'
...@@ -8,12 +8,13 @@ from django.http import Http404 ...@@ -8,12 +8,13 @@ from django.http import Http404
from django.conf import settings from django.conf import settings
from edxmako.shortcuts import render_to_string from edxmako.shortcuts import render_to_string
from xmodule.course_module import CourseDescriptor from xmodule.modulestore import XML_MODULESTORE_TYPE
from xmodule.modulestore import Location, XML_MODULESTORE_TYPE, MONGO_MODULESTORE_TYPE from xmodule.modulestore.keys import CourseKey
from xmodule.modulestore.django import modulestore, loc_mapper from xmodule.modulestore.django import modulestore
from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import StaticContent
from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError
from static_replace import replace_static_urls from static_replace import replace_static_urls
from xmodule.modulestore import MONGO_MODULESTORE_TYPE
from courseware.access import has_access from courseware.access import has_access
from courseware.model_data import FieldDataCache from courseware.model_data import FieldDataCache
...@@ -49,15 +50,15 @@ def get_course(course_id, depth=0): ...@@ -49,15 +50,15 @@ def get_course(course_id, depth=0):
None means infinite depth. Default is to fetch no children. None means infinite depth. Default is to fetch no children.
""" """
try: try:
course_loc = CourseDescriptor.id_to_location(course_id) return modulestore().get_course(course_id, depth=depth)
return modulestore().get_instance(course_id, course_loc, depth=depth)
except (KeyError, ItemNotFoundError): except (KeyError, ItemNotFoundError):
raise ValueError(u"Course not found: {0}".format(course_id)) raise ValueError(u"Course not found: {0}".format(course_id))
except InvalidLocationError: except InvalidLocationError:
raise ValueError(u"Invalid location: {0}".format(course_id)) raise ValueError(u"Invalid location: {0}".format(course_id))
def get_course_by_id(course_id, depth=0): # TODO please rename this function to get_course_by_key at next opportunity!
def get_course_by_id(course_key, depth=0):
""" """
Given a course id, return the corresponding course descriptor. Given a course id, return the corresponding course descriptor.
...@@ -66,50 +67,55 @@ def get_course_by_id(course_id, depth=0): ...@@ -66,50 +67,55 @@ def get_course_by_id(course_id, depth=0):
depth: The number of levels of children for the modulestore to cache. None means infinite depth depth: The number of levels of children for the modulestore to cache. None means infinite depth
""" """
try: try:
course_loc = CourseDescriptor.id_to_location(course_id) course = modulestore().get_course(course_key, depth=depth)
return modulestore().get_instance(course_id, course_loc, depth=depth) if course:
return course
else:
raise Http404("Course not found.")
except (KeyError, ItemNotFoundError): except (KeyError, ItemNotFoundError):
raise Http404("Course not found.") raise Http404("Course not found.")
except InvalidLocationError: except InvalidLocationError:
raise Http404("Invalid location") raise Http404("Invalid location")
def get_course_with_access(user, course_id, action, depth=0): def get_course_with_access(user, action, course_key, depth=0):
""" """
Given a course_id, look up the corresponding course descriptor, Given a course_key, look up the corresponding course descriptor,
check that the user has the access to perform the specified action check that the user has the access to perform the specified action
on the course, and return the descriptor. on the course, and return the descriptor.
Raises a 404 if the course_id is invalid, or the user doesn't have access. Raises a 404 if the course_key is invalid, or the user doesn't have access.
depth: The number of levels of children for the modulestore to cache. None means infinite depth depth: The number of levels of children for the modulestore to cache. None means infinite depth
""" """
course = get_course_by_id(course_id, depth=depth) assert isinstance(course_key, CourseKey)
if not has_access(user, course, action): course = get_course_by_id(course_key, depth=depth)
if not has_access(user, action, course, course_key):
# Deliberately return a non-specific error message to avoid # Deliberately return a non-specific error message to avoid
# leaking info about access control settings # leaking info about access control settings
raise Http404("Course not found.") raise Http404("Course not found.")
return course return course
def get_opt_course_with_access(user, course_id, action): def get_opt_course_with_access(user, action, course_key):
""" """
Same as get_course_with_access, except that if course_id is None, Same as get_course_with_access, except that if course_key is None,
return None without performing any access checks. return None without performing any access checks.
""" """
if course_id is None: if course_key is None:
return None return None
return get_course_with_access(user, course_id, action) return get_course_with_access(user, action, course_key)
def course_image_url(course): def course_image_url(course):
"""Try to look up the image url for the course. If it's not found, """ Determine whether this is an XML or Studio-backed course, and return the appropriate course_image URL """
log an error and return the dead link""" if course.static_asset_path or modulestore().get_modulestore_type(course.id) == XML_MODULESTORE_TYPE:
if course.static_asset_path or modulestore().get_modulestore_type(course.location.course_id) == XML_MODULESTORE_TYPE:
return '/static/' + (course.static_asset_path or getattr(course, 'data_dir', '')) + "/images/course_image.jpg" return '/static/' + (course.static_asset_path or getattr(course, 'data_dir', '')) + "/images/course_image.jpg"
else: else:
loc = StaticContent.compute_location(course.location.org, course.location.course, course.course_image) loc = StaticContent.compute_location(course.location.course_key, course.course_image)
_path = StaticContent.get_url_path_from_location(loc) _path = loc.to_deprecated_string()
return _path return _path
...@@ -158,7 +164,7 @@ def get_course_about_section(course, section_key): ...@@ -158,7 +164,7 @@ def get_course_about_section(course, section_key):
# markup. This can change without effecting this interface when we find a # markup. This can change without effecting this interface when we find a
# good format for defining so many snippets of text/html. # good format for defining so many snippets of text/html.
# TODO: Remove number, instructors from this list # TODO: Remove number, instructors from this list
if section_key in ['short_description', 'description', 'key_dates', 'video', if section_key in ['short_description', 'description', 'key_dates', 'video',
'course_staff_short', 'course_staff_extended', 'course_staff_short', 'course_staff_extended',
'requirements', 'syllabus', 'textbook', 'faq', 'more_info', 'requirements', 'syllabus', 'textbook', 'faq', 'more_info',
...@@ -199,7 +205,7 @@ def get_course_about_section(course, section_key): ...@@ -199,7 +205,7 @@ def get_course_about_section(course, section_key):
except ItemNotFoundError: except ItemNotFoundError:
log.warning( log.warning(
u"Missing about section {key} in course {url}".format(key=section_key, url=course.location.url()) u"Missing about section {key} in course {url}".format(key=section_key, url=course.location.to_deprecated_string())
) )
return None return None
elif section_key == "title": elif section_key == "title":
...@@ -223,14 +229,14 @@ def get_course_info_section(request, course, section_key): ...@@ -223,14 +229,14 @@ def get_course_info_section(request, course, section_key):
- updates - updates
- guest_updates - guest_updates
""" """
loc = Location(course.location.tag, course.location.org, course.location.course, 'course_info', section_key) usage_key = course.id.make_usage_key('course_info', section_key)
# Use an empty cache # Use an empty cache
field_data_cache = FieldDataCache([], course.id, request.user) field_data_cache = FieldDataCache([], course.id, request.user)
info_module = get_module( info_module = get_module(
request.user, request.user,
request, request,
loc, usage_key,
field_data_cache, field_data_cache,
course.id, course.id,
wrap_xmodule_display=False, wrap_xmodule_display=False,
...@@ -279,12 +285,12 @@ def get_course_syllabus_section(course, section_key): ...@@ -279,12 +285,12 @@ def get_course_syllabus_section(course, section_key):
return replace_static_urls( return replace_static_urls(
html_file.read().decode('utf-8'), html_file.read().decode('utf-8'),
getattr(course, 'data_dir', None), getattr(course, 'data_dir', None),
course_id=course.location.course_id, course_id=course.id,
static_asset_path=course.static_asset_path, static_asset_path=course.static_asset_path,
) )
except ResourceNotFoundError: except ResourceNotFoundError:
log.exception( log.exception(
u"Missing syllabus section {key} in course {url}".format(key=section_key, url=course.location.url()) u"Missing syllabus section {key} in course {url}".format(key=section_key, url=course.location.to_deprecated_string())
) )
return "! Syllabus missing !" return "! Syllabus missing !"
...@@ -312,7 +318,7 @@ def get_courses(user, domain=None): ...@@ -312,7 +318,7 @@ def get_courses(user, domain=None):
Returns a list of courses available, sorted by course.number Returns a list of courses available, sorted by course.number
''' '''
courses = branding.get_visible_courses() courses = branding.get_visible_courses()
courses = [c for c in courses if has_access(user, c, 'see_exists')] courses = [c for c in courses if has_access(user, 'see_exists', c)]
courses = sorted(courses, key=lambda course: course.number) courses = sorted(courses, key=lambda course: course.number)
...@@ -332,15 +338,14 @@ def sort_by_announcement(courses): ...@@ -332,15 +338,14 @@ def sort_by_announcement(courses):
return courses return courses
def get_cms_course_link(course): def get_cms_course_link(course, page='course'):
""" """
Returns a link to course_index for editing the course in cms, Returns a link to course_index for editing the course in cms,
assuming that the course is actually cms-backed. assuming that the course is actually cms-backed.
""" """
locator = loc_mapper().translate_location( # This is fragile, but unfortunately the problem is that within the LMS we
course.location.course_id, course.location, False, True # can't use the reverse calls from the CMS
) return u"//{}/{}/{}".format(settings.CMS_BASE, page, unicode(course.id))
return "//" + settings.CMS_BASE + locator.url_reverse('course/', '')
def get_cms_block_link(block, page): def get_cms_block_link(block, page):
...@@ -348,20 +353,20 @@ def get_cms_block_link(block, page): ...@@ -348,20 +353,20 @@ def get_cms_block_link(block, page):
Returns a link to block_index for editing the course in cms, Returns a link to block_index for editing the course in cms,
assuming that the block is actually cms-backed. assuming that the block is actually cms-backed.
""" """
locator = loc_mapper().translate_location( # This is fragile, but unfortunately the problem is that within the LMS we
block.location.course_id, block.location, False, True # can't use the reverse calls from the CMS
) return u"//{}/{}/{}".format(settings.CMS_BASE, page, block.location)
return "//" + settings.CMS_BASE + locator.url_reverse(page, '')
def get_studio_url(course_id, page): def get_studio_url(course_key, page):
""" """
Get the Studio URL of the page that is passed in. Get the Studio URL of the page that is passed in.
""" """
course = get_course_by_id(course_id) assert(isinstance(course_key, CourseKey))
course = get_course_by_id(course_key)
is_studio_course = course.course_edit_method == "Studio" is_studio_course = course.course_edit_method == "Studio"
is_mongo_course = modulestore().get_modulestore_type(course_id) == MONGO_MODULESTORE_TYPE is_mongo_course = modulestore().get_modulestore_type(course_key) == MONGO_MODULESTORE_TYPE
studio_link = None studio_link = None
if is_studio_course and is_mongo_course: if is_studio_course and is_mongo_course:
studio_link = get_cms_block_link(course, page) studio_link = get_cms_course_link(course, page)
return studio_link return studio_link
...@@ -140,7 +140,7 @@ class AnnotatableSteps(object): ...@@ -140,7 +140,7 @@ class AnnotatableSteps(object):
def active_problem_selector(self, subselector): def active_problem_selector(self, subselector):
return 'div[data-problem-id="{}"] {}'.format( return 'div[data-problem-id="{}"] {}'.format(
world.scenario_dict['PROBLEMS'][self.active_problem].location.url(), world.scenario_dict['PROBLEMS'][self.active_problem].location.to_deprecated_string(),
subselector, subselector,
) )
......
...@@ -12,6 +12,7 @@ from django.core.urlresolvers import reverse ...@@ -12,6 +12,7 @@ from django.core.urlresolvers import reverse
from student.models import CourseEnrollment from student.models import CourseEnrollment
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.locations import SlashSeparatedCourseKey
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from courseware.courses import get_course_by_id from courseware.courses import get_course_by_id
from xmodule import seq_module, vertical_module from xmodule import seq_module, vertical_module
...@@ -119,16 +120,19 @@ def go_into_course(step): ...@@ -119,16 +120,19 @@ def go_into_course(step):
def course_id(course_num): def course_id(course_num):
return "%s/%s/%s" % (world.scenario_dict['COURSE'].org, course_num, return SlashSeparatedCourseKey(
world.scenario_dict['COURSE'].url_name) world.scenario_dict['COURSE'].org,
course_num,
world.scenario_dict['COURSE'].url_name
)
def course_location(course_num): def course_location(course_num):
return world.scenario_dict['COURSE'].location._replace(course=course_num) return world.scenario_dict['COURSE'].location.replace(course=course_num)
def section_location(course_num): def section_location(course_num):
return world.scenario_dict['SECTION'].location._replace(course=course_num) return world.scenario_dict['SECTION'].location.replace(course=course_num)
def visit_scenario_item(item_key): def visit_scenario_item(item_key):
...@@ -140,8 +144,8 @@ def visit_scenario_item(item_key): ...@@ -140,8 +144,8 @@ def visit_scenario_item(item_key):
url = django_url(reverse( url = django_url(reverse(
'jump_to', 'jump_to',
kwargs={ kwargs={
'course_id': world.scenario_dict['COURSE'].id, 'course_id': world.scenario_dict['COURSE'].id.to_deprecated_string(),
'location': str(world.scenario_dict[item_key].location), 'location': world.scenario_dict[item_key].location.to_deprecated_string(),
} }
)) ))
......
...@@ -46,16 +46,16 @@ class ConditionalSteps(object): ...@@ -46,16 +46,16 @@ class ConditionalSteps(object):
metadata = { metadata = {
'xml_attributes': { 'xml_attributes': {
'sources': world.scenario_dict['CONDITION_SOURCE'].location.url() condition: cond_value
} }
} }
metadata['xml_attributes'][condition] = cond_value
world.scenario_dict['CONDITIONAL'] = world.ItemFactory( world.scenario_dict['CONDITIONAL'] = world.ItemFactory(
parent_location=world.scenario_dict['WRAPPER'].location, parent_location=world.scenario_dict['WRAPPER'].location,
category='conditional', category='conditional',
display_name="Test Conditional", display_name="Test Conditional",
metadata=metadata metadata=metadata,
sources_list=[world.scenario_dict['CONDITION_SOURCE'].location],
) )
world.ItemFactory( world.ItemFactory(
......
...@@ -201,26 +201,24 @@ def i_am_registered_for_the_course(coursenum, metadata, user='Instructor'): ...@@ -201,26 +201,24 @@ def i_am_registered_for_the_course(coursenum, metadata, user='Instructor'):
metadata.update({'days_early_for_beta': 5, 'start': tomorrow}) metadata.update({'days_early_for_beta': 5, 'start': tomorrow})
create_course_for_lti(coursenum, metadata) create_course_for_lti(coursenum, metadata)
course_descriptor = world.scenario_dict['COURSE'] course_descriptor = world.scenario_dict['COURSE']
course_location = world.scenario_dict['COURSE'].location
# create beta tester # create beta tester
user = BetaTesterFactory(course=course_location) user = BetaTesterFactory(course=course_descriptor.id)
normal_student = UserFactory() normal_student = UserFactory()
instructor = InstructorFactory(course=course_location) instructor = InstructorFactory(course=course_descriptor.id)
assert not has_access(normal_student, course_descriptor, 'load') assert not has_access(normal_student, 'load', course_descriptor)
assert has_access(user, course_descriptor, 'load') assert has_access(user, 'load', course_descriptor)
assert has_access(instructor, course_descriptor, 'load') assert has_access(instructor, 'load', course_descriptor)
else: else:
metadata.update({'start': datetime.datetime(1970, 1, 1, tzinfo=UTC)}) metadata.update({'start': datetime.datetime(1970, 1, 1, tzinfo=UTC)})
create_course_for_lti(coursenum, metadata) create_course_for_lti(coursenum, metadata)
course_descriptor = world.scenario_dict['COURSE'] course_descriptor = world.scenario_dict['COURSE']
course_location = world.scenario_dict['COURSE'].location user = InstructorFactory(course=course_descriptor.id)
user = InstructorFactory(course=course_location)
# Enroll the user in the course and log them in # Enroll the user in the course and log them in
if has_access(user, course_descriptor, 'load'): if has_access(user, 'load', course_descriptor):
world.enroll_user(user, course_id(coursenum)) world.enroll_user(user, course_descriptor.id)
world.log_in(username=user.username, password='test') world.log_in(username=user.username, password='test')
......
...@@ -5,6 +5,7 @@ from lettuce import world, step ...@@ -5,6 +5,7 @@ from lettuce import world, step
from common import course_id, course_location from common import course_id, course_location
from problems_setup import PROBLEM_DICT from problems_setup import PROBLEM_DICT
from nose.tools import assert_in from nose.tools import assert_in
from xmodule.modulestore.locations import SlashSeparatedCourseKey
@step(u'I am viewing a course with multiple sections') @step(u'I am viewing a course with multiple sections')
...@@ -148,7 +149,7 @@ def create_course(): ...@@ -148,7 +149,7 @@ def create_course():
def create_user_and_visit_course(): def create_user_and_visit_course():
world.register_by_course_id('edx/999/Test_Course') world.register_by_course_key(SlashSeparatedCourseKey('edx', '999', 'Test_Course'))
world.log_in() world.log_in()
world.visit('/courses/edx/999/Test_Course/courseware/') world.visit('/courses/edx/999/Test_Course/courseware/')
......
...@@ -10,7 +10,7 @@ logger = getLogger(__name__) ...@@ -10,7 +10,7 @@ logger = getLogger(__name__)
@step('I navigate to an openended question$') @step('I navigate to an openended question$')
def navigate_to_an_openended_question(step): def navigate_to_an_openended_question(step):
world.register_by_course_id('MITx/3.091x/2012_Fall') world.register_by_course_key('MITx/3.091x/2012_Fall')
world.log_in(email='robot@edx.org', password='test') world.log_in(email='robot@edx.org', password='test')
problem = '/courses/MITx/3.091x/2012_Fall/courseware/Week_10/Polymer_Synthesis/' problem = '/courses/MITx/3.091x/2012_Fall/courseware/Week_10/Polymer_Synthesis/'
world.browser.visit(django_url(problem)) world.browser.visit(django_url(problem))
...@@ -20,7 +20,7 @@ def navigate_to_an_openended_question(step): ...@@ -20,7 +20,7 @@ def navigate_to_an_openended_question(step):
@step('I navigate to an openended question as staff$') @step('I navigate to an openended question as staff$')
def navigate_to_an_openended_question_as_staff(step): def navigate_to_an_openended_question_as_staff(step):
world.register_by_course_id('MITx/3.091x/2012_Fall', True) world.register_by_course_key('MITx/3.091x/2012_Fall', True)
world.log_in(email='robot@edx.org', password='test') world.log_in(email='robot@edx.org', password='test')
problem = '/courses/MITx/3.091x/2012_Fall/courseware/Week_10/Polymer_Synthesis/' problem = '/courses/MITx/3.091x/2012_Fall/courseware/Week_10/Polymer_Synthesis/'
world.browser.visit(django_url(problem)) world.browser.visit(django_url(problem))
......
...@@ -7,7 +7,7 @@ from lettuce.django import django_url ...@@ -7,7 +7,7 @@ from lettuce.django import django_url
@step('I register for the course "([^"]*)"$') @step('I register for the course "([^"]*)"$')
def i_register_for_the_course(_step, course): def i_register_for_the_course(_step, course):
url = django_url('courses/%s/about' % world.scenario_dict['COURSE'].id) url = django_url('courses/%s/about' % world.scenario_dict['COURSE'].id.to_deprecated_string())
world.browser.visit(url) world.browser.visit(url)
world.css_click('section.intro a.register') world.css_click('section.intro a.register')
......
...@@ -195,7 +195,7 @@ def add_vertical_to_course(course_num): ...@@ -195,7 +195,7 @@ def add_vertical_to_course(course_num):
def last_vertical_location(course_num): def last_vertical_location(course_num):
return world.scenario_dict['LAST_VERTICAL'].location._replace(course=course_num) return world.scenario_dict['LAST_VERTICAL'].location.replace(course=course_num)
def upload_file(filename, location): def upload_file(filename, location):
...@@ -204,7 +204,7 @@ def upload_file(filename, location): ...@@ -204,7 +204,7 @@ def upload_file(filename, location):
mime_type = "application/json" mime_type = "application/json"
content_location = StaticContent.compute_location( content_location = StaticContent.compute_location(
location.org, location.course, filename location.course_key, filename
) )
content = StaticContent(content_location, filename, mime_type, f.read()) content = StaticContent(content_location, filename, mime_type, f.read())
contentstore().save(content) contentstore().save(content)
......
...@@ -23,6 +23,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError ...@@ -23,6 +23,7 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.util.duedate import get_extended_due_date from xmodule.util.duedate import get_extended_due_date
from .models import StudentModule from .models import StudentModule
from .module_render import get_module_for_descriptor from .module_render import get_module_for_descriptor
from opaque_keys import InvalidKeyError
log = logging.getLogger("edx.courseware") log = logging.getLogger("edx.courseware")
...@@ -50,9 +51,9 @@ def yield_dynamic_descriptor_descendents(descriptor, module_creator): ...@@ -50,9 +51,9 @@ def yield_dynamic_descriptor_descendents(descriptor, module_creator):
yield next_descriptor yield next_descriptor
def answer_distributions(course_id): def answer_distributions(course_key):
""" """
Given a course_id, return answer distributions in the form of a dictionary Given a course_key, return answer distributions in the form of a dictionary
mapping: mapping:
(problem url_name, problem display_name, problem_id) -> {dict: answer -> count} (problem url_name, problem display_name, problem_id) -> {dict: answer -> count}
...@@ -82,44 +83,40 @@ def answer_distributions(course_id): ...@@ -82,44 +83,40 @@ def answer_distributions(course_id):
# dict: { module.module_state_key : (url_name, display_name) } # dict: { module.module_state_key : (url_name, display_name) }
state_keys_to_problem_info = {} # For caching, used by url_and_display_name state_keys_to_problem_info = {} # For caching, used by url_and_display_name
def url_and_display_name(module_state_key): def url_and_display_name(usage_key):
""" """
For a given module_state_key, return the problem's url and display_name. For a given usage_key, return the problem's url and display_name.
Handle modulestore access and caching. This method ignores permissions. Handle modulestore access and caching. This method ignores permissions.
May throw an ItemNotFoundError if there is no content that corresponds
to this module_state_key. Raises:
InvalidKeyError: if the usage_key does not parse
ItemNotFoundError: if there is no content that corresponds
to this usage_key.
""" """
problem_store = modulestore() problem_store = modulestore()
if module_state_key not in state_keys_to_problem_info: if usage_key not in state_keys_to_problem_info:
problems = problem_store.get_items(module_state_key, course_id=course_id, depth=1) problem = problem_store.get_item(usage_key)
if not problems:
# Likely means that the problem was deleted from the course
# after the student had answered. We log this suspicion where
# this exception is caught.
raise ItemNotFoundError(
"Answer Distribution: Module {} not found for course {}"
.format(module_state_key, course_id)
)
problem = problems[0]
problem_info = (problem.url_name, problem.display_name_with_default) problem_info = (problem.url_name, problem.display_name_with_default)
state_keys_to_problem_info[module_state_key] = problem_info state_keys_to_problem_info[usage_key] = problem_info
return state_keys_to_problem_info[module_state_key] return state_keys_to_problem_info[usage_key]
# Iterate through all problems submitted for this course in no particular # Iterate through all problems submitted for this course in no particular
# order, and build up our answer_counts dict that we will eventually return # order, and build up our answer_counts dict that we will eventually return
answer_counts = defaultdict(lambda: defaultdict(int)) answer_counts = defaultdict(lambda: defaultdict(int))
for module in StudentModule.all_submitted_problems_read_only(course_id): for module in StudentModule.all_submitted_problems_read_only(course_key):
try: try:
state_dict = json.loads(module.state) if module.state else {} state_dict = json.loads(module.state) if module.state else {}
raw_answers = state_dict.get("student_answers", {}) raw_answers = state_dict.get("student_answers", {})
except ValueError: except ValueError:
log.error( log.error(
"Answer Distribution: Could not parse module state for " + "Answer Distribution: Could not parse module state for " +
"StudentModule id={}, course={}".format(module.id, course_id) "StudentModule id={}, course={}".format(module.id, course_key)
) )
continue continue
try:
url, display_name = url_and_display_name(module.module_id.map_into_course(course_key))
# Each problem part has an ID that is derived from the # Each problem part has an ID that is derived from the
# module.module_state_key (with some suffix appended) # module.module_state_key (with some suffix appended)
for problem_part_id, raw_answer in raw_answers.items(): for problem_part_id, raw_answer in raw_answers.items():
...@@ -128,22 +125,19 @@ def answer_distributions(course_id): ...@@ -128,22 +125,19 @@ def answer_distributions(course_id):
# unicode and not str -- state comes from the json decoder, and that # unicode and not str -- state comes from the json decoder, and that
# always returns unicode for strings. # always returns unicode for strings.
answer = unicode(raw_answer) answer = unicode(raw_answer)
answer_counts[(url, display_name, problem_part_id)][answer] += 1
try: except (ItemNotFoundError, InvalidKeyError):
url, display_name = url_and_display_name(module.module_state_key)
except ItemNotFoundError:
msg = "Answer Distribution: Item {} referenced in StudentModule {} " + \ msg = "Answer Distribution: Item {} referenced in StudentModule {} " + \
"for user {} in course {} not found; " + \ "for user {} in course {} not found; " + \
"This can happen if a student answered a question that " + \ "This can happen if a student answered a question that " + \
"was later deleted from the course. This answer will be " + \ "was later deleted from the course. This answer will be " + \
"omitted from the answer distribution CSV." "omitted from the answer distribution CSV."
log.warning( log.warning(
msg.format(module.module_state_key, module.id, module.student_id, course_id) msg.format(module.module_state_key, module.id, module.student_id, course_key)
) )
continue continue
answer_counts[(url, display_name, problem_part_id)][answer] += 1
return answer_counts return answer_counts
@transaction.commit_manually @transaction.commit_manually
...@@ -183,7 +177,9 @@ def _grade(student, request, course, keep_raw_scores): ...@@ -183,7 +177,9 @@ def _grade(student, request, course, keep_raw_scores):
# Dict of item_ids -> (earned, possible) point tuples. This *only* grabs # Dict of item_ids -> (earned, possible) point tuples. This *only* grabs
# scores that were registered with the submissions API, which for the moment # scores that were registered with the submissions API, which for the moment
# means only openassessment (edx-ora2) # means only openassessment (edx-ora2)
submissions_scores = sub_api.get_scores(course.id, anonymous_id_for_user(student, course.id)) submissions_scores = sub_api.get_scores(
course.id.to_deprecated_string(), anonymous_id_for_user(student, course.id)
)
totaled_scores = {} totaled_scores = {}
# This next complicated loop is just to collect the totaled_scores, which is # This next complicated loop is just to collect the totaled_scores, which is
...@@ -206,7 +202,7 @@ def _grade(student, request, course, keep_raw_scores): ...@@ -206,7 +202,7 @@ def _grade(student, request, course, keep_raw_scores):
# API. If scores exist, we have to calculate grades for this section. # API. If scores exist, we have to calculate grades for this section.
if not should_grade_section: if not should_grade_section:
should_grade_section = any( should_grade_section = any(
descriptor.location.url() in submissions_scores descriptor.location.to_deprecated_string() in submissions_scores
for descriptor in section['xmoduledescriptors'] for descriptor in section['xmoduledescriptors']
) )
...@@ -214,7 +210,7 @@ def _grade(student, request, course, keep_raw_scores): ...@@ -214,7 +210,7 @@ def _grade(student, request, course, keep_raw_scores):
with manual_transaction(): with manual_transaction():
should_grade_section = StudentModule.objects.filter( should_grade_section = StudentModule.objects.filter(
student=student, student=student,
module_state_key__in=[ module_id__in=[
descriptor.location for descriptor in section['xmoduledescriptors'] descriptor.location for descriptor in section['xmoduledescriptors']
] ]
).exists() ).exists()
...@@ -350,7 +346,7 @@ def _progress_summary(student, request, course): ...@@ -350,7 +346,7 @@ def _progress_summary(student, request, course):
# This student must not have access to the course. # This student must not have access to the course.
return None return None
submissions_scores = sub_api.get_scores(course.id, anonymous_id_for_user(student, course.id)) submissions_scores = sub_api.get_scores(course.id.to_deprecated_string(), anonymous_id_for_user(student, course.id))
chapters = [] chapters = []
# Don't include chapters that aren't displayable (e.g. due to error) # Don't include chapters that aren't displayable (e.g. due to error)
...@@ -427,7 +423,7 @@ def get_score(course_id, user, problem_descriptor, module_creator, scores_cache= ...@@ -427,7 +423,7 @@ def get_score(course_id, user, problem_descriptor, module_creator, scores_cache=
if not user.is_authenticated(): if not user.is_authenticated():
return (None, None) return (None, None)
location_url = problem_descriptor.location.url() location_url = problem_descriptor.location.to_deprecated_string()
if location_url in scores_cache: if location_url in scores_cache:
return scores_cache[location_url] return scores_cache[location_url]
...@@ -451,7 +447,7 @@ def get_score(course_id, user, problem_descriptor, module_creator, scores_cache= ...@@ -451,7 +447,7 @@ def get_score(course_id, user, problem_descriptor, module_creator, scores_cache=
student_module = StudentModule.objects.get( student_module = StudentModule.objects.get(
student=user, student=user,
course_id=course_id, course_id=course_id,
module_state_key=problem_descriptor.location module_id=problem_descriptor.location
) )
except StudentModule.DoesNotExist: except StudentModule.DoesNotExist:
student_module = None student_module = None
......
...@@ -73,7 +73,7 @@ def import_with_checks(course_dir, verbose=True): ...@@ -73,7 +73,7 @@ def import_with_checks(course_dir, verbose=True):
return (False, None) return (False, None)
course = courses[0] course = courses[0]
errors = modulestore.get_item_errors(course.location) errors = modulestore.get_course_errors(course.id)
if len(errors) != 0: if len(errors) != 0:
all_ok = False all_ok = False
print '\n' print '\n'
......
...@@ -24,18 +24,12 @@ class Command(BaseCommand): ...@@ -24,18 +24,12 @@ class Command(BaseCommand):
) )
def handle(self, *args, **options): def handle(self, *args, **options):
results = []
try: try:
name = options['modulestore'] name = options['modulestore']
store = modulestore(name) store = modulestore(name)
except KeyError: except KeyError:
raise CommandError("Unknown modulestore {}".format(name)) raise CommandError("Unknown modulestore {}".format(name))
for course in store.get_courses(): output = u'\n'.join(course.id.to_deprecated_string() for course in store.get_courses()) + '\n'
course_id = course.location.course_id
results.append(course_id)
output = '\n'.join(results) + '\n'
return output.encode('utf-8') return output.encode('utf-8')
...@@ -25,6 +25,8 @@ from django.core.management.base import BaseCommand, CommandError ...@@ -25,6 +25,8 @@ from django.core.management.base import BaseCommand, CommandError
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.inheritance import own_metadata, compute_inherited_metadata from xmodule.modulestore.inheritance import own_metadata, compute_inherited_metadata
from xblock.fields import Scope from xblock.fields import Scope
from opaque_keys import InvalidKeyError
from xmodule.modulestore.locations import SlashSeparatedCourseKey
FILTER_LIST = ['xml_attributes', 'checklists'] FILTER_LIST = ['xml_attributes', 'checklists']
INHERITED_FILTER_LIST = ['children', 'xml_attributes', 'checklists'] INHERITED_FILTER_LIST = ['children', 'xml_attributes', 'checklists']
...@@ -66,7 +68,11 @@ class Command(BaseCommand): ...@@ -66,7 +68,11 @@ class Command(BaseCommand):
# Get the course data # Get the course data
course_id = args[0] try:
course_id = SlashSeparatedCourseKey.from_deprecated_string(args[0])
except InvalidKeyError:
raise CommandError("Invalid course_id")
course = store.get_course(course_id) course = store.get_course(course_id)
if course is None: if course is None:
raise CommandError("Invalid course_id") raise CommandError("Invalid course_id")
...@@ -90,12 +96,12 @@ def dump_module(module, destination=None, inherited=False, defaults=False): ...@@ -90,12 +96,12 @@ def dump_module(module, destination=None, inherited=False, defaults=False):
destination = destination if destination else {} destination = destination if destination else {}
items = own_metadata(module).iteritems() items = own_metadata(module)
filtered_metadata = {k: v for k, v in items if k not in FILTER_LIST} filtered_metadata = {k: v for k, v in items.iteritems() if k not in FILTER_LIST}
destination[module.location.url()] = { destination[module.location.to_deprecated_string()] = {
'category': module.location.category, 'category': module.location.category,
'children': [str(child) for child in getattr(module, 'children', [])], 'children': [child.to_deprecated_string() for child in getattr(module, 'children', [])],
'metadata': filtered_metadata, 'metadata': filtered_metadata,
} }
...@@ -116,7 +122,7 @@ def dump_module(module, destination=None, inherited=False, defaults=False): ...@@ -116,7 +122,7 @@ def dump_module(module, destination=None, inherited=False, defaults=False):
return field.values != field.default return field.values != field.default
inherited_metadata = {field.name: field.read_json(module) for field in module.fields.values() if is_inherited(field)} inherited_metadata = {field.name: field.read_json(module) for field in module.fields.values() if is_inherited(field)}
destination[module.location.url()]['inherited_metadata'] = inherited_metadata destination[module.location.to_deprecated_string()]['inherited_metadata'] = inherited_metadata
for child in module.get_children(): for child in module.get_children():
dump_module(child, destination, inherited, defaults) dump_module(child, destination, inherited, defaults)
......
...@@ -17,6 +17,8 @@ from django.core.management.base import BaseCommand, CommandError ...@@ -17,6 +17,8 @@ from django.core.management.base import BaseCommand, CommandError
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.xml_exporter import export_to_xml from xmodule.modulestore.xml_exporter import export_to_xml
from opaque_keys import InvalidKeyError
from xmodule.modulestore.locations import SlashSeparatedCourseKey
class Command(BaseCommand): class Command(BaseCommand):
...@@ -39,8 +41,10 @@ class Command(BaseCommand): ...@@ -39,8 +41,10 @@ class Command(BaseCommand):
def _parse_arguments(self, args): def _parse_arguments(self, args):
"""Parse command line arguments""" """Parse command line arguments"""
try: try:
course_id = args[0] course_id = SlashSeparatedCourseKey.from_deprecated_string(args[0])
filename = args[1] filename = args[1]
except InvalidKeyError:
raise CommandError("Unparsable course_id")
except IndexError: except IndexError:
raise CommandError("Insufficient arguments") raise CommandError("Insufficient arguments")
...@@ -54,7 +58,6 @@ class Command(BaseCommand): ...@@ -54,7 +58,6 @@ class Command(BaseCommand):
def _get_results(self, filename): def _get_results(self, filename):
"""Load results from file""" """Load results from file"""
results = None
with open(filename) as f: with open(filename) as f:
results = f.read() results = f.read()
os.remove(filename) os.remove(filename)
...@@ -78,8 +81,8 @@ def export_course_to_directory(course_id, root_dir): ...@@ -78,8 +81,8 @@ def export_course_to_directory(course_id, root_dir):
if course is None: if course is None:
raise CommandError("Invalid course_id") raise CommandError("Invalid course_id")
course_name = course.location.course_id.replace('/', '-') course_name = course.id.to_deprecated_string().replace('/', '-')
export_to_xml(store, None, course.location, root_dir, course_name) export_to_xml(store, None, course.id, root_dir, course_name)
course_dir = path(root_dir) / course_name course_dir = path(root_dir) / course_name
return course_dir return course_dir
......
...@@ -38,7 +38,7 @@ def import_course(course_dir, verbose=True): ...@@ -38,7 +38,7 @@ def import_course(course_dir, verbose=True):
return None return None
course = courses[0] course = courses[0]
errors = modulestore.get_item_errors(course.location) errors = modulestore.get_course_errors(course.id)
if len(errors) != 0: if len(errors) != 0:
sys.stderr.write('ERRORs during import: {0}\n'.format('\n'.join(map(str_of_err, errors)))) sys.stderr.write('ERRORs during import: {0}\n'.format('\n'.join(map(str_of_err, errors))))
......
...@@ -22,6 +22,7 @@ from xmodule.modulestore.django import modulestore ...@@ -22,6 +22,7 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.locations import SlashSeparatedCourseKey
DATA_DIR = 'common/test/data/' DATA_DIR = 'common/test/data/'
...@@ -53,7 +54,8 @@ class CommandsTestBase(TestCase): ...@@ -53,7 +54,8 @@ class CommandsTestBase(TestCase):
modulestore=store) modulestore=store)
courses = store.get_courses() courses = store.get_courses()
if TEST_COURSE_ID not in [c.id for c in courses]: # NOTE: if xml store owns these, it won't import them into mongo
if SlashSeparatedCourseKey.from_deprecated_string(TEST_COURSE_ID) not in [c.id for c in courses]:
import_from_xml(store, DATA_DIR, ['toy', 'simple']) import_from_xml(store, DATA_DIR, ['toy', 'simple'])
return [course.id for course in store.get_courses()] return [course.id for course in store.get_courses()]
...@@ -70,7 +72,9 @@ class CommandsTestBase(TestCase): ...@@ -70,7 +72,9 @@ class CommandsTestBase(TestCase):
output = self.call_command('dump_course_ids', **kwargs) output = self.call_command('dump_course_ids', **kwargs)
dumped_courses = output.decode('utf-8').strip().split('\n') dumped_courses = output.decode('utf-8').strip().split('\n')
self.assertEqual(self.loaded_courses, dumped_courses) course_ids = {course_id.to_deprecated_string() for course_id in self.loaded_courses}
dumped_ids = set(dumped_courses)
self.assertEqual(course_ids, dumped_ids)
def test_dump_course_structure(self): def test_dump_course_structure(self):
args = [TEST_COURSE_ID] args = [TEST_COURSE_ID]
...@@ -81,16 +85,15 @@ class CommandsTestBase(TestCase): ...@@ -81,16 +85,15 @@ class CommandsTestBase(TestCase):
# check that all elements in the course structure have metadata, # check that all elements in the course structure have metadata,
# but not inherited metadata: # but not inherited metadata:
for element_name in dump: for element in dump.itervalues():
element = dump[element_name]
self.assertIn('metadata', element) self.assertIn('metadata', element)
self.assertIn('children', element) self.assertIn('children', element)
self.assertIn('category', element) self.assertIn('category', element)
self.assertNotIn('inherited_metadata', element) self.assertNotIn('inherited_metadata', element)
# Check a few elements in the course dump # Check a few elements in the course dump
test_course_key = SlashSeparatedCourseKey.from_deprecated_string(TEST_COURSE_ID)
parent_id = 'i4x://edX/simple/chapter/Overview' parent_id = test_course_key.make_usage_key('chapter', 'Overview').to_deprecated_string()
self.assertEqual(dump[parent_id]['category'], 'chapter') self.assertEqual(dump[parent_id]['category'], 'chapter')
self.assertEqual(len(dump[parent_id]['children']), 3) self.assertEqual(len(dump[parent_id]['children']), 3)
...@@ -98,7 +101,7 @@ class CommandsTestBase(TestCase): ...@@ -98,7 +101,7 @@ class CommandsTestBase(TestCase):
self.assertEqual(dump[child_id]['category'], 'videosequence') self.assertEqual(dump[child_id]['category'], 'videosequence')
self.assertEqual(len(dump[child_id]['children']), 2) self.assertEqual(len(dump[child_id]['children']), 2)
video_id = 'i4x://edX/simple/video/Welcome' video_id = test_course_key.make_usage_key('video', 'Welcome').to_deprecated_string()
self.assertEqual(dump[video_id]['category'], 'video') self.assertEqual(dump[video_id]['category'], 'video')
self.assertEqual(len(dump[video_id]['metadata']), 4) self.assertEqual(len(dump[video_id]['metadata']), 4)
self.assertIn('youtube_id_1_0', dump[video_id]['metadata']) self.assertIn('youtube_id_1_0', dump[video_id]['metadata'])
...@@ -114,8 +117,7 @@ class CommandsTestBase(TestCase): ...@@ -114,8 +117,7 @@ class CommandsTestBase(TestCase):
dump = json.loads(output) dump = json.loads(output)
# check that all elements in the course structure have inherited metadata, # check that all elements in the course structure have inherited metadata,
# and that it contains a particular value as well: # and that it contains a particular value as well:
for element_name in dump: for element in dump.itervalues():
element = dump[element_name]
self.assertIn('metadata', element) self.assertIn('metadata', element)
self.assertIn('children', element) self.assertIn('children', element)
self.assertIn('category', element) self.assertIn('category', element)
...@@ -131,8 +133,7 @@ class CommandsTestBase(TestCase): ...@@ -131,8 +133,7 @@ class CommandsTestBase(TestCase):
dump = json.loads(output) dump = json.loads(output)
# check that all elements in the course structure have inherited metadata, # check that all elements in the course structure have inherited metadata,
# and that it contains a particular value as well: # and that it contains a particular value as well:
for element_name in dump: for element in dump.itervalues():
element = dump[element_name]
self.assertIn('metadata', element) self.assertIn('metadata', element)
self.assertIn('children', element) self.assertIn('children', element)
self.assertIn('category', element) self.assertIn('category', element)
...@@ -158,7 +159,7 @@ class CommandsTestBase(TestCase): ...@@ -158,7 +159,7 @@ class CommandsTestBase(TestCase):
self.check_export_file(tar_file) self.check_export_file(tar_file)
def run_export_course(self, filename): # pylint: disable=missing-docstring def run_export_course(self, filename): # pylint: disable=missing-docstring
args = ['edX/simple/2012_Fall', filename] args = [TEST_COURSE_ID, filename]
kwargs = {'modulestore': 'default'} kwargs = {'modulestore': 'default'}
return self.call_command('export_course', *args, **kwargs) return self.call_command('export_course', *args, **kwargs)
......
...@@ -12,6 +12,7 @@ from .models import ( ...@@ -12,6 +12,7 @@ from .models import (
XModuleStudentInfoField XModuleStudentInfoField
) )
import logging import logging
from xmodule.modulestore.locations import SlashSeparatedCourseKey, Location
from django.db import DatabaseError from django.db import DatabaseError
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -59,6 +60,8 @@ class FieldDataCache(object): ...@@ -59,6 +60,8 @@ class FieldDataCache(object):
self.cache = {} self.cache = {}
self.descriptors = descriptors self.descriptors = descriptors
self.select_for_update = select_for_update self.select_for_update = select_for_update
assert isinstance(course_id, SlashSeparatedCourseKey)
self.course_id = course_id self.course_id = course_id
self.user = user self.user = user
...@@ -141,8 +144,8 @@ class FieldDataCache(object): ...@@ -141,8 +144,8 @@ class FieldDataCache(object):
if scope == Scope.user_state: if scope == Scope.user_state:
return self._chunked_query( return self._chunked_query(
StudentModule, StudentModule,
'module_state_key__in', 'module_id__in',
(str(descriptor.scope_ids.usage_id) for descriptor in self.descriptors), (descriptor.scope_ids.usage_id for descriptor in self.descriptors),
course_id=self.course_id, course_id=self.course_id,
student=self.user.pk, student=self.user.pk,
) )
...@@ -150,7 +153,7 @@ class FieldDataCache(object): ...@@ -150,7 +153,7 @@ class FieldDataCache(object):
return self._chunked_query( return self._chunked_query(
XModuleUserStateSummaryField, XModuleUserStateSummaryField,
'usage_id__in', 'usage_id__in',
(str(descriptor.scope_ids.usage_id) for descriptor in self.descriptors), (descriptor.scope_ids.usage_id for descriptor in self.descriptors),
field_name__in=set(field.name for field in fields), field_name__in=set(field.name for field in fields),
) )
elif scope == Scope.preferences: elif scope == Scope.preferences:
...@@ -185,9 +188,9 @@ class FieldDataCache(object): ...@@ -185,9 +188,9 @@ class FieldDataCache(object):
Return the key used in the FieldDataCache for the specified KeyValueStore key Return the key used in the FieldDataCache for the specified KeyValueStore key
""" """
if key.scope == Scope.user_state: if key.scope == Scope.user_state:
return (key.scope, key.block_scope_id.url()) return (key.scope, key.block_scope_id)
elif key.scope == Scope.user_state_summary: elif key.scope == Scope.user_state_summary:
return (key.scope, key.block_scope_id.url(), key.field_name) return (key.scope, key.block_scope_id, key.field_name)
elif key.scope == Scope.preferences: elif key.scope == Scope.preferences:
return (key.scope, key.block_scope_id, key.field_name) return (key.scope, key.block_scope_id, key.field_name)
elif key.scope == Scope.user_info: elif key.scope == Scope.user_info:
...@@ -199,9 +202,15 @@ class FieldDataCache(object): ...@@ -199,9 +202,15 @@ class FieldDataCache(object):
field field
""" """
if scope == Scope.user_state: if scope == Scope.user_state:
return (scope, field_object.module_state_key) assert (field_object.module_state_key.org == self.course_id.org and
field_object.module_state_key.course == self.course_id.course)
return (scope, field_object.module_state_key.map_into_course(self.course_id))
elif scope == Scope.user_state_summary: elif scope == Scope.user_state_summary:
return (scope, field_object.usage_id, field_object.field_name) assert (field_object.usage_id.org == self.course_id.org and
field_object.usage_id.course == self.course_id.course)
return (scope, field_object.usage_id.map_into_course(self.course_id), field_object.field_name)
elif scope == Scope.preferences: elif scope == Scope.preferences:
return (scope, field_object.module_type, field_object.field_name) return (scope, field_object.module_type, field_object.field_name)
elif scope == Scope.user_info: elif scope == Scope.user_info:
...@@ -233,10 +242,13 @@ class FieldDataCache(object): ...@@ -233,10 +242,13 @@ class FieldDataCache(object):
return field_object return field_object
if key.scope == Scope.user_state: if key.scope == Scope.user_state:
# When we start allowing block_scope_ids to be either Locations or Locators,
# this assertion will fail. Fix the code here when that happens!
assert(isinstance(key.block_scope_id, Location))
field_object, _ = StudentModule.objects.get_or_create( field_object, _ = StudentModule.objects.get_or_create(
course_id=self.course_id, course_id=self.course_id,
student=User.objects.get(id=key.user_id), student=User.objects.get(id=key.user_id),
module_state_key=key.block_scope_id.url(), module_id=key.block_scope_id.replace(run=None),
defaults={ defaults={
'state': json.dumps({}), 'state': json.dumps({}),
'module_type': key.block_scope_id.category, 'module_type': key.block_scope_id.category,
...@@ -245,7 +257,7 @@ class FieldDataCache(object): ...@@ -245,7 +257,7 @@ class FieldDataCache(object):
elif key.scope == Scope.user_state_summary: elif key.scope == Scope.user_state_summary:
field_object, _ = XModuleUserStateSummaryField.objects.get_or_create( field_object, _ = XModuleUserStateSummaryField.objects.get_or_create(
field_name=key.field_name, field_name=key.field_name,
usage_id=key.block_scope_id.url() usage_id=key.block_scope_id
) )
elif key.scope == Scope.preferences: elif key.scope == Scope.preferences:
field_object, _ = XModuleStudentPrefsField.objects.get_or_create( field_object, _ = XModuleStudentPrefsField.objects.get_or_create(
......
...@@ -18,6 +18,8 @@ from django.db import models ...@@ -18,6 +18,8 @@ from django.db import models
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from xmodule_django.models import CourseKeyField, LocationKeyField
class StudentModule(models.Model): class StudentModule(models.Model):
""" """
...@@ -38,12 +40,31 @@ class StudentModule(models.Model): ...@@ -38,12 +40,31 @@ class StudentModule(models.Model):
# but for abtests and the like, this can be set to a shared value # but for abtests and the like, this can be set to a shared value
# for many instances of the module. # for many instances of the module.
# Filename for homeworks, etc. # Filename for homeworks, etc.
module_state_key = models.CharField(max_length=255, db_index=True, db_column='module_id') module_id = LocationKeyField(max_length=255, db_index=True, db_column='module_id')
student = models.ForeignKey(User, db_index=True) student = models.ForeignKey(User, db_index=True)
course_id = models.CharField(max_length=255, db_index=True) # TODO: This is a lie; course_id now represents something more like a course_key. We may
# or may not want to change references to this to something like course_key or course_key_field in
# this file. (Certain changes would require a DB migration which is probably not what we want.)
# Someone should look at this and reevaluate before the final merge into master.
course_id = CourseKeyField(max_length=255, db_index=True)
@property
def module_state_key(self):
"""
Returns a Location based on module_id and course_id
"""
return self.course_id.make_usage_key(self.module_id.category, self.module_id.name)
@module_state_key.setter
def module_state_key(self, usage_key):
"""
Set the module_id and course_id from the passed UsageKey
"""
self.course_id = usage_key.course_key
self.module_id = usage_key
class Meta: class Meta:
unique_together = (('student', 'module_state_key', 'course_id'),) unique_together = (('student', 'module_id', 'course_id'),)
## Internal state of the object ## Internal state of the object
state = models.TextField(null=True, blank=True) state = models.TextField(null=True, blank=True)
...@@ -110,7 +131,7 @@ class StudentModuleHistory(models.Model): ...@@ -110,7 +131,7 @@ class StudentModuleHistory(models.Model):
max_grade = models.FloatField(null=True, blank=True) max_grade = models.FloatField(null=True, blank=True)
@receiver(post_save, sender=StudentModule) @receiver(post_save, sender=StudentModule)
def save_history(sender, instance, **kwargs): def save_history(sender, instance, **kwargs): # pylint: disable=no-self-argument
if instance.module_type in StudentModuleHistory.HISTORY_SAVING_TYPES: if instance.module_type in StudentModuleHistory.HISTORY_SAVING_TYPES:
history_entry = StudentModuleHistory(student_module=instance, history_entry = StudentModuleHistory(student_module=instance,
version=None, version=None,
...@@ -133,7 +154,7 @@ class XModuleUserStateSummaryField(models.Model): ...@@ -133,7 +154,7 @@ class XModuleUserStateSummaryField(models.Model):
field_name = models.CharField(max_length=64, db_index=True) field_name = models.CharField(max_length=64, db_index=True)
# The definition id for the module # The definition id for the module
usage_id = models.CharField(max_length=255, db_index=True) usage_id = LocationKeyField(max_length=255, db_index=True)
# The value of the field. Defaults to None dumped as json # The value of the field. Defaults to None dumped as json
value = models.TextField(default='null') value = models.TextField(default='null')
...@@ -221,7 +242,7 @@ class OfflineComputedGrade(models.Model): ...@@ -221,7 +242,7 @@ class OfflineComputedGrade(models.Model):
Table of grades computed offline for a given user and course. Table of grades computed offline for a given user and course.
""" """
user = models.ForeignKey(User, db_index=True) user = models.ForeignKey(User, db_index=True)
course_id = models.CharField(max_length=255, db_index=True) course_id = CourseKeyField(max_length=255, db_index=True)
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True) created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
updated = models.DateTimeField(auto_now=True, db_index=True) updated = models.DateTimeField(auto_now=True, db_index=True)
...@@ -244,10 +265,10 @@ class OfflineComputedGradeLog(models.Model): ...@@ -244,10 +265,10 @@ class OfflineComputedGradeLog(models.Model):
ordering = ["-created"] ordering = ["-created"]
get_latest_by = "created" get_latest_by = "created"
course_id = models.CharField(max_length=255, db_index=True) course_id = CourseKeyField(max_length=255, db_index=True)
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True) created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
seconds = models.IntegerField(default=0) # seconds elapsed for computation seconds = models.IntegerField(default=0) # seconds elapsed for computation
nstudents = models.IntegerField(default=0) nstudents = models.IntegerField(default=0)
def __unicode__(self): def __unicode__(self):
return "[OCGLog] %s: %s" % (self.course_id, self.created) return "[OCGLog] %s: %s" % (self.course_id.to_deprecated_string(), self.created) # pylint: disable=no-member
...@@ -7,6 +7,7 @@ import static_replace ...@@ -7,6 +7,7 @@ import static_replace
from functools import partial from functools import partial
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
from dogapi import dog_stats_api from dogapi import dog_stats_api
from opaque_keys import InvalidKeyError
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -21,7 +22,7 @@ from courseware.access import has_access, get_user_role ...@@ -21,7 +22,7 @@ from courseware.access import has_access, get_user_role
from courseware.masquerade import setup_masquerade from courseware.masquerade import setup_masquerade
from courseware.model_data import FieldDataCache, DjangoKeyValueStore from courseware.model_data import FieldDataCache, DjangoKeyValueStore
from lms.lib.xblock.field_data import LmsFieldData from lms.lib.xblock.field_data import LmsFieldData
from lms.lib.xblock.runtime import LmsModuleSystem, unquote_slashes from lms.lib.xblock.runtime import LmsModuleSystem, unquote_slashes, quote_slashes
from edxmako.shortcuts import render_to_string from edxmako.shortcuts import render_to_string
from eventtracking import tracker from eventtracking import tracker
from psychometrics.psychoanalyze import make_psychometrics_data_update_handler from psychometrics.psychoanalyze import make_psychometrics_data_update_handler
...@@ -33,7 +34,7 @@ from xblock.exceptions import NoSuchHandlerError ...@@ -33,7 +34,7 @@ from xblock.exceptions import NoSuchHandlerError
from xblock.django.request import django_to_webob_request, webob_to_django_response from xblock.django.request import django_to_webob_request, webob_to_django_response
from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError from xmodule.exceptions import NotFoundError, ProcessingError
from xmodule.modulestore import Location from xmodule.modulestore.locations import SlashSeparatedCourseKey
from xmodule.modulestore.django import modulestore, ModuleI18nService from xmodule.modulestore.django import modulestore, ModuleI18nService
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.util.duedate import get_extended_due_date from xmodule.util.duedate import get_extended_due_date
...@@ -49,16 +50,20 @@ log = logging.getLogger(__name__) ...@@ -49,16 +50,20 @@ log = logging.getLogger(__name__)
if settings.XQUEUE_INTERFACE.get('basic_auth') is not None: if settings.XQUEUE_INTERFACE.get('basic_auth') is not None:
requests_auth = HTTPBasicAuth(*settings.XQUEUE_INTERFACE['basic_auth']) REQUESTS_AUTH = HTTPBasicAuth(*settings.XQUEUE_INTERFACE['basic_auth'])
else: else:
requests_auth = None REQUESTS_AUTH = None
xqueue_interface = XQueueInterface( XQUEUE_INTERFACE = XQueueInterface(
settings.XQUEUE_INTERFACE['url'], settings.XQUEUE_INTERFACE['url'],
settings.XQUEUE_INTERFACE['django_auth'], settings.XQUEUE_INTERFACE['django_auth'],
requests_auth, REQUESTS_AUTH,
) )
# TODO basically all instances of course_id in this file *should* be changed to course_key, but
# there's a couple tricky ones I'm too afraid to change before we merge the jellyfish branches.
# This should be fixed after the jellyfish merge, before merge into master.
def make_track_function(request): def make_track_function(request):
''' '''
...@@ -127,7 +132,7 @@ def toc_for_course(user, request, course, active_chapter, active_section, field_ ...@@ -127,7 +132,7 @@ def toc_for_course(user, request, course, active_chapter, active_section, field_
return chapters return chapters
def get_module(user, request, location, field_data_cache, course_id, def get_module(user, request, usage_key, field_data_cache,
position=None, not_found_ok=False, wrap_xmodule_display=True, position=None, not_found_ok=False, wrap_xmodule_display=True,
grade_bucket_type=None, depth=0, grade_bucket_type=None, depth=0,
static_asset_path=''): static_asset_path=''):
...@@ -157,9 +162,8 @@ def get_module(user, request, location, field_data_cache, course_id, ...@@ -157,9 +162,8 @@ def get_module(user, request, location, field_data_cache, course_id,
if possible. If not possible, return None. if possible. If not possible, return None.
""" """
try: try:
location = Location(location) descriptor = modulestore().get_item(usage_key, depth=depth)
descriptor = modulestore().get_instance(course_id, location, depth=depth) return get_module_for_descriptor(user, request, descriptor, field_data_cache, usage_key.course_key,
return get_module_for_descriptor(user, request, descriptor, field_data_cache, course_id,
position=position, position=position,
wrap_xmodule_display=wrap_xmodule_display, wrap_xmodule_display=wrap_xmodule_display,
grade_bucket_type=grade_bucket_type, grade_bucket_type=grade_bucket_type,
...@@ -198,7 +202,7 @@ def get_module_for_descriptor(user, request, descriptor, field_data_cache, cours ...@@ -198,7 +202,7 @@ def get_module_for_descriptor(user, request, descriptor, field_data_cache, cours
See get_module() docstring for further details. See get_module() docstring for further details.
""" """
# allow course staff to masquerade as student # allow course staff to masquerade as student
if has_access(user, descriptor, 'staff', course_id): if has_access(user, 'staff', descriptor, course_id):
setup_masquerade(request, True) setup_masquerade(request, True)
track_function = make_track_function(request) track_function = make_track_function(request)
...@@ -223,7 +227,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -223,7 +227,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
# Do not check access when it's a noauth request. # Do not check access when it's a noauth request.
if getattr(user, 'known', True): if getattr(user, 'known', True):
# Short circuit--if the user shouldn't have access, bail without doing any work # Short circuit--if the user shouldn't have access, bail without doing any work
if not has_access(user, descriptor, 'load', course_id): if not has_access(user, 'load', descriptor, course_id):
return None return None
student_data = KvsFieldData(DjangoKeyValueStore(field_data_cache)) student_data = KvsFieldData(DjangoKeyValueStore(field_data_cache))
...@@ -234,9 +238,9 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -234,9 +238,9 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
relative_xqueue_callback_url = reverse( relative_xqueue_callback_url = reverse(
'xqueue_callback', 'xqueue_callback',
kwargs=dict( kwargs=dict(
course_id=course_id, course_id=course_id.to_deprecated_string(),
userid=str(user.id), userid=str(user.id),
mod_id=descriptor.location.url(), mod_id=descriptor.location.to_deprecated_string(),
dispatch=dispatch dispatch=dispatch
), ),
) )
...@@ -248,7 +252,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -248,7 +252,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course xqueue_default_queuename = descriptor.location.org + '-' + descriptor.location.course
xqueue = { xqueue = {
'interface': xqueue_interface, 'interface': XQUEUE_INTERFACE,
'construct_callback': make_xqueue_callback, 'construct_callback': make_xqueue_callback,
'default_queuename': xqueue_default_queuename.replace(' ', '_'), 'default_queuename': xqueue_default_queuename.replace(' ', '_'),
'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS 'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
...@@ -312,12 +316,10 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -312,12 +316,10 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
# Bin score into range and increment stats # Bin score into range and increment stats
score_bucket = get_score_bucket(student_module.grade, student_module.max_grade) score_bucket = get_score_bucket(student_module.grade, student_module.max_grade)
course_id_dict = Location.parse_course_id(course_id)
tags = [ tags = [
u"org:{org}".format(**course_id_dict), u"org:{}".format(course_id.org),
u"course:{course}".format(**course_id_dict), u"course:{}".format(course_id),
u"run:{name}".format(**course_id_dict),
u"score_bucket:{0}".format(score_bucket) u"score_bucket:{0}".format(score_bucket)
] ]
...@@ -340,7 +342,11 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -340,7 +342,11 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
# Wrap the output display in a single div to allow for the XModule # Wrap the output display in a single div to allow for the XModule
# javascript to be bound correctly # javascript to be bound correctly
if wrap_xmodule_display is True: if wrap_xmodule_display is True:
block_wrappers.append(partial(wrap_xblock, 'LmsRuntime', extra_data={'course-id': course_id})) block_wrappers.append(partial(
wrap_xblock, 'LmsRuntime',
extra_data={'course-id': course_id.to_deprecated_string()},
usage_id_serializer=lambda usage_id: quote_slashes(usage_id.to_deprecated_string())
))
# TODO (cpennington): When modules are shared between courses, the static # TODO (cpennington): When modules are shared between courses, the static
# prefix is going to have to be specific to the module, not the directory # prefix is going to have to be specific to the module, not the directory
...@@ -366,11 +372,11 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -366,11 +372,11 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
block_wrappers.append(partial( block_wrappers.append(partial(
replace_jump_to_id_urls, replace_jump_to_id_urls,
course_id, course_id,
reverse('jump_to_id', kwargs={'course_id': course_id, 'module_id': ''}), reverse('jump_to_id', kwargs={'course_id': course_id.to_deprecated_string(), 'module_id': ''}),
)) ))
if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'): if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'):
if has_access(user, descriptor, 'staff', course_id): if has_access(user, 'staff', descriptor, course_id):
block_wrappers.append(partial(add_staff_markup, user)) block_wrappers.append(partial(add_staff_markup, user))
# These modules store data using the anonymous_student_id as a key. # These modules store data using the anonymous_student_id as a key.
...@@ -385,7 +391,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -385,7 +391,7 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
if is_pure_xblock or is_lti_module: if is_pure_xblock or is_lti_module:
anonymous_student_id = anonymous_id_for_user(user, course_id) anonymous_student_id = anonymous_id_for_user(user, course_id)
else: else:
anonymous_student_id = anonymous_id_for_user(user, '') anonymous_student_id = anonymous_id_for_user(user, None)
system = LmsModuleSystem( system = LmsModuleSystem(
track_function=track_function, track_function=track_function,
...@@ -409,12 +415,12 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -409,12 +415,12 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
), ),
replace_course_urls=partial( replace_course_urls=partial(
static_replace.replace_course_urls, static_replace.replace_course_urls,
course_id=course_id course_key=course_id
), ),
replace_jump_to_id_urls=partial( replace_jump_to_id_urls=partial(
static_replace.replace_jump_to_id_urls, static_replace.replace_jump_to_id_urls,
course_id=course_id, course_id=course_id,
jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': course_id, 'module_id': ''}) jump_to_id_base_url=reverse('jump_to_id', kwargs={'course_id': course_id.to_deprecated_string(), 'module_id': ''})
), ),
node_path=settings.NODE_PATH, node_path=settings.NODE_PATH,
publish=publish, publish=publish,
...@@ -440,13 +446,13 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours ...@@ -440,13 +446,13 @@ def get_module_for_descriptor_internal(user, descriptor, field_data_cache, cours
if settings.FEATURES.get('ENABLE_PSYCHOMETRICS'): if settings.FEATURES.get('ENABLE_PSYCHOMETRICS'):
system.set( system.set(
'psychometrics_handler', # set callback for updating PsychometricsData 'psychometrics_handler', # set callback for updating PsychometricsData
make_psychometrics_data_update_handler(course_id, user, descriptor.location.url()) make_psychometrics_data_update_handler(course_id, user, descriptor.location.to_deprecated_string())
) )
system.set(u'user_is_staff', has_access(user, descriptor.location, u'staff', course_id)) system.set(u'user_is_staff', has_access(user, u'staff', descriptor.location, course_id))
# make an ErrorDescriptor -- assuming that the descriptor's system is ok # make an ErrorDescriptor -- assuming that the descriptor's system is ok
if has_access(user, descriptor.location, 'staff', course_id): if has_access(user, u'staff', descriptor.location, course_id):
system.error_descriptor_class = ErrorDescriptor system.error_descriptor_class = ErrorDescriptor
else: else:
system.error_descriptor_class = NonStaffErrorDescriptor system.error_descriptor_class = NonStaffErrorDescriptor
...@@ -460,15 +466,17 @@ def find_target_student_module(request, user_id, course_id, mod_id): ...@@ -460,15 +466,17 @@ def find_target_student_module(request, user_id, course_id, mod_id):
""" """
Retrieve target StudentModule Retrieve target StudentModule
""" """
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
usage_key = course_id.make_usage_key_from_deprecated_string(mod_id)
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
field_data_cache = FieldDataCache.cache_for_descriptor_descendents( field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
course_id, course_id,
user, user,
modulestore().get_instance(course_id, mod_id), modulestore().get_item(usage_key),
depth=0, depth=0,
select_for_update=True select_for_update=True
) )
instance = get_module(user, request, mod_id, field_data_cache, course_id, grade_bucket_type='xqueue') instance = get_module(user, request, usage_key, field_data_cache, grade_bucket_type='xqueue')
if instance is None: if instance is None:
msg = "No module {0} for user {1}--access denied?".format(mod_id, user) msg = "No module {0} for user {1}--access denied?".format(mod_id, user)
log.debug(msg) log.debug(msg)
...@@ -566,11 +574,19 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user): ...@@ -566,11 +574,19 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
""" """
Invoke an XBlock handler, either authenticated or not. Invoke an XBlock handler, either authenticated or not.
""" Arguments:
location = unquote_slashes(usage_id) request (HttpRequest): the current request
course_id (str): A string of the form org/course/run
usage_id (str): A string of the form i4x://org/course/category/name@revision
handler (str): The name of the handler to invoke
suffix (str): The suffix to pass to the handler when invoked
user (User): The currently logged in user
# Check parameters and fail fast if there's a problem """
if not Location.is_valid(location): try:
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
usage_key = course_id.make_usage_key_from_deprecated_string(unquote_slashes(usage_id))
except InvalidKeyError:
raise Http404("Invalid location") raise Http404("Invalid location")
# Check submitted files # Check submitted files
...@@ -580,12 +596,12 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user): ...@@ -580,12 +596,12 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
return HttpResponse(json.dumps({'success': error_msg})) return HttpResponse(json.dumps({'success': error_msg}))
try: try:
descriptor = modulestore().get_instance(course_id, location) descriptor = modulestore().get_item(usage_key)
except ItemNotFoundError: except ItemNotFoundError:
log.warn( log.warn(
"Invalid location for course id {course_id}: {location}".format( "Invalid location for course id {course_id}: {usage_key}".format(
course_id=course_id, course_id=usage_key.course_key,
location=location usage_key=usage_key
) )
) )
raise Http404 raise Http404
...@@ -602,11 +618,11 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user): ...@@ -602,11 +618,11 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, user):
user, user,
descriptor descriptor
) )
instance = get_module(user, request, location, field_data_cache, course_id, grade_bucket_type='ajax') instance = get_module(user, request, usage_key, field_data_cache, grade_bucket_type='ajax')
if instance is None: if instance is None:
# Either permissions just changed, or someone is trying to be clever # Either permissions just changed, or someone is trying to be clever
# and load something they shouldn't have access to. # and load something they shouldn't have access to.
log.debug("No module %s for user %s -- access denied?", location, user) log.debug("No module %s for user %s -- access denied?", usage_key, user)
raise Http404 raise Http404
req = django_to_webob_request(request) req = django_to_webob_request(request)
......
...@@ -94,7 +94,7 @@ class BaseTestXmodule(ModuleStoreTestCase): ...@@ -94,7 +94,7 @@ class BaseTestXmodule(ModuleStoreTestCase):
#self.item_module = self.item_descriptor.xmodule_runtime.xmodule_instance #self.item_module = self.item_descriptor.xmodule_runtime.xmodule_instance
#self.item_module is None at this time #self.item_module is None at this time
self.item_url = Location(self.item_descriptor.location).url() self.item_url = self.item_descriptor.location.to_deprecated_string()
def setup_course(self): def setup_course(self):
self.course = CourseFactory.create(data=self.COURSE_DATA) self.course = CourseFactory.create(data=self.COURSE_DATA)
...@@ -139,7 +139,7 @@ class BaseTestXmodule(ModuleStoreTestCase): ...@@ -139,7 +139,7 @@ class BaseTestXmodule(ModuleStoreTestCase):
"""Return item url with dispatch.""" """Return item url with dispatch."""
return reverse( return reverse(
'xblock_handler', 'xblock_handler',
args=(self.course.id, quote_slashes(self.item_url), 'xmodule_handler', dispatch) args=(self.course.id.to_deprecated_string(), quote_slashes(self.item_url), 'xmodule_handler', dispatch)
) )
......
...@@ -6,9 +6,6 @@ from factory.django import DjangoModelFactory ...@@ -6,9 +6,6 @@ from factory.django import DjangoModelFactory
# Imported to re-export # Imported to re-export
# pylint: disable=unused-import # pylint: disable=unused-import
from student.tests.factories import UserFactory # Imported to re-export from student.tests.factories import UserFactory # Imported to re-export
from student.tests.factories import GroupFactory # Imported to re-export
from student.tests.factories import CourseEnrollmentAllowedFactory # Imported to re-export
from student.tests.factories import RegistrationFactory # Imported to re-export
# pylint: enable=unused-import # pylint: enable=unused-import
from student.tests.factories import UserProfileFactory as StudentUserProfileFactory from student.tests.factories import UserProfileFactory as StudentUserProfileFactory
...@@ -23,10 +20,11 @@ from student.roles import ( ...@@ -23,10 +20,11 @@ from student.roles import (
OrgInstructorRole, OrgInstructorRole,
) )
from xmodule.modulestore import Location from xmodule.modulestore.locations import SlashSeparatedCourseKey
location = partial(Location, 'i4x', 'edX', 'test_course', 'problem') course_id = SlashSeparatedCourseKey(u'edX', u'test_course', u'test')
location = partial(course_id.make_usage_key, u'problem')
class UserProfileFactory(StudentUserProfileFactory): class UserProfileFactory(StudentUserProfileFactory):
...@@ -41,9 +39,10 @@ class InstructorFactory(UserFactory): ...@@ -41,9 +39,10 @@ class InstructorFactory(UserFactory):
last_name = "Instructor" last_name = "Instructor"
@factory.post_generation @factory.post_generation
# TODO Change this from course to course_key at next opportunity
def course(self, create, extracted, **kwargs): def course(self, create, extracted, **kwargs):
if extracted is None: if extracted is None:
raise ValueError("Must specify a course location for a course instructor user") raise ValueError("Must specify a CourseKey for a course instructor user")
CourseInstructorRole(extracted).add_users(self) CourseInstructorRole(extracted).add_users(self)
...@@ -55,9 +54,10 @@ class StaffFactory(UserFactory): ...@@ -55,9 +54,10 @@ class StaffFactory(UserFactory):
last_name = "Staff" last_name = "Staff"
@factory.post_generation @factory.post_generation
# TODO Change this from course to course_key at next opportunity
def course(self, create, extracted, **kwargs): def course(self, create, extracted, **kwargs):
if extracted is None: if extracted is None:
raise ValueError("Must specify a course location for a course staff user") raise ValueError("Must specify a CourseKey for a course staff user")
CourseStaffRole(extracted).add_users(self) CourseStaffRole(extracted).add_users(self)
...@@ -69,9 +69,10 @@ class BetaTesterFactory(UserFactory): ...@@ -69,9 +69,10 @@ class BetaTesterFactory(UserFactory):
last_name = "Beta-Tester" last_name = "Beta-Tester"
@factory.post_generation @factory.post_generation
# TODO Change this from course to course_key at next opportunity
def course(self, create, extracted, **kwargs): def course(self, create, extracted, **kwargs):
if extracted is None: if extracted is None:
raise ValueError("Must specify a course location for a beta-tester user") raise ValueError("Must specify a CourseKey for a beta-tester user")
CourseBetaTesterRole(extracted).add_users(self) CourseBetaTesterRole(extracted).add_users(self)
...@@ -83,10 +84,11 @@ class OrgStaffFactory(UserFactory): ...@@ -83,10 +84,11 @@ class OrgStaffFactory(UserFactory):
last_name = "Org-Staff" last_name = "Org-Staff"
@factory.post_generation @factory.post_generation
# TODO Change this from course to course_key at next opportunity
def course(self, create, extracted, **kwargs): def course(self, create, extracted, **kwargs):
if extracted is None: if extracted is None:
raise ValueError("Must specify a course location for an org-staff user") raise ValueError("Must specify a CourseKey for an org-staff user")
OrgStaffRole(extracted).add_users(self) OrgStaffRole(extracted.org).add_users(self)
class OrgInstructorFactory(UserFactory): class OrgInstructorFactory(UserFactory):
...@@ -97,10 +99,11 @@ class OrgInstructorFactory(UserFactory): ...@@ -97,10 +99,11 @@ class OrgInstructorFactory(UserFactory):
last_name = "Org-Instructor" last_name = "Org-Instructor"
@factory.post_generation @factory.post_generation
# TODO Change this from course to course_key at next opportunity
def course(self, create, extracted, **kwargs): def course(self, create, extracted, **kwargs):
if extracted is None: if extracted is None:
raise ValueError("Must specify a course location for an org-instructor user") raise ValueError("Must specify a CourseKey for an org-instructor user")
OrgInstructorRole(extracted).add_users(self) OrgInstructorRole(extracted.org).add_users(self)
class GlobalStaffFactory(UserFactory): class GlobalStaffFactory(UserFactory):
...@@ -119,7 +122,7 @@ class StudentModuleFactory(DjangoModelFactory): ...@@ -119,7 +122,7 @@ class StudentModuleFactory(DjangoModelFactory):
module_type = "problem" module_type = "problem"
student = factory.SubFactory(UserFactory) student = factory.SubFactory(UserFactory)
course_id = "MITx/999/Robot_Super_Course" course_id = SlashSeparatedCourseKey("MITx", "999", "Robot_Super_Course")
state = None state = None
grade = None grade = None
max_grade = None max_grade = None
...@@ -131,7 +134,7 @@ class UserStateSummaryFactory(DjangoModelFactory): ...@@ -131,7 +134,7 @@ class UserStateSummaryFactory(DjangoModelFactory):
field_name = 'existing_field' field_name = 'existing_field'
value = json.dumps('old_value') value = json.dumps('old_value')
usage_id = location('usage_id').url() usage_id = location('usage_id')
class StudentPrefsFactory(DjangoModelFactory): class StudentPrefsFactory(DjangoModelFactory):
......
...@@ -130,7 +130,7 @@ class LoginEnrollmentTestCase(TestCase): ...@@ -130,7 +130,7 @@ class LoginEnrollmentTestCase(TestCase):
""" """
resp = self.client.post(reverse('change_enrollment'), { resp = self.client.post(reverse('change_enrollment'), {
'enrollment_action': 'enroll', 'enrollment_action': 'enroll',
'course_id': course.id, 'course_id': course.id.to_deprecated_string(),
}) })
result = resp.status_code == 200 result = resp.status_code == 200
if verify: if verify:
...@@ -142,5 +142,7 @@ class LoginEnrollmentTestCase(TestCase): ...@@ -142,5 +142,7 @@ class LoginEnrollmentTestCase(TestCase):
Unenroll the currently logged-in user, and check that it worked. Unenroll the currently logged-in user, and check that it worked.
`course` is an instance of CourseDescriptor. `course` is an instance of CourseDescriptor.
""" """
check_for_post_code(self, 200, reverse('change_enrollment'), {'enrollment_action': 'unenroll', check_for_post_code(self, 200, reverse('change_enrollment'), {
'course_id': course.id}) 'enrollment_action': 'unenroll',
'course_id': course.id.to_deprecated_string()
})
...@@ -9,6 +9,7 @@ from .helpers import LoginEnrollmentTestCase ...@@ -9,6 +9,7 @@ from .helpers import LoginEnrollmentTestCase
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.locations import SlashSeparatedCourseKey
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
...@@ -22,13 +23,13 @@ class AboutTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -22,13 +23,13 @@ class AboutTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
def test_logged_in(self): def test_logged_in(self):
self.setup_user() self.setup_user()
url = reverse('about_course', args=[self.course.id]) url = reverse('about_course', args=[self.course.id.to_deprecated_string()])
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertIn("OOGIE BLOOGIE", resp.content) self.assertIn("OOGIE BLOOGIE", resp.content)
def test_anonymous_user(self): def test_anonymous_user(self):
url = reverse('about_course', args=[self.course.id]) url = reverse('about_course', args=[self.course.id.to_deprecated_string()])
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertIn("OOGIE BLOOGIE", resp.content) self.assertIn("OOGIE BLOOGIE", resp.content)
...@@ -39,7 +40,7 @@ class AboutTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -39,7 +40,7 @@ class AboutTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
# The following XML test course (which lives at common/test/data/2014) # The following XML test course (which lives at common/test/data/2014)
# is closed; we're testing that an about page still appears when # is closed; we're testing that an about page still appears when
# the course is already closed # the course is already closed
xml_course_id = 'edX/detached_pages/2014' xml_course_id = SlashSeparatedCourseKey('edX', 'detached_pages', '2014')
# this text appears in that course's about page # this text appears in that course's about page
# common/test/data/2014/about/overview.html # common/test/data/2014/about/overview.html
...@@ -48,14 +49,14 @@ class AboutTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -48,14 +49,14 @@ class AboutTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
@mock.patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) @mock.patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
def test_logged_in_xml(self): def test_logged_in_xml(self):
self.setup_user() self.setup_user()
url = reverse('about_course', args=[self.xml_course_id]) url = reverse('about_course', args=[self.xml_course_id.to_deprecated_string()])
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertIn(self.xml_data, resp.content) self.assertIn(self.xml_data, resp.content)
@mock.patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) @mock.patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
def test_anonymous_user_xml(self): def test_anonymous_user_xml(self):
url = reverse('about_course', args=[self.xml_course_id]) url = reverse('about_course', args=[self.xml_course_id.to_deprecated_string()])
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertIn(self.xml_data, resp.content) self.assertIn(self.xml_data, resp.content)
...@@ -82,7 +83,7 @@ class AboutWithCappedEnrollmentsTestCase(LoginEnrollmentTestCase, ModuleStoreTes ...@@ -82,7 +83,7 @@ class AboutWithCappedEnrollmentsTestCase(LoginEnrollmentTestCase, ModuleStoreTes
This test will make sure that enrollment caps are enforced This test will make sure that enrollment caps are enforced
""" """
self.setup_user() self.setup_user()
url = reverse('about_course', args=[self.course.id]) url = reverse('about_course', args=[self.course.id.to_deprecated_string()])
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertIn('<a href="#" class="register">', resp.content) self.assertIn('<a href="#" class="register">', resp.content)
......
...@@ -6,11 +6,12 @@ from mock import Mock ...@@ -6,11 +6,12 @@ from mock import Mock
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from courseware.tests.factories import UserFactory, CourseEnrollmentAllowedFactory, StaffFactory, InstructorFactory from courseware.tests.factories import UserFactory, StaffFactory, InstructorFactory
from student.tests.factories import AnonymousUserFactory from student.tests.factories import AnonymousUserFactory, CourseEnrollmentAllowedFactory
from xmodule.modulestore import Location from xmodule.modulestore import Location
from courseware.tests.tests import TEST_DATA_MIXED_MODULESTORE from courseware.tests.tests import TEST_DATA_MIXED_MODULESTORE
import pytz import pytz
from xmodule.modulestore.locations import SlashSeparatedCourseKey
# pylint: disable=protected-access # pylint: disable=protected-access
...@@ -21,129 +22,161 @@ class AccessTestCase(TestCase): ...@@ -21,129 +22,161 @@ class AccessTestCase(TestCase):
""" """
def setUp(self): def setUp(self):
self.course = Location('i4x://edX/toy/course/2012_Fall') course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
self.course = course_key.make_usage_key('course', course_key.run)
self.anonymous_user = AnonymousUserFactory() self.anonymous_user = AnonymousUserFactory()
self.student = UserFactory() self.student = UserFactory()
self.global_staff = UserFactory(is_staff=True) self.global_staff = UserFactory(is_staff=True)
self.course_staff = StaffFactory(course=self.course) # TODO please change the StaffFactory and InstructorFactory parameters ASAP!
self.course_instructor = InstructorFactory(course=self.course) self.course_staff = StaffFactory(course=self.course.course_key)
self.course_instructor = InstructorFactory(course=self.course.course_key)
def test__has_access_to_location(self): def test__has_access_to_location(self):
self.assertFalse(access._has_access_to_location(None, self.course, 'staff', None)) self.assertFalse(access._has_access_to_location(
None, 'staff', self.course, self.course.course_key
self.assertFalse(access._has_access_to_location(self.anonymous_user, self.course, 'staff', None)) ))
self.assertFalse(access._has_access_to_location(self.anonymous_user, self.course, 'instructor', None))
self.assertFalse(access._has_access_to_location(
self.assertTrue(access._has_access_to_location(self.global_staff, self.course, 'staff', None)) self.anonymous_user, 'staff', self.course, self.course.course_key
self.assertTrue(access._has_access_to_location(self.global_staff, self.course, 'instructor', None)) ))
self.assertFalse(access._has_access_to_location(
self.anonymous_user, 'instructor', self.course, self.course.course_key
))
self.assertTrue(access._has_access_to_location(
self.global_staff, 'staff', self.course, self.course.course_key
))
self.assertTrue(access._has_access_to_location(
self.global_staff, 'instructor', self.course, self.course.course_key
))
# A user has staff access if they are in the staff group # A user has staff access if they are in the staff group
self.assertTrue(access._has_access_to_location(self.course_staff, self.course, 'staff', None)) self.assertTrue(access._has_access_to_location(
self.assertFalse(access._has_access_to_location(self.course_staff, self.course, 'instructor', None)) self.course_staff, 'staff', self.course, self.course.course_key
))
self.assertFalse(access._has_access_to_location(
self.course_staff, 'instructor', self.course, self.course.course_key
))
# A user has staff and instructor access if they are in the instructor group # A user has staff and instructor access if they are in the instructor group
self.assertTrue(access._has_access_to_location(self.course_instructor, self.course, 'staff', None)) self.assertTrue(access._has_access_to_location(
self.assertTrue(access._has_access_to_location(self.course_instructor, self.course, 'instructor', None)) self.course_instructor, 'staff', self.course, self.course.course_key
))
self.assertTrue(access._has_access_to_location(
self.course_instructor, 'instructor', self.course, self.course.course_key
))
# A user does not have staff or instructor access if they are # A user does not have staff or instructor access if they are
# not in either the staff or the the instructor group # not in either the staff or the the instructor group
self.assertFalse(access._has_access_to_location(self.student, self.course, 'staff', None)) self.assertFalse(access._has_access_to_location(
self.assertFalse(access._has_access_to_location(self.student, self.course, 'instructor', None)) self.student, 'staff', self.course, self.course.course_key
))
self.assertFalse(access._has_access_to_location(
self.student, 'instructor', self.course, self.course.course_key
))
def test__has_access_string(self): def test__has_access_string(self):
u = Mock(is_staff=True) user = Mock(is_staff=True)
self.assertFalse(access._has_access_string(u, 'not_global', 'staff', None)) self.assertFalse(access._has_access_string(user, 'staff', 'not_global', self.course.course_key))
u._has_global_staff_access.return_value = True user._has_global_staff_access.return_value = True
self.assertTrue(access._has_access_string(u, 'global', 'staff', None)) self.assertTrue(access._has_access_string(user, 'staff', 'global', self.course.course_key))
self.assertRaises(ValueError, access._has_access_string, u, 'global', 'not_staff', None) self.assertRaises(ValueError, access._has_access_string, user, 'not_staff', 'global', self.course.course_key)
def test__has_access_descriptor(self): def test__has_access_descriptor(self):
# TODO: override DISABLE_START_DATES and test the start date branch of the method # TODO: override DISABLE_START_DATES and test the start date branch of the method
u = Mock() user = Mock()
d = Mock() date = Mock()
d.start = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) # make sure the start time is in the past date.start = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) # make sure the start time is in the past
# Always returns true because DISABLE_START_DATES is set in test.py # Always returns true because DISABLE_START_DATES is set in test.py
self.assertTrue(access._has_access_descriptor(u, d, 'load')) self.assertTrue(access._has_access_descriptor(user, 'load', date))
self.assertRaises(ValueError, access._has_access_descriptor, u, d, 'not_load_or_staff') with self.assertRaises(ValueError):
access._has_access_descriptor(user, 'not_load_or_staff', date)
def test__has_access_course_desc_can_enroll(self): def test__has_access_course_desc_can_enroll(self):
u = Mock() user = Mock()
yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1) yesterday = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=1)
tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1) tomorrow = datetime.datetime.now(pytz.utc) + datetime.timedelta(days=1)
c = Mock(enrollment_start=yesterday, enrollment_end=tomorrow, enrollment_domain='') course = Mock(enrollment_start=yesterday, enrollment_end=tomorrow, enrollment_domain='')
# User can enroll if it is between the start and end dates # User can enroll if it is between the start and end dates
self.assertTrue(access._has_access_course_desc(u, c, 'enroll')) self.assertTrue(access._has_access_course_desc(user, 'enroll', course))
# User can enroll if authenticated and specifically allowed for that course # User can enroll if authenticated and specifically allowed for that course
# even outside the open enrollment period # even outside the open enrollment period
u = Mock(email='test@edx.org', is_staff=False) user = Mock(email='test@edx.org', is_staff=False)
u.is_authenticated.return_value = True user.is_authenticated.return_value = True
c = Mock(enrollment_start=tomorrow, enrollment_end=tomorrow, id='edX/test/2012_Fall', enrollment_domain='') course = Mock(
enrollment_start=tomorrow, enrollment_end=tomorrow,
id=SlashSeparatedCourseKey('edX', 'test', '2012_Fall'), enrollment_domain=''
)
allowed = CourseEnrollmentAllowedFactory(email=u.email, course_id=c.id) CourseEnrollmentAllowedFactory(email=user.email, course_id=course.id)
self.assertTrue(access._has_access_course_desc(u, c, 'enroll')) self.assertTrue(access._has_access_course_desc(user, 'enroll', course))
# Staff can always enroll even outside the open enrollment period # Staff can always enroll even outside the open enrollment period
u = Mock(email='test@edx.org', is_staff=True) user = Mock(email='test@edx.org', is_staff=True)
u.is_authenticated.return_value = True user.is_authenticated.return_value = True
c = Mock(enrollment_start=tomorrow, enrollment_end=tomorrow, id='edX/test/Whenever', enrollment_domain='') course = Mock(
self.assertTrue(access._has_access_course_desc(u, c, 'enroll')) enrollment_start=tomorrow, enrollment_end=tomorrow,
id=SlashSeparatedCourseKey('edX', 'test', 'Whenever'), enrollment_domain='',
)
self.assertTrue(access._has_access_course_desc(user, 'enroll', course))
# TODO: # TODO:
# Non-staff cannot enroll outside the open enrollment period if not specifically allowed # Non-staff cannot enroll outside the open enrollment period if not specifically allowed
def test__user_passed_as_none(self): def test__user_passed_as_none(self):
"""Ensure has_access handles a user being passed as null""" """Ensure has_access handles a user being passed as null"""
access.has_access(None, 'global', 'staff', None) access.has_access(None, 'staff', 'global', None)
class UserRoleTestCase(TestCase): class UserRoleTestCase(TestCase):
""" """
Tests for user roles. Tests for user roles.
""" """
def setUp(self): def setUp(self):
self.course = Location('i4x://edX/toy/course/2012_Fall') self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
self.anonymous_user = AnonymousUserFactory() self.anonymous_user = AnonymousUserFactory()
self.student = UserFactory() self.student = UserFactory()
self.global_staff = UserFactory(is_staff=True) self.global_staff = UserFactory(is_staff=True)
self.course_staff = StaffFactory(course=self.course) self.course_staff = StaffFactory(course=self.course_key)
self.course_instructor = InstructorFactory(course=self.course) self.course_instructor = InstructorFactory(course=self.course_key)
def test_user_role_staff(self): def test_user_role_staff(self):
"""Ensure that user role is student for staff masqueraded as student.""" """Ensure that user role is student for staff masqueraded as student."""
self.assertEqual( self.assertEqual(
'staff', 'staff',
access.get_user_role(self.course_staff, self.course.course_id) access.get_user_role(self.course_staff, self.course_key)
) )
# Masquerade staff # Masquerade staff
self.course_staff.masquerade_as_student = True self.course_staff.masquerade_as_student = True
self.assertEqual( self.assertEqual(
'student', 'student',
access.get_user_role(self.course_staff, self.course.course_id) access.get_user_role(self.course_staff, self.course_key)
) )
def test_user_role_instructor(self): def test_user_role_instructor(self):
"""Ensure that user role is student for instructor masqueraded as student.""" """Ensure that user role is student for instructor masqueraded as student."""
self.assertEqual( self.assertEqual(
'instructor', 'instructor',
access.get_user_role(self.course_instructor, self.course.course_id) access.get_user_role(self.course_instructor, self.course_key)
) )
# Masquerade instructor # Masquerade instructor
self.course_instructor.masquerade_as_student = True self.course_instructor.masquerade_as_student = True
self.assertEqual( self.assertEqual(
'student', 'student',
access.get_user_role(self.course_instructor, self.course.course_id) access.get_user_role(self.course_instructor, self.course_key)
) )
def test_user_role_anonymous(self): def test_user_role_anonymous(self):
"""Ensure that user role is student for anonymous user.""" """Ensure that user role is student for anonymous user."""
self.assertEqual( self.assertEqual(
'student', 'student',
access.get_user_role(self.anonymous_user, self.course.course_id) access.get_user_role(self.anonymous_user, self.course_key)
) )
...@@ -22,13 +22,13 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -22,13 +22,13 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
def test_logged_in(self): def test_logged_in(self):
self.setup_user() self.setup_user()
url = reverse('info', args=[self.course.id]) url = reverse('info', args=[self.course.id.to_deprecated_string()])
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertIn("OOGIE BLOOGIE", resp.content) self.assertIn("OOGIE BLOOGIE", resp.content)
def test_anonymous_user(self): def test_anonymous_user(self):
url = reverse('info', args=[self.course.id]) url = reverse('info', args=[self.course.id.to_deprecated_string()])
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertNotIn("OOGIE BLOOGIE", resp.content) self.assertNotIn("OOGIE BLOOGIE", resp.content)
......
...@@ -4,7 +4,6 @@ Tests for course access ...@@ -4,7 +4,6 @@ Tests for course access
""" """
import mock import mock
from django.http import Http404
from django.test.utils import override_settings from django.test.utils import override_settings
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xmodule.modulestore.django import get_default_store_name_for_current_request from xmodule.modulestore.django import get_default_store_name_for_current_request
...@@ -14,16 +13,12 @@ from xmodule.tests.xml import factories as xml ...@@ -14,16 +13,12 @@ from xmodule.tests.xml import factories as xml
from xmodule.tests.xml import XModuleXmlImportTest from xmodule.tests.xml import XModuleXmlImportTest
from courseware.courses import ( from courseware.courses import (
get_course_by_id, get_course_by_id, get_cms_course_link, course_image_url,
get_course, get_course_info_section, get_course_about_section, get_course
get_cms_course_link,
get_cms_block_link,
course_image_url,
get_course_info_section,
get_course_about_section
) )
from courseware.tests.helpers import get_request_for_user from courseware.tests.helpers import get_request_for_user
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE, TEST_DATA_MIXED_MODULESTORE from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE, TEST_DATA_MIXED_MODULESTORE
from xmodule.modulestore.locations import SlashSeparatedCourseKey
CMS_BASE_TEST = 'testcms' CMS_BASE_TEST = 'testcms'
...@@ -34,25 +29,29 @@ class CoursesTest(ModuleStoreTestCase): ...@@ -34,25 +29,29 @@ class CoursesTest(ModuleStoreTestCase):
def test_get_course_by_id_invalid_chars(self): def test_get_course_by_id_invalid_chars(self):
""" """
Test that `get_course_by_id` throws a 404, rather than Test that `get_course` throws a 404, rather than an exception,
an exception, when faced with unexpected characters when faced with unexpected characters (such as unicode characters,
(such as unicode characters, and symbols such as = and ' ') and symbols such as = and ' ')
""" """
with self.assertRaises(Http404): with self.assertRaises(Http404):
get_course_by_id('MITx/foobar/statistics=introduction') get_course_by_id(SlashSeparatedCourseKey('MITx', 'foobar', 'business and management'))
get_course_by_id('MITx/foobar/business and management') with self.assertRaises(Http404):
get_course_by_id('MITx/foobar/NiñøJoséMaríáßç') get_course_by_id(SlashSeparatedCourseKey('MITx', 'foobar' 'statistics=introduction'))
with self.assertRaises(Http404):
get_course_by_id(SlashSeparatedCourseKey('MITx', 'foobar', 'NiñøJoséMaríáßç'))
def test_get_course_invalid_chars(self): def test_get_course_invalid_chars(self):
""" """
Test that `get_course` throws a ValueError, rather than Test that `get_course` throws a ValueError, rather than a 404,
a 404, when faced with unexpected characters when faced with unexpected characters (such as unicode characters,
(such as unicode characters, and symbols such as = and ' ') and symbols such as = and ' ')
""" """
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
get_course('MITx/foobar/statistics=introduction') get_course(SlashSeparatedCourseKey('MITx', 'foobar', 'business and management'))
get_course('MITx/foobar/business and management') with self.assertRaises(ValueError):
get_course('MITx/foobar/NiñøJoséMaríáßç') get_course(SlashSeparatedCourseKey('MITx', 'foobar', 'statistics=introduction'))
with self.assertRaises(ValueError):
get_course(SlashSeparatedCourseKey('MITx', 'foobar', 'NiñøJoséMaríáßç'))
@override_settings( @override_settings(
MODULESTORE=TEST_DATA_MONGO_MODULESTORE, CMS_BASE=CMS_BASE_TEST MODULESTORE=TEST_DATA_MONGO_MODULESTORE, CMS_BASE=CMS_BASE_TEST
...@@ -67,6 +66,7 @@ class CoursesTest(ModuleStoreTestCase): ...@@ -67,6 +66,7 @@ class CoursesTest(ModuleStoreTestCase):
org='org', number='num', display_name='name' org='org', number='num', display_name='name'
) )
cms_url = u"//{}/course/slashes:org+num+name".format(CMS_BASE_TEST)
self.assertEqual(cms_url, get_cms_course_link(self.course)) self.assertEqual(cms_url, get_cms_course_link(self.course))
self.assertEqual(cms_url, get_cms_block_link(self.course, 'course')) self.assertEqual(cms_url, get_cms_block_link(self.course, 'course'))
...@@ -146,10 +146,11 @@ class XmlCourseImageTestCase(XModuleXmlImportTest): ...@@ -146,10 +146,11 @@ class XmlCourseImageTestCase(XModuleXmlImportTest):
class CoursesRenderTest(ModuleStoreTestCase): class CoursesRenderTest(ModuleStoreTestCase):
"""Test methods related to rendering courses content.""" """Test methods related to rendering courses content."""
toy_course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
def test_get_course_info_section_render(self): def test_get_course_info_section_render(self):
course = get_course_by_id('edX/toy/2012_Fall') course = get_course_by_id(self.toy_course_key)
request = get_request_for_user(UserFactory.create()) request = get_request_for_user(UserFactory.create())
# Test render works okay # Test render works okay
...@@ -167,7 +168,7 @@ class CoursesRenderTest(ModuleStoreTestCase): ...@@ -167,7 +168,7 @@ class CoursesRenderTest(ModuleStoreTestCase):
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
@mock.patch('courseware.courses.get_request_for_thread') @mock.patch('courseware.courses.get_request_for_thread')
def test_get_course_about_section_render(self, mock_get_request): def test_get_course_about_section_render(self, mock_get_request):
course = get_course_by_id('edX/toy/2012_Fall') course = get_course_by_id(self.toy_course_key)
request = get_request_for_user(UserFactory.create()) request = get_request_for_user(UserFactory.create())
mock_get_request.return_value = request mock_get_request.return_value = request
......
...@@ -2,7 +2,7 @@ from django.test import TestCase ...@@ -2,7 +2,7 @@ from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location from xmodule.modulestore.locations import SlashSeparatedCourseKey
from modulestore_config import TEST_DATA_DRAFT_MONGO_MODULESTORE from modulestore_config import TEST_DATA_DRAFT_MONGO_MODULESTORE
...@@ -13,8 +13,7 @@ class TestDraftModuleStore(TestCase): ...@@ -13,8 +13,7 @@ class TestDraftModuleStore(TestCase):
store = modulestore() store = modulestore()
# fix was to allow get_items() to take the course_id parameter # fix was to allow get_items() to take the course_id parameter
store.get_items(Location(None, None, 'vertical', None, None), store.get_items(SlashSeparatedCourseKey('a', 'b', 'c'), category='vertical')
course_id='abc', depth=0)
# test success is just getting through the above statement. # test success is just getting through the above statement.
# The bug was that 'course_id' argument was # The bug was that 'course_id' argument was
......
...@@ -9,6 +9,7 @@ from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE ...@@ -9,6 +9,7 @@ from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.locations import SlashSeparatedCourseKey
from courseware.grades import grade, iterate_grades_for from courseware.grades import grade, iterate_grades_for
...@@ -62,7 +63,7 @@ class TestGradeIteration(ModuleStoreTestCase): ...@@ -62,7 +63,7 @@ class TestGradeIteration(ModuleStoreTestCase):
should be raised. This is a horrible crossing of abstraction boundaries should be raised. This is a horrible crossing of abstraction boundaries
and should be fixed, but for now we're just testing the behavior. :-(""" and should be fixed, but for now we're just testing the behavior. :-("""
with self.assertRaises(Http404): with self.assertRaises(Http404):
gradeset_results = iterate_grades_for("I/dont/exist", []) gradeset_results = iterate_grades_for(SlashSeparatedCourseKey("I", "dont", "exist"), [])
gradeset_results.next() gradeset_results.next()
def test_all_empty_grades(self): def test_all_empty_grades(self):
......
...@@ -27,7 +27,8 @@ class TestLTI(BaseTestXmodule): ...@@ -27,7 +27,8 @@ class TestLTI(BaseTestXmodule):
mocked_signature_after_sign = u'my_signature%3D' mocked_signature_after_sign = u'my_signature%3D'
mocked_decoded_signature = u'my_signature=' mocked_decoded_signature = u'my_signature='
context_id = self.item_descriptor.course_id # TODO this course_id is actually a course_key; please change this ASAP!
context_id = self.item_descriptor.course_id.to_deprecated_string()
user_id = unicode(self.item_descriptor.xmodule_runtime.anonymous_student_id) user_id = unicode(self.item_descriptor.xmodule_runtime.anonymous_student_id)
hostname = self.item_descriptor.xmodule_runtime.hostname hostname = self.item_descriptor.xmodule_runtime.hostname
resource_link_id = unicode(urllib.quote('{}-{}'.format(hostname, self.item_descriptor.location.html_id()))) resource_link_id = unicode(urllib.quote('{}-{}'.format(hostname, self.item_descriptor.location.html_id())))
...@@ -38,10 +39,6 @@ class TestLTI(BaseTestXmodule): ...@@ -38,10 +39,6 @@ class TestLTI(BaseTestXmodule):
user_id=user_id user_id=user_id
) )
lis_outcome_service_url = 'https://{host}{path}'.format(
host=hostname,
path=self.item_descriptor.xmodule_runtime.handler_url(self.item_descriptor, 'grade_handler', thirdparty=True).rstrip('/?')
)
self.correct_headers = { self.correct_headers = {
u'user_id': user_id, u'user_id': user_id,
u'oauth_callback': u'about:blank', u'oauth_callback': u'about:blank',
......
...@@ -12,14 +12,14 @@ import json ...@@ -12,14 +12,14 @@ import json
from django.test.utils import override_settings from django.test.utils import override_settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from courseware.tests.factories import StaffFactory
from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.helpers import LoginEnrollmentTestCase
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from student.roles import CourseStaffRole
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.django import modulestore, clear_existing_modulestores from xmodule.modulestore.django import modulestore, clear_existing_modulestores
from lms.lib.xblock.runtime import quote_slashes from lms.lib.xblock.runtime import quote_slashes
from xmodule.modulestore.locations import SlashSeparatedCourseKey
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
...@@ -33,26 +33,19 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -33,26 +33,19 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
# Clear out the modulestores, causing them to reload # Clear out the modulestores, causing them to reload
clear_existing_modulestores() clear_existing_modulestores()
self.graded_course = modulestore().get_course("edX/graded/2012_Fall") self.graded_course = modulestore().get_course(SlashSeparatedCourseKey("edX", "graded", "2012_Fall"))
# Create staff account # Create staff account
self.instructor = 'view2@test.com' self.staff = StaffFactory(course=self.graded_course.id)
self.password = 'foo'
self.create_account('u2', self.instructor, self.password)
self.activate_user(self.instructor)
def make_instructor(course):
CourseStaffRole(course.location).add_users(User.objects.get(email=self.instructor))
make_instructor(self.graded_course)
self.logout() self.logout()
self.login(self.instructor, self.password) # self.staff.password is the sha hash but login takes the plain text
self.login(self.staff.email, 'test')
self.enroll(self.graded_course) self.enroll(self.graded_course)
def get_cw_section(self): def get_cw_section(self):
url = reverse('courseware_section', url = reverse('courseware_section',
kwargs={'course_id': self.graded_course.id, kwargs={'course_id': self.graded_course.id.to_deprecated_string(),
'chapter': 'GradedChapter', 'chapter': 'GradedChapter',
'section': 'Homework1'}) 'section': 'Homework1'})
...@@ -64,7 +57,7 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -64,7 +57,7 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
def test_staff_debug_for_staff(self): def test_staff_debug_for_staff(self):
resp = self.get_cw_section() resp = self.get_cw_section()
sdebug = 'Staff Debug Info' sdebug = 'Staff Debug Info'
print resp.content
self.assertTrue(sdebug in resp.content) self.assertTrue(sdebug in resp.content)
def toggle_masquerade(self): def toggle_masquerade(self):
...@@ -88,11 +81,11 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase) ...@@ -88,11 +81,11 @@ class TestStaffMasqueradeAsStudent(ModuleStoreTestCase, LoginEnrollmentTestCase)
def get_problem(self): def get_problem(self):
pun = 'H1P1' pun = 'H1P1'
problem_location = "i4x://edX/graded/problem/%s" % pun problem_location = self.graded_course.id.make_usage_key("problem", pun)
modx_url = reverse('xblock_handler', modx_url = reverse('xblock_handler',
kwargs={'course_id': self.graded_course.id, kwargs={'course_id': self.graded_course.id.to_deprecated_string(),
'usage_id': quote_slashes(problem_location), 'usage_id': quote_slashes(problem_location.to_deprecated_string()),
'handler': 'xmodule_handler', 'handler': 'xmodule_handler',
'suffix': 'problem_get'}) 'suffix': 'problem_get'})
......
...@@ -11,12 +11,11 @@ from courseware.models import StudentModule, XModuleUserStateSummaryField ...@@ -11,12 +11,11 @@ from courseware.models import StudentModule, XModuleUserStateSummaryField
from courseware.models import XModuleStudentInfoField, XModuleStudentPrefsField from courseware.models import XModuleStudentInfoField, XModuleStudentPrefsField
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from courseware.tests.factories import StudentModuleFactory as cmfStudentModuleFactory from courseware.tests.factories import StudentModuleFactory as cmfStudentModuleFactory, location, course_id
from courseware.tests.factories import UserStateSummaryFactory from courseware.tests.factories import UserStateSummaryFactory
from courseware.tests.factories import StudentPrefsFactory, StudentInfoFactory from courseware.tests.factories import StudentPrefsFactory, StudentInfoFactory
from xblock.fields import Scope, BlockScope, ScopeIds from xblock.fields import Scope, BlockScope, ScopeIds
from xmodule.modulestore import Location
from django.test import TestCase from django.test import TestCase
from django.db import DatabaseError from django.db import DatabaseError
from xblock.core import KeyValueMultiSaveError from xblock.core import KeyValueMultiSaveError
...@@ -37,9 +36,6 @@ def mock_descriptor(fields=[]): ...@@ -37,9 +36,6 @@ def mock_descriptor(fields=[]):
descriptor.module_class.__name__ = 'MockProblemModule' descriptor.module_class.__name__ = 'MockProblemModule'
return descriptor return descriptor
location = partial(Location, 'i4x', 'edX', 'test_course', 'problem')
course_id = 'edX/test_course/test'
# The user ids here are 1 because we make a student in the setUp functions, and # The user ids here are 1 because we make a student in the setUp functions, and
# they get an id of 1. There's an assertion in setUp to ensure that assumption # they get an id of 1. There's an assertion in setUp to ensure that assumption
# is still true. # is still true.
...@@ -51,7 +47,7 @@ user_info_key = partial(DjangoKeyValueStore.Key, Scope.user_info, 1, None) ...@@ -51,7 +47,7 @@ user_info_key = partial(DjangoKeyValueStore.Key, Scope.user_info, 1, None)
class StudentModuleFactory(cmfStudentModuleFactory): class StudentModuleFactory(cmfStudentModuleFactory):
module_state_key = location('usage_id').url() module_state_key = location('usage_id')
course_id = course_id course_id = course_id
...@@ -204,7 +200,7 @@ class TestMissingStudentModule(TestCase): ...@@ -204,7 +200,7 @@ class TestMissingStudentModule(TestCase):
student_module = StudentModule.objects.all()[0] student_module = StudentModule.objects.all()[0]
self.assertEquals({'a_field': 'a_value'}, json.loads(student_module.state)) self.assertEquals({'a_field': 'a_value'}, json.loads(student_module.state))
self.assertEquals(self.user, student_module.student) self.assertEquals(self.user, student_module.student)
self.assertEquals(location('usage_id').url(), student_module.module_state_key) self.assertEquals(location('usage_id'), student_module.module_state_key)
self.assertEquals(course_id, student_module.course_id) self.assertEquals(course_id, student_module.course_id)
def test_delete_field_from_missing_student_module(self): def test_delete_field_from_missing_student_module(self):
...@@ -317,12 +313,12 @@ class StorageTestBase(object): ...@@ -317,12 +313,12 @@ class StorageTestBase(object):
self.assertEquals(exception.saved_field_names[0], 'existing_field') self.assertEquals(exception.saved_field_names[0], 'existing_field')
class TestContentStorage(StorageTestBase, TestCase): class TestUserStateSummaryStorage(StorageTestBase, TestCase):
"""Tests for ContentStorage""" """Tests for UserStateSummaryStorage"""
factory = UserStateSummaryFactory factory = UserStateSummaryFactory
scope = Scope.user_state_summary scope = Scope.user_state_summary
key_factory = user_state_summary_key key_factory = user_state_summary_key
storage_class = XModuleUserStateSummaryField storage_class = factory.FACTORY_FOR
class TestStudentPrefsStorage(OtherUserFailureTestMixin, StorageTestBase, TestCase): class TestStudentPrefsStorage(OtherUserFailureTestMixin, StorageTestBase, TestCase):
......
...@@ -18,11 +18,11 @@ from xblock.field_data import FieldData ...@@ -18,11 +18,11 @@ from xblock.field_data import FieldData
from xblock.runtime import Runtime from xblock.runtime import Runtime
from xblock.fields import ScopeIds from xblock.fields import ScopeIds
from xmodule.lti_module import LTIDescriptor from xmodule.lti_module import LTIDescriptor
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
from xmodule.x_module import XModuleDescriptor from xmodule.x_module import XModuleDescriptor
from xmodule.modulestore.locations import SlashSeparatedCourseKey
from courseware import module_render as render from courseware import module_render as render
from courseware.courses import get_course_with_access, course_image_url, get_course_info_section from courseware.courses import get_course_with_access, course_image_url, get_course_info_section
...@@ -43,9 +43,9 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -43,9 +43,9 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
Tests of courseware.module_render Tests of courseware.module_render
""" """
def setUp(self): def setUp(self):
self.location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview') self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
self.course_id = 'edX/toy/2012_Fall' self.location = self.course_key.make_usage_key('chapter', 'Overview')
self.toy_course = modulestore().get_course(self.course_id) self.toy_course = modulestore().get_course(self.course_key)
self.mock_user = UserFactory() self.mock_user = UserFactory()
self.mock_user.id = 1 self.mock_user.id = 1
self.request_factory = RequestFactory() self.request_factory = RequestFactory()
...@@ -56,7 +56,7 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -56,7 +56,7 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.dispatch = 'score_update' self.dispatch = 'score_update'
# Construct a 'standard' xqueue_callback url # Construct a 'standard' xqueue_callback url
self.callback_url = reverse('xqueue_callback', kwargs=dict(course_id=self.course_id, self.callback_url = reverse('xqueue_callback', kwargs=dict(course_id=self.course_key.to_deprecated_string(),
userid=str(self.mock_user.id), userid=str(self.mock_user.id),
mod_id=self.mock_module.id, mod_id=self.mock_module.id,
dispatch=self.dispatch)) dispatch=self.dispatch))
...@@ -76,17 +76,17 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -76,17 +76,17 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
mock_request = MagicMock() mock_request = MagicMock()
mock_request.user = self.mock_user mock_request.user = self.mock_user
course = get_course_with_access(self.mock_user, self.course_id, 'load') course = get_course_with_access(self.mock_user, 'load', self.course_key)
field_data_cache = FieldDataCache.cache_for_descriptor_descendents( field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
self.course_id, self.mock_user, course, depth=2) self.course_key, self.mock_user, course, depth=2)
module = render.get_module( module = render.get_module(
self.mock_user, self.mock_user,
mock_request, mock_request,
Location('i4x', 'edX', 'toy', 'html', 'toyjumpto'), self.course_key.make_usage_key('html', 'toyjumpto'),
field_data_cache, field_data_cache,
self.course_id self.course_key
) )
# get the rendered HTML output which should have the rewritten link # get the rendered HTML output which should have the rewritten link
...@@ -94,7 +94,7 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -94,7 +94,7 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
# See if the url got rewritten to the target link # See if the url got rewritten to the target link
# note if the URL mapping changes then this assertion will break # note if the URL mapping changes then this assertion will break
self.assertIn('/courses/' + self.course_id + '/jump_to_id/vertical_test', html) self.assertIn('/courses/' + self.course_key.to_deprecated_string() + '/jump_to_id/vertical_test', html)
def test_xqueue_callback_success(self): def test_xqueue_callback_success(self):
...@@ -113,7 +113,7 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -113,7 +113,7 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
get_fake_module.return_value = self.mock_module get_fake_module.return_value = self.mock_module
# call xqueue_callback with our mocked information # call xqueue_callback with our mocked information
request = self.request_factory.post(self.callback_url, data) request = self.request_factory.post(self.callback_url, data)
render.xqueue_callback(request, self.course_id, self.mock_user.id, self.mock_module.id, self.dispatch) render.xqueue_callback(request, self.course_key, self.mock_user.id, self.mock_module.id, self.dispatch)
# Verify that handle ajax is called with the correct data # Verify that handle ajax is called with the correct data
request.POST['queuekey'] = fake_key request.POST['queuekey'] = fake_key
...@@ -130,12 +130,12 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -130,12 +130,12 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
# Test with missing xqueue data # Test with missing xqueue data
with self.assertRaises(Http404): with self.assertRaises(Http404):
request = self.request_factory.post(self.callback_url, {}) request = self.request_factory.post(self.callback_url, {})
render.xqueue_callback(request, self.course_id, self.mock_user.id, self.mock_module.id, self.dispatch) render.xqueue_callback(request, self.course_key, self.mock_user.id, self.mock_module.id, self.dispatch)
# Test with missing xqueue_header # Test with missing xqueue_header
with self.assertRaises(Http404): with self.assertRaises(Http404):
request = self.request_factory.post(self.callback_url, data) request = self.request_factory.post(self.callback_url, data)
render.xqueue_callback(request, self.course_id, self.mock_user.id, self.mock_module.id, self.dispatch) render.xqueue_callback(request, self.course_key, self.mock_user.id, self.mock_module.id, self.dispatch)
def test_get_score_bucket(self): def test_get_score_bucket(self):
self.assertEquals(render.get_score_bucket(0, 10), 'incorrect') self.assertEquals(render.get_score_bucket(0, 10), 'incorrect')
...@@ -149,8 +149,8 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -149,8 +149,8 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
dispatch_url = reverse( dispatch_url = reverse(
'xblock_handler', 'xblock_handler',
args=[ args=[
'edX/toy/2012_Fall', self.course_key.to_deprecated_string(),
quote_slashes('i4x://edX/toy/videosequence/Toy_Videos'), quote_slashes(self.course_key.make_usage_key('videosequence', 'Toy_Videos').to_deprecated_string()),
'xmodule_handler', 'xmodule_handler',
'goto_position' 'goto_position'
] ]
...@@ -166,9 +166,9 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -166,9 +166,9 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
""" """
def setUp(self): def setUp(self):
self.location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview') self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
self.course_id = 'edX/toy/2012_Fall' self.location = self.course_key.make_usage_key('chapter', 'Overview')
self.toy_course = modulestore().get_course(self.course_id) self.toy_course = modulestore().get_course(self.course_key)
self.mock_user = UserFactory() self.mock_user = UserFactory()
self.mock_user.id = 1 self.mock_user.id = 1
self.request_factory = RequestFactory() self.request_factory = RequestFactory()
...@@ -179,10 +179,14 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -179,10 +179,14 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.dispatch = 'score_update' self.dispatch = 'score_update'
# Construct a 'standard' xqueue_callback url # Construct a 'standard' xqueue_callback url
self.callback_url = reverse('xqueue_callback', kwargs=dict(course_id=self.course_id, self.callback_url = reverse(
userid=str(self.mock_user.id), 'xqueue_callback', kwargs={
mod_id=self.mock_module.id, 'course_id': self.course_key.to_deprecated_string(),
dispatch=self.dispatch)) 'userid': str(self.mock_user.id),
'mod_id': self.mock_module.id,
'dispatch': self.dispatch
}
)
def _mock_file(self, name='file', size=10): def _mock_file(self, name='file', size=10):
"""Create a mock file object for testing uploads""" """Create a mock file object for testing uploads"""
...@@ -201,7 +205,7 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -201,7 +205,7 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
with self.assertRaises(Http404): with self.assertRaises(Http404):
render.handle_xblock_callback( render.handle_xblock_callback(
request, request,
'dummy/course/id', self.course_key.to_deprecated_string(),
'invalid Location', 'invalid Location',
'dummy_handler' 'dummy_handler'
'dummy_dispatch' 'dummy_dispatch'
...@@ -216,8 +220,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -216,8 +220,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.assertEquals( self.assertEquals(
render.handle_xblock_callback( render.handle_xblock_callback(
request, request,
'dummy/course/id', self.course_key.to_deprecated_string(),
quote_slashes(str(self.location)), quote_slashes(self.location.to_deprecated_string()),
'dummy_handler' 'dummy_handler'
).content, ).content,
json.dumps({ json.dumps({
...@@ -236,8 +240,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -236,8 +240,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.assertEquals( self.assertEquals(
render.handle_xblock_callback( render.handle_xblock_callback(
request, request,
'dummy/course/id', self.course_key.to_deprecated_string(),
quote_slashes(str(self.location)), quote_slashes(self.location.to_deprecated_string()),
'dummy_handler' 'dummy_handler'
).content, ).content,
json.dumps({ json.dumps({
...@@ -251,8 +255,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -251,8 +255,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
request.user = self.mock_user request.user = self.mock_user
response = render.handle_xblock_callback( response = render.handle_xblock_callback(
request, request,
self.course_id, self.course_key.to_deprecated_string(),
quote_slashes(str(self.location)), quote_slashes(self.location.to_deprecated_string()),
'xmodule_handler', 'xmodule_handler',
'goto_position', 'goto_position',
) )
...@@ -265,7 +269,7 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -265,7 +269,7 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
render.handle_xblock_callback( render.handle_xblock_callback(
request, request,
'bad_course_id', 'bad_course_id',
quote_slashes(str(self.location)), quote_slashes(self.location.to_deprecated_string()),
'xmodule_handler', 'xmodule_handler',
'goto_position', 'goto_position',
) )
...@@ -276,8 +280,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -276,8 +280,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
with self.assertRaises(Http404): with self.assertRaises(Http404):
render.handle_xblock_callback( render.handle_xblock_callback(
request, request,
self.course_id, self.course_key.to_deprecated_string(),
quote_slashes(str(Location('i4x', 'edX', 'toy', 'chapter', 'bad_location'))), quote_slashes(self.course_key.make_usage_key('chapter', 'bad_location').to_deprecated_string()),
'xmodule_handler', 'xmodule_handler',
'goto_position', 'goto_position',
) )
...@@ -288,8 +292,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -288,8 +292,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
with self.assertRaises(Http404): with self.assertRaises(Http404):
render.handle_xblock_callback( render.handle_xblock_callback(
request, request,
self.course_id, self.course_key.to_deprecated_string(),
quote_slashes(str(self.location)), quote_slashes(self.location.to_deprecated_string()),
'xmodule_handler', 'xmodule_handler',
'bad_dispatch', 'bad_dispatch',
) )
...@@ -300,8 +304,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -300,8 +304,8 @@ class TestHandleXBlockCallback(ModuleStoreTestCase, LoginEnrollmentTestCase):
with self.assertRaises(Http404): with self.assertRaises(Http404):
render.handle_xblock_callback( render.handle_xblock_callback(
request, request,
self.course_id, self.course_key.to_deprecated_string(),
quote_slashes(str(self.location)), quote_slashes(self.location.to_deprecated_string()),
'bad_handler', 'bad_handler',
'bad_dispatch', 'bad_dispatch',
) )
...@@ -313,13 +317,13 @@ class TestTOC(TestCase): ...@@ -313,13 +317,13 @@ class TestTOC(TestCase):
def setUp(self): def setUp(self):
# Toy courses should be loaded # Toy courses should be loaded
self.course_name = 'edX/toy/2012_Fall' self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
self.toy_course = modulestore().get_course(self.course_name) self.toy_course = modulestore().get_course(self.course_key)
self.portal_user = UserFactory() self.portal_user = UserFactory()
def test_toc_toy_from_chapter(self): def test_toc_toy_from_chapter(self):
chapter = 'Overview' chapter = 'Overview'
chapter_url = '%s/%s/%s' % ('/courses', self.course_name, chapter) chapter_url = '%s/%s/%s' % ('/courses', self.course_key, chapter)
factory = RequestFactory() factory = RequestFactory()
request = factory.get(chapter_url) request = factory.get(chapter_url)
field_data_cache = FieldDataCache.cache_for_descriptor_descendents( field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
...@@ -346,7 +350,7 @@ class TestTOC(TestCase): ...@@ -346,7 +350,7 @@ class TestTOC(TestCase):
def test_toc_toy_from_section(self): def test_toc_toy_from_section(self):
chapter = 'Overview' chapter = 'Overview'
chapter_url = '%s/%s/%s' % ('/courses', self.course_name, chapter) chapter_url = '%s/%s/%s' % ('/courses', self.course_key, chapter)
section = 'Welcome' section = 'Welcome'
factory = RequestFactory() factory = RequestFactory()
request = factory.get(chapter_url) request = factory.get(chapter_url)
...@@ -506,7 +510,7 @@ class TestHtmlModifiers(ModuleStoreTestCase): ...@@ -506,7 +510,7 @@ class TestHtmlModifiers(ModuleStoreTestCase):
self.assertIn( self.assertIn(
'/courses/{course_id}/bar/content'.format( '/courses/{course_id}/bar/content'.format(
course_id=self.course.id course_id=self.course.id.to_deprecated_string()
), ),
result_fragment.content result_fragment.content
) )
...@@ -558,11 +562,11 @@ class ViewInStudioTest(ModuleStoreTestCase): ...@@ -558,11 +562,11 @@ class ViewInStudioTest(ModuleStoreTestCase):
Define the XML backed course to use. Define the XML backed course to use.
Toy courses are already loaded in XML and mixed modulestores. Toy courses are already loaded in XML and mixed modulestores.
""" """
course_id = 'edX/toy/2012_Fall' course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview') location = course_key.make_usage_key('chapter', 'Overview')
descriptor = modulestore().get_instance(course_id, location) descriptor = modulestore().get_item(location)
self._get_module(course_id, descriptor, location) self._get_module(course_key, descriptor, location)
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
...@@ -722,7 +726,7 @@ class TestStaffDebugInfo(ModuleStoreTestCase): ...@@ -722,7 +726,7 @@ class TestStaffDebugInfo(ModuleStoreTestCase):
StudentModuleFactory.create( StudentModuleFactory.create(
course_id=self.course.id, course_id=self.course.id,
module_state_key=self.location, module_id=self.location,
student=UserFactory(), student=UserFactory(),
grade=1, grade=1,
max_grade=1, max_grade=1,
...@@ -761,7 +765,7 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -761,7 +765,7 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase):
@patch('courseware.module_render.has_access', Mock(return_value=True)) @patch('courseware.module_render.has_access', Mock(return_value=True))
def _get_anonymous_id(self, course_id, xblock_class): def _get_anonymous_id(self, course_id, xblock_class):
location = Location('dummy_org', 'dummy_course', 'dummy_category', 'dummy_name') location = course_id.make_usage_key('dummy_category', 'dummy_name')
descriptor = Mock( descriptor = Mock(
spec=xblock_class, spec=xblock_class,
_field_data=Mock(spec=FieldData), _field_data=Mock(spec=FieldData),
...@@ -796,7 +800,7 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -796,7 +800,7 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase):
# This value is set by observation, so that later changes to the student # This value is set by observation, so that later changes to the student
# id computation don't break old data # id computation don't break old data
'5afe5d9bb03796557ee2614f5c9611fb', '5afe5d9bb03796557ee2614f5c9611fb',
self._get_anonymous_id(course_id, descriptor_class) self._get_anonymous_id(SlashSeparatedCourseKey.from_deprecated_string(course_id), descriptor_class)
) )
@data(*PER_COURSE_ANONYMIZED_DESCRIPTORS) @data(*PER_COURSE_ANONYMIZED_DESCRIPTORS)
...@@ -805,14 +809,14 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -805,14 +809,14 @@ class TestAnonymousStudentId(ModuleStoreTestCase, LoginEnrollmentTestCase):
# This value is set by observation, so that later changes to the student # This value is set by observation, so that later changes to the student
# id computation don't break old data # id computation don't break old data
'e3b0b940318df9c14be59acb08e78af5', 'e3b0b940318df9c14be59acb08e78af5',
self._get_anonymous_id('MITx/6.00x/2012_Fall', descriptor_class) self._get_anonymous_id(SlashSeparatedCourseKey('MITx', '6.00x', '2012_Fall'), descriptor_class)
) )
self.assertEquals( self.assertEquals(
# This value is set by observation, so that later changes to the student # This value is set by observation, so that later changes to the student
# id computation don't break old data # id computation don't break old data
'f82b5416c9f54b5ce33989511bb5ef2e', 'f82b5416c9f54b5ce33989511bb5ef2e',
self._get_anonymous_id('MITx/6.00x/2013_Spring', descriptor_class) self._get_anonymous_id(SlashSeparatedCourseKey('MITx', '6.00x', '2013_Spring'), descriptor_class)
) )
...@@ -858,8 +862,8 @@ class TestModuleTrackingContext(ModuleStoreTestCase): ...@@ -858,8 +862,8 @@ class TestModuleTrackingContext(ModuleStoreTestCase):
render.handle_xblock_callback( render.handle_xblock_callback(
self.request, self.request,
self.course.id, self.course.id.to_deprecated_string(),
quote_slashes(str(descriptor.location)), quote_slashes(descriptor.location.to_deprecated_string()),
'xmodule_handler', 'xmodule_handler',
'problem_check', 'problem_check',
) )
......
...@@ -75,10 +75,10 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -75,10 +75,10 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.enroll(self.test_course, True) self.enroll(self.test_course, True)
resp = self.client.get(reverse('courseware', resp = self.client.get(reverse('courseware',
kwargs={'course_id': self.course.id})) kwargs={'course_id': self.course.id.to_deprecated_string()}))
self.assertRedirects(resp, reverse( self.assertRedirects(resp, reverse(
'courseware_section', kwargs={'course_id': self.course.id, 'courseware_section', kwargs={'course_id': self.course.id.to_deprecated_string(),
'chapter': 'Overview', 'chapter': 'Overview',
'section': 'Welcome'})) 'section': 'Welcome'}))
...@@ -92,16 +92,22 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -92,16 +92,22 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.enroll(self.course, True) self.enroll(self.course, True)
self.enroll(self.test_course, True) self.enroll(self.test_course, True)
self.client.get(reverse('courseware_section', kwargs={'course_id': self.course.id, self.client.get(reverse('courseware_section', kwargs={
'course_id': self.course.id.to_deprecated_string(),
'chapter': 'Overview', 'chapter': 'Overview',
'section': 'Welcome'})) 'section': 'Welcome',
}))
resp = self.client.get(reverse('courseware', resp = self.client.get(reverse('courseware',
kwargs={'course_id': self.course.id})) kwargs={'course_id': self.course.id.to_deprecated_string()}))
self.assertRedirects(resp, reverse('courseware_chapter', self.assertRedirects(resp, reverse(
kwargs={'course_id': self.course.id, 'courseware_chapter',
'chapter': 'Overview'})) kwargs={
'course_id': self.course.id.to_deprecated_string(),
'chapter': 'Overview'
}
))
def test_accordion_state(self): def test_accordion_state(self):
""" """
...@@ -113,15 +119,19 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -113,15 +119,19 @@ class TestNavigation(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.enroll(self.test_course, True) self.enroll(self.test_course, True)
# Now we directly navigate to a section in a chapter other than 'Overview'. # Now we directly navigate to a section in a chapter other than 'Overview'.
check_for_get_code(self, 200, reverse('courseware_section', check_for_get_code(self, 200, reverse(
kwargs={'course_id': self.course.id, 'courseware_section',
kwargs={
'course_id': self.course.id.to_deprecated_string(),
'chapter': 'factory_chapter', 'chapter': 'factory_chapter',
'section': 'factory_section'})) 'section': 'factory_section'
}
))
# And now hitting the courseware tab should redirect to 'factory_chapter' # And now hitting the courseware tab should redirect to 'factory_chapter'
resp = self.client.get(reverse('courseware', resp = self.client.get(reverse('courseware',
kwargs={'course_id': self.course.id})) kwargs={'course_id': self.course.id.to_deprecated_string()}))
self.assertRedirects(resp, reverse('courseware_chapter', self.assertRedirects(resp, reverse('courseware_chapter',
kwargs={'course_id': self.course.id, kwargs={'course_id': self.course.id.to_deprecated_string(),
'chapter': 'factory_chapter'})) 'chapter': 'factory_chapter'}))
...@@ -113,11 +113,10 @@ class SplitTestBase(ModuleStoreTestCase): ...@@ -113,11 +113,10 @@ class SplitTestBase(ModuleStoreTestCase):
resp = self.client.get(reverse( resp = self.client.get(reverse(
'courseware_section', 'courseware_section',
kwargs={'course_id': self.course.id, kwargs={'course_id': self.course.id.to_deprecated_string(),
'chapter': self.chapter.url_name, 'chapter': self.chapter.url_name,
'section': self.sequential.url_name} 'section': self.sequential.url_name}
)) ))
content = resp.content content = resp.content
# Assert we see the proper icon in the top display # Assert we see the proper icon in the top display
...@@ -176,15 +175,15 @@ class TestVertSplitTestVert(SplitTestBase): ...@@ -176,15 +175,15 @@ class TestVertSplitTestVert(SplitTestBase):
display_name="Split test vertical", display_name="Split test vertical",
) )
# pylint: disable=protected-access # pylint: disable=protected-access
c0_url = self.course.location._replace(category="vertical", name="split_test_cond0") c0_url = self.course.id.make_usage_key("vertical", "split_test_cond0")
c1_url = self.course.location._replace(category="vertical", name="split_test_cond1") c1_url = self.course.id.make_usage_key("vertical", "split_test_cond1")
split_test = ItemFactory.create( split_test = ItemFactory.create(
parent_location=vert1.location, parent_location=vert1.location,
category="split_test", category="split_test",
display_name="Split test", display_name="Split test",
user_partition_id='0', user_partition_id='0',
group_id_to_child={"0": c0_url.url(), "1": c1_url.url()}, group_id_to_child={"0": c0_url, "1": c1_url},
) )
cond0vert = ItemFactory.create( cond0vert = ItemFactory.create(
...@@ -242,15 +241,15 @@ class TestSplitTestVert(SplitTestBase): ...@@ -242,15 +241,15 @@ class TestSplitTestVert(SplitTestBase):
# split_test cond 0 = vert <- {video, problem} # split_test cond 0 = vert <- {video, problem}
# split_test cond 1 = vert <- {video, html} # split_test cond 1 = vert <- {video, html}
# pylint: disable=protected-access # pylint: disable=protected-access
c0_url = self.course.location._replace(category="vertical", name="split_test_cond0") c0_url = self.course.id.make_usage_key("vertical", "split_test_cond0")
c1_url = self.course.location._replace(category="vertical", name="split_test_cond1") c1_url = self.course.id.make_usage_key("vertical", "split_test_cond1")
split_test = ItemFactory.create( split_test = ItemFactory.create(
parent_location=self.sequential.location, parent_location=self.sequential.location,
category="split_test", category="split_test",
display_name="Split test", display_name="Split test",
user_partition_id='0', user_partition_id='0',
group_id_to_child={"0": c0_url.url(), "1": c1_url.url()}, group_id_to_child={"0": c0_url, "1": c1_url},
) )
cond0vert = ItemFactory.create( cond0vert = ItemFactory.create(
......
...@@ -46,6 +46,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -46,6 +46,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase):
def setUp(self): def setUp(self):
super(TestSubmittingProblems, self).setUp()
# Create course # Create course
self.course = CourseFactory.create(display_name=self.COURSE_NAME, number=self.COURSE_SLUG) self.course = CourseFactory.create(display_name=self.COURSE_NAME, number=self.COURSE_SLUG)
assert self.course, "Couldn't load course %r" % self.COURSE_NAME assert self.course, "Couldn't load course %r" % self.COURSE_NAME
...@@ -63,14 +64,14 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -63,14 +64,14 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase):
""" """
Re-fetch the course from the database so that the object being dealt with has everything added to it. Re-fetch the course from the database so that the object being dealt with has everything added to it.
""" """
self.course = modulestore().get_instance(self.course.id, self.course.location) self.course = modulestore().get_course(self.course.id)
def problem_location(self, problem_url_name): def problem_location(self, problem_url_name):
""" """
Returns the url of the problem given the problem's name Returns the url of the problem given the problem's name
""" """
return "i4x://" + self.course.org + "/{}/problem/{}".format(self.COURSE_SLUG, problem_url_name) return self.course.id.make_usage_key('problem', problem_url_name)
def modx_url(self, problem_location, dispatch): def modx_url(self, problem_location, dispatch):
""" """
...@@ -84,8 +85,8 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -84,8 +85,8 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase):
return reverse( return reverse(
'xblock_handler', 'xblock_handler',
kwargs={ kwargs={
'course_id': self.course.id, 'course_id': self.course.id.to_deprecated_string(),
'usage_id': quote_slashes(problem_location), 'usage_id': quote_slashes(problem_location.to_deprecated_string()),
'handler': 'xmodule_handler', 'handler': 'xmodule_handler',
'suffix': dispatch, 'suffix': dispatch,
} }
...@@ -247,7 +248,7 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -247,7 +248,7 @@ class TestCourseGrader(TestSubmittingProblems):
""" """
fake_request = self.factory.get( fake_request = self.factory.get(
reverse('progress', kwargs={'course_id': self.course.id}) reverse('progress', kwargs={'course_id': self.course.id.to_deprecated_string()})
) )
return grades.grade(self.student_user, fake_request, self.course) return grades.grade(self.student_user, fake_request, self.course)
...@@ -265,7 +266,7 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -265,7 +266,7 @@ class TestCourseGrader(TestSubmittingProblems):
""" """
fake_request = self.factory.get( fake_request = self.factory.get(
reverse('progress', kwargs={'course_id': self.course.id}) reverse('progress', kwargs={'course_id': self.course.id.to_deprecated_string()})
) )
progress_summary = grades.progress_summary( progress_summary = grades.progress_summary(
...@@ -493,7 +494,7 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -493,7 +494,7 @@ class TestCourseGrader(TestSubmittingProblems):
# score read from StudentModule and our student gets an A instead. # score read from StudentModule and our student gets an A instead.
with patch('submissions.api.get_scores') as mock_get_scores: with patch('submissions.api.get_scores') as mock_get_scores:
mock_get_scores.return_value = { mock_get_scores.return_value = {
self.problem_location('p3'): (1, 1) self.problem_location('p3').to_deprecated_string(): (1, 1)
} }
self.check_grade_percent(1.0) self.check_grade_percent(1.0)
self.assertEqual(self.get_grade_summary()['grade'], 'A') self.assertEqual(self.get_grade_summary()['grade'], 'A')
...@@ -509,12 +510,14 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -509,12 +510,14 @@ class TestCourseGrader(TestSubmittingProblems):
with patch('submissions.api.get_scores') as mock_get_scores: with patch('submissions.api.get_scores') as mock_get_scores:
mock_get_scores.return_value = { mock_get_scores.return_value = {
self.problem_location('p3'): (1, 1) self.problem_location('p3').to_deprecated_string(): (1, 1)
} }
self.get_grade_summary() self.get_grade_summary()
# Verify that the submissions API was sent an anonymized student ID # Verify that the submissions API was sent an anonymized student ID
mock_get_scores.assert_called_with(self.course.id, '99ac6730dc5f900d69fd735975243b31') mock_get_scores.assert_called_with(
self.course.id.to_deprecated_string(), '99ac6730dc5f900d69fd735975243b31'
)
def test_weighted_homework(self): def test_weighted_homework(self):
""" """
...@@ -631,7 +634,7 @@ class ProblemWithUploadedFilesTest(TestSubmittingProblems): ...@@ -631,7 +634,7 @@ class ProblemWithUploadedFilesTest(TestSubmittingProblems):
self.addCleanup(fileobj.close) self.addCleanup(fileobj.close)
self.problem_setup("the_problem", filenames) self.problem_setup("the_problem", filenames)
with patch('courseware.module_render.xqueue_interface.session') as mock_session: with patch('courseware.module_render.XQUEUE_INTERFACE.session') as mock_session:
resp = self.submit_question_answer("the_problem", {'2_1': fileobjs}) resp = self.submit_question_answer("the_problem", {'2_1': fileobjs})
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
...@@ -946,7 +949,7 @@ class TestAnswerDistributions(TestSubmittingProblems): ...@@ -946,7 +949,7 @@ class TestAnswerDistributions(TestSubmittingProblems):
user2 = UserFactory.create() user2 = UserFactory.create()
problems = StudentModule.objects.filter( problems = StudentModule.objects.filter(
course_id=self.course.id, course_id=self.course.id,
student_id=self.student_user.id student=self.student_user
) )
for problem in problems: for problem in problems:
problem.student_id = user2.id problem.student_id = user2.id
...@@ -981,7 +984,7 @@ class TestAnswerDistributions(TestSubmittingProblems): ...@@ -981,7 +984,7 @@ class TestAnswerDistributions(TestSubmittingProblems):
# Now fetch the state entry for that problem. # Now fetch the state entry for that problem.
student_module = StudentModule.objects.get( student_module = StudentModule.objects.get(
course_id=self.course.id, course_id=self.course.id,
student_id=self.student_user.id student=self.student_user
) )
for val in ('Correct', True, False, 0, 0.0, 1, 1.0, None): for val in ('Correct', True, False, 0, 0.0, 1, 1.0, None):
state = json.loads(student_module.state) state = json.loads(student_module.state)
...@@ -1008,9 +1011,11 @@ class TestAnswerDistributions(TestSubmittingProblems): ...@@ -1008,9 +1011,11 @@ class TestAnswerDistributions(TestSubmittingProblems):
# to a non-existent problem. # to a non-existent problem.
student_module = StudentModule.objects.get( student_module = StudentModule.objects.get(
course_id=self.course.id, course_id=self.course.id,
student_id=self.student_user.id student=self.student_user
)
student_module.module_state_key = student_module.module_state_key.replace(
name=student_module.module_state_key.name + "_fake"
) )
student_module.module_state_key += "_fake"
student_module.save() student_module.save()
# It should be empty (ignored) # It should be empty (ignored)
...@@ -1027,7 +1032,7 @@ class TestAnswerDistributions(TestSubmittingProblems): ...@@ -1027,7 +1032,7 @@ class TestAnswerDistributions(TestSubmittingProblems):
# Now fetch the StudentModule entry for p1 so we can corrupt its state # Now fetch the StudentModule entry for p1 so we can corrupt its state
prb1 = StudentModule.objects.get( prb1 = StudentModule.objects.get(
course_id=self.course.id, course_id=self.course.id,
student_id=self.student_user.id student=self.student_user
) )
# Submit p2 # Submit p2
......
...@@ -15,6 +15,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase ...@@ -15,6 +15,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from courseware.tests.helpers import get_request_for_user, LoginEnrollmentTestCase from courseware.tests.helpers import get_request_for_user, LoginEnrollmentTestCase
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from xmodule.modulestore.locations import SlashSeparatedCourseKey
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
...@@ -27,29 +28,30 @@ class StaticTabDateTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -27,29 +28,30 @@ class StaticTabDateTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
category="static_tab", parent_location=self.course.location, category="static_tab", parent_location=self.course.location,
data="OOGIE BLOOGIE", display_name="new_tab" data="OOGIE BLOOGIE", display_name="new_tab"
) )
self.toy_course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
def test_logged_in(self): def test_logged_in(self):
self.setup_user() self.setup_user()
url = reverse('static_tab', args=[self.course.id, 'new_tab']) url = reverse('static_tab', args=[self.course.id.to_deprecated_string(), 'new_tab'])
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertIn("OOGIE BLOOGIE", resp.content) self.assertIn("OOGIE BLOOGIE", resp.content)
def test_anonymous_user(self): def test_anonymous_user(self):
url = reverse('static_tab', args=[self.course.id, 'new_tab']) url = reverse('static_tab', args=[self.course.id.to_deprecated_string(), 'new_tab'])
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertIn("OOGIE BLOOGIE", resp.content) self.assertIn("OOGIE BLOOGIE", resp.content)
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
def test_get_static_tab_contents(self): def test_get_static_tab_contents(self):
course = get_course_by_id('edX/toy/2012_Fall') course = get_course_by_id(self.toy_course_key)
request = get_request_for_user(UserFactory.create()) request = get_request_for_user(UserFactory.create())
tab = CourseTabList.get_tab_by_slug(course.tabs, 'resources') tab = CourseTabList.get_tab_by_slug(course.tabs, 'resources')
# Test render works okay # Test render works okay
tab_content = get_static_tab_contents(request, course, tab) tab_content = get_static_tab_contents(request, course, tab)
self.assertIn('edX/toy/2012_Fall', tab_content) self.assertIn(self.toy_course_key.to_deprecated_string(), tab_content)
self.assertIn('static_tab', tab_content) self.assertIn('static_tab', tab_content)
# Test when render raises an exception # Test when render raises an exception
...@@ -66,7 +68,7 @@ class StaticTabDateTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -66,7 +68,7 @@ class StaticTabDateTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
# The following XML test course (which lives at common/test/data/2014) # The following XML test course (which lives at common/test/data/2014)
# is closed; we're testing that tabs still appear when # is closed; we're testing that tabs still appear when
# the course is already closed # the course is already closed
xml_course_id = 'edX/detached_pages/2014' xml_course_key = SlashSeparatedCourseKey('edX', 'detached_pages', '2014')
# this text appears in the test course's tab # this text appears in the test course's tab
# common/test/data/2014/tabs/8e4cce2b4aaf4ba28b1220804619e41f.html # common/test/data/2014/tabs/8e4cce2b4aaf4ba28b1220804619e41f.html
...@@ -76,14 +78,14 @@ class StaticTabDateTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -76,14 +78,14 @@ class StaticTabDateTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
def test_logged_in_xml(self): def test_logged_in_xml(self):
self.setup_user() self.setup_user()
url = reverse('static_tab', args=[self.xml_course_id, self.xml_url]) url = reverse('static_tab', args=[self.xml_course_key.to_deprecated_string(), self.xml_url])
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertIn(self.xml_data, resp.content) self.assertIn(self.xml_data, resp.content)
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
def test_anonymous_user_xml(self): def test_anonymous_user_xml(self):
url = reverse('static_tab', args=[self.xml_course_id, self.xml_url]) url = reverse('static_tab', args=[self.xml_course_key.to_deprecated_string(), self.xml_url])
resp = self.client.get(url) resp = self.client.get(url)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertIn(self.xml_data, resp.content) self.assertIn(self.xml_data, resp.content)
......
...@@ -10,7 +10,6 @@ from datetime import timedelta ...@@ -10,7 +10,6 @@ from datetime import timedelta
from webob import Request from webob import Request
from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import StaticContent
from xmodule.modulestore import Location
from xmodule.contentstore.django import contentstore from xmodule.contentstore.django import contentstore
from . import BaseTestXmodule from . import BaseTestXmodule
from .test_video_xml import SOURCE_XML from .test_video_xml import SOURCE_XML
...@@ -21,6 +20,7 @@ from xmodule.video_module.transcripts_utils import ( ...@@ -21,6 +20,7 @@ from xmodule.video_module.transcripts_utils import (
TranscriptException, TranscriptException,
TranscriptsGenerationException, TranscriptsGenerationException,
) )
from xmodule.modulestore.mongo.base import MongoModuleStore
SRT_content = textwrap.dedent(""" SRT_content = textwrap.dedent("""
0 0
...@@ -46,7 +46,7 @@ def _check_asset(location, asset_name): ...@@ -46,7 +46,7 @@ def _check_asset(location, asset_name):
Check that asset with asset_name exists in assets. Check that asset with asset_name exists in assets.
""" """
content_location = StaticContent.compute_location( content_location = StaticContent.compute_location(
location.org, location.course, asset_name location.course_key, asset_name
) )
try: try:
contentstore().find(content_location) contentstore().find(content_location)
...@@ -61,16 +61,12 @@ def _clear_assets(location): ...@@ -61,16 +61,12 @@ def _clear_assets(location):
""" """
store = contentstore() store = contentstore()
content_location = StaticContent.compute_location( assets, __ = store.get_all_content_for_course(location.course_key)
location.org, location.course, location.name
)
assets, __ = store.get_all_content_for_course(content_location)
for asset in assets: for asset in assets:
asset_location = Location(asset["_id"]) asset_location = MongoModuleStore._location_from_id(asset["_id"], location.course_key.run)
del_cached_content(asset_location) del_cached_content(asset_location)
id = StaticContent.get_id_from_location(asset_location) mongo_id = StaticContent.get_id_from_location(asset_location)
store.delete(id) store.delete(mongo_id)
def _get_subs_id(filename): def _get_subs_id(filename):
...@@ -97,7 +93,7 @@ def _upload_sjson_file(subs_file, location, default_filename='subs_{}.srt.sjson' ...@@ -97,7 +93,7 @@ def _upload_sjson_file(subs_file, location, default_filename='subs_{}.srt.sjson'
def _upload_file(subs_file, location, filename): def _upload_file(subs_file, location, filename):
mime_type = subs_file.content_type mime_type = subs_file.content_type
content_location = StaticContent.compute_location( content_location = StaticContent.compute_location(
location.org, location.course, filename location.course_key, filename
) )
content = StaticContent(content_location, filename, mime_type, subs_file.read()) content = StaticContent(content_location, filename, mime_type, subs_file.read())
contentstore().save(content) contentstore().save(content)
......
...@@ -14,6 +14,7 @@ from xmodule.video_module import create_youtube_string ...@@ -14,6 +14,7 @@ from xmodule.video_module import create_youtube_string
from xmodule.tests import get_test_descriptor_system from xmodule.tests import get_test_descriptor_system
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.video_module import VideoDescriptor from xmodule.video_module import VideoDescriptor
from xmodule.modulestore.locations import SlashSeparatedCourseKey
from . import BaseTestXmodule from . import BaseTestXmodule
from .test_video_xml import SOURCE_XML from .test_video_xml import SOURCE_XML
...@@ -511,10 +512,11 @@ class VideoDescriptorTest(unittest.TestCase): ...@@ -511,10 +512,11 @@ class VideoDescriptorTest(unittest.TestCase):
def setUp(self): def setUp(self):
system = get_test_descriptor_system() system = get_test_descriptor_system()
location = Location('i4x://org/course/video/name') course_key = SlashSeparatedCourseKey('org', 'course', 'run')
usage_key = course_key.make_usage_key('video', 'name')
self.descriptor = system.construct_xblock_from_class( self.descriptor = system.construct_xblock_from_class(
VideoDescriptor, VideoDescriptor,
scope_ids=ScopeIds(None, None, location, location), scope_ids=ScopeIds(None, None, usage_key, usage_key),
field_data=DictFieldData({}), field_data=DictFieldData({}),
) )
self.descriptor.runtime.handler_url = MagicMock() self.descriptor.runtime.handler_url = MagicMock()
......
...@@ -48,7 +48,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -48,7 +48,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
Returns a list URLs corresponding to section in the passed in course. Returns a list URLs corresponding to section in the passed in course.
""" """
return [reverse(name, kwargs={'course_id': course.id}) return [reverse(name, kwargs={'course_id': course.id.to_deprecated_string()})
for name in names] for name in names]
def _check_non_staff_light(self, course): def _check_non_staff_light(self, course):
...@@ -57,7 +57,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -57,7 +57,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
`course` is an instance of CourseDescriptor. `course` is an instance of CourseDescriptor.
""" """
urls = [reverse('about_course', kwargs={'course_id': course.id}), reverse('courses')] urls = [reverse('about_course', kwargs={'course_id': course.id.to_deprecated_string()}),
reverse('courses')]
for url in urls: for url in urls:
check_for_get_code(self, 200, url) check_for_get_code(self, 200, url)
...@@ -69,7 +70,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -69,7 +70,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
names = ['courseware', 'instructor_dashboard', 'progress'] names = ['courseware', 'instructor_dashboard', 'progress']
urls = self._reverse_urls(names, course) urls = self._reverse_urls(names, course)
urls.extend([ urls.extend([
reverse('book', kwargs={'course_id': course.id, reverse('book', kwargs={'course_id': course.id.to_deprecated_string(),
'book_index': index}) 'book_index': index})
for index, book in enumerate(course.textbooks) for index, book in enumerate(course.textbooks)
]) ])
...@@ -83,7 +84,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -83,7 +84,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
names = ['about_course', 'instructor_dashboard', 'progress'] names = ['about_course', 'instructor_dashboard', 'progress']
urls = self._reverse_urls(names, course) urls = self._reverse_urls(names, course)
urls.extend([ urls.extend([
reverse('book', kwargs={'course_id': course.id, reverse('book', kwargs={'course_id': course.id.to_deprecated_string(),
'book_index': index}) 'book_index': index})
for index in xrange(len(course.textbooks)) for index in xrange(len(course.textbooks))
]) ])
...@@ -97,7 +98,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -97,7 +98,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
# to make access checking smarter and understand both the effective # to make access checking smarter and understand both the effective
# user (the student), and the requesting user (the prof) # user (the student), and the requesting user (the prof)
url = reverse('student_progress', url = reverse('student_progress',
kwargs={'course_id': course.id, kwargs={'course_id': course.id.to_deprecated_string(),
'student_id': self.enrolled_user.id}) 'student_id': self.enrolled_user.id})
check_for_get_code(self, 404, url) check_for_get_code(self, 404, url)
...@@ -137,12 +138,11 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -137,12 +138,11 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
CourseEnrollmentFactory(user=self.enrolled_user, course_id=self.course.id) CourseEnrollmentFactory(user=self.enrolled_user, course_id=self.course.id)
CourseEnrollmentFactory(user=self.enrolled_user, course_id=self.test_course.id) CourseEnrollmentFactory(user=self.enrolled_user, course_id=self.test_course.id)
self.staff_user = StaffFactory(course=self.course.location) self.staff_user = StaffFactory(course=self.course.id)
self.instructor_user = InstructorFactory( self.instructor_user = InstructorFactory(
course=self.course.location) course=self.course.id)
self.org_staff_user = OrgStaffFactory(course=self.course.location) self.org_staff_user = OrgStaffFactory(course=self.course.id)
self.org_instructor_user = OrgInstructorFactory( self.org_instructor_user = OrgInstructorFactory(course=self.course.id)
course=self.course.location)
def test_redirection_unenrolled(self): def test_redirection_unenrolled(self):
""" """
...@@ -151,10 +151,10 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -151,10 +151,10 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
""" """
self.login(self.unenrolled_user) self.login(self.unenrolled_user)
response = self.client.get(reverse('courseware', response = self.client.get(reverse('courseware',
kwargs={'course_id': self.course.id})) kwargs={'course_id': self.course.id.to_deprecated_string()}))
self.assertRedirects(response, self.assertRedirects(response,
reverse('about_course', reverse('about_course',
args=[self.course.id])) args=[self.course.id.to_deprecated_string()]))
def test_redirection_enrolled(self): def test_redirection_enrolled(self):
""" """
...@@ -164,11 +164,11 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -164,11 +164,11 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.login(self.enrolled_user) self.login(self.enrolled_user)
response = self.client.get(reverse('courseware', response = self.client.get(reverse('courseware',
kwargs={'course_id': self.course.id})) kwargs={'course_id': self.course.id.to_deprecated_string()}))
self.assertRedirects(response, self.assertRedirects(response,
reverse('courseware_section', reverse('courseware_section',
kwargs={'course_id': self.course.id, kwargs={'course_id': self.course.id.to_deprecated_string(),
'chapter': 'Overview', 'chapter': 'Overview',
'section': 'Welcome'})) 'section': 'Welcome'}))
...@@ -179,8 +179,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -179,8 +179,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
""" """
self.login(self.enrolled_user) self.login(self.enrolled_user)
urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id}), urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()}),
reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id})] reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id.to_deprecated_string()})]
# Shouldn't be able to get to the instructor pages # Shouldn't be able to get to the instructor pages
for url in urls: for url in urls:
...@@ -194,10 +194,10 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -194,10 +194,10 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.login(self.staff_user) self.login(self.staff_user)
# Now should be able to get to self.course, but not self.test_course # Now should be able to get to self.course, but not self.test_course
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
check_for_get_code(self, 200, url) check_for_get_code(self, 200, url)
url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id.to_deprecated_string()})
check_for_get_code(self, 404, url) check_for_get_code(self, 404, url)
def test_instructor_course_access(self): def test_instructor_course_access(self):
...@@ -208,10 +208,10 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -208,10 +208,10 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.login(self.instructor_user) self.login(self.instructor_user)
# Now should be able to get to self.course, but not self.test_course # Now should be able to get to self.course, but not self.test_course
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
check_for_get_code(self, 200, url) check_for_get_code(self, 200, url)
url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id.to_deprecated_string()})
check_for_get_code(self, 404, url) check_for_get_code(self, 404, url)
def test_org_staff_access(self): def test_org_staff_access(self):
...@@ -220,13 +220,13 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -220,13 +220,13 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
and student profile pages for course in their org. and student profile pages for course in their org.
""" """
self.login(self.org_staff_user) self.login(self.org_staff_user)
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
check_for_get_code(self, 200, url) check_for_get_code(self, 200, url)
url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id.to_deprecated_string()})
check_for_get_code(self, 200, url) check_for_get_code(self, 200, url)
url = reverse('instructor_dashboard', kwargs={'course_id': self.other_org_course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': self.other_org_course.id.to_deprecated_string()})
check_for_get_code(self, 404, url) check_for_get_code(self, 404, url)
def test_org_instructor_access(self): def test_org_instructor_access(self):
...@@ -235,13 +235,13 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -235,13 +235,13 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
and student profile pages for course in their org. and student profile pages for course in their org.
""" """
self.login(self.org_instructor_user) self.login(self.org_instructor_user)
url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()})
check_for_get_code(self, 200, url) check_for_get_code(self, 200, url)
url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id.to_deprecated_string()})
check_for_get_code(self, 200, url) check_for_get_code(self, 200, url)
url = reverse('instructor_dashboard', kwargs={'course_id': self.other_org_course.id}) url = reverse('instructor_dashboard', kwargs={'course_id': self.other_org_course.id.to_deprecated_string()})
check_for_get_code(self, 404, url) check_for_get_code(self, 404, url)
def test_global_staff_access(self): def test_global_staff_access(self):
...@@ -251,8 +251,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -251,8 +251,8 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.login(self.global_staff_user) self.login(self.global_staff_user)
# and now should be able to load both # and now should be able to load both
urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id}), urls = [reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()}),
reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id})] reverse('instructor_dashboard', kwargs={'course_id': self.test_course.id.to_deprecated_string()})]
for url in urls: for url in urls:
check_for_get_code(self, 200, url) check_for_get_code(self, 200, url)
...@@ -374,7 +374,7 @@ class TestBetatesterAccess(ModuleStoreTestCase): ...@@ -374,7 +374,7 @@ class TestBetatesterAccess(ModuleStoreTestCase):
self.content = ItemFactory(parent=self.course) self.content = ItemFactory(parent=self.course)
self.normal_student = UserFactory() self.normal_student = UserFactory()
self.beta_tester = BetaTesterFactory(course=self.course.location) self.beta_tester = BetaTesterFactory(course=self.course.id)
@patch.dict('courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False}) @patch.dict('courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False})
def test_course_beta_period(self): def test_course_beta_period(self):
...@@ -384,10 +384,10 @@ class TestBetatesterAccess(ModuleStoreTestCase): ...@@ -384,10 +384,10 @@ class TestBetatesterAccess(ModuleStoreTestCase):
self.assertFalse(self.course.has_started()) self.assertFalse(self.course.has_started())
# student user shouldn't see it # student user shouldn't see it
self.assertFalse(has_access(self.normal_student, self.course, 'load')) self.assertFalse(has_access(self.normal_student, 'load', self.course))
# now the student should see it # now the student should see it
self.assertTrue(has_access(self.beta_tester, self.course, 'load')) self.assertTrue(has_access(self.beta_tester, 'load', self.course))
@patch.dict('courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False}) @patch.dict('courseware.access.settings.FEATURES', {'DISABLE_START_DATES': False})
def test_content_beta_period(self): def test_content_beta_period(self):
...@@ -395,7 +395,7 @@ class TestBetatesterAccess(ModuleStoreTestCase): ...@@ -395,7 +395,7 @@ class TestBetatesterAccess(ModuleStoreTestCase):
Check that beta-test access works for content. Check that beta-test access works for content.
""" """
# student user shouldn't see it # student user shouldn't see it
self.assertFalse(has_access(self.normal_student, self.content, 'load', self.course.id)) self.assertFalse(has_access(self.normal_student, 'load', self.content, self.course.id))
# now the student should see it # now the student should see it
self.assertTrue(has_access(self.beta_tester, self.content, 'load', self.course.id)) self.assertTrue(has_access(self.beta_tester, 'load', self.content, self.course.id))
...@@ -24,6 +24,7 @@ from xmodule.modulestore import Location ...@@ -24,6 +24,7 @@ from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.locations import SlashSeparatedCourseKey
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
import courseware.views as views import courseware.views as views
...@@ -42,31 +43,32 @@ class TestJumpTo(TestCase): ...@@ -42,31 +43,32 @@ class TestJumpTo(TestCase):
def setUp(self): def setUp(self):
# Use toy course from XML # Use toy course from XML
self.course_name = 'edX/toy/2012_Fall' self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
def test_jumpto_invalid_location(self): def test_jumpto_invalid_location(self):
location = Location('i4x', 'edX', 'toy', 'NoSuchPlace', None) location = self.course_key.make_usage_key(None, 'NoSuchPlace')
jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_name, location) # This is fragile, but unfortunately the problem is that within the LMS we
# can't use the reverse calls from the CMS
jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string())
response = self.client.get(jumpto_url) response = self.client.get(jumpto_url)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
def test_jumpto_from_chapter(self): def test_jumpto_from_chapter(self):
location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview') location = self.course_key.make_usage_key('chapter', 'Overview')
jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_name, location) jumpto_url = '{0}/{1}/jump_to/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string())
expected = 'courses/edX/toy/2012_Fall/courseware/Overview/' expected = 'courses/edX/toy/2012_Fall/courseware/Overview/'
response = self.client.get(jumpto_url) response = self.client.get(jumpto_url)
self.assertRedirects(response, expected, status_code=302, target_status_code=302) self.assertRedirects(response, expected, status_code=302, target_status_code=302)
def test_jumpto_id(self): def test_jumpto_id(self):
location = Location('i4x', 'edX', 'toy', 'chapter', 'Overview') jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), 'Overview')
jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_name, location.name)
expected = 'courses/edX/toy/2012_Fall/courseware/Overview/' expected = 'courses/edX/toy/2012_Fall/courseware/Overview/'
response = self.client.get(jumpto_url) response = self.client.get(jumpto_url)
self.assertRedirects(response, expected, status_code=302, target_status_code=302) self.assertRedirects(response, expected, status_code=302, target_status_code=302)
def test_jumpto_id_invalid_location(self): def test_jumpto_id_invalid_location(self):
location = Location('i4x', 'edX', 'toy', 'NoSuchPlace', None) location = Location('edX', 'toy', 'NoSuchPlace', None, None, None)
jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_name, location.name) jumpto_url = '{0}/{1}/jump_to_id/{2}'.format('/courses', self.course_key.to_deprecated_string(), location.to_deprecated_string())
response = self.client.get(jumpto_url) response = self.client.get(jumpto_url)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
...@@ -80,15 +82,15 @@ class ViewsTestCase(TestCase): ...@@ -80,15 +82,15 @@ class ViewsTestCase(TestCase):
self.user = User.objects.create(username='dummy', password='123456', self.user = User.objects.create(username='dummy', password='123456',
email='test@mit.edu') email='test@mit.edu')
self.date = datetime(2013, 1, 22, tzinfo=UTC) self.date = datetime(2013, 1, 22, tzinfo=UTC)
self.course_id = 'edX/toy/2012_Fall' self.course_key = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall')
self.enrollment = CourseEnrollment.enroll(self.user, self.course_id) self.enrollment = CourseEnrollment.enroll(self.user, self.course_key)
self.enrollment.created = self.date self.enrollment.created = self.date
self.enrollment.save() self.enrollment.save()
self.location = ['tag', 'org', 'course', 'category', 'name'] self.location = ['tag', 'org', 'course', 'category', 'name']
self.request_factory = RequestFactory() self.request_factory = RequestFactory()
chapter = 'Overview' chapter = 'Overview'
self.chapter_url = '%s/%s/%s' % ('/courses', self.course_id, chapter) self.chapter_url = '%s/%s/%s' % ('/courses', self.course_key, chapter)
@unittest.skipUnless(settings.FEATURES.get('ENABLE_SHOPPING_CART'), "Shopping Cart not enabled in settings") @unittest.skipUnless(settings.FEATURES.get('ENABLE_SHOPPING_CART'), "Shopping Cart not enabled in settings")
@patch.dict(settings.FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True}) @patch.dict(settings.FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True})
...@@ -96,22 +98,22 @@ class ViewsTestCase(TestCase): ...@@ -96,22 +98,22 @@ class ViewsTestCase(TestCase):
in_cart_span = '<span class="add-to-cart">' in_cart_span = '<span class="add-to-cart">'
# don't mock this course due to shopping cart existence checking # don't mock this course due to shopping cart existence checking
course = CourseFactory.create(org="new", number="unenrolled", display_name="course") course = CourseFactory.create(org="new", number="unenrolled", display_name="course")
request = self.request_factory.get(reverse('about_course', args=[course.id])) request = self.request_factory.get(reverse('about_course', args=[course.id.to_deprecated_string()]))
request.user = AnonymousUser() request.user = AnonymousUser()
response = views.course_about(request, course.id) response = views.course_about(request, course.id.to_deprecated_string())
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertNotIn(in_cart_span, response.content) self.assertNotIn(in_cart_span, response.content)
# authenticated user with nothing in cart # authenticated user with nothing in cart
request.user = self.user request.user = self.user
response = views.course_about(request, course.id) response = views.course_about(request, course.id.to_deprecated_string())
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertNotIn(in_cart_span, response.content) self.assertNotIn(in_cart_span, response.content)
# now add the course to the cart # now add the course to the cart
cart = shoppingcart.models.Order.get_cart_for_user(self.user) cart = shoppingcart.models.Order.get_cart_for_user(self.user)
shoppingcart.models.PaidCourseRegistration.add_to_order(cart, course.id) shoppingcart.models.PaidCourseRegistration.add_to_order(cart, course.id)
response = views.course_about(request, course.id) response = views.course_about(request, course.id.to_deprecated_string())
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertIn(in_cart_span, response.content) self.assertIn(in_cart_span, response.content)
...@@ -146,15 +148,15 @@ class ViewsTestCase(TestCase): ...@@ -146,15 +148,15 @@ class ViewsTestCase(TestCase):
mock_user.is_authenticated.return_value = False mock_user.is_authenticated.return_value = False
self.assertFalse(views.registered_for_course('dummy', mock_user)) self.assertFalse(views.registered_for_course('dummy', mock_user))
mock_course = MagicMock() mock_course = MagicMock()
mock_course.id = self.course_id mock_course.id = self.course_key
self.assertTrue(views.registered_for_course(mock_course, self.user)) self.assertTrue(views.registered_for_course(mock_course, self.user))
def test_jump_to_invalid(self): def test_jump_to_invalid(self):
# TODO add a test for invalid location
# TODO add a test for no data *
request = self.request_factory.get(self.chapter_url) request = self.request_factory.get(self.chapter_url)
self.assertRaisesRegexp(Http404, 'Invalid location', views.jump_to, self.assertRaisesRegexp(Http404, 'Invalid course_key or usage_key', views.jump_to,
request, 'bar', ()) request, 'bar', ())
self.assertRaisesRegexp(Http404, 'No data*', views.jump_to, request,
'dummy', self.location)
def test_no_end_on_about_page(self): def test_no_end_on_about_page(self):
# Toy course has no course end date or about/end_date blob # Toy course has no course end date or about/end_date blob
...@@ -170,6 +172,13 @@ class ViewsTestCase(TestCase): ...@@ -170,6 +172,13 @@ class ViewsTestCase(TestCase):
self.verify_end_date("edX/test_about_blob_end_date/2012_Fall", "Learning never ends") self.verify_end_date("edX/test_about_blob_end_date/2012_Fall", "Learning never ends")
def verify_end_date(self, course_id, expected_end_text=None): def verify_end_date(self, course_id, expected_end_text=None):
"""
Visits the about page for `course_id` and tests that both the text "Classes End", as well
as the specified `expected_end_text`, is present on the page.
If `expected_end_text` is None, verifies that the about page *does not* contain the text
"Classes End".
"""
request = self.request_factory.get("foo") request = self.request_factory.get("foo")
request.user = self.user request.user = self.user
...@@ -214,7 +223,7 @@ class ViewsTestCase(TestCase): ...@@ -214,7 +223,7 @@ class ViewsTestCase(TestCase):
def test_course_mktg_register(self): def test_course_mktg_register(self):
admin = AdminFactory() admin = AdminFactory()
self.client.login(username=admin.username, password='test') self.client.login(username=admin.username, password='test')
url = reverse('mktg_about_course', kwargs={'course_id': self.course_id}) url = reverse('mktg_about_course', kwargs={'course_id': self.course_key.to_deprecated_string()})
response = self.client.get(url) response = self.client.get(url)
self.assertIn('Register for', response.content) self.assertIn('Register for', response.content)
self.assertNotIn('and choose your student track', response.content) self.assertNotIn('and choose your student track', response.content)
...@@ -223,12 +232,12 @@ class ViewsTestCase(TestCase): ...@@ -223,12 +232,12 @@ class ViewsTestCase(TestCase):
admin = AdminFactory() admin = AdminFactory()
CourseMode.objects.get_or_create(mode_slug='honor', CourseMode.objects.get_or_create(mode_slug='honor',
mode_display_name='Honor Code Certificate', mode_display_name='Honor Code Certificate',
course_id=self.course_id) course_id=self.course_key)
CourseMode.objects.get_or_create(mode_slug='verified', CourseMode.objects.get_or_create(mode_slug='verified',
mode_display_name='Verified Certificate', mode_display_name='Verified Certificate',
course_id=self.course_id) course_id=self.course_key)
self.client.login(username=admin.username, password='test') self.client.login(username=admin.username, password='test')
url = reverse('mktg_about_course', kwargs={'course_id': self.course_id}) url = reverse('mktg_about_course', kwargs={'course_id': self.course_key.to_deprecated_string()})
response = self.client.get(url) response = self.client.get(url)
self.assertIn('Register for', response.content) self.assertIn('Register for', response.content)
self.assertIn('and choose your student track', response.content) self.assertIn('and choose your student track', response.content)
...@@ -243,7 +252,7 @@ class ViewsTestCase(TestCase): ...@@ -243,7 +252,7 @@ class ViewsTestCase(TestCase):
# try it with an existing user and a malicious location # try it with an existing user and a malicious location
url = reverse('submission_history', kwargs={ url = reverse('submission_history', kwargs={
'course_id': self.course_id, 'course_id': self.course_key.to_deprecated_string(),
'student_username': 'dummy', 'student_username': 'dummy',
'location': '<script>alert("hello");</script>' 'location': '<script>alert("hello");</script>'
}) })
...@@ -252,13 +261,14 @@ class ViewsTestCase(TestCase): ...@@ -252,13 +261,14 @@ class ViewsTestCase(TestCase):
# try it with a malicious user and a non-existent location # try it with a malicious user and a non-existent location
url = reverse('submission_history', kwargs={ url = reverse('submission_history', kwargs={
'course_id': self.course_id, 'course_id': self.course_key.to_deprecated_string(),
'student_username': '<script>alert("hello");</script>', 'student_username': '<script>alert("hello");</script>',
'location': 'dummy' 'location': 'dummy'
}) })
response = self.client.get(url) response = self.client.get(url)
self.assertFalse('<script>' in response.content) self.assertFalse('<script>' in response.content)
# setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly # setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE, TIME_ZONE_DISPLAYED_FOR_DEADLINES="UTC") @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE, TIME_ZONE_DISPLAYED_FOR_DEADLINES="UTC")
class BaseDueDateTests(ModuleStoreTestCase): class BaseDueDateTests(ModuleStoreTestCase):
...@@ -283,7 +293,7 @@ class BaseDueDateTests(ModuleStoreTestCase): ...@@ -283,7 +293,7 @@ class BaseDueDateTests(ModuleStoreTestCase):
vertical = ItemFactory(category='vertical', parent_location=section.location) vertical = ItemFactory(category='vertical', parent_location=section.location)
ItemFactory(category='problem', parent_location=vertical.location) ItemFactory(category='problem', parent_location=vertical.location)
course = modulestore().get_instance(course.id, course.location) # pylint: disable=no-member course = modulestore().get_course(course.id) # pylint: disable=no-member
self.assertIsNotNone(course.get_children()[0].get_children()[0].due) self.assertIsNotNone(course.get_children()[0].get_children()[0].due)
return course return course
...@@ -356,7 +366,7 @@ class TestProgressDueDate(BaseDueDateTests): ...@@ -356,7 +366,7 @@ class TestProgressDueDate(BaseDueDateTests):
def get_text(self, course): def get_text(self, course):
""" Returns the HTML for the progress page """ """ Returns the HTML for the progress page """
return views.progress(self.request, course.id, self.user.id).content return views.progress(self.request, course.id.to_deprecated_string(), self.user.id).content
class TestAccordionDueDate(BaseDueDateTests): class TestAccordionDueDate(BaseDueDateTests):
...@@ -368,7 +378,7 @@ class TestAccordionDueDate(BaseDueDateTests): ...@@ -368,7 +378,7 @@ class TestAccordionDueDate(BaseDueDateTests):
def get_text(self, course): def get_text(self, course):
""" Returns the HTML for the accordion """ """ Returns the HTML for the accordion """
return views.render_accordion( return views.render_accordion(
self.request, course, course.get_children()[0].id, None, None self.request, course, course.get_children()[0].scope_ids.usage_id.to_deprecated_string(), None, None
) )
...@@ -392,14 +402,14 @@ class StartDateTests(ModuleStoreTestCase): ...@@ -392,14 +402,14 @@ class StartDateTests(ModuleStoreTestCase):
:param course_kwargs: All kwargs are passed to through to the :class:`CourseFactory` :param course_kwargs: All kwargs are passed to through to the :class:`CourseFactory`
""" """
course = CourseFactory(start=datetime(2013, 9, 16, 7, 17, 28)) course = CourseFactory(start=datetime(2013, 9, 16, 7, 17, 28))
course = modulestore().get_instance(course.id, course.location) # pylint: disable=no-member course = modulestore().get_course(course.id) # pylint: disable=no-member
return course return course
def get_about_text(self, course_id): def get_about_text(self, course_key):
""" """
Get the text of the /about page for the course. Get the text of the /about page for the course.
""" """
text = views.course_about(self.request, course_id).content text = views.course_about(self.request, course_key.to_deprecated_string()).content
return text return text
@patch('util.date_utils.pgettext', fake_pgettext(translations={ @patch('util.date_utils.pgettext', fake_pgettext(translations={
...@@ -421,7 +431,7 @@ class StartDateTests(ModuleStoreTestCase): ...@@ -421,7 +431,7 @@ class StartDateTests(ModuleStoreTestCase):
"SHORT_DATE_FORMAT": "%Y-%b-%d", "SHORT_DATE_FORMAT": "%Y-%b-%d",
})) }))
def test_format_localized_in_xml_course(self): def test_format_localized_in_xml_course(self):
text = self.get_about_text('edX/toy/TT_2012_Fall') text = self.get_about_text(SlashSeparatedCourseKey('edX', 'toy', 'TT_2012_Fall'))
# The start date is set in common/test/data/two_toys/policies/TT_2012_Fall/policy.json # The start date is set in common/test/data/two_toys/policies/TT_2012_Fall/policy.json
self.assertIn("2015-JULY-17", text) self.assertIn("2015-JULY-17", text)
...@@ -441,7 +451,7 @@ class ProgressPageTests(ModuleStoreTestCase): ...@@ -441,7 +451,7 @@ class ProgressPageTests(ModuleStoreTestCase):
MakoMiddleware().process_request(self.request) MakoMiddleware().process_request(self.request)
course = CourseFactory(start=datetime(2013, 9, 16, 7, 17, 28)) course = CourseFactory(start=datetime(2013, 9, 16, 7, 17, 28))
self.course = modulestore().get_instance(course.id, course.location) # pylint: disable=no-member self.course = modulestore().get_course(course.id) # pylint: disable=no-member
self.chapter = ItemFactory(category='chapter', parent_location=self.course.location) # pylint: disable=no-member self.chapter = ItemFactory(category='chapter', parent_location=self.course.location) # pylint: disable=no-member
self.section = ItemFactory(category='sequential', parent_location=self.chapter.location) self.section = ItemFactory(category='sequential', parent_location=self.chapter.location)
...@@ -450,6 +460,5 @@ class ProgressPageTests(ModuleStoreTestCase): ...@@ -450,6 +460,5 @@ class ProgressPageTests(ModuleStoreTestCase):
def test_pure_ungraded_xblock(self): def test_pure_ungraded_xblock(self):
ItemFactory(category='acid', parent_location=self.vertical.location) ItemFactory(category='acid', parent_location=self.vertical.location)
resp = views.progress(self.request, self.course.id) resp = views.progress(self.request, self.course.id.to_deprecated_string())
self.assertEquals(resp.status_code, 200) self.assertEquals(resp.status_code, 200)
...@@ -11,7 +11,7 @@ from textwrap import dedent ...@@ -11,7 +11,7 @@ from textwrap import dedent
from xmodule.error_module import ErrorDescriptor from xmodule.error_module import ErrorDescriptor
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore import Location from xmodule.modulestore.locations import SlashSeparatedCourseKey
from xmodule.modulestore.xml_importer import import_from_xml from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
...@@ -48,6 +48,7 @@ class PageLoaderTestCase(LoginEnrollmentTestCase): ...@@ -48,6 +48,7 @@ class PageLoaderTestCase(LoginEnrollmentTestCase):
Base class that adds a function to load all pages in a modulestore. Base class that adds a function to load all pages in a modulestore.
""" """
# TODO once everything is merged can someone please check whether this function takes a course_id or course_key
def check_all_pages_load(self, course_id): def check_all_pages_load(self, course_id):
""" """
Assert that all pages in the course load correctly. Assert that all pages in the course load correctly.
...@@ -61,18 +62,7 @@ class PageLoaderTestCase(LoginEnrollmentTestCase): ...@@ -61,18 +62,7 @@ class PageLoaderTestCase(LoginEnrollmentTestCase):
self.enroll(course, True) self.enroll(course, True)
# Search for items in the course # Search for items in the course
# None is treated as a wildcard items = store.get_items(course_id)
course_loc = course.location
location_query = Location(
course_loc.tag, course_loc.org,
course_loc.course, None, None, None
)
items = store.get_items(
location_query,
course_id=course_id,
depth=2
)
if len(items) < 1: if len(items) < 1:
self.fail('Could not retrieve any items from course') self.fail('Could not retrieve any items from course')
...@@ -82,22 +72,22 @@ class PageLoaderTestCase(LoginEnrollmentTestCase): ...@@ -82,22 +72,22 @@ class PageLoaderTestCase(LoginEnrollmentTestCase):
if descriptor.location.category == 'about': if descriptor.location.category == 'about':
self._assert_loads('about_course', self._assert_loads('about_course',
{'course_id': course_id}, {'course_id': course_id.to_deprecated_string()},
descriptor) descriptor)
elif descriptor.location.category == 'static_tab': elif descriptor.location.category == 'static_tab':
kwargs = {'course_id': course_id, kwargs = {'course_id': course_id.to_deprecated_string(),
'tab_slug': descriptor.location.name} 'tab_slug': descriptor.location.name}
self._assert_loads('static_tab', kwargs, descriptor) self._assert_loads('static_tab', kwargs, descriptor)
elif descriptor.location.category == 'course_info': elif descriptor.location.category == 'course_info':
self._assert_loads('info', {'course_id': course_id}, self._assert_loads('info', {'course_id': course_id.to_deprecated_string()},
descriptor) descriptor)
else: else:
kwargs = {'course_id': course_id, kwargs = {'course_id': course_id.to_deprecated_string(),
'location': descriptor.location.url()} 'location': descriptor.location.to_deprecated_string()}
self._assert_loads('jump_to', kwargs, descriptor, self._assert_loads('jump_to', kwargs, descriptor,
expect_redirect=True, expect_redirect=True,
...@@ -118,7 +108,7 @@ class PageLoaderTestCase(LoginEnrollmentTestCase): ...@@ -118,7 +108,7 @@ class PageLoaderTestCase(LoginEnrollmentTestCase):
if response.status_code != 200: if response.status_code != 200:
self.fail('Status %d for page %s' % self.fail('Status %d for page %s' %
(response.status_code, descriptor.location.url())) (response.status_code, descriptor.location))
if expect_redirect: if expect_redirect:
self.assertEqual(response.redirect_chain[0][1], 302) self.assertEqual(response.redirect_chain[0][1], 302)
...@@ -142,7 +132,7 @@ class TestXmlCoursesLoad(ModuleStoreTestCase, PageLoaderTestCase): ...@@ -142,7 +132,7 @@ class TestXmlCoursesLoad(ModuleStoreTestCase, PageLoaderTestCase):
# Load one of the XML based courses # Load one of the XML based courses
# Our test mapping rules allow the MixedModuleStore # Our test mapping rules allow the MixedModuleStore
# to load this course from XML, not Mongo. # to load this course from XML, not Mongo.
self.check_all_pages_load('edX/toy/2012_Fall') self.check_all_pages_load(SlashSeparatedCourseKey('edX', 'toy', '2012_Fall'))
# Importing XML courses isn't possible with MixedModuleStore, # Importing XML courses isn't possible with MixedModuleStore,
...@@ -169,7 +159,7 @@ class TestMongoCoursesLoad(ModuleStoreTestCase, PageLoaderTestCase): ...@@ -169,7 +159,7 @@ class TestMongoCoursesLoad(ModuleStoreTestCase, PageLoaderTestCase):
</table_of_contents> </table_of_contents>
""").strip() """).strip()
location = Location(['i4x', 'edX', 'toy', 'course', '2012_Fall', None]) location = SlashSeparatedCourseKey('edX', 'toy', '2012_Fall').make_usage_key('course', '2012_Fall')
course = self.store.get_item(location) course = self.store.get_item(location)
self.assertGreater(len(course.textbooks), 0) self.assertGreater(len(course.textbooks), 0)
...@@ -180,8 +170,7 @@ class TestDraftModuleStore(ModuleStoreTestCase): ...@@ -180,8 +170,7 @@ class TestDraftModuleStore(ModuleStoreTestCase):
store = modulestore() store = modulestore()
# fix was to allow get_items() to take the course_id parameter # fix was to allow get_items() to take the course_id parameter
store.get_items(Location(None, None, 'vertical', None, None), store.get_items(SlashSeparatedCourseKey('abc', 'def', 'ghi'), category='vertical')
course_id='abc', depth=0)
# test success is just getting through the above statement. # test success is just getting through the above statement.
# The bug was that 'course_id' argument was # The bug was that 'course_id' argument was
......
...@@ -37,15 +37,15 @@ from student.models import UserTestGroup, CourseEnrollment ...@@ -37,15 +37,15 @@ from student.models import UserTestGroup, CourseEnrollment
from student.views import course_from_id, single_course_reverification_info from student.views import course_from_id, single_course_reverification_info
from util.cache import cache, cache_if_anonymous from util.cache import cache, cache_if_anonymous
from xblock.fragment import Fragment from xblock.fragment import Fragment
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from xmodule.modulestore.search import path_to_location from xmodule.modulestore.search import path_to_location
from xmodule.course_module import CourseDescriptor
from xmodule.tabs import CourseTabList, StaffGradingTab, PeerGradingTab, OpenEndedGradingTab from xmodule.tabs import CourseTabList, StaffGradingTab, PeerGradingTab, OpenEndedGradingTab
import shoppingcart import shoppingcart
from opaque_keys import InvalidKeyError
from microsite_configuration import microsite from microsite_configuration import microsite
from xmodule.modulestore.locations import SlashSeparatedCourseKey
log = logging.getLogger("edx.courseware") log = logging.getLogger("edx.courseware")
...@@ -106,7 +106,7 @@ def render_accordion(request, course, chapter, section, field_data_cache): ...@@ -106,7 +106,7 @@ def render_accordion(request, course, chapter, section, field_data_cache):
context = dict([ context = dict([
('toc', toc), ('toc', toc),
('course_id', course.id), ('course_id', course.id.to_deprecated_string()),
('csrf', csrf(request)['csrf_token']), ('csrf', csrf(request)['csrf_token']),
('due_date_display_format', course.due_date_display_format) ('due_date_display_format', course.due_date_display_format)
] + template_imports.items()) ] + template_imports.items())
...@@ -152,7 +152,7 @@ def redirect_to_course_position(course_module): ...@@ -152,7 +152,7 @@ def redirect_to_course_position(course_module):
the first child. the first child.
""" """
urlargs = {'course_id': course_module.id} urlargs = {'course_id': course_module.id.to_deprecated_string()}
chapter = get_current_child(course_module) chapter = get_current_child(course_module)
if chapter is None: if chapter is None:
# oops. Something bad has happened. # oops. Something bad has happened.
...@@ -176,7 +176,7 @@ def save_child_position(seq_module, child_name): ...@@ -176,7 +176,7 @@ def save_child_position(seq_module, child_name):
child_name: url_name of the child child_name: url_name of the child
""" """
for position, c in enumerate(seq_module.get_display_items(), start=1): for position, c in enumerate(seq_module.get_display_items(), start=1):
if c.url_name == child_name: if c.location.name == child_name:
# Only save if position changed # Only save if position changed
if position != seq_module.position: if position != seq_module.position:
seq_module.position = position seq_module.position = position
...@@ -241,29 +241,30 @@ def index(request, course_id, chapter=None, section=None, ...@@ -241,29 +241,30 @@ def index(request, course_id, chapter=None, section=None,
- HTTPresponse - HTTPresponse
""" """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
user = User.objects.prefetch_related("groups").get(id=request.user.id) user = User.objects.prefetch_related("groups").get(id=request.user.id)
request.user = user # keep just one instance of User request.user = user # keep just one instance of User
course = get_course_with_access(user, course_id, 'load', depth=2) course = get_course_with_access(user, 'load', course_key, depth=2)
staff_access = has_access(user, course, 'staff') staff_access = has_access(user, 'staff', course)
registered = registered_for_course(course, user) registered = registered_for_course(course, user)
if not registered: if not registered:
# TODO (vshnayder): do course instructors need to be registered to see course? # TODO (vshnayder): do course instructors need to be registered to see course?
log.debug(u'User %s tried to view course %s but is not enrolled', user, course.location.url()) log.debug(u'User %s tried to view course %s but is not enrolled', user, course.location.to_deprecated_string())
return redirect(reverse('about_course', args=[course.id])) return redirect(reverse('about_course', args=[course_key.to_deprecated_string()]))
masq = setup_masquerade(request, staff_access) masq = setup_masquerade(request, staff_access)
try: try:
field_data_cache = FieldDataCache.cache_for_descriptor_descendents( field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
course.id, user, course, depth=2) course_key, user, course, depth=2)
course_module = get_module_for_descriptor(user, request, course, field_data_cache, course.id) course_module = get_module_for_descriptor(user, request, course, field_data_cache, course_key)
if course_module is None: if course_module is None:
log.warning(u'If you see this, something went wrong: if we got this' log.warning(u'If you see this, something went wrong: if we got this'
u' far, should have gotten a course module for this user') u' far, should have gotten a course module for this user')
return redirect(reverse('about_course', args=[course.id])) return redirect(reverse('about_course', args=[course_key.to_deprecated_string()]))
studio_url = get_studio_url(course_id, 'course') studio_url = get_studio_url(course_key, 'course')
if chapter is None: if chapter is None:
return redirect_to_course_position(course_module) return redirect_to_course_position(course_module)
...@@ -279,7 +280,7 @@ def index(request, course_id, chapter=None, section=None, ...@@ -279,7 +280,7 @@ def index(request, course_id, chapter=None, section=None,
'studio_url': studio_url, 'studio_url': studio_url,
'masquerade': masq, 'masquerade': masq,
'xqa_server': settings.FEATURES.get('USE_XQA_SERVER', 'http://xqa:server@content-qa.mitx.mit.edu/xqa'), 'xqa_server': settings.FEATURES.get('USE_XQA_SERVER', 'http://xqa:server@content-qa.mitx.mit.edu/xqa'),
'reverifications': fetch_reverify_banner_info(request, course_id), 'reverifications': fetch_reverify_banner_info(request, course_key),
} }
# Only show the chat if it's enabled by the course and in the # Only show the chat if it's enabled by the course and in the
...@@ -294,44 +295,44 @@ def index(request, course_id, chapter=None, section=None, ...@@ -294,44 +295,44 @@ def index(request, course_id, chapter=None, section=None,
context['show_chat'] = show_chat context['show_chat'] = show_chat
chapter_descriptor = course.get_child_by(lambda m: m.url_name == chapter) chapter_descriptor = course.get_child_by(lambda m: m.location.name == chapter)
if chapter_descriptor is not None: if chapter_descriptor is not None:
save_child_position(course_module, chapter) save_child_position(course_module, chapter)
else: else:
raise Http404('No chapter descriptor found with name {}'.format(chapter)) raise Http404('No chapter descriptor found with name {}'.format(chapter))
chapter_module = course_module.get_child_by(lambda m: m.url_name == chapter) chapter_module = course_module.get_child_by(lambda m: m.location.name == chapter)
if chapter_module is None: if chapter_module is None:
# User may be trying to access a chapter that isn't live yet # User may be trying to access a chapter that isn't live yet
if masq == 'student': # if staff is masquerading as student be kinder, don't 404 if masq == 'student': # if staff is masquerading as student be kinder, don't 404
log.debug('staff masq as student: no chapter %s' % chapter) log.debug('staff masq as student: no chapter %s' % chapter)
return redirect(reverse('courseware', args=[course.id])) return redirect(reverse('courseware', args=[course.id.to_deprecated_string()]))
raise Http404 raise Http404
if section is not None: if section is not None:
section_descriptor = chapter_descriptor.get_child_by(lambda m: m.url_name == section) section_descriptor = chapter_descriptor.get_child_by(lambda m: m.location.name == section)
if section_descriptor is None: if section_descriptor is None:
# Specifically asked-for section doesn't exist # Specifically asked-for section doesn't exist
if masq == 'student': # if staff is masquerading as student be kinder, don't 404 if masq == 'student': # if staff is masquerading as student be kinder, don't 404
log.debug('staff masq as student: no section %s' % section) log.debug('staff masq as student: no section %s' % section)
return redirect(reverse('courseware', args=[course.id])) return redirect(reverse('courseware', args=[course.id.to_deprecated_string()]))
raise Http404 raise Http404
# cdodge: this looks silly, but let's refetch the section_descriptor with depth=None # cdodge: this looks silly, but let's refetch the section_descriptor with depth=None
# which will prefetch the children more efficiently than doing a recursive load # which will prefetch the children more efficiently than doing a recursive load
section_descriptor = modulestore().get_instance(course.id, section_descriptor.location, depth=None) section_descriptor = modulestore().get_item(section_descriptor.location, depth=None)
# Load all descendants of the section, because we're going to display its # Load all descendants of the section, because we're going to display its
# html, which in general will need all of its children # html, which in general will need all of its children
section_field_data_cache = FieldDataCache.cache_for_descriptor_descendents( section_field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
course_id, user, section_descriptor, depth=None) course_key, user, section_descriptor, depth=None)
section_module = get_module_for_descriptor( section_module = get_module_for_descriptor(
request.user, request.user,
request, request,
section_descriptor, section_descriptor,
section_field_data_cache, section_field_data_cache,
course_id, course_key,
position position
) )
...@@ -346,14 +347,16 @@ def index(request, course_id, chapter=None, section=None, ...@@ -346,14 +347,16 @@ def index(request, course_id, chapter=None, section=None,
context['section_title'] = section_descriptor.display_name_with_default context['section_title'] = section_descriptor.display_name_with_default
else: else:
# section is none, so display a message # section is none, so display a message
studio_url = get_studio_url(course_id, 'course') studio_url = get_studio_url(course_key, 'course')
prev_section = get_current_child(chapter_module) prev_section = get_current_child(chapter_module)
if prev_section is None: if prev_section is None:
# Something went wrong -- perhaps this chapter has no sections visible to the user # Something went wrong -- perhaps this chapter has no sections visible to the user
raise Http404 raise Http404
prev_section_url = reverse('courseware_section', kwargs={'course_id': course_id, prev_section_url = reverse('courseware_section', kwargs={
'course_id': course_key.to_deprecated_string(),
'chapter': chapter_descriptor.url_name, 'chapter': chapter_descriptor.url_name,
'section': prev_section.url_name}) 'section': prev_section.url_name
})
context['fragment'] = Fragment(content=render_to_string( context['fragment'] = Fragment(content=render_to_string(
'courseware/welcome-back.html', 'courseware/welcome-back.html',
{ {
...@@ -404,13 +407,8 @@ def jump_to_id(request, course_id, module_id): ...@@ -404,13 +407,8 @@ def jump_to_id(request, course_id, module_id):
This entry point allows for a shorter version of a jump to where just the id of the element is This entry point allows for a shorter version of a jump to where just the id of the element is
passed in. This assumes that id is unique within the course_id namespace passed in. This assumes that id is unique within the course_id namespace
""" """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course_location = CourseDescriptor.id_to_location(course_id) items = modulestore().get_items(course_key, name=module_id)
items = modulestore().get_items(
Location('i4x', course_location.org, course_location.course, None, module_id),
course_id=course_id
)
if len(items) == 0: if len(items) == 0:
raise Http404( raise Http404(
...@@ -420,10 +418,10 @@ def jump_to_id(request, course_id, module_id): ...@@ -420,10 +418,10 @@ def jump_to_id(request, course_id, module_id):
if len(items) > 1: if len(items) > 1:
log.warning( log.warning(
u"Multiple items found with id: {0} in course_id: {1}. Referer: {2}. Using first: {3}".format( u"Multiple items found with id: {0} in course_id: {1}. Referer: {2}. Using first: {3}".format(
module_id, course_id, request.META.get("HTTP_REFERER", ""), items[0].location.url() module_id, course_id, request.META.get("HTTP_REFERER", ""), items[0].location.to_deprecated_string()
)) ))
return jump_to(request, course_id, items[0].location.url()) return jump_to(request, course_id, items[0].location.to_deprecated_string())
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -436,31 +434,29 @@ def jump_to(request, course_id, location): ...@@ -436,31 +434,29 @@ def jump_to(request, course_id, location):
Otherwise, delegates to the index view to figure out whether this user Otherwise, delegates to the index view to figure out whether this user
has access, and what they should see. has access, and what they should see.
""" """
# Complain if the location isn't valid
try: try:
location = Location(location) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
except InvalidLocationError: usage_key = course_key.make_usage_key_from_deprecated_string(location)
raise Http404("Invalid location") except InvalidKeyError:
raise Http404(u"Invalid course_key or usage_key")
# Complain if there's not data for this location
try: try:
(course_id, chapter, section, position) = path_to_location(modulestore(), course_id, location) (course_key, chapter, section, position) = path_to_location(modulestore(), usage_key)
except ItemNotFoundError: except ItemNotFoundError:
raise Http404(u"No data at this location: {0}".format(location)) raise Http404(u"No data at this location: {0}".format(usage_key))
except NoPathToItem: except NoPathToItem:
raise Http404(u"This location is not in any class: {0}".format(location)) raise Http404(u"This location is not in any class: {0}".format(usage_key))
# choose the appropriate view (and provide the necessary args) based on the # choose the appropriate view (and provide the necessary args) based on the
# args provided by the redirect. # args provided by the redirect.
# Rely on index to do all error handling and access control. # Rely on index to do all error handling and access control.
if chapter is None: if chapter is None:
return redirect('courseware', course_id=course_id) return redirect('courseware', course_id=course_key.to_deprecated_string())
elif section is None: elif section is None:
return redirect('courseware_chapter', course_id=course_id, chapter=chapter) return redirect('courseware_chapter', course_id=course_key.to_deprecated_string(), chapter=chapter)
elif position is None: elif position is None:
return redirect('courseware_section', course_id=course_id, chapter=chapter, section=section) return redirect('courseware_section', course_id=course_key.to_deprecated_string(), chapter=chapter, section=section)
else: else:
return redirect('courseware_position', course_id=course_id, chapter=chapter, section=section, position=position) return redirect('courseware_position', course_id=course_key.to_deprecated_string(), chapter=chapter, section=section, position=position)
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -470,15 +466,16 @@ def course_info(request, course_id): ...@@ -470,15 +466,16 @@ def course_info(request, course_id):
Assumes the course_id is in a valid format. Assumes the course_id is in a valid format.
""" """
course = get_course_with_access(request.user, course_id, 'load') course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
staff_access = has_access(request.user, course, 'staff') course = get_course_with_access(request.user, 'load', course_key)
staff_access = has_access(request.user, 'staff', course)
masq = setup_masquerade(request, staff_access) # allow staff to toggle masquerade on info page masq = setup_masquerade(request, staff_access) # allow staff to toggle masquerade on info page
studio_url = get_studio_url(course_id, 'course_info') reverifications = fetch_reverify_banner_info(request, course_key)
reverifications = fetch_reverify_banner_info(request, course_id) studio_url = get_studio_url(course_key, 'course_info')
context = { context = {
'request': request, 'request': request,
'course_id': course_id, 'course_id': course_key.to_deprecated_string(),
'cache': None, 'cache': None,
'course': course, 'course': course,
'staff_access': staff_access, 'staff_access': staff_access,
...@@ -497,7 +494,8 @@ def static_tab(request, course_id, tab_slug): ...@@ -497,7 +494,8 @@ def static_tab(request, course_id, tab_slug):
Assumes the course_id is in a valid format. Assumes the course_id is in a valid format.
""" """
course = get_course_with_access(request.user, course_id, 'load') course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'load', course_key)
tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug) tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug)
if tab is None: if tab is None:
...@@ -527,8 +525,9 @@ def syllabus(request, course_id): ...@@ -527,8 +525,9 @@ def syllabus(request, course_id):
Assumes the course_id is in a valid format. Assumes the course_id is in a valid format.
""" """
course = get_course_with_access(request.user, course_id, 'load') course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
staff_access = has_access(request.user, course, 'staff') course = get_course_with_access(request.user, 'load', course_key)
staff_access = has_access(request.user, 'staff', course)
return render_to_response('courseware/syllabus.html', { return render_to_response('courseware/syllabus.html', {
'course': course, 'course': course,
...@@ -562,18 +561,18 @@ def course_about(request, course_id): ...@@ -562,18 +561,18 @@ def course_about(request, course_id):
settings.FEATURES.get('ENABLE_MKTG_SITE', False) settings.FEATURES.get('ENABLE_MKTG_SITE', False)
): ):
raise Http404 raise Http404
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, course_id, 'see_exists') course = get_course_with_access(request.user, 'see_exists', course_key)
registered = registered_for_course(course, request.user) registered = registered_for_course(course, request.user)
staff_access = has_access(request.user, course, 'staff') staff_access = has_access(request.user, 'staff', course)
studio_url = get_studio_url(course_id, 'settings/details') studio_url = get_studio_url(course_key, 'settings/details')
if has_access(request.user, course, 'load'): if has_access(request.user, 'load', course):
course_target = reverse('info', args=[course.id]) course_target = reverse('info', args=[course.id.to_deprecated_string()])
else: else:
course_target = reverse('about_course', args=[course.id]) course_target = reverse('about_course', args=[course.id.to_deprecated_string()])
show_courseware_link = (has_access(request.user, course, 'load') or show_courseware_link = (has_access(request.user, 'load', course) or
settings.FEATURES.get('ENABLE_LMS_MIGRATION')) settings.FEATURES.get('ENABLE_LMS_MIGRATION'))
# Note: this is a flow for payment for course registration, not the Verified Certificate flow. # Note: this is a flow for payment for course registration, not the Verified Certificate flow.
...@@ -582,14 +581,14 @@ def course_about(request, course_id): ...@@ -582,14 +581,14 @@ def course_about(request, course_id):
reg_then_add_to_cart_link = "" reg_then_add_to_cart_link = ""
if (settings.FEATURES.get('ENABLE_SHOPPING_CART') and if (settings.FEATURES.get('ENABLE_SHOPPING_CART') and
settings.FEATURES.get('ENABLE_PAID_COURSE_REGISTRATION')): settings.FEATURES.get('ENABLE_PAID_COURSE_REGISTRATION')):
registration_price = CourseMode.min_course_price_for_currency(course_id, registration_price = CourseMode.min_course_price_for_currency(course_key,
settings.PAID_COURSE_REGISTRATION_CURRENCY[0]) settings.PAID_COURSE_REGISTRATION_CURRENCY[0])
if request.user.is_authenticated(): if request.user.is_authenticated():
cart = shoppingcart.models.Order.get_cart_for_user(request.user) cart = shoppingcart.models.Order.get_cart_for_user(request.user)
in_cart = shoppingcart.models.PaidCourseRegistration.contained_in_order(cart, course_id) in_cart = shoppingcart.models.PaidCourseRegistration.contained_in_order(cart, course_key)
reg_then_add_to_cart_link = "{reg_url}?course_id={course_id}&enrollment_action=add_to_cart".format( reg_then_add_to_cart_link = "{reg_url}?course_id={course_id}&enrollment_action=add_to_cart".format(
reg_url=reverse('register_user'), course_id=course.id) reg_url=reverse('register_user'), course_id=course.id.to_deprecated_string())
# see if we have already filled up all allowed enrollments # see if we have already filled up all allowed enrollments
is_course_full = CourseEnrollment.is_course_full(course) is_course_full = CourseEnrollment.is_course_full(course)
...@@ -615,25 +614,26 @@ def mktg_course_about(request, course_id): ...@@ -615,25 +614,26 @@ def mktg_course_about(request, course_id):
This is the button that gets put into an iframe on the Drupal site This is the button that gets put into an iframe on the Drupal site
""" """
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
try: try:
course = get_course_with_access(request.user, course_id, 'see_exists') course = get_course_with_access(request.user, 'see_exists', course_key)
except (ValueError, Http404) as e: except (ValueError, Http404) as e:
# if a course does not exist yet, display a coming # if a course does not exist yet, display a coming
# soon button # soon button
return render_to_response( return render_to_response(
'courseware/mktg_coming_soon.html', {'course_id': course_id} 'courseware/mktg_coming_soon.html', {'course_id': course_key.to_deprecated_string()}
) )
registered = registered_for_course(course, request.user) registered = registered_for_course(course, request.user)
if has_access(request.user, course, 'load'): if has_access(request.user, 'load', course):
course_target = reverse('info', args=[course.id]) course_target = reverse('info', args=[course.id.to_deprecated_string()])
else: else:
course_target = reverse('about_course', args=[course.id]) course_target = reverse('about_course', args=[course.id.to_deprecated_string()])
allow_registration = has_access(request.user, course, 'enroll') allow_registration = has_access(request.user, 'enroll', course)
show_courseware_link = (has_access(request.user, course, 'load') or show_courseware_link = (has_access(request.user, 'load', course) or
settings.FEATURES.get('ENABLE_LMS_MIGRATION')) settings.FEATURES.get('ENABLE_LMS_MIGRATION'))
course_modes = CourseMode.modes_for_course(course.id) course_modes = CourseMode.modes_for_course(course.id)
...@@ -656,10 +656,10 @@ def progress(request, course_id, student_id=None): ...@@ -656,10 +656,10 @@ def progress(request, course_id, student_id=None):
there are unanticipated errors. there are unanticipated errors.
""" """
with grades.manual_transaction(): with grades.manual_transaction():
return _progress(request, course_id, student_id) return _progress(request, SlashSeparatedCourseKey.from_deprecated_string(course_id), student_id)
def _progress(request, course_id, student_id): def _progress(request, course_key, student_id):
""" """
Unwrapped version of "progress". Unwrapped version of "progress".
...@@ -667,8 +667,8 @@ def _progress(request, course_id, student_id): ...@@ -667,8 +667,8 @@ def _progress(request, course_id, student_id):
Course staff are allowed to see the progress of students in their class. Course staff are allowed to see the progress of students in their class.
""" """
course = get_course_with_access(request.user, course_id, 'load', depth=None) course = get_course_with_access(request.user, 'load', course_key, depth=None)
staff_access = has_access(request.user, course, 'staff') staff_access = has_access(request.user, 'staff', course)
if student_id is None or student_id == request.user.id: if student_id is None or student_id == request.user.id:
# always allowed to see your own profile # always allowed to see your own profile
...@@ -687,7 +687,7 @@ def _progress(request, course_id, student_id): ...@@ -687,7 +687,7 @@ def _progress(request, course_id, student_id):
student = User.objects.prefetch_related("groups").get(id=student.id) student = User.objects.prefetch_related("groups").get(id=student.id)
courseware_summary = grades.progress_summary(student, request, course) courseware_summary = grades.progress_summary(student, request, course)
studio_url = get_studio_url(course_id, 'settings/grading') studio_url = get_studio_url(course_key, 'settings/grading')
grade_summary = grades.grade(student, request, course) grade_summary = grades.grade(student, request, course)
if courseware_summary is None: if courseware_summary is None:
...@@ -701,7 +701,7 @@ def _progress(request, course_id, student_id): ...@@ -701,7 +701,7 @@ def _progress(request, course_id, student_id):
'grade_summary': grade_summary, 'grade_summary': grade_summary,
'staff_access': staff_access, 'staff_access': staff_access,
'student': student, 'student': student,
'reverifications': fetch_reverify_banner_info(request, course_id) 'reverifications': fetch_reverify_banner_info(request, course_key)
} }
with grades.manual_transaction(): with grades.manual_transaction():
...@@ -710,7 +710,7 @@ def _progress(request, course_id, student_id): ...@@ -710,7 +710,7 @@ def _progress(request, course_id, student_id):
return response return response
def fetch_reverify_banner_info(request, course_id): def fetch_reverify_banner_info(request, course_key):
""" """
Fetches needed context variable to display reverification banner in courseware Fetches needed context variable to display reverification banner in courseware
""" """
...@@ -718,8 +718,8 @@ def fetch_reverify_banner_info(request, course_id): ...@@ -718,8 +718,8 @@ def fetch_reverify_banner_info(request, course_id):
user = request.user user = request.user
if not user.id: if not user.id:
return reverifications return reverifications
enrollment = CourseEnrollment.get_or_create_enrollment(request.user, course_id) enrollment = CourseEnrollment.get_or_create_enrollment(request.user, course_key)
course = course_from_id(course_id) course = course_from_id(course_key)
info = single_course_reverification_info(user, course, enrollment) info = single_course_reverification_info(user, course, enrollment)
if info: if info:
reverifications[info.status].append(info) reverifications[info.status].append(info)
...@@ -733,9 +733,18 @@ def submission_history(request, course_id, student_username, location): ...@@ -733,9 +733,18 @@ def submission_history(request, course_id, student_username, location):
Right now this only works for problems because that's all Right now this only works for problems because that's all
StudentModuleHistory records. StudentModuleHistory records.
""" """
try:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
except (InvalidKeyError, AssertionError):
return HttpResponse(escape(_(u'Invalid course id.')))
course = get_course_with_access(request.user, course_id, 'load') try:
staff_access = has_access(request.user, course, 'staff') usage_key = course_key.make_usage_key_from_deprecated_string(location)
except (InvalidKeyError, AssertionError):
return HttpResponse(escape(_(u'Invalid location.')))
course = get_course_with_access(request.user, 'load', course_key)
staff_access = has_access(request.user, 'staff', course)
# Permission Denied if they don't have staff access and are trying to see # Permission Denied if they don't have staff access and are trying to see
# somebody else's submission history. # somebody else's submission history.
...@@ -745,8 +754,8 @@ def submission_history(request, course_id, student_username, location): ...@@ -745,8 +754,8 @@ def submission_history(request, course_id, student_username, location):
try: try:
student = User.objects.get(username=student_username) student = User.objects.get(username=student_username)
student_module = StudentModule.objects.get( student_module = StudentModule.objects.get(
course_id=course_id, course_id=course_key,
module_state_key=location, module_id=usage_key,
student_id=student.id student_id=student.id
) )
except User.DoesNotExist: except User.DoesNotExist:
...@@ -756,7 +765,6 @@ def submission_history(request, course_id, student_username, location): ...@@ -756,7 +765,6 @@ def submission_history(request, course_id, student_username, location):
username=student_username, username=student_username,
location=location location=location
))) )))
history_entries = StudentModuleHistory.objects.filter( history_entries = StudentModuleHistory.objects.filter(
student_module=student_module student_module=student_module
).order_by('-id') ).order_by('-id')
...@@ -772,7 +780,7 @@ def submission_history(request, course_id, student_username, location): ...@@ -772,7 +780,7 @@ def submission_history(request, course_id, student_username, location):
'history_entries': history_entries, 'history_entries': history_entries,
'username': student.username, 'username': student.username,
'location': location, 'location': location,
'course_id': course_id 'course_id': course_key.to_deprecated_string()
} }
return render_to_response('courseware/submission_history.html', context) return render_to_response('courseware/submission_history.html', context)
...@@ -801,15 +809,12 @@ def get_static_tab_contents(request, course, tab): ...@@ -801,15 +809,12 @@ def get_static_tab_contents(request, course, tab):
""" """
Returns the contents for the given static tab Returns the contents for the given static tab
""" """
loc = Location( loc = course.id.make_usage_key(
course.location.tag,
course.location.org,
course.location.course,
tab.type, tab.type,
tab.url_slug, tab.url_slug,
) )
field_data_cache = FieldDataCache.cache_for_descriptor_descendents( field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
course.id, request.user, modulestore().get_instance(course.id, loc), depth=0 course.id, request.user, modulestore().get_item(loc), depth=0
) )
tab_module = get_module( tab_module = get_module(
request.user, request, loc, field_data_cache, course.id, static_asset_path=course.static_asset_path request.user, request, loc, field_data_cache, course.id, static_asset_path=course.static_asset_path
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment