Commit 3d10180c authored by Mickaël Desfrênes's avatar Mickaël Desfrênes
Browse files

refactoring project root

parent aaea841d
Loading
Loading
Loading
Loading
+0 −5
Original line number Diff line number Diff line
@@ -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)
@@ -114,7 +110,6 @@ def set_basic_vocabularies_metas(project: Project):


def load_fixtures(*args):
    set_root()
    set_base_permissions()
    set_file_types()

+2 −0
Original line number Diff line number Diff line
@@ -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"
@@ -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"
+43 −98
Original line number Diff line number Diff line
@@ -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.
@@ -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,
@@ -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.

@@ -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
    )
@@ -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.
@@ -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,
@@ -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:
@@ -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())
@@ -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
@@ -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
@@ -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
@@ -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(),
@@ -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,
+1 −3
Original line number Diff line number Diff line
@@ -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": [],
+16 −5
Original line number Diff line number Diff line
@@ -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")
@@ -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
        )
@@ -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)

@@ -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",
            )