diff --git a/cms/djangoapps/contentstore/context_processors.py b/cms/djangoapps/contentstore/context_processors.py
index b6046caec4..9d3131dd13 100644
--- a/cms/djangoapps/contentstore/context_processors.py
+++ b/cms/djangoapps/contentstore/context_processors.py
@@ -1,21 +1,84 @@
import ConfigParser
from django.conf import settings
+import logging
+
+log = logging.getLogger(__name__)
+
+
+# Open and parse the configuration file when the module is initialized
config_file = open(settings.REPO_ROOT / "docs" / "config.ini")
config = ConfigParser.ConfigParser()
config.readfp(config_file)
-def doc_url(request):
- # in the future, we will detect the locale; for now, we will
- # hardcode en_us, since we only have English documentation
- locale = "en_us"
+def doc_url(request=None): # pylint: disable=unused-argument
+ """
+ This function is added in the list of TEMPLATE_CONTEXT_PROCESSORS, which is a django setting for
+ a tuple of callables that take a request object as their argument and return a dictionary of items
+ to be merged into the RequestContext.
- def get_doc_url(token):
- try:
- return config.get(locale, token)
- except ConfigParser.NoOptionError:
- return config.get(locale, "default")
+ This function returns a dict with get_online_help_info, making it directly available to all mako templates.
- return {"doc_url": get_doc_url}
+ Args:
+ request: Currently not used, but is passed by django to context processors.
+ May be used in the future for determining the language of choice.
+ """
+
+ def get_online_help_info(page_token=None):
+ """
+ Args:
+ page_token: A string that identifies the page for which the help information is requested.
+ It should correspond to an option in the docs/config.ini file. If it doesn't, the "default"
+ option is used instead.
+
+ Returns:
+ A dict mapping the following items
+ * "doc_url" - a string with the url corresponding to the online help location for the given page_token.
+ * "pdf_url" - a string with the url corresponding to the location of the PDF help file.
+ """
+
+ def get_config_value_with_default(section_name, option, default_option="default"):
+ """
+ Args:
+ section_name: name of the section in the configuration from which the option should be found
+ option: name of the configuration option
+ default_option: name of the default configuration option whose value should be returned if the
+ requested option is not found
+ """
+ try:
+ return config.get(section_name, option)
+ except (ConfigParser.NoOptionError, AttributeError):
+ log.debug("Didn't find a configuration option for '%s' section and '%s' option", section_name, option)
+ return config.get(section_name, default_option)
+
+ def get_doc_url():
+ """
+ Returns:
+ The URL for the documentation
+ """
+ return "{url_base}/{language}/{version}/{page_path}".format(
+ url_base=config.get("help_settings", "url_base"),
+ language=get_config_value_with_default("locales", settings.LANGUAGE_CODE),
+ version=config.get("help_settings", "version"),
+ page_path=get_config_value_with_default("pages", page_token),
+ )
+
+ def get_pdf_url():
+ """
+ Returns:
+ The URL for the PDF document using the pdf_settings and the help_settings (version) in the configuration
+ """
+ return "{pdf_base}/{version}/{pdf_file}".format(
+ pdf_base=config.get("pdf_settings", "pdf_base"),
+ version=config.get("help_settings", "version"),
+ pdf_file=config.get("pdf_settings", "pdf_file"),
+ )
+
+ return {
+ "doc_url": get_doc_url(),
+ "pdf_url": get_pdf_url(),
+ }
+
+ return {'get_online_help_info': get_online_help_info}
diff --git a/cms/djangoapps/contentstore/features/course-export.py b/cms/djangoapps/contentstore/features/course-export.py
index a889f292df..580e582f5d 100644
--- a/cms/djangoapps/contentstore/features/course-export.py
+++ b/cms/djangoapps/contentstore/features/course-export.py
@@ -1,5 +1,6 @@
-# disable missing docstring
# pylint: disable=C0111
+# pylint: disable=W0621
+# pylint: disable=W0613
from lettuce import world, step
from component_settings_editor_helpers import enter_xml_in_advanced_problem
@@ -8,11 +9,16 @@ from xmodule.modulestore.locations import SlashSeparatedCourseKey
from contentstore.utils import reverse_usage_url
-@step('I export the course$')
-def i_export_the_course(step):
+@step('I go to the export page$')
+def i_go_to_the_export_page(step):
world.click_tools()
link_css = 'li.nav-course-tools-export a'
world.css_click(link_css)
+
+
+@step('I export the course$')
+def i_export_the_course(step):
+ step.given('I go to the export page')
world.css_click('a.action-export')
@@ -32,7 +38,7 @@ def i_enter_bad_xml(step):
@step('I edit and enter an ampersand$')
-def i_enter_bad_xml(step):
+def i_enter_an_ampersand(step):
enter_xml_in_advanced_problem(step, "&")
diff --git a/cms/djangoapps/contentstore/features/course_import.py b/cms/djangoapps/contentstore/features/course_import.py
index 84b7affe30..42131f097e 100644
--- a/cms/djangoapps/contentstore/features/course_import.py
+++ b/cms/djangoapps/contentstore/features/course_import.py
@@ -1,5 +1,9 @@
+# pylint: disable=C0111
+# pylint: disable=W0621
+# pylint: disable=W0613
+
import os
-from lettuce import world
+from lettuce import world, step
from django.conf import settings
@@ -14,7 +18,8 @@ def import_file(filename):
world.css_click(outline_css)
-def go_to_import():
+@step('I go to the import page$')
+def go_to_import(step):
menu_css = 'li.nav-course-tools'
import_css = 'li.nav-course-tools-import a'
world.css_click(menu_css)
diff --git a/cms/djangoapps/contentstore/features/help.feature b/cms/djangoapps/contentstore/features/help.feature
new file mode 100644
index 0000000000..ef6bfe33cc
--- /dev/null
+++ b/cms/djangoapps/contentstore/features/help.feature
@@ -0,0 +1,61 @@
+@shard_1
+Feature: CMS.Help
+ As a course author, I am able to access online help
+
+ Scenario: Users can access online help on course listing page
+ Given There are no courses
+ And I am logged into Studio
+ Then I should see online help for "get_started"
+
+
+ Scenario: Users can access online help within a course
+ Given I have opened a new course in Studio
+
+ And I click the course link in My Courses
+ Then I should see online help for "organizing_course"
+
+ And I go to the course updates page
+ Then I should see online help for "updates"
+
+ And I go to the pages page
+ Then I should see online help for "pages"
+
+ And I go to the files and uploads page
+ Then I should see online help for "files"
+
+ And I go to the textbooks page
+ Then I should see online help for "textbooks"
+
+ And I select Schedule and Details
+ Then I should see online help for "setting_up"
+
+ And I am viewing the grading settings
+ Then I should see online help for "grading"
+
+ And I am viewing the course team settings
+ Then I should see online help for "course-team"
+
+ And I select the Advanced Settings
+ Then I should see online help for "index"
+
+ And I select Checklists from the Tools menu
+ Then I should see online help for "checklist"
+
+ And I go to the import page
+ Then I should see online help for "import"
+
+ And I go to the export page
+ Then I should see online help for "export"
+
+
+ Scenario: Users can access online help on the unit page
+ Given I am in Studio editing a new unit
+ Then I should see online help for "units"
+
+
+ Scenario: Users can access online help on the subsection page
+ Given I have opened a new course section in Studio
+ And I have added a new subsection
+ And I click on the subsection
+ Then I should see online help for "subsections"
+
diff --git a/cms/djangoapps/contentstore/features/help.py b/cms/djangoapps/contentstore/features/help.py
new file mode 100644
index 0000000000..639aad9c01
--- /dev/null
+++ b/cms/djangoapps/contentstore/features/help.py
@@ -0,0 +1,24 @@
+# pylint: disable=C0111
+# pylint: disable=W0621
+# pylint: disable=W0613
+
+from nose.tools import assert_false # pylint: disable=no-name-in-module
+from lettuce import step, world
+
+
+@step(u'I should see online help for "([^"]*)"$')
+def see_online_help_for(step, page_name):
+ # make sure the online Help link exists on this page and contains the expected page name
+ elements_found = world.browser.find_by_xpath(
+ '//li[contains(@class, "nav-account-help")]//a[contains(@href, "{page_name}")]'.format(
+ page_name=page_name
+ )
+ )
+ assert_false(elements_found.is_empty())
+
+ # make sure the PDF link on the sock of this page exists
+ # for now, the PDF link stays constant for all the pages so we just check for "pdf"
+ elements_found = world.browser.find_by_xpath(
+ '//section[contains(@class, "sock")]//li[contains(@class, "js-help-pdf")]//a[contains(@href, "pdf")]'
+ )
+ assert_false(elements_found.is_empty())
diff --git a/cms/djangoapps/contentstore/features/problem-editor.py b/cms/djangoapps/contentstore/features/problem-editor.py
index b031363d00..a57e5bda01 100644
--- a/cms/djangoapps/contentstore/features/problem-editor.py
+++ b/cms/djangoapps/contentstore/features/problem-editor.py
@@ -6,7 +6,7 @@ from lettuce import world, step
from nose.tools import assert_equal, assert_true # pylint: disable=E0611
from common import type_in_codemirror, open_new_course
from advanced_settings import change_value
-from course_import import import_file, go_to_import
+from course_import import import_file
DISPLAY_NAME = "Display Name"
MAXIMUM_ATTEMPTS = "Maximum Attempts"
@@ -218,11 +218,6 @@ def i_have_empty_course(step):
open_new_course()
-@step(u'I go to the import page')
-def i_go_to_import(_step):
- go_to_import()
-
-
@step(u'I import the file "([^"]*)"$')
def i_import_the_file(_step, filename):
import_file(filename)
diff --git a/cms/envs/devstack.py b/cms/envs/devstack.py
index 6118eb3018..6dc95c1925 100644
--- a/cms/envs/devstack.py
+++ b/cms/envs/devstack.py
@@ -4,6 +4,10 @@ Specific overrides to the base prod settings to make development easier.
from .aws import * # pylint: disable=wildcard-import, unused-wildcard-import
+# Don't use S3 in devstack, fall back to filesystem
+del DEFAULT_FILE_STORAGE
+MEDIA_ROOT = "/edx/var/edxapp/uploads"
+
DEBUG = True
USE_I18N = True
TEMPLATE_DEBUG = DEBUG
diff --git a/cms/templates/asset_index.html b/cms/templates/asset_index.html
index 3cb6b34a07..04f6cef0bb 100644
--- a/cms/templates/asset_index.html
+++ b/cms/templates/asset_index.html
@@ -1,4 +1,5 @@
<%inherit file="base.html" />
+<%def name="online_help_token()"><% return "files" %>%def>
<%!
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
diff --git a/cms/templates/base.html b/cms/templates/base.html
index 93a3ec4ff7..c5ba8ac3a2 100644
--- a/cms/templates/base.html
+++ b/cms/templates/base.html
@@ -264,7 +264,8 @@