diff --git a/pillar/application/modules/nodes.py b/pillar/application/modules/nodes.py index 4fdb0583..4849140b 100644 --- a/pillar/application/modules/nodes.py +++ b/pillar/application/modules/nodes.py @@ -207,6 +207,43 @@ def before_returning_node_resource_permissions(response): check_permissions('nodes', item, 'GET', append_allowed_methods=True) +def node_set_default_picture(node, original=None): + """Uses the image of an image asset or colour map of texture node as picture.""" + + if node.get('picture'): + log.debug('Node %s already has a picture, not overriding', node.get('_id')) + return + + node_type = node.get('node_type') + props = node.get('properties', {}) + content = props.get('content_type') + + if node_type == 'asset' and content == 'image': + image_file_id = props.get('file') + elif node_type == 'texture': + # Find the colour map, defaulting to the first image map available. + image_file_id = None + for image in props.get('files', []): + if image_file_id is None or image.get('map_type') == u'color': + image_file_id = image.get('file') + else: + log.debug('Not setting default picture on node type %s content type %s', + node_type, content) + return + + if image_file_id is None: + log.debug('Nothing to set the picture to.') + return + + log.debug('Setting default picture for node %s to %s', node.get('_id'), image_file_id) + node['picture'] = image_file_id + + +def nodes_set_default_picture(nodes): + for node in nodes: + node_set_default_picture(node) + + def setup_app(app): # Permission hooks app.on_fetched_item_nodes += before_returning_node_permissions @@ -214,9 +251,13 @@ def setup_app(app): app.on_fetched_item_nodes += item_parse_attachments app.on_fetched_resource_nodes += resource_parse_attachments + app.on_replace_nodes += before_replacing_node app.on_replace_nodes += deduct_content_type + app.on_replace_nodes += node_set_default_picture app.on_replaced_nodes += after_replacing_node + app.on_insert_nodes += before_inserting_nodes app.on_insert_nodes += nodes_deduct_content_type + app.on_insert_nodes += nodes_set_default_picture app.on_inserted_nodes += after_inserting_nodes diff --git a/tests/common_test_class.py b/tests/common_test_class.py index f09d7c56..6598420b 100644 --- a/tests/common_test_class.py +++ b/tests/common_test_class.py @@ -106,7 +106,8 @@ class AbstractPillarTest(TestMinimal): return found['_id'], found - def create_user(self, user_id='cafef00dc379cf10c4aaceaf', roles=('subscriber',)): + def create_user(self, user_id='cafef00dc379cf10c4aaceaf', roles=('subscriber',), + groups=None): from application.utils.authentication import make_unique_username with self.app.test_request_context(): @@ -118,7 +119,7 @@ class AbstractPillarTest(TestMinimal): '_updated': datetime.datetime(2016, 4, 15, 13, 15, 11, tzinfo=tz_util.utc), '_created': datetime.datetime(2016, 4, 15, 13, 15, 11, tzinfo=tz_util.utc), 'username': make_unique_username('tester'), - 'groups': [], + 'groups': groups or [], 'roles': list(roles), 'settings': {'email_communications': 1}, 'auth': [{'token': '', diff --git a/tests/common_test_data.py b/tests/common_test_data.py index 5e543736..75fb9639 100644 --- a/tests/common_test_data.py +++ b/tests/common_test_data.py @@ -2,6 +2,8 @@ import datetime from bson import tz_util, ObjectId +EXAMPLE_ADMIN_GROUP_ID = ObjectId('5596e975ea893b269af85c0e') + EXAMPLE_PROJECT_ID = ObjectId('5672beecc0261b2005ed1a33') EXAMPLE_FILE = {u'_id': ObjectId('5672e2c1c379cf0007b31995'), @@ -52,7 +54,7 @@ EXAMPLE_PROJECT = { u'form_schema': {u'order': {}, u'status': {}, u'url': {}}, u'name': u'group_texture', u'parent': [u'group_texture', u'project'], - u'permissions': {u'groups': [{u'group': ObjectId('5596e975ea893b269af85c0e'), + u'permissions': {u'groups': [{u'group': EXAMPLE_ADMIN_GROUP_ID, u'methods': [u'GET', u'PUT', u'POST']}, {u'group': ObjectId('5596e975ea893b269af85c0f'), u'methods': [u'GET']}, @@ -71,7 +73,7 @@ EXAMPLE_PROJECT = { u'form_schema': {u'notes': {}, u'order': {}, u'status': {}, u'url': {}}, u'name': u'group', u'parent': [u'group', u'project'], - u'permissions': {u'groups': [{u'group': ObjectId('5596e975ea893b269af85c0e'), + u'permissions': {u'groups': [{u'group': EXAMPLE_ADMIN_GROUP_ID, u'methods': [u'GET', u'PUT', u'POST']}, {u'group': ObjectId('5596e975ea893b269af85c0f'), u'methods': [u'GET']}, @@ -119,7 +121,7 @@ EXAMPLE_PROJECT = { u'tags': {}}, u'name': u'asset', u'parent': [u'group'], - u'permissions': {u'groups': [{u'group': ObjectId('5596e975ea893b269af85c0e'), + u'permissions': {u'groups': [{u'group': EXAMPLE_ADMIN_GROUP_ID, u'methods': [u'GET', u'PUT', u'POST']}, {u'group': ObjectId('5596e975ea893b269af85c0f'), u'methods': [u'DELETE', u'GET']}, @@ -133,7 +135,7 @@ EXAMPLE_PROJECT = { u'form_schema': {u'backend': {}, u'subdir': {}}, u'name': u'storage', u'parent': [u'group', u'project'], - u'permissions': {u'groups': [{u'group': ObjectId('5596e975ea893b269af85c0e'), + u'permissions': {u'groups': [{u'group': EXAMPLE_ADMIN_GROUP_ID, u'methods': [u'GET', u'PUT', u'POST']}, {u'group': ObjectId('5596e975ea893b269af85c0f'), u'methods': [u'GET']}, @@ -164,7 +166,7 @@ EXAMPLE_PROJECT = { u'status': {}}, u'name': u'comment', u'parent': [u'asset', u'comment'], - u'permissions': {u'groups': [{u'group': ObjectId('5596e975ea893b269af85c0e'), + u'permissions': {u'groups': [{u'group': EXAMPLE_ADMIN_GROUP_ID, u'methods': [u'GET', u'PUT', u'POST']}, {u'group': ObjectId('5596e975ea893b269af85c0f'), u'methods': [u'GET', u'POST']}, @@ -179,7 +181,7 @@ EXAMPLE_PROJECT = { u'form_schema': {u'categories': {}, u'template': {}}, u'name': u'blog', u'parent': [u'project'], - u'permissions': {u'groups': [{u'group': ObjectId('5596e975ea893b269af85c0e'), + u'permissions': {u'groups': [{u'group': EXAMPLE_ADMIN_GROUP_ID, u'methods': [u'GET', u'PUT', u'POST']}], u'users': [], u'world': [u'GET']}}, @@ -218,7 +220,7 @@ EXAMPLE_PROJECT = { u'url': {}}, u'name': u'post', u'parent': [u'blog'], - u'permissions': {u'groups': [{u'group': ObjectId('5596e975ea893b269af85c0e'), + u'permissions': {u'groups': [{u'group': EXAMPLE_ADMIN_GROUP_ID, u'methods': [u'GET', u'PUT', u'POST']}], u'users': [], u'world': [u'GET']}}, @@ -231,10 +233,10 @@ EXAMPLE_PROJECT = { u'resource': u'files'}, u'type': u'objectid'}, u'is_tileable': {u'type': u'boolean'}, - u'map_type': {u'allowed': [u'spec', + u'map_type': {u'allowed': [u'color', + u'specular', u'bump', - u'nor', - u'col', + u'normal', u'translucency', u'emission', u'alpha'], @@ -245,10 +247,10 @@ EXAMPLE_PROJECT = { u'is_tileable': {u'type': u'boolean'}, u'order': {u'type': u'integer'}, u'resolution': {u'type': u'string'}, - u'stat_ensure_file_existsus': {u'allowed': [u'published', - u'pending', - u'processing'], - u'type': u'string'}, + u'status': {u'allowed': [u'published', + u'pending', + u'processing'], + u'type': u'string'}, u'tags': {u'schema': {u'type': u'string'}, u'type': u'list'}}, u'form_schema': {u'aspect_ratio': {}, u'categories': {}, @@ -262,7 +264,7 @@ EXAMPLE_PROJECT = { u'tags': {}}, u'name': u'texture', u'parent': [u'group'], - u'permissions': {u'groups': [{u'group': ObjectId('5596e975ea893b269af85c0e'), + u'permissions': {u'groups': [{u'group': EXAMPLE_ADMIN_GROUP_ID, u'methods': [u'GET', u'PUT', u'POST']}, {u'group': ObjectId('5596e975ea893b269af85c0f'), u'methods': [u'GET']}, @@ -275,7 +277,7 @@ EXAMPLE_PROJECT = { u'nodes_latest': [], u'organization': ObjectId('55a99fb43004867fb9934f01'), u'owners': {u'groups': [], u'users': []}, - u'permissions': {u'groups': [{u'group': ObjectId('5596e975ea893b269af85c0e'), + u'permissions': {u'groups': [{u'group': EXAMPLE_ADMIN_GROUP_ID, u'methods': [u'GET', u'POST', u'PUT']}], u'users': [], u'world': [u'GET']}, diff --git a/tests/test_nodes.py b/tests/test_nodes.py index 9482f132..2fb48503 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -7,21 +7,22 @@ from flask import g from werkzeug.exceptions import UnprocessableEntity from common_test_class import AbstractPillarTest +import common_test_data as ctd class NodeContentTypeTest(AbstractPillarTest): + def mkfile(self, file_id, content_type): + file_id, _ = self.ensure_file_exists(file_overrides={ + '_id': ObjectId(file_id), + 'content_type': content_type}) + return file_id + def test_node_types(self): """Tests that the node's content_type properties is updated correctly from its file.""" - def mkfile(file_id, content_type): - file_id, _ = self.ensure_file_exists(file_overrides={ - '_id': ObjectId(file_id), - 'content_type': content_type}) - return file_id - - file_id_image = mkfile('cafef00dcafef00dcafef00d', 'image/jpeg') - file_id_video = mkfile('cafef00dcafef00dcafecafe', 'video/matroska') - file_id_blend = mkfile('cafef00dcafef00ddeadbeef', 'application/x-blender') + file_id_image = self.mkfile('cafef00dcafef00dcafef00d', 'image/jpeg') + file_id_video = self.mkfile('cafef00dcafef00dcafecafe', 'video/matroska') + file_id_blend = self.mkfile('cafef00dcafef00ddeadbeef', 'application/x-blender') user_id = self.create_user() project_id, _ = self.ensure_project_exists() @@ -81,3 +82,104 @@ class NodeContentTypeTest(AbstractPillarTest): data = json.loads(resp.data) self.assertEqual([u'GET'], data['allowed_methods']) + + def test_default_picture_image_asset(self): + from application.utils import dumps + + file_id_image = self.mkfile(24 * 'a', 'image/jpeg') + file_id_video = self.mkfile(24 * 'b', 'video/matroska') + file_id_image_spec = self.mkfile(24 * 'c', 'image/jpeg') + file_id_image_bump = self.mkfile(24 * 'd', 'image/jpeg') + + user_id = self.create_user(groups=[ctd.EXAMPLE_ADMIN_GROUP_ID]) + self.create_valid_auth_token(user_id, 'token') + project_id, _ = self.ensure_project_exists() + + def test_for(node, expected_picture_id): + # Create the node + resp = self.client.post('/nodes', + data=dumps(node), + headers={'Authorization': self.make_header('token'), + 'Content-Type': 'application/json'}) + self.assertEqual(resp.status_code, 201, resp.data) + node_id = json.loads(resp.data)['_id'] + + # Test that the node has the attached file as picture. + resp = self.client.get('/nodes/%s' % node_id, + headers={'Authorization': self.make_header('token')}) + self.assertEqual(resp.status_code, 200, resp.data) + json_node = json.loads(resp.data) + + if expected_picture_id: + self.assertEqual(ObjectId(json_node['picture']), expected_picture_id) + else: + self.assertNotIn('picture', json_node) + + # Image asset node + test_for({'description': '', + 'project': project_id, + 'node_type': 'asset', + 'user': user_id, + 'properties': {'status': 'published', + 'tags': [], + 'order': 0, + 'categories': '', + 'file': file_id_image}, + 'name': 'Image asset'}, + file_id_image) + + # Video asset node, should not get default picture + test_for({'description': '', + 'project': project_id, + 'node_type': 'asset', + 'user': user_id, + 'properties': {'status': 'published', + 'tags': [], + 'order': 0, + 'categories': '', + 'file': file_id_video}, + 'name': 'Video asset'}, + None) + + # Texture node, should default to colour map. + test_for({'description': '', + 'project': project_id, + 'node_type': 'texture', + 'user': user_id, + 'properties': {'status': 'published', + 'tags': [], + 'order': 0, + 'categories': '', + 'files': [ + {'file': file_id_image_bump, 'map_type': 'bump'}, + {'file': file_id_image_spec, 'map_type': 'specular'}, + {'file': file_id_image, 'map_type': 'color'}, + ], + 'is_tileable': False, + 'aspect_ratio': 0.0, + 'is_landscape': False, + 'resolution': '', + }, + 'name': 'Texture node'}, + file_id_image) + + # Texture node, should default to first image if there is no colour map. + test_for({'description': '', + 'project': project_id, + 'node_type': 'texture', + 'user': user_id, + 'properties': {'status': 'published', + 'tags': [], + 'order': 0, + 'categories': '', + 'files': [ + {'file': file_id_image_bump, 'map_type': 'bump'}, + {'file': file_id_image_spec, 'map_type': 'specular'}, + ], + 'is_tileable': False, + 'aspect_ratio': 0.0, + 'is_landscape': False, + 'resolution': '', + }, + 'name': 'Texture node'}, + file_id_image_bump)