diff --git a/attract/shots/__init__.py b/attract/shots/__init__.py index b7f8999..d226611 100644 --- a/attract/shots/__init__.py +++ b/attract/shots/__init__.py @@ -29,6 +29,7 @@ VALID_PATCH_OPERATIONS = { u'from-web': { u'properties.status', u'properties.notes', + u'description', }, } log = logging.getLogger(__name__) @@ -114,13 +115,17 @@ class ShotManager(object): """ api = pillar_api() - shot = pillarsdk.Node.find(shot_id, api=api) + shot = pillarsdk.Node({'_id': shot_id}) - shot._etag = fields.pop('_etag') - shot.name = fields.pop('name') - shot.description = fields.pop('description') - shot.properties.status = fields.pop('status') - shot.properties.notes = fields.pop('notes', '').strip() or None + patch = { + 'op': 'from-web', + '$set': { + 'description': fields.pop('description', '').strip() or None, + 'properties.notes': (fields.pop('notes', '') or '').strip() or None, + 'properties.status': fields.pop('status'), + } + } + # shot._etag = fields.pop('_etag') self._log.info('Saving shot %s', shot.to_dict()) @@ -128,8 +133,7 @@ class ShotManager(object): self._log.warning('edit_shot(%r, ...) called with unknown fields %r; ignoring them.', shot_id, fields) - shot.update(api=api) - return shot + shot.patch(patch, api=api) def node_setattr(node, key, value): diff --git a/attract/shots/routes.py b/attract/shots/routes.py index 6caa83a..050b554 100644 --- a/attract/shots/routes.py +++ b/attract/shots/routes.py @@ -92,8 +92,11 @@ def save(project, shot_id): log.debug('Form data: %s', request.form) shot_dict = request.form.to_dict() - shot = current_attract.shot_manager.edit_shot(shot_id, **shot_dict) + current_attract.shot_manager.edit_shot(shot_id, **shot_dict) + # Return the patched node in all its glory. + api = pillar_api() + shot = pillarsdk.Node.find(shot_id, api=api) return pillar.api.utils.jsonify(shot.to_dict()) diff --git a/src/templates/attract/shots/view_shot_embed.jade b/src/templates/attract/shots/view_shot_embed.jade index 1e92135..29f2e32 100644 --- a/src/templates/attract/shots/view_shot_embed.jade +++ b/src/templates/attract/shots/view_shot_embed.jade @@ -2,12 +2,9 @@ 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", - type="text", - placeholder='Name', - value="{{ shot.name | hide_none }}") + h2 {{ shot.name | hide_none }} button.copy-to-clipboard.btn.item-id( + style="margin-left: auto", name="Copy to Clipboard", type="button", data-clipboard-text="{{ shot._id }}", diff --git a/tests/test_shots.py b/tests/test_shots.py index 88f5d4d..f6f56a5 100644 --- a/tests/test_shots.py +++ b/tests/test_shots.py @@ -91,6 +91,7 @@ class ShotManagerTest(AbstractShotTest): @responses.activate def test_edit_shot(self): shot = self.create_shot() + pre_edit_shot = shot.to_dict() with self.app.test_request_context(): # Log in as project admin user @@ -98,25 +99,27 @@ class ShotManagerTest(AbstractShotTest): 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') + # No Etag checking, see T49555 + # 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', + notes=None, _etag=shot._etag) # Test directly with MongoDB with self.app.test_request_context(): nodes_coll = self.app.data.driver.db['nodes'] found = nodes_coll.find_one(ObjectId(shot['_id'])) - self.assertEqual(u'ผัดไทย', found['name']) + self.assertEqual(pre_edit_shot['name'], found['name']) # shouldn't be edited. self.assertEqual(u'todo', found['properties']['status']) self.assertEqual(u'Shoot the Pad Thai', found['description']) self.assertNotIn(u'notes', found['properties']) @@ -189,14 +192,66 @@ class PatchShotTest(AbstractShotTest): patch = { 'op': 'from-blender', '$set': { + 'name': u'"shot" is "geschoten" in Dutch', 'properties.trim_start_in_frames': 123, 'properties.duration_in_edit_in_frames': 4215, 'properties.cut_in_timeline_in_frames': 1245, - 'properties.status': 'todo', + 'properties.status': u'on_hold', } } self.patch(url, json=patch, auth_token='token') + dbnode = self.get(url, auth_token='token').json() + self.assertEqual(u'"shot" is "geschoten" in Dutch', dbnode['name']) + self.assertEqual(123, dbnode['properties']['trim_start_in_frames']) + self.assertEqual(u'on_hold', dbnode['properties']['status']) + + @responses.activate + def test_patch_from_web_happy(self): + shot = self.create_shot() + self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token') + + url = '/api/nodes/%s' % shot._id + patch = { + 'op': 'from-web', + '$set': { + 'description': u'Таким образом, этот человек заходит в бар, и говорит…', + 'properties.notes': u'Два бокала вашей лучшей водки, пожалуйста.', + 'properties.status': u'final', + } + } + self.patch(url, json=patch, auth_token='token') + + dbnode = self.get(url, auth_token='token').json() + self.assertEqual(u'Таким образом, этот человек заходит в бар, и говорит…', + dbnode['description']) + self.assertEqual(u'Два бокала вашей лучшей водки, пожалуйста.', + dbnode['properties']['notes']) + self.assertEqual(u'final', dbnode['properties']['status']) + self.assertEqual(u'New shot', dbnode['name']) + + @responses.activate + def test_patch_from_web_happy_nones(self): + shot = self.create_shot() + self.create_valid_auth_token(ctd.EXAMPLE_PROJECT_OWNER_ID, 'token') + + url = '/api/nodes/%s' % shot._id + patch = { + 'op': 'from-web', + '$set': { + 'description': None, + 'properties.notes': None, + 'properties.status': u'final', + } + } + self.patch(url, json=patch, auth_token='token') + + dbnode = self.get(url, auth_token='token').json() + self.assertNotIn('description', dbnode) + self.assertNotIn('notes', dbnode['properties']) + self.assertEqual(u'final', dbnode['properties']['status']) + self.assertEqual(u'New shot', dbnode['name']) + @responses.activate def test_patch_bad_op(self): shot = self.create_shot()