Merge branch 'master' into feature/btalbot/studio-alerts
This commit is contained in:
@@ -7,6 +7,7 @@ Feature: Advanced (manual) course policy
|
||||
When I select the Advanced Settings
|
||||
Then I see only the display name
|
||||
|
||||
@skip-phantom
|
||||
Scenario: Test if there are no policy settings without existing UI controls
|
||||
Given I am on the Advanced Course Settings page in Studio
|
||||
When I delete the display name
|
||||
@@ -14,6 +15,7 @@ Feature: Advanced (manual) course policy
|
||||
And I reload the page
|
||||
Then there are no advanced policy settings
|
||||
|
||||
@skip-phantom
|
||||
Scenario: Test cancel editing key name
|
||||
Given I am on the Advanced Course Settings page in Studio
|
||||
When I edit the name of a policy key
|
||||
@@ -32,6 +34,7 @@ Feature: Advanced (manual) course policy
|
||||
And I press the "Cancel" notification button
|
||||
Then the policy key value is unchanged
|
||||
|
||||
@skip-phantom
|
||||
Scenario: Test editing key value
|
||||
Given I am on the Advanced Course Settings page in Studio
|
||||
When I edit the value of a policy key
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from lettuce import world, step
|
||||
from common import *
|
||||
import time
|
||||
from selenium.common.exceptions import WebDriverException
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
from nose.tools import assert_equal
|
||||
from nose.tools import assert_true
|
||||
from nose.tools import assert_true, assert_false, assert_equal
|
||||
|
||||
"""
|
||||
http://selenium.googlecode.com/svn/trunk/docs/api/py/webdriver/selenium.webdriver.common.keys.html
|
||||
@@ -19,6 +20,7 @@ def i_select_advanced_settings(step):
|
||||
css_click(expand_icon_css)
|
||||
link_css = 'li.nav-course-settings-advanced a'
|
||||
css_click(link_css)
|
||||
# world.browser.click_link_by_text('Advanced Settings')
|
||||
|
||||
|
||||
@step('I am on the Advanced Course Settings page in Studio$')
|
||||
@@ -37,13 +39,25 @@ def reload_the_page(step):
|
||||
def edit_the_name_of_a_policy_key(step):
|
||||
policy_key_css = 'input.policy-key'
|
||||
e = css_find(policy_key_css).first
|
||||
e.fill('new')
|
||||
e.type('_new')
|
||||
|
||||
|
||||
@step(u'I press the "([^"]*)" notification button$')
|
||||
def press_the_notification_button(step, name):
|
||||
world.browser.click_link_by_text(name)
|
||||
def is_visible(driver):
|
||||
return EC.visibility_of_element_located((By.CSS_SELECTOR,css,))
|
||||
def is_invisible(driver):
|
||||
return EC.invisibility_of_element_located((By.CSS_SELECTOR,css,))
|
||||
|
||||
css = 'a.%s-button' % name.lower()
|
||||
wait_for(is_visible)
|
||||
|
||||
try:
|
||||
css_click_at(css)
|
||||
wait_for(is_invisible)
|
||||
except WebDriverException, e:
|
||||
css_click_at(css)
|
||||
wait_for(is_invisible)
|
||||
|
||||
@step(u'I edit the value of a policy key$')
|
||||
def edit_the_value_of_a_policy_key(step):
|
||||
@@ -83,7 +97,12 @@ def i_see_only_display_name(step):
|
||||
|
||||
@step('there are no advanced policy settings$')
|
||||
def no_policy_settings(step):
|
||||
assert_policy_entries([], [])
|
||||
keys_css = 'input.policy-key'
|
||||
val_css = 'textarea.json'
|
||||
k = world.browser.is_element_not_present_by_css(keys_css, 5)
|
||||
v = world.browser.is_element_not_present_by_css(val_css, 5)
|
||||
assert_true(k)
|
||||
assert_true(v)
|
||||
|
||||
|
||||
@step('they are alphabetized$')
|
||||
@@ -99,29 +118,29 @@ def it_is_formatted(step):
|
||||
@step(u'the policy key name is unchanged$')
|
||||
def the_policy_key_name_is_unchanged(step):
|
||||
policy_key_css = 'input.policy-key'
|
||||
e = css_find(policy_key_css).first
|
||||
assert_equal(e.value, 'display_name')
|
||||
val = css_find(policy_key_css).first.value
|
||||
assert_equal(val, 'display_name')
|
||||
|
||||
|
||||
@step(u'the policy key name is changed$')
|
||||
def the_policy_key_name_is_changed(step):
|
||||
policy_key_css = 'input.policy-key'
|
||||
e = css_find(policy_key_css).first
|
||||
assert_equal(e.value, 'new')
|
||||
val = css_find(policy_key_css).first.value
|
||||
assert_equal(val, 'display_name_new')
|
||||
|
||||
|
||||
@step(u'the policy key value is unchanged$')
|
||||
def the_policy_key_value_is_unchanged(step):
|
||||
policy_value_css = 'li.course-advanced-policy-list-item div.value textarea'
|
||||
e = css_find(policy_value_css).first
|
||||
assert_equal(e.value, '"Robot Super Course"')
|
||||
val = css_find(policy_value_css).first.value
|
||||
assert_equal(val, '"Robot Super Course"')
|
||||
|
||||
|
||||
@step(u'the policy key value is changed$')
|
||||
def the_policy_key_value_is_unchanged(step):
|
||||
policy_value_css = 'li.course-advanced-policy-list-item div.value textarea'
|
||||
e = css_find(policy_value_css).first
|
||||
assert_equal(e.value, '"Robot Super Course X"')
|
||||
val = css_find(policy_value_css).first.value
|
||||
assert_equal(val, '"Robot Super Course X"')
|
||||
|
||||
|
||||
############# HELPERS ###############
|
||||
@@ -132,19 +151,23 @@ def create_entry(key, value):
|
||||
new_key_css = 'div#__new_advanced_key__ input'
|
||||
new_key_element = css_find(new_key_css).first
|
||||
new_key_element.fill(key)
|
||||
# For some reason have to get the instance for each command (get error that it is no longer attached to the DOM)
|
||||
# Have to do all this because Selenium has a bug that fill does not remove existing text
|
||||
# For some reason have to get the instance for each command
|
||||
# (get error that it is no longer attached to the DOM)
|
||||
# Have to do all this because Selenium fill does not remove existing text
|
||||
new_value_css = 'div.CodeMirror textarea'
|
||||
css_find(new_value_css).last.fill("")
|
||||
css_find(new_value_css).last._element.send_keys(Keys.DELETE, Keys.DELETE)
|
||||
css_find(new_value_css).last.fill(value)
|
||||
# Add in a TAB key press because intermittently on ubuntu the
|
||||
# last character of "value" above was not getting typed in
|
||||
css_find(new_value_css).last._element.send_keys(Keys.TAB)
|
||||
|
||||
|
||||
def delete_entry(index):
|
||||
"""
|
||||
Delete the nth entry where index is 0-based
|
||||
"""
|
||||
css = '.delete-button'
|
||||
css = 'a.delete-button'
|
||||
assert_true(world.browser.is_element_present_by_css(css, 5))
|
||||
delete_buttons = css_find(css)
|
||||
assert_true(len(delete_buttons) > index, "no delete button exists for entry " + str(index))
|
||||
@@ -152,8 +175,8 @@ def delete_entry(index):
|
||||
|
||||
|
||||
def assert_policy_entries(expected_keys, expected_values):
|
||||
assert_entries('.key input', expected_keys)
|
||||
assert_entries('.json', expected_values)
|
||||
assert_entries('.key input.policy-key', expected_keys)
|
||||
assert_entries('textarea.json', expected_values)
|
||||
|
||||
|
||||
def assert_entries(css, expected_values):
|
||||
@@ -165,16 +188,8 @@ def assert_entries(css, expected_values):
|
||||
|
||||
|
||||
def click_save():
|
||||
css = ".save-button"
|
||||
|
||||
def is_shown(driver):
|
||||
visible = css_find(css).first.visible
|
||||
if visible:
|
||||
# Even when waiting for visible, this fails sporadically. Adding in a small wait.
|
||||
time.sleep(float(1))
|
||||
return visible
|
||||
wait_for(is_shown)
|
||||
css_click(css)
|
||||
css = "a.save-button"
|
||||
css_click_at(css)
|
||||
|
||||
|
||||
def fill_last_field(value):
|
||||
|
||||
@@ -3,18 +3,20 @@ from lettuce.django import django_url
|
||||
from nose.tools import assert_true
|
||||
from nose.tools import assert_equal
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.common.exceptions import WebDriverException, StaleElementReferenceException
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
from terrain.factories import UserFactory, RegistrationFactory, UserProfileFactory
|
||||
from terrain.factories import CourseFactory, GroupFactory
|
||||
import xmodule.modulestore.django
|
||||
from xmodule.modulestore.django import _MODULESTORES, modulestore
|
||||
from xmodule.templates import update_templates
|
||||
from auth.authz import get_user_by_email
|
||||
|
||||
from logging import getLogger
|
||||
logger = getLogger(__name__)
|
||||
|
||||
########### STEP HELPERS ##############
|
||||
|
||||
|
||||
@step('I (?:visit|access|open) the Studio homepage$')
|
||||
def i_visit_the_studio_homepage(step):
|
||||
# To make this go to port 8001, put
|
||||
@@ -52,9 +54,8 @@ def i_have_opened_a_new_course(step):
|
||||
log_into_studio()
|
||||
create_a_course()
|
||||
|
||||
|
||||
####### HELPER FUNCTIONS ##############
|
||||
|
||||
|
||||
def create_studio_user(
|
||||
uname='robot',
|
||||
email='robot+studio@edx.org',
|
||||
@@ -83,9 +84,9 @@ def flush_xmodule_store():
|
||||
# (though it shouldn't), do this manually
|
||||
# from the bash shell to drop it:
|
||||
# $ mongo test_xmodule --eval "db.dropDatabase()"
|
||||
xmodule.modulestore.django._MODULESTORES = {}
|
||||
xmodule.modulestore.django.modulestore().collection.drop()
|
||||
xmodule.templates.update_templates()
|
||||
_MODULESTORES = {}
|
||||
modulestore().collection.drop()
|
||||
update_templates()
|
||||
|
||||
|
||||
def assert_css_with_text(css, text):
|
||||
@@ -94,8 +95,16 @@ def assert_css_with_text(css, text):
|
||||
|
||||
|
||||
def css_click(css):
|
||||
assert_true(world.browser.is_element_present_by_css(css, 5))
|
||||
world.browser.find_by_css(css).first.click()
|
||||
'''
|
||||
First try to use the regular click method,
|
||||
but if clicking in the middle of an element
|
||||
doesn't work it might be that it thinks some other
|
||||
element is on top of it there so click in the upper left
|
||||
'''
|
||||
try:
|
||||
css_find(css).first.click()
|
||||
except WebDriverException, e:
|
||||
css_click_at(css)
|
||||
|
||||
|
||||
def css_click_at(css, x=10, y=10):
|
||||
@@ -103,8 +112,7 @@ def css_click_at(css, x=10, y=10):
|
||||
A method to click at x,y coordinates of the element
|
||||
rather than in the center of the element
|
||||
'''
|
||||
assert_true(world.browser.is_element_present_by_css(css, 5))
|
||||
e = world.browser.find_by_css(css).first
|
||||
e = css_find(css).first
|
||||
e.action_chains.move_to_element_with_offset(e._element, x, y)
|
||||
e.action_chains.click()
|
||||
e.action_chains.perform()
|
||||
@@ -115,11 +123,16 @@ def css_fill(css, value):
|
||||
|
||||
|
||||
def css_find(css):
|
||||
def is_visible(driver):
|
||||
return EC.visibility_of_element_located((By.CSS_SELECTOR,css,))
|
||||
|
||||
world.browser.is_element_present_by_css(css, 5)
|
||||
wait_for(is_visible)
|
||||
return world.browser.find_by_css(css)
|
||||
|
||||
|
||||
def wait_for(func):
|
||||
WebDriverWait(world.browser.driver, 10).until(func)
|
||||
WebDriverWait(world.browser.driver, 5).until(func)
|
||||
|
||||
|
||||
def id_find(id):
|
||||
|
||||
@@ -26,9 +26,10 @@ Feature: Create Section
|
||||
And I save a new section release date
|
||||
Then the section release date is updated
|
||||
|
||||
@skip-phantom
|
||||
Scenario: Delete section
|
||||
Given I have opened a new course in Studio
|
||||
And I have added a new section
|
||||
When I press the "section" delete icon
|
||||
And I confirm the alert
|
||||
Then the section does not exist
|
||||
Then the section does not exist
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from lettuce import world, step
|
||||
from common import *
|
||||
from nose.tools import assert_equal
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
import time
|
||||
|
||||
############### ACTIONS ####################
|
||||
|
||||
@@ -37,10 +39,14 @@ def i_save_a_new_section_release_date(step):
|
||||
date_css = 'input.start-date.date.hasDatepicker'
|
||||
time_css = 'input.start-time.time.ui-timepicker-input'
|
||||
css_fill(date_css, '12/25/2013')
|
||||
# click here to make the calendar go away
|
||||
css_click(time_css)
|
||||
# hit TAB to get to the time field
|
||||
e = css_find(date_css).first
|
||||
e._element.send_keys(Keys.TAB)
|
||||
css_fill(time_css, '12:00am')
|
||||
css_click('a.save-button')
|
||||
e = css_find(time_css).first
|
||||
e._element.send_keys(Keys.TAB)
|
||||
time.sleep(float(1))
|
||||
world.browser.click_link_by_text('Save')
|
||||
|
||||
|
||||
############ ASSERTIONS ###################
|
||||
@@ -106,7 +112,7 @@ def the_section_release_date_picker_not_visible(step):
|
||||
def the_section_release_date_is_updated(step):
|
||||
css = 'span.published-status'
|
||||
status_text = world.browser.find_by_css(css).text
|
||||
assert status_text == 'Will Release: 12/25/2013 at 12:00am'
|
||||
assert_equal(status_text,'Will Release: 12/25/2013 at 12:00am')
|
||||
|
||||
|
||||
############ HELPER METHODS ###################
|
||||
@@ -120,4 +126,4 @@ def save_section_name(name):
|
||||
|
||||
def see_my_section_on_the_courseware_page(name):
|
||||
section_css = 'span.section-name-span'
|
||||
assert_css_with_text(section_css, name)
|
||||
assert_css_with_text(section_css, name)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from lettuce import world, step
|
||||
from common import *
|
||||
|
||||
|
||||
@step('I fill in the registration form$')
|
||||
@@ -13,10 +14,11 @@ def i_fill_in_the_registration_form(step):
|
||||
|
||||
@step('I press the Create My Account button on the registration form$')
|
||||
def i_press_the_button_on_the_registration_form(step):
|
||||
register_form = world.browser.find_by_css('form#register_form')
|
||||
submit_css = 'button#submit'
|
||||
register_form.find_by_css(submit_css).click()
|
||||
|
||||
submit_css = 'form#register_form button#submit'
|
||||
# Workaround for click not working on ubuntu
|
||||
# for some unknown reason.
|
||||
e = css_find(submit_css)
|
||||
e.type(' ')
|
||||
|
||||
@step('I should see be on the studio home page$')
|
||||
def i_should_see_be_on_the_studio_home_page(step):
|
||||
|
||||
@@ -21,6 +21,7 @@ Feature: Overview Toggle Section
|
||||
Then I see the "Collapse All Sections" link
|
||||
And all sections are expanded
|
||||
|
||||
@skip-phantom
|
||||
Scenario: Collapse link is not removed after last section of a course is deleted
|
||||
Given I have a course with 1 section
|
||||
And I navigate to the course overview page
|
||||
|
||||
@@ -17,6 +17,7 @@ Feature: Create Subsection
|
||||
And I click to edit the subsection name
|
||||
Then I see the complete subsection name with a quote in the editor
|
||||
|
||||
@skip-phantom
|
||||
Scenario: Delete a subsection
|
||||
Given I have opened a new course section in Studio
|
||||
And I have added a new subsection
|
||||
|
||||
@@ -9,10 +9,8 @@ from tempdir import mkdtemp_clean
|
||||
import json
|
||||
from fs.osfs import OSFS
|
||||
import copy
|
||||
from mock import Mock
|
||||
from json import dumps, loads
|
||||
from json import loads
|
||||
|
||||
from student.models import Registration
|
||||
from django.contrib.auth.models import User
|
||||
from cms.djangoapps.contentstore.utils import get_modulestore
|
||||
|
||||
@@ -22,12 +20,11 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.store_utilities import clone_course
|
||||
from xmodule.modulestore.store_utilities import delete_course
|
||||
from xmodule.modulestore.django import modulestore, _MODULESTORES
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.templates import update_templates
|
||||
from xmodule.modulestore.xml_exporter import export_to_xml
|
||||
from xmodule.modulestore.xml_importer import import_from_xml
|
||||
from xmodule.templates import update_templates
|
||||
|
||||
from xmodule.capa_module import CapaDescriptor
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
@@ -63,7 +60,6 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
self.client = Client()
|
||||
self.client.login(username=uname, password=password)
|
||||
|
||||
|
||||
def check_edit_unit(self, test_course_name):
|
||||
import_from_xml(modulestore(), 'common/test/data/', [test_course_name])
|
||||
|
||||
@@ -82,8 +78,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
def test_static_tab_reordering(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
|
||||
ms = modulestore('direct')
|
||||
course = ms.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
|
||||
module_store = modulestore('direct')
|
||||
course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
|
||||
|
||||
# reverse the ordering
|
||||
reverse_tabs = []
|
||||
@@ -91,9 +87,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
if tab['type'] == 'static_tab':
|
||||
reverse_tabs.insert(0, 'i4x://edX/full/static_tab/{0}'.format(tab['url_slug']))
|
||||
|
||||
resp = self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs': reverse_tabs}), "application/json")
|
||||
self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs': reverse_tabs}), "application/json")
|
||||
|
||||
course = ms.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
|
||||
course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
|
||||
|
||||
# compare to make sure that the tabs information is in the expected order after the server call
|
||||
course_tabs = []
|
||||
@@ -103,28 +99,60 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
|
||||
self.assertEqual(reverse_tabs, course_tabs)
|
||||
|
||||
def test_delete(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
|
||||
module_store = modulestore('direct')
|
||||
|
||||
sequential = module_store.get_item(Location(['i4x', 'edX', 'full', 'sequential', 'Administrivia_and_Circuit_Elements', None]))
|
||||
|
||||
chapter = module_store.get_item(Location(['i4x', 'edX', 'full', 'chapter','Week_1', None]))
|
||||
|
||||
# make sure the parent no longer points to the child object which was deleted
|
||||
self.assertTrue(sequential.location.url() in chapter.definition['children'])
|
||||
|
||||
self.client.post(reverse('delete_item'),
|
||||
json.dumps({'id': sequential.location.url(), 'delete_children':'true'}),
|
||||
"application/json")
|
||||
|
||||
found = False
|
||||
try:
|
||||
module_store.get_item(Location(['i4x', 'edX', 'full', 'sequential', 'Administrivia_and_Circuit_Elements', None]))
|
||||
found = True
|
||||
except ItemNotFoundError:
|
||||
pass
|
||||
|
||||
self.assertFalse(found)
|
||||
|
||||
chapter = module_store.get_item(Location(['i4x', 'edX', 'full', 'chapter','Week_1', None]))
|
||||
|
||||
# make sure the parent no longer points to the child object which was deleted
|
||||
self.assertFalse(sequential.location.url() in chapter.definition['children'])
|
||||
|
||||
|
||||
|
||||
def test_about_overrides(self):
|
||||
'''
|
||||
This test case verifies that a course can use specialized override for about data, e.g. /about/Fall_2012/effort.html
|
||||
while there is a base definition in /about/effort.html
|
||||
'''
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
ms = modulestore('direct')
|
||||
effort = ms.get_item(Location(['i4x', 'edX', 'full', 'about', 'effort', None]))
|
||||
module_store = modulestore('direct')
|
||||
effort = module_store.get_item(Location(['i4x', 'edX', 'full', 'about', 'effort', None]))
|
||||
self.assertEqual(effort.definition['data'], '6 hours')
|
||||
|
||||
# this one should be in a non-override folder
|
||||
effort = ms.get_item(Location(['i4x', 'edX', 'full', 'about', 'end_date', None]))
|
||||
effort = module_store.get_item(Location(['i4x', 'edX', 'full', 'about', 'end_date', None]))
|
||||
self.assertEqual(effort.definition['data'], 'TBD')
|
||||
|
||||
def test_remove_hide_progress_tab(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
|
||||
ms = modulestore('direct')
|
||||
cs = contentstore()
|
||||
module_store = modulestore('direct')
|
||||
content_store = contentstore()
|
||||
|
||||
source_location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
|
||||
course = ms.get_item(source_location)
|
||||
course = module_store.get_item(source_location)
|
||||
self.assertNotIn('hide_progress_tab', course.metadata)
|
||||
|
||||
def test_clone_course(self):
|
||||
@@ -143,19 +171,19 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
data = parse_json(resp)
|
||||
self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course')
|
||||
|
||||
ms = modulestore('direct')
|
||||
cs = contentstore()
|
||||
module_store = modulestore('direct')
|
||||
content_store = contentstore()
|
||||
|
||||
source_location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
|
||||
dest_location = CourseDescriptor.id_to_location('MITx/999/Robot_Super_Course')
|
||||
|
||||
clone_course(ms, cs, source_location, dest_location)
|
||||
clone_course(module_store, content_store, source_location, dest_location)
|
||||
|
||||
# now loop through all the units in the course and verify that the clone can render them, which
|
||||
# means the objects are at least present
|
||||
items = ms.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
|
||||
items = module_store.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
|
||||
self.assertGreater(len(items), 0)
|
||||
clone_items = ms.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)
|
||||
for descriptor in items:
|
||||
new_loc = descriptor.location._replace(org='MITx', course='999')
|
||||
@@ -166,14 +194,14 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
def test_delete_course(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
|
||||
ms = modulestore('direct')
|
||||
cs = contentstore()
|
||||
module_store = modulestore('direct')
|
||||
content_store = contentstore()
|
||||
|
||||
location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
|
||||
|
||||
delete_course(ms, cs, location, commit=True)
|
||||
delete_course(module_store, content_store, location, commit=True)
|
||||
|
||||
items = ms.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
|
||||
items = module_store.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
|
||||
self.assertEqual(len(items), 0)
|
||||
|
||||
def verify_content_existence(self, modulestore, root_dir, location, dirname, category_name, filename_suffix=''):
|
||||
@@ -188,10 +216,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
self.assertTrue(fs.exists(item.location.name + filename_suffix))
|
||||
|
||||
def test_export_course(self):
|
||||
ms = modulestore('direct')
|
||||
cs = contentstore()
|
||||
module_store = modulestore('direct')
|
||||
content_store = contentstore()
|
||||
|
||||
import_from_xml(ms, 'common/test/data/', ['full'])
|
||||
import_from_xml(module_store, 'common/test/data/', ['full'])
|
||||
location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
|
||||
|
||||
root_dir = path(mkdtemp_clean())
|
||||
@@ -199,43 +227,43 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
print 'Exporting to tempdir = {0}'.format(root_dir)
|
||||
|
||||
# export out to a tempdir
|
||||
export_to_xml(ms, cs, location, root_dir, 'test_export')
|
||||
export_to_xml(module_store, content_store, location, root_dir, 'test_export')
|
||||
|
||||
# check for static tabs
|
||||
self.verify_content_existence(ms, root_dir, location, 'tabs', 'static_tab', '.html')
|
||||
self.verify_content_existence(module_store, root_dir, location, 'tabs', 'static_tab', '.html')
|
||||
|
||||
# check for custom_tags
|
||||
self.verify_content_existence(ms, root_dir, location, 'info', 'course_info', '.html')
|
||||
self.verify_content_existence(module_store, root_dir, location, 'info', 'course_info', '.html')
|
||||
|
||||
# check for custom_tags
|
||||
self.verify_content_existence(ms, 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
|
||||
fs = OSFS(root_dir / 'test_export/policies/6.002_Spring_2012')
|
||||
self.assertTrue(fs.exists('grading_policy.json'))
|
||||
|
||||
course = ms.get_item(location)
|
||||
course = module_store.get_item(location)
|
||||
# compare what's on disk compared to what we have in our course
|
||||
with fs.open('grading_policy.json','r') as grading_policy:
|
||||
on_disk = loads(grading_policy.read())
|
||||
with fs.open('grading_policy.json', 'r') as grading_policy:
|
||||
on_disk = loads(grading_policy.read())
|
||||
self.assertEqual(on_disk, course.definition['data']['grading_policy'])
|
||||
|
||||
#check for policy.json
|
||||
self.assertTrue(fs.exists('policy.json'))
|
||||
|
||||
# compare what's on disk to what we have in the course module
|
||||
with fs.open('policy.json','r') as course_policy:
|
||||
with fs.open('policy.json', 'r') as course_policy:
|
||||
on_disk = loads(course_policy.read())
|
||||
self.assertIn('course/6.002_Spring_2012', on_disk)
|
||||
self.assertEqual(on_disk['course/6.002_Spring_2012'], course.metadata)
|
||||
|
||||
# remove old course
|
||||
delete_course(ms, cs, location)
|
||||
delete_course(module_store, content_store, location)
|
||||
|
||||
# reimport
|
||||
import_from_xml(ms, root_dir, ['test_export'])
|
||||
import_from_xml(module_store, root_dir, ['test_export'])
|
||||
|
||||
items = ms.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
|
||||
items = module_store.get_items(Location(['i4x', 'edX', 'full', 'vertical', None]))
|
||||
self.assertGreater(len(items), 0)
|
||||
for descriptor in items:
|
||||
print "Checking {0}....".format(descriptor.location.url())
|
||||
@@ -245,11 +273,11 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
shutil.rmtree(root_dir)
|
||||
|
||||
def test_course_handouts_rewrites(self):
|
||||
ms = modulestore('direct')
|
||||
cs = contentstore()
|
||||
module_store = modulestore('direct')
|
||||
content_store = contentstore()
|
||||
|
||||
# import a test course
|
||||
import_from_xml(ms, 'common/test/data/', ['full'])
|
||||
import_from_xml(module_store, 'common/test/data/', ['full'])
|
||||
|
||||
handout_location = Location(['i4x', 'edX', 'full', 'course_info', 'handouts'])
|
||||
|
||||
@@ -264,32 +292,32 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
self.assertContains(resp, '/c4x/edX/full/asset/handouts_schematic_tutorial.pdf')
|
||||
|
||||
def test_export_course_with_unknown_metadata(self):
|
||||
ms = modulestore('direct')
|
||||
cs = contentstore()
|
||||
module_store = modulestore('direct')
|
||||
content_store = contentstore()
|
||||
|
||||
import_from_xml(ms, 'common/test/data/', ['full'])
|
||||
import_from_xml(module_store, 'common/test/data/', ['full'])
|
||||
location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
|
||||
|
||||
root_dir = path(mkdtemp_clean())
|
||||
|
||||
course = ms.get_item(location)
|
||||
course = module_store.get_item(location)
|
||||
|
||||
# add a bool piece of unknown metadata so we can verify we don't throw an exception
|
||||
course.metadata['new_metadata'] = True
|
||||
|
||||
ms.update_metadata(location, course.metadata)
|
||||
module_store.update_metadata(location, course.metadata)
|
||||
|
||||
print 'Exporting to tempdir = {0}'.format(root_dir)
|
||||
|
||||
# export out to a tempdir
|
||||
bExported = False
|
||||
exported = False
|
||||
try:
|
||||
export_to_xml(ms, cs, location, root_dir, 'test_export')
|
||||
bExported = True
|
||||
export_to_xml(module_store, content_store, location, root_dir, 'test_export')
|
||||
exported = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.assertTrue(bExported)
|
||||
self.assertTrue(exported)
|
||||
|
||||
class ContentStoreTest(ModuleStoreTestCase):
|
||||
"""
|
||||
@@ -428,7 +456,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
|
||||
def test_capa_module(self):
|
||||
"""Test that a problem treats markdown specially."""
|
||||
course = CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
|
||||
CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
|
||||
|
||||
problem_data = {
|
||||
'parent_location': 'i4x://MITx/999/course/Robot_Super_Course',
|
||||
@@ -450,10 +478,10 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
|
||||
def test_import_metadata_with_attempts_empty_string(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['simple'])
|
||||
ms = modulestore('direct')
|
||||
module_store = modulestore('direct')
|
||||
did_load_item = False
|
||||
try:
|
||||
ms.get_item(Location(['i4x', 'edX', 'simple', 'problem', 'ps01-simple', None]))
|
||||
module_store.get_item(Location(['i4x', 'edX', 'simple', 'problem', 'ps01-simple', None]))
|
||||
did_load_item = True
|
||||
except ItemNotFoundError:
|
||||
pass
|
||||
@@ -464,10 +492,10 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
def test_metadata_inheritance(self):
|
||||
import_from_xml(modulestore(), 'common/test/data/', ['full'])
|
||||
|
||||
ms = modulestore('direct')
|
||||
course = ms.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
|
||||
module_store = modulestore('direct')
|
||||
course = module_store.get_item(Location(['i4x', 'edX', 'full', 'course', '6.002_Spring_2012', None]))
|
||||
|
||||
verticals = ms.get_items(['i4x', 'edX', 'full', 'vertical', None, None])
|
||||
verticals = module_store.get_items(['i4x', 'edX', 'full', 'vertical', None, None])
|
||||
|
||||
# let's assert on the metadata_inheritance on an existing vertical
|
||||
for vertical in verticals:
|
||||
@@ -478,15 +506,15 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
|
||||
new_component_location = Location('i4x', 'edX', 'full', 'html', 'new_component')
|
||||
source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page')
|
||||
|
||||
|
||||
# crate a new module and add it as a child to a vertical
|
||||
ms.clone_item(source_template_location, new_component_location)
|
||||
module_store.clone_item(source_template_location, new_component_location)
|
||||
parent = verticals[0]
|
||||
ms.update_children(parent.location, parent.definition.get('children', []) + [new_component_location.url()])
|
||||
module_store.update_children(parent.location, parent.definition.get('children', []) + [new_component_location.url()])
|
||||
|
||||
# flush the cache
|
||||
ms.get_cached_metadata_inheritance_tree(new_component_location, -1)
|
||||
new_module = ms.get_item(new_component_location)
|
||||
module_store.get_cached_metadata_inheritance_tree(new_component_location, -1)
|
||||
new_module = module_store.get_item(new_component_location)
|
||||
|
||||
# check for grace period definition which should be defined at the course level
|
||||
self.assertIn('graceperiod', new_module.metadata)
|
||||
@@ -499,11 +527,11 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
# now let's define an override at the leaf node level
|
||||
#
|
||||
new_module.metadata['graceperiod'] = '1 day'
|
||||
ms.update_metadata(new_module.location, new_module.metadata)
|
||||
module_store.update_metadata(new_module.location, new_module.metadata)
|
||||
|
||||
# flush the cache and refetch
|
||||
ms.get_cached_metadata_inheritance_tree(new_component_location, -1)
|
||||
new_module = ms.get_item(new_component_location)
|
||||
module_store.get_cached_metadata_inheritance_tree(new_component_location, -1)
|
||||
new_module = module_store.get_item(new_component_location)
|
||||
|
||||
self.assertIn('graceperiod', new_module.metadata)
|
||||
self.assertEqual('1 day', new_module.metadata['graceperiod'])
|
||||
@@ -512,15 +540,15 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
class TemplateTestCase(ModuleStoreTestCase):
|
||||
|
||||
def test_template_cleanup(self):
|
||||
ms = modulestore('direct')
|
||||
module_store = modulestore('direct')
|
||||
|
||||
# insert a bogus template in the store
|
||||
bogus_template_location = Location('i4x', 'edx', 'templates', 'html', 'bogus')
|
||||
source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page')
|
||||
|
||||
ms.clone_item(source_template_location, bogus_template_location)
|
||||
|
||||
verify_create = ms.get_item(bogus_template_location)
|
||||
module_store.clone_item(source_template_location, bogus_template_location)
|
||||
|
||||
verify_create = module_store.get_item(bogus_template_location)
|
||||
self.assertIsNotNone(verify_create)
|
||||
|
||||
# now run cleanup
|
||||
@@ -529,10 +557,9 @@ class TemplateTestCase(ModuleStoreTestCase):
|
||||
# now try to find dangling template, it should not be in DB any longer
|
||||
asserted = False
|
||||
try:
|
||||
verify_create = ms.get_item(bogus_template_location)
|
||||
verify_create = module_store.get_item(bogus_template_location)
|
||||
except ItemNotFoundError:
|
||||
asserted = True
|
||||
|
||||
self.assertTrue(asserted)
|
||||
|
||||
self.assertTrue(asserted)
|
||||
|
||||
|
||||
@@ -86,12 +86,14 @@ def signup(request):
|
||||
csrf_token = csrf(request)['csrf_token']
|
||||
return render_to_response('signup.html', {'csrf': csrf_token})
|
||||
|
||||
|
||||
def old_login_redirect(request):
|
||||
'''
|
||||
Redirect to the active login url.
|
||||
'''
|
||||
return redirect('login', permanent=True)
|
||||
|
||||
|
||||
@ssl_login_shortcut
|
||||
@ensure_csrf_cookie
|
||||
def login_page(request):
|
||||
@@ -104,6 +106,7 @@ def login_page(request):
|
||||
'forgot_password_link': "//{base}/#forgot-password-modal".format(base=settings.LMS_BASE),
|
||||
})
|
||||
|
||||
|
||||
def howitworks(request):
|
||||
if request.user.is_authenticated():
|
||||
return index(request)
|
||||
@@ -115,6 +118,7 @@ def ux_alerts(request):
|
||||
|
||||
# ==== Views for any logged-in user ==================================
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def index(request):
|
||||
@@ -148,6 +152,7 @@ def index(request):
|
||||
|
||||
# ==== Views with per-item permissions================================
|
||||
|
||||
|
||||
def has_access(user, location, role=STAFF_ROLE_NAME):
|
||||
'''
|
||||
Return True if user allowed to access this piece of data
|
||||
@@ -396,6 +401,7 @@ def preview_component(request, location):
|
||||
'editor': wrap_xmodule(component.get_html, component, 'xmodule_edit.html')(),
|
||||
})
|
||||
|
||||
|
||||
@expect_json
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
@@ -639,6 +645,17 @@ def delete_item(request):
|
||||
if item.location.revision is None and item.location.category == 'vertical' and delete_all_versions:
|
||||
modulestore('direct').delete_item(item.location)
|
||||
|
||||
# cdodge: we need to remove our parent's pointer to us so that it is no longer dangling
|
||||
|
||||
parent_locs = modulestore('direct').get_parent_locations(item_loc, None)
|
||||
|
||||
for parent_loc in parent_locs:
|
||||
parent = modulestore('direct').get_item(parent_loc)
|
||||
item_url = item_loc.url()
|
||||
if item_url in parent.definition["children"]:
|
||||
parent.definition["children"].remove(item_url)
|
||||
modulestore('direct').update_children(parent.location, parent.definition["children"])
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
@@ -712,6 +729,7 @@ def create_draft(request):
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
def publish_draft(request):
|
||||
@@ -741,6 +759,7 @@ def unpublish_unit(request):
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
def clone_item(request):
|
||||
@@ -771,8 +790,7 @@ def clone_item(request):
|
||||
|
||||
return HttpResponse(json.dumps({'id': dest_location.url()}))
|
||||
|
||||
#@login_required
|
||||
#@ensure_csrf_cookie
|
||||
|
||||
def upload_asset(request, org, course, coursename):
|
||||
'''
|
||||
cdodge: this method allows for POST uploading of files into the course asset library, which will
|
||||
@@ -834,6 +852,7 @@ def upload_asset(request, org, course, coursename):
|
||||
response['asset_url'] = StaticContent.get_url_path_from_location(content.location)
|
||||
return response
|
||||
|
||||
|
||||
'''
|
||||
This view will return all CMS users who are editors for the specified course
|
||||
'''
|
||||
@@ -866,6 +885,7 @@ def create_json_response(errmsg = None):
|
||||
|
||||
return resp
|
||||
|
||||
|
||||
'''
|
||||
This POST-back view will add a user - specified by email - to the list of editors for
|
||||
the specified course
|
||||
@@ -898,6 +918,7 @@ def add_user(request, location):
|
||||
|
||||
return create_json_response()
|
||||
|
||||
|
||||
'''
|
||||
This POST-back view will remove a user - specified by email - from the list of editors for
|
||||
the specified course
|
||||
@@ -929,6 +950,7 @@ def remove_user(request, location):
|
||||
def landing(request, org, course, coursename):
|
||||
return render_to_response('temp-course-landing.html', {})
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def static_pages(request, org, course, coursename):
|
||||
@@ -1032,6 +1054,7 @@ def edit_tabs(request, org, course, coursename):
|
||||
'components': components
|
||||
})
|
||||
|
||||
|
||||
def not_found(request):
|
||||
return render_to_response('error.html', {'error': '404'})
|
||||
|
||||
@@ -1067,6 +1090,7 @@ def course_info(request, org, course, name, provided_id=None):
|
||||
'handouts_location': Location(['i4x', org, course, 'course_info', 'handouts']).url()
|
||||
})
|
||||
|
||||
|
||||
@expect_json
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
@@ -1164,6 +1188,7 @@ def get_course_settings(request, org, course, name):
|
||||
"section": "details"})
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def course_config_graders_page(request, org, course, name):
|
||||
@@ -1187,6 +1212,7 @@ def course_config_graders_page(request, org, course, name):
|
||||
'course_details': json.dumps(course_details, cls=CourseSettingsEncoder)
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def course_config_advanced_page(request, org, course, name):
|
||||
@@ -1210,6 +1236,7 @@ def course_config_advanced_page(request, org, course, name):
|
||||
'advanced_dict' : json.dumps(CourseMetadata.fetch(location)),
|
||||
})
|
||||
|
||||
|
||||
@expect_json
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
@@ -1241,6 +1268,7 @@ def course_settings_updates(request, org, course, name, section):
|
||||
return HttpResponse(json.dumps(manager.update_from_json(request.POST), cls=CourseSettingsEncoder),
|
||||
mimetype="application/json")
|
||||
|
||||
|
||||
@expect_json
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
@@ -1275,7 +1303,7 @@ def course_grader_updates(request, org, course, name, grader_index=None):
|
||||
return HttpResponse(json.dumps(CourseGradingModel.update_grader_from_json(Location(['i4x', org, course, 'course', name]), request.POST)),
|
||||
mimetype="application/json")
|
||||
|
||||
|
||||
|
||||
## NB: expect_json failed on ["key", "key2"] and json payload
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
@@ -1366,6 +1394,7 @@ def asset_index(request, org, course, name):
|
||||
def edge(request):
|
||||
return render_to_response('university_profiles/edge.html', {})
|
||||
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
def create_new_course(request):
|
||||
@@ -1421,6 +1450,7 @@ def create_new_course(request):
|
||||
|
||||
return HttpResponse(json.dumps({'id': new_course.location.url()}))
|
||||
|
||||
|
||||
def initialize_course_tabs(course):
|
||||
# set up the default tabs
|
||||
# I've added this because when we add static tabs, the LMS either expects a None for the tabs list or
|
||||
@@ -1438,6 +1468,7 @@ def initialize_course_tabs(course):
|
||||
|
||||
modulestore('direct').update_metadata(course.location.url(), course.own_metadata)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@login_required
|
||||
def import_course(request, org, course, name):
|
||||
@@ -1515,6 +1546,7 @@ def import_course(request, org, course, name):
|
||||
course_module.location.name])
|
||||
})
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@login_required
|
||||
def generate_export_course(request, org, course, name):
|
||||
@@ -1566,6 +1598,7 @@ def export_course(request, org, course, name):
|
||||
'successful_import_redirect_url': ''
|
||||
})
|
||||
|
||||
|
||||
def event(request):
|
||||
'''
|
||||
A noop to swallow the analytics call so that cms methods don't spook and poor developers looking at
|
||||
|
||||
@@ -10,7 +10,7 @@ class CourseMetadata(object):
|
||||
'''
|
||||
# __new_advanced_key__ is used by client not server; so, could argue against it being here
|
||||
FILTERED_LIST = XModuleDescriptor.system_metadata_fields + ['start', 'end', 'enrollment_start', 'enrollment_end', 'tabs', 'graceperiod', '__new_advanced_key__']
|
||||
|
||||
|
||||
@classmethod
|
||||
def fetch(cls, course_location):
|
||||
"""
|
||||
@@ -18,17 +18,17 @@ class CourseMetadata(object):
|
||||
"""
|
||||
if not isinstance(course_location, Location):
|
||||
course_location = Location(course_location)
|
||||
|
||||
|
||||
course = {}
|
||||
|
||||
|
||||
descriptor = get_modulestore(course_location).get_item(course_location)
|
||||
|
||||
|
||||
for k, v in descriptor.metadata.iteritems():
|
||||
if k not in cls.FILTERED_LIST:
|
||||
course[k] = v
|
||||
|
||||
|
||||
return course
|
||||
|
||||
|
||||
@classmethod
|
||||
def update_from_json(cls, course_location, jsondict):
|
||||
"""
|
||||
@@ -37,7 +37,7 @@ class CourseMetadata(object):
|
||||
Ensures none of the fields are in the blacklist.
|
||||
"""
|
||||
descriptor = get_modulestore(course_location).get_item(course_location)
|
||||
|
||||
|
||||
dirty = False
|
||||
|
||||
for k, v in jsondict.iteritems():
|
||||
@@ -45,26 +45,26 @@ class CourseMetadata(object):
|
||||
if k not in cls.FILTERED_LIST and (k not in descriptor.metadata or descriptor.metadata[k] != v):
|
||||
dirty = True
|
||||
descriptor.metadata[k] = v
|
||||
|
||||
|
||||
if dirty:
|
||||
get_modulestore(course_location).update_metadata(course_location, descriptor.metadata)
|
||||
|
||||
|
||||
# Could just generate and return a course obj w/o doing any db reads, but I put the reads in as a means to confirm
|
||||
# it persisted correctly
|
||||
return cls.fetch(course_location)
|
||||
|
||||
|
||||
@classmethod
|
||||
def delete_key(cls, course_location, payload):
|
||||
'''
|
||||
Remove the given metadata key(s) from the course. payload can be a single key or [key..]
|
||||
'''
|
||||
descriptor = get_modulestore(course_location).get_item(course_location)
|
||||
|
||||
|
||||
for key in payload['deleteKeys']:
|
||||
if key in descriptor.metadata:
|
||||
del descriptor.metadata[key]
|
||||
|
||||
|
||||
get_modulestore(course_location).update_metadata(course_location, descriptor.metadata)
|
||||
|
||||
|
||||
return cls.fetch(course_location)
|
||||
|
||||
@@ -62,3 +62,6 @@ AWS_SECRET_ACCESS_KEY = AUTH_TOKENS["AWS_SECRET_ACCESS_KEY"]
|
||||
DATABASES = AUTH_TOKENS['DATABASES']
|
||||
MODULESTORE = AUTH_TOKENS['MODULESTORE']
|
||||
CONTENTSTORE = AUTH_TOKENS['CONTENTSTORE']
|
||||
|
||||
# Datadog for events!
|
||||
DATADOG_API = AUTH_TOKENS.get("DATADOG_API")
|
||||
6
cms/one_time_startup.py
Normal file
6
cms/one_time_startup.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from dogapi import dog_http_api, dog_stats_api
|
||||
from django.conf import settings
|
||||
|
||||
if hasattr(settings, 'DATADOG_API'):
|
||||
dog_http_api.api_key = settings.DATADOG_API
|
||||
dog_stats_api.start(api_key=settings.DATADOG_API, statsd=True)
|
||||
@@ -31,7 +31,8 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
|
||||
// because these are outside of this.$el, they can't be in the event hash
|
||||
$('.save-button').on('click', this, this.saveView);
|
||||
$('.cancel-button').on('click', this, this.revertView);
|
||||
this.model.on('error', this.handleValidationError, this);
|
||||
this.listenTo(this.model, 'error', CMS.ServerError);
|
||||
this.listenTo(this.model, 'invalid', this.handleValidationError);
|
||||
},
|
||||
render: function() {
|
||||
// catch potential outside call before template loaded
|
||||
@@ -228,7 +229,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
|
||||
var error = {};
|
||||
error[oldKey] = 'You have already defined "' + newKey + '" in the manual policy definitions.';
|
||||
error[newKey] = "You tried to enter a duplicate of this key.";
|
||||
this.model.trigger("error", this.model, error);
|
||||
this.model.trigger("invalid", this.model, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -244,7 +245,7 @@ CMS.Views.Settings.Advanced = CMS.Views.ValidatingView.extend({
|
||||
// swap to the key which the map knows about
|
||||
validation[oldKey] = validation[newKey];
|
||||
}
|
||||
this.model.trigger("error", this.model, validation);
|
||||
this.model.trigger("invalid", this.model, validation);
|
||||
// abandon update
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ CMS.Views.Settings.Details = CMS.Views.ValidatingView.extend({
|
||||
var dateIntrospect = new Date();
|
||||
this.$el.find('#timezone').html("(" + dateIntrospect.getTimezone() + ")");
|
||||
|
||||
this.model.on('error', this.handleValidationError, this);
|
||||
this.listenTo(this.model, 'error', CMS.ServerError);
|
||||
this.listenTo(this.model, 'invalid', this.handleValidationError);
|
||||
this.selectorToField = _.invert(this.fieldToSelectorMap);
|
||||
},
|
||||
|
||||
|
||||
@@ -44,7 +44,8 @@ CMS.Views.Settings.Grading = CMS.Views.ValidatingView.extend({
|
||||
self.render();
|
||||
}
|
||||
);
|
||||
this.model.on('error', this.handleValidationError, this);
|
||||
this.listenTo(this.model, 'error', CMS.ServerError);
|
||||
this.listenTo(this.model, 'invalid', this.handleValidationError);
|
||||
this.model.get('graders').on('remove', this.render, this);
|
||||
this.model.get('graders').on('reset', this.render, this);
|
||||
this.model.get('graders').on('add', this.render, this);
|
||||
@@ -316,7 +317,8 @@ CMS.Views.Settings.GraderView = CMS.Views.ValidatingView.extend({
|
||||
'blur :input' : "inputUnfocus"
|
||||
},
|
||||
initialize : function() {
|
||||
this.model.on('error', this.handleValidationError, this);
|
||||
this.listenTo(this.model, 'error', CMS.ServerError);
|
||||
this.listenTo(this.model, 'invalid', this.handleValidationError);
|
||||
this.selectorToField = _.invert(this.fieldToSelectorMap);
|
||||
this.render();
|
||||
},
|
||||
|
||||
@@ -3,7 +3,8 @@ CMS.Views.ValidatingView = Backbone.View.extend({
|
||||
// decorates the fields. Needs wiring per class, but this initialization shows how
|
||||
// either have your init call this one or copy the contents
|
||||
initialize : function() {
|
||||
this.model.on('error', this.handleValidationError, this);
|
||||
this.listenTo(this.model, 'error', CMS.ServerError);
|
||||
this.listenTo(this.model, 'invalid', this.handleValidationError);
|
||||
this.selectorToField = _.invert(this.fieldToSelectorMap);
|
||||
},
|
||||
|
||||
@@ -18,20 +19,11 @@ CMS.Views.ValidatingView = Backbone.View.extend({
|
||||
// which may be the subjects of validation errors
|
||||
},
|
||||
_cacheValidationErrors : [],
|
||||
|
||||
handleValidationError : function(model, error) {
|
||||
// error triggered either by validation or server error
|
||||
// error is object w/ fields and error strings
|
||||
for (var field in error) {
|
||||
var ele = this.$el.find('#' + this.fieldToSelectorMap[field]);
|
||||
if (ele.length === 0) {
|
||||
// check if it might a server error: note a typo in the field name
|
||||
// or failure to put in a map may cause this to muffle validation errors
|
||||
if (_.has(error, 'error') && _.has(error, 'responseText')) {
|
||||
CMS.ServerError(model, error);
|
||||
return;
|
||||
}
|
||||
else continue;
|
||||
}
|
||||
this._cacheValidationErrors.push(ele);
|
||||
if ($(ele).is('div')) {
|
||||
// put error on the contained inputs
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls import patterns, include, url
|
||||
from . import one_time_startup
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
# from django.contrib import admin
|
||||
|
||||
Reference in New Issue
Block a user