diff --git a/attract/shots/__init__.py b/attract/shots/__init__.py index dcd4b6b..650fd29 100644 --- a/attract/shots/__init__.py +++ b/attract/shots/__init__.py @@ -92,6 +92,7 @@ class ShotManager(object): api = pillar_api() shot = pillarsdk.Node.find(shot_id, api=api) + shot._etag = fields.pop('_etag') shot.name = fields.pop('name') shot.description = fields.pop('description') shot.properties.status = fields.pop('status') diff --git a/attract/static/js/tasks.js b/attract/static/js/tasks.js index 50d7e21..fc9f753 100644 --- a/attract/static/js/tasks.js +++ b/attract/static/js/tasks.js @@ -133,13 +133,16 @@ function attract_form_save(form_id, item_id, item_save_url, options={}) if (console) console.log('Done saving', saved_item); $('#status-bar') .text('Saved ' + options.type + '. ' + saved_item._updated); + $form.find("input[name='_etag']").val(saved_item._etag); if (options.done) options.done($item, saved_item); }) .fail(function(xhr_or_response_data) { // jQuery sends the response data (if JSON), or an XHR object (if not JSON). if (console) console.log('Failed saving', xhr_or_response_data); - $('#status-bar').text('Failed saving. ' + xhr_or_response_data.responseText); + + $button.removeClass('btn-default').addClass('btn-danger'); + $('#status-bar').text('Failed saving. ' + xhr_or_response_data.status); if (options.fail) options.fail($item, xhr_or_response_data); }) @@ -171,7 +174,12 @@ function task_save(task_id, task_url) { ; }, fail: function($item, xhr_or_response_data) { - $('#task-details').html(xhr_or_response_data.responseText); + if (xhr_or_response_data.status == 412) { + // TODO: implement something nice here. Just make sure we don't throw + // away the user's edits. It's up to the user to handle this. + } else { + $('#task-details').html(xhr_or_response_data.responseText); + } }, type: 'task' }); @@ -189,7 +197,12 @@ function shot_save(shot_id, shot_url) { ; }, fail: function($item, xhr_or_response_data) { - $('#task-details').html(xhr_or_response_data.responseText); + if (xhr_or_response_data.status == 412) { + // TODO: implement something nice here. Just make sure we don't throw + // away the user's edits. It's up to the user to handle this. + } else { + $('#task-details').html(xhr_or_response_data.responseText); + } }, type: 'shot' }); diff --git a/attract/tasks/__init__.py b/attract/tasks/__init__.py index 9b0bc83..348a837 100644 --- a/attract/tasks/__init__.py +++ b/attract/tasks/__init__.py @@ -66,6 +66,7 @@ class TaskManager(object): api = pillar_api() task = pillarsdk.Node.find(task_id, api=api) + task._etag = fields.pop('_etag') task.name = fields.pop('name') task.description = fields.pop('description') task.properties.status = fields.pop('status') diff --git a/src/templates/attract/shots/view_shot_embed.jade b/src/templates/attract/shots/view_shot_embed.jade index 3fec989..917b58d 100644 --- a/src/templates/attract/shots/view_shot_embed.jade +++ b/src/templates/attract/shots/view_shot_embed.jade @@ -1,5 +1,6 @@ .attract-form form#shot_form(onsubmit="return shot_save('{{shot._id}}', '{{ url_for('attract.shots.perproject.save', project_url=project['url'], shot_id=shot._id) }}')") + input(type='hidden',name='_etag',value='{{ shot._etag }}') .input-transparent-group input.input-transparent.item-name( name="name", @@ -30,4 +31,6 @@ .input-group-separator .input-transparent-group + | {% if 'PUT' in shot.allowed_methods %} button.btn.btn-default.btn-block(type=submit) Save Changes + | {% endif %} diff --git a/src/templates/attract/tasks/view_task_embed.jade b/src/templates/attract/tasks/view_task_embed.jade index cec6fc8..630054c 100644 --- a/src/templates/attract/tasks/view_task_embed.jade +++ b/src/templates/attract/tasks/view_task_embed.jade @@ -1,5 +1,6 @@ .attract-form form#task_form(onsubmit="return task_save('{{task._id}}', '{{ url_for('attract.tasks.perproject.save', project_url=project['url'], task_id=task._id) }}')") + input(type='hidden',name='_etag',value='{{ task._etag }}') .input-transparent-group input.input-transparent.item-name( name="name", @@ -47,7 +48,9 @@ | {% endfor %} .input-transparent-group - button.btn.btn-default.btn-block(type=submit) Save Changes + | {% if 'PUT' in task.allowed_methods %} + button.btn.btn-default.btn-block(type='submit') Save Changes + | {% endif %} #task-view-feed diff --git a/tests/test_shots.py b/tests/test_shots.py index 2da85d8..026f179 100644 --- a/tests/test_shots.py +++ b/tests/test_shots.py @@ -4,6 +4,7 @@ import responses from bson import ObjectId import pillarsdk +import pillarsdk.exceptions as sdk_exceptions import pillar.tests import pillar.auth import pillar.tests.common_test_data as ctd @@ -94,10 +95,19 @@ class ShotManagerTest(AbstractAttractTest): self.mock_blenderid_validate_happy() + self.assertRaises(sdk_exceptions.PreconditionFailed, + self.smngr.edit_shot, + shot_id=shot['_id'], + name=u'ผัดไทย', + description=u'Shoot the Pad Thai', + status='todo', + _etag='jemoeder') + self.smngr.edit_shot(shot_id=shot['_id'], name=u'ผัดไทย', description=u'Shoot the Pad Thai', - status='todo') + status='todo', + _etag=shot._etag) # Test directly with MongoDB with self.app.test_request_context(): @@ -107,4 +117,3 @@ class ShotManagerTest(AbstractAttractTest): self.assertEqual(u'todo', found['properties']['status']) self.assertEqual(u'Shoot the Pad Thai', found['description']) self.assertNotIn(u'notes', found['properties']) - diff --git a/tests/test_tasks.py b/tests/test_tasks.py index 6fa1f4f..89ff4bf 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -4,6 +4,7 @@ import responses from bson import ObjectId import pillarsdk +import pillarsdk.exceptions as sdk_exceptions import pillar.api.utils import pillar.tests import pillar.auth @@ -59,11 +60,20 @@ class TaskWorkflowTest(AbstractAttractTest): pillar.auth.login_user(ctd.EXAMPLE_PROJECT_OWNER_ID) self.mock_blenderid_validate_happy() + self.assertRaises(sdk_exceptions.PreconditionFailed, + self.mngr.edit_task, + task._id, + task_type=u'je møder', + name=u'nööw name', + description=u'€ ≠ ¥', + status='todo', + _etag='jemoeder') self.mngr.edit_task(task._id, task_type=u'je møder', name=u'nööw name', description=u'€ ≠ ¥', - status='todo') + status='todo', + _etag=task._etag) # Test directly with MongoDB with self.app.test_request_context():