88from datetime import datetime , timedelta
99from enum import Enum
1010from typing import Optional , List , Dict , Set
11+ from dataclasses import dataclass , asdict
1112
1213from blinker import signal
1314from flask_login import current_user
@@ -324,7 +325,9 @@ def delete(self, removed_by: int = None):
324325 db .session .execute (
325326 upload_table .delete ().where (upload_table .c .project_id == self .id )
326327 )
327- self .access .owners = self .access .writers = self .access .readers = []
328+ self .access .owners = self .access .writers = self .access .editors = (
329+ self .access .readers
330+ ) = []
328331 access_requests = (
329332 AccessRequest .query .filter_by (project_id = self .id )
330333 .filter (AccessRequest .status .is_ (None ))
@@ -339,6 +342,7 @@ def delete(self, removed_by: int = None):
339342class ProjectRole (Enum ):
340343 OWNER = "owner"
341344 WRITER = "writer"
345+ EDITOR = "editor"
342346 READER = "reader"
343347
344348 def __gt__ (self , other ):
@@ -354,6 +358,17 @@ def __gt__(self, other):
354358 return False
355359
356360
361+ @dataclass
362+ class ProjectAccessDetail :
363+ id : int or str
364+ email : str
365+ role : str
366+ username : str
367+ name : Optional [str ]
368+ project_permission : str
369+ type : str
370+
371+
357372class ProjectAccess (db .Model ):
358373 project_id = db .Column (
359374 UUID (as_uuid = True ),
@@ -365,6 +380,7 @@ class ProjectAccess(db.Model):
365380 owners = db .Column (ARRAY (db .Integer ), server_default = "{}" )
366381 readers = db .Column (ARRAY (db .Integer ), server_default = "{}" )
367382 writers = db .Column (ARRAY (db .Integer ), server_default = "{}" )
383+ editors = db .Column (ARRAY (db .Integer ), server_default = "{}" )
368384
369385 project = db .relationship (
370386 "Project" ,
@@ -382,13 +398,15 @@ class ProjectAccess(db.Model):
382398 db .Index ("ix_project_access_owners" , owners , postgresql_using = "gin" ),
383399 db .Index ("ix_project_access_readers" , readers , postgresql_using = "gin" ),
384400 db .Index ("ix_project_access_writers" , writers , postgresql_using = "gin" ),
401+ db .Index ("ix_project_access_editors" , editors , postgresql_using = "gin" ),
385402 )
386403
387404 def __init__ (self , project , public = False ):
388405 self .project = project
389406 self .owners = [project .creator .id ]
390407 self .writers = [project .creator .id ]
391408 self .readers = [project .creator .id ]
409+ self .editors = [project .creator .id ]
392410 self .project_id = project .id
393411 self .public = public
394412
@@ -398,6 +416,8 @@ def get_role(self, user_id: int) -> Optional[ProjectRole]:
398416 return ProjectRole .OWNER
399417 elif user_id in self .writers :
400418 return ProjectRole .WRITER
419+ elif user_id in self .editors :
420+ return ProjectRole .EDITOR
401421 elif user_id in self .readers :
402422 return ProjectRole .READER
403423 else :
@@ -409,8 +429,9 @@ def _permission_attrs(role: ProjectRole) -> List[str]:
409429 # because roles do not inherit, they must be un/set explicitly in db ACLs
410430 perm_list = {
411431 ProjectRole .READER : ["readers" ],
412- ProjectRole .WRITER : ["writers" , "readers" ],
413- ProjectRole .OWNER : ["owners" , "writers" , "readers" ],
432+ ProjectRole .EDITOR : ["editors" , "readers" ],
433+ ProjectRole .WRITER : ["writers" , "editors" , "readers" ],
434+ ProjectRole .OWNER : ["owners" , "writers" , "editors" , "readers" ],
414435 }
415436 return perm_list [role ]
416437
@@ -440,7 +461,7 @@ def unset_role(self, user_id: int) -> None:
440461 def bulk_update (self , new_access : Dict ) -> Set [int ]:
441462 """From new access lists do bulk update and return ids with any change applied"""
442463 diff = set ()
443- for key in ("owners" , "writers" , "readers" ):
464+ for key in ("owners" , "writers" , "editors" , " readers" ):
444465 new_value = new_access .get (key , None )
445466 if not new_value :
446467 continue
@@ -450,7 +471,8 @@ def bulk_update(self, new_access: Dict) -> Set[int]:
450471
451472 # make sure lists are consistent (they inherit from each other)
452473 self .writers = list (set (self .writers ).union (set (self .owners )))
453- self .readers = list (set (self .readers ).union (set (self .writers )))
474+ self .editors = list (set (self .editors ).union (set (self .writers )))
475+ self .readers = list (set (self .readers ).union (set (self .editors )))
454476 return diff
455477
456478
@@ -638,18 +660,16 @@ def expire(self):
638660 def accept (self , permissions ):
639661 """Accept project access request"""
640662 project_access = self .project .access
641- readers = project_access .readers .copy ()
642- writers = project_access .writers .copy ()
643- owners = project_access .owners .copy ()
644- readers .append (self .requested_by )
645- project_access .readers = readers
646- if permissions == "write" or permissions == "owner" :
647- writers .append (self .requested_by )
648- project_access .writers = writers
649- if permissions == "owner" :
650- owners .append (self .requested_by )
651- project_access .owners = owners
663+ PERMISSION_PROJECT_ROLE = {
664+ "read" : ProjectRole .READER ,
665+ "edit" : ProjectRole .EDITOR ,
666+ "write" : ProjectRole .WRITER ,
667+ "owner" : ProjectRole .OWNER ,
668+ }
652669
670+ project_access .set_role (
671+ self .requested_by , PERMISSION_PROJECT_ROLE .get (permissions )
672+ )
653673 self .resolve (RequestStatus .ACCEPTED , current_user .id )
654674 db .session .commit ()
655675
0 commit comments