Commit 6e8e74aa by Your Name

Merge github.com:MITx/mitx into feature/kevin/groups_ui_changes

parents 7a3798f2 2306e7c1
...@@ -656,17 +656,6 @@ input.courseware-unit-search-input { ...@@ -656,17 +656,6 @@ input.courseware-unit-search-input {
} }
} }
// sort/drag and drop - make room
.ui-dragging-pushdown {
@include transition (margin-top 0.5s ease-in-out 0s);
margin-top: 15px;
}
.ui-dragging-pushup {
@include transition (margin-bottom 0.5s ease-in-out 0s);
margin-bottom: 15px;
}
.ui-draggable-dragging { .ui-draggable-dragging {
@include box-shadow(0 1px 2px rgba(0, 0, 0, .3)); @include box-shadow(0 1px 2px rgba(0, 0, 0, .3));
border: 1px solid $darkGrey; border: 1px solid $darkGrey;
...@@ -677,6 +666,11 @@ input.courseware-unit-search-input { ...@@ -677,6 +666,11 @@ input.courseware-unit-search-input {
background: $yellow !important; background: $yellow !important;
} }
} }
// hiding unit button - temporary fix until this semantically corrected
.new-unit-item {
display: none;
}
} }
ol.ui-droppable .branch:first-child .section-item { ol.ui-droppable .branch:first-child .section-item {
......
...@@ -305,6 +305,7 @@ ...@@ -305,6 +305,7 @@
.wrapper-component-editor { .wrapper-component-editor {
z-index: 9999; z-index: 9999;
position: relative; position: relative;
background: $lightBluishGrey2;
} }
.component-editor { .component-editor {
......
...@@ -389,11 +389,18 @@ class CapaModule(XModule): ...@@ -389,11 +389,18 @@ class CapaModule(XModule):
}) })
return json.dumps(d, cls=ComplexEncoder) return json.dumps(d, cls=ComplexEncoder)
def is_past_due(self):
"""
Is it now past this problem's due date, including grace period?
"""
return (self.close_date is not None and
datetime.datetime.utcnow() > self.close_date)
def closed(self): def closed(self):
''' Is the student still allowed to submit answers? ''' ''' Is the student still allowed to submit answers? '''
if self.attempts == self.max_attempts: if self.attempts == self.max_attempts:
return True return True
if self.close_date is not None and datetime.datetime.utcnow() > self.close_date: if self.is_past_due():
return True return True
return False return False
...@@ -408,28 +415,28 @@ class CapaModule(XModule): ...@@ -408,28 +415,28 @@ class CapaModule(XModule):
return self.attempts > 0 return self.attempts > 0
def answer_available(self): def answer_available(self):
''' Is the user allowed to see an answer? '''
Is the user allowed to see an answer?
''' '''
if self.show_answer == '': if self.show_answer == '':
return False return False
elif self.show_answer == "never":
if self.show_answer == "never":
return False return False
elif self.system.user_is_staff:
# Admins can see the answer, unless the problem explicitly prevents it # This is after the 'never' check because admins can see the answer
if self.system.user_is_staff: # unless the problem explicitly prevents it
return True return True
elif self.show_answer == 'attempted':
if self.show_answer == 'attempted':
return self.attempts > 0 return self.attempts > 0
elif self.show_answer == 'answered':
if self.show_answer == 'answered': # NOTE: this is slightly different from 'attempted' -- resetting the problems
# makes lcp.done False, but leaves attempts unchanged.
return self.lcp.done return self.lcp.done
elif self.show_answer == 'closed':
if self.show_answer == 'closed':
return self.closed() return self.closed()
elif self.show_answer == 'past_due':
if self.show_answer == 'always': return self.is_past_due()
elif self.show_answer == 'always':
return True return True
return False return False
...@@ -678,18 +685,18 @@ class CapaDescriptor(RawDescriptor): ...@@ -678,18 +685,18 @@ class CapaDescriptor(RawDescriptor):
# TODO (vshnayder): do problems have any other metadata? Do they # TODO (vshnayder): do problems have any other metadata? Do they
# actually use type and points? # actually use type and points?
metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points') metadata_attributes = RawDescriptor.metadata_attributes + ('type', 'points')
def get_context(self): def get_context(self):
_context = RawDescriptor.get_context(self) _context = RawDescriptor.get_context(self)
_context.update({'markdown': self.metadata.get('markdown', '')}) _context.update({'markdown': self.metadata.get('markdown', '')})
return _context return _context
@property @property
def editable_metadata_fields(self): def editable_metadata_fields(self):
"""Remove metadata from the editable fields since it has its own editor""" """Remove metadata from the editable fields since it has its own editor"""
subset = super(CapaDescriptor,self).editable_metadata_fields subset = super(CapaDescriptor,self).editable_metadata_fields
if 'markdown' in subset: if 'markdown' in subset:
subset.remove('markdown') subset.remove('markdown')
return subset return subset
......
...@@ -50,7 +50,7 @@ class @HTMLEditingDescriptor ...@@ -50,7 +50,7 @@ class @HTMLEditingDescriptor
}) })
@showingVisualEditor = true @showingVisualEditor = true
@element.on('click', '.editor-tabs .tab', @onSwitchEditor) @element.on('click', '.editor-tabs .tab', this, @onSwitchEditor)
@setupTinyMCE: (ed) -> @setupTinyMCE: (ed) ->
ed.addButton('wrapAsCode', { ed.addButton('wrapAsCode', {
...@@ -71,16 +71,18 @@ class @HTMLEditingDescriptor ...@@ -71,16 +71,18 @@ class @HTMLEditingDescriptor
e.preventDefault(); e.preventDefault();
if not $(e.currentTarget).hasClass('current') if not $(e.currentTarget).hasClass('current')
element = e.data.element
$(e.currentTarget).addClass('current') $(e.currentTarget).addClass('current')
$('table.mceToolbar', @element).toggleClass(HTMLEditingDescriptor.isInactiveClass) $(element).find('table.mceToolbar').toggleClass(HTMLEditingDescriptor.isInactiveClass)
$(@advanced_editor.getWrapperElement()).toggleClass(HTMLEditingDescriptor.isInactiveClass) $(@advanced_editor.getWrapperElement()).toggleClass(HTMLEditingDescriptor.isInactiveClass)
visualEditor = @getVisualEditor() visualEditor = @getVisualEditor(element)
if $(e.currentTarget).attr('data-tab') is 'visual' if $(e.currentTarget).attr('data-tab') is 'visual'
$('.html-tab', @element).removeClass('current') $(element).find('.html-tab').removeClass('current')
@showVisualEditor(visualEditor) @showVisualEditor(visualEditor)
else else
$('.visual-tab', @element).removeClass('current') $(element).find('.visual-tab').removeClass('current')
@showAdvancedEditor(visualEditor) @showAdvancedEditor(visualEditor)
# Show the Advanced (codemirror) Editor. Pulled out as a helper method for unit testing. # Show the Advanced (codemirror) Editor. Pulled out as a helper method for unit testing.
...@@ -105,17 +107,17 @@ class @HTMLEditingDescriptor ...@@ -105,17 +107,17 @@ class @HTMLEditingDescriptor
focusVisualEditor: (visualEditor) -> focusVisualEditor: (visualEditor) ->
visualEditor.focus() visualEditor.focus()
getVisualEditor: -> getVisualEditor: (element) ->
### ###
Returns the instance of TinyMCE. Returns the instance of TinyMCE.
This is different from the textarea that exists in the HTML template (@tiny_mce_textarea. This is different from the textarea that exists in the HTML template (@tiny_mce_textarea.
### ###
return tinyMCE.get($('.tiny-mce', this.element).attr('id')) return tinyMCE.get($(element).find('.tiny-mce').attr('id'))
save: -> save: ->
@element.off('click', '.editor-tabs .tab', @onSwitchEditor) @element.off('click', '.editor-tabs .tab', @onSwitchEditor)
text = @advanced_editor.getValue() text = @advanced_editor.getValue()
visualEditor = @getVisualEditor() visualEditor = @getVisualEditor(@element)
if @showingVisualEditor and visualEditor.isDirty() if @showingVisualEditor and visualEditor.isDirty()
text = visualEditor.getContent({no_events: 1}) text = visualEditor.getContent({no_events: 1})
data: text data: text
...@@ -26,7 +26,7 @@ test_system = ModuleSystem( ...@@ -26,7 +26,7 @@ test_system = ModuleSystem(
# "render" to just the context... # "render" to just the context...
render_template=lambda template, context: str(context), render_template=lambda template, context: str(context),
replace_urls=Mock(), replace_urls=Mock(),
user=Mock(), user=Mock(is_staff=False),
filestore=Mock(), filestore=Mock(),
debug=True, debug=True,
xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10}, xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10},
......
import datetime
import json
from mock import Mock
from pprint import pprint
import unittest
from xmodule.capa_module import CapaModule
from xmodule.modulestore import Location
from lxml import etree
from . import test_system
class CapaFactory(object):
"""
A helper class to create problem modules with various parameters for testing.
"""
sample_problem_xml = """<?xml version="1.0"?>
<problem>
<text>
<p>What is pi, to two decimal placs?</p>
</text>
<numericalresponse answer="3.14">
<textline math="1" size="30"/>
</numericalresponse>
</problem>
"""
num = 0
@staticmethod
def next_num():
CapaFactory.num += 1
return CapaFactory.num
@staticmethod
def create(graceperiod=None,
due=None,
max_attempts=None,
showanswer=None,
rerandomize=None,
force_save_button=None,
attempts=None,
problem_state=None,
):
"""
All parameters are optional, and are added to the created problem if specified.
Arguments:
graceperiod:
due:
max_attempts:
showanswer:
force_save_button:
rerandomize: all strings, as specified in the policy for the problem
problem_state: a dict to to be serialized into the instance_state of the
module.
attempts: also added to instance state. Will be converted to an int.
"""
definition = {'data': CapaFactory.sample_problem_xml,}
location = Location(["i4x", "edX", "capa_test", "problem",
"SampleProblem{0}".format(CapaFactory.next_num())])
metadata = {}
if graceperiod is not None:
metadata['graceperiod'] = graceperiod
if due is not None:
metadata['due'] = due
if max_attempts is not None:
metadata['attempts'] = max_attempts
if showanswer is not None:
metadata['showanswer'] = showanswer
if force_save_button is not None:
metadata['force_save_button'] = force_save_button
if rerandomize is not None:
metadata['rerandomize'] = rerandomize
descriptor = Mock(weight="1")
instance_state_dict = {}
if problem_state is not None:
instance_state_dict = problem_state
if attempts is not None:
# converting to int here because I keep putting "0" and "1" in the tests
# since everything else is a string.
instance_state_dict['attempts'] = int(attempts)
if len(instance_state_dict) > 0:
instance_state = json.dumps(instance_state_dict)
else:
instance_state = None
module = CapaModule(test_system, location,
definition, descriptor,
instance_state, None, metadata=metadata)
return module
class CapaModuleTest(unittest.TestCase):
def setUp(self):
now = datetime.datetime.now()
day_delta = datetime.timedelta(days=1)
self.yesterday_str = str(now - day_delta)
self.today_str = str(now)
self.tomorrow_str = str(now + day_delta)
# in the capa grace period format, not in time delta format
self.two_day_delta_str = "2 days"
def test_import(self):
module = CapaFactory.create()
self.assertEqual(module.get_score()['score'], 0)
other_module = CapaFactory.create()
self.assertEqual(module.get_score()['score'], 0)
self.assertNotEqual(module.url_name, other_module.url_name,
"Factory should be creating unique names for each problem")
def test_showanswer_default(self):
"""
Make sure the show answer logic does the right thing.
"""
# default, no due date, showanswer 'closed', so problem is open, and show_answer
# not visible.
problem = CapaFactory.create()
self.assertFalse(problem.answer_available())
def test_showanswer_attempted(self):
problem = CapaFactory.create(showanswer='attempted')
self.assertFalse(problem.answer_available())
problem.attempts = 1
self.assertTrue(problem.answer_available())
def test_showanswer_closed(self):
# can see after attempts used up, even with due date in the future
used_all_attempts = CapaFactory.create(showanswer='closed',
max_attempts="1",
attempts="1",
due=self.tomorrow_str)
self.assertTrue(used_all_attempts.answer_available())
# can see after due date
after_due_date = CapaFactory.create(showanswer='closed',
max_attempts="1",
attempts="0",
due=self.yesterday_str)
self.assertTrue(after_due_date.answer_available())
# can't see because attempts left
attempts_left_open = CapaFactory.create(showanswer='closed',
max_attempts="1",
attempts="0",
due=self.tomorrow_str)
self.assertFalse(attempts_left_open.answer_available())
# Can't see because grace period hasn't expired
still_in_grace = CapaFactory.create(showanswer='closed',
max_attempts="1",
attempts="0",
due=self.yesterday_str,
graceperiod=self.two_day_delta_str)
self.assertFalse(still_in_grace.answer_available())
def test_showanswer_past_due(self):
"""
With showanswer="past_due" should only show answer after the problem is closed
for everyone--e.g. after due date + grace period.
"""
# can see after attempts used up, even with due date in the future
used_all_attempts = CapaFactory.create(showanswer='past_due',
max_attempts="1",
attempts="1",
due=self.tomorrow_str)
self.assertFalse(used_all_attempts.answer_available())
# can see after due date
past_due_date = CapaFactory.create(showanswer='past_due',
max_attempts="1",
attempts="0",
due=self.yesterday_str)
self.assertTrue(past_due_date.answer_available())
# can't see because attempts left
attempts_left_open = CapaFactory.create(showanswer='past_due',
max_attempts="1",
attempts="0",
due=self.tomorrow_str)
self.assertFalse(attempts_left_open.answer_available())
# Can't see because grace period hasn't expired, even though have no more
# attempts.
still_in_grace = CapaFactory.create(showanswer='past_due',
max_attempts="1",
attempts="1",
due=self.yesterday_str,
graceperiod=self.two_day_delta_str)
self.assertFalse(still_in_grace.answer_available())
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