Commit 993c24b7 by Calen Pennington

WIP: Model data caching work

parent 507d2021
......@@ -358,7 +358,7 @@ class CourseDescriptor(SequenceDescriptor):
all_descriptors - This contains a list of all xmodules that can
effect grading a student. This is used to efficiently fetch
all the xmodule state for a StudentModuleCache without walking
all the xmodule state for a ModelDataCache without walking
the descriptor tree again.
......
......@@ -19,10 +19,10 @@ from xmodule.contentstore.content import StaticContent
from xmodule.modulestore.xml import XMLModuleStore
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.x_module import XModule
from courseware.model_data import ModelDataCache
from static_replace import replace_urls, try_staticfiles_lookup
from courseware.access import has_access
import branding
from courseware.models import StudentModuleCache
from xmodule.modulestore.exceptions import ItemNotFoundError
log = logging.getLogger(__name__)
......@@ -148,12 +148,23 @@ def get_course_about_section(course, section_key):
request = get_request_for_thread()
loc = course.location._replace(category='about', name=section_key)
course_module = get_module(request.user, request, loc, None, course.id, not_found_ok = True, wrap_xmodule_display = True)
# Use an empty cache
model_data_cache = ModelDataCache([], course.id, request.user)
about_module = get_module(
request.user,
request,
loc,
model_data_cache,
course.id,
not_found_ok=True,
wrap_xmodule_display=True
)
html = ''
if course_module is not None:
html = course_module.get_html()
if about_module is not None:
html = about_module.get_html()
return html
......@@ -174,7 +185,7 @@ def get_course_about_section(course, section_key):
def get_course_info_section(request, cache, course, section_key):
def get_course_info_section(request, course, section_key):
"""
This returns the snippet of html to be rendered on the course info page,
given the key for the section.
......@@ -188,11 +199,22 @@ def get_course_info_section(request, cache, course, section_key):
loc = Location(course.location.tag, course.location.org, course.location.course, 'course_info', section_key)
course_module = get_module(request.user, request, loc, cache, course.id, wrap_xmodule_display = True)
# Use an empty cache
model_data_cache = ModelDataCache([], course.id, request.user)
info_module = get_module(
request.user,
request,
loc,
model_data_cache,
course.id,
wrap_xmodule_display=True
)
html = ''
if course_module is not None:
html = course_module.get_html()
if info_module is not None:
html = info_module.get_html()
return html
......
......@@ -13,7 +13,7 @@ import xmodule
import mitxmako.middleware as middleware
middleware.MakoMiddleware()
from xmodule.modulestore.django import modulestore
from courseware.models import StudentModuleCache
from courseware.model_data import ModelDataCache
from courseware.module_render import get_module
......@@ -83,7 +83,7 @@ class Command(BaseCommand):
# TODO (cpennington): Get coursename in a legitimate way
course_location = 'i4x://edx/6002xs12/course/6.002_Spring_2012'
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
student_module_cache = ModelDataCache.cache_for_descriptor_descendents(
course_id,
sample_user, modulestore().get_item(course_location))
course = get_module(sample_user, None, course_location, student_module_cache)
......
......@@ -198,106 +198,3 @@ class XModuleStudentInfoField(models.Model):
def __unicode__(self):
return unicode(repr(self))
# TODO (cpennington): Remove these once the LMS switches to using XModuleDescriptors
class StudentModuleCache(object):
"""
A cache of StudentModules for a specific student
"""
def __init__(self, course_id, user, descriptors, select_for_update=False):
'''
Find any StudentModule objects that are needed by any descriptor
in descriptors. Avoids making multiple queries to the database.
Note: Only modules that have store_state = True or have shared
state will have a StudentModule.
Arguments
user: The user for which to fetch maching StudentModules
descriptors: An array of XModuleDescriptors.
select_for_update: Flag indicating whether the rows should be locked until end of transaction
'''
if user.is_authenticated():
module_ids = self._get_module_state_keys(descriptors)
# This works around a limitation in sqlite3 on the number of parameters
# that can be put into a single query
self.cache = []
chunk_size = 500
for id_chunk in [module_ids[i:i + chunk_size] for i in xrange(0, len(module_ids), chunk_size)]:
if select_for_update:
self.cache.extend(StudentModule.objects.select_for_update().filter(
course_id=course_id,
student=user,
module_state_key__in=id_chunk)
)
else:
self.cache.extend(StudentModule.objects.filter(
course_id=course_id,
student=user,
module_state_key__in=id_chunk)
)
else:
self.cache = []
@classmethod
def cache_for_descriptor_descendents(cls, course_id, user, descriptor, depth=None,
descriptor_filter=lambda descriptor: True,
select_for_update=False):
"""
course_id: the course in the context of which we want StudentModules.
user: the django user for whom to load modules.
descriptor: An XModuleDescriptor
depth is the number of levels of descendent modules to load StudentModules for, in addition to
the supplied descriptor. If depth is None, load all descendent StudentModules
descriptor_filter is a function that accepts a descriptor and return wether the StudentModule
should be cached
select_for_update: Flag indicating whether the rows should be locked until end of transaction
"""
def get_child_descriptors(descriptor, depth, descriptor_filter):
if descriptor_filter(descriptor):
descriptors = [descriptor]
else:
descriptors = []
if depth is None or depth > 0:
new_depth = depth - 1 if depth is not None else depth
for child in descriptor.get_children():
descriptors.extend(get_child_descriptors(child, new_depth, descriptor_filter))
return descriptors
descriptors = get_child_descriptors(descriptor, depth, descriptor_filter)
return StudentModuleCache(course_id, user, descriptors, select_for_update)
def _get_module_state_keys(self, descriptors):
'''
Get a list of the state_keys needed for StudentModules
required for this module descriptor
descriptor_filter is a function that accepts a descriptor and return wether the StudentModule
should be cached
'''
return [descriptor.location.url() for descriptor in descriptors if descriptor.stores_state]
def lookup(self, course_id, module_type, module_state_key):
'''
Look for a student module with the given course_id, type, and id in the cache.
cache -- list of student modules
returns first found object, or None
'''
for o in self.cache:
if (o.course_id == course_id and
o.module_type == module_type and
o.module_state_key == module_state_key):
return o
return None
def append(self, student_module):
self.cache.append(student_module)
......@@ -17,7 +17,7 @@ from capa.xqueue_interface import XQueueInterface
from capa.chem import chemcalc
from courseware.access import has_access
from mitxmako.shortcuts import render_to_string
from models import StudentModule, StudentModuleCache
from models import StudentModule
from psychometrics.psychoanalyze import make_psychometrics_data_update_handler
from static_replace import replace_urls
from xmodule.errortracker import exc_info_to_str
......@@ -28,7 +28,7 @@ from xmodule.x_module import ModuleSystem
from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor
from xblock.runtime import DbModel
from xmodule_modifiers import replace_course_urls, replace_static_urls, add_histogram, wrap_xmodule
from .model_data import LmsKeyValueStore, LmsUsage
from .model_data import LmsKeyValueStore, LmsUsage, ModelDataCache
from xmodule.modulestore.exceptions import ItemNotFoundError
from statsd import statsd
......@@ -60,7 +60,7 @@ def make_track_function(request):
return f
def toc_for_course(user, request, course, active_chapter, active_section):
def toc_for_course(user, request, course, active_chapter, active_section, model_data_cache):
'''
Create a table of contents from the module store
......@@ -80,11 +80,11 @@ def toc_for_course(user, request, course, active_chapter, active_section):
NOTE: assumes that if we got this far, user has access to course. Returns
None if this is not the case.
model_data_cache must include data from the course module and 2 levels of its descendents
'''
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course.id, user, course, depth=2)
course_module = get_module(user, request, course.location, student_module_cache, course.id)
course_module = get_module(user, request, course.location, model_data_cache, course.id)
if course_module is None:
return None
......@@ -115,7 +115,7 @@ def toc_for_course(user, request, course, active_chapter, active_section):
return chapters
def get_module(user, request, location, student_module_cache, course_id,
def get_module(user, request, location, model_data_cache, course_id,
position=None, not_found_ok = False, wrap_xmodule_display=True,
grade_bucket_type=None):
"""
......@@ -128,7 +128,7 @@ def get_module(user, request, location, student_module_cache, course_id,
- request : current django HTTPrequest. Note: request.user isn't used for anything--all auth
and such works based on user.
- location : A Location-like object identifying the module to load
- student_module_cache : a StudentModuleCache
- model_data_cache : a ModelDataCache
- course_id : the course_id in the context of which to load module
- position : extra information from URL for user-specified
position within module
......@@ -138,7 +138,7 @@ def get_module(user, request, location, student_module_cache, course_id,
if possible. If not possible, return None.
"""
try:
return _get_module(user, request, location, student_module_cache, course_id, position, wrap_xmodule_display)
return _get_module(user, request, location, model_data_cache, course_id, position, wrap_xmodule_display)
except ItemNotFoundError:
if not not_found_ok:
log.exception("Error in get_module")
......@@ -149,7 +149,7 @@ def get_module(user, request, location, student_module_cache, course_id,
return None
def _get_module(user, request, location, student_module_cache, course_id,
def _get_module(user, request, location, model_data_cache, course_id,
position=None, wrap_xmodule_display=True, grade_bucket_type=None):
"""
Actually implement get_module. See docstring there for details.
......@@ -204,11 +204,11 @@ def _get_module(user, request, location, student_module_cache, course_id,
Delegate to get_module. It does an access check, so may return None
"""
return get_module(user, request, location,
student_module_cache, course_id, position)
model_data_cache, course_id, position)
def xblock_model_data(descriptor):
return DbModel(
LmsKeyValueStore(course_id, user, descriptor._model_data, student_module_cache),
LmsKeyValueStore(descriptor._model_data, model_data_cache),
descriptor.module_class,
user.id,
LmsUsage(location, location)
......@@ -218,18 +218,13 @@ def _get_module(user, request, location, student_module_cache, course_id,
if event.get('event_name') != 'grade':
return
student_module = student_module_cache.lookup(
course_id, descriptor.location.category, descriptor.location.url()
student_module, created = StudentModule.objects.get_or_create(
course_id=course_id,
student=user,
module_type=descriptor.location.category,
module_state_key=descriptor.location.url(),
defaults={'state': '{}'},
)
if student_module is None:
student_module = StudentModule(
course_id=course_id,
student=user,
module_type=descriptor.location.category,
module_state_key=descriptor.location.url(),
state=json.dumps({})
)
student_module_cache.append(student_module)
student_module.grade = event.get('value')
student_module.max_grade = event.get('max_value')
student_module.save()
......@@ -335,9 +330,9 @@ def xqueue_callback(request, course_id, userid, id, dispatch):
# Retrieve target StudentModule
user = User.objects.get(id=userid)
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(course_id,
model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course_id,
user, modulestore().get_instance(course_id, id), depth=0, select_for_update=True)
instance = get_module(user, request, id, student_module_cache, course_id, grade_bucket_type='xqueue')
instance = get_module(user, request, id, model_data_cache, course_id, grade_bucket_type='xqueue')
if instance is None:
log.debug("No module {0} for user {1}--access denied?".format(id, user))
raise Http404
......@@ -394,10 +389,10 @@ def modx_dispatch(request, dispatch, location, course_id):
return HttpResponse(json.dumps({'success': file_too_big_msg}))
p[fileinput_id] = inputfiles
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(course_id,
model_data_cache = ModelDataCache.cache_for_descriptor_descendents(course_id,
request.user, modulestore().get_instance(course_id, location))
instance = get_module(request.user, request, location, student_module_cache, course_id, grade_bucket_type='ajax')
instance = get_module(request.user, request, location, model_data_cache, course_id, grade_bucket_type='ajax')
if instance is None:
# Either permissions just changed, or someone is trying to be clever
# and load something they shouldn't have access to.
......
......@@ -5,18 +5,26 @@ from django.contrib.auth.models import User
from functools import partial
from courseware.model_data import LmsKeyValueStore, InvalidWriteError, InvalidScopeError
from courseware.models import StudentModule, XModuleContentField, XModuleSettingsField, XModuleStudentInfoField, XModuleStudentPrefsField, StudentModuleCache
from courseware.model_data import LmsKeyValueStore, InvalidWriteError, InvalidScopeError, ModelDataCache
from courseware.models import StudentModule, XModuleContentField, XModuleSettingsField, XModuleStudentInfoField, XModuleStudentPrefsField
from xblock.core import Scope, BlockScope
from xmodule.modulestore import Location
from django.test import TestCase
def mock_descriptor():
def mock_field(scope, name):
field = Mock()
field.scope = scope
field.name = name
return field
def mock_descriptor(fields=[], lms_fields=[]):
descriptor = Mock()
descriptor.stores_state = True
descriptor.location = location('def_id')
descriptor.module_class.fields = fields
descriptor.module_class.lms.fields = lms_fields
return descriptor
location = partial(Location, 'i4x', 'edX', 'test_course', 'problem')
......@@ -85,7 +93,7 @@ class TestDescriptorFallback(TestCase):
'field_a': 'content',
'field_b': 'settings',
}
self.kvs = LmsKeyValueStore(course_id, UserFactory.build(), self.desc_md, None)
self.kvs = LmsKeyValueStore(self.desc_md, None)
def test_get_from_descriptor(self):
self.assertEquals('content', self.kvs.get(content_key('field_a')))
......@@ -103,13 +111,11 @@ class TestDescriptorFallback(TestCase):
self.assertEquals('settings', self.desc_md['field_b'])
class TestStudentStateFields(TestCase):
pass
class TestInvalidScopes(TestCase):
def setUp(self):
self.desc_md = {}
self.kvs = LmsKeyValueStore(course_id, UserFactory.build(), self.desc_md, None)
self.mdc = ModelDataCache([mock_descriptor([mock_field(Scope.student_state, 'a_field')])], course_id, self.user)
self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_invalid_scopes(self):
for scope in (Scope(student=True, block=BlockScope.DEFINITION),
......@@ -123,12 +129,10 @@ class TestInvalidScopes(TestCase):
class TestStudentModuleStorage(TestCase):
def setUp(self):
student_module = StudentModuleFactory.create(state=json.dumps({'a_field': 'a_value'}))
self.user = student_module.student
self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, [mock_descriptor()])
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc)
self.mdc = Mock()
self.mdc.find.return_value.state = json.dumps({'a_field': 'a_value'})
self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_existing_field(self):
"Test that getting an existing field in an existing StudentModule works"
......@@ -154,7 +158,7 @@ class TestStudentModuleStorage(TestCase):
"Test that deleting an existing field removes it from the StudentModule"
self.kvs.delete(student_state_key('a_field'))
self.assertEquals(1, StudentModule.objects.all().count())
self.assertEquals({}, json.loads(StudentModule.objects.all()[0].state))
self.assertEquals({}, self.mdc.find.return_value.state)
def test_delete_missing_field(self):
"Test that deleting a missing field from an existing StudentModule raises a KeyError"
......@@ -167,8 +171,8 @@ class TestMissingStudentModule(TestCase):
def setUp(self):
self.user = UserFactory.create()
self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, [mock_descriptor()])
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc)
self.mdc = ModelDataCache([mock_descriptor()], course_id, self.user)
self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_field_from_missing_student_module(self):
"Test that getting a field from a missing StudentModule raises a KeyError"
......@@ -176,12 +180,12 @@ class TestMissingStudentModule(TestCase):
def test_set_field_in_missing_student_module(self):
"Test that setting a field in a missing StudentModule creates the student module"
self.assertEquals(0, len(self.smc.cache))
self.assertEquals(0, len(self.mdc.cache))
self.assertEquals(0, StudentModule.objects.all().count())
self.kvs.set(student_state_key('a_field'), 'a_value')
self.assertEquals(1, len(self.smc.cache))
self.assertEquals(1, len(self.mdc.cache))
self.assertEquals(1, StudentModule.objects.all().count())
student_module = StudentModule.objects.all()[0]
......@@ -201,8 +205,8 @@ class TestSettingsStorage(TestCase):
settings = SettingsFactory.create()
self.user = UserFactory.create()
self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, [])
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc)
self.mdc = ModelDataCache([mock_descriptor()], course_id, self.user)
self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_existing_field(self):
"Test that getting an existing field in an existing SettingsField works"
......@@ -242,8 +246,8 @@ class TestContentStorage(TestCase):
content = ContentFactory.create()
self.user = UserFactory.create()
self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, [])
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc)
self.mdc = ModelDataCache([mock_descriptor()], course_id, self.user)
self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_existing_field(self):
"Test that getting an existing field in an existing ContentField works"
......@@ -283,8 +287,11 @@ class TestStudentPrefsStorage(TestCase):
student_pref = StudentPrefsFactory.create()
self.user = student_pref.student
self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, [])
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc)
self.mdc = ModelDataCache([mock_descriptor([
mock_field(Scope.student_preferences, 'student_pref_field'),
mock_field(Scope.student_preferences, 'not_student_pref_field'),
])], course_id, self.user)
self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_existing_field(self):
"Test that getting an existing field in an existing StudentPrefsField works"
......@@ -309,7 +316,6 @@ class TestStudentPrefsStorage(TestCase):
def test_delete_existing_field(self):
"Test that deleting an existing field removes it"
print list(XModuleStudentPrefsField.objects.all())
self.kvs.delete(student_prefs_key('student_pref_field'))
self.assertEquals(0, XModuleStudentPrefsField.objects.all().count())
......@@ -325,8 +331,11 @@ class TestStudentInfoStorage(TestCase):
student_info = StudentInfoFactory.create()
self.user = student_info.student
self.desc_md = {}
self.smc = StudentModuleCache(course_id, self.user, [])
self.kvs = LmsKeyValueStore(course_id, self.user, self.desc_md, self.smc)
self.mdc = ModelDataCache([mock_descriptor([
mock_field(Scope.student_info, 'student_info_field'),
mock_field(Scope.student_info, 'not_student_info_field'),
])], course_id, self.user)
self.kvs = LmsKeyValueStore(self.desc_md, self.mdc)
def test_get_existing_field(self):
"Test that getting an existing field in an existing StudentInfoField works"
......
......@@ -23,7 +23,7 @@ import xmodule.modulestore.django
# Need access to internal func to put users in the right group
from courseware import grades
from courseware.access import _course_staff_group_name
from courseware.models import StudentModuleCache
from courseware.model_data import ModelDataCache
from student.models import Registration
from xmodule.modulestore.django import modulestore
......@@ -705,27 +705,27 @@ class TestCourseGrader(PageLoader):
self.factory = RequestFactory()
def get_grade_summary(self):
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
self.graded_course.id, self.student_user, self.graded_course)
fake_request = self.factory.get(reverse('progress',
kwargs={'course_id': self.graded_course.id}))
return grades.grade(self.student_user, fake_request,
self.graded_course, student_module_cache)
self.graded_course, model_data_cache)
def get_homework_scores(self):
return self.get_grade_summary()['totaled_scores']['Homework']
def get_progress_summary(self):
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
self.graded_course.id, self.student_user, self.graded_course)
fake_request = self.factory.get(reverse('progress',
kwargs={'course_id': self.graded_course.id}))
progress_summary = grades.progress_summary(self.student_user, fake_request,
self.graded_course, student_module_cache)
self.graded_course, model_data_cache)
return progress_summary
def check_grade_percent(self, percent):
......
......@@ -23,7 +23,7 @@ from courseware import grades
from courseware.access import has_access
from courseware.courses import (get_course_with_access, get_courses_by_university)
import courseware.tabs as tabs
from courseware.models import StudentModuleCache
from courseware.model_data import ModelDataCache
from module_render import toc_for_course, get_module
from student.models import UserProfile
......@@ -81,7 +81,7 @@ def courses(request):
return render_to_response("courses.html", {'universities': universities})
def render_accordion(request, course, chapter, section):
def render_accordion(request, course, chapter, section, model_data_cache):
''' Draws navigation bar. Takes current position in accordion as
parameter.
......@@ -92,7 +92,7 @@ def render_accordion(request, course, chapter, section):
Returns the html string'''
# grab the table of contents
toc = toc_for_course(request.user, request, course, chapter, section)
toc = toc_for_course(request.user, request, course, chapter, section, model_data_cache)
context = dict([('toc', toc),
('course_id', course.id),
......@@ -191,24 +191,21 @@ def index(request, course_id, chapter=None, section=None,
return redirect(reverse('about_course', args=[course.id]))
try:
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
course.id, request.user, course, depth=2)
# Has this student been in this course before?
first_time = student_module_cache.lookup(course_id, 'course', course.location.url()) is None
course_module = get_module(request.user, request, course.location, student_module_cache, course.id)
course_module = get_module(request.user, request, course.location, model_data_cache, course.id)
if course_module is None:
log.warning('If you see this, something went wrong: if we got this'
' far, should have gotten a course module for this user')
return redirect(reverse('about_course', args=[course.id]))
if chapter is None:
return redirect_to_course_position(course_module, first_time)
return redirect_to_course_position(course_module, course_module.first_time_user)
context = {
'csrf': csrf(request)['csrf_token'],
'accordion': render_accordion(request, course, chapter, section),
'accordion': render_accordion(request, course, chapter, section, model_data_cache),
'COURSE_TITLE': course.title,
'course': course,
'init': '',
......@@ -224,7 +221,7 @@ def index(request, course_id, chapter=None, section=None,
raise Http404
chapter_module = get_module(request.user, request, chapter_descriptor.location,
student_module_cache, course_id)
model_data_cache, course_id)
if chapter_module is None:
# User may be trying to access a chapter that isn't live yet
raise Http404
......@@ -235,11 +232,11 @@ def index(request, course_id, chapter=None, section=None,
# Specifically asked-for section doesn't exist
raise Http404
section_student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
section_model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
course_id, request.user, section_descriptor)
section_module = get_module(request.user, request,
section_descriptor.location,
section_student_module_cache, course_id, position)
section_model_data_cache, course_id, position)
if section_module is None:
# User may be trying to be clever and access something
# they don't have access to.
......@@ -491,12 +488,12 @@ def progress(request, course_id, student_id=None):
# additional DB lookup (this kills the Progress page in particular).
student = User.objects.prefetch_related("groups").get(id=student.id)
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
model_data_cache = ModelDataCache.cache_for_descriptor_descendents(
course_id, student, course)
courseware_summary = grades.progress_summary(student, request, course,
student_module_cache)
grade_summary = grades.grade(student, request, course, student_module_cache)
model_data_cache)
grade_summary = grades.grade(student, request, course, model_data_cache)
if courseware_summary is None:
#This means the student didn't have access to the course (which the instructor requested)
......
......@@ -27,20 +27,20 @@ $(document).ready(function(){
% if user.is_authenticated():
<section class="updates">
<h1>Course Updates &amp; News</h1>
${get_course_info_section(request, cache, course, 'updates')}
${get_course_info_section(request, course, 'updates')}
</section>
<section aria-label="Handout Navigation" class="handouts">
<h1>${course.info_sidebar_name}</h1>
${get_course_info_section(request, cache, course, 'handouts')}
${get_course_info_section(request, course, 'handouts')}
</section>
% else:
<section class="updates">
<h1>Course Updates &amp; News</h1>
${get_course_info_section(request, cache, course, 'guest_updates')}
${get_course_info_section(request, course, 'guest_updates')}
</section>
<section aria-label="Handout Navigation" class="handouts">
<h1>Course Handouts</h1>
${get_course_info_section(request, cache, course, 'guest_handouts')}
${get_course_info_section(request, course, 'guest_handouts')}
</section>
% endif
</div>
......
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