Commit c069d637 by Jason Bau

Merge tag 'release-2013-12-19' into edx-west/rc-20140108

@jbau: also did manually removed MITX_FEATURES and mitxmako refs
in edx-west parts of code
Release for Dec 19, 2013

Conflicts:
	cms/envs/test.py
	common/djangoapps/external_auth/views.py
	common/djangoapps/student/tests/factories.py
	common/djangoapps/student/views.py
	lms/djangoapps/branding/tests.py
	lms/djangoapps/branding/views.py
	lms/djangoapps/courseware/tests/test_views.py
	lms/djangoapps/instructor/views/legacy.py
	lms/templates/courseware/course_about.html
	lms/templates/navigation.html
	lms/urls.py
parents f16c0df8 0d91e61e
......@@ -65,7 +65,7 @@ James Tauber <jtauber@jtauber.com>
Greg Price <gprice@edx.org>
Joe Blaylock <jrbl@stanford.edu>
Sef Kloninger <sef@kloninger.com>
Anto Stupak <s2pak.anton@gmail.com>
Anton Stupak <s2pak.anton@gmail.com>
David Adams <dcadams@stanford.edu>
Steve Strassmann <straz@edx.org>
Giulio Gratta <giulio@giuliogratta.com>
......
......@@ -5,6 +5,36 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
Blades: Make LTI module not send grade_back_url if has_score=False. BLD-561.
Blades: LTI additional Python tests. LTI must use HTTPS for
lis_outcome_service_url. BLD-564.
Blades: Fix bug when Image mapping problems are not working for students in IE. BLD-413.
Blades: Add template that displays the most up-to-date features of
drag-and-drop. BLD-479.
Blades: LTI fix bug e-reader error when popping out window. BLD-465.
Common: Switch from mitx.db to edx.db for sqlite databases. This will effectively
reset state for local instances of the code, unless you manually rename your
mitx.db file to edx.db.
Common: significant performance improvement for authorization checks and location translations.
Ensure all auth checks, check all possible permutations of the auth key (Instructor dashboard
now shows when it should for all courses in lms).
Made queries for Studio dashboard 2 orders of magnitude faster (and fewer).
Blades: Video Transcripts: Fix clear and download buttons. BLD-438.
Common: Switch over from MITX_FEATURES to just FEATURES. To override items in
the FEATURES dict, the environment variable you must set to do so is also
now called FEATURES instead of MITX_FEATURES.
LMS: Change the forum role granted to global staff on enrollment in a
course. Previously, staff were given the Moderator role; now, they are
given the Student role.
Blades: Fix Numerical input to support mathematical operations. BLD-525.
Blades: Improve calculator's tooltip accessibility. Add possibility to navigate
......
......@@ -63,7 +63,7 @@ def get_all_course_role_groupnames(location, role, use_filter=True):
# filter to the ones which exist
default = groupnames[0]
if use_filter:
groupnames = [group for group in groupnames if Group.objects.filter(name=group).exists()]
groupnames = [group.name for group in Group.objects.filter(name__in=groupnames)]
return groupnames, default
......@@ -203,12 +203,8 @@ def remove_user_from_course_group(caller, user, location, role):
# see if the user is actually in that role, if not then we don't have to do anything
groupnames, _ = get_all_course_role_groupnames(location, role)
for groupname in groupnames:
groups = user.groups.filter(name=groupname)
if groups:
# will only be one with that name
user.groups.remove(groups[0])
user.save()
user.groups.remove(*user.groups.filter(name__in=groupnames))
user.save()
def remove_user_from_creator_group(caller, user):
......@@ -243,7 +239,7 @@ def is_user_in_course_group_role(user, location, role, check_staff=True):
if check_staff and user.is_staff:
return True
groupnames, _ = get_all_course_role_groupnames(location, role)
return any(user.groups.filter(name=groupname).exists() for groupname in groupnames)
return user.groups.filter(name__in=groupnames).exists()
return False
......@@ -261,12 +257,12 @@ def is_user_in_creator_group(user):
return True
# On edx, we only allow edX staff to create courses. This may be relaxed in the future.
if settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False):
if settings.FEATURES.get('DISABLE_COURSE_CREATION', False):
return False
# Feature flag for using the creator group setting. Will be removed once the feature is complete.
if settings.MITX_FEATURES.get('ENABLE_CREATOR_GROUP', False):
return user.groups.filter(name=COURSE_CREATOR_GROUP_NAME).count() > 0
if settings.FEATURES.get('ENABLE_CREATOR_GROUP', False):
return user.groups.filter(name=COURSE_CREATOR_GROUP_NAME).exists()
return True
......
......@@ -5,6 +5,7 @@ import mock
from django.test import TestCase
from django.contrib.auth.models import User
from xmodule.modulestore import Location
from django.core.exceptions import PermissionDenied
from auth.authz import add_user_to_creator_group, remove_user_from_creator_group, is_user_in_creator_group,\
......@@ -33,7 +34,7 @@ class CreatorGroupTest(TestCase):
def test_creator_group_enabled_but_empty(self):
""" Tests creator group feature on, but group empty. """
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {"ENABLE_CREATOR_GROUP": True}):
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
self.assertFalse(is_user_in_creator_group(self.user))
# Make user staff. This will cause is_user_in_creator_group to return True.
......@@ -42,7 +43,7 @@ class CreatorGroupTest(TestCase):
def test_creator_group_enabled_nonempty(self):
""" Tests creator group feature on, user added. """
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {"ENABLE_CREATOR_GROUP": True}):
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
self.assertTrue(add_user_to_creator_group(self.admin, self.user))
self.assertTrue(is_user_in_creator_group(self.user))
......@@ -70,7 +71,7 @@ class CreatorGroupTest(TestCase):
def test_course_creation_disabled(self):
""" Tests that the COURSE_CREATION_DISABLED flag overrides course creator group settings. """
with mock.patch.dict('django.conf.settings.MITX_FEATURES',
with mock.patch.dict('django.conf.settings.FEATURES',
{'DISABLE_COURSE_CREATION': True, "ENABLE_CREATOR_GROUP": True}):
# Add user to creator group.
self.assertTrue(add_user_to_creator_group(self.admin, self.user))
......@@ -129,7 +130,7 @@ class CourseGroupTest(TestCase):
""" Test case setup """
self.creator = User.objects.create_user('testcreator', 'testcreator+courses@edx.org', 'foo')
self.staff = User.objects.create_user('teststaff', 'teststaff+courses@edx.org', 'foo')
self.location = 'i4x', 'mitX', '101', 'course', 'test'
self.location = Location('i4x', 'mitX', '101', 'course', 'test')
def test_add_user_to_course_group(self):
"""
......@@ -181,7 +182,7 @@ class CourseGroupTest(TestCase):
create_all_course_groups(self.creator, self.location)
add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME)
location2 = 'i4x', 'mitX', '103', 'course', 'test2'
location2 = Location('i4x', 'mitX', '103', 'course', 'test2')
staff2 = User.objects.create_user('teststaff2', 'teststaff2+courses@edx.org', 'foo')
create_all_course_groups(self.creator, location2)
add_user_to_course_group(self.creator, staff2, location2, STAFF_ROLE_NAME)
......@@ -193,7 +194,7 @@ class CourseGroupTest(TestCase):
create_all_course_groups(self.creator, self.location)
add_user_to_course_group(self.creator, self.staff, self.location, STAFF_ROLE_NAME)
location2 = 'i4x', 'mitX', '103', 'course', 'test2'
location2 = Location('i4x', 'mitX', '103', 'course', 'test2')
creator2 = User.objects.create_user('testcreator2', 'testcreator2+courses@edx.org', 'foo')
staff2 = User.objects.create_user('teststaff2', 'teststaff2+courses@edx.org', 'foo')
create_all_course_groups(creator2, location2)
......
......@@ -161,9 +161,14 @@ def verify_all_setting_entries(expected_entries):
@world.absorb
def save_component_and_reopen(step):
def save_component(step):
world.css_click("a.save-button")
world.wait_for_ajax_complete()
@world.absorb
def save_component_and_reopen(step):
save_component(step)
# We have a known issue that modifications are still shown within the edit window after cancel (though)
# they are not persisted. Refresh the browser to make sure the changes WERE persisted after Save.
reload_the_page(step)
......
@shard_1
Feature: Course export
I want to export my course to a tar.gz file to share with others or check into source control
Scenario: User is directed to unit with bad XML when export fails
Given I am in Studio editing a new unit
When I add a "Blank Advanced Problem" "Advanced Problem" component
And I edit and enter bad XML
And I export the course
Then I get an error dialog
And I can click to go to the unit with the error
# disable missing docstring
#pylint: disable=C0111
from lettuce import world, step
from common import type_in_codemirror
from nose.tools import assert_true, assert_equal
@step('I export the course$')
def i_export_the_course(step):
world.click_tools()
link_css = 'li.nav-course-tools-export a'
world.css_click(link_css)
world.css_click('a.action-export')
@step('I edit and enter bad XML$')
def i_enter_bad_xml(step):
world.edit_component()
type_in_codemirror(
0,
"""<problem><h1>Smallest Canvas</h1>
<p>You want to make the smallest canvas you can.</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false"><verbatim><canvas id="myCanvas" width = 10 height = 100> </canvas></verbatim></choice>
<choice correct="true"><code><canvas id="myCanvas" width = 10 height = 10> </canvas></code></choice>
</choicegroup>
</multiplechoiceresponse>
</problem>"""
)
world.save_component(step)
@step('I get an error dialog$')
def get_an_error_dialog(step):
assert_true(world.is_css_present("div.prompt.error"))
@step('I can click to go to the unit with the error$')
def i_click_on_error_dialog(step):
world.click_link_by_text('Correct failed component')
assert_true(world.css_html("span.inline-error").startswith("Problem i4x://MITx/999/problem"))
assert_equal(1, world.browser.url.count("unit/MITx.999.Robot_Super_Course/branch/draft/block/vertical"))
......@@ -653,3 +653,25 @@ Feature: Video Component Editor
Then when I view the video it does show the captions
And I see "LILA FISHER: Hi, welcome to Edx." text in the captions
#35
Scenario: After reverting Transcripts field in the Advanced tab "not found" message should be visible
Given I have created a Video component
And I edit the component
And I enter a "t_not_exist.mp4" source to field number 1
Then I see status message "not found"
And I upload the transcripts file "chinese_transcripts.srt"
Then I see status message "uploaded_successfully"
And I save changes
Then I see "好 各位同学" text in the captions
And I edit the component
And I open tab "Advanced"
And I revert the transcript field"HTML5 Transcript"
And I save changes
Then when I view the video it does not show the captions
And I edit the component
Then I see status message "not found"
......@@ -9,7 +9,7 @@ from django.conf import settings
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError
from splinter.request_handler.request_handler import RequestHandler
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
......@@ -49,6 +49,13 @@ TRANSCRIPTS_BUTTONS = {
}
def _clear_field(index):
world.css_fill(SELECTORS['url_inputs'], '', index)
# In some reason chromeDriver doesn't trigger 'input' event after filling
# field by an empty value. That's why we trigger it manually via jQuery.
world.trigger_event(SELECTORS['url_inputs'], event='input', index=index)
@step('I clear fields$')
def clear_fields(_step):
js_str = '''
......@@ -60,16 +67,18 @@ def clear_fields(_step):
for index in range(1, 4):
js = js_str.format(selector=SELECTORS['url_inputs'], index=index - 1)
world.browser.execute_script(js)
_step.given('I clear field number {0}'.format(index))
_clear_field(index)
world.wait(DELAY)
world.wait_for_ajax_complete()
@step('I clear field number (.+)$')
def clear_field(_step, index):
index = int(index) - 1
world.css_fill(SELECTORS['url_inputs'], '', index)
# In some reason chromeDriver doesn't trigger 'input' event after filling
# field by an empty value. That's why we trigger it manually via jQuery.
world.trigger_event(SELECTORS['url_inputs'], event='input', index=index)
_clear_field(index)
world.wait(DELAY)
world.wait_for_ajax_complete()
@step('I expect (.+) inputs are disabled$')
......@@ -91,40 +100,32 @@ def inputs_are_enabled(_step):
@step('I do not see error message$')
def i_do_not_see_error_message(_step):
world.wait(DELAY)
assert not world.css_visible(SELECTORS['error_bar'])
@step('I see error message "([^"]*)"$')
def i_see_error_message(_step, error):
world.wait(DELAY)
assert world.css_has_text(SELECTORS['error_bar'], ERROR_MESSAGES[error.strip()])
@step('I do not see status message$')
def i_do_not_see_status_message(_step):
world.wait(DELAY)
world.wait_for_ajax_complete()
assert not world.css_visible(SELECTORS['status_bar'])
@step('I see status message "([^"]*)"$')
def i_see_status_message(_step, status):
world.wait(DELAY)
world.wait_for_ajax_complete()
assert not world.css_visible(SELECTORS['error_bar'])
assert world.css_has_text(SELECTORS['status_bar'], STATUSES[status.strip()])
DOWNLOAD_BUTTON = TRANSCRIPTS_BUTTONS["download_to_edit"][0]
if world.is_css_present(DOWNLOAD_BUTTON, wait_time=1) \
and not world.css_find(DOWNLOAD_BUTTON)[0].has_class('is-disabled'):
assert _transcripts_are_downloaded()
@step('I (.*)see button "([^"]*)"$')
def i_see_button(_step, not_see, button_type):
world.wait(DELAY)
world.wait_for_ajax_complete()
button = button_type.strip()
if not_see.strip():
......@@ -135,9 +136,6 @@ def i_see_button(_step, not_see, button_type):
@step('I (.*)see (.*)button "([^"]*)" number (\d+)$')
def i_see_button_with_custom_text(_step, not_see, button_type, custom_text, index):
world.wait(DELAY)
world.wait_for_ajax_complete()
button = button_type.strip()
custom_text = custom_text.strip()
index = int(index.strip()) - 1
......@@ -150,22 +148,18 @@ def i_see_button_with_custom_text(_step, not_see, button_type, custom_text, inde
@step('I click transcript button "([^"]*)"$')
def click_button_transcripts_variant(_step, button_type):
world.wait(DELAY)
world.wait_for_ajax_complete()
button = button_type.strip()
world.css_click(TRANSCRIPTS_BUTTONS[button][0])
world.wait_for_ajax_complete()
@step('I click transcript button "([^"]*)" number (\d+)$')
def click_button_index(_step, button_type, index):
world.wait(DELAY)
world.wait_for_ajax_complete()
button = button_type.strip()
index = int(index.strip()) - 1
world.css_click(TRANSCRIPTS_BUTTONS[button][0], index)
world.wait_for_ajax_complete()
@step('I remove "([^"]+)" transcripts id from store')
......@@ -187,9 +181,6 @@ def remove_transcripts_from_store(_step, subs_id):
@step('I enter a "([^"]+)" source to field number (\d+)$')
def i_enter_a_source(_step, link, index):
world.wait(DELAY)
world.wait_for_ajax_complete()
index = int(index) - 1
if index is not 0 and not world.css_visible(SELECTORS['collapse_bar']):
......@@ -198,6 +189,8 @@ def i_enter_a_source(_step, link, index):
assert world.css_visible(SELECTORS['collapse_bar'])
world.css_fill(SELECTORS['url_inputs'], link, index)
world.wait(DELAY)
world.wait_for_ajax_complete()
@step('I upload the transcripts file "([^"]*)"$')
......@@ -205,6 +198,7 @@ def upload_file(_step, file_name):
path = os.path.join(TEST_ROOT, 'uploads/', file_name.strip())
world.browser.execute_script("$('form.file-chooser').show()")
world.browser.attach_file('file', os.path.abspath(path))
world.wait_for_ajax_complete()
@step('I see "([^"]*)" text in the captions')
......@@ -214,9 +208,6 @@ def check_text_in_the_captions(_step, text):
@step('I see value "([^"]*)" in the field "([^"]*)"$')
def check_transcripts_field(_step, values, field_name):
world.wait(DELAY)
world.wait_for_ajax_complete()
world.click_link_by_text('Advanced')
field_id = '#' + world.browser.find_by_xpath('//label[text()="%s"]' % field_name.strip())[0]['for']
values_list = [i.strip() == world.css_value(field_id) for i in values.split('|')]
......@@ -226,22 +217,34 @@ def check_transcripts_field(_step, values, field_name):
@step('I save changes$')
def save_changes(_step):
world.wait(DELAY)
world.wait_for_ajax_complete()
save_css = 'a.save-button'
world.css_click(save_css)
world.wait_for_ajax_complete()
@step('I open tab "([^"]*)"$')
def open_tab(_step, tab_name):
world.click_link_by_text(tab_name.strip())
world.wait_for_ajax_complete()
@step('I set value "([^"]*)" to the field "([^"]*)"$')
def set_value_transcripts_field(_step, value, field_name):
world.wait(DELAY)
world.wait_for_ajax_complete()
field_id = '#' + world.browser.find_by_xpath('//label[text()="%s"]' % field_name.strip())[0]['for']
world.css_fill(field_id, value.strip())
world.wait_for_ajax_complete()
@step('I revert the transcript field "([^"]*)"$')
def revert_transcripts_field(_step, field_name):
world.revert_setting_entry(field_name)
def _transcripts_are_downloaded():
world.wait_for_ajax_complete()
request = RequestHandler()
DOWNLOAD_BUTTON = world.css_find(TRANSCRIPTS_BUTTONS["download_to_edit"][0]).first
url = DOWNLOAD_BUTTON['href']
request.connect(url)
return request.status_code.is_success()
......@@ -2,6 +2,7 @@ from django.core.management.base import BaseCommand, CommandError
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.xml_importer import check_module_metadata_editability
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore import Location
class Command(BaseCommand):
......@@ -54,8 +55,16 @@ class Command(BaseCommand):
discussion_items = _get_discussion_items(course)
# now query all discussion items via get_items() and compare with the tree-traversal
queried_discussion_items = store.get_items(['i4x', course.location.org, course.location.course,
'discussion', None, None])
queried_discussion_items = store.get_items(
Location(
'i4x',
course.location.org,
course.location.course,
'discussion',
None,
None
)
)
for item in queried_discussion_items:
if item.location.url() not in discussion_items:
......
......@@ -11,7 +11,7 @@ from auth.authz import _copy_course_group
#
# To run from command line: rake cms:clone SOURCE_LOC=MITx/111/Foo1 DEST_LOC=MITx/135/Foo3
# To run from command line: rake cms:clone SOURCE_LOC=edX/111/Foo1 DEST_LOC=edX/135/Foo3
#
class Command(BaseCommand):
"""Clone a MongoDB-backed course to another location"""
......
......@@ -7,7 +7,7 @@ from contentstore.utils import delete_course_and_groups
#
# To run from command line: rake cms:delete_course LOC=MITx/111/Foo1
# To run from command line: rake cms:delete_course LOC=edX/111/Foo1
#
class Command(BaseCommand):
help = '''Delete a MongoDB backed course'''
......@@ -23,7 +23,7 @@ class Command(BaseCommand):
commit = args[1] == 'commit'
if commit:
print 'Actually going to delete the course from DB....'
print('Actually going to delete the course from DB....')
if query_yes_no("Deleting course {0}. Confirm?".format(course_id), default="no"):
if query_yes_no("Are you sure. This action cannot be undone!", default="no"):
......
......@@ -31,9 +31,16 @@ class Command(BaseCommand):
course_dirs = args[1:]
else:
course_dirs = None
print("Importing. Data_dir={data}, course_dirs={courses}".format(
self.stdout.write("Importing. Data_dir={data}, course_dirs={courses}\n".format(
data=data_dir,
courses=course_dirs,
dis=do_import_static))
import_from_xml(modulestore('direct'), data_dir, course_dirs, load_error_modules=False,
try:
mstore = modulestore('direct')
except KeyError:
self.stdout.write('Unable to load direct modulestore, trying '
'default\n')
mstore = modulestore('default')
import_from_xml(mstore, data_dir, course_dirs, load_error_modules=False,
static_content_store=contentstore(), verbose=True, do_import_static=do_import_static)
......@@ -106,7 +106,7 @@ class CourseDetailsTestCase(CourseTestCase):
def test_marketing_site_fetch(self):
settings_details_url = self.course_locator.url_reverse('settings/details/')
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': True}):
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}):
response = self.client.get_html(settings_details_url)
self.assertNotContains(response, "Course Summary Page")
self.assertNotContains(response, "Send a note to students via email")
......@@ -127,7 +127,7 @@ class CourseDetailsTestCase(CourseTestCase):
def test_regular_site_fetch(self):
settings_details_url = self.course_locator.url_reverse('settings/details/')
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': False}):
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': False}):
response = self.client.get_html(settings_details_url)
self.assertContains(response, "Course Summary Page")
self.assertContains(response, "Send a note to students via email")
......
......@@ -20,7 +20,7 @@ class CMSLogTest(TestCase):
{"event": "my_event", "event_type": "my_event_type", "page": "my_page"},
{"event": "{'json': 'object'}", "event_type": unichr(512), "page": "my_page"}
]
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_SQL_TRACKING_LOGS': True}):
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_SQL_TRACKING_LOGS': True}):
for request_params in requests:
response = self.client.post(reverse(cms_user_track), request_params)
self.assertEqual(response.status_code, 204)
......@@ -34,7 +34,7 @@ class CMSLogTest(TestCase):
{"event": "my_event", "event_type": "my_event_type", "page": "my_page"},
{"event": "{'json': 'object'}", "event_type": unichr(512), "page": "my_page"}
]
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_SQL_TRACKING_LOGS': True}):
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_SQL_TRACKING_LOGS': True}):
for request_params in requests:
response = self.client.get(reverse(cms_user_track), request_params)
self.assertEqual(response.status_code, 204)
......@@ -14,11 +14,7 @@ class TextbookIndexTestCase(CourseTestCase):
def setUp(self):
"Set the URL for tests"
super(TextbookIndexTestCase, self).setUp()
self.url = reverse('textbook_index', kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
})
self.url = self.course_locator.url_reverse('textbooks')
def test_view_index(self):
"Basic check that the textbook index page responds correctly"
......@@ -77,13 +73,13 @@ class TextbookIndexTestCase(CourseTestCase):
obj = json.loads(resp.content)
self.assertEqual(content, obj)
def test_view_index_xhr_post(self):
def test_view_index_xhr_put(self):
"Check that you can save information to the server"
textbooks = [
{"tab_title": "Hi, mom!"},
{"tab_title": "Textbook 2"},
]
resp = self.client.post(
resp = self.client.put(
self.url,
data=json.dumps(textbooks),
content_type="application/json",
......@@ -102,9 +98,9 @@ class TextbookIndexTestCase(CourseTestCase):
no_ids.append(textbook)
self.assertEqual(no_ids, textbooks)
def test_view_index_xhr_post_invalid(self):
def test_view_index_xhr_put_invalid(self):
"Check that you can't save invalid JSON"
resp = self.client.post(
resp = self.client.put(
self.url,
data="invalid",
content_type="application/json",
......@@ -122,11 +118,7 @@ class TextbookCreateTestCase(CourseTestCase):
def setUp(self):
"Set up a url and some textbook content for tests"
super(TextbookCreateTestCase, self).setUp()
self.url = reverse('create_textbook', kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
})
self.url = self.course_locator.url_reverse('textbooks')
self.textbook = {
"tab_title": "Economics",
"chapters": {
......@@ -151,15 +143,6 @@ class TextbookCreateTestCase(CourseTestCase):
del textbook["id"]
self.assertEqual(self.textbook, textbook)
def test_get(self):
"Test that GET is not allowed"
resp = self.client.get(
self.url,
HTTP_ACCEPT="application/json",
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(resp.status_code, 405)
def test_valid_id(self):
"Textbook IDs must begin with a number; try a valid one"
self.textbook["id"] = "7x5"
......@@ -188,12 +171,12 @@ class TextbookCreateTestCase(CourseTestCase):
self.assertNotIn("Location", resp)
class TextbookByIdTestCase(CourseTestCase):
"Test cases for the `textbook_by_id` view"
class TextbookDetailTestCase(CourseTestCase):
"Test cases for the `textbook_detail_handler` view"
def setUp(self):
"Set some useful content and URLs for tests"
super(TextbookByIdTestCase, self).setUp()
super(TextbookDetailTestCase, self).setUp()
self.textbook1 = {
"tab_title": "Economics",
"id": 1,
......@@ -202,12 +185,7 @@ class TextbookByIdTestCase(CourseTestCase):
"url": "/a/b/c/ch1.pdf",
}
}
self.url1 = reverse('textbook_by_id', kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
'tid': 1,
})
self.url1 = self.course_locator.url_reverse("textbooks", "1")
self.textbook2 = {
"tab_title": "Algebra",
"id": 2,
......@@ -216,24 +194,14 @@ class TextbookByIdTestCase(CourseTestCase):
"url": "/a/b/ch11.pdf",
}
}
self.url2 = reverse('textbook_by_id', kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
'tid': 2,
})
self.url2 = self.course_locator.url_reverse("textbooks", "2")
self.course.pdf_textbooks = [self.textbook1, self.textbook2]
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
self.course.save()
self.store = get_modulestore(self.course.location)
self.store.update_metadata(self.course.location, own_metadata(self.course))
self.url_nonexist = reverse('textbook_by_id', kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
'tid': 20,
})
self.url_nonexist = self.course_locator.url_reverse("textbooks", "20")
def test_get_1(self):
"Get the first textbook"
......@@ -275,12 +243,7 @@ class TextbookByIdTestCase(CourseTestCase):
"url": "supercool.pdf",
"id": "1supercool",
}
url = reverse("textbook_by_id", kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
'tid': "1supercool",
})
url = self.course_locator.url_reverse("textbooks", "1supercool")
resp = self.client.post(
url,
data=json.dumps(textbook),
......
""" Tests for utils. """
from contentstore import utils
import mock
import unittest
import collections
import copy
import json
from uuid import uuid4
from django.test import TestCase
from xmodule.modulestore.tests.factories import CourseFactory
from django.test.utils import override_settings
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.exceptions import NotFoundError
from xmodule.modulestore import Location
class LMSLinksTestCase(TestCase):
......@@ -28,33 +21,33 @@ class LMSLinksTestCase(TestCase):
@override_settings(MKTG_URLS={'ROOT': 'dummy-root'})
def about_page_marketing_site_test(self):
""" Get URL for about page, marketing root present. """
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': True}):
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}):
self.assertEquals(self.get_about_page_link(), "//dummy-root/courses/mitX/101/test/about")
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': False}):
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': False}):
self.assertEquals(self.get_about_page_link(), "//localhost:8000/courses/mitX/101/test/about")
@override_settings(MKTG_URLS={'ROOT': 'http://www.dummy'})
def about_page_marketing_site_remove_http_test(self):
""" Get URL for about page, marketing root present, remove http://. """
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': True}):
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}):
self.assertEquals(self.get_about_page_link(), "//www.dummy/courses/mitX/101/test/about")
@override_settings(MKTG_URLS={'ROOT': 'https://www.dummy'})
def about_page_marketing_site_remove_https_test(self):
""" Get URL for about page, marketing root present, remove https://. """
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': True}):
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}):
self.assertEquals(self.get_about_page_link(), "//www.dummy/courses/mitX/101/test/about")
@override_settings(MKTG_URLS={'ROOT': 'www.dummyhttps://x'})
def about_page_marketing_site_https__edge_test(self):
""" Get URL for about page, only remove https:// at the beginning of the string. """
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': True}):
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}):
self.assertEquals(self.get_about_page_link(), "//www.dummyhttps://x/courses/mitX/101/test/about")
@override_settings(MKTG_URLS={})
def about_page_marketing_urls_not_set_test(self):
""" Error case. ENABLE_MKTG_SITE is True, but there is either no MKTG_URLS, or no MKTG_URLS Root property. """
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': True}):
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}):
self.assertEquals(self.get_about_page_link(), None)
@override_settings(LMS_BASE=None)
......@@ -64,12 +57,12 @@ class LMSLinksTestCase(TestCase):
def get_about_page_link(self):
""" create mock course and return the about page link """
location = 'i4x', 'mitX', '101', 'course', 'test'
location = Location('i4x', 'mitX', '101', 'course', 'test')
return utils.get_lms_link_for_about_page(location)
def lms_link_test(self):
""" Tests get_lms_link_for_item. """
location = 'i4x', 'mitX', '101', 'vertical', 'contacting_us'
location = Location('i4x', 'mitX', '101', 'vertical', 'contacting_us')
link = utils.get_lms_link_for_item(location, False, "mitX/101/test")
self.assertEquals(link, "//localhost:8000/courses/mitX/101/test/jump_to/i4x://mitX/101/vertical/contacting_us")
link = utils.get_lms_link_for_item(location, True, "mitX/101/test")
......@@ -80,7 +73,7 @@ class LMSLinksTestCase(TestCase):
# If no course_id is passed in, it is obtained from the location. This is the case for
# Studio dashboard.
location = 'i4x', 'mitX', '101', 'course', 'test'
location = Location('i4x', 'mitX', '101', 'course', 'test')
link = utils.get_lms_link_for_item(location)
self.assertEquals(
link,
......
......@@ -57,6 +57,7 @@ class AjaxEnabledTestClient(Client):
"""
return self.get(path, data or {}, follow, HTTP_ACCEPT="application/json", **extra)
@override_settings(MODULESTORE=TEST_MODULESTORE)
class CourseTestCase(ModuleStoreTestCase):
def setUp(self):
......@@ -111,7 +112,7 @@ class CourseTestCase(ModuleStoreTestCase):
client = Client()
client.login(username=uname, password=password)
return client, nonstaff
def populateCourse(self):
"""
Add 2 chapters, 4 sections, 8 verticals, 16 problems to self.course (branching 2)
......
......@@ -196,6 +196,7 @@ def remove_subs_from_store(subs_id, item):
try:
content = contentstore().find(content_location)
contentstore().delete(content.get_id())
del_cached_content(content.location)
log.info("Removed subs %s from store", subs_id)
except NotFoundError:
pass
......@@ -310,7 +311,9 @@ def manage_video_subtitles_save(old_item, new_item):
Video player item has some video fields: HTML5 ones and Youtube one.
1. If value of `sub` field of `new_item` is different from values of video fields of `new_item`,
If value of `sub` field of `new_item` is cleared, transcripts should be removed.
If value of `sub` field of `new_item` is different from values of video fields of `new_item`,
and `new_item.sub` file is present, then code in this function creates copies of
`new_item.sub` file with new names. That names are equal to values of video fields of `new_item`
After that `sub` field of `new_item` is changed to one of values of video fields.
......@@ -328,6 +331,9 @@ def manage_video_subtitles_save(old_item, new_item):
for video_id in possible_video_id_list:
if not video_id:
continue
if not sub_name:
remove_subs_from_store(video_id, new_item)
continue
# copy_or_rename_transcript changes item.sub of module
try:
# updates item.sub with `video_id`, if it is successful.
......
......@@ -79,7 +79,7 @@ def get_course_location_for_item(location):
# @hack! We need to find the course location however, we don't
# know the 'name' parameter in this context, so we have
# to assume there's only one item in this query even though we are not specifying a name
course_search_location = ['i4x', item_loc.org, item_loc.course, 'course', None]
course_search_location = Location('i4x', item_loc.org, item_loc.course, 'course', None)
courses = modulestore().get_items(course_search_location)
# make sure we found exactly one match on this above course search
......@@ -107,7 +107,7 @@ def get_course_for_item(location):
# @hack! We need to find the course location however, we don't
# know the 'name' parameter in this context, so we have
# to assume there's only one item in this query even though we are not specifying a name
course_search_location = ['i4x', item_loc.org, item_loc.course, 'course', None]
course_search_location = Location('i4x', item_loc.org, item_loc.course, 'course', None)
courses = modulestore().get_items(course_search_location)
# make sure we found exactly one match on this above course search
......@@ -136,7 +136,7 @@ def get_lms_link_for_item(location, preview=False, course_id=None):
if settings.LMS_BASE is not None:
if preview:
lms_base = settings.MITX_FEATURES.get('PREVIEW_LMS_BASE')
lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE')
else:
lms_base = settings.LMS_BASE
......@@ -155,7 +155,7 @@ def get_lms_link_for_about_page(location):
"""
Returns the url to the course about page from the location tuple.
"""
if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False):
if settings.FEATURES.get('ENABLE_MKTG_SITE', False):
if not hasattr(settings, 'MKTG_URLS'):
log.exception("ENABLE_MKTG_SITE is True, but MKTG_URLS is not defined.")
about_base = None
......
from auth.authz import STAFF_ROLE_NAME, INSTRUCTOR_ROLE_NAME
from auth.authz import is_user_in_course_group_role
from django.core.exceptions import PermissionDenied
from ..utils import get_course_location_for_item
from xmodule.modulestore import Location
from xmodule.modulestore.locator import CourseLocator
def get_location_and_verify_access(request, org, course, name):
"""
Create the location, verify that the user has permissions
to view the location. Returns the location as a Location
"""
location = ['i4x', org, course, 'course', name]
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
return Location(location)
def has_access(user, location, role=STAFF_ROLE_NAME):
'''
"""
Return True if user allowed to access this piece of data
Note that the CMS permissions model is with respect to courses
There is a super-admin permissions if user.is_staff is set
......@@ -29,7 +13,7 @@ def has_access(user, location, role=STAFF_ROLE_NAME):
I'm presuming that the course instructor (formally known as admin)
will not be in both INSTRUCTOR and STAFF groups, so we have to cascade our
queries here as INSTRUCTOR has all the rights that STAFF do
'''
"""
if not isinstance(location, CourseLocator):
location = get_course_location_for_item(location)
_has_access = is_user_in_course_group_role(user, location, role)
......
......@@ -7,7 +7,7 @@ from django.views.decorators.http import require_http_methods
from django_future.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_POST
from mitxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response
from cache_toolbox.core import del_cached_content
from xmodule.contentstore.django import contentstore
......
......@@ -6,7 +6,7 @@ from django.http import HttpResponseBadRequest
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django_future.csrf import ensure_csrf_cookie
from mitxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response
from django.http import HttpResponseNotFound
from django.core.exceptions import PermissionDenied
from xmodule.modulestore.django import loc_mapper
......
......@@ -9,7 +9,7 @@ from django.core.exceptions import PermissionDenied
from django_future.csrf import ensure_csrf_cookie
from django.conf import settings
from xmodule.modulestore.exceptions import ItemNotFoundError
from mitxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response
from xmodule.modulestore.django import modulestore
from xmodule.util.date_utils import get_default_time_display
......@@ -252,7 +252,7 @@ def unit_handler(request, tag=None, course_id=None, branch=None, version_guid=No
break
index = index + 1
preview_lms_base = settings.MITX_FEATURES.get('PREVIEW_LMS_BASE')
preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE')
preview_lms_link = (
'//{preview_lms_base}/courses/{org}/{course}/'
......
......@@ -4,7 +4,7 @@ These views will NOT be shown on production: trying to access them will result
in a 404 error.
"""
# pylint: disable=W0613
from mitxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response
def dev_mode(request):
......
......@@ -2,7 +2,7 @@
from django.http import (HttpResponse, HttpResponseServerError,
HttpResponseNotFound)
from mitxmako.shortcuts import render_to_string, render_to_response
from edxmako.shortcuts import render_to_string, render_to_response
import functools
import json
......
from django.http import HttpResponse
from django.shortcuts import redirect
from mitxmako.shortcuts import render_to_string, render_to_response
from edxmako.shortcuts import render_to_string, render_to_response
__all__ = ['edge', 'event', 'landing']
......
......@@ -21,7 +21,7 @@ from django.http import HttpResponseNotFound
from django.views.decorators.http import require_http_methods, require_GET
from django.utils.translation import ugettext as _
from mitxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response
from auth.authz import create_all_course_groups
from xmodule.modulestore.xml_importer import import_from_xml
......
......@@ -30,7 +30,7 @@ from student.models import CourseEnrollment
from django.http import HttpResponseBadRequest
from xblock.fields import Scope
from preview import handler_prefix, get_preview_html
from mitxmako.shortcuts import render_to_response, render_to_string
from edxmako.shortcuts import render_to_response, render_to_string
from models.settings.course_grading import CourseGradingModel
__all__ = ['orphan_handler', 'xblock_handler']
......@@ -67,7 +67,7 @@ def xblock_handler(request, tag=None, course_id=None, branch=None, version_guid=
to None! Absent ones will be left alone.
:nullout: which metadata fields to set to None
:graderType: change how this unit is graded
:publish: can be one of three values, 'make_public, 'make_private', or 'create_draft'
:publish: can be one of three values, 'make_public, 'make_private', or 'create_draft'
The JSON representation on the updated xblock (minus children) is returned.
if xblock locator is not specified, create a new xblock instance. The json playload can contain
......@@ -105,6 +105,7 @@ def xblock_handler(request, tag=None, course_id=None, branch=None, version_guid=
# dungeon and surface as uneditable, unsaveable, and undeletable
# component-goblins.
except Exception as exc: # pylint: disable=W0703
log.debug("Unable to render studio_view for %r", component, exc_info=True)
content = render_to_string('html_error.html', {'message': str(exc)})
return render_to_response('component.html', {
......
......@@ -5,7 +5,7 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponseBadRequest
from django.contrib.auth.decorators import login_required
from mitxmako.shortcuts import render_to_response, render_to_string
from edxmako.shortcuts import render_to_response, render_to_string
from xmodule_modifiers import replace_static_urls, wrap_xblock
from xmodule.error_module import ErrorDescriptor
......@@ -159,5 +159,6 @@ def get_preview_html(request, descriptor):
try:
content = module.render("student_view").content
except Exception as exc: # pylint: disable=W0703
log.debug("Unable to render student_view for %r", module, exc_info=True)
content = render_to_string('html_error.html', {'message': str(exc)})
return content
......@@ -6,7 +6,7 @@ from django.core.context_processors import csrf
from django.shortcuts import redirect
from django.conf import settings
from mitxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response
from external_auth.views import ssl_login_shortcut
......
......@@ -9,7 +9,7 @@ from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django_future.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_http_methods
from mitxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response
from xmodule.modulestore import Location
from xmodule.modulestore.inheritance import own_metadata
from xmodule.modulestore.django import modulestore
......@@ -40,7 +40,7 @@ def initialize_course_tabs(course):
{"type": "discussion", "name": _("Discussion")},
{"type": "wiki", "name": _("Wiki")},
{"type": "progress", "name": _("Progress")},
]
]
modulestore('direct').update_metadata(course.location.url(), own_metadata(course))
......
......@@ -6,7 +6,7 @@ from django.views.decorators.http import require_http_methods
from django.utils.translation import ugettext as _
from django.views.decorators.http import require_POST
from django_future.csrf import ensure_csrf_cookie
from mitxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response
from xmodule.modulestore.django import modulestore, loc_mapper
from util.json_request import JsonResponse, expect_json
......
......@@ -8,7 +8,7 @@ from course_creators.views import update_course_creator_group
from ratelimitbackend import admin
from django.conf import settings
from django.dispatch import receiver
from mitxmako.shortcuts import render_to_string
from edxmako.shortcuts import render_to_string
from django.core.mail import send_mail
from smtplib import SMTPException
......@@ -91,7 +91,7 @@ def send_user_notification_callback(sender, **kwargs):
user = kwargs['user']
updated_state = kwargs['state']
studio_request_email = settings.MITX_FEATURES.get('STUDIO_REQUEST_EMAIL', '')
studio_request_email = settings.FEATURES.get('STUDIO_REQUEST_EMAIL', '')
context = {'studio_request_email': studio_request_email}
subject = render_to_string('emails/course_creator_subject.txt', context)
......@@ -118,7 +118,7 @@ def send_admin_notification_callback(sender, **kwargs):
"""
user = kwargs['user']
studio_request_email = settings.MITX_FEATURES.get('STUDIO_REQUEST_EMAIL', '')
studio_request_email = settings.FEATURES.get('STUDIO_REQUEST_EMAIL', '')
context = {'user_name': user.username, 'user_email': user.email}
subject = render_to_string('emails/course_creator_admin_subject.txt', context)
......
......@@ -69,7 +69,7 @@ class CourseCreatorAdminTest(TestCase):
self.studio_request_email
)
with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.enable_creator_group_patch):
with mock.patch.dict('django.conf.settings.FEATURES', self.enable_creator_group_patch):
# User is initially unrequested.
self.assertFalse(is_user_in_creator_group(self.user))
......@@ -119,7 +119,7 @@ class CourseCreatorAdminTest(TestCase):
else:
self.assertEquals(base_num_emails, len(mail.outbox))
with mock.patch.dict('django.conf.settings.MITX_FEATURES', self.enable_creator_group_patch):
with mock.patch.dict('django.conf.settings.FEATURES', self.enable_creator_group_patch):
# E-mail message should be sent to admin only when new state is PENDING, regardless of what
# previous state was (unless previous state was already PENDING).
# E-mail message sent to user only on transition into and out of GRANTED state.
......@@ -159,7 +159,7 @@ class CourseCreatorAdminTest(TestCase):
self.assertFalse(self.creator_admin.has_change_permission(self.request))
def test_rate_limit_login(self):
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_CREATOR_GROUP': True}):
with mock.patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}):
post_params = {'username': self.user.username, 'password': 'wrong_password'}
# try logging in 30 times, the default limit in the number of failed
# login attempts in one 5 minute period before the rate gets limited
......
......@@ -46,7 +46,7 @@ class CourseCreatorView(TestCase):
self.assertEqual('unrequested', get_course_creator_status(self.user))
def test_add_granted(self):
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {"ENABLE_CREATOR_GROUP": True}):
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
# Calling add_user_with_status_granted impacts is_user_in_course_group_role.
self.assertFalse(is_user_in_creator_group(self.user))
......@@ -60,7 +60,7 @@ class CourseCreatorView(TestCase):
self.assertTrue(is_user_in_creator_group(self.user))
def test_update_creator_group(self):
with mock.patch.dict('django.conf.settings.MITX_FEATURES', {"ENABLE_CREATOR_GROUP": True}):
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
self.assertFalse(is_user_in_creator_group(self.user))
update_course_creator_group(self.admin, self.user, True)
self.assertTrue(is_user_in_creator_group(self.user))
......
......@@ -34,7 +34,7 @@ DOC_STORE_CONFIG = {
MODULESTORE_OPTIONS = {
'default_class': 'xmodule.raw_module.RawDescriptor',
'fs_root': TEST_ROOT / "data",
'render_template': 'mitxmako.shortcuts.render_to_string',
'render_template': 'edxmako.shortcuts.render_to_string',
}
MODULESTORE = {
......@@ -87,7 +87,7 @@ PIPELINE = True
STATICFILES_FINDERS += ('pipeline.finders.PipelineFinder', )
# Use the auto_auth workflow for creating users and logging them in
MITX_FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] = True
FEATURES['AUTOMATIC_AUTH_FOR_TESTING'] = True
# HACK
# Setting this flag to false causes imports to not load correctly in the lettuce python files
......
......@@ -90,7 +90,10 @@ with open(CONFIG_ROOT / CONFIG_PREFIX + "env.json") as env_file:
STATIC_URL_BASE = ENV_TOKENS.get('STATIC_URL_BASE', None)
if STATIC_URL_BASE:
# collectstatic will fail if STATIC_URL is a unicode string
STATIC_URL = STATIC_URL_BASE.encode('ascii') + "/" + git.revision + "/"
STATIC_URL = STATIC_URL_BASE.encode('ascii')
if not STATIC_URL.endswith("/"):
STATIC_URL += "/"
STATIC_URL += git.revision + "/"
# GITHUB_REPO_ROOT is the base directory
# for course data
......@@ -105,8 +108,13 @@ if STATIC_ROOT_BASE:
EMAIL_BACKEND = ENV_TOKENS.get('EMAIL_BACKEND', EMAIL_BACKEND)
EMAIL_FILE_PATH = ENV_TOKENS.get('EMAIL_FILE_PATH', None)
EMAIL_HOST = ENV_TOKENS.get('EMAIL_HOST', EMAIL_HOST)
EMAIL_PORT = ENV_TOKENS.get('EMAIL_PORT', EMAIL_PORT)
EMAIL_USE_TLS = ENV_TOKENS.get('EMAIL_USE_TLS', EMAIL_USE_TLS)
LMS_BASE = ENV_TOKENS.get('LMS_BASE')
# Note that MITX_FEATURES['PREVIEW_LMS_BASE'] gets read in from the environment file.
# Note that FEATURES['PREVIEW_LMS_BASE'] gets read in from the environment file.
SITE_NAME = ENV_TOKENS['SITE_NAME']
......@@ -138,8 +146,9 @@ COURSES_WITH_UNSAFE_CODE = ENV_TOKENS.get("COURSES_WITH_UNSAFE_CODE", [])
TIME_ZONE = ENV_TOKENS.get('TIME_ZONE', TIME_ZONE)
for feature, value in ENV_TOKENS.get('MITX_FEATURES', {}).items():
MITX_FEATURES[feature] = value
ENV_FEATURES = ENV_TOKENS.get('FEATURES', ENV_TOKENS.get('MITX_FEATURES', {}))
for feature, value in ENV_FEATURES.items():
FEATURES[feature] = value
LOGGING = get_logger_config(LOG_DIR,
logging_env=ENV_TOKENS['LOGGING_ENV'],
......@@ -160,11 +169,14 @@ if "TRACKING_IGNORE_URL_PATTERNS" in ENV_TOKENS:
with open(CONFIG_ROOT / CONFIG_PREFIX + "auth.json") as auth_file:
AUTH_TOKENS = json.load(auth_file)
EMAIL_HOST_USER = AUTH_TOKENS.get('EMAIL_HOST_USER', EMAIL_HOST_USER)
EMAIL_HOST_PASSWORD = AUTH_TOKENS.get('EMAIL_HOST_PASSWORD', EMAIL_HOST_PASSWORD)
# If Segment.io key specified, load it and turn on Segment.io if the feature flag is set
# Note that this is the Studio key. There is a separate key for the LMS.
SEGMENT_IO_KEY = AUTH_TOKENS.get('SEGMENT_IO_KEY')
if SEGMENT_IO_KEY:
MITX_FEATURES['SEGMENT_IO'] = ENV_TOKENS.get('SEGMENT_IO', False)
FEATURES['SEGMENT_IO'] = ENV_TOKENS.get('SEGMENT_IO', False)
AWS_ACCESS_KEY_ID = AUTH_TOKENS["AWS_ACCESS_KEY_ID"]
if AWS_ACCESS_KEY_ID == "":
......
......@@ -2,7 +2,7 @@
This is the common settings file, intended to set sane defaults. If you have a
piece of configuration that's dependent on a set of feature flags being set,
then create a function that returns the calculated value based on the value of
MITX_FEATURES[...]. Modules that extend this one can change the feature
FEATURES[...]. Modules that extend this one can change the feature
configuration in an environment specific config file and re-calculate those
values.
......@@ -14,7 +14,7 @@ Longer TODO:
1. Right now our treatment of static content in general and in particular
course-specific static content is haphazard.
2. We should have a more disciplined approach to feature flagging, even if it
just means that we stick them in a dict called MITX_FEATURES.
just means that we stick them in a dict called FEATURES.
3. We need to handle configuration for multiple courses. This could be as
multiple sites, but we do need a way to map their data assets.
"""
......@@ -36,7 +36,7 @@ from dealer.git import git
############################ FEATURE CONFIGURATION #############################
MITX_FEATURES = {
FEATURES = {
'USE_DJANGO_PIPELINE': True,
'GITHUB_PUSH': False,
......@@ -67,11 +67,11 @@ ENABLE_JASMINE = False
############################# SET PATH INFORMATION #############################
PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /mitx/cms
PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /edx-platform/cms
REPO_ROOT = PROJECT_ROOT.dirname()
COMMON_ROOT = REPO_ROOT / "common"
LMS_ROOT = REPO_ROOT / "lms"
ENV_ROOT = REPO_ROOT.dirname() # virtualenv dir /mitx is in
ENV_ROOT = REPO_ROOT.dirname() # virtualenv dir /edx-platform is in
GITHUB_REPO_ROOT = ENV_ROOT / "data"
......@@ -99,10 +99,10 @@ for namespace, template_dirs in lms.envs.common.MAKO_TEMPLATES.iteritems():
TEMPLATE_DIRS = MAKO_TEMPLATES['main']
MITX_ROOT_URL = ''
EDX_ROOT_URL = ''
LOGIN_REDIRECT_URL = MITX_ROOT_URL + '/signin'
LOGIN_URL = MITX_ROOT_URL + '/signin'
LOGIN_REDIRECT_URL = EDX_ROOT_URL + '/signin'
LOGIN_URL = EDX_ROOT_URL + '/signin'
TEMPLATE_CONTEXT_PROCESSORS = (
......@@ -161,7 +161,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware',
'track.middleware.TrackMiddleware',
'mitxmako.middleware.MakoMiddleware',
'edxmako.middleware.MakoMiddleware',
# Detects user-requested locale from 'accept-language' header in http request
'django.middleware.locale.LocaleMiddleware',
......@@ -197,6 +197,11 @@ IGNORABLE_404_ENDS = ('favicon.ico')
# Email
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
EMAIL_HOST = 'localhost'
EMAIL_PORT = 25
EMAIL_USE_TLS = False
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
DEFAULT_FROM_EMAIL = 'registration@example.com'
DEFAULT_FEEDBACK_EMAIL = 'feedback@example.com'
SERVER_EMAIL = 'devops@example.com'
......@@ -230,7 +235,7 @@ USE_I18N = False
USE_L10N = True
# Localization strings (e.g. django.po) are under this directory
LOCALE_PATHS = (REPO_ROOT + '/conf/locale',) # mitx/conf/locale/
LOCALE_PATHS = (REPO_ROOT + '/conf/locale',) # edx-platform/conf/locale/
# Messages
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
......@@ -310,6 +315,10 @@ STATICFILES_IGNORE_PATTERNS = (
"coffee/*/*.coffee",
"coffee/*/*/*.coffee",
"coffee/*/*/*/*.coffee",
# Symlinks used by js-test-tool
"xmodule_js",
"common_static",
)
PIPELINE_YUI_BINARY = 'yui-compressor'
......@@ -393,7 +402,7 @@ INSTALLED_APPS = (
'datadog',
# For asset pipelining
'mitxmako',
'edxmako',
'pipeline',
'staticfiles',
'static_replace',
......@@ -414,14 +423,7 @@ INSTALLED_APPS = (
EDXMKTG_COOKIE_NAME = 'edxloggedin'
MKTG_URLS = {}
MKTG_URL_LINK_MAP = {
'ABOUT': 'about_edx',
'CONTACT': 'contact',
'FAQ': 'help_edx',
'COURSES': 'courses',
'ROOT': 'root',
'TOS': 'tos',
'HONOR': 'honor',
'PRIVACY': 'privacy_edx',
}
COURSES_WITH_UNSAFE_CODE = []
......
......@@ -30,7 +30,7 @@ DOC_STORE_CONFIG = {
modulestore_options = {
'default_class': 'xmodule.raw_module.RawDescriptor',
'fs_root': GITHUB_REPO_ROOT,
'render_template': 'mitxmako.shortcuts.render_to_string',
'render_template': 'edxmako.shortcuts.render_to_string',
}
MODULESTORE = {
......@@ -71,12 +71,12 @@ CONTENTSTORE = {
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ENV_ROOT / "db" / "mitx.db",
'NAME': ENV_ROOT / "db" / "edx.db",
}
}
LMS_BASE = "localhost:8000"
MITX_FEATURES['PREVIEW_LMS_BASE'] = "localhost:8000"
FEATURES['PREVIEW_LMS_BASE'] = "localhost:8000"
REPOS = {
'edx4edx': {
......@@ -108,7 +108,7 @@ CACHES = {
# In staging/prod envs, the sessions also live here.
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'mitx_loc_mem_cache',
'LOCATION': 'edx_loc_mem_cache',
'KEY_FUNCTION': 'util.memcache.safe_key',
},
......@@ -129,7 +129,12 @@ CACHES = {
'LOCATION': '/var/tmp/mongo_metadata_inheritance',
'TIMEOUT': 300,
'KEY_FUNCTION': 'util.memcache.safe_key',
}
},
'loc_cache': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'edx_location_mem_cache',
},
}
# Make the keyedcache startup warnings go away
......@@ -178,10 +183,10 @@ DEBUG_TOOLBAR_CONFIG = {
DEBUG_TOOLBAR_MONGO_STACKTRACES = False
# disable NPS survey in dev mode
MITX_FEATURES['STUDIO_NPS_SURVEY'] = False
FEATURES['STUDIO_NPS_SURVEY'] = False
# Enable URL that shows information about the status of variuous services
MITX_FEATURES['ENABLE_SERVICE_STATUS'] = True
FEATURES['ENABLE_SERVICE_STATUS'] = True
############################# SEGMENT-IO ##################################
......@@ -190,7 +195,7 @@ MITX_FEATURES['ENABLE_SERVICE_STATUS'] = True
import os
SEGMENT_IO_KEY = os.environ.get('SEGMENT_IO_KEY')
if SEGMENT_IO_KEY:
MITX_FEATURES['SEGMENT_IO'] = True
FEATURES['SEGMENT_IO'] = True
#####################################################################
......
......@@ -9,8 +9,8 @@
from .common import *
from .dev import *
MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
MITX_FEATURES['USE_DJANGO_PIPELINE'] = False # don't recompile scss
FEATURES['USE_DJANGO_PIPELINE'] = False # don't recompile scss
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 for nginx ssl proxy
......@@ -9,4 +9,4 @@ the same process between preview and published
from .dev import *
MITX_FEATURES['PREVIEW_LMS_BASE'] = "preview.localhost:8000"
FEATURES['PREVIEW_LMS_BASE'] = "preview.localhost:8000"
......@@ -24,7 +24,7 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
################################# LMS INTEGRATION #############################
LMS_BASE = "localhost:8000"
MITX_FEATURES['PREVIEW_LMS_BASE'] = "preview." + LMS_BASE
FEATURES['PREVIEW_LMS_BASE'] = "preview." + LMS_BASE
################################# CELERY ######################################
......
......@@ -4,7 +4,7 @@ sessions. Assumes structure:
/envroot/
/db # This is where it'll write the database file
/mitx # The location of this repo
/edx-platform # The location of this repo
/log # Where we're going to write log files
"""
......@@ -60,7 +60,7 @@ DOC_STORE_CONFIG = {
MODULESTORE_OPTIONS = {
'default_class': 'xmodule.raw_module.RawDescriptor',
'fs_root': TEST_ROOT / "data",
'render_template': 'mitxmako.shortcuts.render_to_string',
'render_template': 'edxmako.shortcuts.render_to_string',
}
MODULESTORE = {
......@@ -109,7 +109,7 @@ DATABASES = {
}
LMS_BASE = "localhost:8000"
MITX_FEATURES['PREVIEW_LMS_BASE'] = "preview"
FEATURES['PREVIEW_LMS_BASE'] = "preview"
CACHES = {
# This is the cache used for most things. Askbot will not work without a
......@@ -117,7 +117,7 @@ CACHES = {
# In staging/prod envs, the sessions also live here.
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'mitx_loc_mem_cache',
'LOCATION': 'edx_loc_mem_cache',
'KEY_FUNCTION': 'util.memcache.safe_key',
},
......@@ -138,7 +138,12 @@ CACHES = {
'LOCATION': '/var/tmp/mongo_metadata_inheritance',
'TIMEOUT': 300,
'KEY_FUNCTION': 'util.memcache.safe_key',
}
},
'loc_cache': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'edx_location_mem_cache',
},
}
# hide ratelimit warnings while running tests
......@@ -161,12 +166,14 @@ PASSWORD_HASHERS = (
SEGMENT_IO_KEY = '***REMOVED***'
# disable NPS survey in test mode
MITX_FEATURES['STUDIO_NPS_SURVEY'] = False
FEATURES['STUDIO_NPS_SURVEY'] = False
MITX_FEATURES['ENABLE_SERVICE_STATUS'] = True
FEATURES['ENABLE_SERVICE_STATUS'] = True
# This is to disable a test under the common directory that will not pass when run under CMS
MITX_FEATURES['DISABLE_PASSWORD_RESET_EMAIL_TEST'] = True
FEATURES['DISABLE_PASSWORD_RESET_EMAIL_TEST'] = True
# This is to disable tests CME Registration tests, under common, that will not pass when run under CMS
MITX_FEATURES['DISABLE_CME_REGISTRATION_TESTS'] = True
FEATURES['DISABLE_CME_REGISTRATION_TESTS'] = True
FEATURES['DISABLE_RESET_EMAIL_TEST'] = True
require ["jquery", "backbone", "coffee/src/main", "sinon", "jasmine-stealth"],
require ["jquery", "backbone", "coffee/src/main", "sinon", "jasmine-stealth", "jquery.cookie"],
($, Backbone, main, sinon) ->
describe "CMS", ->
it "should initialize URL", ->
......
......@@ -11,6 +11,10 @@ define ["backbone", "js/models/textbook", "js/collections/textbook", "js/models/
beforeEach ->
main()
@model = new Textbook()
CMS.URL.TEXTBOOKS = "/textbooks"
afterEach ->
delete CMS.URL.TEXTBOOKS
describe "Basic", ->
it "should have an empty name by default", ->
......@@ -28,8 +32,9 @@ define ["backbone", "js/models/textbook", "js/collections/textbook", "js/models/
it "should be empty by default", ->
expect(@model.isEmpty()).toBeTruthy()
it "should have a URL set", ->
expect(@model.url()).toBeTruthy()
it "should have a URL root", ->
urlRoot = _.result(@model, 'urlRoot')
expect(urlRoot).toBeTruthy()
it "should be able to reset itself", ->
@model.set("name", "foobar")
......@@ -135,12 +140,8 @@ define ["backbone", "js/models/textbook", "js/collections/textbook", "js/models/
delete CMS.URL.TEXTBOOKS
it "should have a url set", ->
expect(@collection.url()).toEqual("/textbooks")
it "can call save", ->
spyOn(@collection, "sync")
@collection.save()
expect(@collection.sync).toHaveBeenCalledWith("update", @collection, undefined)
url = _.result(@collection, 'url')
expect(url).toEqual("/textbooks")
describe "Chapter model", ->
......
......@@ -21,7 +21,7 @@ define ["js/views/course_info_handout", "js/views/course_info_update", "js/model
delete window.analytics
delete window.course_location_analytics
describe "Course Updates", ->
xdescribe "Course Updates", ->
courseInfoTemplate = readFixtures('course_info_update.underscore')
beforeEach ->
......@@ -139,7 +139,7 @@ define ["js/views/course_info_handout", "js/views/course_info_update", "js/model
it "does not remove existing course info on click outside modal", ->
@cancelExistingCourseInfo(false)
describe "Course Handouts", ->
xdescribe "Course Handouts", ->
handoutsTemplate = readFixtures('course_info_handouts.underscore')
beforeEach ->
......@@ -214,4 +214,4 @@ define ["js/views/course_info_handout", "js/views/course_info_update", "js/model
expect($('.edit-handouts-form').is(':hidden')).toEqual(true)
@handoutsEdit.$el.find('.edit-button').click()
expect(@handoutsEdit.$codeMirror.getValue()).toEqual('<p><a href="[URL OF FILE]>[LINK TEXT]</a></p>')
expect($('.edit-handouts-form').is(':hidden')).toEqual(false)
\ No newline at end of file
expect($('.edit-handouts-form').is(':hidden')).toEqual(false)
......@@ -328,7 +328,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
)
expect($('#subsection-2')).not.toHaveClass('collapsed')
describe "AJAX", ->
xdescribe "AJAX", ->
beforeEach ->
@requests = requests = []
@xhr = sinon.useFakeXMLHttpRequest()
......
define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js/models/section",
define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js/models/course",
"js/collections/textbook", "js/views/show_textbook", "js/views/edit_textbook", "js/views/list_textbooks",
"js/views/edit_chapter", "js/views/feedback_prompt", "js/views/feedback_notification",
"sinon", "jasmine-stealth"],
(Textbook, Chapter, ChapterSet, Section, TextbookSet, ShowTextbook, EditTextbook, ListTexbook, EditChapter, Prompt, Notification, sinon) ->
(Textbook, Chapter, ChapterSet, Course, TextbookSet, ShowTextbook, EditTextbook, ListTexbook, EditChapter, Prompt, Notification, sinon) ->
feedbackTpl = readFixtures('system-feedback.underscore')
beforeEach ->
......@@ -30,7 +30,7 @@ define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js
@promptSpies = spyOnConstructor(Prompt, "Warning", ["show", "hide"])
@promptSpies.show.andReturn(@promptSpies)
window.section = new Section({
window.course = new Course({
id: "5",
name: "Course Name",
url_name: "course_name",
......@@ -40,7 +40,7 @@ define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js
});
afterEach ->
delete window.section
delete window.course
describe "Basic", ->
it "should render properly", ->
......@@ -81,9 +81,11 @@ define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js
@savingSpies = spyOnConstructor(Notification, "Mini",
["show", "hide"])
@savingSpies.show.andReturn(@savingSpies)
CMS.URL.TEXTBOOKS = "/textbooks"
afterEach ->
@xhr.restore()
delete CMS.URL.TEXTBOOKS
it "should destroy itself on confirmation", ->
@view.render().$(".delete").click()
......@@ -283,11 +285,11 @@ define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js
@view = new EditChapter({model: @model})
spyOn(@view, "remove").andCallThrough()
CMS.URL.UPLOAD_ASSET = "/upload"
window.section = new Section({name: "abcde"})
window.course = new Course({name: "abcde"})
afterEach ->
delete CMS.URL.UPLOAD_ASSET
delete window.section
delete window.course
it "can render", ->
@view.render()
......
......@@ -2,10 +2,7 @@ define(["backbone", "js/models/textbook"],
function(Backbone, TextbookModel) {
var TextbookCollection = Backbone.Collection.extend({
model: TextbookModel,
url: function() { return CMS.URL.TEXTBOOKS; },
save: function(options) {
return this.sync('update', this, options);
}
url: function() { return CMS.URL.TEXTBOOKS; }
});
return TextbookCollection;
});
define(["backbone", "underscore", "js/models/chapter", "js/collections/chapter", "backbone.associations"],
define(["backbone", "underscore", "js/models/chapter", "js/collections/chapter",
"backbone.associations", "coffee/src/main"],
function(Backbone, _, ChapterModel, ChapterCollection) {
var Textbook = Backbone.AssociatedModel.extend({
......@@ -32,13 +33,7 @@ define(["backbone", "underscore", "js/models/chapter", "js/collections/chapter",
isEmpty: function() {
return !this.get('name') && this.get('chapters').isEmpty();
},
url: function() {
if(this.isNew()) {
return CMS.URL.TEXTBOOKS + "/new";
} else {
return CMS.URL.TEXTBOOKS + "/" + this.id;
}
},
urlRoot: function() { return CMS.URL.TEXTBOOKS; },
parse: function(response) {
var ret = $.extend(true, {}, response);
if("tab_title" in ret && !("name" in ret)) {
......
......@@ -53,7 +53,7 @@ define(["backbone", "underscore", "underscore.string", "jquery", "gettext", "js/
});
var msg = new FileUploadModel({
title: _.template(gettext("Upload a new PDF to “<%= name %>”"),
{name: section.escape('name')}),
{name: course.escape('name')}),
message: "Files must be in PDF format.",
mimeTypes: ['application/pdf']
});
......
......@@ -16,7 +16,7 @@ define(["backbone", "underscore", "gettext", "js/views/feedback_notification", "
render: function() {
var attrs = $.extend({}, this.model.attributes);
attrs.bookindex = this.model.collection.indexOf(this.model);
attrs.course = window.section.attributes;
attrs.course = window.course.attributes;
this.$el.html(this.template(attrs));
return this;
},
......
......@@ -90,7 +90,17 @@ function($, Backbone, _, Utils, MetadataView, MetadataCollection) {
var isSubsModified = (function (values) {
var isSubsChanged = subs.hasChanged("value");
return Boolean(isSubsChanged && _.isString(values.sub));
return Boolean(
isSubsChanged &&
(
// If the user changes the field, `values.sub` contains
// string value;
// If the user clicks `clear` button, the field contains
// null value.
// Otherwise, undefined.
_.isString(values.sub) || _.isNull(subs.getValue())
)
);
}(modifiedValues));
// When we change value of `sub` field in the `Advanced`,
......
......@@ -172,6 +172,15 @@ $tmg-f3: 0.125s;
// ====================
// archetype UI
$ui-action-primary-color: $blue-u2;
$ui-action-primary-color-focus: $blue-s1;
$ui-link-color: $blue-u2;
$ui-link-color-focus: $blue-s1;
// ====================
// specific UI
$ui-notification-height: ($baseline*10);
$ui-update-color: $blue-l4;
......
......@@ -23,10 +23,10 @@
}
a {
color: $gray;
color: $ui-link-color;
&:hover, &:active {
color: $gray-d2;
color: $ui-link-color-focus;
}
}
......@@ -37,7 +37,7 @@
.nav-item {
display: inline-block;
margin-right: ($baseline/2);
margin-right: ($baseline/4);
&:last-child {
margin-right: 0;
......@@ -45,7 +45,7 @@
a {
border-radius: 2px;
padding: ($baseline/2) ($baseline*0.75);
padding: ($baseline/2) ($baseline/2);
background: transparent;
[class^="icon-"] {
......@@ -54,19 +54,6 @@
display: inline-block;
vertical-align: middle;
margin-right: ($baseline/4);
color: $gray-l1;
}
&:hover, &:active {
color: $gray-d2;
[class^="icon-"] {
color: $gray-d2;
}
}
&.is-active {
color: $gray-d2;
}
}
}
......
......@@ -20,7 +20,7 @@
</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="path_prefix" content="${MITX_ROOT_URL}">
<meta name="path_prefix" content="${EDX_ROOT_URL}">
<%static:css group='style-vendor'/>
<%static:css group='style-app'/>
......
......@@ -15,7 +15,7 @@
var hasUnit = ${json.dumps(bool(unit))},
editUnitUrl = "${edit_unit_url or ""}",
courseHomeUrl = "${course_home_url or ""}",
errMsg = "${raw_err_msg or ""}";
errMsg = ${json.dumps(raw_err_msg or "")};
require(["domReady!", "gettext", "js/views/feedback_prompt"], function(doc, gettext, PromptView) {
var dialog;
......
......@@ -45,8 +45,8 @@ require(["domReady!", "jquery", "jquery.form", "js/index"], function(doc, $) {
% if course_creator_status=='granted':
<a href="#" class="button new-button new-course-button"><i class="icon-plus icon-inline"></i>
${_("New Course")}</a>
% elif course_creator_status=='disallowed_for_this_site' and settings.MITX_FEATURES.get('STUDIO_REQUEST_EMAIL',''):
<a href="mailto:${settings.MITX_FEATURES.get('STUDIO_REQUEST_EMAIL','')}">${_("Email staff to create course")}</a>
% elif course_creator_status=='disallowed_for_this_site' and settings.FEATURES.get('STUDIO_REQUEST_EMAIL',''):
<a href="mailto:${settings.FEATURES.get('STUDIO_REQUEST_EMAIL','')}">${_("Email staff to create course")}</a>
% endif
</li>
</ul>
......@@ -61,7 +61,7 @@ require(["domReady!", "jquery", "jquery.form", "js/index"], function(doc, $) {
<article class="content-primary" role="main">
<div class="introduction">
<h2 class="title">${_("Welcome, {0}!".format(user.username))}</h2>
<h2 class="title">${_("Welcome, {0}!").format(user.username)}</h2>
%if len(courses) > 0:
<div class="copy">
......@@ -290,10 +290,10 @@ require(["domReady!", "jquery", "jquery.form", "js/index"], function(doc, $) {
</ol>
</div>
% if course_creator_status=='disallowed_for_this_site' and settings.MITX_FEATURES.get('STUDIO_REQUEST_EMAIL',''):
% if course_creator_status=='disallowed_for_this_site' and settings.FEATURES.get('STUDIO_REQUEST_EMAIL',''):
<div class="bit">
<h3 class="title title-3">${_('Can I create courses in Studio?')}</h3>
<p>${_('In order to create courses in Studio, you must')} <a href="mailto:${settings.MITX_FEATURES.get('STUDIO_REQUEST_EMAIL','')}">${_("contact edX staff to help you create a course")}</a></p>
<p>${_('In order to create courses in Studio, you must')} <a href="mailto:${settings.FEATURES.get('STUDIO_REQUEST_EMAIL','')}">${_("contact edX staff to help you create a course")}</a></p>
</div>
% endif
......
......@@ -10,7 +10,7 @@
<button class="action setting-upload" type="button" name="setting-upload" value="<%= gettext("Upload New Timed Transcript") %>" data-tooltip="<%= gettext("Upload New Timed Transcript") %>">
<span><%= gettext("Upload New Timed Transcript") %></span>
</button>
<a class="action setting-download" href="/transcripts/download?locator=<%= component_locator %>" data-tooltip="<%= gettext("Download to Edit") %>">
<a class="action setting-download" href="/transcripts/download?locator=<%= component_locator %>&subs_id=<%= subs_id %>" data-tooltip="<%= gettext("Download to Edit") %>">
<span><%= gettext("Download to Edit") %></span>
</a>
</div>
......@@ -64,7 +64,7 @@ require(["domReady!", "jquery", "js/models/settings/advanced", "js/views/setting
<section class="group-settings advanced-policies">
<header>
<h2 class="title-2">${_("Manual Policy Definition")}</h2>
<span class="tip">${_("Manually Edit Course Policy Values (JSON Key / Value pairs)")}</span>
<span class="tip">${_("Manually Edit Course Policy Values (JSON Key / Value pairs, use &quot; not &apos;)")}</span>
</header>
<p class="instructions">${_("<strong>Warning</strong>: Do not modify these policies unless you are familiar with their purpose.")}</p>
......
......@@ -60,7 +60,9 @@
<li class="field checkbox required" id="field-tos">
<input id="tos" name="terms_of_service" type="checkbox" value="true" />
<label for="tos">${_("I agree to the Terms of Service")}</label>
<label for="tos">
${_("I agree to the {a_start} Terms of Service {a_end}").format(a_start='<a data-rel="edx.org" href="{}">'.format(marketing_link('TOS')), a_end="</a>")}
</label>
</li>
</ol>
</fieldset>
......
......@@ -23,14 +23,7 @@ CMS.URL.TEXTBOOKS = "${textbook_url}"
CMS.URL.LMS_BASE = "${settings.LMS_BASE}"
require(["js/models/section", "js/collections/textbook", "js/views/list_textbooks"],
function(Section, TextbookCollection, ListTextbooksView) {
window.section = new Section({
name: "${course.display_name_with_default | h}",
url_name: "${course.location.name | h}",
org: "${course.location.org | h}",
num: "${course.location.course | h}",
revision: "${course.location.revision | h}"
});
var textbooks = new TextbookCollection(${json.dumps(course.pdf_textbooks)}, {parse: true});
var textbooks = new TextbookCollection(${json.dumps(textbooks)}, {parse: true});
var tbView = new ListTextbooksView({collection: textbooks});
$(function() {
......
<%! from django.core.urlresolvers import reverse %>
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper-footer wrapper">
<div class="wrapper-footer wrapper">
<footer class="primary" role="contentinfo">
<div class="colophon">
<p>&copy; 2013 <a href="http://www.edx.org" rel="external">edX</a>. ${ _("All rights reserved.")}</p>
......@@ -9,12 +9,12 @@
<nav class="nav-peripheral">
<ol>
<!-- <li class="nav-item nav-peripheral-tos">
<a href="#">Terms of Service</a>
<li class="nav-item nav-peripheral-tos">
<a data-rel="edx.org" href="${marketing_link('TOS')}">${_("Terms of Service")}</a>
</li>
<li class="nav-item nav-peripheral-pp">
<a href="#">Privacy Policy</a>
</li> -->
<a data-rel="edx.org" href="${marketing_link('PRIVACY')}">${_("Privacy Policy")}</a>
</li>
% if user.is_authenticated():
<li class="nav-item nav-peripheral-feedback">
<a href="mailto:courseops@class.stanford.edu" rel="external" title="${_('Use our Google Group to share your feedback')}">${_("Contact Us")}</a>
......
......@@ -13,13 +13,14 @@
<h1 class="branding"><a href="/"><img src="${static.url("img/logo-edx-studio.png")}" alt="edX Studio" /></a></h1>
% if context_course:
<%
<%
ctx_loc = context_course.location
location = loc_mapper().translate_location(ctx_loc.course_id, ctx_loc, False, True)
index_url = location.url_reverse('course')
checklists_url = location.url_reverse('checklists')
course_team_url = location.url_reverse('course_team')
assets_url = location.url_reverse('assets')
textbooks_url = location.url_reverse('textbooks')
import_url = location.url_reverse('import')
course_info_url = location.url_reverse('course_info')
export_url = location.url_reverse('export')
......@@ -58,7 +59,7 @@
<a href="${assets_url}">${_("Files &amp; Uploads")}</a>
</li>
<li class="nav-item nav-course-courseware-textbooks">
<a href="${reverse('textbook_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">${_("Textbooks")}</a>
<a href="${textbooks_url}">${_("Textbooks")}</a>
</li>
</ul>
</div>
......
% if settings.MITX_FEATURES.get('STUDIO_NPS_SURVEY'):
% if settings.FEATURES.get('STUDIO_NPS_SURVEY'):
<!-- Qualaroo is used for net promoter score surveys -->
<script type="text/javascript">
% if user.is_authenticated():
......
......@@ -9,7 +9,7 @@ from xmodule.modulestore.django import loc_mapper
%>
% endif
% if settings.MITX_FEATURES.get('SEGMENT_IO'):
% if settings.FEATURES.get('SEGMENT_IO'):
<!-- begin Segment.io -->
<script type="text/javascript">
// if inside course, inject the course location into the JS namespace
......
from django.conf import settings
from django.conf.urls import patterns, include, url
# TODO: This should be removed once the CMS is running via wsgi on all production servers
import cms.startup as startup
from xmodule.modulestore import parsers
startup.run()
# There is a course creators admin table.
from ratelimitbackend import admin
......@@ -23,13 +19,6 @@ urlpatterns = patterns('', # nopep8
url(r'^preview/xblock/(?P<usage_id>.*?)/handler/(?P<handler>[^/]*)(?:/(?P<suffix>[^/]*))?$',
'contentstore.views.preview_handler', name='preview_handler'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)$',
'contentstore.views.textbook_index', name='textbook_index'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)/new$',
'contentstore.views.create_textbook', name='create_textbook'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/textbooks/(?P<name>[^/]+)/(?P<tid>\d[^/]*)$',
'contentstore.views.textbook_by_id', name='textbook_by_id'),
# temporary landing page for a course
url(r'^edge/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$',
'contentstore.views.landing', name='landing'),
......@@ -89,6 +78,8 @@ urlpatterns += patterns(
url(r'(?ix)^settings/details/{}$'.format(parsers.URL_RE_SOURCE), 'settings_handler'),
url(r'(?ix)^settings/grading/{}(/)?(?P<grader_index>\d+)?$'.format(parsers.URL_RE_SOURCE), 'grading_handler'),
url(r'(?ix)^settings/advanced/{}$'.format(parsers.URL_RE_SOURCE), 'advanced_settings_handler'),
url(r'(?ix)^textbooks/{}$'.format(parsers.URL_RE_SOURCE), 'textbooks_list_handler'),
url(r'(?ix)^textbooks/{}/(?P<tid>\d[^/]*)$'.format(parsers.URL_RE_SOURCE), 'textbooks_detail_handler'),
)
js_info_dict = {
......@@ -101,7 +92,7 @@ urlpatterns += patterns('',
url(r'^i18n.js$', 'django.views.i18n.javascript_catalog', js_info_dict),
)
if settings.MITX_FEATURES.get('ENABLE_SERVICE_STATUS'):
if settings.FEATURES.get('ENABLE_SERVICE_STATUS'):
urlpatterns += patterns('',
url(r'^status/', include('service_status.urls')),
)
......@@ -109,7 +100,7 @@ if settings.MITX_FEATURES.get('ENABLE_SERVICE_STATUS'):
urlpatterns += patterns('', url(r'^admin/', include(admin.site.urls)),)
# enable automatic login
if settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_TESTING'):
if settings.FEATURES.get('AUTOMATIC_AUTH_FOR_TESTING'):
urlpatterns += (
url(r'^auto_auth$', 'student.views.auto_auth'),
)
......
......@@ -21,7 +21,7 @@ from django.core.mail import send_mail
from student.models import Registration
import student
from cme_registration.models import CmeUserProfile
from mitxmako.shortcuts import render_to_response, render_to_string
from edxmako.shortcuts import render_to_response, render_to_string
log = logging.getLogger("mitx.student")
......@@ -154,8 +154,8 @@ def cme_create_account(request, post_override=None):
message = render_to_string('emails/activation_email.txt', email_dict)
try:
if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL']
if settings.FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
dest_addr = settings.FEATURES['REROUTE_ACTIVATION_EMAIL']
message = ("Activation for %s (%s): %s\n" % (user, user.email, cme_user_profile.name) +
'-' * 80 + '\n\n' + message)
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False)
......
......@@ -9,7 +9,7 @@ import logging
import re
from courseware.courses import get_course_with_access
from mitxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response
from . import cohorts
......
from course_modes.models import CourseMode
from factory import DjangoModelFactory
from factory.django import DjangoModelFactory
# Factories don't have __init__ methods, and are self documenting
# pylint: disable=W0232
......
......@@ -13,7 +13,7 @@ from django.utils.translation import ugettext as _
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from mitxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response
from course_modes.models import CourseMode
from courseware.access import has_access
......@@ -37,6 +37,7 @@ class ChooseModeView(View):
enrollment_mode = CourseEnrollment.enrollment_mode_for_user(request.user, course_id)
upgrade = request.GET.get('upgrade', False)
request.session['attempting_upgrade'] = upgrade
# verified users do not need to register or upgrade
if enrollment_mode == 'verified':
......
......@@ -32,13 +32,8 @@ def assign_default_role(sender, instance, **kwargs):
# instance.user.roles.remove(*course_roles)
# return
# We've enrolled the student, so make sure they have a default role
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))
# We've enrolled the student, so make sure they have the Student role
role = Role.objects.get_or_create(course_id=instance.course_id, name="Student")[0]
instance.user.roles.add(role)
......
......@@ -10,6 +10,7 @@ class RoleAssignmentTest(TestCase):
"""
def setUp(self):
# Check a staff account because those used to get the Moderator role
self.staff_user = User.objects.create_user(
"patty",
"patty@fake.edx.org",
......@@ -25,18 +26,13 @@ class RoleAssignmentTest(TestCase):
CourseEnrollment.enroll(self.student_user, self.course_id)
def test_enrollment_auto_role_creation(self):
moderator_role = Role.objects.get(
course_id=self.course_id,
name="Moderator"
)
student_role = Role.objects.get(
course_id=self.course_id,
name="Student"
)
self.assertIn(moderator_role, self.staff_user.roles.all())
self.assertIn(student_role, self.student_user.roles.all())
self.assertNotIn(moderator_role, self.student_user.roles.all())
self.assertEqual([student_role], list(self.staff_user.roles.all()))
self.assertEqual([student_role], list(self.student_user.roles.all()))
# The following was written on the assumption that unenrolling from a course
# should remove all forum Roles for that student for that course. This is
......
......@@ -6,7 +6,7 @@ from django.template.loader import make_origin, get_template_from_string
from django.template.loaders.filesystem import Loader as FilesystemLoader
from django.template.loaders.app_directories import Loader as AppDirectoriesLoader
from mitxmako.template import Template
from edxmako.template import Template
import tempdir
......
......@@ -13,6 +13,7 @@
# limitations under the License.
from django.template import RequestContext
from util.request import safe_get_host
requestcontext = None
......@@ -22,4 +23,4 @@ class MakoMiddleware(object):
global requestcontext
requestcontext = RequestContext(request)
requestcontext['is_secure'] = request.is_secure()
requestcontext['site'] = request.get_host()
requestcontext['site'] = safe_get_host(request)
......@@ -16,8 +16,8 @@ from django.template import Context
from django.http import HttpResponse
import logging
import mitxmako
import mitxmako.middleware
import edxmako
import edxmako.middleware
from django.conf import settings
from django.core.urlresolvers import reverse
log = logging.getLogger(__name__)
......@@ -35,13 +35,13 @@ def marketing_link(name):
# link_map maps URLs from the marketing site to the old equivalent on
# the Django site
link_map = settings.MKTG_URL_LINK_MAP
if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE') and name in settings.MKTG_URLS:
if settings.FEATURES.get('ENABLE_MKTG_SITE') and name in settings.MKTG_URLS:
# special case for when we only want the root marketing URL
if name == 'ROOT':
return settings.MKTG_URLS.get('ROOT')
return settings.MKTG_URLS.get('ROOT') + settings.MKTG_URLS.get(name)
# only link to the old pages when the marketing site isn't on
elif not settings.MITX_FEATURES.get('ENABLE_MKTG_SITE') and name in link_map:
elif not settings.FEATURES.get('ENABLE_MKTG_SITE') and name in link_map:
# don't try to reverse disabled marketing links
if link_map[name] is not None:
return reverse(link_map[name])
......@@ -77,19 +77,19 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'):
# collapse context_instance to a single dictionary for mako
context_dictionary = {}
context_instance['settings'] = settings
context_instance['MITX_ROOT_URL'] = settings.MITX_ROOT_URL
context_instance['EDX_ROOT_URL'] = settings.EDX_ROOT_URL
context_instance['marketing_link'] = marketing_link
# In various testing contexts, there might not be a current request context.
if mitxmako.middleware.requestcontext is not None:
for d in mitxmako.middleware.requestcontext:
if edxmako.middleware.requestcontext is not None:
for d in edxmako.middleware.requestcontext:
context_dictionary.update(d)
for d in context_instance:
context_dictionary.update(d)
if context:
context_dictionary.update(context)
# fetch and render template
template = mitxmako.lookup[namespace].get_template(template_name)
template = edxmako.lookup[namespace].get_template(template_name)
return template.render_unicode(**context_dictionary)
......
......@@ -6,7 +6,7 @@ import tempdir
from django.conf import settings
from mako.lookup import TemplateLookup
import mitxmako
import edxmako
def run():
......@@ -30,4 +30,4 @@ def run():
encoding_errors='replace',
)
mitxmako.lookup = lookup
edxmako.lookup = lookup
......@@ -14,10 +14,10 @@
from django.conf import settings
from mako.template import Template as MakoTemplate
from mitxmako.shortcuts import marketing_link
from edxmako.shortcuts import marketing_link
import mitxmako
import mitxmako.middleware
import edxmako
import edxmako.middleware
django_variables = ['lookup', 'output_encoding', 'encoding_errors']
......@@ -34,7 +34,7 @@ class Template(MakoTemplate):
def __init__(self, *args, **kwargs):
"""Overrides base __init__ to provide django variable overrides"""
if not kwargs.get('no_django', False):
overrides = dict([(k, getattr(mitxmako, k, None),) for k in django_variables])
overrides = dict([(k, getattr(edxmako, k, None),) for k in django_variables])
overrides['lookup'] = overrides['lookup']['main']
kwargs.update(overrides)
super(Template, self).__init__(*args, **kwargs)
......@@ -48,13 +48,13 @@ class Template(MakoTemplate):
context_dictionary = {}
# In various testing contexts, there might not be a current request context.
if mitxmako.middleware.requestcontext is not None:
for d in mitxmako.middleware.requestcontext:
if edxmako.middleware.requestcontext is not None:
for d in edxmako.middleware.requestcontext:
context_dictionary.update(d)
for d in context_instance:
context_dictionary.update(d)
context_dictionary['settings'] = settings
context_dictionary['MITX_ROOT_URL'] = settings.MITX_ROOT_URL
context_dictionary['EDX_ROOT_URL'] = settings.EDX_ROOT_URL
context_dictionary['django_context'] = context_instance
context_dictionary['marketing_link'] = marketing_link
......
from django.test import TestCase
from django.test.utils import override_settings
from django.core.urlresolvers import reverse
from mitxmako.shortcuts import marketing_link
from edxmako.shortcuts import marketing_link
from mock import patch
from util.testing import UrlResetMixin
class ShortcutsTests(UrlResetMixin, TestCase):
"""
Test the mitxmako shortcuts file
Test the edxmako shortcuts file
"""
@override_settings(MKTG_URLS={'ROOT': 'dummy-root', 'ABOUT': '/about-us'})
@override_settings(MKTG_URL_LINK_MAP={'ABOUT': 'login'})
def test_marketing_link(self):
# test marketing site on
with patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': True}):
with patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': True}):
expected_link = 'dummy-root/about-us'
link = marketing_link('ABOUT')
self.assertEquals(link, expected_link)
# test marketing site off
with patch.dict('django.conf.settings.MITX_FEATURES', {'ENABLE_MKTG_SITE': False}):
with patch.dict('django.conf.settings.FEATURES', {'ENABLE_MKTG_SITE': False}):
# we are using login because it is common across both cms and lms
expected_link = reverse('login')
link = marketing_link('ABOUT')
......
#-*- encoding=utf-8 -*-
'''
Created on Jan 18, 2013
@author: brian
'''
import openid
import json
from openid.fetchers import HTTPFetcher, HTTPResponse
from urlparse import parse_qs
from urlparse import parse_qs, urlparse
from django.conf import settings
from django.test import TestCase, LiveServerTestCase
......@@ -72,8 +74,9 @@ class OpenIdProviderTest(TestCase):
Tests of the OpenId login
"""
@skipUnless(settings.MITX_FEATURES.get('AUTH_USE_OPENID') or
settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'), True)
@skipUnless(settings.FEATURES.get('AUTH_USE_OPENID') and
settings.FEATURES.get('AUTH_USE_OPENID_PROVIDER'),
'OpenID not enabled')
def test_begin_login_with_xrds_url(self):
# the provider URL must be converted to an absolute URL in order to be
......@@ -93,6 +96,7 @@ class OpenIdProviderTest(TestCase):
# now we can begin the login process by invoking a local openid client,
# with a pointer to the (also-local) openid provider:
with self.settings(OPENID_SSO_SERVER_URL=abs_provider_url):
url = reverse('openid-login')
resp = self.client.post(url)
code = 200
......@@ -100,8 +104,9 @@ class OpenIdProviderTest(TestCase):
"got code {0} for url '{1}'. Expected code {2}"
.format(resp.status_code, url, code))
@skipUnless(settings.MITX_FEATURES.get('AUTH_USE_OPENID') or
settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'), True)
@skipUnless(settings.FEATURES.get('AUTH_USE_OPENID') and
settings.FEATURES.get('AUTH_USE_OPENID_PROVIDER'),
'OpenID not enabled')
def test_begin_login_with_login_url(self):
# the provider URL must be converted to an absolute URL in order to be
......@@ -183,21 +188,24 @@ class OpenIdProviderTest(TestCase):
"got code {0} for url '{1}'. Expected code {2}"
.format(resp.status_code, url, code))
@skipUnless(settings.MITX_FEATURES.get('AUTH_USE_OPENID') or
settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'), True)
@skipUnless(settings.FEATURES.get('AUTH_USE_OPENID') and
settings.FEATURES.get('AUTH_USE_OPENID_PROVIDER'),
'OpenID not enabled')
def test_open_id_setup(self):
""" Attempt a standard successful login """
self.attempt_login(200)
@skipUnless(settings.MITX_FEATURES.get('AUTH_USE_OPENID') or
settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'), True)
@skipUnless(settings.FEATURES.get('AUTH_USE_OPENID') and
settings.FEATURES.get('AUTH_USE_OPENID_PROVIDER'),
'OpenID not enabled')
def test_invalid_namespace(self):
""" Test for 403 error code when the namespace of the request is invalid"""
self.attempt_login(403, ns="http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0")
@override_settings(OPENID_PROVIDER_TRUSTED_ROOTS=['http://apps.cs50.edx.org'])
@skipUnless(settings.MITX_FEATURES.get('AUTH_USE_OPENID') or
settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'), True)
@skipUnless(settings.FEATURES.get('AUTH_USE_OPENID') and
settings.FEATURES.get('AUTH_USE_OPENID_PROVIDER'),
'OpenID not enabled')
def test_invalid_return_url(self):
""" Test for 403 error code when the url"""
self.attempt_login(403, return_to="http://apps.cs50.edx.or")
......@@ -224,15 +232,17 @@ class OpenIdProviderTest(TestCase):
response = provider_login(request)
return response
@skipUnless(settings.MITX_FEATURES.get('AUTH_USE_OPENID') or
settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'), True)
@skipUnless(settings.FEATURES.get('AUTH_USE_OPENID') and
settings.FEATURES.get('AUTH_USE_OPENID_PROVIDER'),
'OpenID not enabled')
def test_login_openid_handle_redirection(self):
""" Test to see that we can handle login redirection properly"""
response = self._send_bad_redirection_login()
self.assertEquals(response.status_code, 302)
@skipUnless(settings.MITX_FEATURES.get('AUTH_USE_OPENID') or
settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'), True)
@skipUnless(settings.FEATURES.get('AUTH_USE_OPENID') and
settings.FEATURES.get('AUTH_USE_OPENID_PROVIDER'),
'OpenID not enabled')
def test_login_openid_handle_redirection_ratelimited(self):
# try logging in 30 times, the default limit in the number of failed
# log in attempts before the rate gets limited
......@@ -245,6 +255,37 @@ class OpenIdProviderTest(TestCase):
# clear the ratelimit cache so that we don't fail other logins
cache.clear()
@skipUnless(settings.FEATURES.get('AUTH_USE_OPENID_PROVIDER'),
'OpenID not enabled')
def test_openid_final_response(self):
url = reverse('openid-provider-login')
user = UserFactory()
# login to the client so that we can persist session information
for name in ['Robot 33', '☃']:
user.profile.name = name
user.profile.save()
self.client.login(username=user.username, password='test')
# login once to get the right session information
self.attempt_login(200)
post_args = {
'email': user.email,
'password': 'test',
}
# call url again, this time with username and password
resp = self.client.post(url, post_args)
# all information is embedded in the redirect url
location = resp['Location']
# parse the url
parsed_url = urlparse(location)
parsed_qs = parse_qs(parsed_url.query)
self.assertEquals(parsed_qs['openid.ax.type.ext1'][0], 'http://axschema.org/contact/email')
self.assertEquals(parsed_qs['openid.ax.type.ext0'][0], 'http://axschema.org/namePerson')
self.assertEquals(parsed_qs['openid.ax.value.ext1.1'][0], user.email)
self.assertEquals(parsed_qs['openid.ax.value.ext0.1'][0], user.profile.name)
class OpenIdProviderLiveServerTest(LiveServerTestCase):
"""
......@@ -254,8 +295,9 @@ class OpenIdProviderLiveServerTest(LiveServerTestCase):
Here we do the former.
"""
@skipUnless(settings.MITX_FEATURES.get('AUTH_USE_OPENID') or
settings.MITX_FEATURES.get('AUTH_USE_OPENID_PROVIDER'), True)
@skipUnless(settings.FEATURES.get('AUTH_USE_OPENID') and
settings.FEATURES.get('AUTH_USE_OPENID_PROVIDER'),
'OpenID not enabled')
def test_begin_login(self):
# the provider URL must be converted to an absolute URL in order to be
# used as an openid provider.
......@@ -289,4 +331,3 @@ class OpenIdProviderLiveServerTest(LiveServerTestCase):
super(OpenIdProviderLiveServerTest, cls).tearDownClass()
except RuntimeError:
print "Warning: Could not shut down test server."
pass
......@@ -81,7 +81,7 @@ class ShibSPTest(ModuleStoreTestCase):
def setUp(self):
self.store = editable_modulestore()
@unittest.skipUnless(settings.MITX_FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
def test_exception_shib_login(self):
"""
Tests that we get the error page when there is no REMOTE_USER
......@@ -107,7 +107,7 @@ class ShibSPTest(ModuleStoreTestCase):
self.assertIn(u'logged in via Shibboleth', args[0])
self.assertEquals(remote_user, args[1])
@unittest.skipUnless(settings.MITX_FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
def test_shib_login(self):
"""
Tests that:
......@@ -207,7 +207,7 @@ class ShibSPTest(ModuleStoreTestCase):
# no audit logging calls
self.assertEquals(len(audit_log_calls), 0)
@unittest.skipUnless(settings.MITX_FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
def test_registration_form(self):
"""
Tests the registration form showing up with the proper parameters.
......@@ -237,7 +237,7 @@ class ShibSPTest(ModuleStoreTestCase):
# clean up b/c we don't want existing ExternalAuthMap for the next run
client.session['ExternalAuthMap'].delete()
@unittest.skipUnless(settings.MITX_FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
def test_registration_form_submit(self):
"""
Tests user creation after the registration form that pops is submitted. If there is no shib
......@@ -320,7 +320,7 @@ class ShibSPTest(ModuleStoreTestCase):
Registration.objects.filter(user=user).delete()
user.delete()
@unittest.skipUnless(settings.MITX_FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
def test_course_specific_login_and_reg(self):
"""
Tests that the correct course specific login and registration urls work for shib
......@@ -392,7 +392,7 @@ class ShibSPTest(ModuleStoreTestCase):
'?course_id=DNE/DNE/DNE' +
'&enrollment_action=enroll')
@unittest.skipUnless(settings.MITX_FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
def test_enrollment_limit_by_domain(self):
"""
Tests that the enrollmentDomain setting is properly limiting enrollment to those who have
......@@ -456,7 +456,7 @@ class ShibSPTest(ModuleStoreTestCase):
self.assertEqual(response.status_code, 400)
self.assertFalse(CourseEnrollment.is_enrolled(student, course.id))
@unittest.skipUnless(settings.MITX_FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
@unittest.skipUnless(settings.FEATURES.get('AUTH_USE_SHIB'), "AUTH_USE_SHIB not set")
def test_shib_login_enrollment(self):
"""
A functionality test that a student with an existing shib login
......
......@@ -5,7 +5,7 @@ import json
from django.conf import settings
from django.http import HttpResponse
from staticfiles.storage import staticfiles_storage
from mitxmako.shortcuts import render_to_response
from edxmako.shortcuts import render_to_response
def get_xmodule_urls():
......
from mitxmako.shortcuts import render_to_string
from edxmako.shortcuts import render_to_string
from pipeline.conf import settings
from pipeline.packager import Packager
......
......@@ -11,7 +11,7 @@ except:
%>${url}</%def>
<%def name='css(group)'>
% if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']:
% if settings.FEATURES['USE_DJANGO_PIPELINE']:
${compressed_css(group)}
% else:
% for filename in settings.PIPELINE_CSS[group]['source_filenames']:
......@@ -20,7 +20,7 @@ except:
%endif
</%def>
<%def name='js(group)'>
% if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']:
% if settings.FEATURES['USE_DJANGO_PIPELINE']:
${compressed_js(group)}
% else:
% for filename in settings.PIPELINE_JS[group]['source_filenames']:
......
......@@ -119,7 +119,7 @@ def replace_static_urls(text, data_directory, course_id=None, static_asset_path=
# if we're running with a MongoBacked store course_namespace is not None, then use studio style urls
elif (not static_asset_path) and course_id and modulestore().get_modulestore_type(course_id) != XML_MODULESTORE_TYPE:
# first look in the static file pipeline and see if we are trying to reference
# a piece of static content which is in the mitx repo (e.g. JS associated with an xmodule)
# a piece of static content which is in the edx-platform repo (e.g. JS associated with an xmodule)
exists_in_staticfiles_storage = False
try:
......
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
import mitxmako
import edxmako
class Command(BaseCommand):
......@@ -15,8 +15,8 @@ body, and an _subject.txt for the subject. '''
#text = open(args[0]).read()
#subject = open(args[1]).read()
users = User.objects.all()
text = mitxmako.lookup['main'].get_template('email/' + args[0] + ".txt").render()
subject = mitxmako.lookup['main'].get_template('email/' + args[0] + "_subject.txt").render().strip()
text = edxmako.lookup['main'].get_template('email/' + args[0] + ".txt").render()
subject = edxmako.lookup['main'].get_template('email/' + args[0] + "_subject.txt").render().strip()
for user in users:
if user.is_active:
user.email_user(subject, text)
This source diff could not be displayed because it is too large. You can view the blob instead.
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