Commit 50730da1 by Akiva Leffert

Merge branch 'release'

parents d644da9e aa2406d3
......@@ -4,12 +4,12 @@
<div class="wrapper-footer wrapper">
<footer class="primary" role="contentinfo">
<div class="colophon">
<p>&copy; ${settings.COPYRIGHT_YEAR} <a href="${marketing_link('ROOT')}" rel="external">${settings.PLATFORM_NAME}</a>.</p>
<p>&copy; ${settings.COPYRIGHT_YEAR} <a data-rel="edx.org" href="${marketing_link('ROOT')}" rel="external">${settings.PLATFORM_NAME}</a>.</p>
## Site operators: Please do not remove this paragraph! This attributes back to edX and makes your acknowledgement of edX's trademarks clear.
<p>
## Translators: 'EdX', 'edX', 'Studio', and 'Open edX' are trademarks of 'edX Inc.'. Please do not translate any of these trademarks and company names.
${_("EdX, Open edX, Studio, and the edX and Open edX logos are registered trademarks or trademarks of {link_start}edX Inc.{link_end}").format(
link_start=u"<a href='https://www.edx.org/'>",
link_start=u"<a data-rel='edx.org' href='https://www.edx.org/'>",
link_end=u"</a>"
)}
</p>
......@@ -25,7 +25,7 @@
</li>
% if settings.TENDER_DOMAIN and user.is_authenticated():
<li class="nav-item nav-peripheral-feedback">
<a href="http://${settings.TENDER_DOMAIN}/discussion/new" class="show-tender" title="${_('Use our feedback tool, Tender, to share your feedback')}">${_("Contact Us")}</a>
<a data-rel="edx.org" href="http://${settings.TENDER_DOMAIN}/discussion/new" class="show-tender" title="${_('Use our feedback tool, Tender, to share your feedback')}">${_("Contact Us")}</a>
</li>
% endif
</ol>
......
......@@ -6,8 +6,10 @@ defuse_xml_libs()
import contracts
contracts.disable_all()
import os
import openedx.core.operations
openedx.core.operations.install_memory_dumper()
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cms.envs.aws")
import cms.startup as startup
......
......@@ -558,7 +558,9 @@ class TextbookTabs(TextbookTabsBase):
yield SingleTextbookTab(
name=textbook.title,
tab_id='textbook/{0}'.format(index),
link_func=lambda course, reverse_func: reverse_func('book', args=[course.id.to_deprecated_string(), index]),
link_func=lambda course, reverse_func, index=index: reverse_func(
'book', args=[course.id.to_deprecated_string(), index]
),
)
......@@ -578,7 +580,9 @@ class PDFTextbookTabs(TextbookTabsBase):
yield SingleTextbookTab(
name=textbook['tab_title'],
tab_id='pdftextbook/{0}'.format(index),
link_func=lambda course, reverse_func: reverse_func('pdf_book', args=[course.id.to_deprecated_string(), index]),
link_func=lambda course, reverse_func, index=index: reverse_func(
'pdf_book', args=[course.id.to_deprecated_string(), index]
),
)
......@@ -598,7 +602,9 @@ class HtmlTextbookTabs(TextbookTabsBase):
yield SingleTextbookTab(
name=textbook['tab_title'],
tab_id='htmltextbook/{0}'.format(index),
link_func=lambda course, reverse_func: reverse_func('html_book', args=[course.id.to_deprecated_string(), index]),
link_func=lambda course, reverse_func, index=index: reverse_func(
'html_book', args=[course.id.to_deprecated_string(), index]
),
)
......
......@@ -137,6 +137,7 @@ class CourseFixture(XBlockContainerFixture):
self._updates = []
self._handouts = []
self._assets = []
self._textbooks = []
self._advanced_settings = {}
self._course_key = None
......@@ -165,6 +166,12 @@ class CourseFixture(XBlockContainerFixture):
"""
self._assets.extend(asset_name)
def add_textbook(self, book_title, chapters):
"""
Add textbook to the list of textbooks to be added when the install method is called.
"""
self._textbooks.append({"chapters": chapters, "tab_title": book_title})
def add_advanced_settings(self, settings):
"""
Adds advanced settings to be set on the course when the install method is called.
......@@ -181,6 +188,7 @@ class CourseFixture(XBlockContainerFixture):
self._create_course()
self._install_course_updates()
self._install_course_handouts()
self._install_course_textbooks()
self._configure_course()
self._upload_assets()
self._add_advanced_settings()
......@@ -352,6 +360,21 @@ class CourseFixture(XBlockContainerFixture):
raise FixtureError('Could not upload {asset_name} with {url}. Status code: {code}'.format(
asset_name=asset_name, url=url, code=upload_response.status_code))
def _install_course_textbooks(self):
"""
Add textbooks to the course, if any are configured.
"""
url = STUDIO_BASE_URL + '/textbooks/' + self._course_key
for book in self._textbooks:
payload = json.dumps(book)
response = self.session.post(url, headers=self.headers, data=payload)
if not response.ok:
raise FixtureError(
"Could not add book to course: {0} with {1}. Status was {2}".format(
book, url, response.status_code))
def _add_advanced_settings(self):
"""
Add advanced settings.
......
......@@ -497,6 +497,46 @@ class HighLevelTabTest(UniqueCourseTest):
self.assertIn(expected, actual_items)
class PDFTextBooksTabTest(UniqueCourseTest):
"""
Tests that verify each of the textbook tabs available within a course.
"""
def setUp(self):
"""
Initialize pages and install a course fixture.
"""
super(PDFTextBooksTabTest, self).setUp()
self.course_info_page = CourseInfoPage(self.browser, self.course_id)
self.tab_nav = TabNavPage(self.browser)
# Install a course with TextBooks
course_fix = CourseFixture(
self.course_info['org'], self.course_info['number'],
self.course_info['run'], self.course_info['display_name']
)
# Add PDF textbooks to course fixture.
for i in range(1, 3):
course_fix.add_textbook("PDF Book {}".format(i), [{"title": "Chapter Of Book {}".format(i), "url": ""}])
course_fix.install()
# Auto-auth register for the course
AutoAuthPage(self.browser, course_id=self.course_id).visit()
def test_verify_textbook_tabs(self):
"""
Test multiple pdf textbooks loads correctly in lms.
"""
self.course_info_page.visit()
# Verify each PDF textbook tab by visiting, it will fail if correct tab is not loaded.
for i in range(1, 3):
self.tab_nav.go_to_tab("PDF Book {}".format(i))
class VideoTest(UniqueCourseTest):
"""
Navigate to a video in the courseware and play it.
......
......@@ -11,12 +11,12 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from courseware.courses import get_course_by_id
from courseware.tests.helpers import get_request_for_user, LoginEnrollmentTestCase
from xmodule import tabs
from xmodule.modulestore.tests.django_utils import (
TEST_DATA_MIXED_TOY_MODULESTORE, TEST_DATA_MIXED_CLOSED_MODULESTORE
)
from courseware.views import get_static_tab_contents, static_tab
from student.tests.factories import UserFactory
from xmodule.tabs import CourseTabList
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
......@@ -59,7 +59,7 @@ class StaticTabDateTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
def test_get_static_tab_contents(self):
course = get_course_by_id(self.toy_course_key)
request = get_request_for_user(UserFactory.create())
tab = CourseTabList.get_tab_by_slug(course.tabs, 'resources')
tab = tabs.CourseTabList.get_tab_by_slug(course.tabs, 'resources')
# Test render works okay
tab_content = get_static_tab_contents(request, course, tab)
......@@ -170,3 +170,55 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
self.assertEqual(course_tab_list[0]['tab_id'], 'courseware')
self.assertEqual(course_tab_list[0]['name'], 'Entrance Exam')
self.assertEqual(course_tab_list[1]['tab_id'], 'instructor')
@override_settings(MODULESTORE=TEST_DATA_MIXED_TOY_MODULESTORE)
class TextBookTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
"""
Validate tab behavior when dealing with textbooks.
"""
def setUp(self):
self.course = CourseFactory.create()
self.set_up_books(2)
self.course.tabs = [
tabs.CoursewareTab(),
tabs.CourseInfoTab(),
tabs.TextbookTabs(),
tabs.PDFTextbookTabs(),
tabs.HtmlTextbookTabs(),
]
self.setup_user()
self.enroll(self.course)
self.num_textbook_tabs = sum(1 for tab in self.course.tabs if isinstance(tab, tabs.TextbookTabsBase))
self.num_textbooks = self.num_textbook_tabs * len(self.books)
def set_up_books(self, num_books):
"""Initializes the textbooks in the course and adds the given number of books to each textbook"""
self.books = [MagicMock() for _ in range(num_books)]
for book_index, book in enumerate(self.books):
book.title = 'Book{0}'.format(book_index)
self.course.textbooks = self.books
self.course.pdf_textbooks = self.books
self.course.html_textbooks = self.books
def test_pdf_textbook_tabs(self):
"""
Test that all textbooks tab links generating correctly.
"""
type_to_reverse_name = {'textbook': 'book', 'pdftextbook': 'pdf_book', 'htmltextbook': 'html_book'}
course_tab_list = get_course_tab_list(self.course, self.user)
num_of_textbooks_found = 0
for tab in course_tab_list:
# Verify links of all textbook type tabs.
if isinstance(tab, tabs.SingleTextbookTab):
book_type, book_index = tab.tab_id.split("/", 1)
expected_link = reverse(
type_to_reverse_name[book_type],
args=[self.course.id.to_deprecated_string(), book_index]
)
tab_link = tab.link_func(self.course, reverse)
self.assertEqual(tab_link, expected_link)
num_of_textbooks_found += 1
self.assertEqual(num_of_textbooks_found, self.num_textbooks)
......@@ -20,6 +20,11 @@
p, ol, ul {
font-family: $sans-serif;
// override needed for poorly scoped font-family styling on p a:link {}
a {
font-family: $sans-serif;
}
}
a {
......@@ -27,7 +32,6 @@
border-bottom: none;
color: $link-color;
text-decoration: none !important;
font-family: $sans-serif;
&:hover, &:focus, &:active {
border-bottom: 1px dotted $link-color;
......@@ -327,6 +331,12 @@ $edx-footer-bg-color: rgb(252,252,252);
// NOTE: needed for poor LMS span styling
color: inherit;
}
a {
@extend %edx-footer-link;
display: inline-block;
margin-bottom: ($edx-footer-spacing/2);
}
}
.footer-about-links {
......
......@@ -30,17 +30,17 @@
</div>
<div class="footer-about-copyright">
## Using "edX Inc." explicitly here for copyright purposes (settings.PLATFORM_NAME is just "edX", and this footer is only used on edx.org)
<p>&copy; ${settings.COPYRIGHT_YEAR} edX Inc.</p>
## Using "edX Inc." explicitly here for copyright purposes (settings.PLATFORM_NAME is just "edX", and this footer is only used on edx.org)
<p>&copy; ${settings.COPYRIGHT_YEAR} edX Inc.</p>
## Site operators: Please do not remove this paragraph! This attributes back to edX and makes your acknowledgement of edX's trademarks clear.
<p>
## Translators: 'EdX', 'edX', and 'Open edX' are trademarks of 'edX Inc.'. Please do not translate any of these trademarks and company names.
${_("EdX, Open edX, and the edX and Open edX logos are registered trademarks or trademarks of {link_start}edX Inc.{link_end}").format(
link_start=u"<a href='https://www.edx.org/'>",
link_end=u"</a>"
)}
</p>
## Site operators: Please do not remove this paragraph! This attributes back to edX and makes your acknowledgement of edX's trademarks clear.
<p>
## Translators: 'EdX', 'edX', and 'Open edX' are trademarks of 'edX Inc.'. Please do not translate any of these trademarks and company names.
${_("EdX, Open edX, and the edX and Open edX logos are registered trademarks or trademarks of {link_start}edX Inc.{link_end}").format(
link_start=u"<a href='https://www.edx.org/'><span class='copy'>",
link_end=u"</span></a>"
)}
</p>
</div>
<div class="footer-about-links">
......
......@@ -107,14 +107,13 @@
) %>
<% } else if ( !isActive ) { %>
<%- gettext( "You need to activate your account before you can enroll in courses. Check your inbox for an activation email. After you complete activation you can return and refresh this page." ) %>
<% } else if ( !upgrade ) { %>
<% } else { %>
<%- gettext( "You can pay now even if you don't have the following items available, but you will need to have these to qualify to earn a Verified Certificate." ) %>
<% } %>
</p>
<% } %>
</div>
<% if ( !upgrade ) { %>
<div class="requirements-container">
<ul class="list-reqs <% if ( requirements['account-activation-required'] ) { %>account-not-activated<% } %>">
<% if ( requirements['account-activation-required'] ) { %>
......@@ -156,7 +155,6 @@
<% } %>
</ul>
</div>
<% } %>
<% if ( isActive ) { %>
......
......@@ -6,8 +6,10 @@ defuse_xml_libs()
import contracts
contracts.disable_all()
import os
import openedx.core.operations
openedx.core.operations.install_memory_dumper()
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.envs.aws")
import lms.startup as startup
......
import os
import signal
import tempfile
from datetime import datetime
from meliae import scanner
def dump_memory(signum, frame):
"""Dump memory stats for the current process to a temp directory. Uses the meliae output format."""
scanner.dump_all_objects('{}/meliae.{}.{}.dump'.format(tempfile.gettempdir(), datetime.now().isoformat(), os.getpid()))
def install_memory_dumper(dump_signal=signal.SIGPROF):
"""
Install a signal handler on `signal` to dump memory stats for the current process.
"""
signal.signal(dump_signal, dump_memory)
......@@ -50,6 +50,9 @@ lazy==1.1
lxml==3.3.6
mako==0.9.1
Markdown==2.2.1
--allow-external meliae
--allow-unverified meliae
meliae==0.4.0
mongoengine==0.7.10
networkx==1.7
nltk==2.0.4
......@@ -58,7 +61,6 @@ oauthlib==0.6.3
paramiko==1.9.0
path.py==3.0.1
Pillow==2.7.0
pip>=1.4
polib==1.0.3
pycrypto>=2.6
pygments==2.0.1
......
......@@ -7,3 +7,9 @@
# Numpy and scipy can't be installed in the same pip run.
# Install numpy before other things to help resolve the problem.
numpy==1.6.2
# Needed to make sure that options in base.txt are allowed
pip==6.0.7
# Needed for meliae
Cython==0.21.2
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment