Commit 50730da1 by Akiva Leffert

Merge branch 'release'

parents d644da9e aa2406d3
...@@ -4,12 +4,12 @@ ...@@ -4,12 +4,12 @@
<div class="wrapper-footer wrapper"> <div class="wrapper-footer wrapper">
<footer class="primary" role="contentinfo"> <footer class="primary" role="contentinfo">
<div class="colophon"> <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. ## Site operators: Please do not remove this paragraph! This attributes back to edX and makes your acknowledgement of edX's trademarks clear.
<p> <p>
## Translators: 'EdX', 'edX', 'Studio', and 'Open edX' are trademarks of 'edX Inc.'. Please do not translate any of these trademarks and company names. ## 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( ${_("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>" link_end=u"</a>"
)} )}
</p> </p>
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
</li> </li>
% if settings.TENDER_DOMAIN and user.is_authenticated(): % if settings.TENDER_DOMAIN and user.is_authenticated():
<li class="nav-item nav-peripheral-feedback"> <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> </li>
% endif % endif
</ol> </ol>
......
...@@ -6,8 +6,10 @@ defuse_xml_libs() ...@@ -6,8 +6,10 @@ defuse_xml_libs()
import contracts import contracts
contracts.disable_all() 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") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cms.envs.aws")
import cms.startup as startup import cms.startup as startup
......
...@@ -558,7 +558,9 @@ class TextbookTabs(TextbookTabsBase): ...@@ -558,7 +558,9 @@ class TextbookTabs(TextbookTabsBase):
yield SingleTextbookTab( yield SingleTextbookTab(
name=textbook.title, name=textbook.title,
tab_id='textbook/{0}'.format(index), 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): ...@@ -578,7 +580,9 @@ class PDFTextbookTabs(TextbookTabsBase):
yield SingleTextbookTab( yield SingleTextbookTab(
name=textbook['tab_title'], name=textbook['tab_title'],
tab_id='pdftextbook/{0}'.format(index), 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): ...@@ -598,7 +602,9 @@ class HtmlTextbookTabs(TextbookTabsBase):
yield SingleTextbookTab( yield SingleTextbookTab(
name=textbook['tab_title'], name=textbook['tab_title'],
tab_id='htmltextbook/{0}'.format(index), 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): ...@@ -137,6 +137,7 @@ class CourseFixture(XBlockContainerFixture):
self._updates = [] self._updates = []
self._handouts = [] self._handouts = []
self._assets = [] self._assets = []
self._textbooks = []
self._advanced_settings = {} self._advanced_settings = {}
self._course_key = None self._course_key = None
...@@ -165,6 +166,12 @@ class CourseFixture(XBlockContainerFixture): ...@@ -165,6 +166,12 @@ class CourseFixture(XBlockContainerFixture):
""" """
self._assets.extend(asset_name) 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): def add_advanced_settings(self, settings):
""" """
Adds advanced settings to be set on the course when the install method is called. Adds advanced settings to be set on the course when the install method is called.
...@@ -181,6 +188,7 @@ class CourseFixture(XBlockContainerFixture): ...@@ -181,6 +188,7 @@ class CourseFixture(XBlockContainerFixture):
self._create_course() self._create_course()
self._install_course_updates() self._install_course_updates()
self._install_course_handouts() self._install_course_handouts()
self._install_course_textbooks()
self._configure_course() self._configure_course()
self._upload_assets() self._upload_assets()
self._add_advanced_settings() self._add_advanced_settings()
...@@ -352,6 +360,21 @@ class CourseFixture(XBlockContainerFixture): ...@@ -352,6 +360,21 @@ class CourseFixture(XBlockContainerFixture):
raise FixtureError('Could not upload {asset_name} with {url}. Status code: {code}'.format( raise FixtureError('Could not upload {asset_name} with {url}. Status code: {code}'.format(
asset_name=asset_name, url=url, code=upload_response.status_code)) 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): def _add_advanced_settings(self):
""" """
Add advanced settings. Add advanced settings.
......
...@@ -497,6 +497,46 @@ class HighLevelTabTest(UniqueCourseTest): ...@@ -497,6 +497,46 @@ class HighLevelTabTest(UniqueCourseTest):
self.assertIn(expected, actual_items) 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): class VideoTest(UniqueCourseTest):
""" """
Navigate to a video in the courseware and play it. Navigate to a video in the courseware and play it.
......
...@@ -11,12 +11,12 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey ...@@ -11,12 +11,12 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from courseware.courses import get_course_by_id from courseware.courses import get_course_by_id
from courseware.tests.helpers import get_request_for_user, LoginEnrollmentTestCase from courseware.tests.helpers import get_request_for_user, LoginEnrollmentTestCase
from xmodule import tabs
from xmodule.modulestore.tests.django_utils import ( from xmodule.modulestore.tests.django_utils import (
TEST_DATA_MIXED_TOY_MODULESTORE, TEST_DATA_MIXED_CLOSED_MODULESTORE TEST_DATA_MIXED_TOY_MODULESTORE, TEST_DATA_MIXED_CLOSED_MODULESTORE
) )
from courseware.views import get_static_tab_contents, static_tab from courseware.views import get_static_tab_contents, static_tab
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xmodule.tabs import CourseTabList
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
...@@ -59,7 +59,7 @@ class StaticTabDateTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -59,7 +59,7 @@ class StaticTabDateTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
def test_get_static_tab_contents(self): def test_get_static_tab_contents(self):
course = get_course_by_id(self.toy_course_key) course = get_course_by_id(self.toy_course_key)
request = get_request_for_user(UserFactory.create()) 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 # Test render works okay
tab_content = get_static_tab_contents(request, course, tab) tab_content = get_static_tab_contents(request, course, tab)
...@@ -170,3 +170,55 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -170,3 +170,55 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
self.assertEqual(course_tab_list[0]['tab_id'], 'courseware') self.assertEqual(course_tab_list[0]['tab_id'], 'courseware')
self.assertEqual(course_tab_list[0]['name'], 'Entrance Exam') self.assertEqual(course_tab_list[0]['name'], 'Entrance Exam')
self.assertEqual(course_tab_list[1]['tab_id'], 'instructor') 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 @@ ...@@ -20,6 +20,11 @@
p, ol, ul { p, ol, ul {
font-family: $sans-serif; font-family: $sans-serif;
// override needed for poorly scoped font-family styling on p a:link {}
a {
font-family: $sans-serif;
}
} }
a { a {
...@@ -27,7 +32,6 @@ ...@@ -27,7 +32,6 @@
border-bottom: none; border-bottom: none;
color: $link-color; color: $link-color;
text-decoration: none !important; text-decoration: none !important;
font-family: $sans-serif;
&:hover, &:focus, &:active { &:hover, &:focus, &:active {
border-bottom: 1px dotted $link-color; border-bottom: 1px dotted $link-color;
...@@ -327,6 +331,12 @@ $edx-footer-bg-color: rgb(252,252,252); ...@@ -327,6 +331,12 @@ $edx-footer-bg-color: rgb(252,252,252);
// NOTE: needed for poor LMS span styling // NOTE: needed for poor LMS span styling
color: inherit; color: inherit;
} }
a {
@extend %edx-footer-link;
display: inline-block;
margin-bottom: ($edx-footer-spacing/2);
}
} }
.footer-about-links { .footer-about-links {
......
...@@ -30,17 +30,17 @@ ...@@ -30,17 +30,17 @@
</div> </div>
<div class="footer-about-copyright"> <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) ## 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> <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. ## Site operators: Please do not remove this paragraph! This attributes back to edX and makes your acknowledgement of edX's trademarks clear.
<p> <p>
## Translators: 'EdX', 'edX', and 'Open edX' are trademarks of 'edX Inc.'. Please do not translate any of these trademarks and company names. ## 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( ${_("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_start=u"<a href='https://www.edx.org/'><span class='copy'>",
link_end=u"</a>" link_end=u"</span></a>"
)} )}
</p> </p>
</div> </div>
<div class="footer-about-links"> <div class="footer-about-links">
......
...@@ -107,14 +107,13 @@ ...@@ -107,14 +107,13 @@
) %> ) %>
<% } else if ( !isActive ) { %> <% } 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." ) %> <%- 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." ) %> <%- 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> </p>
<% } %> <% } %>
</div> </div>
<% if ( !upgrade ) { %>
<div class="requirements-container"> <div class="requirements-container">
<ul class="list-reqs <% if ( requirements['account-activation-required'] ) { %>account-not-activated<% } %>"> <ul class="list-reqs <% if ( requirements['account-activation-required'] ) { %>account-not-activated<% } %>">
<% if ( requirements['account-activation-required'] ) { %> <% if ( requirements['account-activation-required'] ) { %>
...@@ -156,7 +155,6 @@ ...@@ -156,7 +155,6 @@
<% } %> <% } %>
</ul> </ul>
</div> </div>
<% } %>
<% if ( isActive ) { %> <% if ( isActive ) { %>
......
...@@ -6,8 +6,10 @@ defuse_xml_libs() ...@@ -6,8 +6,10 @@ defuse_xml_libs()
import contracts import contracts
contracts.disable_all() 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") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lms.envs.aws")
import lms.startup as startup 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 ...@@ -50,6 +50,9 @@ lazy==1.1
lxml==3.3.6 lxml==3.3.6
mako==0.9.1 mako==0.9.1
Markdown==2.2.1 Markdown==2.2.1
--allow-external meliae
--allow-unverified meliae
meliae==0.4.0
mongoengine==0.7.10 mongoengine==0.7.10
networkx==1.7 networkx==1.7
nltk==2.0.4 nltk==2.0.4
...@@ -58,7 +61,6 @@ oauthlib==0.6.3 ...@@ -58,7 +61,6 @@ oauthlib==0.6.3
paramiko==1.9.0 paramiko==1.9.0
path.py==3.0.1 path.py==3.0.1
Pillow==2.7.0 Pillow==2.7.0
pip>=1.4
polib==1.0.3 polib==1.0.3
pycrypto>=2.6 pycrypto>=2.6
pygments==2.0.1 pygments==2.0.1
......
...@@ -7,3 +7,9 @@ ...@@ -7,3 +7,9 @@
# Numpy and scipy can't be installed in the same pip run. # Numpy and scipy can't be installed in the same pip run.
# Install numpy before other things to help resolve the problem. # Install numpy before other things to help resolve the problem.
numpy==1.6.2 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