Commit c6c5bc9e by stv

Fix Opaque Keys issues

parent e490e71d
""" Unit tests for utility methods in views.py. """
from django.conf import settings
from contentstore.utils import get_modulestore
from contentstore.utils import reverse_course_url
from contentstore.views.utility import expand_utility_action_url
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.django import loc_mapper
import json
import mock
......@@ -16,8 +16,7 @@ class UtilitiesTestCase(CourseTestCase):
""" Creates the test course. """
super(UtilitiesTestCase, self).setUp()
self.course = CourseFactory.create(org='mitX', number='333', display_name='Utilities Course')
self.location = loc_mapper().translate_location(self.course.location.course_id, self.course.location, False, True)
self.utilities_url = self.location.url_reverse('utilities/', '')
self.utilities_url = reverse_course_url('utility_handler', self.course.id)
def get_persisted_utilities(self):
""" Returns the utilities. """
......@@ -41,16 +40,15 @@ class UtilitiesTestCase(CourseTestCase):
response = self.client.get(self.utilities_url)
self.assertContains(response, "Bulk Operations")
# Verify expansion of action URL happened.
self.assertContains(response, 'captions/mitX.333.Utilities_Course')
self.assertContains(response, '/utility/captions/slashes:mitX+333+Utilities_Course')
# Verify persisted utility does NOT have expanded URL.
utility_0 = self.get_persisted_utilities()[0]
self.assertEqual('utility/captions', get_action_url(utility_0, 0))
self.assertEqual('utility_captions_handler', get_action_url(utility_0, 0))
payload = response.content
# Now delete the utilities from the course and verify they get repopulated (for courses
# created before utilities were introduced).
with mock.patch('django.conf.settings.COURSE_UTILITIES', []):
print settings.COURSE_UTILITIES
modulestore = get_modulestore(self.course.location)
modulestore.update_item(self.course, self.user.id)
self.assertEqual(self.get_persisted_utilities(), [])
......@@ -68,25 +66,25 @@ class UtilitiesTestCase(CourseTestCase):
# Verify that persisted utilities do not have expanded action URLs.
# compare_utilities will verify that returned_utilities DO have expanded action URLs.
pers = self.get_persisted_utilities()
self.assertEqual('utility/captions', get_first_item(pers[0]).get('action_url'))
self.assertEqual('utility_captions_handler', get_first_item(pers[0]).get('action_url'))
for pay, resp in zip(pers, returned_utilities):
self.compare_utilities(pay, resp)
def test_utilities_post_unsupported(self):
""" Post operation is not supported. """
update_url = self.location.url_reverse('utilities/', '100')
update_url = reverse_course_url('utility_handler', self.course.id)
response = self.client.post(update_url)
self.assertEqual(response.status_code, 404)
def test_utilities_put_unsupported(self):
""" Put operation is not supported. """
update_url = self.location.url_reverse('utilities/', '100')
update_url = reverse_course_url('utility_handler', self.course.id)
response = self.client.put(update_url)
self.assertEqual(response.status_code, 404)
def test_utilities_delete_unsupported(self):
""" Delete operation is not supported. """
update_url = self.location.url_reverse('utilities/', '100')
update_url = reverse_course_url('utility_handler', self.course.id)
response = self.client.delete(update_url)
self.assertEqual(response.status_code, 404)
......@@ -107,7 +105,7 @@ class UtilitiesTestCase(CourseTestCase):
# Verify no side effect in the original list.
self.assertEqual(get_action_url(utility, index), stored)
test_expansion(settings.COURSE_UTILITIES[0], 0, 'utility/captions', '/utility/captions/mitX.333.Utilities_Course/branch/draft/block/Utilities_Course')
test_expansion(settings.COURSE_UTILITIES[0], 0, 'utility_captions_handler', '/utility/captions/slashes:mitX+333+Utilities_Course')
def get_first_item(utility):
......
......@@ -12,6 +12,7 @@ from edxmako.shortcuts import render_to_response
from django.http import HttpResponseNotFound
from django.core.exceptions import PermissionDenied
from xmodule.modulestore.keys import CourseKey
from xmodule.modulestore.django import loc_mapper
from xmodule.modulestore.django import modulestore
from contentstore.utils import get_modulestore, reverse_course_url
......@@ -113,12 +114,12 @@ def expand_checklist_action_url(course_module, checklist):
expanded_checklist = copy.deepcopy(checklist)
urlconf_map = {
"ManageUsers": "course_team",
"CourseOutline": "course",
"StaticPages": "tabs",
"SettingsDetails": "settings/details",
"SettingsGrading": "settings/grading",
"SettingsAdvanced": "settings/advanced",
"ManageUsers": "course_team_handler",
"CourseOutline": "course_handler",
"StaticPages": "tabs_handler",
"SettingsDetails": "settings_handler",
"SettingsGrading": "grading_handler",
"SettingsAdvanced": "advanced_settings_handler",
}
lms_urlconf_map = {
"Wiki": "course_wiki",
......@@ -128,16 +129,15 @@ def expand_checklist_action_url(course_module, checklist):
for item in expanded_checklist.get('items'):
action_url = item.get('action_url')
if action_url in urlconf_map:
url_prefix = urlconf_map[action_url]
ctx_loc = course_module.location
location = loc_mapper().translate_location(ctx_loc.course_id, ctx_loc, False, True)
item['action_url'] = location.url_reverse(url_prefix, '')
item['action_url'] = reverse_course_url(urlconf_map[action_url], course_module.id)
elif action_url in lms_urlconf_map:
lms_base = settings.LMS_BASE
course_id = course_module.location.course_id
url_postfix = lms_urlconf_map[action_url]
url = 'http://' + lms_base + '/courses/' + course_id + '/' + url_postfix
item['action_url'] = url
item['action_url'] = '/'.join([
'http:/',
settings.LMS_BASE,
'courses',
course_module.id.to_deprecated_string(),
lms_urlconf_map[action_url],
])
return expanded_checklist
......
......@@ -11,12 +11,15 @@ from django.conf import settings
from xmodule.video_module import transcripts_utils
from contentstore.tests.utils import CourseTestCase
from contentstore.utils import reverse_course_url
from contentstore.views.utilities.captions import get_videos
from cache_toolbox.core import del_cached_content
from xmodule.modulestore.django import modulestore
from xmodule.contentstore.django import contentstore, _CONTENTSTORE
from xmodule.contentstore.content import StaticContent
from xmodule.exceptions import NotFoundError
from xmodule.modulestore.django import loc_mapper
from xmodule.modulestore.keys import UsageKey
from xmodule.modulestore.locator import BlockUsageLocator
from contentstore.tests.modulestore_config import TEST_MODULESTORE
......@@ -35,8 +38,7 @@ class Basetranscripts(CourseTestCase):
"""Remove, if transcripts content exists."""
for youtube_id in self.get_youtube_ids().values():
filename = 'subs_{0}.srt.sjson'.format(youtube_id)
content_location = StaticContent.compute_location(
self.org, self.number, filename)
content_location = StaticContent.compute_location(self.course.id, filename)
try:
content = contentstore().find(content_location)
contentstore().delete(content.get_id())
......@@ -46,20 +48,22 @@ class Basetranscripts(CourseTestCase):
def setUp(self):
"""Create initial data."""
super(Basetranscripts, self).setUp()
self.location = loc_mapper().translate_location(
self.course.location.course_id, self.course.location, False, True
)
self.captions_url = self.location.url_reverse('utility/captions/', '')
self.location = self.course.id.make_usage_key('course', self.course.id.run)
self.captions_url = reverse_course_url('utility_captions_handler', self.course.id)
self.unicode_locator = unicode(self.location)
# Add video module
data = {
# 'parent_locator': self.location.to_deprecated_string(),
'parent_locator': self.unicode_locator,
'category': 'video',
'type': 'video'
}
resp = self.client.ajax_post('/xblock', data)
self.item_locator, self.item_location = self._get_locator(resp)
resp = self.client.ajax_post('http://testserver/xblock/', data)
videos = get_videos(self.course)
self.item_location = self._get_locator(resp)
self.item_location_string = str(self.item_location)
self.assertEqual(resp.status_code, 200)
self.item = modulestore().get_item(self.item_location)
......@@ -74,8 +78,10 @@ class Basetranscripts(CourseTestCase):
def _get_locator(self, resp):
""" Returns the locator and old-style location (as a string) from the response returned by a create operation. """
locator = json.loads(resp.content).get('locator')
return locator, loc_mapper().translate_locator_to_location(BlockUsageLocator(locator)).url()
locator_string = json.loads(resp.content).get('locator')
locator = UsageKey.from_string(locator_string)
# locator = self.course.id.make_usage_key('course', locator_string)
return locator
def get_youtube_ids(self):
"""Return youtube speeds and ids."""
......@@ -102,8 +108,7 @@ class TestCheckcaptions(Basetranscripts):
mime_type = 'application/json'
filename = 'subs_{0}.srt.sjson'.format(subs_id)
content_location = StaticContent.compute_location(
self.org, self.number, filename)
content_location = StaticContent.compute_location(self.course.id, filename)
content = StaticContent(content_location, filename, mime_type, filedata)
contentstore().save(content)
del_cached_content(content_location)
......@@ -133,7 +138,7 @@ class TestCheckcaptions(Basetranscripts):
link = self.captions_url
data = {
'video': json.dumps({'location': self.item_location}),
'video': json.dumps({'location': self.item_location_string}),
}
resp = self.client.get(link, data, HTTP_ACCEPT='application/json')
self.assertEqual(resp.status_code, 200)
......@@ -141,7 +146,7 @@ class TestCheckcaptions(Basetranscripts):
json.loads(resp.content),
{
u'status': True,
u'location': self.item_location,
u'location': self.item_location_string,
u'command': u'use_existing',
u'current_item_subs': subs_id,
u'html5_equal': False,
......@@ -173,7 +178,7 @@ class TestCheckcaptions(Basetranscripts):
self.save_subs_to_store(subs, 'JMD_ifUUfsU')
link = self.captions_url
data = {
'video': json.dumps({'location': self.item_location}),
'video': json.dumps({'location': self.item_location_string}),
}
resp = self.client.get(link, data, HTTP_ACCEPT='application/json')
self.assertEqual(resp.status_code, 200)
......@@ -190,7 +195,7 @@ class TestCheckcaptions(Basetranscripts):
u'youtube_diff': True,
u'youtube_local': True,
u'youtube_server': False,
u'location': self.item_location
u'location': self.item_location_string,
}
)
......@@ -216,7 +221,7 @@ class TestCheckcaptions(Basetranscripts):
# Test for raising `ItemNotFoundError` exception.
data = {
'video': json.dumps({'location': '{0}_{1}'.format(self.item_location, 'BAD_LOCATION')}),
'video': json.dumps({'location': '{0}_{1}'.format(self.item_location_string, 'BAD_LOCATION')}),
}
resp = self.client.get(link, data)
self.assertEqual(resp.status_code, 400)
......@@ -229,8 +234,9 @@ class TestCheckcaptions(Basetranscripts):
'category': 'not_video',
'type': 'not_video'
}
resp = self.client.ajax_post('/xblock', data)
item_locator, item_location = self._get_locator(resp)
resp = self.client.ajax_post('http://testserver/xblock/', data)
item_location = self._get_locator(resp)
item_location_string = str(item_location)
subs_id = str(uuid4())
item = modulestore().get_item(item_location)
item.data = textwrap.dedent("""
......@@ -255,7 +261,7 @@ class TestCheckcaptions(Basetranscripts):
link = self.captions_url
data = {
'video': json.dumps({'location': item_location}),
'video': json.dumps({'location': item_location_string}),
}
resp = self.client.get(link, data)
self.assertEqual(resp.status_code, 400)
......
......@@ -139,6 +139,7 @@ class ChecklistTestCase(CourseTestCase):
test_expansion(self.course.checklists[0], 0, 'ManageUsers', '/course_team/slashes:mitX+333+Checklists_Course/')
test_expansion(self.course.checklists[1], 1, 'CourseOutline', '/course/slashes:mitX+333+Checklists_Course')
raise Exception('FUNK CHECKLIST {}'.format(self.course.checklists[2]))
test_expansion(self.course.checklists[2], 0, 'http://help.edge.edx.org/', 'http://help.edge.edx.org/')
......
......@@ -15,6 +15,8 @@ from models.settings.course_grading import CourseGradingModel
from util.json_request import JsonResponse
from xmodule.modulestore.django import modulestore, loc_mapper
from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError, InsufficientSpecificationError
from xmodule.modulestore.keys import CourseKey
from xmodule.modulestore.keys import UsageKey
from xmodule.modulestore.locator import BlockUsageLocator
from xmodule.video_module.transcripts_utils import (
GetTranscriptsFromYouTubeException,
......@@ -22,7 +24,7 @@ from xmodule.video_module.transcripts_utils import (
download_youtube_subs)
from ..access import has_course_access
from ..transcripts_ajax import get_transcripts_presence
from ..course import _get_locator_and_course
from ..course import _get_course_module
log = logging.getLogger(__name__)
......@@ -32,7 +34,7 @@ __all__ = ['utility_captions_handler']
# pylint: disable=unused-argument
@login_required
def utility_captions_handler(request, tag=None, package_id=None, branch=None, version_guid=None, block=None):
def utility_captions_handler(request, course_key_string):
"""
The restful handler for captions requests in the utilities area.
It provides the list of course videos as well as their status. It also lets
......@@ -44,24 +46,25 @@ def utility_captions_handler(request, tag=None, package_id=None, branch=None, ve
POST
json: update the captions of a given video by copying the version of the captions hosted in youtube.
"""
course_key = CourseKey.from_string(course_key_string)
response_format = request.REQUEST.get('format', 'html')
if response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
if request.method == 'POST': # update
try:
locations = _validate_captions_data_update(request)
locations = _validate_captions_data_update(request, course_key)
except TranscriptsRequestValidationException as e:
return error_response(e.message)
return json_update_videos(request, locations)
elif request.method == 'GET': # get status
try:
data, item = _validate_captions_data_get(request)
data, item = _validate_captions_data_get(request, course_key)
except TranscriptsRequestValidationException as e:
return error_response(e.message)
return json_get_video_status(data, item)
else:
return HttpResponseBadRequest()
elif request.method == 'GET': # assume html
return captions_index(request, package_id, branch, version_guid, block)
return captions_index(request, course_key)
else:
return HttpResponseNotFound()
......@@ -77,7 +80,8 @@ def json_update_videos(request, locations):
locations: list of locations of videos to be updated
"""
results = []
for key in locations:
for key_string in locations:
key = UsageKey.from_string(key_string)
try:
#update transcripts
item = modulestore().get_item(key)
......@@ -91,26 +95,28 @@ def json_update_videos(request, locations):
html5[name] = 'html5'
videos['html5'] = html5
captions_dict = get_transcripts_presence(videos, item)
captions_dict.update({'location': key})
captions_dict.update({'location': key_string})
results.append(captions_dict)
except GetTranscriptsFromYouTubeException as e:
log.debug(e)
results.append({'location': key, 'command': e})
results.append({'location': key_string, 'command': e})
return JsonResponse(results)
@login_required
@ensure_csrf_cookie
def captions_index(request, package_id, branch, version_guid, block):
def captions_index(request, course_key):
"""
Display a list of course videos as well as their status (up to date, or out of date)
org, course, name: Attributes of the Location for the item to edit
"""
locator, course = _get_locator_and_course(
package_id, branch, version_guid, block, request.user, depth=3
course = _get_course_module(
course_key,
request.user,
depth=2,
)
return render_to_response('captions.html',
......@@ -134,7 +140,7 @@ def error_response(message, response=None, status_code=400):
return JsonResponse(response, status_code)
def _validate_captions_data_get(request):
def _validate_captions_data_get(request, course_key):
"""
Happens on 'GET'. Validates, that request contains all proper data for transcripts processing.
......@@ -154,11 +160,11 @@ def _validate_captions_data_get(request):
raise TranscriptsRequestValidationException(_('Incoming video data is empty.'))
location = data.get('location')
item = _validate_location(location)
item = _validate_location(location, course_key)
return data, item
def _validate_captions_data_update(request):
def _validate_captions_data_update(request, course_key):
"""
Happens on 'POST'. Validates, that request contains all proper data for transcripts processing.
......@@ -176,11 +182,12 @@ def _validate_captions_data_update(request):
raise TranscriptsRequestValidationException(_('Incoming update_array data is empty.'))
for location in data:
_validate_location(location)
_validate_location(location, course_key)
return data
def _validate_location(location):
def _validate_location(location, course_key):
location = UsageKey.from_string(location)
try:
item = modulestore().get_item(location)
except (ItemNotFoundError, InvalidLocationError, InsufficientSpecificationError):
......
......@@ -8,11 +8,11 @@ from django.views.decorators.http import require_http_methods
from django_future.csrf import ensure_csrf_cookie
from edxmako.shortcuts import render_to_response
from util.json_request import JsonResponse
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import loc_mapper
from xmodule.modulestore.locator import BlockUsageLocator
from ..utils import get_modulestore
from contentstore.utils import reverse_course_url
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.keys import CourseKey
from .access import has_course_access
__all__ = ['utility_handler']
......@@ -22,7 +22,7 @@ __all__ = ['utility_handler']
@require_http_methods(("GET"))
@login_required
@ensure_csrf_cookie
def utility_handler(request, tag=None, package_id=None, branch=None, version_guid=None, block=None):
def utility_handler(request, course_key_string):
"""
The restful handler for utilities.
......@@ -30,22 +30,17 @@ def utility_handler(request, tag=None, package_id=None, branch=None, version_gui
html: return html page for all utilities
json: return json representing all utilities.
"""
location = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
if not has_course_access(request.user, location):
course_key = CourseKey.from_string(course_key_string)
if not has_course_access(request.user, course_key):
raise PermissionDenied()
old_location = loc_mapper().translate_locator_to_location(location)
modulestore = get_modulestore(old_location)
course_module = modulestore.get_item(old_location)
course_module = modulestore().get_course(course_key)
json_request = 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json')
if request.method == 'GET':
expanded_utilities = expand_all_action_urls(course_module)
if json_request:
return JsonResponse(expanded_utilities)
else:
handler_url = location.url_reverse('utilities/', '')
handler_url = reverse_course_url('utility_handler', course_module.id)
return render_to_response('utilities.html',
{
'handler_url': handler_url,
......@@ -80,8 +75,6 @@ def expand_utility_action_url(course_module, utility):
for item in expanded_utility.get('items'):
url_prefix = item.get('action_url')
ctx_loc = course_module.location
location = loc_mapper().translate_location(ctx_loc.course_id, ctx_loc, False, True)
item['action_url'] = location.url_reverse(url_prefix, '')
item['action_url'] = reverse_course_url(url_prefix, course_module.id)
return expanded_utility
......@@ -476,7 +476,7 @@ COURSE_UTILITIES = [
"items": [
{"short_description": "Get all captions from YouTube",
"long_description": "This utility will attempt to get or update captions for all videos in the course from YouTube. Please allow it a couple of minutes to run.",
"action_url": "utility/captions",
"action_url": "utility_captions_handler",
"action_text": "Check Captions",
"action_external": False}]}
]
......
......@@ -90,8 +90,8 @@ urlpatterns += patterns(
url(r'^settings/advanced/(?P<course_key_string>[^/]+)$', 'advanced_settings_handler'),
url(r'^textbooks/(?P<course_key_string>[^/]+)$', 'textbooks_list_handler'),
url(r'^textbooks/(?P<course_key_string>[^/]+)/(?P<textbook_id>\d[^/]*)$', 'textbooks_detail_handler'),
url(r'^utilities/<?P<course_key_string>[^/]+)$', 'utility_handler'),
url(r'^utility/captions/<?P<course_key_string>[^/]+$', 'utility_captions_handler'),
url(r'^utilities/(?P<course_key_string>[^/]+)$', 'utility_handler'),
url(r'^utility/captions/(?P<course_key_string>[^/]+)$', 'utility_captions_handler'),
)
js_info_dict = {
......
......@@ -708,13 +708,15 @@ def _check_can_enroll_in_course(user, course_id, access_type="enroll"):
Returns (bool, error_message), where error message is only applicable if bool == False
"""
try:
course = course_from_id(course_id)
course = modulestore().get_course(course_id)
except ItemNotFoundError:
log.warning("User {0} tried to enroll in non-existent course {1}"
.format(user.username, course_id))
log.warning(
"User {0} tried to enroll in non-existent course {1}"
.format(user.username, course_id),
)
return False, _("Course id is invalid")
if not has_access(user, course, access_type):
if not has_access(user, access_type, course):
return False, _("Enrollment is closed")
return True, ""
......
......@@ -12,7 +12,6 @@ from django.conf import settings
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore import Location
from courseware.tests import BaseTestXmodule
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
......@@ -148,29 +147,18 @@ class TestLTIModuleListing(ModuleStoreTestCase):
display_name="section2",
category='sequential')
self.published_location_dict = {'tag': 'i4x',
'org': self.course.location.org,
'category': 'lti',
'course': self.course.location.course,
'name': 'lti_published'}
self.draft_location_dict = {'tag': 'i4x',
'org': self.course.location.org,
'category': 'lti',
'course': self.course.location.course,
'name': 'lti_draft',
'revision': 'draft'}
# creates one draft and one published lti module, in different sections
self.lti_published = ItemFactory.create(
parent_location=self.section1.location,
display_name="lti published",
category="lti",
location=Location(self.published_location_dict),
location=self.course.id.make_usage_key('lti', 'lti_published'),
)
self.lti_draft = ItemFactory.create(
parent_location=self.section2.location,
display_name="lti draft",
category="lti",
location=Location(self.draft_location_dict),
location=self.course.id.make_usage_key('lti', 'lti_published').replace(revision='draft'),
)
def expected_handler_url(self, handler):
......
......@@ -111,17 +111,17 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
mock_request = MagicMock()
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(
self.course_id, self.mock_user, course, depth=2)
self.toy_course.id, self.mock_user, course, depth=2)
module = render.get_module(
self.mock_user,
mock_request,
['i4x', 'edX', 'toy', 'html', 'toyjumpto'],
self.location,
field_data_cache,
self.course_id
self.toy_course.id,
)
self.assertTrue(module.xmodule_runtime.send_users_emailaddr_with_coderesponse)
self.assertEqual(module.xmodule_runtime.deanonymized_user_email, self.mock_user.email)
......@@ -133,17 +133,17 @@ class ModuleRenderTestCase(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""
mock_request = MagicMock()
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(
self.course_id, self.mock_user, course, depth=2)
self.toy_course.id, self.mock_user, course, depth=2)
module = render.get_module(
self.mock_user,
mock_request,
['i4x', 'edX', 'toy', 'html', 'toyjumpto'],
self.location,
field_data_cache,
self.course_id
self.toy_course.id,
)
self.assertFalse(hasattr(module.xmodule_runtime, 'send_users_emailaddr_with_coderesponse'))
self.assertFalse(hasattr(module.xmodule_runtime, 'deanonymized_user_email'))
......
......@@ -596,13 +596,14 @@ def course_about(request, course_id):
# 1) within enrollment period
# 2) course specifies it's okay
# 3) request.user is not a registered user.
sneakpeek_allowed = (has_access(request.user, course, 'within_enrollment_period') and
CoursePreference.course_allows_nonregistered_access(course_id) and
sneakpeek_allowed = (has_access(request.user, 'within_enrollment_period', course) and
CoursePreference.course_allows_nonregistered_access(course.id.to_deprecated_string()) and
not UserProfile.has_registered(request.user))
# see if we have already filled up all allowed enrollments
is_course_full = CourseEnrollment.is_course_full(course)
# python -m coverage run --rcfile=lms/.coveragerc hich ./manage.pyu
return render_to_response('courseware/course_about.html', {
'course': course,
'regularly_registered': regularly_registered,
......
......@@ -59,7 +59,7 @@
$(".course_sneakpeek").click(function(event) {
$.ajax({
url: "${reverse('course_sneakpeek', args=[course.id])}",
url: "${reverse('course_sneakpeek', args=[course.id.to_deprecated_string()])}",
type: "POST",
complete: sneakpeek_handler
});
......
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