<%- interpolate(
- gettext('If you select an option other than "%(hide_label)s", after the subsection release date has passed, published units in this subsection will become available to learners unless units are explicitly hidden.'),
+ gettext('If you select an option other than "%(hide_label)s", published units in this subsection become available to learners unless they are explicitly hidden.'),
{ hide_label: hide_label },
true
) %>
diff --git a/cms/templates/js/course-outline.underscore b/cms/templates/js/course-outline.underscore
index 383176314c..4e1418a658 100644
--- a/cms/templates/js/course-outline.underscore
+++ b/cms/templates/js/course-outline.underscore
@@ -201,7 +201,12 @@ if (is_proctored_exam) {
<% if (xblockInfo.get('hide_after_due')) { %>
- <%- gettext("Subsection is hidden after due date") %>
+
+ <% if (course.get('self_paced')) { %>
+ <%- gettext("Subsection is hidden after course end date") %>
+ <% } else { %>
+ <%- gettext("Subsection is hidden after due date") %>
+ <% } %>
<% } %>
diff --git a/common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee b/common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee
index 545d7c8c29..4ca8d5585b 100644
--- a/common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee
+++ b/common/lib/xmodule/xmodule/js/spec/capa/display_spec.coffee
@@ -236,56 +236,26 @@ describe 'Problem', ->
expect(@problem.el).toHaveHtml contents
expect(window.SR.readTexts).toHaveBeenCalledWith ['no, try again']
- it 'tests if all the capa buttons are disabled while submitting', (done)->
- deferred = $.Deferred()
+ it 'tests if the submit button is disabled while submitting and the text changes on the button', ->
self = this
-
- runs = ->
- spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
- promise = undefined
- callback
- success: 'incorrect'
- contents: 'Incorrect'
- promise =
- always: (callable) ->
- callable()
- done: (callable) ->
- callable()
- spyOn @problem, 'enableAllButtons'
- @problem.submit()
- expect(@problem.enableAllButtons).toHaveBeenCalledWith false, true
- if jQuery.active == 0
- deferred.resolve()
- deferred.promise()
-
- runs.call(self).then(->
- expect(self.problem.enableAllButtons).toHaveBeenCalledWith true, true
- return
- ).always done
-
- it 'tests the expected change in text of submit button', (done) ->
- deferred = $.Deferred()
- self = this
-
- runs = ->
- spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
- promise = undefined
- promise =
- always: (callable) ->
- callable()
- done: (callable) ->
- callable()
- spyOn @problem.submitButtonLabel, 'text'
- @problem.submit()
- expect(@problem.submitButtonLabel.text).toHaveBeenCalledWith 'Submitting'
- if jQuery.active == 0
- deferred.resolve()
- deferred.promise()
-
- runs.call(self).then(->
- expect(self.problem.submitButtonLabel.text).toHaveBeenCalledWith 'Submit'
- return
- ).always done
+ curr_html = @problem.el.html()
+ spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
+ # At this point enableButtons should have been called, making the submit button disabled with text 'submitting'
+ expect(self.problem.submitButton).toHaveAttr('disabled');
+ expect(self.problem.submitButtonLabel.text()).toBe('Submitting');
+ callback
+ success: 'incorrect' # does not matter if correct or incorrect here
+ contents: curr_html
+ promise =
+ always: (callable) -> callable()
+ done: (callable) -> callable()
+ # Make sure the submit button is enabled before submitting
+ $('#input_example_1').val('test').trigger('input')
+ expect(@problem.submitButton).not.toHaveAttr('disabled')
+ @problem.submit()
+ # After submit, the button should not be disabled and should have text as 'Submit'
+ expect(@problem.submitButtonLabel.text()).toBe('Submit')
+ expect(@problem.submitButton).not.toHaveAttr('disabled')
describe 'submit button on problems', ->
beforeEach ->
@@ -424,27 +394,22 @@ describe 'Problem', ->
@problem.reset()
expect($('.notification-gentle-alert .notification-message').text()).toEqual("Error on reset.")
- it 'tests if all the buttons are disabled and the text of submit button remains same while resetting', (done) ->
- deferred = $.Deferred()
+ it 'tests that reset does not enable submit or modify the text while resetting', ->
self = this
-
- runs = ->
- spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
- promise = undefined
- promise = always: (callable) ->
- callable()
- spyOn @problem, 'enableAllButtons'
- @problem.reset()
- expect(@problem.enableAllButtons).toHaveBeenCalledWith false, false
- expect(@problem.submitButtonLabel).toHaveText 'Submit'
- if jQuery.active == 0
- deferred.resolve()
- deferred.promise()
-
- runs.call(self).then(->
- expect(self.problem.enableAllButtons).toHaveBeenCalledWith true, false
- expect(self.problem.submitButtonLabel).toHaveText 'Submit'
- ).always done
+ curr_html = @problem.el.html()
+ spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
+ # enableButtons should have been called at this point to set them to all disabled
+ expect(self.problem.submitButton).toHaveAttr('disabled')
+ expect(self.problem.submitButtonLabel.text()).toBe('Submit')
+ callback(success: 'correct', html: curr_html)
+ promise =
+ always: (callable) -> callable()
+ # Submit should be disabled
+ expect(@problem.submitButton).toHaveAttr('disabled')
+ @problem.reset()
+ # Submit should remain disabled
+ expect(self.problem.submitButton).toHaveAttr('disabled')
+ expect(self.problem.submitButtonLabel.text()).toBe('Submit')
describe 'show', ->
beforeEach ->
@@ -483,22 +448,14 @@ describe 'Problem', ->
@problem.show()
expect(@problem.el.find('.show').attr('disabled')).toEqual('disabled')
- it 'sends a SR message when answer is present', (done) ->
- deferred = $.Deferred()
+ it 'sends a SR message when answer is present', ->
- runs = ->
- spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
- callback answers:
- '1_1': 'answers'
- @problem.show()
- if jQuery.active == 0
- deferred.resolve()
- deferred.promise()
+ spyOn($, 'postWithPrefix').and.callFake (url, callback) ->
+ callback answers:
+ '1_1': 'answers'
+ @problem.show()
- runs.call(this).then(->
- expect(window.SR.readText).toHaveBeenCalledWith 'Answers to this problem are now shown. Navigate through the problem to review it with answers inline.'
- return
- ).always done
+ expect(window.SR.readText).toHaveBeenCalledWith 'Answers to this problem are now shown. Navigate through the problem to review it with answers inline.'
describe 'multiple choice question', ->
beforeEach ->
@@ -723,28 +680,42 @@ describe 'Problem', ->
expect($.postWithPrefix).toHaveBeenCalledWith '/problem/Problem1/problem_save',
'foo=1&bar=2', jasmine.any(Function)
- it 'tests if all the buttons are disabled and the text of submit button does not change while saving.', (done) ->
- deferred = $.Deferred()
+ it 'tests that save does not enable the submit button or change the text when submit is originally disabled', ->
self = this
curr_html = @problem.el.html()
- runs = ->
- spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
- promise = undefined
- callback(success: 'correct', html: curr_html)
- promise = always: (callable) ->
- callable()
- spyOn @problem, 'enableAllButtons'
- @problem.save()
- expect(@problem.enableAllButtons).toHaveBeenCalledWith false, false
- expect(@problem.submitButtonLabel).toHaveText 'Submit'
- if jQuery.active == 0
- deferred.resolve()
- deferred.promise()
+ spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
+ # enableButtons should have been called at this point and the submit button should be unaffected
+ expect(self.problem.submitButton).toHaveAttr('disabled')
+ expect(self.problem.submitButtonLabel.text()).toBe('Submit')
+ callback(success: 'correct', html: curr_html)
+ promise =
+ always: (callable) -> callable()
+ # Expect submit to be disabled and labeled properly at the start
+ expect(@problem.submitButton).toHaveAttr('disabled')
+ expect(@problem.submitButtonLabel.text()).toBe('Submit')
+ @problem.save()
+ # Submit button should have the same state after save has completed
+ expect(@problem.submitButton).toHaveAttr('disabled')
+ expect(@problem.submitButtonLabel.text()).toBe('Submit')
- runs.call(self).then(->
- expect(self.problem.enableAllButtons).toHaveBeenCalledWith true, false
- expect(self.problem.submitButtonLabel).toHaveText 'Submit'
- ).always done
+ it 'tests that save does not disable the submit button or change the text when submit is originally enabled', ->
+ self = this
+ curr_html = @problem.el.html()
+ spyOn($, 'postWithPrefix').and.callFake (url, answers, callback) ->
+ # enableButtons should have been called at this point, and the submit button should be disabled while submitting
+ expect(self.problem.submitButton).toHaveAttr('disabled')
+ expect(self.problem.submitButtonLabel.text()).toBe('Submit')
+ callback(success: 'correct', html: curr_html)
+ promise =
+ always: (callable) -> callable()
+ # Expect submit to be enabled and labeled properly at the start after adding an input
+ $('#input_example_1').val('test').trigger('input')
+ expect(@problem.submitButton).not.toHaveAttr('disabled')
+ expect(@problem.submitButtonLabel.text()).toBe('Submit')
+ @problem.save()
+ # Submit button should have the same state after save has completed
+ expect(@problem.submitButton).not.toHaveAttr('disabled')
+ expect(@problem.submitButtonLabel.text()).toBe('Submit')
describe 'refreshMath', ->
beforeEach ->
diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.js b/common/lib/xmodule/xmodule/js/src/capa/display.js
index ab51436107..19aab27fa9 100644
--- a/common/lib/xmodule/xmodule/js/src/capa/display.js
+++ b/common/lib/xmodule/xmodule/js/src/capa/display.js
@@ -36,9 +36,6 @@
}
return Problem.prototype.enableSubmitButton.apply(that, arguments);
};
- this.enableAllButtons = function(enable, isFromCheckOperation) { // eslint-disable-line no-unused-vars
- return Problem.prototype.enableAllButtons.apply(that, arguments);
- };
this.disableAllButtonsWhileRunning = function(
operationCallback, isFromCheckOperation // eslint-disable-line no-unused-vars
) {
@@ -1157,32 +1154,38 @@
*/
Problem.prototype.disableAllButtonsWhileRunning = function(operationCallback, isFromCheckOperation) {
var that = this;
- this.enableAllButtons(false, isFromCheckOperation);
+ var allButtons = [this.resetButton, this.saveButton, this.showButton, this.hintButton, this.submitButton];
+ var initiallyEnabledButtons = allButtons.filter(function(button) {
+ return !button.attr('disabled');
+ });
+ this.enableButtons(initiallyEnabledButtons, false, isFromCheckOperation);
return operationCallback().always(function() {
- return that.enableAllButtons(true, isFromCheckOperation);
+ return that.enableButtons(initiallyEnabledButtons, true, isFromCheckOperation);
});
};
/**
- * Used to enable/disable all buttons in problem.
+ * Enables/disables buttons by removing/adding the disabled attribute. The submit button is checked
+ * separately due to the changing text it contains.
*
* params:
- * 'enable' is a boolean to determine enabling/disabling of buttons.
- * 'isFromCheckOperation' is a boolean to keep track if operation was initiated
+ * 'buttons' is an array of buttons that will have their 'disabled' attribute modified
+ * 'enable' a boolean to either enable or disable the buttons passed in the first parameter
+ * 'changeSubmitButtonText' is a boolean to keep track if operation was initiated
* from submit so that text of submit button will also be changed while disabling/enabling
* the submit button.
*/
- Problem.prototype.enableAllButtons = function(enable, isFromCheckOperation) {
- // Called by disableAllButtonsWhileRunning to automatically disable all buttons while check,reset, or
- // save internal are running. Then enable all the buttons again after it is done.
- if (enable) {
- this.resetButton.add(this.saveButton).add(this.hintButton).add(this.showButton).
- removeAttr('disabled');
- } else {
- this.resetButton.add(this.saveButton).add(this.hintButton).add(this.showButton).
- attr({disabled: 'disabled'});
- }
- return this.enableSubmitButton(enable, isFromCheckOperation);
+ Problem.prototype.enableButtons = function(buttons, enable, changeSubmitButtonText) {
+ var that = this;
+ buttons.forEach(function(button) {
+ if (button.hasClass('submit')) {
+ that.enableSubmitButton(enable, changeSubmitButtonText);
+ } else if (enable) {
+ button.removeAttr('disabled');
+ } else {
+ button.attr({disabled: 'disabled'});
+ }
+ });
};
/**
diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py
index ca588eee33..8233f2a669 100644
--- a/common/lib/xmodule/xmodule/seq_module.py
+++ b/common/lib/xmodule/xmodule/seq_module.py
@@ -202,16 +202,16 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
raise NotFoundError('Unexpected dispatch type')
@classmethod
- def verify_current_content_visibility(cls, due, hide_after_due):
+ def verify_current_content_visibility(cls, date, hide_after_date):
"""
Returns whether the content visibility policy passes
- for the given due date and hide_after_due values and
+ for the given date and hide_after_date values and
the current date-time.
"""
return (
- not due or
- not hide_after_due or
- datetime.now(UTC()) < due
+ not date or
+ not hide_after_date or
+ datetime.now(UTC()) < date
)
def student_view(self, context):
@@ -246,20 +246,17 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
runtime user. If so, returns a banner_text or the fragment to
display depending on whether staff is masquerading.
"""
- if not self._can_user_view_content():
- subsection_format = (self.format or _("subsection")).lower() # pylint: disable=no-member
-
- # Translators: subsection_format refers to the assignment
- # type of the subsection, such as Homework, Lab, Exam, etc.
- banner_text = _(
- "Because the due date has passed, "
- "this {subsection_format} is hidden from the learner."
- ).format(subsection_format=subsection_format)
+ course = self._get_course()
+ if not self._can_user_view_content(course):
+ if course.self_paced:
+ banner_text = _("Because the course has ended, this assignment is hidden from the learner.")
+ else:
+ banner_text = _("Because the due date has passed, this assignment is hidden from the learner.")
hidden_content_html = self.system.render_template(
'hidden_content.html',
{
- 'subsection_format': subsection_format,
+ 'self_paced': course.self_paced,
'progress_url': context.get('progress_url'),
}
)
@@ -280,14 +277,15 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
if content_milestones and self.runtime.user_is_staff:
return banner_text
- def _can_user_view_content(self):
+ def _can_user_view_content(self, course):
"""
Returns whether the runtime user can view the content
of this sequential.
"""
+ hidden_date = course.end if course.self_paced else self.due
return (
self.runtime.user_is_staff or
- self.verify_current_content_visibility(self.due, self.hide_after_due)
+ self.verify_current_content_visibility(hidden_date, self.hide_after_due)
)
def _student_view(self, context, banner_text=None):
diff --git a/common/lib/xmodule/xmodule/tests/test_sequence.py b/common/lib/xmodule/xmodule/tests/test_sequence.py
index 5643ada5ca..26112a6110 100644
--- a/common/lib/xmodule/xmodule/tests/test_sequence.py
+++ b/common/lib/xmodule/xmodule/tests/test_sequence.py
@@ -3,49 +3,46 @@ Tests for sequence module.
"""
# pylint: disable=no-member
from datetime import timedelta
-import ddt
from django.utils.timezone import now
from freezegun import freeze_time
-from mock import Mock
+from mock import Mock, patch
+from xmodule.seq_module import SequenceModule
from xmodule.tests import get_test_system
from xmodule.tests.helpers import StubUserService
-from xmodule.tests.xml import XModuleXmlImportTest
-from xmodule.tests.xml import factories as xml
+from xmodule.tests.xml import factories as xml, XModuleXmlImportTest
from xmodule.x_module import STUDENT_VIEW
-from xmodule.seq_module import SequenceModule
+
+TODAY = now()
+DUE_DATE = TODAY + timedelta(days=7)
+PAST_DUE_BEFORE_END_DATE = TODAY + timedelta(days=14)
+COURSE_END_DATE = TODAY + timedelta(days=21)
-@ddt.ddt
class SequenceBlockTestCase(XModuleXmlImportTest):
"""
- Tests for the Sequence Module.
+ Base class for tests of Sequence Module.
"""
- TODAY = now()
- TOMORROW = TODAY + timedelta(days=1)
- DAY_AFTER_TOMORROW = TOMORROW + timedelta(days=1)
+ def setUp(self):
+ super(SequenceBlockTestCase, self).setUp()
- @classmethod
- def setUpClass(cls):
- super(SequenceBlockTestCase, cls).setUpClass()
+ course_xml = self._set_up_course_xml()
+ self.course = self.process_xml(course_xml)
+ self._set_up_module_system(self.course)
- course_xml = cls._set_up_course_xml()
- cls.course = cls.process_xml(course_xml)
- cls._set_up_module_system(cls.course)
-
- for chapter_index in range(len(cls.course.get_children())):
- chapter = cls._set_up_block(cls.course, chapter_index)
- setattr(cls, 'chapter_{}'.format(chapter_index + 1), chapter)
+ for chapter_index in range(len(self.course.get_children())):
+ chapter = self._set_up_block(self.course, chapter_index)
+ setattr(self, 'chapter_{}'.format(chapter_index + 1), chapter)
for sequence_index in range(len(chapter.get_children())):
- sequence = cls._set_up_block(chapter, sequence_index)
- setattr(cls, 'sequence_{}_{}'.format(chapter_index + 1, sequence_index + 1), sequence)
+ sequence = self._set_up_block(chapter, sequence_index)
+ setattr(self, 'sequence_{}_{}'.format(chapter_index + 1, sequence_index + 1), sequence)
- @classmethod
- def _set_up_course_xml(cls):
+ @staticmethod
+ def _set_up_course_xml():
"""
Sets up and returns XML course structure.
"""
- course = xml.CourseFactory.build()
+ course = xml.CourseFactory.build(end=str(COURSE_END_DATE))
chapter_1 = xml.ChapterFactory.build(parent=course) # has 2 child sequences
xml.ChapterFactory.build(parent=course) # has 0 child sequences
@@ -58,7 +55,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
xml.SequenceFactory.build( # sequence_4_1
parent=chapter_4,
hide_after_due=str(True),
- due=str(cls.TOMORROW),
+ due=str(DUE_DATE),
)
for _ in range(3):
@@ -66,14 +63,13 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
return course
- @classmethod
- def _set_up_block(cls, parent, index_in_parent):
+ def _set_up_block(self, parent, index_in_parent):
"""
Sets up the stub sequence module for testing.
"""
block = parent.get_children()[index_in_parent]
- cls._set_up_module_system(block)
+ self._set_up_module_system(block)
block.xmodule_runtime._services['bookmarks'] = Mock() # pylint: disable=protected-access
block.xmodule_runtime._services['user'] = StubUserService() # pylint: disable=protected-access
@@ -81,8 +77,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
block.parent = parent.location
return block
- @classmethod
- def _set_up_module_system(cls, block):
+ def _set_up_module_system(self, block):
"""
Sets up the test module system for the given block.
"""
@@ -90,6 +85,28 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
module_system.descriptor_runtime = block._runtime # pylint: disable=protected-access
block.xmodule_runtime = module_system
+ def _get_rendered_student_view(self, sequence, requested_child=None, extra_context=None, self_paced=False):
+ """
+ Returns the rendered student view for the given sequence and the
+ requested_child parameter.
+ """
+ context = {'requested_child': requested_child}
+ if extra_context:
+ context.update(extra_context)
+
+ # The render operation will ask modulestore for the current course to get some data. As these tests were
+ # originally not written to be compatible with a real modulestore, we've mocked out the relevant return values.
+ with patch.object(SequenceModule, '_get_course') as mock_course:
+ self.course.self_paced = self_paced
+ mock_course.return_value = self.course
+ return sequence.xmodule_runtime.render(sequence, STUDENT_VIEW, context).content
+
+ def _assert_view_at_position(self, rendered_html, expected_position):
+ """
+ Verifies that the rendered view contains the expected position.
+ """
+ self.assertIn("'position': {}".format(expected_position), rendered_html)
+
def test_student_view_init(self):
seq_module = SequenceModule(runtime=Mock(position=2), descriptor=Mock(), scope_ids=Mock())
self.assertEquals(seq_module.position, 2) # matches position set in the runtime
@@ -112,22 +129,6 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
html = self._get_rendered_student_view(self.sequence_3_1, requested_child='last')
self._assert_view_at_position(html, expected_position=3)
- def _get_rendered_student_view(self, sequence, requested_child=None, extra_context=None):
- """
- Returns the rendered student view for the given sequence and the
- requested_child parameter.
- """
- context = {'requested_child': requested_child}
- if extra_context:
- context.update(extra_context)
- return sequence.xmodule_runtime.render(sequence, STUDENT_VIEW, context).content
-
- def _assert_view_at_position(self, rendered_html, expected_position):
- """
- Verifies that the rendered view contains the expected position.
- """
- self.assertIn("'position': {}".format(expected_position), rendered_html)
-
def test_tooltip(self):
html = self._get_rendered_student_view(self.sequence_3_1, requested_child=None)
for child in self.sequence_3_1.children:
@@ -138,26 +139,18 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
self.assertIn("seq_module.html", html)
self.assertIn("'banner_text': None", html)
- @freeze_time(DAY_AFTER_TOMORROW)
- @ddt.data(
- (None, 'subsection'),
- ('Homework', 'homework'),
- )
- @ddt.unpack
- def test_hidden_content_past_due(self, format_type, expected_text):
+ @freeze_time(COURSE_END_DATE)
+ def test_hidden_content_past_due(self):
progress_url = 'http://test_progress_link'
- self._set_sequence_format(self.sequence_4_1, format_type)
html = self._get_rendered_student_view(
self.sequence_4_1,
extra_context=dict(progress_url=progress_url),
)
self.assertIn("hidden_content.html", html)
self.assertIn(progress_url, html)
- self.assertIn("'subsection_format': '{}'".format(expected_text), html)
- @freeze_time(DAY_AFTER_TOMORROW)
+ @freeze_time(COURSE_END_DATE)
def test_masquerade_hidden_content_past_due(self):
- self._set_sequence_format(self.sequence_4_1, "Homework")
html = self._get_rendered_student_view(
self.sequence_4_1,
extra_context=dict(specific_masquerade=True),
@@ -165,13 +158,23 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
self.assertIn("seq_module.html", html)
self.assertIn(
"'banner_text': 'Because the due date has passed, "
- "this homework is hidden from the learner.'",
+ "this assignment is hidden from the learner.'",
html
)
- def _set_sequence_format(self, sequence, format_type):
- """
- Sets the format field on the given sequence to the
- given value.
- """
- sequence._xmodule.format = format_type # pylint: disable=protected-access
+ @freeze_time(PAST_DUE_BEFORE_END_DATE)
+ def test_hidden_content_self_paced_past_due_before_end(self):
+ html = self._get_rendered_student_view(self.sequence_4_1, self_paced=True)
+ self.assertIn("seq_module.html", html)
+ self.assertIn("'banner_text': None", html)
+
+ @freeze_time(COURSE_END_DATE + timedelta(days=7))
+ def test_hidden_content_self_paced_past_end(self):
+ progress_url = 'http://test_progress_link'
+ html = self._get_rendered_student_view(
+ self.sequence_4_1,
+ extra_context=dict(progress_url=progress_url),
+ self_paced=True,
+ )
+ self.assertIn("hidden_content.html", html)
+ self.assertIn(progress_url, html)
diff --git a/common/test/acceptance/pages/lms/courseware.py b/common/test/acceptance/pages/lms/courseware.py
index d3826372ca..ef5381a49b 100644
--- a/common/test/acceptance/pages/lms/courseware.py
+++ b/common/test/acceptance/pages/lms/courseware.py
@@ -209,13 +209,13 @@ class CoursewarePage(CoursePage):
"""
return self.q(css="div.proctored-exam.completed").visible
- def content_hidden_past_due_date(self, content_type="subsection"):
+ def content_hidden_past_due_date(self):
"""
Returns whether the "the due date for this ___ has passed" message is present.
___ is the type of the hidden content, and defaults to subsection.
This being true implies "the ___ contents are hidden because their due date has passed".
"""
- message = "The due date for this {0} has passed.".format(content_type)
+ message = "this assignment is no longer available"
if self.q(css="div.seq_content").is_present():
return False
for html in self.q(css="div.hidden-content").html:
diff --git a/lms/djangoapps/course_blocks/transformers/hidden_content.py b/lms/djangoapps/course_blocks/transformers/hidden_content.py
index 4e1597513e..3f2e2275c2 100644
--- a/lms/djangoapps/course_blocks/transformers/hidden_content.py
+++ b/lms/djangoapps/course_blocks/transformers/hidden_content.py
@@ -25,7 +25,7 @@ class HiddenContentTransformer(FilteringTransformerMixin, BlockStructureTransfor
Staff users are exempted from hidden content rules.
"""
- VERSION = 1
+ VERSION = 2
MERGED_DUE_DATE = 'merged_due_date'
MERGED_HIDE_AFTER_DUE = 'merged_hide_after_due'
@@ -41,7 +41,7 @@ class HiddenContentTransformer(FilteringTransformerMixin, BlockStructureTransfor
def _get_merged_hide_after_due(cls, block_structure, block_key):
"""
Returns whether the block with the given block_key in the
- given block_structure should be visible to staff only per
+ given block_structure should be hidden after due date per
computed value from ancestry chain.
"""
return block_structure.get_transformer_block_field(
@@ -81,6 +81,8 @@ class HiddenContentTransformer(FilteringTransformerMixin, BlockStructureTransfor
func_merge_ancestors=min,
)
+ block_structure.request_xblock_fields(u'self_paced', u'end')
+
def transform_block_filters(self, usage_info, block_structure):
# Users with staff access bypass the Visibility check.
if usage_info.has_staff_access:
@@ -97,6 +99,10 @@ class HiddenContentTransformer(FilteringTransformerMixin, BlockStructureTransfor
Returns whether the block with the given block_key should
be hidden, given the current time.
"""
- due = self._get_merged_due_date(block_structure, block_key)
hide_after_due = self._get_merged_hide_after_due(block_structure, block_key)
- return not SequenceModule.verify_current_content_visibility(due, hide_after_due)
+ self_paced = block_structure[block_structure.root_block_usage_key].self_paced
+ if self_paced:
+ hidden_date = block_structure[block_structure.root_block_usage_key].end
+ else:
+ hidden_date = self._get_merged_due_date(block_structure, block_key)
+ return not SequenceModule.verify_current_content_visibility(hidden_date, hide_after_due)
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index e6c3b228cc..7ee98fbcb5 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -199,7 +199,7 @@ class IndexQueryTestCase(ModuleStoreTestCase):
NUM_PROBLEMS = 20
@ddt.data(
- (ModuleStoreEnum.Type.mongo, 8),
+ (ModuleStoreEnum.Type.mongo, 9),
(ModuleStoreEnum.Type.split, 4),
)
@ddt.unpack
diff --git a/lms/djangoapps/grades/signals/handlers.py b/lms/djangoapps/grades/signals/handlers.py
index fa05873c45..74bd721acc 100644
--- a/lms/djangoapps/grades/signals/handlers.py
+++ b/lms/djangoapps/grades/signals/handlers.py
@@ -4,15 +4,14 @@ Grades related signals.
from logging import getLogger
-from courseware.model_data import get_score, set_score
from django.dispatch import receiver
-from openedx.core.lib.grade_utils import is_score_higher
from submissions.models import score_set, score_reset
-from util.date_utils import to_timestamp
from courseware.model_data import get_score, set_score
from eventtracking import tracker
+from openedx.core.lib.grade_utils import is_score_higher
from student.models import user_by_anonymous_id
+from util.date_utils import to_timestamp
from track.event_transaction_utils import (
get_event_transaction_type,
get_event_transaction_id,
diff --git a/lms/djangoapps/grades/tasks.py b/lms/djangoapps/grades/tasks.py
index c58647adc4..6a5a235839 100644
--- a/lms/djangoapps/grades/tasks.py
+++ b/lms/djangoapps/grades/tasks.py
@@ -11,6 +11,7 @@ from logging import getLogger
from courseware.model_data import get_score
from lms.djangoapps.course_blocks.api import get_course_blocks
+from openedx.core.djangoapps.celery_utils.task import PersistOnFailureTask
from opaque_keys.edx.keys import UsageKey
from opaque_keys.edx.locator import CourseLocator
from submissions import api as sub_api
@@ -54,7 +55,7 @@ def recalculate_subsection_grade(
)
-@task(default_retry_delay=30, routing_key=settings.RECALCULATE_GRADES_ROUTING_KEY)
+@task(base=PersistOnFailureTask, default_retry_delay=30, routing_key=settings.RECALCULATE_GRADES_ROUTING_KEY)
def recalculate_subsection_grade_v2(**kwargs):
"""
Updates a saved subsection grade.
diff --git a/lms/envs/common.py b/lms/envs/common.py
index c826fd1b9f..cc2145e9e9 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -2153,6 +2153,10 @@ INSTALLED_APPS = (
# additional release utilities to ease automation
'release_util',
+
+ # Customized celery tasks, including persisting failed tasks so they can
+ # be retried
+ 'openedx.core.djangoapps.celery_utils',
)
# Migrations which are not in the standard module "migrations"
diff --git a/lms/templates/hidden_content.html b/lms/templates/hidden_content.html
index 7e6f4b5c04..33d7b79a7e 100644
--- a/lms/templates/hidden_content.html
+++ b/lms/templates/hidden_content.html
@@ -5,22 +5,35 @@ from openedx.core.djangolib.markup import HTML, Text
%>
-
- ${_("The due date for this {subsection_format} has passed.").format(
- subsection_format=subsection_format,
- )}
-
-
-
- ${Text(_(
- "Because the due date has passed, this {subsection_format} "
- "is no longer available.{line_break}If you have completed this {subsection_format}, "
- "your grade is available on the {link_start}progress page{link_end}."
- )).format(
- subsection_format=subsection_format,
- line_break=HTML("
"),
- link_start=HTML("").format(progress_url),
- link_end=HTML(""),
- )}
-
+
+ % if self_paced:
+ ${_("The course has ended.")}
+ % else:
+ ${_("The due date for this assignment has passed.")}
+ % endif
+
+
+
+ % if self_paced:
+ ${Text(_(
+ "Because the course has ended, this assignment is no longer "
+ "available.{line_break}If you have completed this assignment, your "
+ "grade is available on the {link_start}progress page{link_end}."
+ )).format(
+ line_break=HTML("
"),
+ link_start=HTML("").format(progress_url),
+ link_end=HTML(""),
+ )}
+ % else:
+ ${Text(_(
+ "Because the due date has passed, this assignment is no longer "
+ "available.{line_break}If you have completed this assignment, your "
+ "grade is available on the {link_start}progress page{link_end}."
+ )).format(
+ line_break=HTML("
"),
+ link_start=HTML("").format(progress_url),
+ link_end=HTML(""),
+ )}
+ % endif
+
diff --git a/openedx/core/djangoapps/api_admin/tests/test_forms.py b/openedx/core/djangoapps/api_admin/tests/test_forms.py
index e10aa6c771..6e676dd9a2 100644
--- a/openedx/core/djangoapps/api_admin/tests/test_forms.py
+++ b/openedx/core/djangoapps/api_admin/tests/test_forms.py
@@ -1,15 +1,14 @@
#pylint: disable=missing-docstring
-import unittest
import ddt
-from django.conf import settings
from django.test import TestCase
from openedx.core.djangoapps.api_admin.forms import ApiAccessRequestForm
from openedx.core.djangoapps.api_admin.tests.utils import VALID_DATA
+from openedx.core.djangolib.testing.utils import skip_unless_lms
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
@ddt.ddt
class ApiAccessFormTest(TestCase):
diff --git a/openedx/core/djangoapps/api_admin/tests/test_models.py b/openedx/core/djangoapps/api_admin/tests/test_models.py
index 9337d26c0e..8db4abf204 100644
--- a/openedx/core/djangoapps/api_admin/tests/test_models.py
+++ b/openedx/core/djangoapps/api_admin/tests/test_models.py
@@ -2,21 +2,20 @@
from smtplib import SMTPException
import ddt
-from django.conf import settings
from django.db import IntegrityError
from django.test import TestCase
import mock
-import unittest
from microsite_configuration.tests.factories import SiteFactory
from openedx.core.djangoapps.api_admin.models import ApiAccessRequest, ApiAccessConfig
from openedx.core.djangoapps.api_admin.models import log as model_log
from openedx.core.djangoapps.api_admin.tests.factories import ApiAccessRequestFactory
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from student.tests.factories import UserFactory
@ddt.ddt
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class ApiAccessRequestTests(TestCase):
def setUp(self):
@@ -79,7 +78,7 @@ class ApiAccessConfigTests(TestCase):
)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class ApiAccessRequestSignalTests(TestCase):
def setUp(self):
super(ApiAccessRequestSignalTests, self).setUp()
diff --git a/openedx/core/djangoapps/api_admin/tests/test_views.py b/openedx/core/djangoapps/api_admin/tests/test_views.py
index c9e25c50d4..5d2c3757ca 100644
--- a/openedx/core/djangoapps/api_admin/tests/test_views.py
+++ b/openedx/core/djangoapps/api_admin/tests/test_views.py
@@ -1,7 +1,6 @@
""" Tests for the api_admin app's views. """
import json
-import unittest
import ddt
import httpretty
@@ -16,19 +15,26 @@ from openedx.core.djangoapps.api_admin.tests.factories import (
ApiAccessRequestFactory, ApplicationFactory, CatalogFactory
)
from openedx.core.djangoapps.api_admin.tests.utils import VALID_DATA
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from student.tests.factories import UserFactory
Application = get_application_model() # pylint: disable=invalid-name
class ApiAdminTest(TestCase):
+ """
+ Base class to allow API admin access to tests.
+ """
def setUp(self):
super(ApiAdminTest, self).setUp()
ApiAccessConfig(enabled=True).save()
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class ApiRequestViewTest(ApiAdminTest):
+ """
+ Test the API Request View.
+ """
def setUp(self):
super(ApiRequestViewTest, self).setUp()
self.url = reverse('api_admin:api-request')
@@ -93,10 +99,13 @@ class ApiRequestViewTest(ApiAdminTest):
self.assertEqual(response.status_code, 404)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
@override_settings(PLATFORM_NAME='edX')
@ddt.ddt
class ApiRequestStatusViewTest(ApiAdminTest):
+ """
+ Tests of the API Status endpoint.
+ """
def setUp(self):
super(ApiRequestStatusViewTest, self).setUp()
password = 'abc123'
@@ -198,10 +207,15 @@ class ApiRequestStatusViewTest(ApiAdminTest):
self.assertIn('Enter a valid URL.', response.content)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class ApiTosViewTest(ApiAdminTest):
+ """
+ Tests of the API terms of service endpoint.
+ """
def test_get_api_tos(self):
- """Verify that the terms of service can be read."""
+ """
+ Verify that the terms of service can be read.
+ """
url = reverse('api_admin:api-tos')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
@@ -209,6 +223,9 @@ class ApiTosViewTest(ApiAdminTest):
class CatalogTest(ApiAdminTest):
+ """
+ Test the catalog API.
+ """
def setUp(self):
super(CatalogTest, self).setUp()
password = 'abc123'
@@ -232,8 +249,11 @@ class CatalogTest(ApiAdminTest):
)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class CatalogSearchViewTest(CatalogTest):
+ """
+ Test the catalog search endpoint.
+ """
def setUp(self):
super(CatalogSearchViewTest, self).setUp()
self.url = reverse('api_admin:catalog-search')
@@ -254,8 +274,11 @@ class CatalogSearchViewTest(CatalogTest):
self.assertRedirects(response, reverse('api_admin:catalog-search'))
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class CatalogListViewTest(CatalogTest):
+ """
+ Test the catalog list endpoint.
+ """
def setUp(self):
super(CatalogListViewTest, self).setUp()
self.catalog_user = UserFactory()
@@ -304,8 +327,11 @@ class CatalogListViewTest(CatalogTest):
self.assertEqual(len([r for r in httpretty.httpretty.latest_requests if r.method == 'POST']), 0)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class CatalogEditViewTest(CatalogTest):
+ """
+ Test edits to the catalog endpoint.
+ """
def setUp(self):
super(CatalogEditViewTest, self).setUp()
self.catalog_user = UserFactory()
@@ -353,8 +379,11 @@ class CatalogEditViewTest(CatalogTest):
self.assertEqual(len([r for r in httpretty.httpretty.latest_requests if r.method == 'PATCH']), 0)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class CatalogPreviewViewTest(CatalogTest):
+ """
+ Test the catalog preview endpoint.
+ """
def setUp(self):
super(CatalogPreviewViewTest, self).setUp()
self.url = reverse('api_admin:catalog-preview')
diff --git a/openedx/core/djangoapps/bookmarks/tests/test_api.py b/openedx/core/djangoapps/bookmarks/tests/test_api.py
index 975b3bae09..bb8705306b 100644
--- a/openedx/core/djangoapps/bookmarks/tests/test_api.py
+++ b/openedx/core/djangoapps/bookmarks/tests/test_api.py
@@ -4,13 +4,13 @@ Tests for bookmarks api.
import ddt
from mock import patch
from nose.plugins.attrib import attr
-from unittest import skipUnless
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from opaque_keys.edx.keys import UsageKey
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from xmodule.modulestore.exceptions import ItemNotFoundError
from .. import api
@@ -38,7 +38,7 @@ class BookmarkApiEventTestMixin(object):
@attr(shard=2)
@ddt.ddt
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
+@skip_unless_lms
class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
"""
These tests cover the parts of the API methods.
diff --git a/openedx/core/djangoapps/bookmarks/tests/test_models.py b/openedx/core/djangoapps/bookmarks/tests/test_models.py
index 6afb63e79f..cdc9ab3d26 100644
--- a/openedx/core/djangoapps/bookmarks/tests/test_models.py
+++ b/openedx/core/djangoapps/bookmarks/tests/test_models.py
@@ -8,9 +8,6 @@ from freezegun import freeze_time
import mock
from nose.plugins.attrib import attr
import pytz
-from unittest import skipUnless
-
-from django.conf import settings
from opaque_keys.edx.keys import UsageKey
from opaque_keys.edx.locator import CourseLocator, BlockUsageLocator
@@ -19,6 +16,7 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import check_mongo_calls, CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from student.tests.factories import AdminFactory, UserFactory
from .. import DEFAULT_FIELDS, OPTIONAL_FIELDS, PathItem
@@ -228,7 +226,7 @@ class BookmarksTestsBase(ModuleStoreTestCase):
@attr(shard=2)
@ddt.ddt
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
+@skip_unless_lms
class BookmarkModelTests(BookmarksTestsBase):
"""
Test the Bookmark model.
diff --git a/openedx/core/djangoapps/bookmarks/tests/test_services.py b/openedx/core/djangoapps/bookmarks/tests/test_services.py
index 5dbaea99b2..341803cbdf 100644
--- a/openedx/core/djangoapps/bookmarks/tests/test_services.py
+++ b/openedx/core/djangoapps/bookmarks/tests/test_services.py
@@ -2,18 +2,16 @@
Tests for bookmark services.
"""
from nose.plugins.attrib import attr
-from unittest import skipUnless
-
-from django.conf import settings
from opaque_keys.edx.keys import UsageKey
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from ..services import BookmarksService
from .test_models import BookmarksTestsBase
@attr(shard=2)
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
+@skip_unless_lms
class BookmarksServiceTests(BookmarksTestsBase):
"""
Tests the Bookmarks service.
diff --git a/openedx/core/djangoapps/bookmarks/tests/test_views.py b/openedx/core/djangoapps/bookmarks/tests/test_views.py
index c3f0d7dc3d..e8344f70ec 100644
--- a/openedx/core/djangoapps/bookmarks/tests/test_views.py
+++ b/openedx/core/djangoapps/bookmarks/tests/test_views.py
@@ -5,7 +5,6 @@ Tests for bookmark views.
import ddt
import json
from nose.plugins.attrib import attr
-from unittest import skipUnless
import urllib
from django.conf import settings
@@ -13,6 +12,7 @@ from django.core.urlresolvers import reverse
from mock import patch
from rest_framework.test import APIClient
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from xmodule.modulestore import ModuleStoreEnum
from .test_models import BookmarksTestsBase
@@ -66,7 +66,7 @@ class BookmarksViewsTestsBase(BookmarksTestsBase, BookmarkApiEventTestMixin):
@attr(shard=2)
@ddt.ddt
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
+@skip_unless_lms
class BookmarksListViewTests(BookmarksViewsTestsBase):
"""
This contains the tests for GET & POST methods of bookmark.views.BookmarksListView class
@@ -371,7 +371,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
@attr(shard=2)
@ddt.ddt
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
+@skip_unless_lms
class BookmarksDetailViewTests(BookmarksViewsTestsBase):
"""
This contains the tests for GET & DELETE methods of bookmark.views.BookmarksDetailView class
diff --git a/openedx/core/djangoapps/cache_toolbox/tests/test_middleware.py b/openedx/core/djangoapps/cache_toolbox/tests/test_middleware.py
index 2e6744e0ad..adef0877f9 100644
--- a/openedx/core/djangoapps/cache_toolbox/tests/test_middleware.py
+++ b/openedx/core/djangoapps/cache_toolbox/tests/test_middleware.py
@@ -1,12 +1,10 @@
"""Tests for cached authentication middleware."""
-import unittest
-
-from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.test import TestCase
from mock import patch
+from openedx.core.djangolib.testing.utils import skip_unless_cms, skip_unless_lms
from student.tests.factories import UserFactory
@@ -33,13 +31,13 @@ class CachedAuthMiddlewareTestCase(TestCase):
response = self.client.get(test_url)
self.assertRedirects(response, redirect_url)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
def test_session_change_lms(self):
"""Test session verification with LMS-specific URLs."""
dashboard_url = reverse('dashboard')
self._test_change_session_hash(dashboard_url, reverse('signin_user') + '?next=' + dashboard_url)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms')
+ @skip_unless_cms
def test_session_change_cms(self):
"""Test session verification with CMS-specific URLs."""
home_url = reverse('home')
diff --git a/openedx/core/djangoapps/celery_utils/__init__.py b/openedx/core/djangoapps/celery_utils/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/openedx/core/djangoapps/celery_utils/migrations/0001_initial.py b/openedx/core/djangoapps/celery_utils/migrations/0001_initial.py
new file mode 100644
index 0000000000..b85dcc1801
--- /dev/null
+++ b/openedx/core/djangoapps/celery_utils/migrations/0001_initial.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.utils.timezone
+import jsonfield.fields
+import model_utils.fields
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='FailedTask',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
+ ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
+ ('task_name', models.CharField(max_length=255)),
+ ('task_id', models.CharField(max_length=255, db_index=True)),
+ ('args', jsonfield.fields.JSONField(blank=True)),
+ ('kwargs', jsonfield.fields.JSONField(blank=True)),
+ ('exc', models.CharField(max_length=255)),
+ ('datetime_resolved', models.DateTimeField(default=None, null=True, db_index=True, blank=True)),
+ ],
+ ),
+ migrations.AlterIndexTogether(
+ name='failedtask',
+ index_together=set([('task_name', 'exc')]),
+ ),
+ ]
diff --git a/openedx/core/djangoapps/celery_utils/migrations/__init__.py b/openedx/core/djangoapps/celery_utils/migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/openedx/core/djangoapps/celery_utils/models.py b/openedx/core/djangoapps/celery_utils/models.py
new file mode 100644
index 0000000000..b96dd0aabc
--- /dev/null
+++ b/openedx/core/djangoapps/celery_utils/models.py
@@ -0,0 +1,32 @@
+"""
+Models to support persistent tasks.
+"""
+
+from django.db import models
+from jsonfield import JSONField
+from model_utils.models import TimeStampedModel
+
+
+class FailedTask(TimeStampedModel):
+ """
+ Representation of tasks that have failed
+ """
+ task_name = models.CharField(max_length=255)
+ task_id = models.CharField(max_length=255, db_index=True)
+ args = JSONField(blank=True)
+ kwargs = JSONField(blank=True)
+ exc = models.CharField(max_length=255)
+ datetime_resolved = models.DateTimeField(blank=True, null=True, default=None, db_index=True)
+
+ class Meta(object):
+ index_together = [
+ (u'task_name', u'exc'),
+ ]
+
+ def __unicode__(self):
+ return u'FailedTask: {task_name}, args={args}, kwargs={kwargs} ({resolution})'.format(
+ task_name=self.task_name,
+ args=self.args,
+ kwargs=self.kwargs,
+ resolution=u"not resolved" if self.datetime_resolved is None else "resolved"
+ )
diff --git a/openedx/core/djangoapps/celery_utils/task.py b/openedx/core/djangoapps/celery_utils/task.py
new file mode 100644
index 0000000000..2170368db6
--- /dev/null
+++ b/openedx/core/djangoapps/celery_utils/task.py
@@ -0,0 +1,44 @@
+"""
+Celery utility code for persistent tasks.
+"""
+
+from celery import Task
+
+from .models import FailedTask
+
+
+# pylint: disable=abstract-method
+class PersistOnFailureTask(Task):
+ """
+ Custom Celery Task base class that persists task data on failure.
+ """
+
+ def on_failure(self, exc, task_id, args, kwargs, einfo):
+ """
+ If the task fails, persist a record of the task.
+ """
+ FailedTask.objects.create(
+ task_name=_truncate_to_field(FailedTask, 'task_name', self.name),
+ task_id=task_id, # Fixed length UUID: No need to truncate
+ args=args,
+ kwargs=kwargs,
+ exc=_truncate_to_field(FailedTask, 'exc', repr(exc)),
+ )
+ super(PersistOnFailureTask, self).on_failure(exc, task_id, args, kwargs, einfo)
+
+
+def _truncate_to_field(model, field_name, value):
+ """
+ If data is too big for the field, it would cause a failure to
+ insert, so we shorten it, truncating in the middle (because
+ valuable information often shows up at the end.
+ """
+ field = model._meta.get_field(field_name) # pylint: disable=protected-access
+ if len(value) > field.max_length:
+ midpoint = field.max_length // 2
+ len_after_midpoint = field.max_length - midpoint
+ first = value[:midpoint]
+ sep = u'...'
+ last = value[len(value) - len_after_midpoint + len(sep):]
+ value = sep.join([first, last])
+ return value
diff --git a/openedx/core/djangoapps/celery_utils/tests/__init__.py b/openedx/core/djangoapps/celery_utils/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/openedx/core/djangoapps/celery_utils/tests/test_task.py b/openedx/core/djangoapps/celery_utils/tests/test_task.py
new file mode 100644
index 0000000000..8fd8a511a5
--- /dev/null
+++ b/openedx/core/djangoapps/celery_utils/tests/test_task.py
@@ -0,0 +1,79 @@
+u"""
+Testing persistent tasks
+"""
+
+from __future__ import print_function
+
+from celery import task
+from django.test import TestCase
+import six
+
+from openedx.core.djangolib.testing.utils import skip_unless_lms
+from ..models import FailedTask
+from ..task import PersistOnFailureTask
+
+
+@skip_unless_lms
+class PersistOnFailureTaskTestCase(TestCase):
+ """
+ Test that persistent tasks save the appropriate values when needed.
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ @task(base=PersistOnFailureTask)
+ def exampletask(message=None):
+ u"""
+ A simple task for testing persistence
+ """
+ if message:
+ raise ValueError(message)
+ return
+ cls.exampletask = exampletask
+ super(PersistOnFailureTaskTestCase, cls).setUpClass()
+
+ def test_exampletask_without_failure(self):
+ result = self.exampletask.delay()
+ result.wait()
+ self.assertEqual(result.status, u'SUCCESS')
+ self.assertFalse(FailedTask.objects.exists())
+
+ def test_exampletask_with_failure(self):
+ result = self.exampletask.delay(message=u'The example task failed')
+ with self.assertRaises(ValueError):
+ result.wait()
+ self.assertEqual(result.status, u'FAILURE')
+ failed_task_object = FailedTask.objects.get()
+ # Assert that we get the kind of data we expect
+ self.assertEqual(
+ failed_task_object.task_name,
+ u'openedx.core.djangoapps.celery_utils.tests.test_task.exampletask'
+ )
+ self.assertEqual(failed_task_object.args, [])
+ self.assertEqual(failed_task_object.kwargs, {u'message': u'The example task failed'})
+ self.assertEqual(failed_task_object.exc, u"ValueError(u'The example task failed',)")
+ self.assertIsNone(failed_task_object.datetime_resolved)
+
+ def test_persists_when_called_with_wrong_args(self):
+ result = self.exampletask.delay(15, u'2001-03-04', err=True)
+ with self.assertRaises(TypeError):
+ result.wait()
+ self.assertEqual(result.status, u'FAILURE')
+ failed_task_object = FailedTask.objects.get()
+ self.assertEqual(failed_task_object.args, [15, u'2001-03-04'])
+ self.assertEqual(failed_task_object.kwargs, {u'err': True})
+
+ def test_persists_with_overlength_field(self):
+ overlong_message = u''.join(u'%03d' % x for x in six.moves.range(100))
+ result = self.exampletask.delay(message=overlong_message)
+ with self.assertRaises(ValueError):
+ result.wait()
+ failed_task_object = FailedTask.objects.get()
+ # Length is max field length
+ self.assertEqual(len(failed_task_object.exc), 255)
+ # Ellipses are put in the middle
+ self.assertEqual(u'037...590', failed_task_object.exc[124:133])
+ # The beginning of the input is captured
+ self.assertEqual(failed_task_object.exc[:11], u"ValueError(")
+ # The end of the input is captured
+ self.assertEqual(failed_task_object.exc[-9:], u"098099',)")
diff --git a/openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py b/openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py
index bc02328a67..bdab552cdf 100644
--- a/openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py
+++ b/openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py
@@ -2,13 +2,9 @@
Test the partitions and partitions service
"""
-
-import json
-from django.conf import settings
import django.test
from mock import patch
from nose.plugins.attrib import attr
-from unittest import skipUnless
from courseware.masquerade import handle_ajax, setup_masquerade
from courseware.tests.test_masquerade import StaffMasqueradeTestCase
@@ -19,6 +15,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, TEST_DAT
from xmodule.modulestore.tests.factories import ToyCourseFactory
from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartitionScheme
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from ..partition_scheme import CohortPartitionScheme, get_cohorted_user_partition
from ..models import CourseUserGroupPartitionGroup
from ..views import link_cohort_to_partition_group, unlink_cohort_partition_group
@@ -383,7 +380,7 @@ class TestMasqueradedGroup(StaffMasqueradeTestCase):
self._verify_masquerade_for_group(self.user_partition.groups[1])
self._verify_masquerade_for_group(None)
- @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
+ @skip_unless_lms
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
def test_group_masquerade(self):
"""
@@ -391,7 +388,7 @@ class TestMasqueradedGroup(StaffMasqueradeTestCase):
"""
self._verify_masquerade_for_all_groups()
- @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
+ @skip_unless_lms
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
def test_group_masquerade_with_cohort(self):
"""
diff --git a/openedx/core/djangoapps/course_groups/tests/test_views.py b/openedx/core/djangoapps/course_groups/tests/test_views.py
index fcef92da52..ca971f1257 100644
--- a/openedx/core/djangoapps/course_groups/tests/test_views.py
+++ b/openedx/core/djangoapps/course_groups/tests/test_views.py
@@ -8,9 +8,7 @@ import json
from collections import namedtuple
from datetime import datetime
from nose.plugins.attrib import attr
-from unittest import skipUnless
-from django.conf import settings
from django.contrib.auth.models import User
from django.http import Http404
from django.test.client import RequestFactory
@@ -22,6 +20,8 @@ from xmodule.modulestore.tests.factories import CourseFactory
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.modulestore.tests.factories import ItemFactory
from lms.djangoapps.django_comment_client.constants import TYPE_ENTRY, TYPE_SUBCATEGORY
+from openedx.core.djangolib.testing.utils import skip_unless_lms
+
from ..models import CourseUserGroup, CourseCohort
from ..views import (
@@ -1207,7 +1207,7 @@ class RemoveUserFromCohortTestCase(CohortViewsTestCase):
@attr(shard=2)
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
+@skip_unless_lms
class CourseCohortDiscussionTopicsTestCase(CohortViewsTestCase):
"""
Tests the `cohort_discussion_topics` view.
diff --git a/openedx/core/djangoapps/coursetalk/tests/test_helpers.py b/openedx/core/djangoapps/coursetalk/tests/test_helpers.py
index bc742e01d5..379f4ea4f8 100644
--- a/openedx/core/djangoapps/coursetalk/tests/test_helpers.py
+++ b/openedx/core/djangoapps/coursetalk/tests/test_helpers.py
@@ -1,17 +1,15 @@
""" CourseTalk widget helpers tests """
from __future__ import unicode_literals
-from unittest import skipUnless
-
from django import test
-from django.conf import settings
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from openedx.core.djangoapps.coursetalk import helpers
from openedx.core.djangoapps.coursetalk import models
+from openedx.core.djangolib.testing.utils import skip_unless_lms
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
+@skip_unless_lms
class CourseTalkKeyTests(test.TestCase):
"""
CourseTalkKeyTests:
diff --git a/openedx/core/djangoapps/credentials/tests/test_models.py b/openedx/core/djangoapps/credentials/tests/test_models.py
index e934659f39..1e653610da 100644
--- a/openedx/core/djangoapps/credentials/tests/test_models.py
+++ b/openedx/core/djangoapps/credentials/tests/test_models.py
@@ -1,14 +1,13 @@
"""Tests for models supporting Credentials-related functionality."""
-import unittest
-
-from django.conf import settings
from django.test import TestCase
from nose.plugins.attrib import attr
+
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin
+from openedx.core.djangolib.testing.utils import skip_unless_lms
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
@attr(shard=2)
class TestCredentialsApiConfig(CredentialsApiConfigMixin, TestCase):
"""Tests covering the CredentialsApiConfig model."""
diff --git a/openedx/core/djangoapps/credentials/tests/test_utils.py b/openedx/core/djangoapps/credentials/tests/test_utils.py
index 87b1c2a26e..443eec7029 100644
--- a/openedx/core/djangoapps/credentials/tests/test_utils.py
+++ b/openedx/core/djangoapps/credentials/tests/test_utils.py
@@ -1,7 +1,4 @@
"""Tests covering Credentials utilities."""
-import unittest
-
-from django.conf import settings
from django.core.cache import cache
from nose.plugins.attrib import attr
import httpretty
@@ -18,11 +15,11 @@ from openedx.core.djangoapps.credentials.utils import (
from openedx.core.djangoapps.credentials.tests import factories
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
-from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
+from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
from student.tests.factories import UserFactory
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
@attr(shard=2)
class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin, CredentialsDataMixin,
ProgramsDataMixin, CacheIsolationTestCase):
diff --git a/openedx/core/djangoapps/credit/tests/test_api.py b/openedx/core/djangoapps/credit/tests/test_api.py
index cf3fe723fe..da7f374965 100644
--- a/openedx/core/djangoapps/credit/tests/test_api.py
+++ b/openedx/core/djangoapps/credit/tests/test_api.py
@@ -3,10 +3,8 @@ Tests for the API functions in the credit app.
"""
import datetime
import json
-import unittest
import ddt
-from django.conf import settings
from django.contrib.auth.models import User
from django.core import mail
from django.test.utils import override_settings
@@ -36,6 +34,7 @@ from openedx.core.djangoapps.credit.models import (
CreditEligibility,
CreditRequest
)
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from course_modes.models import CourseMode
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
@@ -203,7 +202,7 @@ class CreditApiTestBase(ModuleStoreTestCase):
@attr(shard=2)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
+@skip_unless_lms
@ddt.ddt
class CreditRequirementApiTests(CreditApiTestBase):
"""
@@ -1165,7 +1164,7 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase):
@attr(shard=2)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
+@skip_unless_lms
@override_settings(
ECOMMERCE_API_URL=TEST_API_URL,
ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY,
diff --git a/openedx/core/djangoapps/credit/tests/test_partition.py b/openedx/core/djangoapps/credit/tests/test_partition.py
index c1d893ef39..1fff58795e 100644
--- a/openedx/core/djangoapps/credit/tests/test_partition.py
+++ b/openedx/core/djangoapps/credit/tests/test_partition.py
@@ -5,9 +5,6 @@ Tests for In-Course Reverification Access Control Partition scheme
import ddt
from nose.plugins.attrib import attr
-import unittest
-
-from django.conf import settings
from lms.djangoapps.verify_student.models import (
VerificationCheckpoint,
@@ -15,6 +12,7 @@ from lms.djangoapps.verify_student.models import (
SkippedReverification,
)
from openedx.core.djangoapps.credit.partition_schemes import VerificationPartitionScheme
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.partitions.partitions import UserPartition, Group
@@ -24,7 +22,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
@attr(shard=2)
@ddt.ddt
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class ReverificationPartitionTest(ModuleStoreTestCase):
"""Tests for the Reverification Partition Scheme. """
diff --git a/openedx/core/djangoapps/credit/tests/test_signals.py b/openedx/core/djangoapps/credit/tests/test_signals.py
index a3abe179b9..50566faf3c 100644
--- a/openedx/core/djangoapps/credit/tests/test_signals.py
+++ b/openedx/core/djangoapps/credit/tests/test_signals.py
@@ -6,9 +6,7 @@ import ddt
import pytz
from datetime import timedelta, datetime
from mock import MagicMock
-from unittest import skipUnless
-from django.conf import settings
from django.test.client import RequestFactory
from nose.plugins.attrib import attr
from course_modes.models import CourseMode
@@ -22,10 +20,11 @@ from openedx.core.djangoapps.credit.api import (
)
from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider
from openedx.core.djangoapps.credit.signals import listen_for_grade_calculation
+from openedx.core.djangolib.testing.utils import skip_unless_lms
@attr(shard=2)
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
+@skip_unless_lms
@ddt.ddt
class TestMinGradedRequirementStatus(ModuleStoreTestCase):
"""Test cases to check the minimum grade requirement status updated.
diff --git a/openedx/core/djangoapps/credit/tests/test_views.py b/openedx/core/djangoapps/credit/tests/test_views.py
index a438b4b20e..72cb2a58c3 100644
--- a/openedx/core/djangoapps/credit/tests/test_views.py
+++ b/openedx/core/djangoapps/credit/tests/test_views.py
@@ -8,7 +8,6 @@ from __future__ import unicode_literals
import datetime
import json
-import unittest
import ddt
import pytz
@@ -28,6 +27,7 @@ from openedx.core.djangoapps.credit.signature import signature
from openedx.core.djangoapps.credit.tests.factories import (
CreditProviderFactory, CreditEligibilityFactory, CreditCourseFactory, CreditRequestFactory,
)
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from openedx.core.lib.token_utils import JwtBuilder
from student.tests.factories import UserFactory, AdminFactory
from util.date_utils import to_timestamp
@@ -113,7 +113,7 @@ class ReadOnlyMixin(object):
@attr(shard=2)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class CreditCourseViewSetTests(AuthMixin, UserMixin, TestCase):
""" Tests for the CreditCourse endpoints.
@@ -276,7 +276,7 @@ class CreditCourseViewSetTests(AuthMixin, UserMixin, TestCase):
@attr(shard=2)
@ddt.ddt
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class CreditProviderViewSetTests(ApiTestCaseMixin, ReadOnlyMixin, AuthMixin, UserMixin, TestCase):
""" Tests for CreditProviderViewSet. """
list_path = 'credit:creditprovider-list'
@@ -320,7 +320,7 @@ class CreditProviderViewSetTests(ApiTestCaseMixin, ReadOnlyMixin, AuthMixin, Use
@attr(shard=2)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class CreditProviderRequestCreateViewTests(ApiTestCaseMixin, UserMixin, TestCase):
""" Tests for CreditProviderRequestCreateView. """
@@ -470,7 +470,7 @@ class CreditProviderRequestCreateViewTests(ApiTestCaseMixin, UserMixin, TestCase
@attr(shard=2)
@ddt.ddt
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class CreditProviderCallbackViewTests(UserMixin, TestCase):
""" Tests for CreditProviderCallbackView. """
@@ -624,7 +624,7 @@ class CreditProviderCallbackViewTests(UserMixin, TestCase):
@attr(shard=2)
@ddt.ddt
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class CreditEligibilityViewTests(AuthMixin, UserMixin, ReadOnlyMixin, TestCase):
""" Tests for CreditEligibilityView. """
view_name = 'credit:eligibility_details'
diff --git a/openedx/core/djangoapps/embargo/tests/test_api.py b/openedx/core/djangoapps/embargo/tests/test_api.py
index 614e5cd67d..7f17010fc7 100644
--- a/openedx/core/djangoapps/embargo/tests/test_api.py
+++ b/openedx/core/djangoapps/embargo/tests/test_api.py
@@ -5,7 +5,6 @@ Tests for EmbargoMiddleware
from contextlib import contextmanager
import mock
from nose.plugins.attrib import attr
-import unittest
import pygeoip
import ddt
@@ -14,6 +13,7 @@ from django.test.utils import override_settings
from django.core.cache import cache
from django.db import connection
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import (
@@ -41,7 +41,7 @@ MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {})
@attr(shard=3)
@ddt.ddt
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
@mock.patch.dict(settings.FEATURES, {'EMBARGO': True})
class EmbargoCheckAccessApiTests(ModuleStoreTestCase):
"""Test the embargo API calls to determine whether a user has access. """
@@ -239,7 +239,7 @@ class EmbargoCheckAccessApiTests(ModuleStoreTestCase):
@ddt.ddt
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class EmbargoMessageUrlApiTests(UrlResetMixin, ModuleStoreTestCase):
"""Test the embargo API calls for retrieving the blocking message URLs. """
diff --git a/openedx/core/djangoapps/embargo/tests/test_middleware.py b/openedx/core/djangoapps/embargo/tests/test_middleware.py
index cd64ab691c..bf1d7a35ed 100644
--- a/openedx/core/djangoapps/embargo/tests/test_middleware.py
+++ b/openedx/core/djangoapps/embargo/tests/test_middleware.py
@@ -2,7 +2,6 @@
Tests for EmbargoMiddleware with CountryAccessRules
"""
-import unittest
from mock import patch
from nose.plugins.attrib import attr
import ddt
@@ -11,11 +10,12 @@ from django.core.urlresolvers import reverse
from django.conf import settings
from django.core.cache import cache as django_cache
+from config_models.models import cache as config_cache
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from util.testing import UrlResetMixin
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from config_models.models import cache as config_cache
from ..models import RestrictedCourse, IPFilter
from ..test_utils import restrict_course
@@ -23,7 +23,7 @@ from ..test_utils import restrict_course
@attr(shard=3)
@ddt.ddt
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class EmbargoMiddlewareAccessTests(UrlResetMixin, ModuleStoreTestCase):
"""Tests of embargo middleware country access rules.
diff --git a/openedx/core/djangoapps/embargo/tests/test_views.py b/openedx/core/djangoapps/embargo/tests/test_views.py
index 48afff368c..f9b00c566e 100644
--- a/openedx/core/djangoapps/embargo/tests/test_views.py
+++ b/openedx/core/djangoapps/embargo/tests/test_views.py
@@ -1,6 +1,5 @@
"""Tests for embargo app views. """
-import unittest
from mock import patch
from django.core.urlresolvers import reverse
from django.conf import settings
@@ -8,11 +7,11 @@ import ddt
from util.testing import UrlResetMixin
from .. import messages
-from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
+from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
@ddt.ddt
class CourseAccessMessageViewTest(CacheIsolationTestCase, UrlResetMixin):
"""Tests for the courseware access message view.
diff --git a/openedx/core/djangoapps/external_auth/tests/test_ssl.py b/openedx/core/djangoapps/external_auth/tests/test_ssl.py
index 910a20e822..71e1a11802 100644
--- a/openedx/core/djangoapps/external_auth/tests/test_ssl.py
+++ b/openedx/core/djangoapps/external_auth/tests/test_ssl.py
@@ -3,10 +3,10 @@ Provides unit tests for SSL based authentication portions
of the external_auth app.
"""
# pylint: disable=no-member
-import copy
-import unittest
-
from contextlib import contextmanager
+import copy
+from mock import Mock, patch
+
from django.conf import settings
from django.contrib.auth import SESSION_KEY
from django.contrib.auth.models import AnonymousUser, User
@@ -15,10 +15,10 @@ from django.core.urlresolvers import reverse
from django.test.client import Client
from django.test.client import RequestFactory
from django.test.utils import override_settings
-from mock import Mock, patch
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
import openedx.core.djangoapps.external_auth.views as external_auth_views
+from openedx.core.djangolib.testing.utils import skip_unless_cms, skip_unless_lms
from student.models import CourseEnrollment
from student.roles import CourseStaffRole
from student.tests.factories import UserFactory
@@ -81,7 +81,7 @@ class SSLClientTest(ModuleStoreTestCase):
self.factory = RequestFactory()
self.mock = Mock()
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
def test_ssl_login_with_signup_lms(self):
"""
Validate that an SSL login creates an eamap user and
@@ -101,7 +101,7 @@ class SSLClientTest(ModuleStoreTestCase):
with self.assertRaises(User.DoesNotExist):
User.objects.get(email=self.USER_EMAIL)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms')
+ @skip_unless_cms
def test_ssl_login_with_signup_cms(self):
"""
Validate that an SSL login creates an eamap user and
@@ -120,7 +120,7 @@ class SSLClientTest(ModuleStoreTestCase):
with self.assertRaises(User.DoesNotExist):
User.objects.get(email=self.USER_EMAIL)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH_IMMEDIATE_SIGNUP)
def test_ssl_login_without_signup_lms(self):
"""
@@ -140,7 +140,7 @@ class SSLClientTest(ModuleStoreTestCase):
except ExternalAuthMap.DoesNotExist, ex:
self.fail('User did not get properly added to internal users, exception was {0}'.format(str(ex)))
- @unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms')
+ @skip_unless_cms
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH_IMMEDIATE_SIGNUP)
def test_ssl_login_without_signup_cms(self):
"""
@@ -166,7 +166,7 @@ class SSLClientTest(ModuleStoreTestCase):
except ExternalAuthMap.DoesNotExist, ex:
self.fail('User did not get properly added to internal users, exception was {0}'.format(str(ex)))
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH_IMMEDIATE_SIGNUP)
def test_default_login_decorator_ssl(self):
"""
@@ -184,7 +184,7 @@ class SSLClientTest(ModuleStoreTestCase):
response.redirect_chain[-1])
self.assertIn(SESSION_KEY, self.client.session)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH_IMMEDIATE_SIGNUP)
def test_registration_page_bypass(self):
"""
@@ -198,7 +198,7 @@ class SSLClientTest(ModuleStoreTestCase):
response.redirect_chain[-1])
self.assertIn(SESSION_KEY, self.client.session)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms')
+ @skip_unless_cms
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH_IMMEDIATE_SIGNUP)
def test_cms_registration_page_bypass(self):
"""
@@ -217,7 +217,7 @@ class SSLClientTest(ModuleStoreTestCase):
response = self.client.get(reverse('signup'), follow=True)
self.assertEqual(response.status_code, 404)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH_IMMEDIATE_SIGNUP)
def test_signin_page_bypass(self):
"""
@@ -238,7 +238,7 @@ class SSLClientTest(ModuleStoreTestCase):
response.redirect_chain[-1])
self.assertIn(SESSION_KEY, self.client.session)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH_IMMEDIATE_SIGNUP)
def test_ssl_bad_eamap(self):
"""
@@ -263,7 +263,7 @@ class SSLClientTest(ModuleStoreTestCase):
SSL_CLIENT_S_DN=self.AUTH_DN.format(self.USER_NAME, self.USER_EMAIL))
self.assertIn(SESSION_KEY, self.client.session)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
@override_settings(FEATURES=FEATURES_WITHOUT_SSL_AUTH)
def test_ssl_decorator_no_certs(self):
"""Make sure no external auth happens without SSL enabled"""
@@ -279,7 +279,7 @@ class SSLClientTest(ModuleStoreTestCase):
self.assertTrue(self.mock.called)
self.assertEqual(0, len(ExternalAuthMap.objects.all()))
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
def test_ssl_login_decorator(self):
"""Create mock function to test ssl login decorator"""
@@ -305,7 +305,7 @@ class SSLClientTest(ModuleStoreTestCase):
dec_mock(request)
self.assertTrue(self.mock.called)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH_IMMEDIATE_SIGNUP)
def test_ssl_decorator_auto_signup(self):
"""
@@ -330,7 +330,7 @@ class SSLClientTest(ModuleStoreTestCase):
self.assertTrue(self.mock.called)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH_AUTO_ACTIVATE)
def test_ssl_lms_redirection(self):
"""
@@ -361,7 +361,7 @@ class SSLClientTest(ModuleStoreTestCase):
response.redirect_chain[-1])
self.assertIn(SESSION_KEY, self.client.session)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms')
+ @skip_unless_cms
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH_AUTO_ACTIVATE)
def test_ssl_cms_redirection(self):
"""
@@ -393,7 +393,7 @@ class SSLClientTest(ModuleStoreTestCase):
response.redirect_chain[-1])
self.assertIn(SESSION_KEY, self.client.session)
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
@override_settings(FEATURES=FEATURES_WITH_SSL_AUTH_AUTO_ACTIVATE)
def test_ssl_logout(self):
"""
diff --git a/openedx/core/djangoapps/profile_images/tests/test_images.py b/openedx/core/djangoapps/profile_images/tests/test_images.py
index 27b43a5b21..40e3474092 100644
--- a/openedx/core/djangoapps/profile_images/tests/test_images.py
+++ b/openedx/core/djangoapps/profile_images/tests/test_images.py
@@ -5,9 +5,7 @@ from contextlib import closing
from itertools import product
import os
from tempfile import NamedTemporaryFile
-import unittest
-from django.conf import settings
from django.core.files.uploadedfile import UploadedFile
from django.test import TestCase
from django.test.utils import override_settings
@@ -17,6 +15,7 @@ from nose.plugins.attrib import attr
import piexif
from PIL import Image
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from ..exceptions import ImageValidationError
from ..images import (
create_profile_images,
@@ -31,7 +30,7 @@ from .helpers import make_image_file, make_uploaded_file
@attr(shard=2)
@ddt.ddt
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
+@skip_unless_lms
class TestValidateUploadedImage(TestCase):
"""
Test validate_uploaded_image
@@ -127,7 +126,7 @@ class TestValidateUploadedImage(TestCase):
@attr(shard=2)
@ddt.ddt
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
+@skip_unless_lms
class TestGenerateProfileImages(TestCase):
"""
Test create_profile_images
@@ -224,7 +223,7 @@ class TestGenerateProfileImages(TestCase):
@attr(shard=2)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
+@skip_unless_lms
class TestRemoveProfileImages(TestCase):
"""
Test remove_profile_images
diff --git a/openedx/core/djangoapps/profile_images/tests/test_views.py b/openedx/core/djangoapps/profile_images/tests/test_views.py
index c54934d4bf..9f885a37de 100644
--- a/openedx/core/djangoapps/profile_images/tests/test_views.py
+++ b/openedx/core/djangoapps/profile_images/tests/test_views.py
@@ -5,9 +5,7 @@ from contextlib import closing
import datetime
from nose.plugins.attrib import attr
from pytz import UTC
-import unittest
-from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpResponse
@@ -24,6 +22,7 @@ from openedx.core.djangoapps.user_api.accounts.image_helpers import (
get_profile_image_names,
get_profile_image_storage,
)
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from ..images import create_profile_images, ImageValidationError
from ..views import LOG_MESSAGE_CREATE, LOG_MESSAGE_DELETE
@@ -153,7 +152,7 @@ class ProfileImageEndpointMixin(UserSettingsEventTestMixin):
@attr(shard=2)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
+@skip_unless_lms
@mock.patch('openedx.core.djangoapps.profile_images.views.log')
class ProfileImageViewGeneralTestCase(ProfileImageEndpointMixin, APITestCase):
"""
@@ -174,7 +173,7 @@ class ProfileImageViewGeneralTestCase(ProfileImageEndpointMixin, APITestCase):
@attr(shard=2)
@ddt.ddt
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
+@skip_unless_lms
@mock.patch('openedx.core.djangoapps.profile_images.views.log')
class ProfileImageViewPostTestCase(ProfileImageEndpointMixin, APITestCase):
"""
@@ -384,7 +383,7 @@ class ProfileImageViewPostTestCase(ProfileImageEndpointMixin, APITestCase):
@attr(shard=2)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
+@skip_unless_lms
@mock.patch('openedx.core.djangoapps.profile_images.views.log')
class ProfileImageViewDeleteTestCase(ProfileImageEndpointMixin, APITestCase):
"""
@@ -515,7 +514,7 @@ class DeprecatedProfileImageTestMixin(ProfileImageEndpointMixin):
@attr(shard=2)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
+@skip_unless_lms
@mock.patch('openedx.core.djangoapps.profile_images.views.log')
class DeprecatedProfileImageUploadTestCase(DeprecatedProfileImageTestMixin, APITestCase):
"""
@@ -528,7 +527,7 @@ class DeprecatedProfileImageUploadTestCase(DeprecatedProfileImageTestMixin, APIT
@attr(shard=2)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
+@skip_unless_lms
@mock.patch('openedx.core.djangoapps.profile_images.views.log')
class DeprecatedProfileImageRemoveTestCase(DeprecatedProfileImageTestMixin, APITestCase):
"""
diff --git a/openedx/core/djangoapps/programs/tasks/v1/tests/test_tasks.py b/openedx/core/djangoapps/programs/tasks/v1/tests/test_tasks.py
index 642b2d20dc..73e360e88b 100644
--- a/openedx/core/djangoapps/programs/tasks/v1/tests/test_tasks.py
+++ b/openedx/core/djangoapps/programs/tasks/v1/tests/test_tasks.py
@@ -2,7 +2,6 @@
Tests for programs celery tasks.
"""
import json
-import unittest
from celery.exceptions import MaxRetriesExceededError
import ddt
@@ -20,7 +19,7 @@ from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfi
from openedx.core.djangoapps.programs.tests import factories
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
from openedx.core.djangoapps.programs.tasks.v1 import tasks
-from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
+from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
from student.tests.factories import UserFactory
@@ -28,7 +27,7 @@ TASKS_MODULE = 'openedx.core.djangoapps.programs.tasks.v1.tasks'
UTILS_MODULE = 'openedx.core.djangoapps.programs.utils'
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class GetApiClientTestCase(TestCase, ProgramsApiConfigMixin):
"""
Test the get_api_client function
@@ -53,7 +52,7 @@ class GetApiClientTestCase(TestCase, ProgramsApiConfigMixin):
@httpretty.activate
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class GetCompletedProgramsTestCase(ProgramsApiConfigMixin, CacheIsolationTestCase):
"""
Test the get_completed_programs function
@@ -113,7 +112,7 @@ class GetCompletedProgramsTestCase(ProgramsApiConfigMixin, CacheIsolationTestCas
self._assert_num_requests(1)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class GetAwardedCertificateProgramsTestCase(TestCase):
"""
Test the get_awarded_certificate_programs function
@@ -154,7 +153,7 @@ class GetAwardedCertificateProgramsTestCase(TestCase):
self.assertEqual(result, [1])
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class AwardProgramCertificateTestCase(TestCase):
"""
Test the award_program_certificate function
@@ -183,7 +182,7 @@ class AwardProgramCertificateTestCase(TestCase):
self.assertEqual(json.loads(httpretty.last_request().body), expected_body)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
@ddt.ddt
@mock.patch(TASKS_MODULE + '.award_program_certificate')
@mock.patch(TASKS_MODULE + '.get_awarded_certificate_programs')
diff --git a/openedx/core/djangoapps/programs/tests/test_backpopulate_program_credentials.py b/openedx/core/djangoapps/programs/tests/test_backpopulate_program_credentials.py
index e28f9204a7..9382139210 100644
--- a/openedx/core/djangoapps/programs/tests/test_backpopulate_program_credentials.py
+++ b/openedx/core/djangoapps/programs/tests/test_backpopulate_program_credentials.py
@@ -1,9 +1,7 @@
"""Tests for the backpopulate_program_credentials management command."""
import json
-from unittest import skipUnless
import ddt
-from django.conf import settings
from django.core.management import call_command, CommandError
from django.test import TestCase
from edx_oauth2_provider.tests.factories import ClientFactory
@@ -17,6 +15,7 @@ from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFact
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.programs.tests import factories
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from student.tests.factories import UserFactory
@@ -26,7 +25,7 @@ COMMAND_MODULE = 'openedx.core.djangoapps.programs.management.commands.backpopul
@ddt.ddt
@httpretty.activate
@mock.patch(COMMAND_MODULE + '.award_program_certificates.delay')
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class BackpopulateProgramCredentialsTests(ProgramsApiConfigMixin, TestCase):
"""Tests for the backpopulate_program_credentials management command."""
course_id, alternate_course_id = 'org/course/run', 'org/alternate/run'
diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py
index 4e42b8c6ef..f72bd4f5a7 100644
--- a/openedx/core/djangoapps/programs/tests/test_utils.py
+++ b/openedx/core/djangoapps/programs/tests/test_utils.py
@@ -2,11 +2,9 @@
import copy
import datetime
import json
-from unittest import skipUnless
import uuid
import ddt
-from django.conf import settings
from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.test import TestCase
@@ -29,7 +27,7 @@ from openedx.core.djangoapps.programs import utils
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.programs.tests import factories
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin, ProgramsDataMixin
-from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
+from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from util.date_utils import strftime_localized
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
@@ -45,7 +43,7 @@ MARKETING_URL = 'https://www.example.com/marketing/path'
@ddt.ddt
@attr(shard=2)
@httpretty.activate
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, CredentialsDataMixin,
CredentialsApiConfigMixin, CacheIsolationTestCase):
"""Tests covering the retrieval of programs from the Programs service."""
@@ -190,7 +188,7 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, Credential
self.assertEqual(actual, [])
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class GetProgramsByRunTests(TestCase):
"""Tests verifying that programs are inverted correctly."""
maxDiff = None
@@ -262,7 +260,7 @@ class GetProgramsByRunTests(TestCase):
self.assertEqual(course_ids, [])
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class GetCompletedCoursesTestCase(TestCase):
"""
Test the get_completed_courses function
@@ -308,7 +306,7 @@ class GetCompletedCoursesTestCase(TestCase):
@attr(shard=2)
@httpretty.activate
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class TestProgramProgressMeter(ProgramsApiConfigMixin, TestCase):
"""Tests of the program progress utility class."""
def setUp(self):
@@ -700,7 +698,7 @@ class TestProgramProgressMeter(ProgramsApiConfigMixin, TestCase):
@ddt.ddt
@override_settings(ECOMMERCE_PUBLIC_URL_ROOT=ECOMMERCE_URL_ROOT)
-@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
@mock.patch(UTILS_MODULE + '.get_run_marketing_url', mock.Mock(return_value=MARKETING_URL))
class TestProgramDataExtender(ProgramsApiConfigMixin, ModuleStoreTestCase):
"""Tests of the program data extender utility class."""
diff --git a/openedx/core/djangoapps/site_configuration/tests/test_middleware.py b/openedx/core/djangoapps/site_configuration/tests/test_middleware.py
index 97850352f9..48277a4f7b 100644
--- a/openedx/core/djangoapps/site_configuration/tests/test_middleware.py
+++ b/openedx/core/djangoapps/site_configuration/tests/test_middleware.py
@@ -3,7 +3,6 @@
Test site_configuration middleware.
"""
import ddt
-import unittest
from mock import patch
from django.conf import settings
@@ -22,6 +21,7 @@ from microsite_configuration.tests.tests import (
MICROSITE_BACKENDS,
)
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory, SiteFactory
+from openedx.core.djangolib.testing.utils import skip_unless_lms
# NOTE: We set SESSION_SAVE_EVERY_REQUEST to True in order to make sure
@@ -29,7 +29,7 @@ from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfi
# pylint: disable=no-member, protected-access
@ddt.ddt
@override_settings(SESSION_SAVE_EVERY_REQUEST=True)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class SessionCookieDomainMicrositeOverrideTests(DatabaseMicrositeTestCase):
"""
Tests regarding the session cookie management in the middlware for Microsites
diff --git a/openedx/core/djangoapps/theming/tests/test_finders.py b/openedx/core/djangoapps/theming/tests/test_finders.py
index 83497603f1..f429f0f259 100644
--- a/openedx/core/djangoapps/theming/tests/test_finders.py
+++ b/openedx/core/djangoapps/theming/tests/test_finders.py
@@ -1,15 +1,15 @@
"""
Tests for comprehensive theme static files finders.
"""
-import unittest
from django.conf import settings
from django.test import TestCase
from openedx.core.djangoapps.theming.finders import ThemeFilesFinder
+from openedx.core.djangolib.testing.utils import skip_unless_lms
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class TestThemeFinders(TestCase):
"""
Test comprehensive theming static files finders.
diff --git a/openedx/core/djangoapps/theming/tests/test_helpers.py b/openedx/core/djangoapps/theming/tests/test_helpers.py
index b8ffd048a7..cdac467a67 100644
--- a/openedx/core/djangoapps/theming/tests/test_helpers.py
+++ b/openedx/core/djangoapps/theming/tests/test_helpers.py
@@ -1,7 +1,6 @@
"""
Test helpers for Comprehensive Theming.
"""
-import unittest
from mock import patch, Mock
from django.test import TestCase, override_settings
@@ -12,6 +11,7 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_
from openedx.core.djangoapps.theming import helpers as theming_helpers
from openedx.core.djangoapps.theming.helpers import get_template_path_with_theme, strip_site_theme_templates_path, \
get_themes, Theme, get_theme_base_dir
+from openedx.core.djangolib.testing.utils import skip_unless_cms, skip_unless_lms
class TestHelpers(TestCase):
@@ -201,7 +201,7 @@ class TestHelpers(TestCase):
self.assertEqual(theming_helpers.get_template_path("about.html"), "/microsite/about.html")
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class TestHelpersLMS(TestCase):
"""Test comprehensive theming helper functions."""
@@ -244,7 +244,7 @@ class TestHelpersLMS(TestCase):
self.assertEqual(template_path, '/red-theme/lms/templates/header.html')
-@unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms')
+@skip_unless_cms
class TestHelpersCMS(TestCase):
"""Test comprehensive theming helper functions."""
diff --git a/openedx/core/djangoapps/theming/tests/test_microsites.py b/openedx/core/djangoapps/theming/tests/test_microsites.py
index 0f3a79fba1..1cb6c007af 100644
--- a/openedx/core/djangoapps/theming/tests/test_microsites.py
+++ b/openedx/core/djangoapps/theming/tests/test_microsites.py
@@ -1,16 +1,15 @@
"""
Tests for microsites and comprehensive themes.
"""
-import unittest
-
from django.conf import settings
from django.test import TestCase
from django.contrib.sites.models import Site
from openedx.core.djangoapps.theming.models import SiteTheme
+from openedx.core.djangolib.testing.utils import skip_unless_lms
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class TestComprehensiveThemeLMS(TestCase):
"""
Test html, sass and static file overrides for comprehensive themes.
diff --git a/openedx/core/djangoapps/theming/tests/test_storage.py b/openedx/core/djangoapps/theming/tests/test_storage.py
index f0d544743f..35409fc079 100644
--- a/openedx/core/djangoapps/theming/tests/test_storage.py
+++ b/openedx/core/djangoapps/theming/tests/test_storage.py
@@ -2,7 +2,6 @@
Tests for comprehensive theme static files storage classes.
"""
import ddt
-import unittest
import re
from mock import patch
@@ -12,9 +11,10 @@ from django.conf import settings
from openedx.core.djangoapps.theming.helpers import get_theme_base_dirs, Theme, get_theme_base_dir
from openedx.core.djangoapps.theming.storage import ThemeStorage
+from openedx.core.djangolib.testing.utils import skip_unless_lms
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
@ddt.ddt
class TestStorageLMS(TestCase):
"""
diff --git a/openedx/core/djangoapps/theming/tests/test_theme_locales.py b/openedx/core/djangoapps/theming/tests/test_theme_locales.py
index f577ef23bd..3cc7bc4e53 100644
--- a/openedx/core/djangoapps/theming/tests/test_theme_locales.py
+++ b/openedx/core/djangoapps/theming/tests/test_theme_locales.py
@@ -1,18 +1,19 @@
"""
-Tests for Themeing locales
+Tests for Theming locales
"""
+import os
-import unittest
from django.conf import settings
from django.test import TestCase
-import os
+
+from openedx.core.djangolib.testing.utils import skip_unless_lms
class TestComprehensiveThemeLocale(TestCase):
"""
Test Comprehensive Theme Locales
"""
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
def test_theme_locale_path_in_settings(self):
"""
test comprehensive theming paths in settings.
diff --git a/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py b/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py
index e6bbad5f7f..a1a0c8d0b3 100644
--- a/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py
+++ b/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py
@@ -1,18 +1,17 @@
"""
- Tests for comprehensive themes.
+Tests for comprehensive themes.
"""
-import unittest
-
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.contrib import staticfiles
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
+from openedx.core.djangolib.testing.utils import skip_unless_cms, skip_unless_lms
from student.tests.factories import UserFactory
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class TestComprehensiveThemeLMS(TestCase):
"""
Test html, sass and static file overrides for comprehensive themes.
@@ -66,7 +65,7 @@ class TestComprehensiveThemeLMS(TestCase):
self.assertEqual(result, settings.TEST_THEME / 'lms/static/images/logo.png')
-@unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms')
+@skip_unless_cms
class TestComprehensiveThemeCMS(TestCase):
"""
Test html, sass and static file overrides for comprehensive themes.
@@ -92,7 +91,7 @@ class TestComprehensiveThemeCMS(TestCase):
self.assertContains(resp, "Login Page override for test-theme.")
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class TestComprehensiveThemeDisabledLMS(TestCase):
"""
Test Sass compilation order and sass overrides for comprehensive themes.
@@ -115,7 +114,7 @@ class TestComprehensiveThemeDisabledLMS(TestCase):
self.assertEqual(result, settings.REPO_ROOT / 'lms/static/images/logo.png')
-@unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms')
+@skip_unless_cms
class TestComprehensiveThemeDisabledCMS(TestCase):
"""
Test default html, sass and static file when no theme is applied.
@@ -139,7 +138,7 @@ class TestComprehensiveThemeDisabledCMS(TestCase):
self.assertNotContains(resp, "Login Page override for test-theme.")
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+@skip_unless_lms
class TestStanfordTheme(TestCase):
"""
Test html, sass and static file overrides for stanford theme.
diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py
index 348f636db2..514d322739 100644
--- a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py
+++ b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py
@@ -17,6 +17,7 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.core import mail
from django.test.client import RequestFactory
+from openedx.core.djangolib.testing.utils import skip_unless_lms
from student.models import PendingEmailChange
from student.tests.tests import UserSettingsEventTestMixin
from ...errors import (
@@ -35,7 +36,7 @@ def mock_render_to_string(template_name, context):
@attr(shard=2)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Account APIs are only supported in LMS')
+@skip_unless_lms
class TestAccountApi(UserSettingsEventTestMixin, TestCase):
"""
These tests specifically cover the parts of the API methods that are not covered by test_views.py.
@@ -239,7 +240,7 @@ class TestAccountApi(UserSettingsEventTestMixin, TestCase):
{'full': 50, 'small': 10},
clear=True
)
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Account APIs are only supported in LMS')
+@skip_unless_lms
class AccountSettingsOnCreationTest(TestCase):
# pylint: disable=missing-docstring
@@ -333,7 +334,7 @@ class AccountCreationActivationAndPasswordChangeTest(TestCase):
u'a' * (PASSWORD_MAX_LENGTH + 1)
]
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @skip_unless_lms
def test_activate_account(self):
# Create the account, which is initially inactive
activation_key = create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
@@ -393,7 +394,7 @@ class AccountCreationActivationAndPasswordChangeTest(TestCase):
def test_activate_account_invalid_key(self):
activate_account(u'invalid')
- @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
+ @skip_unless_lms
def test_request_password_change(self):
# Create and activate an account
activation_key = create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
@@ -411,7 +412,7 @@ class AccountCreationActivationAndPasswordChangeTest(TestCase):
result = re.search(r'(?P