Commit aca5cbb2 by cahrens

Merge branch 'master' into bug/christina/preview_url

parents e0bb4ad4 256a3677
...@@ -19,9 +19,7 @@ DISPLAY_NAME_VALUE = '"Robot Super Course"' ...@@ -19,9 +19,7 @@ DISPLAY_NAME_VALUE = '"Robot Super Course"'
############### ACTIONS #################### ############### ACTIONS ####################
@step('I select the Advanced Settings$') @step('I select the Advanced Settings$')
def i_select_advanced_settings(step): def i_select_advanced_settings(step):
expand_icon_css = 'li.nav-course-settings i.icon-expand' world.click_course_settings()
if world.browser.is_element_present_by_css(expand_icon_css):
world.css_click(expand_icon_css)
link_css = 'li.nav-course-settings-advanced a' link_css = 'li.nav-course-settings-advanced a'
world.css_click(link_css) world.css_click(link_css)
......
...@@ -10,9 +10,7 @@ from selenium.common.exceptions import StaleElementReferenceException ...@@ -10,9 +10,7 @@ from selenium.common.exceptions import StaleElementReferenceException
############### ACTIONS #################### ############### ACTIONS ####################
@step('I select Checklists from the Tools menu$') @step('I select Checklists from the Tools menu$')
def i_select_checklists(step): def i_select_checklists(step):
expand_icon_css = 'li.nav-course-tools i.icon-expand' world.click_tools()
if world.browser.is_element_present_by_css(expand_icon_css):
world.css_click(expand_icon_css)
link_css = 'li.nav-course-tools-checklists a' link_css = 'li.nav-course-tools-checklists a'
world.css_click(link_css) world.css_click(link_css)
......
...@@ -25,9 +25,7 @@ DEFAULT_TIME = "00:00" ...@@ -25,9 +25,7 @@ DEFAULT_TIME = "00:00"
############### ACTIONS #################### ############### ACTIONS ####################
@step('I select Schedule and Details$') @step('I select Schedule and Details$')
def test_i_select_schedule_and_details(step): def test_i_select_schedule_and_details(step):
expand_icon_css = 'li.nav-course-settings i.icon-expand' world.click_course_settings()
if world.browser.is_element_present_by_css(expand_icon_css):
world.css_click(expand_icon_css)
link_css = 'li.nav-course-settings-schedule a' link_css = 'li.nav-course-settings-schedule a'
world.css_click(link_css) world.css_click(link_css)
......
...@@ -62,4 +62,4 @@ def i_am_on_tab(step, tab_name): ...@@ -62,4 +62,4 @@ def i_am_on_tab(step, tab_name):
@step('I see a link for adding a new section$') @step('I see a link for adding a new section$')
def i_see_new_section_link(step): def i_see_new_section_link(step):
link_css = 'a.new-courseware-section-button' link_css = 'a.new-courseware-section-button'
assert world.css_has_text(link_css, '+ New Section') assert world.css_has_text(link_css, 'New Section')
...@@ -62,7 +62,7 @@ def i_click_to_edit_section_name(step): ...@@ -62,7 +62,7 @@ def i_click_to_edit_section_name(step):
@step('I see the complete section name with a quote in the editor$') @step('I see the complete section name with a quote in the editor$')
def i_see_complete_section_name_with_quote_in_editor(step): def i_see_complete_section_name_with_quote_in_editor(step):
css = '.edit-section-name' css = '.section-name-edit input[type=text]'
assert world.is_css_present(css) assert world.is_css_present(css)
assert_equal(world.browser.find_by_css(css).value, 'Section with "Quote"') assert_equal(world.browser.find_by_css(css).value, 'Section with "Quote"')
......
...@@ -34,6 +34,8 @@ from xmodule.course_module import CourseDescriptor ...@@ -34,6 +34,8 @@ from xmodule.course_module import CourseDescriptor
from xmodule.seq_module import SequenceDescriptor from xmodule.seq_module import SequenceDescriptor
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from django_comment_common.utils import are_permissions_roles_seeded
TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE) TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data') TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data') TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data')
...@@ -45,7 +47,7 @@ class MongoCollectionFindWrapper(object): ...@@ -45,7 +47,7 @@ class MongoCollectionFindWrapper(object):
self.counter = 0 self.counter = 0
def find(self, query, *args, **kwargs): def find(self, query, *args, **kwargs):
self.counter = self.counter+1 self.counter = self.counter + 1
return self.original(query, *args, **kwargs) return self.original(query, *args, **kwargs)
...@@ -352,7 +354,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -352,7 +354,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
clone_items = module_store.get_items(Location(['i4x', 'MITx', '999', 'vertical', None])) clone_items = module_store.get_items(Location(['i4x', 'MITx', '999', 'vertical', None]))
self.assertGreater(len(clone_items), 0) self.assertGreater(len(clone_items), 0)
for descriptor in items: for descriptor in items:
new_loc = descriptor.location._replace(org='MITx', course='999') new_loc = descriptor.location.replace(org='MITx', course='999')
print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url()) print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url())
resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()})) resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()}))
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
...@@ -375,15 +377,15 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -375,15 +377,15 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.assertEqual(len(items), 0) self.assertEqual(len(items), 0)
def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''): def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''):
fs = OSFS(root_dir / 'test_export') filesystem = OSFS(root_dir / 'test_export')
self.assertTrue(fs.exists(dirname)) self.assertTrue(filesystem.exists(dirname))
query_loc = Location('i4x', location.org, location.course, category_name, None) query_loc = Location('i4x', location.org, location.course, category_name, None)
items = modulestore.get_items(query_loc) items = modulestore.get_items(query_loc)
for item in items: for item in items:
fs = OSFS(root_dir / ('test_export/' + dirname)) filesystem = OSFS(root_dir / ('test_export/' + dirname))
self.assertTrue(fs.exists(item.location.name + filename_suffix)) self.assertTrue(filesystem.exists(item.location.name + filename_suffix))
def test_export_course(self): def test_export_course(self):
module_store = modulestore('direct') module_store = modulestore('direct')
...@@ -415,7 +417,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -415,7 +417,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# add private to list of children # add private to list of children
sequential = module_store.get_item(Location(['i4x', 'edX', 'full', sequential = module_store.get_item(Location(['i4x', 'edX', 'full',
'sequential', 'Administrivia_and_Circuit_Elements', None])) 'sequential', 'Administrivia_and_Circuit_Elements', None]))
private_location_no_draft = private_vertical.location._replace(revision=None) private_location_no_draft = private_vertical.location.replace(revision=None)
module_store.update_children(sequential.location, sequential.children + module_store.update_children(sequential.location, sequential.children +
[private_location_no_draft.url()]) [private_location_no_draft.url()])
...@@ -440,20 +442,20 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -440,20 +442,20 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.verify_content_existence(module_store, root_dir, location, 'custom_tags', 'custom_tag_template') self.verify_content_existence(module_store, root_dir, location, 'custom_tags', 'custom_tag_template')
# check for graiding_policy.json # check for graiding_policy.json
fs = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012') filesystem = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012')
self.assertTrue(fs.exists('grading_policy.json')) self.assertTrue(filesystem.exists('grading_policy.json'))
course = module_store.get_item(location) course = module_store.get_item(location)
# compare what's on disk compared to what we have in our course # compare what's on disk compared to what we have in our course
with fs.open('grading_policy.json', 'r') as grading_policy: with filesystem.open('grading_policy.json', 'r') as grading_policy:
on_disk = loads(grading_policy.read()) on_disk = loads(grading_policy.read())
self.assertEqual(on_disk, course.grading_policy) self.assertEqual(on_disk, course.grading_policy)
#check for policy.json #check for policy.json
self.assertTrue(fs.exists('policy.json')) self.assertTrue(filesystem.exists('policy.json'))
# compare what's on disk to what we have in the course module # compare what's on disk to what we have in the course module
with fs.open('policy.json', 'r') as course_policy: with filesystem.open('policy.json', 'r') as course_policy:
on_disk = loads(course_policy.read()) on_disk = loads(course_policy.read())
self.assertIn('course/6.002_Spring_2012', on_disk) self.assertIn('course/6.002_Spring_2012', on_disk)
self.assertEqual(on_disk['course/6.002_Spring_2012'], own_metadata(course)) self.assertEqual(on_disk['course/6.002_Spring_2012'], own_metadata(course))
...@@ -608,6 +610,14 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -608,6 +610,14 @@ class ContentStoreTest(ModuleStoreTestCase):
data = parse_json(resp) data = parse_json(resp)
self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course') self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course')
def test_create_course_check_forum_seeding(self):
"""Test new course creation and verify forum seeding """
resp = self.client.post(reverse('create_new_course'), self.course_data)
self.assertEqual(resp.status_code, 200)
data = parse_json(resp)
self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course')
self.assertTrue(are_permissions_roles_seeded('MITx/999/Robot_Super_Course'))
def test_create_course_duplicate_course(self): def test_create_course_duplicate_course(self):
"""Test new course creation - error path""" """Test new course creation - error path"""
resp = self.client.post(reverse('create_new_course'), self.course_data) resp = self.client.post(reverse('create_new_course'), self.course_data)
...@@ -801,37 +811,37 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -801,37 +811,37 @@ class ContentStoreTest(ModuleStoreTestCase):
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# go look at a subsection page # go look at a subsection page
subsection_location = loc._replace(category='sequential', name='test_sequence') subsection_location = loc.replace(category='sequential', name='test_sequence')
resp = self.client.get(reverse('edit_subsection', resp = self.client.get(reverse('edit_subsection',
kwargs={'location': subsection_location.url()})) kwargs={'location': subsection_location.url()}))
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# go look at the Edit page # go look at the Edit page
unit_location = loc._replace(category='vertical', name='test_vertical') unit_location = loc.replace(category='vertical', name='test_vertical')
resp = self.client.get(reverse('edit_unit', resp = self.client.get(reverse('edit_unit',
kwargs={'location': unit_location.url()})) kwargs={'location': unit_location.url()}))
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# delete a component # delete a component
del_loc = loc._replace(category='html', name='test_html') del_loc = loc.replace(category='html', name='test_html')
resp = self.client.post(reverse('delete_item'), resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json") json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# delete a unit # delete a unit
del_loc = loc._replace(category='vertical', name='test_vertical') del_loc = loc.replace(category='vertical', name='test_vertical')
resp = self.client.post(reverse('delete_item'), resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json") json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# delete a unit # delete a unit
del_loc = loc._replace(category='sequential', name='test_sequence') del_loc = loc.replace(category='sequential', name='test_sequence')
resp = self.client.post(reverse('delete_item'), resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json") json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
# delete a chapter # delete a chapter
del_loc = loc._replace(category='chapter', name='chapter_2') del_loc = loc.replace(category='chapter', name='chapter_2')
resp = self.client.post(reverse('delete_item'), resp = self.client.post(reverse('delete_item'),
json.dumps({'id': del_loc.url()}), "application/json") json.dumps({'id': del_loc.url()}), "application/json")
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
......
...@@ -13,17 +13,13 @@ from django.core.urlresolvers import reverse ...@@ -13,17 +13,13 @@ from django.core.urlresolvers import reverse
from mitxmako.shortcuts import render_to_response from mitxmako.shortcuts import render_to_response
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions \
import ItemNotFoundError, InvalidLocationError from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError
from xmodule.modulestore import Location from xmodule.modulestore import Location
from contentstore.course_info_model \ from contentstore.course_info_model import get_course_updates, update_course_updates, delete_course_update
import get_course_updates, update_course_updates, delete_course_update from contentstore.utils import get_lms_link_for_item, add_extra_panel_tab, remove_extra_panel_tab
from contentstore.utils \ from models.settings.course_details import CourseDetails, CourseSettingsEncoder
import get_lms_link_for_item, add_extra_panel_tab, \
remove_extra_panel_tab
from models.settings.course_details \
import CourseDetails, CourseSettingsEncoder
from models.settings.course_grading import CourseGradingModel from models.settings.course_grading import CourseGradingModel
from models.settings.course_metadata import CourseMetadata from models.settings.course_metadata import CourseMetadata
from auth.authz import create_all_course_groups from auth.authz import create_all_course_groups
...@@ -35,6 +31,10 @@ from .tabs import initialize_course_tabs ...@@ -35,6 +31,10 @@ from .tabs import initialize_course_tabs
from .component import OPEN_ENDED_COMPONENT_TYPES, \ from .component import OPEN_ENDED_COMPONENT_TYPES, \
NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY NOTE_COMPONENT_TYPES, ADVANCED_COMPONENT_POLICY_KEY
from django_comment_common.utils import seed_permissions_roles
# TODO: should explicitly enumerate exports with __all__
__all__ = ['course_index', 'create_new_course', 'course_info', __all__ = ['course_index', 'create_new_course', 'course_info',
'course_info_updates', 'get_course_settings', 'course_info_updates', 'get_course_settings',
'course_config_graders_page', 'course_config_graders_page',
...@@ -136,6 +136,9 @@ def create_new_course(request): ...@@ -136,6 +136,9 @@ def create_new_course(request):
create_all_course_groups(request.user, new_course.location) create_all_course_groups(request.user, new_course.location)
# seed the forums
seed_permissions_roles(new_course.location.course_id)
return HttpResponse(json.dumps({'id': new_course.location.url()})) return HttpResponse(json.dumps({'id': new_course.location.url()}))
......
...@@ -322,6 +322,9 @@ INSTALLED_APPS = ( ...@@ -322,6 +322,9 @@ INSTALLED_APPS = (
'pipeline', 'pipeline',
'staticfiles', 'staticfiles',
'static_replace', 'static_replace',
# comment common
'django_comment_common',
) )
################# EDX MARKETING SITE ################################## ################# EDX MARKETING SITE ##################################
......
...@@ -128,8 +128,7 @@ CELERY_ALWAYS_EAGER = True ...@@ -128,8 +128,7 @@ CELERY_ALWAYS_EAGER = True
################################ DEBUG TOOLBAR ################################# ################################ DEBUG TOOLBAR #################################
INSTALLED_APPS += ('debug_toolbar', 'debug_toolbar_mongo') INSTALLED_APPS += ('debug_toolbar', 'debug_toolbar_mongo')
MIDDLEWARE_CLASSES += ('django_comment_client.utils.QueryCountDebugMiddleware', MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
'debug_toolbar.middleware.DebugToolbarMiddleware',)
INTERNAL_IPS = ('127.0.0.1',) INTERNAL_IPS = ('127.0.0.1',)
DEBUG_TOOLBAR_PANELS = ( DEBUG_TOOLBAR_PANELS = (
......
...@@ -36,8 +36,13 @@ PIPELINE_JS['spec'] = { ...@@ -36,8 +36,13 @@ PIPELINE_JS['spec'] = {
} }
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
JASMINE_REPORT_DIR = os.environ.get('JASMINE_REPORT_DIR', 'reports/cms/jasmine')
TEMPLATE_CONTEXT_PROCESSORS += ('settings_context_processor.context_processors.settings',)
TEMPLATE_VISIBLE_SETTINGS = ('JASMINE_REPORT_DIR', )
STATICFILES_DIRS.append(REPO_ROOT/'node_modules/phantom-jasmine/lib') STATICFILES_DIRS.append(REPO_ROOT/'node_modules/phantom-jasmine/lib')
STATICFILES_DIRS.append(REPO_ROOT/'node_modules/jasmine-reporters/src')
# Remove the localization middleware class because it requires the test database # Remove the localization middleware class because it requires the test database
# to be sync'd and migrated in order to run the jasmine tests interactively # to be sync'd and migrated in order to run the jasmine tests interactively
...@@ -45,4 +50,4 @@ STATICFILES_DIRS.append(REPO_ROOT/'node_modules/phantom-jasmine/lib') ...@@ -45,4 +50,4 @@ STATICFILES_DIRS.append(REPO_ROOT/'node_modules/phantom-jasmine/lib')
MIDDLEWARE_CLASSES = tuple(e for e in MIDDLEWARE_CLASSES \ MIDDLEWARE_CLASSES = tuple(e for e in MIDDLEWARE_CLASSES \
if e != 'django.middleware.locale.LocaleMiddleware') if e != 'django.middleware.locale.LocaleMiddleware')
INSTALLED_APPS += ('django_jasmine', ) INSTALLED_APPS += ('django_jasmine', 'settings_context_processor')
...@@ -20,8 +20,6 @@ ...@@ -20,8 +20,6 @@
<%static:css group='base-style'/> <%static:css group='base-style'/>
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-symbolicons-block.css')}" />
<link rel="stylesheet" type="text/css" href="${static.url('css/vendor/symbolset.ss-standard.css')}" />
<%include file="widgets/segment-io.html" /> <%include file="widgets/segment-io.html" />
...@@ -43,8 +41,6 @@ ...@@ -43,8 +41,6 @@
<script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/backbone-min.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/markitup/jquery.markitup.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/markitup/jquery.markitup.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/markitup/sets/wiki/set.js')}"></script> <script type="text/javascript" src="${static.url('js/vendor/markitup/sets/wiki/set.js')}"></script>
<script src="${static.url('js/vendor/symbolset.ss-standard.js')}"></script>
<script src="${static.url('js/vendor/symbolset.ss-symbolicons.js')}"></script>
<%static:js group='main'/> <%static:js group='main'/>
<%static:js group='module-js'/> <%static:js group='module-js'/>
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script> <script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/checklists_view.js')}"></script> <script type="text/javascript" src="${static.url('js/views/checklists_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/checklists.js')}"></script> <script type="text/javascript" src="${static.url('js/models/checklists.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
<script type="text/javascript" src="${static.url('js/models/course_info.js')}"></script> <script type="text/javascript" src="${static.url('js/models/course_info.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/module_info.js')}"></script> <script type="text/javascript" src="${static.url('js/models/module_info.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/course_info_edit.js')}"></script> <script type="text/javascript" src="${static.url('js/views/course_info_edit.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" /> <link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script> <script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
<script src="${static.url('js/vendor/timepicker/datepair.js')}"></script> <script src="${static.url('js/vendor/timepicker/datepair.js')}"></script>
......
...@@ -15,7 +15,6 @@ from contentstore import utils ...@@ -15,7 +15,6 @@ from contentstore import utils
<script src="${static.url('js/vendor/date.js')}"></script> <script src="${static.url('js/vendor/date.js')}"></script>
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script> <script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script> <script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script> <script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/settings/main_settings_view.js')}"></script> <script type="text/javascript" src="${static.url('js/views/settings/main_settings_view.js')}"></script>
......
...@@ -11,7 +11,6 @@ from contentstore import utils ...@@ -11,7 +11,6 @@ from contentstore import utils
<%block name="jsextra"> <%block name="jsextra">
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script> <script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script> <script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/advanced.js')}"></script> <script type="text/javascript" src="${static.url('js/models/settings/advanced.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/settings/advanced_view.js')}"></script> <script type="text/javascript" src="${static.url('js/views/settings/advanced_view.js')}"></script>
......
...@@ -6,27 +6,26 @@ ...@@ -6,27 +6,26 @@
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%! <%!
from contentstore import utils from contentstore import utils
%> %>
<%block name="jsextra"> <%block name="jsextra">
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script> <script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script> <script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script> <script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function(){ $(document).ready(function(){
}); });
</script> </script>
</%block> </%block>
<%block name="content"> <%block name="content">
<!-- --> <!-- -->
<div class="main-wrapper"> <div class="main-wrapper">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h1>Settings</h1> <h1>Settings</h1>
<article class="settings-overview"> <article class="settings-overview">
<div class="settings-page-section main-column"> <div class="settings-page-section main-column">
...@@ -74,7 +73,7 @@ from contentstore import utils ...@@ -74,7 +73,7 @@ from contentstore import utils
<div class="field"> <div class="field">
<textarea class="long tall edit-box tinymce" id="course-faculty-1-bio"></textarea> <textarea class="long tall edit-box tinymce" id="course-faculty-1-bio"></textarea>
<span class="tip tip-stacked">A brief description of your education, experience, and expertise</span> <span class="tip tip-stacked">A brief description of your education, experience, and expertise</span>
</div> </div>
</div> </div>
<a href="#" class="remove-item remove-faculty-data"><span class="delete-icon"></span> Delete Faculty Member</a> <a href="#" class="remove-item remove-faculty-data"><span class="delete-icon"></span> Delete Faculty Member</a>
...@@ -102,7 +101,7 @@ from contentstore import utils ...@@ -102,7 +101,7 @@ from contentstore import utils
<a href="#" class="new-item new-faculty-photo add-faculty-photo-data" id="course-faculty-2-photo"> <a href="#" class="new-item new-faculty-photo add-faculty-photo-data" id="course-faculty-2-photo">
<span class="upload-icon"></span>Upload Faculty Photo <span class="upload-icon"></span>Upload Faculty Photo
</a> </a>
<span class="tip tip-inline">Max size: 30KB</span> <span class="tip tip-inline">Max size: 30KB</span>
</div> </div>
</div> </div>
</div> </div>
...@@ -114,7 +113,7 @@ from contentstore import utils ...@@ -114,7 +113,7 @@ from contentstore import utils
<textarea class="long tall edit-box tinymce" id="course-faculty-2-bio"></textarea> <textarea class="long tall edit-box tinymce" id="course-faculty-2-bio"></textarea>
<span class="tip tip-stacked">A brief description of your education, experience, and expertise</span> <span class="tip tip-stacked">A brief description of your education, experience, and expertise</span>
</div> </div>
</div> </div>
</div> </div>
</li> </li>
</ul> </ul>
...@@ -143,7 +142,7 @@ from contentstore import utils ...@@ -143,7 +142,7 @@ from contentstore import utils
<div class="field"> <div class="field">
<div class="input input-radio"> <div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-always" value="Always"> <input checked="checked" type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-always" value="Always">
<div class="copy"> <div class="copy">
<label for="course-problems-general-randomization-always">Always</label> <label for="course-problems-general-randomization-always">Always</label>
<span class="tip tip-stacked"><strong>randomize all</strong> problems</span> <span class="tip tip-stacked"><strong>randomize all</strong> problems</span>
...@@ -217,7 +216,7 @@ from contentstore import utils ...@@ -217,7 +216,7 @@ from contentstore import utils
<div class="field"> <div class="field">
<div class="input input-radio"> <div class="input input-radio">
<input checked="checked" type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-always" value="Always"> <input checked="checked" type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-always" value="Always">
<div class="copy"> <div class="copy">
<label for="course-problems-assignment-1-randomization-always">Always</label> <label for="course-problems-assignment-1-randomization-always">Always</label>
<span class="tip tip-stacked"><strong>randomize all</strong> problems</span> <span class="tip tip-stacked"><strong>randomize all</strong> problems</span>
...@@ -283,7 +282,7 @@ from contentstore import utils ...@@ -283,7 +282,7 @@ from contentstore import utils
<section class="settings-discussions"> <section class="settings-discussions">
<h2 class="title">Discussions</h2> <h2 class="title">Discussions</h2>
<section class="settings-discussions-general"> <section class="settings-discussions-general">
<header> <header>
<h3>General Settings</h3> <h3>General Settings</h3>
...@@ -296,7 +295,7 @@ from contentstore import utils ...@@ -296,7 +295,7 @@ from contentstore import utils
<div class="field"> <div class="field">
<div class="input input-radio"> <div class="input input-radio">
<input type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow"> <input type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow">
<div class="copy"> <div class="copy">
<label for="course-discussions-anonymous-allow">Allow</label> <label for="course-discussions-anonymous-allow">Allow</label>
<span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span> <span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span>
...@@ -320,7 +319,7 @@ from contentstore import utils ...@@ -320,7 +319,7 @@ from contentstore import utils
<div class="field"> <div class="field">
<div class="input input-radio"> <div class="input input-radio">
<input checked="checked" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow"> <input checked="checked" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow">
<div class="copy"> <div class="copy">
<label for="course-discussions-anonymous-allow">Allow</label> <label for="course-discussions-anonymous-allow">Allow</label>
<span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span> <span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span>
...@@ -329,7 +328,7 @@ from contentstore import utils ...@@ -329,7 +328,7 @@ from contentstore import utils
<div class="input input-radio"> <div class="input input-radio">
<input disabled="disabled" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-dontallow" value="Do Not Allow"> <input disabled="disabled" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-dontallow" value="Do Not Allow">
<div class="copy"> <div class="copy">
<label for="course-discussions-anonymous-dontallow">Do not allow</label> <label for="course-discussions-anonymous-dontallow">Do not allow</label>
<span class="tip tip-stacked">This option is disabled since there are previous discussions that are anonymous.</span> <span class="tip tip-stacked">This option is disabled since there are previous discussions that are anonymous.</span>
...@@ -351,7 +350,7 @@ from contentstore import utils ...@@ -351,7 +350,7 @@ from contentstore import utils
<a href="#" class="drag-handle"></a> <a href="#" class="drag-handle"></a>
</li> </li>
<li class="input input-existing input-default course-discussions-categories-list-item sortable-item"> <li class="input input-existing input-default course-discussions-categories-list-item sortable-item">
<div class="group"> <div class="group">
<label for="course-discussions-categories-2-name">Category Name: </label> <label for="course-discussions-categories-2-name">Category Name: </label>
......
...@@ -12,7 +12,6 @@ from contentstore import utils ...@@ -12,7 +12,6 @@ from contentstore import utils
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script> <script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script> <script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script> <script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script> <script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script> <script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script>
......
<%include file="metadata-edit.html" />
<section class="combinedopenended-editor editor">
<div class="row">
%if enable_markdown:
<div class="editor-bar">
<ul class="format-buttons">
<li><a href="#" class="prompt-button" data-tooltip="Prompt"><span
class="combinedopenended-editor-icon icon-quote-left"></span></a></li>
<li><a href="#" class="rubric-button" data-tooltip="Rubric"><span
class="combinedopenended-editor-icon icon-table"></span></a></li>
<li><a href="#" class="tasks-button" data-tooltip="Tasks"><span
class="combinedopenended-editor-icon icon-sitemap"></span></a></li>
</ul>
<ul class="editor-tabs">
<li><a href="#" class="xml-tab advanced-toggle" data-tab="xml">Advanced Editor</a></li>
<li><a href="#" class="cheatsheet-toggle" data-tooltip="Toggle Cheatsheet">?</a></li>
</ul>
</div>
<textarea class="markdown-box">${markdown | h}</textarea>
%endif
<textarea class="xml-box" rows="8" cols="40">${data | h}</textarea>
</div>
</section>
<script type="text/template" id="open-ended-template">
<openended %min_max_string%>
<openendedparam>
<initial_display>Enter essay here.</initial_display>
<answer_display>This is the answer.</answer_display>
<grader_payload>{"grader_settings" : "%grading_config%", "problem_id" : "6.002x/Welcome/OETest"}</grader_payload>
</openendedparam>
</openended>
</script>
<script type="text/template" id="simple-editor-open-ended-cheatsheet">
<article class="simple-editor-open-ended-cheatsheet">
<div class="cheatsheet-wrapper">
<div class="row">
<h6>Prompt</h6>
<div class="col prompt">
</div>
<div class="col">
<pre><code>
[prompt]
Why is the sky blue?
[prompt]
</code></pre>
</div>
<div class="col">
<p>The student will respond to the prompt. The prompt can contain any html tags, such as paragraph tags and header tags.</p>
</div>
</div>
<div class="row">
<h6>Rubric</h6>
<div class="col sample rubric"><!DOCTYPE html>
</div>
<div class="col">
<pre><code>
[rubric]
+ Color Identification
- Incorrect
- Correct
+ Grammar
- Poor
- Acceptable
- Superb
[rubric]
</code></pre>
</div>
<div class="col">
<p>The rubric is used for feedback and self-assessment. The rubric can have as many categories (+) and options (-) as desired. </p>
<p>The total score for the problem will be the sum of all the points possible on the rubric. The options will be numbered sequentially from zero in each category, so each category will be worth as many points as its number of options minus one. </p>
</div>
</div>
<div class="row">
<h6>Tasks</h6>
<div class="col sample tasks">
</div>
<div class="col">
<pre><code>
[tasks]
(Self), ({1-3}AI), ({2-3}Peer)
[tasks]
</code></pre>
</div>
<div class="col">
<p>The tasks define what feedback the student will get from the problem.</p>
<p>Each task is defined with parentheses around it. Brackets (ie {2-3} above), specify the minimum and maximum score needed to attempt the given task.</p>
<p>In the example above, the student will first be asked to self-assess. If they give themselves greater than or equal to a 1/3 and less than or equal to a 3/3 on the problem, then they will be moved to AI assessment. If they score themselves a 2/3 or 3/3 on AI assessment, they will move to peer assessment.</p>
<p>Students will be given feedback from each task, and their final score for a given attempt of the problem will be their score last task that is completed.</p>
</div>
</div>
</div>
</article>
</script>
# -*- coding: utf-8 -*-
from south.v2 import SchemaMigration
class Migration(SchemaMigration):
#
# cdodge: This is basically an empty migration since everything has - up to now - managed in the django_comment_client app
# But going forward we should be using this migration
#
def forwards(self, orm):
pass
def backwards(self, orm):
pass
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}),
'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}),
'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}),
'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}),
'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'django_comment_common.permission': {
'Meta': {'object_name': 'Permission'},
'name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'primary_key': 'True'}),
'roles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'permissions'", 'symmetrical': 'False', 'to': "orm['django_comment_common.Role']"})
},
'django_comment_common.role': {
'Meta': {'object_name': 'Role'},
'course_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'roles'", 'symmetrical': 'False', 'to': "orm['auth.User']"})
}
}
complete_apps = ['django_comment_common']
import logging
from django.db import models
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from student.models import CourseEnrollment
from xmodule.modulestore.django import modulestore
from xmodule.course_module import CourseDescriptor
FORUM_ROLE_ADMINISTRATOR = 'Administrator'
FORUM_ROLE_MODERATOR = 'Moderator'
FORUM_ROLE_COMMUNITY_TA = 'Community TA'
FORUM_ROLE_STUDENT = 'Student'
@receiver(post_save, sender=CourseEnrollment)
def assign_default_role(sender, instance, **kwargs):
if instance.user.is_staff:
role = Role.objects.get_or_create(course_id=instance.course_id, name="Moderator")[0]
else:
role = Role.objects.get_or_create(course_id=instance.course_id, name="Student")[0]
logging.info("assign_default_role: adding %s as %s" % (instance.user, role))
instance.user.roles.add(role)
class Role(models.Model):
name = models.CharField(max_length=30, null=False, blank=False)
users = models.ManyToManyField(User, related_name="roles")
course_id = models.CharField(max_length=255, blank=True, db_index=True)
class Meta:
# use existing table that was originally created from django_comment_client app
db_table = 'django_comment_client_role'
def __unicode__(self):
return self.name + " for " + (self.course_id if self.course_id else "all courses")
def inherit_permissions(self, role): # TODO the name of this method is a little bit confusing,
# since it's one-off and doesn't handle inheritance later
if role.course_id and role.course_id != self.course_id:
logging.warning("%s cannot inherit permissions from %s due to course_id inconsistency", \
self, role)
for per in role.permissions.all():
self.add_permission(per)
def add_permission(self, permission):
self.permissions.add(Permission.objects.get_or_create(name=permission)[0])
def has_permission(self, permission):
course_loc = CourseDescriptor.id_to_location(self.course_id)
course = modulestore().get_instance(self.course_id, course_loc)
if self.name == FORUM_ROLE_STUDENT and \
(permission.startswith('edit') or permission.startswith('update') or permission.startswith('create')) and \
(not course.forum_posts_allowed):
return False
return self.permissions.filter(name=permission).exists()
class Permission(models.Model):
name = models.CharField(max_length=30, null=False, blank=False, primary_key=True)
roles = models.ManyToManyField(Role, related_name="permissions")
class Meta:
# use existing table that was originally created from django_comment_client app
db_table = 'django_comment_client_permission'
def __unicode__(self):
return self.name
from django_comment_common.models import Role
_STUDENT_ROLE_PERMISSIONS = ["vote", "update_thread", "follow_thread", "unfollow_thread",
"update_comment", "create_sub_comment", "unvote", "create_thread",
"follow_commentable", "unfollow_commentable", "create_comment", ]
_MODERATOR_ROLE_PERMISSIONS = ["edit_content", "delete_thread", "openclose_thread",
"endorse_comment", "delete_comment", "see_all_cohorts"]
_ADMINISTRATOR_ROLE_PERMISSIONS = ["manage_moderator"]
def seed_permissions_roles(course_id):
administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0]
moderator_role = Role.objects.get_or_create(name="Moderator", course_id=course_id)[0]
community_ta_role = Role.objects.get_or_create(name="Community TA", course_id=course_id)[0]
student_role = Role.objects.get_or_create(name="Student", course_id=course_id)[0]
for per in _STUDENT_ROLE_PERMISSIONS:
student_role.add_permission(per)
for per in _MODERATOR_ROLE_PERMISSIONS:
moderator_role.add_permission(per)
for per in _ADMINISTRATOR_ROLE_PERMISSIONS:
administrator_role.add_permission(per)
moderator_role.inherit_permissions(student_role)
# For now, Community TA == Moderator, except for the styling.
community_ta_role.inherit_permissions(moderator_role)
administrator_role.inherit_permissions(moderator_role)
def are_permissions_roles_seeded(course_id):
try:
administrator_role = Role.objects.get(name="Administrator", course_id=course_id)
moderator_role = Role.objects.get(name="Moderator", course_id=course_id)
student_role = Role.objects.get(name="Student", course_id=course_id)
except:
return False
for per in _STUDENT_ROLE_PERMISSIONS:
if not student_role.has_permission(per):
return False
for per in _MODERATOR_ROLE_PERMISSIONS + _STUDENT_ROLE_PERMISSIONS:
if not moderator_role.has_permission(per):
return False
for per in _ADMINISTRATOR_ROLE_PERMISSIONS + _MODERATOR_ROLE_PERMISSIONS + _STUDENT_ROLE_PERMISSIONS:
if not administrator_role.has_permission(per):
return False
return True
from student.models import (User, UserProfile, Registration, from student.models import (User, UserProfile, Registration,
CourseEnrollmentAllowed, CourseEnrollment) CourseEnrollmentAllowed, CourseEnrollment,
PendingEmailChange)
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from datetime import datetime from datetime import datetime
from factory import DjangoModelFactory, Factory, SubFactory, PostGenerationMethodCall, post_generation from factory import DjangoModelFactory, SubFactory, PostGenerationMethodCall, post_generation, Sequence
from uuid import uuid4 from uuid import uuid4
# Factories don't have __init__ methods, and are self documenting
# pylint: disable=W0232
class GroupFactory(DjangoModelFactory): class GroupFactory(DjangoModelFactory):
FACTORY_FOR = Group FACTORY_FOR = Group
name = 'staff_MITx/999/Robot_Super_Course' name = u'staff_MITx/999/Robot_Super_Course'
class UserProfileFactory(DjangoModelFactory): class UserProfileFactory(DjangoModelFactory):
FACTORY_FOR = UserProfile FACTORY_FOR = UserProfile
user = None user = None
name = 'Robot Test' name = u'Robot Test'
level_of_education = None level_of_education = None
gender = 'm' gender = u'm'
mailing_address = None mailing_address = None
goals = 'World domination' goals = u'World domination'
class RegistrationFactory(DjangoModelFactory): class RegistrationFactory(DjangoModelFactory):
FACTORY_FOR = Registration FACTORY_FOR = Registration
user = None user = None
activation_key = uuid4().hex activation_key = uuid4().hex.decode('ascii')
class UserFactory(DjangoModelFactory): class UserFactory(DjangoModelFactory):
FACTORY_FOR = User FACTORY_FOR = User
username = 'robot' username = Sequence(u'robot{0}'.format)
email = 'robot+test@edx.org' email = Sequence(u'robot+test+{0}@edx.org'.format)
password = PostGenerationMethodCall('set_password', password = PostGenerationMethodCall('set_password',
'test') 'test')
first_name = 'Robot' first_name = Sequence(u'Robot{0}'.format)
last_name = 'Test' last_name = 'Test'
is_staff = False is_staff = False
is_active = True is_active = True
...@@ -64,7 +68,7 @@ class CourseEnrollmentFactory(DjangoModelFactory): ...@@ -64,7 +68,7 @@ class CourseEnrollmentFactory(DjangoModelFactory):
FACTORY_FOR = CourseEnrollment FACTORY_FOR = CourseEnrollment
user = SubFactory(UserFactory) user = SubFactory(UserFactory)
course_id = 'edX/toy/2012_Fall' course_id = u'edX/toy/2012_Fall'
class CourseEnrollmentAllowedFactory(DjangoModelFactory): class CourseEnrollmentAllowedFactory(DjangoModelFactory):
...@@ -72,3 +76,17 @@ class CourseEnrollmentAllowedFactory(DjangoModelFactory): ...@@ -72,3 +76,17 @@ class CourseEnrollmentAllowedFactory(DjangoModelFactory):
email = 'test@edx.org' email = 'test@edx.org'
course_id = 'edX/test/2012_Fall' course_id = 'edX/test/2012_Fall'
class PendingEmailChangeFactory(DjangoModelFactory):
"""Factory for PendingEmailChange objects
user: generated by UserFactory
new_email: sequence of new+email+{}@edx.org
activation_key: sequence of integers, padded to 30 characters
"""
FACTORY_FOR = PendingEmailChange
user = SubFactory(UserFactory)
new_email = Sequence(u'new+email+{0}@edx.org'.format)
activation_key = Sequence(u'{:0<30d}'.format)
...@@ -19,7 +19,7 @@ from django.core.context_processors import csrf ...@@ -19,7 +19,7 @@ from django.core.context_processors import csrf
from django.core.mail import send_mail from django.core.mail import send_mail
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.validators import validate_email, validate_slug, ValidationError from django.core.validators import validate_email, validate_slug, ValidationError
from django.db import IntegrityError from django.db import IntegrityError, transaction
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseRedirect, Http404 from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseRedirect, Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django_future.csrf import ensure_csrf_cookie, csrf_exempt from django_future.csrf import ensure_csrf_cookie, csrf_exempt
...@@ -655,7 +655,7 @@ def create_account(request, post_override=None): ...@@ -655,7 +655,7 @@ def create_account(request, post_override=None):
elif not settings.GENERATE_RANDOM_USER_CREDENTIALS: elif not settings.GENERATE_RANDOM_USER_CREDENTIALS:
res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
except: except:
log.exception(sys.exc_info()) log.warning('Unable to send activation email to user', exc_info=True)
js['value'] = 'Could not send activation e-mail.' js['value'] = 'Could not send activation e-mail.'
return HttpResponse(json.dumps(js)) return HttpResponse(json.dumps(js))
...@@ -975,7 +975,11 @@ def reactivation_email_for_user(user): ...@@ -975,7 +975,11 @@ def reactivation_email_for_user(user):
subject = ''.join(subject.splitlines()) subject = ''.join(subject.splitlines())
message = render_to_string('emails/activation_email.txt', d) message = render_to_string('emails/activation_email.txt', d)
res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) try:
res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
except:
log.warning('Unable to send reactivation email', exc_info=True)
return HttpResponse(json.dumps({'success': False, 'error': 'Unable to send reactivation email'}))
return HttpResponse(json.dumps({'success': True})) return HttpResponse(json.dumps({'success': True}))
...@@ -1001,7 +1005,7 @@ def change_email_request(request): ...@@ -1001,7 +1005,7 @@ def change_email_request(request):
return HttpResponse(json.dumps({'success': False, return HttpResponse(json.dumps({'success': False,
'error': 'Valid e-mail address required.'})) 'error': 'Valid e-mail address required.'}))
if len(User.objects.filter(email=new_email)) != 0: if User.objects.filter(email=new_email).count() != 0:
## CRITICAL TODO: Handle case sensitivity for e-mails ## CRITICAL TODO: Handle case sensitivity for e-mails
return HttpResponse(json.dumps({'success': False, return HttpResponse(json.dumps({'success': False,
'error': 'An account with this e-mail already exists.'})) 'error': 'An account with this e-mail already exists.'}))
...@@ -1036,41 +1040,63 @@ def change_email_request(request): ...@@ -1036,41 +1040,63 @@ def change_email_request(request):
@ensure_csrf_cookie @ensure_csrf_cookie
@transaction.commit_manually
def confirm_email_change(request, key): def confirm_email_change(request, key):
''' User requested a new e-mail. This is called when the activation ''' User requested a new e-mail. This is called when the activation
link is clicked. We confirm with the old e-mail, and update link is clicked. We confirm with the old e-mail, and update
''' '''
try: try:
pec = PendingEmailChange.objects.get(activation_key=key) try:
except PendingEmailChange.DoesNotExist: pec = PendingEmailChange.objects.get(activation_key=key)
return render_to_response("invalid_email_key.html", {}) except PendingEmailChange.DoesNotExist:
transaction.rollback()
user = pec.user return render_to_response("invalid_email_key.html", {})
d = {'old_email': user.email,
'new_email': pec.new_email} user = pec.user
address_context = {
'old_email': user.email,
'new_email': pec.new_email
}
if len(User.objects.filter(email=pec.new_email)) != 0: if len(User.objects.filter(email=pec.new_email)) != 0:
return render_to_response("email_exists.html", d) transaction.rollback()
return render_to_response("email_exists.html", {})
subject = render_to_string('emails/email_change_subject.txt', address_context)
subject = ''.join(subject.splitlines())
message = render_to_string('emails/confirm_email_change.txt', address_context)
up = UserProfile.objects.get(user=user)
meta = up.get_meta()
if 'old_emails' not in meta:
meta['old_emails'] = []
meta['old_emails'].append([user.email, datetime.datetime.now().isoformat()])
up.set_meta(meta)
up.save()
# Send it to the old email...
try:
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
except Exception:
transaction.rollback()
log.warning('Unable to send confirmation email to old address', exc_info=True)
return render_to_response("email_change_failed.html", {'email': user.email})
subject = render_to_string('emails/email_change_subject.txt', d) user.email = pec.new_email
subject = ''.join(subject.splitlines()) user.save()
message = render_to_string('emails/confirm_email_change.txt', d) pec.delete()
up = UserProfile.objects.get(user=user) # And send it to the new email...
meta = up.get_meta() try:
if 'old_emails' not in meta: user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
meta['old_emails'] = [] except Exception:
meta['old_emails'].append([user.email, datetime.datetime.now().isoformat()]) transaction.rollback()
up.set_meta(meta) log.warning('Unable to send confirmation email to new address', exc_info=True)
up.save() return render_to_response("email_change_failed.html", {'email': pec.new_email})
# Send it to the old email...
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) transaction.commit()
user.email = pec.new_email return render_to_response("email_change_successful.html", address_context)
user.save() except Exception:
pec.delete() # If we get an unexpected exception, be sure to rollback the transaction
# And send it to the new email... transaction.rollback()
user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) raise
return render_to_response("email_change_successful.html", d)
@ensure_csrf_cookie @ensure_csrf_cookie
......
...@@ -123,3 +123,17 @@ def save_the_html(path='/tmp'): ...@@ -123,3 +123,17 @@ def save_the_html(path='/tmp'):
f = open('%s/%s' % (path, filename), 'w') f = open('%s/%s' % (path, filename), 'w')
f.write(html) f.write(html)
f.close() f.close()
@world.absorb
def click_course_settings():
course_settings_css = 'li.nav-course-settings'
if world.browser.is_element_present_by_css(course_settings_css):
world.css_click(course_settings_css)
@world.absorb
def click_tools():
tools_css = 'li.nav-course-tools'
if world.browser.is_element_present_by_css(tools_css):
world.css_click(tools_css)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Jasmine Test Runner</title>
<link rel="stylesheet" type="text/css" href="<%= phantom_jasmine_path %>/vendor/jasmine-1.2.0/jasmine.css">
<script type="text/javascript" src="<%= phantom_jasmine_path %>/vendor/jasmine-1.2.0/jasmine.js"></script>
<script type="text/javascript" src="<%= phantom_jasmine_path %>/vendor/jasmine-1.2.0/jasmine-html.js"></script>
<script type="text/javascript" src="<%= phantom_jasmine_path %>/lib/console-runner.js"></script>
<script type="text/javascript" src="<%= common_coffee_root %>/ajax_prefix.js"></script>
<script type="text/javascript" src="<%= common_coffee_root %>/logger.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/jquery.min.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/jasmine-jquery.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/jquery.cookie.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/CodeMirror/codemirror.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/tiny_mce/jquery.tinymce.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/tiny_mce/tiny_mce.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/RequireJS.js"></script>
<script type="text/javascript">
AjaxPrefix.addAjaxPrefix(jQuery, function() {
return "";
});
</script>
<!-- SOURCE FILES -->
<% for src in js_source %>
<script type="text/javascript" src="<%= src %>"></script>
<% end %>
<!-- SPEC FILES -->
<% for src in js_specs %>
<script type="text/javascript" src="<%= src %>"></script>
<% end %>
</head>
<body>
<script type="text/javascript">
var console_reporter = new jasmine.ConsoleReporter()
jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
jasmine.getEnv().addReporter(console_reporter);
jasmine.getEnv().execute();
</script>
</body>
</html>
...@@ -54,21 +54,22 @@ class CombinedOpenEndedFields(object): ...@@ -54,21 +54,22 @@ class CombinedOpenEndedFields(object):
state = String(help="Which step within the current task that the student is on.", default="initial", state = String(help="Which step within the current task that the student is on.", default="initial",
scope=Scope.user_state) scope=Scope.user_state)
student_attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0, student_attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0,
scope=Scope.user_state) scope=Scope.user_state)
ready_to_reset = StringyBoolean(help="If the problem is ready to be reset or not.", default=False, ready_to_reset = StringyBoolean(help="If the problem is ready to be reset or not.", default=False,
scope=Scope.user_state) scope=Scope.user_state)
attempts = StringyInteger(help="Maximum number of attempts that a student is allowed.", default=1, scope=Scope.settings) attempts = StringyInteger(help="Maximum number of attempts that a student is allowed.", default=1, scope=Scope.settings)
is_graded = StringyBoolean(help="Whether or not the problem is graded.", default=False, scope=Scope.settings) is_graded = StringyBoolean(help="Whether or not the problem is graded.", default=False, scope=Scope.settings)
accept_file_upload = StringyBoolean(help="Whether or not the problem accepts file uploads.", default=False, accept_file_upload = StringyBoolean(help="Whether or not the problem accepts file uploads.", default=False,
scope=Scope.settings) scope=Scope.settings)
skip_spelling_checks = StringyBoolean(help="Whether or not to skip initial spelling checks.", default=True, skip_spelling_checks = StringyBoolean(help="Whether or not to skip initial spelling checks.", default=True,
scope=Scope.settings) scope=Scope.settings)
due = Date(help="Date that this problem is due by", default=None, scope=Scope.settings) due = Date(help="Date that this problem is due by", default=None, scope=Scope.settings)
graceperiod = String(help="Amount of time after the due date that submissions will be accepted", default=None, graceperiod = String(help="Amount of time after the due date that submissions will be accepted", default=None,
scope=Scope.settings) scope=Scope.settings)
version = VersionInteger(help="Current version number", default=DEFAULT_VERSION, scope=Scope.settings) version = VersionInteger(help="Current version number", default=DEFAULT_VERSION, scope=Scope.settings)
data = String(help="XML data for the problem", scope=Scope.content) data = String(help="XML data for the problem", scope=Scope.content)
weight = StringyFloat(help="How much to weight this problem by", scope=Scope.settings) weight = StringyFloat(help="How much to weight this problem by", scope=Scope.settings)
markdown = String(help="Markdown source of this module", scope=Scope.settings)
class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule): class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
...@@ -213,11 +214,36 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor): ...@@ -213,11 +214,36 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
""" """
Module for adding combined open ended questions Module for adding combined open ended questions
""" """
mako_template = "widgets/raw-edit.html" mako_template = "widgets/open-ended-edit.html"
module_class = CombinedOpenEndedModule module_class = CombinedOpenEndedModule
filename_extension = "xml"
stores_state = True stores_state = True
has_score = True has_score = True
always_recalculate_grades = True always_recalculate_grades = True
template_dir_name = "combinedopenended" template_dir_name = "combinedopenended"
#Specify whether or not to pass in S3 interface
needs_s3_interface = True
#Specify whether or not to pass in open ended interface
needs_open_ended_interface = True
metadata_attributes = RawDescriptor.metadata_attributes
js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/edit.coffee')]}
js_module_name = "OpenEndedMarkdownEditingDescriptor"
css = {'scss': [resource_string(__name__, 'css/editor/edit.scss'), resource_string(__name__, 'css/combinedopenended/edit.scss')]}
def get_context(self):
_context = RawDescriptor.get_context(self)
_context.update({'markdown': self.markdown,
'enable_markdown': self.markdown is not None})
return _context
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(CombinedOpenEndedDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([CombinedOpenEndedDescriptor.due, CombinedOpenEndedDescriptor.graceperiod,
CombinedOpenEndedDescriptor.markdown])
return non_editable_fields
.editor-bar {
.editor-tabs {
.advanced-toggle {
@include white-button;
height: auto;
margin-top: -1px;
padding: 3px 9px;
font-size: 12px;
&.current {
border: 1px solid $lightGrey !important;
border-radius: 3px !important;
background: $lightGrey !important;
color: $darkGrey !important;
pointer-events: none;
cursor: none;
&:hover {
box-shadow: 0 0 0 0 !important;
}
}
}
.cheatsheet-toggle {
width: 21px;
height: 21px;
padding: 0;
margin: 0 5px 0 15px;
border-radius: 22px;
border: 1px solid #a5aaaf;
background: #e5ecf3;
font-size: 13px;
font-weight: 700;
color: #565d64;
text-align: center;
}
}
}
.simple-editor-open-ended-cheatsheet {
position: absolute;
top: 0;
left: 100%;
width: 0;
border-radius: 0 3px 3px 0;
@include linear-gradient(left, rgba(0, 0, 0, .1), rgba(0, 0, 0, 0) 4px);
background-color: #fff;
overflow: hidden;
@include transition(width .3s);
&.shown {
width: 300px;
height: 100%;
overflow-y: scroll;
}
.cheatsheet-wrapper {
width: 240px;
padding: 20px 30px;
}
h6 {
margin-bottom: 7px;
font-size: 15px;
font-weight: 700;
}
.row {
@include clearfix;
padding-bottom: 5px !important;
margin-bottom: 10px !important;
border-bottom: 1px solid #ddd !important;
&:last-child {
border-bottom: none !important;
margin-bottom: 0 !important;
}
}
.col {
float: left;
&.sample {
width: 60px;
margin-right: 30px;
}
}
pre {
font-size: 12px;
line-height: 18px;
}
code {
padding: 0;
background: none;
}
}
.combinedopenended-editor-icon {
display: inline-block;
vertical-align: middle;
color: #333;
}
<section class="combinedopenended-editor editor">
<div class="row">
<textarea class="markdown-box">markdown</textarea>
<textarea class="xml-box" rows="8" cols="40">xml</textarea>
</div>
</section>
<section class="combinedopenended-editor editor">
<div class="row">
<textarea class="xml-box" rows="8" cols="40">xml only</textarea>
</div>
</section>
describe 'OpenEndedMarkdownEditingDescriptor', ->
describe 'save stores the correct data', ->
it 'saves markdown from markdown editor', ->
loadFixtures 'combinedopenended-with-markdown.html'
@descriptor = new OpenEndedMarkdownEditingDescriptor($('.combinedopenended-editor'))
saveResult = @descriptor.save()
expect(saveResult.metadata.markdown).toEqual('markdown')
expect(saveResult.data).toEqual('<combinedopenended>\nmarkdown\n</combinedopenended>')
it 'clears markdown when xml editor is selected', ->
loadFixtures 'combinedopenended-with-markdown.html'
@descriptor = new OpenEndedMarkdownEditingDescriptor($('.combinedopenended-editor'))
@descriptor.createXMLEditor('replace with markdown')
saveResult = @descriptor.save()
expect(saveResult.metadata.markdown).toEqual(null)
expect(saveResult.data).toEqual('replace with markdown')
it 'saves xml from the xml editor', ->
loadFixtures 'combinedopenended-without-markdown.html'
@descriptor = new OpenEndedMarkdownEditingDescriptor($('.combinedopenended-editor'))
saveResult = @descriptor.save()
expect(saveResult.metadata.markdown).toEqual(null)
expect(saveResult.data).toEqual('xml only')
describe 'insertPrompt', ->
it 'inserts the template if selection is empty', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertPrompt('')
expect(revisedSelection).toEqual(OpenEndedMarkdownEditingDescriptor.promptTemplate)
it 'recognizes html in the prompt', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertPrompt('[prompt]<h1>Hello</h1>[prompt]')
expect(revisedSelection).toEqual('[prompt]<h1>Hello</h1>[prompt]')
describe 'insertRubric', ->
it 'inserts the template if selection is empty', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertRubric('')
expect(revisedSelection).toEqual(OpenEndedMarkdownEditingDescriptor.rubricTemplate)
it 'recognizes a proper rubric', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertRubric('[rubric]\n+1\n-1\n-2\n[rubric]')
expect(revisedSelection).toEqual('[rubric]\n+1\n-1\n-2\n[rubric]')
describe 'insertTasks', ->
it 'inserts the template if selection is empty', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertTasks('')
expect(revisedSelection).toEqual(OpenEndedMarkdownEditingDescriptor.tasksTemplate)
it 'recognizes a proper task string', ->
revisedSelection = OpenEndedMarkdownEditingDescriptor.insertTasks('[tasks](Self)[tasks]')
expect(revisedSelection).toEqual('[tasks](Self)[tasks]')
describe 'markdownToXml', ->
# test default templates
it 'converts prompt to xml', ->
data = OpenEndedMarkdownEditingDescriptor.markdownToXml("""[prompt]
<h1>Prompt!</h1>
This is my super awesome prompt.
[prompt]
""")
data = data.replace(/[\t\n\s]/gmi,'')
expect(data).toEqual("""
<combinedopenended>
<prompt>
<h1>Prompt!</h1>
This is my super awesome prompt.
</prompt>
</combinedopenended>
""".replace(/[\t\n\s]/gmi,''))
it 'converts rubric to xml', ->
data = OpenEndedMarkdownEditingDescriptor.markdownToXml("""[rubric]
+ 1
-1
-2
+ 2
-1
-2
+3
-1
-2
-3
[rubric]
""")
data = data.replace(/[\t\n\s]/gmi,'')
expect(data).toEqual("""
<combinedopenended>
<rubric>
<rubric>
<category>
<description>1</description>
<option>1</option>
<option>2</option>
</category>
<category>
<description>2</description>
<option>1</option>
<option>2</option>
</category>
<category>
<description>3</description>
<option>1</option>
<option>2</option>
<option>3</option>
</category>
</rubric>
</rubric>
</combinedopenended>
""".replace(/[\t\n\s]/gmi,''))
it 'converts tasks to xml', ->
data = OpenEndedMarkdownEditingDescriptor.markdownToXml("""[tasks]
(Self), ({1-2}AI), ({1-4}AI), ({1-2}Peer
[tasks]
""")
data = data.replace(/[\t\n\s]/gmi,'')
equality_list = """
<combinedopenended>
<task>
<selfassessment/>
</task>
<task>
<openended min_score_to_attempt="1" max_score_to_attempt="2">ml_grading.conf</openended>
</task>
<task>
<openended min_score_to_attempt="1" max_score_to_attempt="4">ml_grading.conf</openended>
</task>
<task>
<openended min_score_to_attempt="1" max_score_to_attempt="2">peer_grading.conf</openended>
</task>
</combinedopenended>
"""
expect(data).toEqual(equality_list.replace(/[\t\n\s]/gmi,''))
...@@ -9,7 +9,7 @@ import re ...@@ -9,7 +9,7 @@ import re
from collections import namedtuple from collections import namedtuple
from .exceptions import InvalidLocationError, InsufficientSpecificationError from .exceptions import InvalidLocationError, InsufficientSpecificationError
from xmodule.errortracker import ErrorLog, make_error_tracker from xmodule.errortracker import make_error_tracker
from bson.son import SON from bson.son import SON
log = logging.getLogger('mitx.' + 'modulestore') log = logging.getLogger('mitx.' + 'modulestore')
...@@ -64,7 +64,6 @@ class Location(_LocationBase): ...@@ -64,7 +64,6 @@ class Location(_LocationBase):
""" """
return re.sub('_+', '_', invalid.sub('_', value)) return re.sub('_+', '_', invalid.sub('_', value))
@staticmethod @staticmethod
def clean(value): def clean(value):
""" """
...@@ -72,7 +71,6 @@ class Location(_LocationBase): ...@@ -72,7 +71,6 @@ class Location(_LocationBase):
""" """
return Location._clean(value, INVALID_CHARS) return Location._clean(value, INVALID_CHARS)
@staticmethod @staticmethod
def clean_keeping_underscores(value): def clean_keeping_underscores(value):
""" """
...@@ -82,7 +80,6 @@ class Location(_LocationBase): ...@@ -82,7 +80,6 @@ class Location(_LocationBase):
""" """
return INVALID_CHARS.sub('_', value) return INVALID_CHARS.sub('_', value)
@staticmethod @staticmethod
def clean_for_url_name(value): def clean_for_url_name(value):
""" """
...@@ -154,9 +151,7 @@ class Location(_LocationBase): ...@@ -154,9 +151,7 @@ class Location(_LocationBase):
to mean wildcard selection. to mean wildcard selection.
""" """
if (org is None and course is None and category is None and name is None and revision is None):
if (org is None and course is None and category is None and
name is None and revision is None):
location = loc_or_tag location = loc_or_tag
else: else:
location = (loc_or_tag, org, course, category, name, revision) location = (loc_or_tag, org, course, category, name, revision)
...@@ -191,7 +186,7 @@ class Location(_LocationBase): ...@@ -191,7 +186,7 @@ class Location(_LocationBase):
match = MISSING_SLASH_URL_RE.match(location) match = MISSING_SLASH_URL_RE.match(location)
if match is None: if match is None:
log.debug('location is instance of %s but no URL match' % basestring) log.debug('location is instance of %s but no URL match' % basestring)
raise InvalidLocationError(location) raise InvalidLocationError(location)
groups = match.groupdict() groups = match.groupdict()
check_dict(groups) check_dict(groups)
return _LocationBase.__new__(_cls, **groups) return _LocationBase.__new__(_cls, **groups)
...@@ -233,7 +228,7 @@ class Location(_LocationBase): ...@@ -233,7 +228,7 @@ class Location(_LocationBase):
html id attributes html id attributes
""" """
s = "-".join(str(v) for v in self.list() s = "-".join(str(v) for v in self.list()
if v is not None) if v is not None)
return Location.clean_for_html(s) return Location.clean_for_html(s)
def dict(self): def dict(self):
...@@ -258,6 +253,12 @@ class Location(_LocationBase): ...@@ -258,6 +253,12 @@ class Location(_LocationBase):
at the location URL hierachy""" at the location URL hierachy"""
return "/".join([self.org, self.course, self.name]) return "/".join([self.org, self.course, self.name])
def replace(self, **kwargs):
'''
Expose a public method for replacing location elements
'''
return self._replace(**kwargs)
class ModuleStore(object): class ModuleStore(object):
""" """
...@@ -382,12 +383,6 @@ class ModuleStore(object): ...@@ -382,12 +383,6 @@ class ModuleStore(object):
''' '''
raise NotImplementedError raise NotImplementedError
def get_course(self, course_id):
'''
Look for a specific course id. Returns the course descriptor, or None if not found.
'''
raise NotImplementedError
def get_parent_locations(self, location, course_id): def get_parent_locations(self, location, course_id):
'''Find all locations that are the parents of this location in this '''Find all locations that are the parents of this location in this
course. Needed for path_to_location(). course. Needed for path_to_location().
...@@ -406,8 +401,7 @@ class ModuleStore(object): ...@@ -406,8 +401,7 @@ class ModuleStore(object):
courses = [ courses = [
course course
for course in self.get_courses() for course in self.get_courses()
if course.location.org == location.org if course.location.org == location.org and course.location.course == location.course
and course.location.course == location.course
] ]
return courses return courses
......
...@@ -13,11 +13,12 @@ def as_draft(location): ...@@ -13,11 +13,12 @@ def as_draft(location):
""" """
return Location(location)._replace(revision=DRAFT) return Location(location)._replace(revision=DRAFT)
def as_published(location): def as_published(location):
""" """
Returns the Location that is the published version for `location` Returns the Location that is the published version for `location`
""" """
return Location(location)._replace(revision=None) return Location(location)._replace(revision=None)
def wrap_draft(item): def wrap_draft(item):
......
...@@ -3,7 +3,6 @@ from time import gmtime ...@@ -3,7 +3,6 @@ from time import gmtime
from uuid import uuid4 from uuid import uuid4
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.timeparse import stringify_time
from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.inheritance import own_metadata
......
...@@ -847,8 +847,8 @@ class CombinedOpenEndedV1Descriptor(): ...@@ -847,8 +847,8 @@ class CombinedOpenEndedV1Descriptor():
if len(xml_object.xpath(child)) == 0: if len(xml_object.xpath(child)) == 0:
#This is a staff_facing_error #This is a staff_facing_error
raise ValueError( raise ValueError(
"Combined Open Ended definition must include at least one '{0}' tag. Contact the learning sciences group for assistance.".format( "Combined Open Ended definition must include at least one '{0}' tag. Contact the learning sciences group for assistance. {1}".format(
child)) child, xml_object))
def parse_task(k): def parse_task(k):
"""Assumes that xml_object has child k""" """Assumes that xml_object has child k"""
......
...@@ -53,8 +53,9 @@ class GradingService(object): ...@@ -53,8 +53,9 @@ class GradingService(object):
except (RequestException, ConnectionError, HTTPError) as err: except (RequestException, ConnectionError, HTTPError) as err:
# reraise as promised GradingServiceError, but preserve stacktrace. # reraise as promised GradingServiceError, but preserve stacktrace.
#This is a dev_facing_error #This is a dev_facing_error
log.error("Problem posting data to the grading controller. URL: {0}, data: {1}".format(url, data)) error_string = "Problem posting data to the grading controller. URL: {0}, data: {1}".format(url, data)
raise GradingServiceError, str(err), sys.exc_info()[2] log.error(error_string)
raise GradingServiceError(error_string)
return r.text return r.text
...@@ -71,8 +72,9 @@ class GradingService(object): ...@@ -71,8 +72,9 @@ class GradingService(object):
except (RequestException, ConnectionError, HTTPError) as err: except (RequestException, ConnectionError, HTTPError) as err:
# reraise as promised GradingServiceError, but preserve stacktrace. # reraise as promised GradingServiceError, but preserve stacktrace.
#This is a dev_facing_error #This is a dev_facing_error
log.error("Problem getting data from the grading controller. URL: {0}, params: {1}".format(url, params)) error_string = "Problem getting data from the grading controller. URL: {0}, params: {1}".format(url, params)
raise GradingServiceError, str(err), sys.exc_info()[2] log.error(error_string)
raise GradingServiceError(error_string)
return r.text return r.text
......
...@@ -168,7 +168,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -168,7 +168,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
#This is a student_facing_error #This is a student_facing_error
return {'success': False, 'msg': "There was an error saving your feedback. Please contact course staff."} return {'success': False, 'msg': "There was an error saving your feedback. Please contact course staff."}
qinterface = system.xqueue['interface'] xqueue = system.get('xqueue')
if xqueue is None:
return {'success': False, 'msg': "Couldn't submit feedback."}
qinterface = xqueue['interface']
qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
anonymous_student_id = system.anonymous_student_id anonymous_student_id = system.anonymous_student_id
queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime + queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime +
...@@ -176,7 +179,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -176,7 +179,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
str(len(self.child_history))) str(len(self.child_history)))
xheader = xqueue_interface.make_xheader( xheader = xqueue_interface.make_xheader(
lms_callback_url=system.xqueue['construct_callback'](), lms_callback_url=xqueue['construct_callback'](),
lms_key=queuekey, lms_key=queuekey,
queue_name=self.message_queue_name queue_name=self.message_queue_name
) )
...@@ -219,7 +222,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -219,7 +222,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
# Prepare xqueue request # Prepare xqueue request
#------------------------------------------------------------ #------------------------------------------------------------
qinterface = system.xqueue['interface'] xqueue = system.get('xqueue')
if xqueue is None:
return False
qinterface = xqueue['interface']
qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
anonymous_student_id = system.anonymous_student_id anonymous_student_id = system.anonymous_student_id
...@@ -230,7 +236,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -230,7 +236,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
str(len(self.child_history))) str(len(self.child_history)))
xheader = xqueue_interface.make_xheader( xheader = xqueue_interface.make_xheader(
lms_callback_url=system.xqueue['construct_callback'](), lms_callback_url=xqueue['construct_callback'](),
lms_key=queuekey, lms_key=queuekey,
queue_name=self.queue_name queue_name=self.queue_name
) )
......
...@@ -587,3 +587,6 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor): ...@@ -587,3 +587,6 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor):
has_score = True has_score = True
always_recalculate_grades = True always_recalculate_grades = True
template_dir_name = "peer_grading" template_dir_name = "peer_grading"
#Specify whether or not to pass in open ended interface
needs_open_ended_interface = True
...@@ -4,6 +4,8 @@ import random ...@@ -4,6 +4,8 @@ import random
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.seq_module import SequenceDescriptor from xmodule.seq_module import SequenceDescriptor
from lxml import etree
from xblock.core import Scope, Integer from xblock.core import Scope, Integer
log = logging.getLogger('mitx.' + __name__) log = logging.getLogger('mitx.' + __name__)
......
...@@ -7,6 +7,7 @@ metadata: ...@@ -7,6 +7,7 @@ metadata:
skip_spelling_checks: False skip_spelling_checks: False
accept_file_upload: False accept_file_upload: False
weight: "" weight: ""
markdown: ""
data: | data: |
<combinedopenended> <combinedopenended>
<rubric> <rubric>
...@@ -39,5 +40,4 @@ data: | ...@@ -39,5 +40,4 @@ data: |
</task> </task>
</combinedopenended> </combinedopenended>
children: [] children: []
...@@ -407,7 +407,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): ...@@ -407,7 +407,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
self.assertTrue(changed) self.assertTrue(changed)
def test_get_max_score(self): def test_get_max_score(self):
changed = self.combinedoe.update_task_states() self.combinedoe.update_task_states()
self.combinedoe.state = "done" self.combinedoe.state = "done"
self.combinedoe.is_scored = True self.combinedoe.is_scored = True
max_score = self.combinedoe.max_score() max_score = self.combinedoe.max_score()
...@@ -611,11 +611,11 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore): ...@@ -611,11 +611,11 @@ class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore):
self.assertEqual(module.current_task_number, 1) self.assertEqual(module.current_task_number, 1)
#Get html and other data client will request #Get html and other data client will request
html = module.get_html() module.get_html()
legend = module.handle_ajax("get_legend", {}) legend = module.handle_ajax("get_legend", {})
self.assertTrue(isinstance(legend, basestring)) self.assertTrue(isinstance(legend, basestring))
status = module.handle_ajax("get_status", {}) module.handle_ajax("get_status", {})
module.handle_ajax("skip_post_assessment", {}) module.handle_ajax("skip_post_assessment", {})
self.assertTrue(isinstance(legend, basestring)) self.assertTrue(isinstance(legend, basestring))
......
...@@ -136,6 +136,7 @@ class XmlDescriptor(XModuleDescriptor): ...@@ -136,6 +136,7 @@ class XmlDescriptor(XModuleDescriptor):
'hide_progress_tab': bool_map, 'hide_progress_tab': bool_map,
'allow_anonymous': bool_map, 'allow_anonymous': bool_map,
'allow_anonymous_to_peers': bool_map, 'allow_anonymous_to_peers': bool_map,
'show_timezone': bool_map,
} }
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
<script src="{% static 'jasmine-latest/jasmine-html.js' %}"></script> <script src="{% static 'jasmine-latest/jasmine-html.js' %}"></script>
<script src="{% static 'js/vendor/jasmine-jquery.js' %}"></script> <script src="{% static 'js/vendor/jasmine-jquery.js' %}"></script>
<script src="{% static 'console-runner.js' %}"></script> <script src="{% static 'console-runner.js' %}"></script>
<script src="{% static 'jasmine.junit_reporter.js' %}"></script>
{% load compressed %} {% load compressed %}
{# static files #} {# static files #}
...@@ -37,15 +38,14 @@ ...@@ -37,15 +38,14 @@
<script> <script>
{% block jasmine %} {% block jasmine %}
var console_reporter = new jasmine.ConsoleReporter();
(function() { (function() {
var jasmineEnv = jasmine.getEnv(); var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000; jasmineEnv.updateInterval = 1000;
var trivialReporter = new jasmine.TrivialReporter(); var trivialReporter = new jasmine.TrivialReporter()
jasmineEnv.addReporter(trivialReporter); jasmineEnv.addReporter(trivialReporter);
jasmine.getEnv().addReporter(console_reporter); jasmineEnv.addReporter(new jasmine.ConsoleReporter());
jasmineEnv.addReporter(new jasmine.JUnitXmlReporter('{{ JASMINE_REPORT_DIR }}/'));
jasmineEnv.specFilter = function(spec) { jasmineEnv.specFilter = function(spec) {
return trivialReporter.specFilter(spec); return trivialReporter.specFilter(spec);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
<script type="text/javascript" src="<%= phantom_jasmine_path %>/vendor/jasmine-1.2.0/jasmine-html.js"></script> <script type="text/javascript" src="<%= phantom_jasmine_path %>/vendor/jasmine-1.2.0/jasmine-html.js"></script>
<script type="text/javascript" src="<%= phantom_jasmine_path %>/lib/console-runner.js"></script> <script type="text/javascript" src="<%= phantom_jasmine_path %>/lib/console-runner.js"></script>
<script type="text/javascript" src="<%= jasmine_reporters_path %>/src/jasmine.junit_reporter.js"></script>
<script type="text/javascript" src="<%= common_coffee_root %>/ajax_prefix.js"></script> <script type="text/javascript" src="<%= common_coffee_root %>/ajax_prefix.js"></script>
<script type="text/javascript" src="<%= common_coffee_root %>/logger.js"></script> <script type="text/javascript" src="<%= common_coffee_root %>/logger.js"></script>
<script type="text/javascript" src="<%= common_js_root %>/vendor/jasmine-jquery.js"></script> <script type="text/javascript" src="<%= common_js_root %>/vendor/jasmine-jquery.js"></script>
...@@ -44,30 +45,10 @@ ...@@ -44,30 +45,10 @@
<body> <body>
<script type="text/javascript"> <script type="text/javascript">
var jasmineEnv = jasmine.getEnv(); jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
jasmine.getEnv().addReporter(new jasmine.ConsoleReporter());
var htmlReporter = new jasmine.HtmlReporter(); jasmine.getEnv().addReporter(new jasmine.JUnitXmlReporter('<%= report_dir %>/'));
var console_reporter = new jasmine.ConsoleReporter() jasmine.getEnv().execute();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.addReporter(console_reporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
</script> </script>
</body> </body>
......
<course filename="6.002_Spring_2012" slug="6.002_Spring_2012" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never" name="6.002 Spring 2012" start="2015-07-17T12:00" course="full" org="edX" /> <course filename="6.002_Spring_2012" slug="6.002_Spring_2012" graceperiod="1 day 12 hours 59 minutes 59 seconds" showanswer="attempted" rerandomize="never" name="6.002 Spring 2012" start="2015-07-17T12:00" course="full" org="edX" show_timezone="true"/>
...@@ -12,4 +12,13 @@ ...@@ -12,4 +12,13 @@
<html slug="html_95">Minor correction: Six elements (five resistors)…</html> <html slug="html_95">Minor correction: Six elements (five resistors)…</html>
<customtag tag="S1" slug="discuss_96" impl="discuss"/> <customtag tag="S1" slug="discuss_96" impl="discuss"/>
</vertical> </vertical>
<randomize url_name="PS1_Q4" display_name="Problem 4: Read a Molecule">
<vertical>
<html slug="html_900">
<!-- UTF-8 characters are acceptable… HTML entities are not -->
<h1>Inline content…</h1>
</html>
</vertical>
</randomize>
</sequential> </sequential>
phantom-jasmine @ a54d435b
Subproject commit a54d435b5556650efbcdb0490e6c7928ac75238a
...@@ -8,7 +8,7 @@ and acceptance tests. ...@@ -8,7 +8,7 @@ and acceptance tests.
### Unit Tests ### Unit Tests
* Each test case should be concise: setup, execute, check, and teardown. * Each test case should be concise: setup, execute, check, and teardown.
If you find yourself writing tests with many steps, consider refactoring If you find yourself writing tests with many steps, consider refactoring
the unit under tests into smaller units, and then testing those individually. the unit under tests into smaller units, and then testing those individually.
* As a rule of thumb, your unit tests should cover every code branch. * As a rule of thumb, your unit tests should cover every code branch.
...@@ -16,19 +16,19 @@ the unit under tests into smaller units, and then testing those individually. ...@@ -16,19 +16,19 @@ the unit under tests into smaller units, and then testing those individually.
* Mock or patch external dependencies. * Mock or patch external dependencies.
We use [voidspace mock](http://www.voidspace.org.uk/python/mock/). We use [voidspace mock](http://www.voidspace.org.uk/python/mock/).
* We unit test Python code (using [unittest](http://docs.python.org/2/library/unittest.html)) and * We unit test Python code (using [unittest](http://docs.python.org/2/library/unittest.html)) and
Javascript (using [Jasmine](http://pivotal.github.io/jasmine/)) Javascript (using [Jasmine](http://pivotal.github.io/jasmine/))
### Integration Tests ### Integration Tests
* Test several units at the same time. * Test several units at the same time.
Note that you can still mock or patch dependencies Note that you can still mock or patch dependencies
that are not under test! For example, you might test that that are not under test! For example, you might test that
`LoncapaProblem`, `NumericalResponse`, and `CorrectMap` in the `LoncapaProblem`, `NumericalResponse`, and `CorrectMap` in the
`capa` package work together, while still mocking out template rendering. `capa` package work together, while still mocking out template rendering.
* Use integration tests to ensure that units are hooked up correctly. * Use integration tests to ensure that units are hooked up correctly.
You do not need to test every possible input--that's what unit You do not need to test every possible input--that's what unit
tests are for. Instead, focus on testing the "happy path" tests are for. Instead, focus on testing the "happy path"
to verify that the components work together correctly. to verify that the components work together correctly.
* Many of our tests use the [Django test client](https://docs.djangoproject.com/en/dev/topics/testing/overview/) to simulate * Many of our tests use the [Django test client](https://docs.djangoproject.com/en/dev/topics/testing/overview/) to simulate
...@@ -43,8 +43,8 @@ these tests simulate user interactions through the browser using ...@@ -43,8 +43,8 @@ these tests simulate user interactions through the browser using
Overall, you want to write the tests that **maximize coverage** Overall, you want to write the tests that **maximize coverage**
while **minimizing maintenance**. while **minimizing maintenance**.
In practice, this usually means investing heavily In practice, this usually means investing heavily
in unit tests, which tend to be the most robust to changes in the code base. in unit tests, which tend to be the most robust to changes in the code base.
![Test Pyramid](test_pyramid.png) ![Test Pyramid](test_pyramid.png)
...@@ -53,13 +53,13 @@ and acceptance tests. Most of our tests are unit tests or integration tests. ...@@ -53,13 +53,13 @@ and acceptance tests. Most of our tests are unit tests or integration tests.
## Test Locations ## Test Locations
* Python unit and integration tests: Located in * Python unit and integration tests: Located in
subpackages called `tests`. subpackages called `tests`.
For example, the tests for the `capa` package are located in For example, the tests for the `capa` package are located in
`common/lib/capa/capa/tests`. `common/lib/capa/capa/tests`.
* Javascript unit tests: Located in `spec` folders. For example, * Javascript unit tests: Located in `spec` folders. For example,
`common/lib/xmodule/xmodule/js/spec` and `{cms,lms}/static/coffee/spec` `common/lib/xmodule/xmodule/js/spec` and `{cms,lms}/static/coffee/spec`
For consistency, you should use the same directory structure for implementation For consistency, you should use the same directory structure for implementation
and test. For example, the test for `src/views/module.coffee` and test. For example, the test for `src/views/module.coffee`
should be written in `spec/views/module_spec.coffee`. should be written in `spec/views/module_spec.coffee`.
...@@ -88,7 +88,7 @@ because the `capa` package handles problem XML. ...@@ -88,7 +88,7 @@ because the `capa` package handles problem XML.
Before running tests, ensure that you have all the dependencies. You can install dependencies using: Before running tests, ensure that you have all the dependencies. You can install dependencies using:
pip install -r requirements.txt rake install_prereqs
## Running Python Unit tests ## Running Python Unit tests
...@@ -101,7 +101,7 @@ You can run tests using `rake` commands. For example, ...@@ -101,7 +101,7 @@ You can run tests using `rake` commands. For example,
rake test rake test
runs all the tests. It also runs `collectstatic`, which prepares the static files used by the site (for example, compiling Coffeescript to Javascript). runs all the tests. It also runs `collectstatic`, which prepares the static files used by the site (for example, compiling Coffeescript to Javascript).
You can also run the tests without `collectstatic`, which tends to be faster: You can also run the tests without `collectstatic`, which tends to be faster:
...@@ -117,12 +117,11 @@ xmodule can be tested independently, with this: ...@@ -117,12 +117,11 @@ xmodule can be tested independently, with this:
To run a single django test class: To run a single django test class:
django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/courseware/tests/tests.py:TestViewAuth rake test_lms[courseware.tests.tests:testViewAuth]
To run a single django test: To run a single django test:
django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/courseware/tests/tests.py:TestViewAuth.test_dark_launch rake test_lms[courseware.tests.tests:TestViewAuth.test_dark_launch]
To run a single nose test file: To run a single nose test file:
...@@ -150,7 +149,7 @@ If the `phantomjs` binary is not on the path, set the `PHANTOMJS_PATH` environme ...@@ -150,7 +149,7 @@ If the `phantomjs` binary is not on the path, set the `PHANTOMJS_PATH` environme
PHANTOMJS_PATH=/path/to/phantomjs rake phantomjs_jasmine_{lms,cms} PHANTOMJS_PATH=/path/to/phantomjs rake phantomjs_jasmine_{lms,cms}
Once you have run the `rake` command, your browser should open to Once you have run the `rake` command, your browser should open to
to `http://localhost/_jasmine/`, which displays the test results. to `http://localhost/_jasmine/`, which displays the test results.
**Troubleshooting**: If you get an error message while running the `rake` task, **Troubleshooting**: If you get an error message while running the `rake` task,
...@@ -163,7 +162,7 @@ Most of our tests use [Splinter](http://splinter.cobrateam.info/) ...@@ -163,7 +162,7 @@ Most of our tests use [Splinter](http://splinter.cobrateam.info/)
to simulate UI browser interactions. Splinter, in turn, to simulate UI browser interactions. Splinter, in turn,
uses [Selenium](http://docs.seleniumhq.org/) to control the Chrome browser. uses [Selenium](http://docs.seleniumhq.org/) to control the Chrome browser.
**Prerequisite**: You must have [ChromeDriver](https://code.google.com/p/selenium/wiki/ChromeDriver) **Prerequisite**: You must have [ChromeDriver](https://code.google.com/p/selenium/wiki/ChromeDriver)
installed to run the tests in Chrome. The tests are confirmed to run installed to run the tests in Chrome. The tests are confirmed to run
with Chrome (not Chromium) version 26.0.0.1410.63 with ChromeDriver with Chrome (not Chromium) version 26.0.0.1410.63 with ChromeDriver
version r195636. version r195636.
...@@ -184,13 +183,7 @@ To start the debugger on failure, add the `--pdb` option: ...@@ -184,13 +183,7 @@ To start the debugger on failure, add the `--pdb` option:
To run tests faster by not collecting static files, you can use To run tests faster by not collecting static files, you can use
`rake fasttest_acceptance_lms` and `rake fasttest_acceptance_cms`. `rake fasttest_acceptance_lms` and `rake fasttest_acceptance_cms`.
**Note**: The acceptance tests can *not* currently run in parallel.
**Troubleshooting**: If you get an error message that says something about harvest not being a command, you probably are missing a requirement.
Try running:
pip install -r requirements.txt
**Note**: The acceptance tests can *not* currently run in parallel.
## Viewing Test Coverage ## Viewing Test Coverage
......
...@@ -73,8 +73,8 @@ rake pylint > pylint.log || cat pylint.log ...@@ -73,8 +73,8 @@ rake pylint > pylint.log || cat pylint.log
TESTS_FAILED=0 TESTS_FAILED=0
# Run the python unit tests # Run the python unit tests
rake test_cms[false] || TESTS_FAILED=1 rake test_cms || TESTS_FAILED=1
rake test_lms[false] || TESTS_FAILED=1 rake test_lms || TESTS_FAILED=1
rake test_common/lib/capa || TESTS_FAILED=1 rake test_common/lib/capa || TESTS_FAILED=1
rake test_common/lib/xmodule || TESTS_FAILED=1 rake test_common/lib/xmodule || TESTS_FAILED=1
...@@ -82,7 +82,7 @@ rake test_common/lib/xmodule || TESTS_FAILED=1 ...@@ -82,7 +82,7 @@ rake test_common/lib/xmodule || TESTS_FAILED=1
rake phantomjs_jasmine_lms || TESTS_FAILED=1 rake phantomjs_jasmine_lms || TESTS_FAILED=1
rake phantomjs_jasmine_cms || TESTS_FAILED=1 rake phantomjs_jasmine_cms || TESTS_FAILED=1
rake phantomjs_jasmine_common/lib/xmodule || TESTS_FAILED=1 rake phantomjs_jasmine_common/lib/xmodule || TESTS_FAILED=1
rake phantomjs_jasmine_discussion || TESTS_FAILED=1 rake phantomjs_jasmine_common/static/coffee || TESTS_FAILED=1
rake coverage:xml coverage:html rake coverage:xml coverage:html
......
...@@ -214,22 +214,27 @@ def get_module_for_descriptor(user, request, descriptor, model_data_cache, cours ...@@ -214,22 +214,27 @@ def get_module_for_descriptor(user, request, descriptor, model_data_cache, cours
#This is a hacky way to pass settings to the combined open ended xmodule #This is a hacky way to pass settings to the combined open ended xmodule
#It needs an S3 interface to upload images to S3 #It needs an S3 interface to upload images to S3
#It needs the open ended grading interface in order to get peer grading to be done #It needs the open ended grading interface in order to get peer grading to be done
#TODO: refactor these settings into module-specific settings when possible.
#this first checks to see if the descriptor is the correct one, and only sends settings if it is #this first checks to see if the descriptor is the correct one, and only sends settings if it is
is_descriptor_combined_open_ended = (descriptor.__class__.__name__ == 'CombinedOpenEndedDescriptor')
is_descriptor_peer_grading = (descriptor.__class__.__name__ == 'PeerGradingDescriptor') #Get descriptor metadata fields indicating needs for various settings
needs_open_ended_interface = getattr(descriptor, "needs_open_ended_interface", False)
needs_s3_interface = getattr(descriptor, "needs_s3_interface", False)
#Initialize interfaces to None
open_ended_grading_interface = None open_ended_grading_interface = None
s3_interface = None s3_interface = None
if is_descriptor_combined_open_ended or is_descriptor_peer_grading:
#Create interfaces if needed
if needs_open_ended_interface:
open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
open_ended_grading_interface['mock_peer_grading'] = settings.MOCK_PEER_GRADING open_ended_grading_interface['mock_peer_grading'] = settings.MOCK_PEER_GRADING
open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING
if is_descriptor_combined_open_ended: if needs_s3_interface:
s3_interface = { s3_interface = {
'access_key' : getattr(settings,'AWS_ACCESS_KEY_ID',''), 'access_key': getattr(settings, 'AWS_ACCESS_KEY_ID', ''),
'secret_access_key' : getattr(settings,'AWS_SECRET_ACCESS_KEY',''), 'secret_access_key': getattr(settings, 'AWS_SECRET_ACCESS_KEY', ''),
'storage_bucket_name' : getattr(settings,'AWS_STORAGE_BUCKET_NAME','openended') 'storage_bucket_name': getattr(settings, 'AWS_STORAGE_BUCKET_NAME', 'openended')
} }
def inner_get_module(descriptor): def inner_get_module(descriptor):
""" """
......
...@@ -26,7 +26,7 @@ from course_groups.cohorts import get_cohort_id, is_commentable_cohorted ...@@ -26,7 +26,7 @@ from course_groups.cohorts import get_cohort_id, is_commentable_cohorted
from django_comment_client.utils import JsonResponse, JsonError, extract, get_courseware_context from django_comment_client.utils import JsonResponse, JsonError, extract, get_courseware_context
from django_comment_client.permissions import check_permissions_by_view, cached_has_permission from django_comment_client.permissions import check_permissions_by_view, cached_has_permission
from django_comment_client.models import Role from django_comment_common.models import Role
from courseware.access import has_access from courseware.access import has_access
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
......
from optparse import make_option from optparse import make_option
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django_comment_client.models import Role from django_comment_common.models import Role
from django.contrib.auth.models import User from django.contrib.auth.models import User
......
...@@ -7,7 +7,7 @@ Enrollments. ...@@ -7,7 +7,7 @@ Enrollments.
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from student.models import CourseEnrollment from student.models import CourseEnrollment
from django_comment_client.models import assign_default_role from django_comment_common.models import assign_default_role
class Command(BaseCommand): class Command(BaseCommand):
......
...@@ -7,7 +7,7 @@ Enrollments. ...@@ -7,7 +7,7 @@ Enrollments.
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from student.models import CourseEnrollment from student.models import CourseEnrollment
from django_comment_client.models import assign_default_role from django_comment_common.models import assign_default_role
class Command(BaseCommand): class Command(BaseCommand):
......
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django_comment_client.models import Role from django_comment_common.utils import seed_permissions_roles
class Command(BaseCommand): class Command(BaseCommand):
...@@ -13,26 +13,4 @@ class Command(BaseCommand): ...@@ -13,26 +13,4 @@ class Command(BaseCommand):
raise CommandError("Too many arguments") raise CommandError("Too many arguments")
course_id = args[0] course_id = args[0]
administrator_role = Role.objects.get_or_create(name="Administrator", course_id=course_id)[0] seed_permissions_roles(course_id)
moderator_role = Role.objects.get_or_create(name="Moderator", course_id=course_id)[0]
community_ta_role = Role.objects.get_or_create(name="Community TA", course_id=course_id)[0]
student_role = Role.objects.get_or_create(name="Student", course_id=course_id)[0]
for per in ["vote", "update_thread", "follow_thread", "unfollow_thread",
"update_comment", "create_sub_comment", "unvote", "create_thread",
"follow_commentable", "unfollow_commentable", "create_comment", ]:
student_role.add_permission(per)
for per in ["edit_content", "delete_thread", "openclose_thread",
"endorse_comment", "delete_comment", "see_all_cohorts"]:
moderator_role.add_permission(per)
for per in ["manage_moderator"]:
administrator_role.add_permission(per)
moderator_role.inherit_permissions(student_role)
# For now, Community TA == Moderator, except for the styling.
community_ta_role.inherit_permissions(moderator_role)
administrator_role.inherit_permissions(moderator_role)
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django_comment_common.models import Permission, Role
from django.contrib.auth.models import User from django.contrib.auth.models import User
......
import logging # This file is intentionally blank. It has been moved to common/djangoapps/django_comment_common
from django.db import models
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from student.models import CourseEnrollment
from courseware.courses import get_course_by_id
FORUM_ROLE_ADMINISTRATOR = 'Administrator'
FORUM_ROLE_MODERATOR = 'Moderator'
FORUM_ROLE_COMMUNITY_TA = 'Community TA'
FORUM_ROLE_STUDENT = 'Student'
@receiver(post_save, sender=CourseEnrollment)
def assign_default_role(sender, instance, **kwargs):
if instance.user.is_staff:
role = Role.objects.get_or_create(course_id=instance.course_id, name="Moderator")[0]
else:
role = Role.objects.get_or_create(course_id=instance.course_id, name="Student")[0]
logging.info("assign_default_role: adding %s as %s" % (instance.user, role))
instance.user.roles.add(role)
class Role(models.Model):
name = models.CharField(max_length=30, null=False, blank=False)
users = models.ManyToManyField(User, related_name="roles")
course_id = models.CharField(max_length=255, blank=True, db_index=True)
def __unicode__(self):
return self.name + " for " + (self.course_id if self.course_id else "all courses")
def inherit_permissions(self, role): # TODO the name of this method is a little bit confusing,
# since it's one-off and doesn't handle inheritance later
if role.course_id and role.course_id != self.course_id:
logging.warning("%s cannot inherit permissions from %s due to course_id inconsistency",
self, role)
for per in role.permissions.all():
self.add_permission(per)
def add_permission(self, permission):
self.permissions.add(Permission.objects.get_or_create(name=permission)[0])
def has_permission(self, permission):
course = get_course_by_id(self.course_id)
if self.name == FORUM_ROLE_STUDENT and \
(permission.startswith('edit') or permission.startswith('update') or permission.startswith('create')) and \
(not course.forum_posts_allowed):
return False
return self.permissions.filter(name=permission).exists()
class Permission(models.Model):
name = models.CharField(max_length=30, null=False, blank=False, primary_key=True)
roles = models.ManyToManyField(Role, related_name="permissions")
def __unicode__(self):
return self.name
from .models import Role, Permission from django_comment_common.models import Role, Permission
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 student.models import CourseEnrollment from student.models import CourseEnrollment
......
...@@ -6,7 +6,7 @@ from django.test import TestCase ...@@ -6,7 +6,7 @@ from django.test import TestCase
from student.models import CourseEnrollment from student.models import CourseEnrollment
from django_comment_client.permissions import has_permission from django_comment_client.permissions import has_permission
from django_comment_client.models import Role from django_comment_common.models import Role
class PermissionsTestCase(TestCase): class PermissionsTestCase(TestCase):
......
from factory import DjangoModelFactory from factory import DjangoModelFactory
from django_comment_client.models import Role, Permission from django_comment_common.models import Role, Permission
class RoleFactory(DjangoModelFactory): class RoleFactory(DjangoModelFactory):
......
import django_comment_client.models as models import django_comment_common.models as models
import django_comment_client.permissions as permissions import django_comment_client.permissions as permissions
from django.test import TestCase from django.test import TestCase
......
from django.test import TestCase from django.test import TestCase
from student.tests.factories import UserFactory, CourseEnrollmentFactory from student.tests.factories import UserFactory, CourseEnrollmentFactory
from django_comment_common.models import Role, Permission
from factories import RoleFactory from factories import RoleFactory
import django_comment_client.utils as utils import django_comment_client.utils as utils
......
...@@ -14,7 +14,7 @@ from django.core.urlresolvers import reverse ...@@ -14,7 +14,7 @@ from django.core.urlresolvers import reverse
from django.db import connection from django.db import connection
from django.http import HttpResponse from django.http import HttpResponse
from django.utils import simplejson from django.utils import simplejson
from django_comment_client.models import Role from django_comment_common.models import Role
from django_comment_client.permissions import check_permissions_by_view from django_comment_client.permissions import check_permissions_by_view
from xmodule.modulestore.exceptions import NoPathToItem from xmodule.modulestore.exceptions import NoPathToItem
......
...@@ -9,7 +9,7 @@ from django.test.utils import override_settings ...@@ -9,7 +9,7 @@ from django.test.utils import override_settings
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django_comment_client.models import Role, FORUM_ROLE_ADMINISTRATOR, \ from django_comment_common.models import Role, FORUM_ROLE_ADMINISTRATOR, \
FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_STUDENT FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_STUDENT
from django_comment_client.utils import has_forum_access from django_comment_client.utils import has_forum_access
......
...@@ -27,7 +27,7 @@ from courseware.access import (has_access, get_access_group_name, ...@@ -27,7 +27,7 @@ from courseware.access import (has_access, get_access_group_name,
course_beta_test_group_name) course_beta_test_group_name)
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from courseware.models import StudentModule from courseware.models import StudentModule
from django_comment_client.models import (Role, from django_comment_common.models import (Role,
FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_ADMINISTRATOR,
FORUM_ROLE_MODERATOR, FORUM_ROLE_MODERATOR,
FORUM_ROLE_COMMUNITY_TA) FORUM_ROLE_COMMUNITY_TA)
......
...@@ -700,8 +700,7 @@ INSTALLED_APPS = ( ...@@ -700,8 +700,7 @@ INSTALLED_APPS = (
# Discussion forums # Discussion forums
'django_comment_client', 'django_comment_client',
'django_comment_common',
# Student notes
'notes', 'notes',
) )
......
...@@ -36,7 +36,12 @@ PIPELINE_JS['spec'] = { ...@@ -36,7 +36,12 @@ PIPELINE_JS['spec'] = {
} }
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
JASMINE_REPORT_DIR = os.environ.get('JASMINE_REPORT_DIR', 'reports/lms/jasmine')
TEMPLATE_CONTEXT_PROCESSORS += ('settings_context_processor.context_processors.settings',)
TEMPLATE_VISIBLE_SETTINGS = ('JASMINE_REPORT_DIR', )
STATICFILES_DIRS.append(REPO_ROOT/'node_modules/phantom-jasmine/lib') STATICFILES_DIRS.append(REPO_ROOT/'node_modules/phantom-jasmine/lib')
STATICFILES_DIRS.append(REPO_ROOT/'node_modules/jasmine-reporters/src')
INSTALLED_APPS += ('django_jasmine', ) INSTALLED_APPS += ('django_jasmine', 'settings_context_processor')
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
// overflow-y: scroll; // overflow-y: scroll;
// } // }
body { html, body {
background: rgb(250,250,250); background: $body-bg;
font-family: $sans-serif; font-family: $sans-serif;
font-size: 1em; font-size: 1em;
font-style: normal; font-style: normal;
...@@ -61,20 +61,20 @@ p + p, ul + p, ol + p { ...@@ -61,20 +61,20 @@ p + p, ul + p, ol + p {
p { p {
a:link, a:visited { a:link, a:visited {
color: $blue; color: $link-color;
font: normal 1em/1em $serif; font: normal 1em/1em $serif;
text-decoration: none; text-decoration: none;
@include transition(all, 0.1s, linear); @include transition(all, 0.1s, linear);
&:hover { &:hover {
color: $blue; color: $link-color;
text-decoration: underline; text-decoration: underline;
} }
} }
} }
a:link, a:visited { a:link, a:visited {
color: $blue; color: $link-color;
font: normal 1em/1em $sans-serif; font: normal 1em/1em $sans-serif;
text-decoration: none; text-decoration: none;
@include transition(all, 0.1s, linear); @include transition(all, 0.1s, linear);
...@@ -87,8 +87,8 @@ a:link, a:visited { ...@@ -87,8 +87,8 @@ a:link, a:visited {
.content-wrapper { .content-wrapper {
width: flex-grid(12); width: flex-grid(12);
margin: 0 auto; margin: 0 auto;
background: $content-wrapper-bg;
padding-bottom: ($baseline*2); padding-bottom: ($baseline*2);
background: rgb(255,255,255);
} }
.container { .container {
...@@ -164,7 +164,7 @@ mark { ...@@ -164,7 +164,7 @@ mark {
display: none; display: none;
padding: 10px; padding: 10px;
@include linear-gradient(top, rgba(0, 0, 0, .1), rgba(0, 0, 0, .0)); @include linear-gradient(top, rgba(0, 0, 0, .1), rgba(0, 0, 0, .0));
background-color: $pink; background-color: $site-status-color;
box-shadow: 0 -1px 0 rgba(0, 0, 0, .3) inset; box-shadow: 0 -1px 0 rgba(0, 0, 0, .3) inset;
font-size: 14px; font-size: 14px;
......
.faded-hr-divider { .faded-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%, @include background-image($faded-hr-image-1);
rgba(200,200,200, 1) 50%,
rgba(200,200,200, 0)));
height: 1px; height: 1px;
width: 100%; width: 100%;
} }
.faded-hr-divider-medium { .faded-hr-divider-medium {
@include background-image(linear-gradient(180deg, rgba(240,240,240, 0) 0%, @include background-image($faded-hr-image-4);
rgba(240,240,240, 1) 50%,
rgba(240,240,240, 0)));
height: 1px; height: 1px;
width: 100%; width: 100%;
} }
.faded-hr-divider-light { .faded-hr-divider-light {
@include background-image(linear-gradient(180deg, rgba(255,255,255, 0) 0%, @include background-image($faded-hr-image-5);
rgba(255,255,255, 0.8) 50%,
rgba(255,255,255, 0)));
height: 1px; height: 1px;
width: 100%; width: 100%;
} }
.faded-vertical-divider { .faded-vertical-divider {
@include background-image(linear-gradient(90deg, rgba(200,200,200, 0) 0%, @include background-image($faded-hr-image-1);
rgba(200,200,200, 1) 50%,
rgba(200,200,200, 0)));
height: 100%; height: 100%;
width: 1px; width: 1px;
} }
.faded-vertical-divider-light { .faded-vertical-divider-light {
@include background-image(linear-gradient(90deg, rgba(255,255,255, 0) 0%, @include background-image($faded-hr-image-6);
rgba(255,255,255, 0.6) 50%, background: transparent;
rgba(255,255,255, 0)));
height: 100%; height: 100%;
width: 1px; width: 1px;
} }
...@@ -66,14 +57,12 @@ ...@@ -66,14 +57,12 @@
} }
.fade-right-hr-divider { .fade-right-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 0) 0%, @include background-image($faded-hr-image-2);
rgba(200,200,200, 1)));
border: none; border: none;
} }
.fade-left-hr-divider { .fade-left-hr-divider {
@include background-image(linear-gradient(180deg, rgba(200,200,200, 1) 0%, @include background-image($faded-hr-image-3);
rgba(200,200,200, 0)));
border: none; border: none;
} }
......
...@@ -14,6 +14,14 @@ $monospace: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', monospace; ...@@ -14,6 +14,14 @@ $monospace: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', monospace;
$body-font-family: $sans-serif; $body-font-family: $sans-serif;
$serif: $georgia; $serif: $georgia;
$body-font-size: em(14);
$body-line-height: golden-ratio(.875em, 1);
$base-font-color: rgb(60,60,60);
$baseFontColor: rgb(60,60,60);
$base-font-color: rgb(60,60,60);
$lighter-base-font-color: rgb(100,100,100);
$very-light-text: #fff;
$white: rgb(255,255,255); $white: rgb(255,255,255);
$black: rgb(0,0,0); $black: rgb(0,0,0);
$blue: rgb(29,157,217); $blue: rgb(29,157,217);
...@@ -52,6 +60,66 @@ $baseFontColor: rgb(60,60,60); ...@@ -52,6 +60,66 @@ $baseFontColor: rgb(60,60,60);
$lighter-base-font-color: rgb(100,100,100); $lighter-base-font-color: rgb(100,100,100);
$text-color: $dark-gray; $text-color: $dark-gray;
$body-font-family: $sans-serif; $body-bg: rgb(250,250,250);
$body-font-size: em(14); $header-image: linear-gradient(-90deg, rgba(255,255,255, 1), rgba(230,230,230, 0.9));
$body-line-height: golden-ratio(.875em, 1); $header-bg: transparent;
$courseware-header-image: linear-gradient(top, #fff, #eee);
$courseware-header-bg: transparent;
$footer-bg: transparent;
$courseware-footer-border: none;
$courseware-footer-shadow: none;
$courseware-footer-margin: 0px;
$button-bg-image: linear-gradient(#fff 0%, rgb(250,250,250) 50%, rgb(237,237,237) 50%, rgb(220,220,220) 100%);
$button-bg-color: transparent;
$button-bg-hover-color: #fff;
$faded-hr-image-1: linear-gradient(180deg, rgba(200,200,200, 0) 0%, rgba(200,200,200, 1) 50%, rgba(200,200,200, 0));
$faded-hr-image-2: linear-gradient(180deg, rgba(200,200,200, 0) 0%, rgba(200,200,200, 1));
$faded-hr-image-3: linear-gradient(180deg, rgba(200,200,200, 1) 0%, rgba(200,200,200, 0));
$faded-hr-image-4: linear-gradient(180deg, rgba(240,240,240, 0) 0%, rgba(240,240,240, 1) 50%, rgba(240,240,240, 0));
$faded-hr-image-5: linear-gradient(180deg, rgba(255,255,255, 0) 0%, rgba(255,255,255, 0.8) 50%, rgba(255,255,255, 0));
$faded-hr-image-6: linear-gradient(90deg, rgba(255,255,255, 0) 0%, rgba(255,255,255, 0.6) 50%, rgba(255,255,255, 0));
$dashboard-profile-header-image: linear-gradient(-90deg, rgb(255,255,255), rgb(245,245,245));
$dashboard-profile-header-color: transparent;
$dashboard-profile-color: rgb(252,252,252);
$dot-color: $light-gray;
$content-wrapper-bg: rgb(255,255,255);
$course-bg-color: #d6d6d6;
$course-bg-image: url(../images/bg-texture.png);
$course-profile-bg: rgb(245,245,245);
$course-header-bg: rgba(255,255,255, 0.93);
$border-color-1: rgb(190,190,190);
$border-color-2: rgb(200,200,200);
$border-color-3: rgb(100,100,100);
$border-color-4: rgb(252,252,252);
$link-color: $blue;
$link-hover: $pink;
$selection-color-1: $pink;
$selection-color-2: #444;
$site-status-color: $pink;
$button-color: $blue;
$button-archive-color: #eee;
$shadow-color: $blue;
$sidebar-chapter-bg-top: rgba(255, 255, 255, .6);
$sidebar-chapter-bg-bottom: rgba(255, 255, 255, 0);
$sidebar-chapter-bg: #eee;
$sidebar-active-image: linear-gradient(top, #e6e6e6, #d6d6d6);
$form-bg-color: #fff;
$modal-bg-color: rgb(245,245,245);
//-----------------
// CSS BG Images
//-----------------
$homepage-bg-image: '../images/homepage-bg.jpg';
$video-thumb-url: '../images/courses/video-thumb.jpg';
\ No newline at end of file
...@@ -117,7 +117,7 @@ div.info-wrapper { ...@@ -117,7 +117,7 @@ div.info-wrapper {
@include transition(all .2s); @include transition(all .2s);
h4 { h4 {
color: $blue; color: $link-color;
font-size: 1em; font-size: 1em;
font-weight: normal; font-weight: normal;
padding-left: 30px; padding-left: 30px;
......
body { body {
min-width: 980px; min-width: 980px;
min-height: 100%; min-height: 100%;
background: url(../images/bg-texture.png) #d6d6d6; background-image: $course-bg-image;
background-color: $course-bg-color;
} }
body, h1, h2, h3, h4, h5, h6, p, p a:link, p a:visited, a, label { body, h1, h2, h3, h4, h5, h6, p, p a:link, p a:visited, a, label {
...@@ -34,7 +35,7 @@ a { ...@@ -34,7 +35,7 @@ a {
width: 100%; width: 100%;
border-radius: 3px; border-radius: 3px;
border: 1px solid $outer-border-color; border: 1px solid $outer-border-color;
background: #fff; background: $body-bg;
@include box-shadow(0 1px 2px rgba(0, 0, 0, 0.05)); @include box-shadow(0 1px 2px rgba(0, 0, 0, 0.05));
} }
} }
...@@ -49,8 +50,8 @@ textarea, ...@@ -49,8 +50,8 @@ textarea,
input[type="text"], input[type="text"],
input[type="email"], input[type="email"],
input[type="password"] { input[type="password"] {
background: rgb(250,250,250); background: $body-bg;
border: 1px solid rgb(200,200,200); border: 1px solid $border-color-2;
@include border-radius(0); @include border-radius(0);
@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6), inset 0 0 3px 0 rgba(0,0,0, 0.1)); @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6), inset 0 0 3px 0 rgba(0,0,0, 0.1));
@include box-sizing(border-box); @include box-sizing(border-box);
...@@ -65,7 +66,7 @@ input[type="password"] { ...@@ -65,7 +66,7 @@ input[type="password"] {
} }
&:focus { &:focus {
border-color: lighten($blue, 20%); border-color: lighten($link-color, 20%);
@include box-shadow(0 0 6px 0 rgba($blue, 0.4), inset 0 0 4px 0 rgba(0,0,0, 0.15)); @include box-shadow(0 0 6px 0 rgba($blue, 0.4), inset 0 0 4px 0 rgba(0,0,0, 0.15));
outline: none; outline: none;
} }
...@@ -94,7 +95,7 @@ img { ...@@ -94,7 +95,7 @@ img {
} }
::selection, ::-moz-selection, ::-webkit-selection { ::selection, ::-moz-selection, ::-webkit-selection {
background: #444; background: $selection-color-2;
color: #fff; color: #fff;
} }
...@@ -143,7 +144,7 @@ img { ...@@ -143,7 +144,7 @@ img {
max-width: 350px; max-width: 350px;
padding: 15px 20px 17px; padding: 15px 20px 17px;
border-radius: 3px; border-radius: 3px;
border: 1px solid #333; border: 1px solid $border-color-3;
background: -webkit-linear-gradient(top, rgba(255, 255, 255, .1), rgba(255, 255, 255, 0)) rgba(30, 30, 30, .92); background: -webkit-linear-gradient(top, rgba(255, 255, 255, .1), rgba(255, 255, 255, 0)) rgba(30, 30, 30, .92);
box-shadow: 0 1px 3px rgba(0, 0, 0, .3), 0 1px 0 rgba(255, 255, 255, .1) inset; box-shadow: 0 1px 3px rgba(0, 0, 0, .3), 0 1px 0 rgba(255, 255, 255, .1) inset;
font-size: 13px; font-size: 13px;
......
h1.top-header { h1.top-header {
border-bottom: 1px solid #e3e3e3; border-bottom: 1px solid $border-color-2;
text-align: left; text-align: left;
font-size: em(24); font-size: em(24);
font-weight: 100; font-weight: 100;
......
...@@ -2,7 +2,7 @@ section.course-index { ...@@ -2,7 +2,7 @@ section.course-index {
@extend .sidebar; @extend .sidebar;
@extend .tran; @extend .tran;
@include border-radius(3px 0 0 3px); @include border-radius(3px 0 0 3px);
border-right: 1px solid #ddd; border-right: 1px solid $border-color-2;
#open_close_accordion { #open_close_accordion {
display: none; display: none;
...@@ -70,8 +70,8 @@ section.course-index { ...@@ -70,8 +70,8 @@ section.course-index {
width: 100% !important; width: 100% !important;
@include box-sizing(border-box); @include box-sizing(border-box);
padding: 11px 14px; padding: 11px 14px;
@include linear-gradient(top, rgba(255, 255, 255, .6), rgba(255, 255, 255, 0)); @include linear-gradient(top, $sidebar-chapter-bg-top, $sidebar-chapter-bg-bottom);
background-color: #eee; background-color: $sidebar-chapter-bg;
@include box-shadow(0 1px 0 #fff inset, 0 -1px 0 rgba(0, 0, 0, .1) inset); @include box-shadow(0 1px 0 #fff inset, 0 -1px 0 rgba(0, 0, 0, .1) inset);
@include transition(background-color .1s); @include transition(background-color .1s);
...@@ -169,9 +169,9 @@ section.course-index { ...@@ -169,9 +169,9 @@ section.course-index {
} }
> a { > a {
border: 1px solid #bbb; border: 1px solid $border-color-1;
@include box-shadow(0 1px 0 rgba(255, 255, 255, .35) inset); @include box-shadow(0 1px 0 rgba(255, 255, 255, .35) inset);
@include linear-gradient(top, #e6e6e6, #d6d6d6); background: $sidebar-active-image;
&:after { &:after {
opacity: 1; opacity: 1;
......
...@@ -75,9 +75,9 @@ header.global.slim { ...@@ -75,9 +75,9 @@ header.global.slim {
&#login { &#login {
display: block; display: block;
@include background-image(linear-gradient(-90deg, lighten($blue, 8%), lighten($blue, 5%) 50%, $blue 50%, darken($blue, 10%) 100%)); @include background-image(linear-gradient(-90deg, lighten($link-color, 8%), lighten($link-color, 5%) 50%, $link-color 50%, darken($link-color, 10%) 100%));
border: 1px solid transparent; border: 1px solid transparent;
border-color: darken($blue, 10%); border-color: darken($link-color, 10%);
@include border-radius(3px); @include border-radius(3px);
@include box-sizing(border-box); @include box-sizing(border-box);
@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6));
...@@ -97,7 +97,7 @@ header.global.slim { ...@@ -97,7 +97,7 @@ header.global.slim {
vertical-align: middle; vertical-align: middle;
&:hover, &.active { &:hover, &.active {
@include background-image(linear-gradient(-90deg, $blue, $blue 50%, $blue 50%, $blue 100%)); @include background-image(linear-gradient(-90deg, $link-color, $link-color 50%, $link-color 50%, $link-color 100%));
} }
} }
} }
......
footer { footer {
border: none; border: $courseware-footer-border;
box-shadow: none; box-shadow: $courseware-footer-shadow;
margin-top: $courseware-footer-margin;
} }
\ No newline at end of file
...@@ -113,7 +113,7 @@ section.wiki { ...@@ -113,7 +113,7 @@ section.wiki {
} }
&:focus { &:focus {
border-color: $blue; border-color: $link-color;
} }
} }
} }
...@@ -276,7 +276,7 @@ section.wiki { ...@@ -276,7 +276,7 @@ section.wiki {
li { li {
&.active { &.active {
a { a {
color: $blue; color: $link-color;
.icon-view, .icon-view,
.icon-home { .icon-home {
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
} }
header.course-profile { header.course-profile {
background: rgb(245,245,245); background: $course-profile-bg;
@include background-image(url('/static/images/homepage-bg.jpg')); @include background-image(url($homepage-bg-image));
background-size: cover; background-size: cover;
@include box-shadow(0 1px 80px 0 rgba(0,0,0, 0.5)); @include box-shadow(0 1px 80px 0 rgba(0,0,0, 0.5));
border-bottom: 1px solid rgb(100,100,100); border-bottom: 1px solid $border-color-3;
@include box-shadow(inset 0 1px 5px 0 rgba(0,0,0, 0.1)); @include box-shadow(inset 0 1px 5px 0 rgba(0,0,0, 0.1));
height: 280px; height: 280px;
margin-top: -69px; margin-top: -69px;
...@@ -18,8 +18,8 @@ ...@@ -18,8 +18,8 @@
width: 100%; width: 100%;
.intro-inner-wrapper { .intro-inner-wrapper {
background: rgba(255,255,255, 0.93); background: $course-header-bg;
border: 1px solid rgb(100,100,100); border: 1px solid $border-color-3;
@include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5)); @include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5));
@include box-sizing(border-box); @include box-sizing(border-box);
@include clearfix; @include clearfix;
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
z-index: 2; z-index: 2;
> hgroup { > hgroup {
border-bottom: 1px solid rgb(210,210,210); border-bottom: 1px solid $border-color-2;
@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6));
margin-bottom: 20px; margin-bottom: 20px;
padding-bottom: 20px; padding-bottom: 20px;
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
text-transform: none; text-transform: none;
&:hover { &:hover {
color: $blue; color: $link-color;
} }
} }
} }
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
text-transform: none; text-transform: none;
&:hover { &:hover {
color: $blue; color: $link-color;
} }
} }
} }
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
width: flex-grid(12); width: flex-grid(12);
> a.find-courses, a.register { > a.find-courses, a.register {
@include button(shiny, $blue); @include button(shiny, $button-color);
@include box-sizing(border-box); @include box-sizing(border-box);
@include border-radius(3px); @include border-radius(3px);
display: block; display: block;
...@@ -122,7 +122,7 @@ ...@@ -122,7 +122,7 @@
} }
strong { strong {
@include button(shiny, $blue); @include button(shiny, $button-color);
@include box-sizing(border-box); @include box-sizing(border-box);
@include border-radius(3px); @include border-radius(3px);
display: block; display: block;
...@@ -140,10 +140,10 @@ ...@@ -140,10 +140,10 @@
} }
span.register { span.register {
background: lighten($blue, 20%); background: $button-archive-color;
border: 1px solid $blue; border: 1px solid darken($button-archive-color, 50%);
@include box-sizing(border-box); @include box-sizing(border-box);
color: darken($blue, 20%); color: darken($button-archive-color, 50%);
display: block; display: block;
letter-spacing: 1px; letter-spacing: 1px;
padding: 10px 0px 8px; padding: 10px 0px 8px;
...@@ -176,7 +176,7 @@ ...@@ -176,7 +176,7 @@
z-index: 2; z-index: 2;
.hero { .hero {
border: 1px solid rgb(100,100,100); border: 1px solid $border-color-3;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
...@@ -235,7 +235,7 @@ ...@@ -235,7 +235,7 @@
@include clearfix; @include clearfix;
nav { nav {
border-bottom: 1px solid rgb(220,220,220); border-bottom: 1px solid $border-color-2;
@include box-sizing(border-box); @include box-sizing(border-box);
@include clearfix; @include clearfix;
margin: 40px 0; margin: 40px 0;
...@@ -262,7 +262,7 @@ ...@@ -262,7 +262,7 @@
} }
&:hover, &.active { &:hover, &.active {
border-color: rgb(200,200,200); border-color: $border-color-2;
color: $base-font-color; color: $base-font-color;
text-decoration: none; text-decoration: none;
} }
...@@ -296,7 +296,7 @@ ...@@ -296,7 +296,7 @@
.teacher-image { .teacher-image {
background: rgb(255,255,255); background: rgb(255,255,255);
border: 1px solid rgb(200,200,200); border: 1px solid $border-color-2;
height: 115px; height: 115px;
float: left; float: left;
margin: 0 15px 0px 0; margin: 0 15px 0px 0;
...@@ -351,7 +351,7 @@ ...@@ -351,7 +351,7 @@
> section { > section {
@include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.15)); @include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.15));
border: 1px solid rgb(200,200,200); border: 1px solid $border-color-2;
&.course-summary { &.course-summary {
padding: 16px 20px 30px; padding: 16px 20px 30px;
...@@ -401,7 +401,7 @@ ...@@ -401,7 +401,7 @@
} }
a.university-name { a.university-name {
border-right: 1px solid rgb(200,200,200); border-right: 1px solid $border-color-2;
color: $base-font-color; color: $base-font-color;
font-family: $sans-serif; font-family: $sans-serif;
font-style: italic; font-style: italic;
...@@ -498,12 +498,12 @@ ...@@ -498,12 +498,12 @@
li { li {
@include clearfix; @include clearfix;
border-bottom: 1px dotted rgb(220,220,220); border-bottom: 1px dotted $border-color-2;
margin-bottom: 20px; margin-bottom: 20px;
padding-bottom: 10px; padding-bottom: 10px;
&.prerequisites { &.prerequisites {
border: 1px solid rgb(220,220,220); border: 1px solid $border-color-2;
margin: 0 -10px 0; margin: 0 -10px 0;
padding: 10px; padding: 10px;
......
.find-courses, .university-profile { .find-courses, .university-profile {
background: rgb(252,252,252); background: $course-profile-bg;
padding-bottom: 60px; padding-bottom: 60px;
header.search { header.search {
background: rgb(240,240,240); background: $course-profile-bg;
background-size: cover; background-size: cover;
@include background-image(url($homepage-bg-image));
background-position: center top !important; background-position: center top !important;
border-bottom: 1px solid rgb(100,100,100); border-bottom: 1px solid $border-color-3;
@include box-shadow(inset 0 -1px 8px 0 rgba(0,0,0, 0.2), inset 0 1px 12px 0 rgba(0,0,0, 0.3)); @include box-shadow(inset 0 -1px 8px 0 rgba(0,0,0, 0.2), inset 0 1px 12px 0 rgba(0,0,0, 0.3));
height: 430px; height: 430px;
margin-top: -69px; margin-top: -69px;
...@@ -24,8 +25,8 @@ ...@@ -24,8 +25,8 @@
> hgroup { > hgroup {
background: #FFF; background: #FFF;
background: rgba(255,255,255, 0.93); background: $course-header-bg;
border: 1px solid rgb(100,100,100); border: 1px solid $border-color-3;
@include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5)); @include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5));
padding: 20px 30px; padding: 20px 30px;
position: relative; position: relative;
...@@ -83,7 +84,7 @@ ...@@ -83,7 +84,7 @@
} }
section.message { section.message {
border-top: 1px solid rgb(220,220,220); border-top: 1px solid $border-color-2;
@include clearfix; @include clearfix;
margin-top: 20px; margin-top: 20px;
padding-top: 60px; padding-top: 60px;
......
...@@ -30,8 +30,9 @@ ...@@ -30,8 +30,9 @@
width: flex-grid(3); width: flex-grid(3);
header.profile { header.profile {
@include background-image(linear-gradient(-90deg, rgb(255,255,255), rgb(245,245,245))); @include background-image($dashboard-profile-header-image);
border: 1px solid rgb(200,200,200); background-color: $dashboard-profile-header-color;
border: 1px solid $border-color-2;
@include border-radius(4px); @include border-radius(4px);
@include box-sizing(border-box); @include box-sizing(border-box);
width: flex-grid(12); width: flex-grid(12);
...@@ -53,8 +54,8 @@ ...@@ -53,8 +54,8 @@
padding: 0px 10px; padding: 0px 10px;
> ul { > ul {
background: rgb(252,252,252); background: $dashboard-profile-color;
border: 1px solid rgb(200,200,200); border: 1px solid $border-color-2;
border-top: none; border-top: none;
//@include border-bottom-radius(4px); //@include border-bottom-radius(4px);
@include box-sizing(border-box); @include box-sizing(border-box);
...@@ -66,7 +67,7 @@ ...@@ -66,7 +67,7 @@
li { li {
@include clearfix; @include clearfix;
border-bottom: 1px dotted rgb(220,220,220); border-bottom: 1px dotted $border-color-2;
list-style: none; list-style: none;
margin-bottom: 15px; margin-bottom: 15px;
padding-bottom: 17px; padding-bottom: 17px;
...@@ -128,8 +129,8 @@ ...@@ -128,8 +129,8 @@
.news-carousel { .news-carousel {
@include clearfix; @include clearfix;
margin: 30px 10px 0; margin: 30px 10px 0;
border: 1px solid rgb(200,200,200); border: 1px solid $border-color-2;
background: rgb(252,252,252); background: $dashboard-profile-color;
@include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.15)); @include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.15));
* { * {
...@@ -156,14 +157,14 @@ ...@@ -156,14 +157,14 @@
width: 11px; width: 11px;
height: 11px; height: 11px;
border-radius: 11px; border-radius: 11px;
background: $light-gray; background: $dot-color;
&:hover { &:hover {
background: #ccc; background: $lighter-base-font-color;
} }
&.current { &.current {
background: $blue; background: $link-color;
} }
} }
...@@ -201,7 +202,7 @@ ...@@ -201,7 +202,7 @@
img { img {
width: 100%; width: 100%;
border: 1px solid $light-gray; border: 1px solid $border-color-1;
} }
} }
...@@ -229,7 +230,7 @@ ...@@ -229,7 +230,7 @@
width: flex-grid(9); width: flex-grid(9);
> header { > header {
border-bottom: 1px solid rgb(210,210,210); border-bottom: 1px solid $border-color-2;
margin-bottom: 30px; margin-bottom: 30px;
} }
...@@ -246,8 +247,9 @@ ...@@ -246,8 +247,9 @@
a { a {
background: rgb(240,240,240); background: rgb(240,240,240);
@include background-image(linear-gradient(-90deg, rgb(245,245,245) 0%, rgb(243,243,243) 50%, rgb(237,237,237) 50%, rgb(235,235,235) 100%)); @include background-image($button-bg-image);
border: 1px solid rgb(220,220,220); background-color: $button-bg-color;
border: 1px solid $border-color-2;
@include border-radius(4px); @include border-radius(4px);
@include box-shadow(0 1px 8px 0 rgba(0,0,0, 0.1)); @include box-shadow(0 1px 8px 0 rgba(0,0,0, 0.1));
@include box-sizing(border-box); @include box-sizing(border-box);
...@@ -260,7 +262,7 @@ ...@@ -260,7 +262,7 @@
text-shadow: 0 1px rgba(255,255,255, 0.6); text-shadow: 0 1px rgba(255,255,255, 0.6);
&:hover { &:hover {
color: $blue; color: $link-color;
text-decoration: none; text-decoration: none;
} }
} }
...@@ -272,7 +274,7 @@ ...@@ -272,7 +274,7 @@
margin-right: flex-gutter(); margin-right: flex-gutter();
margin-bottom: 50px; margin-bottom: 50px;
padding-bottom: 50px; padding-bottom: 50px;
border-bottom: 1px solid $light-gray; border-bottom: 1px solid $border-color-1;
position: relative; position: relative;
width: flex-grid(12); width: flex-grid(12);
z-index: 20; z-index: 20;
...@@ -343,7 +345,7 @@ ...@@ -343,7 +345,7 @@
.course-status { .course-status {
background: $yellow; background: $yellow;
border: 1px solid rgb(200,200,200); border: 1px solid $border-color-2;
@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6));
margin-top: 17px; margin-top: 17px;
margin-right: flex-gutter(); margin-right: flex-gutter();
...@@ -362,7 +364,7 @@ ...@@ -362,7 +364,7 @@
.course-status-completed { .course-status-completed {
background: #ccc; background: #ccc;
color: #fff; color: $very-light-text;
p { p {
color: #222; color: #222;
...@@ -374,7 +376,7 @@ ...@@ -374,7 +376,7 @@
} }
.enter-course { .enter-course {
@include button(simple, $blue); @include button(simple, $button-color);
@include box-sizing(border-box); @include box-sizing(border-box);
@include border-radius(3px); @include border-radius(3px);
display: block; display: block;
...@@ -386,7 +388,7 @@ ...@@ -386,7 +388,7 @@
margin-top: 16px; margin-top: 16px;
&.archived { &.archived {
@include button(simple, #eee); @include button(simple, $button-archive-color);
font: normal 15px/1.6rem $sans-serif; font: normal 15px/1.6rem $sans-serif;
padding: 6px 32px 7px; padding: 6px 32px 7px;
......
...@@ -7,15 +7,15 @@ ...@@ -7,15 +7,15 @@
} }
> header { > header {
background: rgb(255,255,255); background: $dashboard-profile-color;
@include background-image(url('/static/images/homepage-bg.jpg')); @include background-image(url($homepage-bg-image));
background-size: cover; background-size: cover;
border-bottom: 1px solid rgb(80,80,80); border-bottom: 1px solid $border-color-3;
@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.9), inset 0 -1px 5px 0 rgba(0,0,0, 0.1)); @include box-shadow(0 1px 0 0 $course-header-bg, inset 0 -1px 5px 0 rgba(0,0,0, 0.1));
@include clearfix; @include clearfix;
height: 460px; height: 460px;
margin-top: -69px;
overflow: hidden; overflow: hidden;
margin-top: -69px;
padding: 0px; padding: 0px;
width: flex-grid(12); width: flex-grid(12);
...@@ -31,8 +31,8 @@ ...@@ -31,8 +31,8 @@
.title { .title {
background: #FFF; background: #FFF;
background: rgba(255,255,255, 0.93); background: $course-header-bg;
border: 1px solid rgb(100,100,100); border: 1px solid $border-color-3;
@include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5)); @include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5));
@include box-sizing(border-box); @include box-sizing(border-box);
min-height: 120px; min-height: 120px;
...@@ -80,8 +80,8 @@ ...@@ -80,8 +80,8 @@
.media { .media {
background: #FFF; background: #FFF;
background: rgba(255,255,255, 0.93); background: $course-header-bg;
border: 1px solid rgb(100,100,100); border: 1px solid $border-color-3;
border-left: 0; border-left: 0;
@include box-sizing(border-box); @include box-sizing(border-box);
// @include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5)); // @include box-shadow(0 4px 25px 0 rgba(0,0,0, 0.5));
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background: url('../images/courses/video-thumb.jpg') center no-repeat; background: url($video-thumb-url) center no-repeat;
@include background-size(cover); @include background-size(cover);
.play-intro { .play-intro {
...@@ -164,9 +164,9 @@ ...@@ -164,9 +164,9 @@
> h2 { > h2 {
@include background-image(linear-gradient(-90deg, rgb(250,250,250), rgb(230,230,230))); @include background-image(linear-gradient(-90deg, rgb(250,250,250), rgb(230,230,230)));
border: 1px solid rgb(200,200,200); border: 1px solid $border-color-2;
@include border-radius(4px); @include border-radius(4px);
border-top-color: rgb(190,190,190); border-top-color: $border-color-1;
@include box-shadow(inset 0 0 0 1px rgba(255,255,255, 0.4), 0 0px 12px 0 rgba(0,0,0, 0.2)); @include box-shadow(inset 0 0 0 1px rgba(255,255,255, 0.4), 0 0px 12px 0 rgba(0,0,0, 0.2));
color: $lighter-base-font-color; color: $lighter-base-font-color;
letter-spacing: 1px; letter-spacing: 1px;
...@@ -180,7 +180,7 @@ ...@@ -180,7 +180,7 @@
} }
.university-partners { .university-partners {
border-bottom: 1px solid rgb(210,210,210); border-bottom: 1px solid $border-color-2;
margin-bottom: 0px; margin-bottom: 0px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
...@@ -366,13 +366,13 @@ ...@@ -366,13 +366,13 @@
} }
.more-info { .more-info {
border: 1px solid rgb(200,200,200); border: 1px solid $border-color-2;
margin-bottom: 80px; margin-bottom: 80px;
width: flex-grid(12); width: flex-grid(12);
header { header {
@include background-image(linear-gradient(-90deg, rgb(250,250,250), rgb(230,230,230))); @include background-image(linear-gradient(-90deg, rgb(250,250,250), rgb(230,230,230)));
border-bottom: 1px solid rgb(200,200,200); border-bottom: 1px solid $border-color-2;
@include clearfix; @include clearfix;
padding: 10px 20px 8px; padding: 10px 20px 8px;
position: relative; position: relative;
...@@ -415,14 +415,14 @@ ...@@ -415,14 +415,14 @@
width: flex-grid(12); width: flex-grid(12);
.blog-posts { .blog-posts {
border-bottom: 1px solid rgb(220,220,220); border-bottom: 1px solid $border-color-2;
margin-bottom: 20px; margin-bottom: 20px;
padding-bottom: 20px; padding-bottom: 20px;
@include clearfix; @include clearfix;
> article { > article {
border: 1px dotted transparent; border: 1px dotted transparent;
border-color: rgb(220,220,220); border-color: $border-color-2;
@include box-sizing(border-box); @include box-sizing(border-box);
@include clearfix; @include clearfix;
float: left; float: left;
...@@ -432,8 +432,8 @@ ...@@ -432,8 +432,8 @@
width: flex-grid(4); width: flex-grid(4);
&:hover { &:hover {
background: rgb(248,248,248); background: $body-bg;
border: 1px solid rgb(220,220,220); border: 1px solid $border-color-2;
@include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.1)); @include box-shadow(inset 0 0 3px 0 rgba(0,0,0, 0.1));
} }
...@@ -442,7 +442,7 @@ ...@@ -442,7 +442,7 @@
} }
.post-graphics { .post-graphics {
border: 1px solid rgb(190,190,190); border: 1px solid $border-color-1;
@include box-sizing(border-box); @include box-sizing(border-box);
display: block; display: block;
float: left; float: left;
......
...@@ -31,8 +31,8 @@ ...@@ -31,8 +31,8 @@
} }
.course { .course {
background: rgb(250,250,250); background: $body-bg;
border: 1px solid rgb(180,180,180); border: 1px solid $border-color-1;
@include border-radius(2px); @include border-radius(2px);
@include box-sizing(border-box); @include box-sizing(border-box);
@include box-shadow(0 1px 10px 0 rgba(0,0,0, 0.15), inset 0 0 0 1px rgba(255,255,255, 0.9)); @include box-shadow(0 1px 10px 0 rgba(0,0,0, 0.15), inset 0 0 0 1px rgba(255,255,255, 0.9));
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
@include transition(all, 0.15s, linear); @include transition(all, 0.15s, linear);
.status { .status {
background: $blue; background: $link-color;
color: white; color: white;
font-size: 10px; font-size: 10px;
left: 10px; left: 10px;
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
} }
.status:after { .status:after {
border-bottom: 6px solid shade($blue, 50%); border-bottom: 6px solid shade($link-color, 50%);
border-right: 6px solid transparent; border-right: 6px solid transparent;
content: ""; content: "";
display: block; display: block;
...@@ -90,7 +90,7 @@ ...@@ -90,7 +90,7 @@
} }
.inner-wrapper { .inner-wrapper {
border: 1px solid rgba(255,255,255, 1); border: 1px solid $border-color-4;
height: 100%; height: 100%;
height: 200px; height: 200px;
overflow: hidden; overflow: hidden;
...@@ -116,12 +116,12 @@ ...@@ -116,12 +116,12 @@
text-decoration: none; text-decoration: none;
.info-link { .info-link {
color: $blue; color: $link-color;
opacity: 1; opacity: 1;
} }
h2 { h2 {
color: $blue; color: $link-color;
} }
} }
...@@ -176,7 +176,7 @@ ...@@ -176,7 +176,7 @@
// } // }
.info { .info {
background: rgb(255,255,255); background: $content-wrapper-bg;
height: 220px + 130px; height: 220px + 130px;
left: 0px; left: 0px;
position: absolute; position: absolute;
...@@ -221,14 +221,14 @@ ...@@ -221,14 +221,14 @@
width: 100%; width: 100%;
.university { .university {
border-right: 1px solid rgb(200,200,200); border-right: 1px solid $border-color-2;
color: $lighter-base-font-color; color: $lighter-base-font-color;
letter-spacing: 1px; letter-spacing: 1px;
margin-right: 10px; margin-right: 10px;
padding-right: 10px; padding-right: 10px;
&:hover { &:hover {
color: $blue; color: $link-color;
} }
} }
...@@ -240,9 +240,9 @@ ...@@ -240,9 +240,9 @@
} }
&:hover { &:hover {
background: rgb(245,245,245); background: $course-profile-bg;
border-color: rgb(170,170,170); border-color: $border-color-1;
@include box-shadow(0 1px 16px 0 rgba($blue, 0.4)); @include box-shadow(0 1px 16px 0 rgba($shadow-color, 0.4));
.info { .info {
top: -150px; top: -150px;
......
...@@ -159,4 +159,4 @@ ...@@ -159,4 +159,4 @@
width: 360px; width: 360px;
} }
} }
} }
\ No newline at end of file
...@@ -15,8 +15,8 @@ input[type="text"], ...@@ -15,8 +15,8 @@ input[type="text"],
input[type="email"], input[type="email"],
input[type="password"], input[type="password"],
input[type="tel"] { input[type="tel"] {
background: rgb(250,250,250); background: $form-bg-color;
border: 1px solid rgb(200,200,200); border: 1px solid $border-color-2;
@include border-radius(3px); @include border-radius(3px);
@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6), inset 0 0 3px 0 rgba(0,0,0, 0.1)); @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6), inset 0 0 3px 0 rgba(0,0,0, 0.1));
@include box-sizing(border-box); @include box-sizing(border-box);
...@@ -31,8 +31,8 @@ input[type="tel"] { ...@@ -31,8 +31,8 @@ input[type="tel"] {
} }
&:focus { &:focus {
border-color: lighten($blue, 20%); border-color: darken($button-archive-color, 50%);
@include box-shadow(0 0 6px 0 rgba($blue, 0.4), inset 0 0 4px 0 rgba(0,0,0, 0.15)); @include box-shadow(0 0 6px 0 darken($button-archive-color, 50%), inset 0 0 4px 0 rgba(0,0,0, 0.15));
outline: none; outline: none;
} }
} }
...@@ -46,7 +46,7 @@ input[type="button"], ...@@ -46,7 +46,7 @@ input[type="button"],
button, button,
.button { .button {
@include border-radius(3px); @include border-radius(3px);
@include button(shiny, $blue); @include button(shiny, $button-color);
font: normal 1.2rem/1.6rem $sans-serif; font: normal 1.2rem/1.6rem $sans-serif;
letter-spacing: 1px; letter-spacing: 1px;
padding: 4px 20px; padding: 4px 20px;
......
...@@ -54,8 +54,7 @@ header.global { ...@@ -54,8 +54,7 @@ header.global {
li.secondary { li.secondary {
> a { > a {
color: $lighter-base-font-color; color: $link-color;
color: $blue;
display: block; display: block;
font-family: $sans-serif; font-family: $sans-serif;
@include inline-block; @include inline-block;
...@@ -78,9 +77,9 @@ header.global { ...@@ -78,9 +77,9 @@ header.global {
margin-right: 5px; margin-right: 5px;
> a { > a {
@include background-image(linear-gradient(#fff 0%, rgb(250,250,250) 50%, rgb(237,237,237) 50%, rgb(220,220,220) 100%)); @include background-image($button-bg-image);
border: 1px solid transparent; background-color: $button-bg-color;
border-color: rgb(200,200,200); border: 1px solid $border-color-2;
@include border-radius(3px); @include border-radius(3px);
@include box-sizing(border-box); @include box-sizing(border-box);
@include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6)); @include box-shadow(0 1px 0 0 rgba(255,255,255, 0.6));
...@@ -101,7 +100,7 @@ header.global { ...@@ -101,7 +100,7 @@ header.global {
} }
&:hover, &.active { &:hover, &.active {
background: #FFF; background: $button-bg-hover-color;
} }
} }
} }
...@@ -159,10 +158,10 @@ header.global { ...@@ -159,10 +158,10 @@ header.global {
} }
ul.dropdown-menu { ul.dropdown-menu {
background: rgb(252,252,252); background: $border-color-4;
@include border-radius(4px); @include border-radius(4px);
@include box-shadow(0 2px 24px 0 rgba(0,0,0, 0.3)); @include box-shadow(0 2px 24px 0 rgba(0,0,0, 0.3));
border: 1px solid rgb(100,100,100); border: 1px solid $border-color-3;
display: none; display: none;
padding: 5px 10px; padding: 5px 10px;
position: absolute; position: absolute;
...@@ -178,12 +177,12 @@ header.global { ...@@ -178,12 +177,12 @@ header.global {
&::before { &::before {
background: transparent; background: transparent;
border: { border: {
top: 6px solid rgba(252,252,252, 1); top: 6px solid $border-color-4;
right: 6px solid rgba(252,252,252, 1); right: 6px solid $border-color-4;
bottom: 6px solid transparent; bottom: 6px solid transparent;
left: 6px solid transparent; left: 6px solid transparent;
} }
@include box-shadow(1px 0 0 0 rgb(0,0,0), 0 -1px 0 0 rgb(0,0,0)); @include box-shadow(1px 0 0 0 $border-color-3, 0 -1px 0 0 $border-color-3);
content: ""; content: "";
display: block; display: block;
height: 0px; height: 0px;
...@@ -196,7 +195,7 @@ header.global { ...@@ -196,7 +195,7 @@ header.global {
li { li {
display: block; display: block;
border-top: 1px dotted rgba(200,200,200, 1); border-top: 1px dotted $border-color-2;
@include box-shadow(inset 0 1px 0 0 rgba(255,255,255, 0.05)); @include box-shadow(inset 0 1px 0 0 rgba(255,255,255, 0.05));
&:first-child { &:first-child {
...@@ -208,7 +207,7 @@ header.global { ...@@ -208,7 +207,7 @@ header.global {
border: 1px solid transparent; border: 1px solid transparent;
@include border-radius(3px); @include border-radius(3px);
@include box-sizing(border-box); @include box-sizing(border-box);
color: $blue; color: $link-color;
cursor: pointer; cursor: pointer;
display: block; display: block;
margin: 5px 0px; margin: 5px 0px;
...@@ -328,4 +327,4 @@ header.global { ...@@ -328,4 +327,4 @@ header.global {
text-decoration: none; text-decoration: none;
color: $m-blue-s1 !important; color: $m-blue-s1 !important;
} }
} }
\ No newline at end of file
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
} }
.inner-wrapper { .inner-wrapper {
background: rgb(245,245,245); background: $modal-bg-color;
@include border-radius(0px); @include border-radius(0px);
border: 1px solid rgba(0, 0, 0, 0.9); border: 1px solid rgba(0, 0, 0, 0.9);
@include box-shadow(inset 0 1px 0 0 rgba(255, 255, 255, 0.7)); @include box-shadow(inset 0 1px 0 0 rgba(255, 255, 255, 0.7));
...@@ -149,7 +149,7 @@ ...@@ -149,7 +149,7 @@
} }
label { label {
color: #646464; color: $text-color;
&.field-error { &.field-error {
display: block; display: block;
......
<h1>E-mail change failed.</h1>
<p>We were unable to send a confirmation email to ${email}</p>
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"coffee-script": "1.6.X", "coffee-script": "1.6.X",
"phantom-jasmine": "0.1.0" "phantom-jasmine": "0.1.0",
"jasmine-reporters": "0.2.1"
} }
} }
...@@ -110,7 +110,9 @@ generated-members= ...@@ -110,7 +110,9 @@ generated-members=
get_url, get_url,
size, size,
content, content,
status_code status_code,
# For factory_body factories
create
[BASIC] [BASIC]
......
...@@ -48,6 +48,7 @@ def template_jasmine_runner(lib) ...@@ -48,6 +48,7 @@ def template_jasmine_runner(lib)
sh("node_modules/.bin/coffee -c #{coffee_files.join(' ')}") sh("node_modules/.bin/coffee -c #{coffee_files.join(' ')}")
end end
phantom_jasmine_path = File.expand_path("node_modules/phantom-jasmine") phantom_jasmine_path = File.expand_path("node_modules/phantom-jasmine")
jasmine_reporters_path = File.expand_path("node_modules/jasmine-reporters")
common_js_root = File.expand_path("common/static/js") common_js_root = File.expand_path("common/static/js")
common_coffee_root = File.expand_path("common/static/coffee/src") common_coffee_root = File.expand_path("common/static/coffee/src")
...@@ -58,6 +59,7 @@ def template_jasmine_runner(lib) ...@@ -58,6 +59,7 @@ def template_jasmine_runner(lib)
js_specs = Dir[spec_glob].sort_by {|p| [p.split('/').length, p]} .map {|f| File.expand_path(f)} js_specs = Dir[spec_glob].sort_by {|p| [p.split('/').length, p]} .map {|f| File.expand_path(f)}
js_source = Dir[src_glob].sort_by {|p| [p.split('/').length, p]} .map {|f| File.expand_path(f)} js_source = Dir[src_glob].sort_by {|p| [p.split('/').length, p]} .map {|f| File.expand_path(f)}
report_dir = report_dir_path("#{lib}/jasmine")
template = ERB.new(File.read("common/templates/jasmine/jasmine_test_runner.html.erb")) template = ERB.new(File.read("common/templates/jasmine/jasmine_test_runner.html.erb"))
template_output = "#{lib}/jasmine_test_runner.html" template_output = "#{lib}/jasmine_test_runner.html"
File.open(template_output, 'w') do |f| File.open(template_output, 'w') do |f|
...@@ -66,6 +68,11 @@ def template_jasmine_runner(lib) ...@@ -66,6 +68,11 @@ def template_jasmine_runner(lib)
yield File.expand_path(template_output) yield File.expand_path(template_output)
end end
def run_phantom_js(url)
phantomjs = ENV['PHANTOMJS_PATH'] || 'phantomjs'
sh("#{phantomjs} node_modules/jasmine-reporters/test/phantomjs-testrunner.js #{url}")
end
[:lms, :cms].each do |system| [:lms, :cms].each do |system|
desc "Open jasmine tests for #{system} in your default browser" desc "Open jasmine tests for #{system} in your default browser"
task "browse_jasmine_#{system}" => :assets do task "browse_jasmine_#{system}" => :assets do
...@@ -78,14 +85,16 @@ end ...@@ -78,14 +85,16 @@ end
desc "Use phantomjs to run jasmine tests for #{system} from the console" desc "Use phantomjs to run jasmine tests for #{system} from the console"
task "phantomjs_jasmine_#{system}" => :assets do task "phantomjs_jasmine_#{system}" => :assets do
phantomjs = ENV['PHANTOMJS_PATH'] || 'phantomjs'
django_for_jasmine(system, false) do |jasmine_url| django_for_jasmine(system, false) do |jasmine_url|
sh("#{phantomjs} node_modules/phantom-jasmine/lib/run_jasmine_test.coffee #{jasmine_url}") run_phantom_js(jasmine_url)
end end
end end
end end
Dir["common/lib/*"].select{|lib| File.directory?(lib)}.each do |lib| STATIC_JASMINE_TESTS = Dir["common/lib/*"].select{|lib| File.directory?(lib)}
STATIC_JASMINE_TESTS << 'common/static/coffee'
STATIC_JASMINE_TESTS.each do |lib|
desc "Open jasmine tests for #{lib} in your default browser" desc "Open jasmine tests for #{lib} in your default browser"
task "browse_jasmine_#{lib}" do task "browse_jasmine_#{lib}" do
template_jasmine_runner(lib) do |f| template_jasmine_runner(lib) do |f|
...@@ -97,26 +106,14 @@ Dir["common/lib/*"].select{|lib| File.directory?(lib)}.each do |lib| ...@@ -97,26 +106,14 @@ Dir["common/lib/*"].select{|lib| File.directory?(lib)}.each do |lib|
desc "Use phantomjs to run jasmine tests for #{lib} from the console" desc "Use phantomjs to run jasmine tests for #{lib} from the console"
task "phantomjs_jasmine_#{lib}" do task "phantomjs_jasmine_#{lib}" do
phantomjs = ENV['PHANTOMJS_PATH'] || 'phantomjs'
template_jasmine_runner(lib) do |f| template_jasmine_runner(lib) do |f|
sh("#{phantomjs} node_modules/phantom-jasmine/lib/run_jasmine_test.coffee #{f}") run_phantom_js(f)
end end
end end
end end
desc "Open jasmine tests for discussion in your default browser" desc "Open jasmine tests for discussion in your default browser"
task "browse_jasmine_discussion" do task "browse_jasmine_discussion" => "browse_jasmine_common/static/coffee"
template_jasmine_runner("common/static/coffee") do |f|
sh("python -m webbrowser -t 'file://#{f}'")
puts "Press ENTER to terminate".red
$stdin.gets
end
end
desc "Use phantomjs to run jasmine tests for discussion from the console" desc "Use phantomjs to run jasmine tests for discussion from the console"
task "phantomjs_jasmine_discussion" do task "phantomjs_jasmine_discussion" => "phantomjs_jasmine_common/static/coffee"
phantomjs = ENV['PHANTOMJS_PATH'] || 'phantomjs'
template_jasmine_runner("common/static/coffee") do |f|
sh("#{phantomjs} node_modules/phantom-jasmine/lib/run_jasmine_test.coffee #{f}")
end
end
...@@ -31,6 +31,7 @@ task :install_python_prereqs => "ws:migrate" do ...@@ -31,6 +31,7 @@ task :install_python_prereqs => "ws:migrate" do
unchanged = 'Python requirements unchanged, nothing to install' unchanged = 'Python requirements unchanged, nothing to install'
when_changed(unchanged, ['requirements/**/*'], [site_packages_dir]) do when_changed(unchanged, ['requirements/**/*'], [site_packages_dir]) do
ENV['PIP_DOWNLOAD_CACHE'] ||= '.pip_download_cache' ENV['PIP_DOWNLOAD_CACHE'] ||= '.pip_download_cache'
sh('pip install --exists-action w -r requirements/edx/pre.txt')
sh('pip install --exists-action w -r requirements/edx/base.txt') sh('pip install --exists-action w -r requirements/edx/base.txt')
sh('pip install --exists-action w -r requirements/edx/post.txt') sh('pip install --exists-action w -r requirements/edx/post.txt')
# requirements/private.txt is used to install our libs as # requirements/private.txt is used to install our libs as
......
...@@ -12,10 +12,11 @@ def run_under_coverage(cmd, root) ...@@ -12,10 +12,11 @@ def run_under_coverage(cmd, root)
return cmd return cmd
end end
def run_tests(system, report_dir, stop_on_failure=true) def run_tests(system, report_dir, test_id=nil, stop_on_failure=true)
ENV['NOSE_XUNIT_FILE'] = File.join(report_dir, "nosetests.xml") ENV['NOSE_XUNIT_FILE'] = File.join(report_dir, "nosetests.xml")
dirs = Dir["common/djangoapps/*"] + Dir["#{system}/djangoapps/*"] dirs = Dir["common/djangoapps/*"] + Dir["#{system}/djangoapps/*"]
cmd = django_admin(system, :test, 'test', '--logging-clear-handlers', *dirs.each) test_id = dirs.join(' ') if test_id.nil? or test_id == ''
cmd = django_admin(system, :test, 'test', '--logging-clear-handlers', test_id)
sh(run_under_coverage(cmd, system)) do |ok, res| sh(run_under_coverage(cmd, system)) do |ok, res|
if !ok and stop_on_failure if !ok and stop_on_failure
abort "Test failed!" abort "Test failed!"
...@@ -25,6 +26,16 @@ def run_tests(system, report_dir, stop_on_failure=true) ...@@ -25,6 +26,16 @@ def run_tests(system, report_dir, stop_on_failure=true)
end end
def run_acceptance_tests(system, report_dir, harvest_args) def run_acceptance_tests(system, report_dir, harvest_args)
# HACK: Since now the CMS depends on the existence of some database tables
# that used to be in LMS (Role/Permissions for Forums) we need to make
# sure the acceptance tests create/migrate the database tables
# that are represented in the LMS. We might be able to address this by moving
# out the migrations from lms/django_comment_client, but then we'd have to
# repair all the existing migrations from the upgrade tables in the DB.
if system == :cms
sh(django_admin('lms', 'acceptance', 'syncdb', '--noinput'))
sh(django_admin('lms', 'acceptance', 'migrate', '--noinput'))
end
sh(django_admin(system, 'acceptance', 'syncdb', '--noinput')) sh(django_admin(system, 'acceptance', 'syncdb', '--noinput'))
sh(django_admin(system, 'acceptance', 'migrate', '--noinput')) sh(django_admin(system, 'acceptance', 'migrate', '--noinput'))
sh(django_admin(system, 'acceptance', 'harvest', '--debug-mode', '--tag -skip', harvest_args)) sh(django_admin(system, 'acceptance', 'harvest', '--debug-mode', '--tag -skip', harvest_args))
...@@ -44,13 +55,13 @@ TEST_TASK_DIRS = [] ...@@ -44,13 +55,13 @@ TEST_TASK_DIRS = []
# Per System tasks # Per System tasks
desc "Run all django tests on our djangoapps for the #{system}" desc "Run all django tests on our djangoapps for the #{system}"
task "test_#{system}", [:stop_on_failure] => ["clean_test_files", :predjango, "#{system}:gather_assets:test", "fasttest_#{system}"] task "test_#{system}", [:test_id, :stop_on_failure] => ["clean_test_files", :predjango, "#{system}:gather_assets:test", "fasttest_#{system}"]
# Have a way to run the tests without running collectstatic -- useful when debugging without # Have a way to run the tests without running collectstatic -- useful when debugging without
# messing with static files. # messing with static files.
task "fasttest_#{system}", [:stop_on_failure] => [report_dir, :install_prereqs, :predjango] do |t, args| task "fasttest_#{system}", [:test_id, :stop_on_failure] => [report_dir, :install_prereqs, :predjango] do |t, args|
args.with_defaults(:stop_on_failure => 'true') args.with_defaults(:stop_on_failure => 'true', :test_id => nil)
run_tests(system, report_dir, args.stop_on_failure) run_tests(system, report_dir, args.test_id, args.stop_on_failure)
end end
# Run acceptance tests # Run acceptance tests
...@@ -100,7 +111,7 @@ end ...@@ -100,7 +111,7 @@ end
task :test do task :test do
TEST_TASK_DIRS.each do |dir| TEST_TASK_DIRS.each do |dir|
Rake::Task["test_#{dir}"].invoke(false) Rake::Task["test_#{dir}"].invoke(nil, false)
end end
if $failed_tests > 0 if $failed_tests > 0
......
...@@ -29,7 +29,6 @@ mako==0.7.3 ...@@ -29,7 +29,6 @@ mako==0.7.3
Markdown==2.2.1 Markdown==2.2.1
networkx==1.7 networkx==1.7
nltk==2.0.4 nltk==2.0.4
numpy==1.6.2
paramiko==1.9.0 paramiko==1.9.0
path.py==3.0.1 path.py==3.0.1
Pillow==1.7.8 Pillow==1.7.8
...@@ -43,6 +42,7 @@ python-openid==2.2.5 ...@@ -43,6 +42,7 @@ python-openid==2.2.5
pytz==2012h pytz==2012h
PyYAML==3.10 PyYAML==3.10
requests==0.14.2 requests==0.14.2
scipy==0.11.0
Shapely==1.2.16 Shapely==1.2.16
sorl-thumbnail==11.12 sorl-thumbnail==11.12
South==0.7.6 South==0.7.6
...@@ -71,7 +71,7 @@ transifex-client==0.8 ...@@ -71,7 +71,7 @@ transifex-client==0.8
coverage==3.6 coverage==3.6
factory_boy==2.0.2 factory_boy==2.0.2
lettuce==0.2.16 lettuce==0.2.16
mock==0.8.0 mock==1.0.1
nosexcover==1.0.7 nosexcover==1.0.7
pep8==1.4.5 pep8==1.4.5
pylint==0.28 pylint==0.28
...@@ -82,3 +82,5 @@ django_nose==1.1 ...@@ -82,3 +82,5 @@ django_nose==1.1
django-jasmine==0.3.2 django-jasmine==0.3.2
django_debug_toolbar django_debug_toolbar
django-debug-toolbar-mongo django-debug-toolbar-mongo
git+https://github.com/mfogel/django-settings-context-processor.git
...@@ -9,4 +9,4 @@ ...@@ -9,4 +9,4 @@
# Our libraries: # Our libraries:
-e git+https://github.com/edx/XBlock.git@2144a25d#egg=XBlock -e git+https://github.com/edx/XBlock.git@2144a25d#egg=XBlock
-e git+https://github.com/edx/codejail.git@07494f1#egg=codejail -e git+https://github.com/edx/codejail.git@72cf791#egg=codejail
# This must be installed after distribute has been updated.
# This must be installed after distribute 0.6.28 MySQL-python==1.2.4
MySQL-python==1.2.4c1
# This must be installed after numpy
scipy==0.11.0
# Numpy and scipy can't be installed in the same pip run.
# Install numpy before other things to help resolve the problem.
numpy==1.6.2
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