Skip to content

Commit 38e2060

Browse files
committed
collection patch && tests
1 parent ae93c7d commit 38e2060

File tree

5 files changed

+106
-41
lines changed

5 files changed

+106
-41
lines changed

py/API_operations/CRUD/Collections.py

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Copyright (C) 2015-2020 Picheral, Colin, Irisson (UPMC-CNRS)
44
#
55
from typing import List, Union, Optional, Tuple, Dict, Any
6-
6+
from fastapi import HTTPException
77
from API_models.crud import CreateCollectionReq, CollectionAggregatedRsp
88
from API_models.exports import TaxonomyRecast
99
from BO.Collection import CollectionBO, CollectionIDT
@@ -38,20 +38,23 @@ def create(
3838
coll_id = CollectionBO.create(self.session, req.title, req.project_ids)
3939
return coll_id
4040

41-
def _check_access(
42-
self, a_coll: CollectionBO, user_id: UserIDT
43-
) -> Optional[CollectionBO]:
41+
def update(
42+
self,
43+
current_user_id: UserIDT,
44+
collection_id: CollectionIDT,
45+
req: Dict[str, Any],
46+
):
4447
"""
45-
Quick & dirty access check by catching the exception.
48+
Update a collection.
4649
"""
47-
try:
50+
if "project_ids" in req:
4851
PermissionConsistentProjectSet(
49-
self.ro_session,
50-
a_coll.project_ids, # Need the R/W session here, as the projects MRU is written to. TODO
51-
).can_be_administered_by(user_id, update_preference=False)
52-
except AssertionError:
53-
return None
54-
return a_coll
52+
self.session, req["project_ids"]
53+
).can_be_administered_by(current_user_id)
54+
present_collection = self.query(current_user_id, collection_id, for_update=True)
55+
if present_collection is None:
56+
raise HTTPException(status_code=404, detail="Collection not found")
57+
present_collection.update(session=self.session, collection_update=req)
5558

5659
def list(
5760
self, current_user_id: UserIDT, collection_ids: Optional[str] = None
@@ -65,11 +68,11 @@ def list(
6568
for a_rec in qry:
6669
coll_bo = CollectionBO(a_rec)
6770
coll_bo._read_composing_projects()
68-
checked = self._check_access(coll_bo, current_user_id)
71+
checked = self._check_access(current_user_id, coll_bo.project_ids)
6972
if checked is None:
7073
continue
71-
checked.enrich()
72-
ret.append(checked)
74+
coll_bo.enrich()
75+
ret.append(coll_bo)
7376
return ret
7477

7578
def search(self, current_user_id: UserIDT, title: str) -> List[CollectionBO]:
@@ -78,11 +81,11 @@ def search(self, current_user_id: UserIDT, title: str) -> List[CollectionBO]:
7881
for a_rec in qry:
7982
coll_bo = CollectionBO(a_rec)
8083
coll_bo._read_composing_projects()
81-
checked = self._check_access(coll_bo, current_user_id)
84+
checked = self._check_access(current_user_id, coll_bo.project_ids)
8285
if checked is None:
8386
continue
84-
checked.enrich()
85-
ret.append(checked)
87+
coll_bo.enrich()
88+
ret.append(coll_bo)
8689
return ret
8790

8891
def query(
@@ -93,7 +96,10 @@ def query(
9396
)
9497
if ret is None:
9598
return ret
96-
return self._check_access(ret, current_user_id)
99+
check = self._check_access(current_user_id, ret.project_ids)
100+
if check is None:
101+
return None
102+
return ret
97103

98104
def query_by_title(self, title: str) -> CollectionBO:
99105
# Return a unique collection from its title
@@ -151,6 +157,21 @@ def _query_recast_for_coll(self, coll_id: CollectionIDT):
151157
)
152158
return qry
153159

160+
def _check_access(
161+
self, user_id: UserIDT, project_ids: ProjectIDListT
162+
) -> Optional[ProjectIDListT]:
163+
"""
164+
Quick & dirty access check by catching the exception.
165+
"""
166+
try:
167+
PermissionConsistentProjectSet(
168+
self.ro_session,
169+
project_ids, # Need the R/W session here, as the projects MRU is written to. TODO
170+
).can_be_administered_by(user_id, update_preference=False)
171+
except AssertionError:
172+
return None
173+
return project_ids
174+
154175
def aggregated_from_projects(
155176
self,
156177
current_user_id: UserIDT,

py/BO/Collection.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def _add_composing_projects(
121121
@staticmethod
122122
def _get_annotators_from_histo(
123123
session, project_ids: ProjectIDListT, status: Optional[int] = None
124-
) -> List[User]:
124+
) -> List[UserIDT]:
125125
pqry = session.query(User.id, User.organisation)
126126
pqry.join(User, User.id == ObjectHeader.classif_who)
127127
pqry.filter(Project.projid == any_(project_ids))
@@ -130,8 +130,7 @@ def _get_annotators_from_histo(
130130
pqry.filter(User.status == status)
131131
users: List[Any] = pqry.all()
132132
creator_user = [u.id for u in users]
133-
creator_org = [u.organisation for u in users]
134-
return creator_user, creator_org
133+
return creator_user
135134

136135
def update(
137136
self,
@@ -148,7 +147,7 @@ def update(
148147
COLLECTION_ROLE_ASSOCIATED_PERSON: "associate_organisations",
149148
},
150149
}
151-
by_role = {}
150+
by_role: Dict[str, Any] = {}
152151
print("____coll_udpate", collection_update)
153152
for key, value in collection_update.items():
154153
if key == "project_ids":
@@ -157,7 +156,14 @@ def update(
157156
# Redo sanity check & aggregation as underlying projects might have changed (or not as stated just above. lol)
158157
self.set_composing_projects(session, value)
159158
# Simple fields update
160-
if key in ["title", "short_title", "citation", "abstract", "description"]:
159+
if key in [
160+
"title",
161+
"short_title",
162+
"citation",
163+
"abstract",
164+
"description",
165+
"license",
166+
]:
161167
setattr(self._collection, key, value)
162168
if key in ["provider_user", "contact_user"] and value is not None:
163169
# Copy provider user id contact user id
@@ -333,6 +339,6 @@ class MinimalCollectionBO(BaseModel):
333339
external_id: Union[str, None] = None
334340
title: str
335341
short_title: Union[str, None] = None
336-
provider_user_id: UserIDT
337-
contact_user: Union[ContactUserBO, None] = None
342+
provider_user: UserIDT
343+
contact_user: Union[UserIDT, None] = None
338344
project_ids: ProjectIDListT

py/BO/Project.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -719,15 +719,16 @@ def in_collections(session: Session, projid: int) -> List[MinimalCollectionBO]:
719719
CollectionProject.collection_id == r.id
720720
)
721721
project_ids = [p.project_id for p in qry_proj]
722+
722723
ret.append(
723724
MinimalCollectionBO(
724-
r.id,
725-
r.external_id,
726-
r.title,
727-
r.short_title,
728-
r.provider_user_id,
729-
contact,
730-
project_ids,
725+
id=r.id,
726+
external_id=r.external_id or None,
727+
title=r.title,
728+
short_title=r.short_title or None,
729+
provider_user=r.provider_user_id,
730+
contact_user=r.contact_user_id or None,
731+
project_ids=project_ids,
731732
)
732733
)
733734
return ret

py/DB/Collection.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,19 @@ def __str__(self) -> str:
6969
COLLECTION_ROLE_INSTITUTION_CODE_PROVIDER = "P"
7070

7171

72+
class CollectionRole(Model):
73+
__tablename__ = "collection_role"
74+
""" n<->n valued (with role) relationship b/w collection and users """
75+
collection_id: int = Column(INTEGER, ForeignKey("collection.id"), primary_key=True)
76+
identity: str = Column(VARCHAR(1000), nullable=False)
77+
role: str = Column(VARCHAR(1), nullable=False, primary_key=True)
78+
# The relationships are created in Relations.py but the typing here helps IDE
79+
collection: relationship
80+
81+
def __str__(self) -> str:
82+
return "{0},{1}:{2}".format(self.collection_id, self.identity, self.role)
83+
84+
7285
class CollectionUserRole(Model):
7386
__tablename__ = "collection_user_role"
7487
""" n<->n valued (with role) relationship b/w collection and users """

py/main.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -851,7 +851,7 @@ def get_collection(
851851
responses={200: {"content": {"application/json": {"example": null}}}},
852852
)
853853
def update_collection(
854-
collection: CollectionReq = Body(...),
854+
collection: CollectionModel = Body(...),
855855
collection_id: int = Path(
856856
...,
857857
description="Internal, the unique numeric id of this collection.",
@@ -867,15 +867,39 @@ def update_collection(
867867
868868
Note: The collection is updated only if manageable.
869869
"""
870+
collection_update = collection.dict()
870871
with CollectionsService() as sce:
871872
with RightsThrower():
872-
present_collection = sce.query(current_user, collection_id, for_update=True)
873-
if present_collection is None:
874-
raise HTTPException(status_code=404, detail="Collection not found")
875-
collection_update = collection.dict(exclude_unset=True)
876-
present_collection.update(
877-
session=sce.session, collection_update=collection_update
878-
)
873+
sce.update(current_user, collection_id, collection_update)
874+
875+
876+
@app.patch(
877+
"/collections/{collection_id}",
878+
operation_id="patch_collection",
879+
tags=["collections"],
880+
responses={200: {"content": {"application/json": {"example": null}}}},
881+
)
882+
def patch_collection(
883+
collection: CollectionReq = Body(...),
884+
collection_id: int = Path(
885+
...,
886+
description="Internal, the unique numeric id of this collection.",
887+
example=1,
888+
),
889+
current_user: int = Depends(get_current_user),
890+
) -> None:
891+
"""
892+
**Partial Update of the collection**. Note that some updates are silently failing when not compatible
893+
with the composing projects.
894+
895+
**Returns NULL upon success.**
896+
897+
Note: The collection is partiallly updated only if manageable.
898+
"""
899+
collection_update = collection.dict(exclude_unset=True)
900+
with CollectionsService() as sce:
901+
with RightsThrower():
902+
sce.update(current_user, collection_id, collection_update)
879903

880904

881905
@app.put(

0 commit comments

Comments
 (0)