Skip to content

Commit

Permalink
webapp/jupyter2: also checking for any output modifications, explicit…
Browse files Browse the repository at this point in the history
… list of protected cell attributes in _set
  • Loading branch information
haraldschilly committed Feb 5, 2018
1 parent 1e83d44 commit f6612b4
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 22 deletions.
87 changes: 69 additions & 18 deletions src/smc-webapp/jupyter/actions.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,7 @@ class exports.JupyterActions extends Actions
# Set the input of the given cell in the syncdb, which will also change the store.
# Might throw a CellWriteProtectedException
set_cell_input: (id, input, save=true) =>
if not @store.is_cell_editable(id)
throw CellWriteProtectedException
@_set
@_checked_set
type : 'cell'
id : id
input : input
Expand All @@ -260,24 +258,36 @@ class exports.JupyterActions extends Actions
save

set_cell_output: (id, output, save=true) =>
@_set
@_checked_set
type : 'cell'
id : id
output : output,
save

clear_selected_outputs: =>
cells = @store.get('cells')
for id in @store.get_selected_cell_ids_list()
v = @store.get_selected_cell_ids_list()
for id in v
cell = cells.get(id)
if cell.get('output')? or cell.get('exec_count')
@_set({type:'cell', id:id, output:null, exec_count:null}, false)
try
if cell.get('output')? or cell.get('exec_count')
@_set({type:'cell', id:id, output:null, exec_count:null}, false)
catch ex
if ex is CellWriteProtectedException
if v.length == 1
@show_edit_protection_error()
else
throw ex
@_sync()

clear_all_outputs: =>
@store.get('cells').forEach (cell, id) =>
if cell.get('output')? or cell.get('exec_count')
@_set({type:'cell', id:id, output:null, exec_count:null}, false)
try
@_set({type:'cell', id:id, output:null, exec_count:null}, false)
catch ex
if ex isnt CellWriteProtectedException
throw ex
return
@_sync()

Expand Down Expand Up @@ -308,7 +318,7 @@ class exports.JupyterActions extends Actions
if cell_type != 'markdown' and cell_type != 'raw' and cell_type != 'code'
throw Error("cell type (='#{cell_type}') must be 'markdown', 'raw', or 'code'")
if not @store.is_cell_editable(id)
@set_error("This cell is protected from being edited.")
@show_edit_protection_error()
return
obj =
type : 'cell'
Expand Down Expand Up @@ -350,7 +360,7 @@ class exports.JupyterActions extends Actions
@set_md_cell_editing(id)
catch ex
if ex is CellWriteProtectedException
@set_error("This cell is protected from being edited.")
@show_edit_protection_error()
return
else
throw ex
Expand Down Expand Up @@ -636,12 +646,26 @@ class exports.JupyterActions extends Actions
_set: (obj, save=true) =>
if @_state == 'closed'
return
# check write protection regarding specific keys to be set
if (obj.type == 'cell') and (obj.id?) and (not @store.is_cell_editable(obj.id))
for protected_key in ['input', 'output', 'exec_count', 'cell_type']
if misc.has_key(protected_key)
throw CellWriteProtectedException
#@dbg("_set")("obj=#{misc.to_json(obj)}")
@syncdb.exit_undo_mode()
@syncdb.set(obj, save)
# ensure that we update locally immediately for our own changes.
@_syncdb_change(immutable.fromJS([misc.copy_with(obj, ['id', 'type'])]))

_checked_set: (obj, save=true) =>
try
@_set(obj, save)
catch ex
if ex is CellWriteProtectedException
@show_edit_protection_error()
else
throw ex

# might throw a CellDeleteProtectedException
_delete: (obj, save=true) =>
if @_state == 'closed'
Expand Down Expand Up @@ -719,7 +743,7 @@ class exports.JupyterActions extends Actions
@_sync()
if not_deletable > 0
if selected.length == 1
@set_error("This cell is protected from deletion.")
@show_delete_protection_error()
@move_cursor_to_cell(id)
else
verb = if not_deletable == 1 then 'is' else 'are'
Expand Down Expand Up @@ -789,6 +813,8 @@ class exports.JupyterActions extends Actions
run_code_cell: (id, save=true) =>
# We mark the start timestamp uniquely, so that the backend can sort
# multiple cells with a simultaneous time to start request.
return if @check_edit_protection(id)

start = @_client.server_time() - 0
if @_last_start? and start <= @_last_start
start = @_last_start + 1
Expand All @@ -807,6 +833,7 @@ class exports.JupyterActions extends Actions
@set_trust_notebook(true)

clear_cell: (id, save=true) =>
return if @check_edit_protection(id)
@_set
type : 'cell'
id : id
Expand Down Expand Up @@ -964,9 +991,7 @@ class exports.JupyterActions extends Actions
if cursor.id != cur_id
# cursor isn't in currently selected cell, so don't know how to split
return
if not @store.is_cell_editable(cur_id)
@set_error('This cell is protected from being edited.')
return
return if @check_edit_protection(cur_id)
# insert a new cell before the currently selected one
new_id = @insert_cell(-1)

Expand Down Expand Up @@ -1010,11 +1035,11 @@ class exports.JupyterActions extends Actions
if not next_id?
return
for cell_id in [cur_id, next_id]
if not @store.is_cell_deletable(cur_id)
@set_error('A cell protected from being deleted cannot be merged.')
return
if not @store.is_cell_editable(cur_id)
@set_error('A cell protected from being edited cannot be merged.')
@set_error('A cell protected from editing cannot be merged.')
return
if not @store.is_cell_deletable(cur_id)
@set_error('A cell protected from deletion cannot be merged.')
return
cells = @store.get('cells')
if not cells?
Expand Down Expand Up @@ -1091,11 +1116,32 @@ class exports.JupyterActions extends Actions
@set_md_cell_not_editing(id)
@toggle_metadata_boolean('editable', f)

check_edit_protection: (id) =>
if not @store.is_cell_editable(id)
@show_edit_protection_error()
return true
else
return false

show_edit_protection_error: =>
@set_error("This cell is protected from being edited.")


# this prevents any cell from being deleted, either directly, or indirectly via a "merge"
# example: teacher handout notebook and student should not be able to modify an instruction cell in any way
toggle_delete_protection: =>
@toggle_metadata_boolean('deletable')

check_delete_protection: (id) =>
if not @store.is_cell_deletable(id)
@show_delete_protection_error()
return true
else
return false

show_delete_protection_error: =>
@set_error("This cell is protected from deletion.")

# This toggles the boolean value of given metadata field.
# If not set, it is assumed to be true and toggled to false
# For more than one cell, the first one is used to toggle all cells to the inverted state
Expand Down Expand Up @@ -1832,6 +1878,7 @@ class exports.JupyterActions extends Actions
set_cell_slide: (id, value) =>
if not value
value = null # delete
return if @check_edit_protection(id)
@_set
type : 'cell'
id : id
Expand Down Expand Up @@ -1864,6 +1911,7 @@ class exports.JupyterActions extends Actions
insert_input_at_cursor: (id, s, save) =>
if not @store.getIn(['cells', id])?
return
return if @check_edit_protection(id)
input = @_get_cell_input(id)
cursor = @_cursor_locs?[0]
if cursor?.id == id
Expand All @@ -1881,6 +1929,7 @@ class exports.JupyterActions extends Actions
if not cell?
# no such cell
return
return if @check_edit_protection(id)
attachments = cell.get('attachments')?.toJS() ? {}
attachments[name] = val
@_set
Expand Down Expand Up @@ -1908,13 +1957,15 @@ class exports.JupyterActions extends Actions
@set_cell_input(id, misc.replace_all(@_get_cell_input(id), @_attachment_markdown(name), ''))

add_tag: (id, tag, save=true) =>
return if @check_edit_protection(id)
@_set
type : 'cell'
id : id
tags : {"#{tag}":true},
save

remove_tag: (id, tag, save=true) =>
return if @check_edit_protection(id)
@_set
type : 'cell'
id : id
Expand Down
4 changes: 2 additions & 2 deletions src/smc-webapp/jupyter/cell.cjsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ exports.Cell = rclass
{
if not @props.deletable
<Tip title={'Protected from deletion'} placement={'right'} size={'small'} style={lock_style}>
<Icon name='lock' />
<Icon name='ban' />
</Tip>
}
{
if not @props.editable
<Tip title={'Protected from modifications'} placement={'right'} size={'small'}>
<Icon name='ban'/>
<Icon name='lock' />
</Tip>
}
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/smc-webapp/jupyter/main.cjsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ exports.JupyterEditor = rclass ({name}) ->

reduxProps :
"#{name}" :
view_mode : rtypes.string # 'normal', 'json', 'raw'
view_mode : rtypes.oneOf(['normal', 'json', 'raw'])
kernel : rtypes.string # string name of the kernel
error : rtypes.string
fatal : rtypes.string # *FATAL* error; user must edit file to fix.
Expand All @@ -47,7 +47,7 @@ exports.JupyterEditor = rclass ({name}) ->
cells : rtypes.immutable.Map # map from ids to cells
cur_id : rtypes.string
sel_ids : rtypes.immutable.Set.isRequired # set of selected cells
mode : rtypes.string.isRequired # 'edit' or 'escape'
mode : rtypes.oneOf(['edit', 'escape']).isRequired
font_size : rtypes.number
md_edit_ids : rtypes.immutable.Set.isRequired # ids of markdown cells in edit mode
cm_options : rtypes.immutable.Map # settings for all the codemirror editors
Expand Down

0 comments on commit f6612b4

Please sign in to comment.