From 4a72b377bdc72a4b470cd40a45e1d98ee35cc86f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 20 Jul 2016 14:12:54 +0200 Subject: [PATCH] Include HDRi projects in /bcloud/texture-libraries This depends on the version of the Blender Cloud Addon, which will be sent as Blender-Cloud-Addon HTTP header in a future version of the addon. --- .../modules/blender_cloud/__init__.py | 24 +++++ .../modules/blender_cloud/texture_libs.py | 22 ++++- tests/test_bcloud_home_project.py | 89 ++++++++++++++++++- 3 files changed, 130 insertions(+), 5 deletions(-) diff --git a/pillar/application/modules/blender_cloud/__init__.py b/pillar/application/modules/blender_cloud/__init__.py index 5bde2c86..6a621274 100644 --- a/pillar/application/modules/blender_cloud/__init__.py +++ b/pillar/application/modules/blender_cloud/__init__.py @@ -1,3 +1,27 @@ +from flask import request +from werkzeug import exceptions as wz_exceptions + + +def blender_cloud_addon_version(): + """Returns the version of the Blender Cloud Addon, or None if not given in the request. + + Uses the 'Blender-Cloud-Addon' HTTP header. + + :returns: the version of the addon, as tuple (major, minor, micro) + :rtype: tuple or None + :raises: werkzeug.exceptions.BadRequest if the header is malformed. + """ + + header = request.headers.get('Blender-Cloud-Addon') + if not header: + return None + + parts = header.split('.') + try: + return tuple(int(part) for part in parts) + except ValueError: + raise wz_exceptions.BadRequest('Invalid Blender-Cloud-Addon header') + def setup_app(app, url_prefix): from . import texture_libs, home_project diff --git a/pillar/application/modules/blender_cloud/texture_libs.py b/pillar/application/modules/blender_cloud/texture_libs.py index 90f148ca..c84a22c5 100644 --- a/pillar/application/modules/blender_cloud/texture_libs.py +++ b/pillar/application/modules/blender_cloud/texture_libs.py @@ -1,3 +1,4 @@ +import functools import logging from flask import Blueprint, request, current_app, g @@ -9,6 +10,7 @@ from werkzeug.exceptions import InternalServerError from application import utils from application.utils.authorization import require_login +FIRST_ADDON_VERSION_WITH_HDRI = (1, 4, 0) TL_PROJECTION = utils.dumps({'name': 1, 'url': 1, 'permissions': 1,}) TL_SORT = utils.dumps([('name', 1)]) @@ -60,15 +62,22 @@ def keep_fetching_texture_libraries(proj_filter): @blueprint.route('/texture-libraries') @require_login() def texture_libraries(): + from . import blender_cloud_addon_version + # Use Eve method so that we get filtering on permissions for free. # This gives all the projects that contain the required node types. - request.args = MultiDict(request.args) # allow changes; it's an ImmutableMultiDict by default. request.args.setlist(eve_config.QUERY_PROJECTION, [TL_PROJECTION]) request.args.setlist(eve_config.QUERY_SORT, [TL_SORT]) + # Determine whether to return HDRi projects too, based on the version + # of the Blender Cloud Addon. If the addon version is None, we're dealing + # with a version of the BCA that's so old it doesn't send its version along. + return_hdri = blender_cloud_addon_version() > FIRST_ADDON_VERSION_WITH_HDRI + accept_as_library = functools.partial(has_texture_node, return_hdri=return_hdri) + # Construct eve-like response. - projects = list(keep_fetching_texture_libraries(has_texture_node)) + projects = list(keep_fetching_texture_libraries(accept_as_library)) result = {'_items': projects, '_meta': { 'max_results': len(projects), @@ -79,13 +88,18 @@ def texture_libraries(): return utils.jsonify(result) -def has_texture_node(proj): +def has_texture_node(proj, return_hdri=True): """Returns True iff the project has a top-level (group)texture node.""" nodes_collection = current_app.data.driver.db['nodes'] + # See which types of nodes we support. + node_types = ['group_texture'] + if return_hdri: + node_types.append('hdri') + count = nodes_collection.count( - {'node_type': 'group_texture', + {'node_type': {'$in': node_types}, 'project': proj['_id'], 'parent': None}) return count > 0 diff --git a/tests/test_bcloud_home_project.py b/tests/test_bcloud_home_project.py index 5b01866d..3e6e2c59 100644 --- a/tests/test_bcloud_home_project.py +++ b/tests/test_bcloud_home_project.py @@ -8,13 +8,14 @@ import logging import responses from bson import ObjectId from flask import url_for +from werkzeug import exceptions as wz_exceptions from common_test_class import AbstractPillarTest, TEST_EMAIL_ADDRESS log = logging.getLogger(__name__) -class HomeProjectTest(AbstractPillarTest): +class AbstractHomeProjectTest(AbstractPillarTest): def setUp(self, **kwargs): AbstractPillarTest.setUp(self, **kwargs) self.create_standard_groups() @@ -28,6 +29,8 @@ class HomeProjectTest(AbstractPillarTest): self.create_valid_auth_token(user_id, token) return user_id + +class HomeProjectTest(AbstractHomeProjectTest): def test_create_home_project(self): from application.modules.blender_cloud import home_project from application.utils.authentication import validate_token @@ -421,3 +424,87 @@ class HomeProjectUserChangedRoleTest(AbstractPillarTest): headers={'Authorization': self.make_header('token'), 'Content-Type': 'application/json'}) self.assertEqual(status_code, resp.status_code, resp.data) + + +class TextureLibraryTest(AbstractHomeProjectTest): + def create_node(self, node_doc): + with self.app.test_request_context(): + nodes_coll = self.app.data.driver.db['nodes'] + result = nodes_coll.insert_one(node_doc) + return result.inserted_id + + def setUp(self, **kwargs): + AbstractHomeProjectTest.setUp(self, **kwargs) + + user_id = self._create_user_with_token(set(), 'token') + self.hdri_proj_id, proj = self.ensure_project_exists(project_overrides={'_id': 24 * 'a'}) + self.tex_proj_id, proj2 = self.ensure_project_exists(project_overrides={'_id': 24 * 'b'}) + + self.create_node({'description': '', + 'project': self.hdri_proj_id, + 'node_type': 'hdri', + 'user': user_id, + 'properties': {'status': 'published', + 'tags': [], + 'order': 0, + 'categories': '', + 'files': ''}, + 'name': 'HDRi test node'} + ) + + self.create_node({'description': '', + 'project': self.tex_proj_id, + 'node_type': 'group_texture', + 'user': user_id, + 'properties': {'status': 'published', + 'tags': [], + 'order': 0, + 'categories': '', + 'files': ''}, + 'name': 'Group texture test node'} + ) + + def test_blender_cloud_addon_version(self): + from application.modules.blender_cloud import blender_cloud_addon_version + + # Three-digit version + with self.app.test_request_context(headers={'Blender-Cloud-Addon': '1.3.3'}): + self.assertEqual((1, 3, 3), blender_cloud_addon_version()) + + # Two-digit version + with self.app.test_request_context(headers={'Blender-Cloud-Addon': '1.5'}): + self.assertEqual((1, 5), blender_cloud_addon_version()) + + # No version + with self.app.test_request_context(): + self.assertEqual(None, blender_cloud_addon_version()) + + # Malformed version + with self.app.test_request_context(headers={'Blender-Cloud-Addon': 'je moeder'}): + self.assertRaises(wz_exceptions.BadRequest, blender_cloud_addon_version) + + def test_hdri_library__no_bcloud_version(self): + resp = self.get('/bcloud/texture-libraries', auth_token='token') + libs = resp.json()['_items'] + library_project_ids = {proj['_id'] for proj in libs} + + self.assertNotIn(unicode(self.hdri_proj_id), library_project_ids) + self.assertIn(unicode(self.tex_proj_id), library_project_ids) + + def test_hdri_library__old_bcloud_addon(self): + resp = self.get('/bcloud/texture-libraries', + auth_token='token', + headers={'Blender-Cloud-Addon': '1.3.3'}) + libs = resp.json()['_items'] + library_project_ids = {proj['_id'] for proj in libs} + self.assertNotIn(unicode(self.hdri_proj_id), library_project_ids) + self.assertIn(unicode(self.tex_proj_id), library_project_ids) + + def test_hdri_library__new_bcloud_addon(self): + resp = self.get('/bcloud/texture-libraries', + auth_token='token', + headers={'Blender-Cloud-Addon': '1.4.0'}) + libs = resp.json()['_items'] + library_project_ids = {proj['_id'] for proj in libs} + self.assertNotIn(unicode(self.hdri_proj_id), library_project_ids) + self.assertIn(unicode(self.tex_proj_id), library_project_ids)