Loading resources/management/commands/loadfixtures.py +0 −5 Original line number Diff line number Diff line Loading @@ -45,10 +45,6 @@ def set_base_permissions(): Permission.objects.get_or_create(label="file.upload") def set_root(): Collection.objects.get_or_create(title="root") def set_file_types(): for title in ["text", "numeric", "json", "xml", "integer", "float"]: MetadataType.objects.get_or_create(title=title) Loading Loading @@ -114,7 +110,6 @@ def set_basic_vocabularies_metas(project: Project): def load_fixtures(*args): set_root() set_base_permissions() set_file_types() Loading rpc/const.py +2 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ NO_SUCH_COLLECTION = "no such collection" NO_SUCH_METADATASET = "no such metadatas set" NO_SUCH_PROJECT = "no such project" NO_SUCH_RESOURCE = "no such resource" NO_PROJECT_ROOT = "project has no root" PERM_COLLECTION_CREATE = "collection.create" PERM_COLLECTION_DELETE = "collection.delete" PERM_COLLECTION_PUBLIC_ONLY = "collection.public_only" Loading @@ -33,3 +34,4 @@ SEARCH_TERMS_LIMIT = 10 SUPERUSER_NEEDED = "only for superusers" TOO_MANY_SEARCH_TERMS = "Too many search terms." WRONG_ARGUMENT = "wrong argument" NO_FOOTGUNS = "Can't shoot your own foot" rpc/methods.py +43 −98 Original line number Diff line number Diff line Loading @@ -237,20 +237,18 @@ def metadata(user: User, metadata_id: int) -> Union[Dict, None]: @_rpc_groups(["Collections"]) def collections( user: User, parent_id: int = None, parent_id: int, recursive: bool = False, limit_from: int = 0, limit_to: int = 2000, flat_list: bool = False, only_published: bool = False, project_id: int = None, order_by: str = "title", only_deleted_items: bool = False, ) -> List[Dict]: """ Return the user's collections under the parent collection specified by 'parent_id'. If 'parent_id' is null, will return collections available at root. If 'recursive' is true, will specified by 'parent_id'. If 'recursive' is true, will return all the descendants recursively in the 'children' key. If recursive is false, 'children' is null. If flat_list is True, collections are returned as a flat list and parent_id is ignored. Loading Loading @@ -290,12 +288,7 @@ def collections( WRONG_ARGUMENT + ": order_by must be one of {}".format(", ".join(available_order_by)) ) if not parent_id and not project_id: raise ServiceException(NEED_PROJECT_ID_IF_PARENT_ID_NULL) try: if parent_id is None: parent = Collection.objects.get(title=ROOT_COLLECTION_TITLE, parent_id=None) else: parent = Collection.objects.get( pk=parent_id, deleted_at__isnull=not only_deleted_items, Loading Loading @@ -345,9 +338,7 @@ def collections( @_rpc_groups(["Collections"]) def collection( user: User, collection_id: int = None, project_id: int = None ) -> Union[Dict, None]: def collection(user: User, collection_id: int) -> Union[Dict, None]: """ Get a particular collection given its id. Loading @@ -366,42 +357,12 @@ def collection( "tags": [], } ``` if collection id is null, will only return the number of collections at root for the given project_id: ``` { "id": None, "title": "root", "children_count": 25, } ``` """ if not collection_id and not project_id: raise ServiceException(NEED_COLLECTION_ID_OR_PROJECT_ID) # Special case, fast count of children collections if root. # No other key to speed up. if not collection_id: parent = Collection.objects.get(title=ROOT_COLLECTION_TITLE, parent_id=None) data = { "id": None, "title": ROOT_COLLECTION_TITLE, "children_count": Collection.objects.filter( parent=parent, deleted_at__isnull=True, project_id=project_id, project__projectaccess__user=user, project__projectaccess__role__permissions__label=PERM_COLLECTION_READ, ).count(), } return data query_set: Union[Iterator[Collection], QuerySet] = Collection.objects.filter( collection_instance = Collection.objects.filter( pk=collection_id, deleted_at__isnull=True ) collection_instance = query_set.first() ).first() if not collection_instance: return None raise ServiceException(NO_SUCH_COLLECTION) public_only = _user_has_permission( user, collection_instance.project, PERM_COLLECTION_PUBLIC_ONLY ) Loading @@ -413,13 +374,9 @@ def collection( @_rpc_groups(["Collections"]) def add_collection( user: User, title: str, parent_id: int = None, project_id: int = None ) -> Union[Dict, None]: def add_collection(user: User, title: str, parent_id: int) -> Union[Dict, None]: """ Create a new collection based on 'title'. If 'parent_id' is set, will create new collection as child of parent. Otherwise, project_id is needed to create the new collection at the project's root. Create a new collection based on 'title' and parent_id Returns either the serialized new collection of null if parent does not exist. Loading @@ -443,17 +400,6 @@ def add_collection( } ``` """ if parent_id is None and project_id is None: raise ServiceException(NEED_PROJECT_ID_OR_PARENT_ID) if parent_id and project_id: raise ServiceException(NEED_PROJECT_ID_OR_PARENT_ID) if project_id: # no parent available, add collection at project root _check_project_permission(user, project_id, PERM_COLLECTION_CREATE) parent = Collection.objects.filter( title=ROOT_COLLECTION_TITLE, parent_id=None ).first() else: # fetch parent, check parent's project add access parent = Collection.objects.filter( pk=parent_id, Loading @@ -462,12 +408,12 @@ def add_collection( project__projectaccess__role__permissions__label=PERM_COLLECTION_READ, ).first() if not parent: return None raise ServiceException(NO_SUCH_COLLECTION) else: _check_project_permission(user, parent.project, PERM_COLLECTION_CREATE) collection_instance, created = Collection.objects.get_or_create( title=title, parent=parent, project_id=project_id or parent.project.pk title=title, parent=parent, project_id=parent.project.pk ) # collection was previously soft-deleted, reactivate it. if collection_instance.deleted_at: Loading Loading @@ -544,7 +490,7 @@ def add_collection_from_path(user: User, path: str, project_id: int) -> List[Dic raise ServiceException(NO_SUCH_PROJECT) _check_project_permission(user, project, PERM_COLLECTION_CREATE) hierarchy = [] previous_dir = Collection.objects.get(title=ROOT_COLLECTION_TITLE, parent=None) previous_dir = Collection.objects.get(parent=None, project=project) for dir_name in path.split("/"): # force ascii representation of unicode strings dir_name = unidecode.unidecode(dir_name.strip()) Loading Loading @@ -1714,18 +1660,16 @@ def unpublish_collection(user: User, collection_id: int) -> bool: @_rpc_groups(["Collections"]) def move_collection( user: User, child_collection_id: int, parent_collection_id: int = None user: User, child_collection_id: int, parent_collection_id: int ) -> bool: """ Move a collection from a parent to another. Will return false in the following cases: Will raise ServiceException in the following cases: - 'child_collection_id' and 'parent_collection_id' are equal - parent collection does not exist - parent collection is a descendant of child collection If 'parent_collection_id' is null, collection is moved to the root. """ if child_collection_id == parent_collection_id: # no loop ! return False Loading @@ -1734,21 +1678,16 @@ def move_collection( ) child_collection_instance: Collection = query_set.first() if not child_collection_instance: return False raise ServiceException(NO_SUCH_COLLECTION) if parent_collection_id is None: query_set: Union[Iterator[Collection], QuerySet] = Collection.objects.filter( title=ROOT_COLLECTION_TITLE, parent_id=None ) else: query_set: Union[Iterator[Collection], QuerySet] = Collection.objects.filter( pk=parent_collection_id, deleted_at__isnull=True ) parent_collection_instance: Collection = query_set.first() if not parent_collection_instance: return False raise ServiceException(NO_SUCH_COLLECTION) if parent_collection_instance in child_collection_instance.descendants(): return False # don't cut the branch ! raise ServiceException(NO_FOOTGUNS) _check_project_permission( user, parent_collection_instance.project, PERM_COLLECTION_READ Loading Loading @@ -2261,6 +2200,7 @@ def project_stats(user: User, project_id: int) -> dict: """ Get infos from given project: - id of project collection root - number of descendants - number of descendant resources - number of resources Loading @@ -2268,10 +2208,15 @@ def project_stats(user: User, project_id: int) -> dict: """ try: project = Project.objects.get(pk=project_id) project_root = Collection.objects.filter( project=project, parent__isnull=True ).first() if not project_root: raise ServiceException(NO_PROJECT_ROOT) _check_project_permission(user, project, PERM_COLLECTION_READ) _check_project_permission(user, project, PERM_RESOURCE_READ) root = Collection.objects.get(title=ROOT_COLLECTION_TITLE, parent_id=None) return { "project_root_collection_id": project_root.pk, "descendants_count": Collection.objects.filter( project=project, deleted_at__isnull=True ).count(), Loading @@ -2279,7 +2224,7 @@ def project_stats(user: User, project_id: int) -> dict: ptr_project=project, deleted_at__isnull=True ).count(), "children_count": Collection.objects.filter( project=project, deleted_at__isnull=True, parent=root project=project, deleted_at__isnull=True, parent=project_root ).count(), "resources_count": Resource.objects.filter( ptr_project=project, Loading rpc/serializers.py +1 −3 Original line number Diff line number Diff line Loading @@ -85,9 +85,7 @@ def collection( .count(), # "descendants_count": collection_instance.descendants_count(), # "descendants_resources_count": collection_instance.descendants_resources_count(), "parent": collection_instance.parent_id if collection_instance.parent.parent_id else None, "parent": collection_instance.parent_id, "children": None, "project_id": collection_instance.project_id, "metas": [], Loading rpc/tests.py +16 −5 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ from rpc.methods import ServiceException, SUPERUSER_NEEDED class ServiceTestCase(TestCase): def setUp(self): # load file types, set collections root, create permissions # load file types, create permissions load_fixtures() self.test_user = User.objects.create(username="basic_user") Loading @@ -23,6 +23,9 @@ class ServiceTestCase(TestCase): self.test_project = models.Project.objects.create( label="projet test", description="projet test" ) self.test_project_root_collection = models.Collection.objects.create( title="root", parent_id=None, project=self.test_project ) self.test_metadataset = models.MetadataSet.objects.create( title="test metadata set", project=self.test_project ) Loading Loading @@ -59,10 +62,12 @@ class ServiceTestCase(TestCase): def test_add_collection(self): collection = methods.add_collection( self.test_user, "test_collection", project_id=self.test_project.pk self.test_user, "test_collection", parent_id=self.test_project_root_collection.pk, ) collections = methods.collections( self.test_user, project_id=self.test_project.pk self.test_user, parent_id=self.test_project_root_collection.pk ) self.assertEqual(len(collections), 1) Loading @@ -88,11 +93,17 @@ class ServiceTestCase(TestCase): def test_resources_bad_order_by(self): collection = methods.add_collection( self.test_user, "test_collection", project_id=self.test_project.pk self.test_user, "test_collection", parent_id=self.test_project_root_collection.pk, ) with self.assertRaises(ServiceException): methods.resources(self.test_user, collection["id"], order_by="pouet") def test_collections_bad_order_by(self): with self.assertRaises(ServiceException): methods.collections(self.test_user, order_by="pouet") methods.collections( self.test_user, parent_id=self.test_project_root_collection.pk, order_by="pouet", ) Loading
resources/management/commands/loadfixtures.py +0 −5 Original line number Diff line number Diff line Loading @@ -45,10 +45,6 @@ def set_base_permissions(): Permission.objects.get_or_create(label="file.upload") def set_root(): Collection.objects.get_or_create(title="root") def set_file_types(): for title in ["text", "numeric", "json", "xml", "integer", "float"]: MetadataType.objects.get_or_create(title=title) Loading Loading @@ -114,7 +110,6 @@ def set_basic_vocabularies_metas(project: Project): def load_fixtures(*args): set_root() set_base_permissions() set_file_types() Loading
rpc/const.py +2 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ NO_SUCH_COLLECTION = "no such collection" NO_SUCH_METADATASET = "no such metadatas set" NO_SUCH_PROJECT = "no such project" NO_SUCH_RESOURCE = "no such resource" NO_PROJECT_ROOT = "project has no root" PERM_COLLECTION_CREATE = "collection.create" PERM_COLLECTION_DELETE = "collection.delete" PERM_COLLECTION_PUBLIC_ONLY = "collection.public_only" Loading @@ -33,3 +34,4 @@ SEARCH_TERMS_LIMIT = 10 SUPERUSER_NEEDED = "only for superusers" TOO_MANY_SEARCH_TERMS = "Too many search terms." WRONG_ARGUMENT = "wrong argument" NO_FOOTGUNS = "Can't shoot your own foot"
rpc/methods.py +43 −98 Original line number Diff line number Diff line Loading @@ -237,20 +237,18 @@ def metadata(user: User, metadata_id: int) -> Union[Dict, None]: @_rpc_groups(["Collections"]) def collections( user: User, parent_id: int = None, parent_id: int, recursive: bool = False, limit_from: int = 0, limit_to: int = 2000, flat_list: bool = False, only_published: bool = False, project_id: int = None, order_by: str = "title", only_deleted_items: bool = False, ) -> List[Dict]: """ Return the user's collections under the parent collection specified by 'parent_id'. If 'parent_id' is null, will return collections available at root. If 'recursive' is true, will specified by 'parent_id'. If 'recursive' is true, will return all the descendants recursively in the 'children' key. If recursive is false, 'children' is null. If flat_list is True, collections are returned as a flat list and parent_id is ignored. Loading Loading @@ -290,12 +288,7 @@ def collections( WRONG_ARGUMENT + ": order_by must be one of {}".format(", ".join(available_order_by)) ) if not parent_id and not project_id: raise ServiceException(NEED_PROJECT_ID_IF_PARENT_ID_NULL) try: if parent_id is None: parent = Collection.objects.get(title=ROOT_COLLECTION_TITLE, parent_id=None) else: parent = Collection.objects.get( pk=parent_id, deleted_at__isnull=not only_deleted_items, Loading Loading @@ -345,9 +338,7 @@ def collections( @_rpc_groups(["Collections"]) def collection( user: User, collection_id: int = None, project_id: int = None ) -> Union[Dict, None]: def collection(user: User, collection_id: int) -> Union[Dict, None]: """ Get a particular collection given its id. Loading @@ -366,42 +357,12 @@ def collection( "tags": [], } ``` if collection id is null, will only return the number of collections at root for the given project_id: ``` { "id": None, "title": "root", "children_count": 25, } ``` """ if not collection_id and not project_id: raise ServiceException(NEED_COLLECTION_ID_OR_PROJECT_ID) # Special case, fast count of children collections if root. # No other key to speed up. if not collection_id: parent = Collection.objects.get(title=ROOT_COLLECTION_TITLE, parent_id=None) data = { "id": None, "title": ROOT_COLLECTION_TITLE, "children_count": Collection.objects.filter( parent=parent, deleted_at__isnull=True, project_id=project_id, project__projectaccess__user=user, project__projectaccess__role__permissions__label=PERM_COLLECTION_READ, ).count(), } return data query_set: Union[Iterator[Collection], QuerySet] = Collection.objects.filter( collection_instance = Collection.objects.filter( pk=collection_id, deleted_at__isnull=True ) collection_instance = query_set.first() ).first() if not collection_instance: return None raise ServiceException(NO_SUCH_COLLECTION) public_only = _user_has_permission( user, collection_instance.project, PERM_COLLECTION_PUBLIC_ONLY ) Loading @@ -413,13 +374,9 @@ def collection( @_rpc_groups(["Collections"]) def add_collection( user: User, title: str, parent_id: int = None, project_id: int = None ) -> Union[Dict, None]: def add_collection(user: User, title: str, parent_id: int) -> Union[Dict, None]: """ Create a new collection based on 'title'. If 'parent_id' is set, will create new collection as child of parent. Otherwise, project_id is needed to create the new collection at the project's root. Create a new collection based on 'title' and parent_id Returns either the serialized new collection of null if parent does not exist. Loading @@ -443,17 +400,6 @@ def add_collection( } ``` """ if parent_id is None and project_id is None: raise ServiceException(NEED_PROJECT_ID_OR_PARENT_ID) if parent_id and project_id: raise ServiceException(NEED_PROJECT_ID_OR_PARENT_ID) if project_id: # no parent available, add collection at project root _check_project_permission(user, project_id, PERM_COLLECTION_CREATE) parent = Collection.objects.filter( title=ROOT_COLLECTION_TITLE, parent_id=None ).first() else: # fetch parent, check parent's project add access parent = Collection.objects.filter( pk=parent_id, Loading @@ -462,12 +408,12 @@ def add_collection( project__projectaccess__role__permissions__label=PERM_COLLECTION_READ, ).first() if not parent: return None raise ServiceException(NO_SUCH_COLLECTION) else: _check_project_permission(user, parent.project, PERM_COLLECTION_CREATE) collection_instance, created = Collection.objects.get_or_create( title=title, parent=parent, project_id=project_id or parent.project.pk title=title, parent=parent, project_id=parent.project.pk ) # collection was previously soft-deleted, reactivate it. if collection_instance.deleted_at: Loading Loading @@ -544,7 +490,7 @@ def add_collection_from_path(user: User, path: str, project_id: int) -> List[Dic raise ServiceException(NO_SUCH_PROJECT) _check_project_permission(user, project, PERM_COLLECTION_CREATE) hierarchy = [] previous_dir = Collection.objects.get(title=ROOT_COLLECTION_TITLE, parent=None) previous_dir = Collection.objects.get(parent=None, project=project) for dir_name in path.split("/"): # force ascii representation of unicode strings dir_name = unidecode.unidecode(dir_name.strip()) Loading Loading @@ -1714,18 +1660,16 @@ def unpublish_collection(user: User, collection_id: int) -> bool: @_rpc_groups(["Collections"]) def move_collection( user: User, child_collection_id: int, parent_collection_id: int = None user: User, child_collection_id: int, parent_collection_id: int ) -> bool: """ Move a collection from a parent to another. Will return false in the following cases: Will raise ServiceException in the following cases: - 'child_collection_id' and 'parent_collection_id' are equal - parent collection does not exist - parent collection is a descendant of child collection If 'parent_collection_id' is null, collection is moved to the root. """ if child_collection_id == parent_collection_id: # no loop ! return False Loading @@ -1734,21 +1678,16 @@ def move_collection( ) child_collection_instance: Collection = query_set.first() if not child_collection_instance: return False raise ServiceException(NO_SUCH_COLLECTION) if parent_collection_id is None: query_set: Union[Iterator[Collection], QuerySet] = Collection.objects.filter( title=ROOT_COLLECTION_TITLE, parent_id=None ) else: query_set: Union[Iterator[Collection], QuerySet] = Collection.objects.filter( pk=parent_collection_id, deleted_at__isnull=True ) parent_collection_instance: Collection = query_set.first() if not parent_collection_instance: return False raise ServiceException(NO_SUCH_COLLECTION) if parent_collection_instance in child_collection_instance.descendants(): return False # don't cut the branch ! raise ServiceException(NO_FOOTGUNS) _check_project_permission( user, parent_collection_instance.project, PERM_COLLECTION_READ Loading Loading @@ -2261,6 +2200,7 @@ def project_stats(user: User, project_id: int) -> dict: """ Get infos from given project: - id of project collection root - number of descendants - number of descendant resources - number of resources Loading @@ -2268,10 +2208,15 @@ def project_stats(user: User, project_id: int) -> dict: """ try: project = Project.objects.get(pk=project_id) project_root = Collection.objects.filter( project=project, parent__isnull=True ).first() if not project_root: raise ServiceException(NO_PROJECT_ROOT) _check_project_permission(user, project, PERM_COLLECTION_READ) _check_project_permission(user, project, PERM_RESOURCE_READ) root = Collection.objects.get(title=ROOT_COLLECTION_TITLE, parent_id=None) return { "project_root_collection_id": project_root.pk, "descendants_count": Collection.objects.filter( project=project, deleted_at__isnull=True ).count(), Loading @@ -2279,7 +2224,7 @@ def project_stats(user: User, project_id: int) -> dict: ptr_project=project, deleted_at__isnull=True ).count(), "children_count": Collection.objects.filter( project=project, deleted_at__isnull=True, parent=root project=project, deleted_at__isnull=True, parent=project_root ).count(), "resources_count": Resource.objects.filter( ptr_project=project, Loading
rpc/serializers.py +1 −3 Original line number Diff line number Diff line Loading @@ -85,9 +85,7 @@ def collection( .count(), # "descendants_count": collection_instance.descendants_count(), # "descendants_resources_count": collection_instance.descendants_resources_count(), "parent": collection_instance.parent_id if collection_instance.parent.parent_id else None, "parent": collection_instance.parent_id, "children": None, "project_id": collection_instance.project_id, "metas": [], Loading
rpc/tests.py +16 −5 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ from rpc.methods import ServiceException, SUPERUSER_NEEDED class ServiceTestCase(TestCase): def setUp(self): # load file types, set collections root, create permissions # load file types, create permissions load_fixtures() self.test_user = User.objects.create(username="basic_user") Loading @@ -23,6 +23,9 @@ class ServiceTestCase(TestCase): self.test_project = models.Project.objects.create( label="projet test", description="projet test" ) self.test_project_root_collection = models.Collection.objects.create( title="root", parent_id=None, project=self.test_project ) self.test_metadataset = models.MetadataSet.objects.create( title="test metadata set", project=self.test_project ) Loading Loading @@ -59,10 +62,12 @@ class ServiceTestCase(TestCase): def test_add_collection(self): collection = methods.add_collection( self.test_user, "test_collection", project_id=self.test_project.pk self.test_user, "test_collection", parent_id=self.test_project_root_collection.pk, ) collections = methods.collections( self.test_user, project_id=self.test_project.pk self.test_user, parent_id=self.test_project_root_collection.pk ) self.assertEqual(len(collections), 1) Loading @@ -88,11 +93,17 @@ class ServiceTestCase(TestCase): def test_resources_bad_order_by(self): collection = methods.add_collection( self.test_user, "test_collection", project_id=self.test_project.pk self.test_user, "test_collection", parent_id=self.test_project_root_collection.pk, ) with self.assertRaises(ServiceException): methods.resources(self.test_user, collection["id"], order_by="pouet") def test_collections_bad_order_by(self): with self.assertRaises(ServiceException): methods.collections(self.test_user, order_by="pouet") methods.collections( self.test_user, parent_id=self.test_project_root_collection.pk, order_by="pouet", )