Commit eb70f230 by Ali Mohammad

Merge pull request #8749 from edx/rc/2015-06-29

Merge back into master
parents 0bb519c2 9ada9d61
...@@ -154,7 +154,7 @@ class XBlockVisibilityTestCase(ModuleStoreTestCase): ...@@ -154,7 +154,7 @@ class XBlockVisibilityTestCase(ModuleStoreTestCase):
super(XBlockVisibilityTestCase, self).setUp() super(XBlockVisibilityTestCase, self).setUp()
self.dummy_user = ModuleStoreEnum.UserID.test self.dummy_user = ModuleStoreEnum.UserID.test
self.past = datetime(1970, 1, 1) self.past = datetime(1970, 1, 1, tzinfo=UTC)
self.future = datetime.now(UTC) + timedelta(days=1) self.future = datetime.now(UTC) + timedelta(days=1)
self.course = CourseFactory.create() self.course = CourseFactory.create()
......
...@@ -141,6 +141,8 @@ define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape", "js/vie ...@@ -141,6 +141,8 @@ define(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape", "js/vie
e.preventDefault(); e.preventDefault();
$('.courses-tab').toggleClass('active', tab === 'courses'); $('.courses-tab').toggleClass('active', tab === 'courses');
$('.libraries-tab').toggleClass('active', tab === 'libraries'); $('.libraries-tab').toggleClass('active', tab === 'libraries');
// Also toggle this course-related notice shown below the course tab, if it is present:
$('.wrapper-creationrights').toggleClass('is-hidden', tab === 'libraries');
}; };
}; };
......
...@@ -103,57 +103,57 @@ ...@@ -103,57 +103,57 @@
</form> </form>
</div> </div>
%if libraries_enabled: % endif
<div class="wrapper-create-element wrapper-create-library">
<form class="form-create create-library library-info" id="create-library-form" name="create-library-form">
<div class="wrap-error">
<div id="library_creation_error" name="library_creation_error" class="message message-status message-status error" role="alert">
<p>${_("Please correct the highlighted fields below.")}</p>
</div>
</div>
<div class="wrapper-form"> %if libraries_enabled and show_new_library_button:
<h3 class="title">${_("Create a New Library")}</h3> <div class="wrapper-create-element wrapper-create-library">
<form class="form-create create-library library-info" id="create-library-form" name="create-library-form">
<div class="wrap-error">
<div id="library_creation_error" name="library_creation_error" class="message message-status message-status error" role="alert">
<p>${_("Please correct the highlighted fields below.")}</p>
</div>
</div>
<fieldset> <div class="wrapper-form">
<legend class="sr">${_("Required Information to Create a New Library")}</legend> <h3 class="title">${_("Create a New Library")}</h3>
<ol class="list-input"> <fieldset>
<li class="field text required" id="field-library-name"> <legend class="sr">${_("Required Information to Create a New Library")}</legend>
<label for="new-library-name">${_("Library Name")}</label>
## Translators: This is an example name for a new content library, seen when filling out the form to create a new library. (A library is a collection of content or problems.)
<input class="new-library-name" id="new-library-name" type="text" name="new-library-name" required placeholder="${_('e.g. Computer Science Problems')}" aria-describedby="tip-new-library-name tip-error-new-library-name" />
<span class="tip" id="tip-new-library-name">${_("The public display name for your library.")}</span>
<span class="tip tip-error is-hiding" id="tip-error-new-library-name"></span>
</li>
<li class="field text required" id="field-organization">
<label for="new-library-org">${_("Organization")}</label>
<input class="new-library-org" id="new-library-org" type="text" name="new-library-org" required placeholder="${_('e.g. UniversityX or OrganizationX')}" aria-describedby="tip-new-library-org tip-error-new-library-org" />
<span class="tip" id="tip-new-library-org">${_("The public organization name for your library.")} ${_("This cannot be changed.")}</span>
<span class="tip tip-error is-hiding" id="tip-error-new-library-org"></span>
</li>
<li class="field text required" id="field-library-number"> <ol class="list-input">
<label for="new-library-number">${_("Library Code")}</label> <li class="field text required" id="field-library-name">
## Translators: This is an example for the "code" used to identify a library, seen when filling out the form to create a new library. This example is short for "Computer Science Problems". The example number may contain letters but must not contain spaces. <label for="new-library-name">${_("Library Name")}</label>
<input class="new-library-number" id="new-library-number" type="text" name="new-library-number" required placeholder="${_('e.g. CSPROB')}" aria-describedby="tip-new-library-number tip-error-new-library-number" /> ## Translators: This is an example name for a new content library, seen when filling out the form to create a new library. (A library is a collection of content or problems.)
<span class="tip" id="tip-new-library-number">${_("The unique code that identifies this library.")} <strong>${_("Note: This is part of your library URL, so no spaces or special characters are allowed.")}</strong> ${_("This cannot be changed.")}</span> <input class="new-library-name" id="new-library-name" type="text" name="new-library-name" required placeholder="${_('e.g. Computer Science Problems')}" aria-describedby="tip-new-library-name tip-error-new-library-name" />
<span class="tip tip-error is-hiding" id="tip-error-new-library-number"></span> <span class="tip" id="tip-new-library-name">${_("The public display name for your library.")}</span>
</li> <span class="tip tip-error is-hiding" id="tip-error-new-library-name"></span>
</ol> </li>
<li class="field text required" id="field-organization">
<label for="new-library-org">${_("Organization")}</label>
<input class="new-library-org" id="new-library-org" type="text" name="new-library-org" required placeholder="${_('e.g. UniversityX or OrganizationX')}" aria-describedby="tip-new-library-org tip-error-new-library-org" />
<span class="tip" id="tip-new-library-org">${_("The public organization name for your library.")} ${_("This cannot be changed.")}</span>
<span class="tip tip-error is-hiding" id="tip-error-new-library-org"></span>
</li>
</fieldset> <li class="field text required" id="field-library-number">
</div> <label for="new-library-number">${_("Library Code")}</label>
## Translators: This is an example for the "code" used to identify a library, seen when filling out the form to create a new library. This example is short for "Computer Science Problems". The example number may contain letters but must not contain spaces.
<input class="new-library-number" id="new-library-number" type="text" name="new-library-number" required placeholder="${_('e.g. CSPROB')}" aria-describedby="tip-new-library-number tip-error-new-library-number" />
<span class="tip" id="tip-new-library-number">${_("The unique code that identifies this library.")} <strong>${_("Note: This is part of your library URL, so no spaces or special characters are allowed.")}</strong> ${_("This cannot be changed.")}</span>
<span class="tip tip-error is-hiding" id="tip-error-new-library-number"></span>
</li>
</ol>
<div class="actions"> </fieldset>
<input type="hidden" value="${allow_unicode_course_id}" class="allow-unicode-course-id" /> </div>
<input type="submit" value="${_('Create')}" class="action action-primary new-library-save" />
<input type="button" value="${_('Cancel')}" class="action action-secondary action-cancel new-library-cancel" />
</div>
</form>
</div>
% endif
<div class="actions">
<input type="hidden" value="${allow_unicode_course_id}" class="allow-unicode-course-id" />
<input type="submit" value="${_('Create')}" class="action action-primary new-library-save" />
<input type="button" value="${_('Cancel')}" class="action action-secondary action-cancel new-library-cancel" />
</div>
</form>
</div>
% endif % endif
<!-- STATE: processing courses --> <!-- STATE: processing courses -->
......
...@@ -17,7 +17,6 @@ from dark_lang.models import DarkLangConfig ...@@ -17,7 +17,6 @@ from dark_lang.models import DarkLangConfig
from openedx.core.djangoapps.user_api.preferences.api import ( from openedx.core.djangoapps.user_api.preferences.api import (
delete_user_preference, get_user_preference, set_user_preference delete_user_preference, get_user_preference, set_user_preference
) )
from openedx.core.djangoapps.user_api.errors import UserNotFound
from lang_pref import LANGUAGE_KEY from lang_pref import LANGUAGE_KEY
# TODO re-import this once we're on Django 1.5 or greater. [PLAT-671] # TODO re-import this once we're on Django 1.5 or greater. [PLAT-671]
...@@ -133,27 +132,36 @@ class DarkLangMiddleware(object): ...@@ -133,27 +132,36 @@ class DarkLangMiddleware(object):
and that language doesn't appear in ``self.released_langs``, and that language doesn't appear in ``self.released_langs``,
then set the session LANGUAGE_SESSION_KEY to that language. then set the session LANGUAGE_SESSION_KEY to that language.
""" """
auth_user = request.user.is_authenticated()
if 'clear-lang' in request.GET: if 'clear-lang' in request.GET:
# Reset dark lang # delete the session language key (if one is set)
delete_user_preference(request.user, DARK_LANGUAGE_KEY) if LANGUAGE_SESSION_KEY in request.session:
# Reset user's language to their language preference, if they have one
user_pref = get_user_preference(request.user, LANGUAGE_KEY)
if user_pref:
request.session[LANGUAGE_SESSION_KEY] = user_pref
elif LANGUAGE_SESSION_KEY in request.session:
del request.session[LANGUAGE_SESSION_KEY] del request.session[LANGUAGE_SESSION_KEY]
if auth_user:
# Reset user's dark lang preference to null
delete_user_preference(request.user, DARK_LANGUAGE_KEY)
# Get & set user's preferred language
user_pref = get_user_preference(request.user, LANGUAGE_KEY)
if user_pref:
request.session[LANGUAGE_SESSION_KEY] = user_pref
return return
# Get the user's preview lang - this is either going to be set from a query
# param `?preview-lang=xx`, or we may have one already set as a dark lang preference.
preview_lang = request.GET.get('preview-lang', None) preview_lang = request.GET.get('preview-lang', None)
if not preview_lang: if not preview_lang and auth_user:
try: # Get the request user's dark lang preference
# Try to get the request user's preference (might not have a user, though) preview_lang = get_user_preference(request.user, DARK_LANGUAGE_KEY)
preview_lang = get_user_preference(request.user, DARK_LANGUAGE_KEY)
except UserNotFound:
return
# User doesn't have a dark lang preference, so just return
if not preview_lang: if not preview_lang:
return return
# Set the session key to the requested preview lang
request.session[LANGUAGE_SESSION_KEY] = preview_lang request.session[LANGUAGE_SESSION_KEY] = preview_lang
set_user_preference(request.user, DARK_LANGUAGE_KEY, preview_lang)
# Make sure that we set the requested preview lang as the dark lang preference for the
# user, so that the lang_pref middleware doesn't clobber away the dark lang preview.
if auth_user:
set_user_preference(request.user, DARK_LANGUAGE_KEY, preview_lang)
...@@ -65,7 +65,12 @@ def set_logged_in_cookies(request, response, user): ...@@ -65,7 +65,12 @@ def set_logged_in_cookies(request, response, user):
# is logged in. This is just a boolean value, so it's not very useful. # is logged in. This is just a boolean value, so it's not very useful.
# In the future, we should be able to replace this with the "user info" # In the future, we should be able to replace this with the "user info"
# cookie set below. # cookie set below.
response.set_cookie(settings.EDXMKTG_LOGGED_IN_COOKIE_NAME, 'true', secure=None, **cookie_settings) response.set_cookie(
settings.EDXMKTG_LOGGED_IN_COOKIE_NAME.encode('utf-8'),
'true',
secure=None,
**cookie_settings
)
# Set a cookie with user info. This can be used by external sites # Set a cookie with user info. This can be used by external sites
# to customize content based on user information. Currently, # to customize content based on user information. Currently,
...@@ -107,7 +112,7 @@ def set_logged_in_cookies(request, response, user): ...@@ -107,7 +112,7 @@ def set_logged_in_cookies(request, response, user):
user_info_cookie_is_secure = request.is_secure() user_info_cookie_is_secure = request.is_secure()
response.set_cookie( response.set_cookie(
settings.EDXMKTG_USER_INFO_COOKIE_NAME, settings.EDXMKTG_USER_INFO_COOKIE_NAME.encode('utf-8'),
json.dumps(user_info), json.dumps(user_info),
secure=user_info_cookie_is_secure, secure=user_info_cookie_is_secure,
**cookie_settings **cookie_settings
...@@ -128,7 +133,11 @@ def delete_logged_in_cookies(response): ...@@ -128,7 +133,11 @@ def delete_logged_in_cookies(response):
""" """
for cookie_name in [settings.EDXMKTG_LOGGED_IN_COOKIE_NAME, settings.EDXMKTG_USER_INFO_COOKIE_NAME]: for cookie_name in [settings.EDXMKTG_LOGGED_IN_COOKIE_NAME, settings.EDXMKTG_USER_INFO_COOKIE_NAME]:
response.delete_cookie(cookie_name, path='/', domain=settings.SESSION_COOKIE_DOMAIN) response.delete_cookie(
cookie_name.encode('utf-8'),
path='/',
domain=settings.SESSION_COOKIE_DOMAIN
)
return response return response
......
...@@ -6,6 +6,7 @@ import unittest ...@@ -6,6 +6,7 @@ import unittest
from django.test import TestCase from django.test import TestCase
from django.test.client import Client from django.test.client import Client
from django.test.utils import override_settings
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
...@@ -195,6 +196,20 @@ class LoginTest(TestCase): ...@@ -195,6 +196,20 @@ class LoginTest(TestCase):
cookie = self.client.cookies[cookie_name] cookie = self.client.cookies[cookie_name]
self.assertIn("01-Jan-1970", cookie.get('expires')) self.assertIn("01-Jan-1970", cookie.get('expires'))
@override_settings(
EDXMKTG_LOGGED_IN_COOKIE_NAME=u"unicode-logged-in",
EDXMKTG_USER_INFO_COOKIE_NAME=u"unicode-user-info",
)
def test_unicode_mktg_cookie_names(self):
# When logged in cookie names are loaded from JSON files, they may
# have type `unicode` instead of `str`, which can cause errors
# when calling Django cookie manipulation functions.
response, _ = self._login_response('test@edx.org', 'test_password')
self._assert_response(response, success=True)
response = self.client.post(reverse('logout'))
self.assertRedirects(response, "/")
@patch.dict("django.conf.settings.FEATURES", {'SQUELCH_PII_IN_LOGS': True}) @patch.dict("django.conf.settings.FEATURES", {'SQUELCH_PII_IN_LOGS': True})
def test_logout_logging_no_pii(self): def test_logout_logging_no_pii(self):
response, _ = self._login_response('test@edx.org', 'test_password') response, _ = self._login_response('test@edx.org', 'test_password')
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
// * +Problem - Rubric // * +Problem - Rubric
// * +Problem - Annotation // * +Problem - Annotation
// * +Problem - Choice Text Group // * +Problem - Choice Text Group
// * +Problem - Image Input Overrides
// +Variables - Capa // +Variables - Capa
// ==================== // ====================
...@@ -1350,3 +1351,24 @@ div.problem { ...@@ -1350,3 +1351,24 @@ div.problem {
} }
} }
} }
// +Problem - Image Input Overrides
// ====================
// NOTE: temporary override until image inputs use same base html structure as other common capa input types.
div.problem .imageinput.capa_inputtype {
.status {
display: inline-block;
position: relative;
top: 3px;
width: 25px;
height: 20px;
}
.correct {
background: url('../images/correct-icon.png') center center no-repeat;
}
.incorrect {
background: url('../images/incorrect-icon.png') center center no-repeat;
}
}
...@@ -1875,6 +1875,11 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase): ...@@ -1875,6 +1875,11 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
block_data.fields = settings block_data.fields = settings
new_id = new_structure['_id'] new_id = new_structure['_id']
# source_version records which revision a block was copied from. In this method, we're updating
# the block, so it's no longer a direct copy, and we can remove the source_version reference.
block_data.edit_info.source_version = None
self.version_block(block_data, user_id, new_id) self.version_block(block_data, user_id, new_id)
self.update_structure(course_key, new_structure) self.update_structure(course_key, new_structure)
# update the index entry if appropriate # update the index entry if appropriate
...@@ -2969,8 +2974,11 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase): ...@@ -2969,8 +2974,11 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
if getattr(destination_block.edit_info, key) is None: if getattr(destination_block.edit_info, key) is None:
setattr(destination_block.edit_info, key, val) setattr(destination_block.edit_info, key, val)
# introduce new edit info field for tracing where copied/published blocks came # If the block we are copying from was itself a copy, then just
destination_block.edit_info.source_version = new_block.edit_info.update_version # reference the original source, rather than the copy.
destination_block.edit_info.source_version = (
new_block.edit_info.source_version or new_block.edit_info.update_version
)
if blacklist != EXCLUDE_ALL: if blacklist != EXCLUDE_ALL:
for child in destination_block.fields.get('children', []): for child in destination_block.fields.get('children', []):
......
...@@ -523,6 +523,75 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup): ...@@ -523,6 +523,75 @@ class TestMixedModuleStore(CommonMixedModuleStoreSetup):
component = self.store.publish(component.location, self.user_id) component = self.store.publish(component.location, self.user_id)
self.assertFalse(self.store.has_changes(component)) self.assertFalse(self.store.has_changes(component))
@ddt.data('draft', 'split')
def test_unit_stuck_in_draft_mode(self, default_ms):
"""
After revert_to_published() the has_changes() should return false if draft has no changes
"""
self.initdb(default_ms)
test_course = self.store.create_course('testx', 'GreekHero', 'test_run', self.user_id)
# Create a dummy component to test against
xblock = self.store.create_item(
self.user_id,
test_course.id,
'vertical',
block_id='test_vertical'
)
# Not yet published, so changes are present
self.assertTrue(self.store.has_changes(xblock))
# Publish and verify that there are no unpublished changes
component = self.store.publish(xblock.location, self.user_id)
self.assertFalse(self.store.has_changes(component))
self.store.revert_to_published(component.location, self.user_id)
component = self.store.get_item(component.location)
self.assertFalse(self.store.has_changes(component))
# Publish and verify again
component = self.store.publish(component.location, self.user_id)
self.assertFalse(self.store.has_changes(component))
@ddt.data('draft', 'split')
def test_unit_stuck_in_published_mode(self, default_ms):
"""
After revert_to_published() the has_changes() should return true if draft has changes
"""
self.initdb(default_ms)
test_course = self.store.create_course('testx', 'GreekHero', 'test_run', self.user_id)
# Create a dummy component to test against
xblock = self.store.create_item(
self.user_id,
test_course.id,
'vertical',
block_id='test_vertical'
)
# Not yet published, so changes are present
self.assertTrue(self.store.has_changes(xblock))
# Publish and verify that there are no unpublished changes
component = self.store.publish(xblock.location, self.user_id)
self.assertFalse(self.store.has_changes(component))
# Discard changes and verify that there are no changes
self.store.revert_to_published(component.location, self.user_id)
component = self.store.get_item(component.location)
self.assertFalse(self.store.has_changes(component))
# Change the component, then check that there now are changes
component = self.store.get_item(component.location)
component.display_name = 'Changed Display Name'
self.store.update_item(component, self.user_id)
# Verify that changes are present
self.assertTrue(self.store.has_changes(component))
def setup_has_changes(self, default_ms): def setup_has_changes(self, default_ms):
""" """
Common set up for has_changes tests below. Common set up for has_changes tests below.
......
...@@ -1182,9 +1182,7 @@ def _update_module_location(module, new_location): ...@@ -1182,9 +1182,7 @@ def _update_module_location(module, new_location):
# in which one component of the key is the XBlock's location (equivalent to "scope_ids"). # in which one component of the key is the XBlock's location (equivalent to "scope_ids").
# Since we've changed the XBlock's location, we need to re-save # Since we've changed the XBlock's location, we need to re-save
# all the XBlock's fields so they will be stored using the new location in the key. # all the XBlock's fields so they will be stored using the new location in the key.
# However, since XBlocks only save "dirty" fields, we need to first # However, since XBlocks only save "dirty" fields, we need to call
# explicitly set each field to its current value before triggering the save. # XBlock's `force_save_fields_method`
if len(rekey_fields) > 0: if len(rekey_fields) > 0:
for rekey_field_name in rekey_fields: module.force_save_fields(rekey_fields)
setattr(module, rekey_field_name, getattr(module, rekey_field_name))
module.save()
...@@ -188,9 +188,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase): ...@@ -188,9 +188,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase):
num_attempts = 1 num_attempts = 1
(module, result) = self.create_and_check( (module, result) = self.create_and_check(
num_attempts=num_attempts, num_attempts=num_attempts,
last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36), last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC),
submission_wait_seconds=180, submission_wait_seconds=180,
considered_now=datetime.datetime(2013, 12, 6, 0, 18, 36) considered_now=datetime.datetime(2013, 12, 6, 0, 18, 36, tzinfo=UTC)
) )
# You should get a dialog that tells you to wait 2 minutes # You should get a dialog that tells you to wait 2 minutes
# Also, the number of attempts should not be incremented # Also, the number of attempts should not be incremented
...@@ -202,9 +202,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase): ...@@ -202,9 +202,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase):
num_attempts = 1 num_attempts = 1
(module, result) = self.create_and_check( (module, result) = self.create_and_check(
num_attempts=num_attempts, num_attempts=num_attempts,
last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36), last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC),
submission_wait_seconds=180, submission_wait_seconds=180,
considered_now=datetime.datetime(2013, 12, 6, 0, 20, 35) considered_now=datetime.datetime(2013, 12, 6, 0, 20, 35, tzinfo=UTC)
) )
# You should get a dialog that tells you to wait 2 minutes # You should get a dialog that tells you to wait 2 minutes
# Also, the number of attempts should not be incremented # Also, the number of attempts should not be incremented
...@@ -216,9 +216,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase): ...@@ -216,9 +216,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase):
num_attempts = 1 num_attempts = 1
(module, result) = self.create_and_check( (module, result) = self.create_and_check(
num_attempts=num_attempts, num_attempts=num_attempts,
last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36), last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC),
submission_wait_seconds=180, submission_wait_seconds=180,
considered_now=datetime.datetime(2013, 12, 6, 0, 20, 36) considered_now=datetime.datetime(2013, 12, 6, 0, 20, 36, tzinfo=UTC)
) )
# Successfully submitted and answered # Successfully submitted and answered
# Also, the number of attempts should increment by 1 # Also, the number of attempts should increment by 1
...@@ -230,9 +230,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase): ...@@ -230,9 +230,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase):
num_attempts = 1 num_attempts = 1
(module, result) = self.create_and_check( (module, result) = self.create_and_check(
num_attempts=num_attempts, num_attempts=num_attempts,
last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36), last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC),
submission_wait_seconds=180, submission_wait_seconds=180,
considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0) considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0, tzinfo=UTC)
) )
# Successfully submitted and answered # Successfully submitted and answered
# Also, the number of attempts should increment by 1 # Also, the number of attempts should increment by 1
...@@ -246,17 +246,17 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase): ...@@ -246,17 +246,17 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase):
with self.assertRaises(xmodule.exceptions.NotFoundError): with self.assertRaises(xmodule.exceptions.NotFoundError):
(module, unused_result) = self.create_and_check( (module, unused_result) = self.create_and_check(
num_attempts=num_attempts, num_attempts=num_attempts,
last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36), last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC),
submission_wait_seconds=180, submission_wait_seconds=180,
considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0) considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0, tzinfo=UTC)
) )
# Now try it without the check_problem # Now try it without the check_problem
(module, unused_result) = self.create_and_check( (module, unused_result) = self.create_and_check(
num_attempts=num_attempts, num_attempts=num_attempts,
last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36), last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC),
submission_wait_seconds=180, submission_wait_seconds=180,
considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0), considered_now=datetime.datetime(2013, 12, 6, 0, 24, 0, tzinfo=UTC),
skip_check_problem=True skip_check_problem=True
) )
# Expect that number of attempts NOT incremented # Expect that number of attempts NOT incremented
...@@ -267,9 +267,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase): ...@@ -267,9 +267,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase):
num_attempts = 1 num_attempts = 1
(module, result) = self.create_and_check( (module, result) = self.create_and_check(
num_attempts=num_attempts, num_attempts=num_attempts,
last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36), last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC),
submission_wait_seconds=60 * 60 * 2, submission_wait_seconds=60 * 60 * 2,
considered_now=datetime.datetime(2013, 12, 6, 2, 15, 35) considered_now=datetime.datetime(2013, 12, 6, 2, 15, 35, tzinfo=UTC)
) )
# You should get a dialog that tells you to wait 2 minutes # You should get a dialog that tells you to wait 2 minutes
# Also, the number of attempts should not be incremented # Also, the number of attempts should not be incremented
...@@ -281,9 +281,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase): ...@@ -281,9 +281,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase):
num_attempts = 1 num_attempts = 1
(module, result) = self.create_and_check( (module, result) = self.create_and_check(
num_attempts=num_attempts, num_attempts=num_attempts,
last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36), last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC),
submission_wait_seconds=60 * 60 * 2 + 63, submission_wait_seconds=60 * 60 * 2 + 63,
considered_now=datetime.datetime(2013, 12, 6, 1, 15, 40) considered_now=datetime.datetime(2013, 12, 6, 1, 15, 40, tzinfo=UTC)
) )
# You should get a dialog that tells you to wait 2 minutes # You should get a dialog that tells you to wait 2 minutes
# Also, the number of attempts should not be incremented # Also, the number of attempts should not be incremented
...@@ -295,9 +295,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase): ...@@ -295,9 +295,9 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase):
num_attempts = 1 num_attempts = 1
(module, result) = self.create_and_check( (module, result) = self.create_and_check(
num_attempts=num_attempts, num_attempts=num_attempts,
last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36), last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC),
submission_wait_seconds=60, submission_wait_seconds=60,
considered_now=datetime.datetime(2013, 12, 6, 0, 17, 36) considered_now=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC)
) )
# You should get a dialog that tells you to wait 2 minutes # You should get a dialog that tells you to wait 2 minutes
# Also, the number of attempts should not be incremented # Also, the number of attempts should not be incremented
......
...@@ -381,9 +381,10 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler ...@@ -381,9 +381,10 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
if not self.fields['download_video'].is_set_on(self): if not self.fields['download_video'].is_set_on(self):
self.download_video = True self.download_video = True
# Set download_video field to default value if its not explicitly set for backward compatibility. # Force download_video field to default value if it's not explicitly set for backward compatibility.
if not self.fields['download_video'].is_set_on(self): if not self.fields['download_video'].is_set_on(self):
self.download_video = self.download_video self.download_video = self.download_video
self.force_save_fields(['download_video'])
# for backward compatibility. # for backward compatibility.
# If course was existed and was not re-imported by the moment of adding `download_track` field, # If course was existed and was not re-imported by the moment of adding `download_track` field,
......
...@@ -18,7 +18,7 @@ from capa.tests.response_xml_factory import ( ...@@ -18,7 +18,7 @@ from capa.tests.response_xml_factory import (
CodeResponseXMLFactory, CodeResponseXMLFactory,
) )
from courseware import grades from courseware import grades
from courseware.models import StudentModule from courseware.models import StudentModule, StudentModuleHistory
from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.helpers import LoginEnrollmentTestCase
from lms.djangoapps.lms_xblock.runtime import quote_slashes from lms.djangoapps.lms_xblock.runtime import quote_slashes
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
...@@ -431,6 +431,32 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -431,6 +431,32 @@ class TestCourseGrader(TestSubmittingProblems):
) )
self.assertEqual(json.loads(resp.content).get("success"), err_msg) self.assertEqual(json.loads(resp.content).get("success"), err_msg)
def test_show_answer_doesnt_write_to_csm(self):
self.basic_setup()
self.submit_question_answer('p1', {'2_1': u'Correct'})
# Now fetch the state entry for that problem.
student_module = StudentModule.objects.get(
course_id=self.course.id,
student=self.student_user
)
# count how many state history entries there are
baseline = StudentModuleHistory.objects.filter(
student_module=student_module
)
baseline_count = baseline.count()
self.assertEqual(baseline_count, 3)
# now click "show answer"
self.show_question_answer('p1')
# check that we don't have more state history entries
csmh = StudentModuleHistory.objects.filter(
student_module=student_module
)
current_count = csmh.count()
self.assertEqual(current_count, 3)
def test_none_grade(self): def test_none_grade(self):
""" """
Check grade is 0 to begin with. Check grade is 0 to begin with.
......
...@@ -113,6 +113,11 @@ ...@@ -113,6 +113,11 @@
padding-bottom: ($baseline +1); padding-bottom: ($baseline +1);
} }
// HACK: fix global header height in verification flow ECOM-1808
header.global {
height: 76px;
}
// HACK: nasty override due to our bad input/button styling // HACK: nasty override due to our bad input/button styling
button, input[type="submit"], input[type="button"], button[type="submit"] { button, input[type="submit"], input[type="button"], button[type="submit"] {
@include font-size(16); @include font-size(16);
......
...@@ -32,7 +32,7 @@ git+https://github.com/hmarr/django-debug-toolbar-mongo.git@b0686a76f1ce3532088c ...@@ -32,7 +32,7 @@ git+https://github.com/hmarr/django-debug-toolbar-mongo.git@b0686a76f1ce3532088c
-e git+https://github.com/jazkarta/ccx-keys.git@e6b03704b1bb97c1d2f31301ecb4e3a687c536ea#egg=ccx-keys -e git+https://github.com/jazkarta/ccx-keys.git@e6b03704b1bb97c1d2f31301ecb4e3a687c536ea#egg=ccx-keys
# Our libraries: # Our libraries:
-e git+https://github.com/edx/XBlock.git@74fdc5a361f48e5596acf3846ca3790a33a05253#egg=XBlock -e git+https://github.com/edx/XBlock.git@017b46b80e712b1318379912257cf1cc1b68eb0e#egg=XBlock
-e git+https://github.com/edx/codejail.git@6b17c33a89bef0ac510926b1d7fea2748b73aadd#egg=codejail -e git+https://github.com/edx/codejail.git@6b17c33a89bef0ac510926b1d7fea2748b73aadd#egg=codejail
-e git+https://github.com/edx/js-test-tool.git@v0.1.6#egg=js_test_tool -e git+https://github.com/edx/js-test-tool.git@v0.1.6#egg=js_test_tool
-e git+https://github.com/edx/event-tracking.git@0.2.0#egg=event-tracking -e git+https://github.com/edx/event-tracking.git@0.2.0#egg=event-tracking
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment