Commit 88839c8b by sjang92

Merge pull request #72 from Stanford-Online/sjang92/bulk_settings_rebase

Implemented Bulksettings page & Tests
parents aac28d08 85340312
......@@ -215,3 +215,114 @@ def reverse_usage_url(handler_name, usage_key, kwargs=None):
Creates the URL for handlers that use usage_keys as URL parameters.
"""
return reverse_url(handler_name, 'usage_key_string', usage_key, kwargs)
class BulkSettingsUtil():
"""
Utility class that hold functions for bulksettings operations
"""
COMPONENT_TYPES = ['discussion', 'html', 'problem', 'video']
SECTION_SETTING_TYPES = ['start']
SUBSECTION_SETTING_TYPES = ['start', 'due', 'format']
UNIT_SETTING_TYPES = []
PROBLEM_SETTING_TYPES = ['max_attempts', 'weight', 'rerandomize', 'showanswer', 'submission_wait_seconds']
SECTION_SETTING_MAP = {'start': 'Release Date'}
SUBSECTION_SETTING_MAP = {'start': 'Release', 'due': 'Due', 'format': 'Type'}
CATEGORY_SETTING_MAP = {
"chapter": SECTION_SETTING_TYPES,
"sequential": SUBSECTION_SETTING_TYPES,
"vertical": UNIT_SETTING_TYPES,
"problem": PROBLEM_SETTING_TYPES,
}
@classmethod
def get_settings_dict_for_category(cls, category, child, parent):
"""
Returns the settings dictionary for the given child of given category.
Parent is required since .parent nor .get_parent() work.
"""
settings_dict = {}
settings_dict['name'] = child.display_name
settings_dict['children'] = []
settings_dict['url'] = cls.get_settings_url_for_category(category, child, parent)
for setting_type in cls.CATEGORY_SETTING_MAP[category]:
value = getattr(child, setting_type)
if isinstance(value, datetime):
value = value.strftime('%m/%d/%Y')
settings_dict[setting_type] = value
if category == 'vertical':
settings_dict['ispublic'] = compute_publish_state(child)
return settings_dict
@classmethod
def get_settings_url_for_category(cls, category, child, parent):
"""
Returns the URLs that the user needs to go to in order to change settings.
Chapters and Problems need urls that match for their parents:
- Chapters: Course url
- Problems: Unit url
"""
if category == "chapter":
return reverse('contentstore.views.course_handler',
kwargs={'course_key_string': unicode(parent.id)})
elif category == "sequential":
return reverse('contentstore.views.subsection_handler',
kwargs={'usage_key_string': unicode(child.location)})
elif category == "unit":
return reverse('contentstore.views.unit_handler',
kwargs={'usage_key_string': unicode(child.location)})
else:
return reverse('contentstore.views.unit_handler',
kwargs={'usage_key_string': unicode(parent.location)})
@classmethod
def get_bulksettings_metadata(cls, course):
"""
Returns a list of settings metadata for all sections, subsections, units, and problems.
Each block (section, subsection, unit or problem) settings metadata is saved as a dictionary:
settings_data = {
'name' = name of the block
'key' = opaquekey. Used for link generation
'children' = List of children_settings_data
}
"""
settings_data = []
for section in course.get_children():
section_setting = cls.get_settings_dict_for_category('chapter', section, course)
for subsection in section.get_children():
subsection_setting = cls.get_settings_dict_for_category('sequential', subsection, section)
for unit in subsection.get_children():
unit_setting = cls.get_settings_dict_for_category('vertical', unit, subsection)
for component in unit.get_children():
if component.location.category == 'problem':
curr_problem_settings = cls.get_settings_dict_for_category('problem', component, unit)
unit_setting['children'].append(curr_problem_settings)
if unit_setting['children']:
subsection_setting['children'].append(unit_setting)
if subsection_setting['children']:
section_setting['children'].append(subsection_setting)
if section_setting['children']:
settings_data.append(section_setting)
return settings_data
......@@ -8,6 +8,7 @@ from .assets import *
from .checklist import *
from .utility import *
from .utilities.captions import *
from .utilities.bulksettings import *
from .component import *
from .course import *
from .error import *
......
"""
Tests for the bulk settings page
"""
from contentstore.tests.utils import CourseTestCase
from contentstore.utils import BulkSettingsUtil
from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory
from datetime import datetime
import random
class BulkSettingsTests(CourseTestCase):
"""
Redefine setting type constants for easier testing
Also defined in contentstore.views.utilities.bulksettings,
and these contstants must match those in contentestore.views.utilities.bulksettings.
"""
UNIT_SETTING_TYPES = []
PROBLEM_SETTING_TYPES = BulkSettingsUtil.PROBLEM_SETTING_TYPES
COMPONENT_TYPES = BulkSettingsUtil.COMPONENT_TYPES
SECTION_SETTING_TYPES = BulkSettingsUtil.SECTION_SETTING_TYPES
SUBSECTION_SETTING_TYPES = BulkSettingsUtil.SUBSECTION_SETTING_TYPES
CATEGORY_SETTING_MAP = BulkSettingsUtil.CATEGORY_SETTING_MAP
NUM_RANDOM_TESTS = 10
def setUp(self):
"""
Set up initial data for testing.
CourseTestCase's setUp initializes:
- self.user: staff user
- self.course
- self.client
- self.store
"""
super(BulkSettingsTests, self).setUp()
def populate_course_with_seed_data(self):
"""
Populates the given course hierarchy with initial settings data
Does tree expansion by x2 each level.
"""
self.do_recursive_population(self.course, ['chapter', 'sequential', 'vertical', 'problem'])
def do_recursive_population(self,parent, stack):
block_category = stack.pop(0)
for _ in range(2):
child = self.create_item_of_category(block_category, parent)
stack_copy = list(stack)
if stack_copy:
self.do_recursive_population(child, stack_copy)
def create_item_of_category(self, category, parent):
"""
Given a category and the parent, creates a block (item)
and attaches it to the parent
"""
metadata = {}
setting_types = self.CATEGORY_SETTING_MAP[category]
for setting_type in setting_types:
metadata[setting_type] = self.generate_random_value_for(setting_type)
item = ItemFactory.create(
parent_location = parent.location,
category = category,
metadata = metadata
)
return item
def generate_random_value_for(self, setting_type):
"""
Given a setting type, (metadata attribute of a mixin class)
generate a random value and return it.
"""
randomization_values = ["Always", "On Reset", "Never", "Per Student"]
show_answer_values = ["Always", "Answered", "Attempted", "Closed",
"Finished", "Past Due", "Never"]
format_values = ["Quiz", "Homework"]
if setting_type == "start" or setting_type == "due":
return datetime.today()
elif setting_type == "max_attempts":
return random.randint(1, 10)
elif setting_type == "weight" or setting_type == "submission_wait_seconds":
return random.random() * 10
elif setting_type == "rerandomize":
return randomization_values[random.randint(0, 3)]
elif setting_type == "showanswer":
return show_answer_values[random.randint(0, 6)]
else:
return format_values[random.randint(0,1)]
def test_empty_course(self):
"""
Test that the computed settings data is empty when the course is empty.
"""
empty_course = CourseFactory.create(
org = 'edx',
number = '999',
display_name = 'test_course'
)
computed_settings_data = BulkSettingsUtil.get_bulksettings_metadata(empty_course)
self.assertEqual(len(computed_settings_data), 0, msg="empty course should contain no settings")
def test_empty_problem(self):
"""
Test with no problems. If no unit holds any problems, the list should be empty.
"""
empty_problem_course = CourseFactory.create(
org = 'edx',
number = '999',
display_name = 'no_problem_course'
)
self.do_recursive_population(empty_problem_course, ['chapter', 'sequential', 'vertical'])
computed_settings_data = BulkSettingsUtil.get_bulksettings_metadata(empty_problem_course)
self.assertEqual(len(computed_settings_data), 0,
msg="course with no problem should yield no bulksettings")
def test_multiple_bulksettings(self):
"""
Do multiple tests with randomly generated settings data.
Since this uses self.course, it attaches new xblock components to the same
course every iteration, stress-testing with new random values.
The number of xblocks that are added with each iteration is:
2 + 4 + 8 + 16
This is inteded to test both: 1) Large Courses + 2) Different values
"""
for i in range(self.NUM_RANDOM_TESTS):
self.test_bulksettings_data_correctness()
def test_bulksettings_data_correctness(self):
"""
Test that the populated course settings correctly match the result
from _get_bulksettings_metadata.
_get_bulksettings_metatdata is from contentstore.views.utilities.bulksettings
Since the test populated course only contains problems, assume that indices
match between computed settings & block.children
Populate self.course with 2 chapters, 4 sections, 8 verticals, 16 problems
and for each node, put in random settings
"""
self.populate_course_with_seed_data()
course_module = self.store.get_items(self.course.id, category='course')[0]
computed_settings_data = BulkSettingsUtil.get_bulksettings_metadata(course_module)
num_sections = len(computed_settings_data)
sections = course_module.get_children()
''' Dive into the course hierarchy and check correct values.'''
''' Section Layer'''
for index in range(num_sections):
section = sections[index]
computed_section_settings = computed_settings_data[index]
self.assertTrue(self.do_values_match(self.SECTION_SETTING_TYPES,
section, computed_section_settings))
num_subsections = len(section.get_children())
''' Subsection Layer '''
for index in range(num_subsections):
subsection = section.get_children()[index]
computed_subsection_settings = computed_section_settings['children'][index]
self.assertTrue(self.do_values_match(self.SUBSECTION_SETTING_TYPES,
subsection, computed_subsection_settings))
num_units = len(subsection.get_children())
''' Unit Layer '''
for index in range(num_units):
unit = subsection.get_children()[index]
num_components = len(unit.get_children())
computed_unit_settings = computed_subsection_settings['children'][index]
''' Component Layer (Problems)'''
for index in range(num_components):
component = unit.get_children()[index]
computed_component_settings = computed_unit_settings['children'][index]
self.assertTrue(self.do_values_match(self.PROBLEM_SETTING_TYPES,
component, computed_component_settings))
def do_values_match(self, setting_types, child, computed_settings):
"""
Checks if the actual settings of the given child matches
the computed settings
"""
for setting_type in setting_types:
setting= getattr(child, setting_type)
if setting_type == 'start' or setting_type == 'due':
setting = setting.strftime('%m/%d/%Y')
self.assertEqual(setting, computed_settings[setting_type],
msg="setting_type" + str(setting_type) + "does not match")
if setting != computed_settings[setting_type]:
return False
return True
def populate_settings(self):
"""
For the chapters, sections, verticals, and problems in this course,
put in random settings.
"""
course_module = self.store.get_items(self.course.id, category='course')[0]
for section in course_module.get_children():
for setting_type in self.SECTION_SETTING_TYPES:
setattr(section, setting_type, self.generate_random_value_for(setting_type))
section.save()
for subsection in section.get_children():
for setting_type in self.SUBSECTION_SETTING_TYPES:
setattr(subsection, setting_type, self.generate_random_value_for(setting_type))
subsection.save()
for unit in subsection.get_children():
# All components are initialized as problems
for component in unit.get_children():
for setting_type in self.PROBLEM_SETTING_TYPES:
setattr(component, setting_type, self.generate_random_value_for(setting_type))
component.save()
self.save_course()
"""
Views related to bulk settings change operations on course objects.
"""
import logging
from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import login_required
from edxmako.shortcuts import render_to_response
from contentstore.utils import BulkSettingsUtil
from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.django import modulestore
from ..access import has_course_access
log = logging.getLogger(__name__)
__all__ = ['utility_bulksettings_handler']
COMPONENT_TYPES = ['discussion', 'html', 'problem', 'video']
SECTION_SETTING_MAP = {'start': 'Release Date'}
SUBSECTION_SETTING_MAP = {'start': 'Release', 'due': 'Due', 'format': 'Type'}
CATEGORY_SETTING_MAP = BulkSettingsUtil.CATEGORY_SETTING_MAP
SETTING_TYPE_LIST_MAP = {
"section_setting_types": BulkSettingsUtil.SECTION_SETTING_TYPES,
"subsection_setting_types": BulkSettingsUtil.SUBSECTION_SETTING_TYPES,
"unit_setting_types": BulkSettingsUtil.UNIT_SETTING_TYPES,
"problem_setting_types": BulkSettingsUtil.PROBLEM_SETTING_TYPES
}
@login_required
def utility_bulksettings_handler(request, course_key_string):
"""
Handler for bulk settings view requests in the utilities tool.
Queries for all settings for a given section, subsection & xblocks.
In order to reduce the amount of embedding of functions in the template,
store section - subsection - units - problems as list of hashes
"""
course_key = CourseKey.from_string(course_key_string)
response_format = request.REQUEST.get('format', 'html')
if response_format == 'html':
if request.method == 'GET':
# load data
course = _get_course_module(course_key, request.user, depth=3)
# traverse into the course tree and extract problem settings information
settings_data = BulkSettingsUtil.get_bulksettings_metadata(course)
return render_to_response('bulksettings.html',
{
'context_course':course,
'settings_data':settings_data,
'setting_type_list_map': SETTING_TYPE_LIST_MAP,
'section_setting_map': SECTION_SETTING_MAP,
'subsection_setting_map': SUBSECTION_SETTING_MAP
}
)
def _get_course_module(course_key, user, depth=0):
"""
return the course module for the view functions.
Safety-check if the given user has permissions to access the course module.
"""
if not has_course_access(user, course_key):
raise PermissionDenied()
course_module = modulestore().get_course(course_key, depth = depth)
return course_module
......@@ -480,7 +480,14 @@ COURSE_UTILITIES = [
"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_handler",
"action_text": "Check Captions",
"action_external": False}]}
"action_external": False},
{"short_description": "Bulk view problem settings",
"long_description": "This utility will allow you to view all section, subsection and problem settings in one page.",
"action_url": "utility_bulksettings_handler",
"action_text": "Check Bulk Settings",
"action_external" : False}
]
}
]
############################ APPS #####################################
......
......@@ -46,6 +46,7 @@
@import 'views/users';
@import 'views/checklists';
@import 'views/utilities';
@import 'views/bulksettings';
@import 'views/captions';
@import 'views/textbooks';
@import 'views/export-git';
......
// Studio - Course Utilities - Bulksettings
// ====================
.view-bulksettings {
.content-primary, .content-supplementary {
@include box-sizing(border-box);
float: left;
}
.content-primary {
width: flex-grid(9, 12);
margin-right: flex-gutter();
}
#settings-header {
position: relative;
display: block;
box-sizing: border-box;
z-index: 10;
background: white;
&.fixed-header {
position: fixed;
top: 0;
}
}
.section-header {
border-bottom: 1px solid #b2b2b2;
.section-header-title {
display: block;
float: left;
}
.section-header-date {
font-size: .65em;
padding: 1px 25px;
color: #4c4c4c;
float: left;
}
}
.subsection-header {
background-color: #eee;
td > strong {
padding-left: 30px;
}
}
.subsection-settings {
padding-right: 10px;
}
.unit-header {
td > strong {
padding-left: 50px;
}
}
.problem-header {
td > strong {
padding-left: 70px;
}
}
.course-utility .settings-table {
width: 100%;
tr td {
padding: 5px;
}
}
.key-box {
width: 100%;
margin: 10px;
&.fixed-sidebar {
position: fixed;
top: 0;
}
.key-list {
width:100%;
margin: 20px;
}
}
// utilities - general
.course-utility {
@extend %ui-window;
margin: 0 0 ($baseline*2) 0;
&:last-child {
margin-bottom: 0;
}
// visual status
.viz-utility-status {
@extend %cont-text-hide;
@include size(100%,($baseline/4));
position: relative;
display: block;
margin: 0;
background: $gray-l4;
.viz-utility-status-value {
@include transition(width $tmg-s2 ease-in-out .25s);
position: absolute;
top: 0;
left: 0;
width: 0%;
height: ($baseline/4);
background: $green;
.int {
@extend %cont-text-sr;
}
}
}
// header/title
header {
@include clearfix();
box-shadow: inset 0 -1px 1px $shadow-l1;
margin-bottom: 0;
border-bottom: 1px solid $gray-l3;
padding: $baseline ($baseline*1.5);
.utility-title {
@include transition(color $tmg-f2 ease-in-out 0s);
width: flex-grid(6, 9);
margin: 0 flex-gutter() 0 0;
float: left;
.ui-toggle-expansion {
@include transition(all $tmg-f2 ease-in-out 0s);
@include font-size(21);
display: inline-block;
vertical-align: middle;
margin-right: ($baseline/2);
color: $gray-l4;
}
&.is-selectable {
cursor: pointer;
&:hover {
color: $blue;
.ui-toggle-expansion {
color: $blue;
}
}
}
}
}
// utility actions
.course-utility-actions {
@include clearfix();
@include transition(border $tmg-f2 ease-in-out .25s);
box-shadow: inset 0 1px 1px $shadow-l1;
border-top: 1px solid $gray-l2;
padding: $baseline ($baseline*1.5);
background: $gray-l4;
.action-primary {
@include green-button();
float: left;
}
.action-secondary {
@include grey-button();
@extend %t-action3;
font-weight: 400;
float: right;
}
}
// state - collapsed
&.is-collapsed {
header {
box-shadow: none;
.utility-title {
.ui-toggle-expansion {
@include transform(rotate(-90deg));
@include transform-origin(50% 50%);
}
}
}
.list-tasks {
height: 0;
}
}
// state - completed
&.is-completed {
.viz-utility-status {
.viz-utility-status-value {
width: 100%;
}
}
header {
.utility-title, {
color: $green;
}
.utility-status {
.status-count, .status-amount, {
color: $green;
}
}
}
}
// state - not available
.is-not-available {
}
}
// list of tasks
.list-tasks {
height: auto;
overflow: hidden;
.task {
@include transition(background $tmg-f2 ease-in-out 0s, border $tmg-f3 ease-in-out 0s);
@include clearfix();
position: relative;
border-top: 1px solid $white;
border-bottom: 1px solid $gray-l5;
padding: $baseline ($baseline*1.5);
background: $white;
opacity: 1.0;
&:last-child {
margin-bottom: 0;
border-bottom: none;
}
.task-input {
display: inline-block;
vertical-align: text-top;
float: left;
margin: ($baseline/2) flex-gutter() 0 0;
}
.task-details {
display: inline-block;
vertical-align: text-top;
float: left;
width: flex-grid(6,9);
font-weight: 500;
.task-name {
@include transition(color $tmg-f2 ease-in-out 0s);
vertical-align: baseline;
cursor: pointer;
margin-bottom: 0;
}
.task-description {
@extend %t-copy-sub1;
@include transition(color $tmg-f2 ease-in-out 0s);
color: $gray-l2;
}
.task-support {
@extend %t-copy-sub2;
@include transition(opacity $tmg-f2 ease-in-out 0s);
opacity: 0.0;
pointer-events: none;
}
}
.task-actions {
@include transition(opacity $tmg-f2 ease-in-out 0.25s);
@include clearfix();
display: inline-block;
vertical-align: middle;
float: right;
width: flex-grid(2,9);
margin: ($baseline/2) 0 0 flex-gutter();
opacity: 0.0;
pointer-events: none;
text-align: right;
.action-primary {
@include blue-button;
@extend %t-action4;
font-weight: 600;
text-align: center;
}
.action-secondary {
@extend %t-action4;
margin-top: ($baseline/2);
}
}
// state - hover
&:hover {
background: $blue-l5;
border-bottom-color: $blue-l4;
border-top-color: $blue-l4;
opacity: 1.0;
.task-details {
.task-support {
opacity: 1.0;
pointer-events: auto;
}
}
.task-actions {
opacity: 1.0;
pointer-events: auto;
}
}
// state - completed
&.is-completed {
background: $gray-l6;
border-top-color: $gray-l5;
border-bottom-color: $gray-l5;
.task-name {
color: $gray-l2;
}
.task-actions {
.action-primary {
@include grey-button;
@extend %t-action4;
font-weight: 600;
text-align: center;
}
}
&:hover {
background: $gray-l5;
border-bottom-color: $gray-l4;
border-top-color: $gray-l4;
.task-details {
opacity:1.0;
}
}
}
}
}
.content-supplementary {
width: flex-grid(3, 12);
}
}
<%inherit file="base.html" />
<%!
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
%>
<%block name="title">Utilities - Bulk Settings</%block>
<%block name="bodyclass">is-signedin course view-bulksettings</%block>
<%namespace name='static' file='static_content.html'/>
<%block name="content">
<div class="wrapper-mast wrapper">
<header class="mast has-actions has-subtitle">
<h1 class="page-header">
<small class="subtitle">${_("Tools / Course Utilities")}</small>
<span class="sr">&gt; </span>${_("Bulk Settings")}
</h1>
</header>
</div>
<div class="wrapper-content wrapper">
<section class="content">
<article class="content-primary" role="main">
<!-- Static Bulk Settings Header Table -->
<section class="course-utility" id="settings-header">
<table width="100%" style="margin: 10px">
<tr>
<td width="52%"><strong>Problem Settings:</strong></td>
<td width="8%" title="Attempts"><i class="icon-list-ol"></i></td>
<td width="8%" title="Weight"><i class="icon-dashboard"></i></td>
<td width="8%" title="Randomization"><i class="icon-random"></i></td>
<td width="8%" title="Show Answer"><i class="icon-eye-open"></i></td>
<td width="8%" title="Timer Between Attempts"><i class="icon-time"></i></td>
<td width="8%" title="Status"><i class="icon-key"></i></td>
</tr>
</table>
</section>
<!-- Dynamic Bulk Settings Tables -->
% for section_settings in settings_data:
<section class="course-utility" id="course-utility0">
<header class = "section-header">
<h4 class = "section-header-title" title="Collapse/Expand this utility">
<a href="${section_settings['url']}">${section_settings['name']}</a>
</h4>
<p class="section-header-date">
% for setting_type in setting_type_list_map['section_setting_types']:
<strong>${section_setting_map[setting_type]}:</strong> ${section_settings[setting_type]}
% endfor
</p>
</header>
<table class = "settings-table">
% for subsection_settings in section_settings['children']:
<tr class = "subsection-header">
<td class = "subsection-header-cell" colspan = "7">
<strong><a href="${subsection_settings['url']}">${subsection_settings['name']}</a></strong> -
% for setting_type in setting_type_list_map['subsection_setting_types']:
<span title=${setting_type} class="subsection-settings"><strong>${subsection_setting_map[setting_type]}:</strong> ${subsection_settings[setting_type]}</span>
% endfor
</td>
</tr>
% for unit_settings in subsection_settings['children']:
<tr class = "unit-header">
<!-- TODO: dynamically allocate space ?-->
<td width = "92%" colspan="6"><strong><a href="${unit_settings['url']}">${unit_settings['name']}</a></strong></td>
<td width = "8%">${unit_settings['ispublic']}</td> <!--hard coded right now-->
</tr>
% for problem_settings in unit_settings['children']:
<tr class="problem-header">
<td width="52%"><strong><a href="${problem_settings['url']}">${problem_settings['name']}</a></strong></td>
% for setting_type in setting_type_list_map['problem_setting_types']:
<td width="8%">${problem_settings[setting_type]}</td>
% endfor
<td width="8%"></td> <!-- Padding-->
</tr>
% endfor
% endfor
% endfor
</table>
</section>
% endfor
</article>
<aside class="content-supplementary" role="complimentary">
<div class="bit">
<h3 class="title title-3">${_("What is bulk settings?")}</h3>
<p>
${_("The bulk settings page provides a way to view all problem settings of the current course in one page.")}
</p>
</div>
<div class="bit">
<section class = "course-utility key-box">
<header>
<h3 class = "title title-3">${_("Settings Keys")}</h3>
</header>
<ul class = "key-list">
<li><i class="icon-list-ol"></i> - Max Attempts</li>
<li><i class="icon-dashboard"></i> - Weight</li>
<li><i class="icon-random"></i> - Randomize</li>
<li><i class="icon-eye-open"></i> - Show Answer</li>
<li><i class="icon-time"></i> - Time Between Submissions</li>
<li><i class="icon-key"></i> - Is Public</li>
</ul>
</section>
</div>
</aside>
</section>
</div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
<script type = "text/javascript">
var position = window.pageYOffset;
var originalY = $('#settings-header').offset().top;
var originalY_keybox = $('.key-box').offset().top;
$(window).scroll(function () {
position = window.pageYOffset;
/* Position re-rendering for the headers table */
if(position > originalY) {
parentWidth = $('#settings-header').parent().width();
$('#settings-header').addClass('fixed-header').css({'width': parentWidth});
$('#course-utility0').css({'marginTop': '85px'});
} else {
$('#settings-header').removeClass('fixed-header').css({'width': 'auto'});
$('#course-utility0').css({'marginTop': '0'});
}
/* Position re-rendering for the key box */
if (position > originalY_keybox) {
parentWidth = $('.key-box').parent().width();
$('.key-box').addClass('fixed-sidebar').css({'width': parentWidth});
} else {
$('.key-box').removeClass('fixed-sidebar').css({'width': 'auto'});
}
});
</script>
</%block>
......@@ -92,6 +92,7 @@ urlpatterns += patterns(
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'^utility/bulksettings/(?P<course_key_string>[^/]+)$', 'utility_bulksettings_handler'),
)
js_info_dict = {
......
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