initial checkin of changes for branch

This commit is contained in:
Kelly McDonald 2021-02-24 12:05:06 -05:00
parent ff8a44a7b1
commit 3dbe39c6fe
12 changed files with 299 additions and 7 deletions

View File

@ -6,7 +6,8 @@ from sqlalchemy.exc import IntegrityError
from crc import session
from crc.api.common import ApiError, ApiErrorSchema
from crc.models.protocol_builder import ProtocolBuilderStatus
from crc.models.study import Study, StudyEvent, StudyEventType, StudyModel, StudySchema, StudyForUpdateSchema, StudyStatus
from crc.models.study import Study, StudyEvent, StudyEventType, StudyModel, StudySchema, StudyForUpdateSchema, \
StudyStatus
from crc.services.study_service import StudyService
from crc.services.user_service import UserService
from crc.services.workflow_service import WorkflowService

View File

@ -34,6 +34,7 @@ class StudyEventType(enum.Enum):
automatic = 'automatic'
class StudyModel(db.Model):
__tablename__ = 'study'
id = db.Column(db.Integer, primary_key=True)
@ -50,7 +51,7 @@ class StudyModel(db.Model):
requirements = db.Column(db.ARRAY(db.Integer), nullable=True)
on_hold = db.Column(db.Boolean, default=False)
enrollment_date = db.Column(db.DateTime(timezone=True), nullable=True)
# events = db.relationship("TaskEventModel")
#events = db.relationship("TaskEventModel")
events_history = db.relationship("StudyEvent", cascade="all, delete, delete-orphan")
def update_from_protocol_builder(self, pbs: ProtocolBuilderStudy):
@ -62,6 +63,21 @@ class StudyModel(db.Model):
self.irb_status = IrbStatus.incomplete_in_protocol_builder
class StudyAssociated(db.Model):
"""
This model allows us to associate people with a study, and optionally
give them edit access. This allows us to create a table with PI, D_CH, etc.
and give access to people other than the study owner.
Task_Events will still work as they have previously
"""
__tablename__ = 'study_associated_user'
id = db.Column(db.Integer, primary_key=True)
study_id = db.Column(db.Integer, db.ForeignKey(StudyModel.id), nullable=False)
uid = db.Column(db.String, db.ForeignKey('ldap_model.uid'), nullable=False)
role = db.Column(db.String, nullable=True)
send_email = db.Column(db.Boolean, nullable=True)
access = db.Column(db.Boolean, nullable=True)
class StudyEvent(db.Model):
__tablename__ = 'study_event'
id = db.Column(db.Integer, primary_key=True)

View File

@ -0,0 +1,31 @@
from crc.api.common import ApiError
from crc.scripts.script import Script
from crc.services.study_service import StudyService
class GetStudyAssociates(Script):
def get_description(self):
return """
Returns person assocated with study or an error if one is not associated.
example : get_study_associate('sbp3ey') => {'uid':'sbp3ey','role':'Unicorn Herder', 'send_email': False,
'access':True}
"""
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
if len(args)<1:
return False
return True
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
if len(args<1):
raise ApiError('no_user_id_specified', 'A uva uid is the sole argument to this function')
if not isinstance([0],type('')):
raise ApiError('argument_should_be_string', 'A uva uid is always a string, please check type')
return StudyService.get_study_associate(study_id=study_id,uid=args[0])

View File

@ -0,0 +1,29 @@
from crc.api.common import ApiError
from crc.scripts.script import Script
from crc.services.study_service import StudyService
class GetStudyAssociates(Script):
argument_error_message = "You must supply at least one argument to the " \
"update_study_associates task, an array of objects in the form " \
"{'uid':'someid', 'role': 'text', 'send_email: 'boolean', " \
"'access':'boolean'} "
def get_description(self):
return """
Returns all people associated with the study - Will always return the study owner as assocated
example : get_study_associates() => [{'uid':'sbp3ey','role':'Unicorn Herder', 'send_email': False, 'access':True}]
"""
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
return True
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
return StudyService.get_study_associates(study_id)

View File

@ -35,8 +35,17 @@ class Script(object):
updating the task data.
"""
def make_closure(subclass,task,study_id,workflow_id):
"""
yes - this is black magic
Essentially, we want to build a list of all of the submodules (i.e. email, user_data_get, etc)
and a function that is assocated with them.
This basically creates an Instance of the class and returns a function that calls do_task
on the instance of that class.
the next for x in range, then grabs the name of the module and associates it with the function
that we created.
"""
instance = subclass()
return lambda *a : subclass.do_task(instance,task,study_id,workflow_id,*a)
return lambda *ar,**kw: subclass.do_task(instance,task,study_id,workflow_id,*ar,**kw)
execlist = {}
subclasses = Script.get_all_subclasses()
for x in range(len(subclasses)):

View File

@ -4,7 +4,7 @@ from crc.scripts.script import Script
class StudyDataSet(Script,DataStoreBase):
def get_description(self):
return """Sets study data from the data store."""
return """Sets study data from the data store. Takes two positional arguments key and value"""
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
self.set_validate_common(study_id,

View File

@ -0,0 +1,38 @@
from crc.api.common import ApiError
from crc.scripts.script import Script
from crc.services.study_service import StudyService
class UpdateStudyAssociates(Script):
argument_error_message = "You must supply at least one argument to the " \
"update_study_associates task, an array of objects in the form " \
"{'uid':'someid', 'role': 'text', 'send_email: 'boolean', " \
"'access':'boolean'} "
def get_description(self):
return """
Allows you to associate other users with a study - only 'uid' is a required keyword argument
An empty list will delete the existing Associated list (except owner)
The UID will be validated vs ldap and will raise an error if the uva_uid is not found. This will replace any
association already in place for this user.
example : update_study_associate(uid='sbp3ey',role='Unicorn Herder',send_email=False, access=True)
"""
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
if kwargs.get('uid') is None:
raise ApiError('uid_is_required_argument','a valid keyword argument of uid is required, it should be the '
'uva uid for this user')
return True
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
return StudyService.update_study_associate(study_id=study_id,**kwargs)

View File

@ -0,0 +1,45 @@
from crc.api.common import ApiError
from crc.scripts.script import Script
from crc.services.study_service import StudyService
class UpdateStudyAssociates(Script):
argument_error_message = "You must supply at least one argument to the " \
"update_study_associates task, an array of objects in the form " \
"{'uid':'someid', 'role': 'text', 'send_email: 'boolean', " \
"'access':'boolean'} "
def get_description(self):
return """
Allows you to associate other users with a study - only 'uid' is required in the
incoming dictionary, but will be useless without other information - all values will default to
false or blank
An empty list will delete the existing Associated list (except owner)
Each UID will be validated vs ldap and will raise an error if the uva_uid is not found. This supplied list will replace
any
associations already in place.
example : update_study_associates([{'uid':'sbp3ey','role':'Unicorn Herder', 'send_email': False, 'access':True}])
"""
def validate_arg(self,arg):
if not isinstance(arg,list):
raise ApiError("invalid parameter", "This function is expecting a list of dictionaries")
if not len(arg) > 0 and not isinstance(arg[0],dict):
raise ApiError("invalid paramemter","This function is expecting a list of dictionaries")
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
items = args[0]
self.validate_arg(items)
return all([x.get('uid',False) for x in items])
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
access_list = args[0]
self.validate_arg(access_list)
return StudyService.update_study_associates(study_id,access_list)

View File

@ -6,7 +6,7 @@ from crc.scripts.script import Script
class UserDataGet(Script, DataStoreBase):
def get_description(self):
return """Gets user data from the data store."""
return """Gets user data from the data store - takes only one argument 'key' """
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
self.do_task(task, study_id, workflow_id, *args, **kwargs)

View File

@ -6,7 +6,9 @@ from crc.scripts.script import Script
class UserDataSet(Script,DataStoreBase):
def get_description(self):
return """Sets user data to the data store."""
return """Sets user data to the data store these are positional arguments key and value.
example: user_data_set('mykey','myvalue')
"""
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
self.set_validate_common(None,

View File

@ -48,6 +48,13 @@ class LdapService(object):
LdapService.conn = conn
return LdapService.conn
@staticmethod
def user_exists(uva_uid):
try:
x = LdapService.user_info(uva_uid)
except:
return False
return True
@staticmethod
def user_info(uva_uid):

View File

@ -14,7 +14,7 @@ from crc.models.file import FileDataModel, FileModel, FileModelSchema, File, Loo
from crc.models.ldap import LdapSchema
from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStatus
from crc.models.study import StudyModel, Study, StudyStatus, Category, WorkflowMetadata, StudyEventType, StudyEvent, \
IrbStatus
IrbStatus, StudyAssociated
from crc.models.task_event import TaskEventModel, TaskEvent
from crc.models.workflow import WorkflowSpecCategoryModel, WorkflowModel, WorkflowSpecModel, WorkflowState, \
WorkflowStatus, WorkflowSpecDependencyFile
@ -77,6 +77,120 @@ class StudyService(object):
return study
@staticmethod
def get_study_associate(study_id = None, uid=None):
"""
gets all associated people for a study from the database
"""
study = db.session.query(StudyModel).filter(StudyModel.id == study_id).first()
if study is None:
raise ApiError('study_not_found', 'No study found with id = %d' % study_id)
if uid is None:
raise ApiError('uid not specified','A valid uva uid is required for this function')
if uid == study.user_uid:
return {'uid': ownerid, 'role': 'owner', 'send_email': True, 'access': True}
person = db.session.query(StudyAssociated).filter((StudyAssociated.study_id == study_id)&(
StudyAssociated.uid == uid))
if person:
newAssociate = {'uid',person.uid}
newAssociate['role'] = person.role
newAssociate['send_email'] = person.send_email
newAssociate['access'] = person.access
return newAssociate
raise ApiError('uid_not_associated_with_study',"user id %s was not assocated with study number %d"%(uid,
study_id))
@staticmethod
def get_study_associates(study_id):
"""
gets all associated people for a study from the database
"""
study = db.session.query(StudyModel).filter(StudyModel.id == study_id).first()
if study is None:
raise ApiError('study_not_found','No study found with id = %d'%study_id)
ownerid = study.user_uid
people_list = [{'uid':ownerid,'role':'owner','send_email':True,'access':True}]
people = db.session.query(StudyAssociated).filter(StudyAssociated.study_id == study_id)
for person in people:
newAssociate = {'uid',person.uid}
newAssociate['role'] = person.role
newAssociate['send_email'] = person.send_email
newAssociate['access'] = person.access
people_list.append(newAssociate)
return people_list
@staticmethod
def update_study_associates(study_id,associates):
"""
updates the list of associates in the database for a study_id and a list
of dicts that contains associates
"""
if study_id is None:
raise ApiError('study_id not specified', "This function requires the study_id parameter")
for person in associates:
if not LdapService.user_exists(person.get('uid','impossible_uid')):
if person.get('uid','impossible_uid') == 'impossible_uid':
raise ApiError('associate with no uid','One of the associates passed as a parameter doesnt have '
'a uid specified')
raise ApiError('trying_to_grant_access_to_user_not_found_in_ldap',"You are trying to grant access to "
"%s, but that user was not found in "
"ldap "
"- please check to ensure it is a "
"valid uva uid"%person.get('uid'))
study = db.session.query(StudyModel).filter(StudyModel.id == study_id).first()
if study is None:
raise ApiError('study_id not found', "A study with id# %d was not found"%study_id)
db.session.query(StudyAssociated).filter(StudyAssociated.study_id == study_id).delete()
for person in associates:
newAssociate = StudyAssociated()
newAssociate.uid = person['uid']
newAssociate.role = person.get('role', None)
newAssociate.send_email = person.get('send_email', False)
newAssociate.access = person.get('access',False)
db.session.add(newAssociate)
db.commit()
@staticmethod
def update_study_associate(study_id=None,uid=None,role="",send_email=False,access=False):
if study_id is None:
raise ApiError('study_id not specified', "This function requires the study_id parameter")
if uid is None:
raise ApiError('uid not specified', "This function requires a uva uid parameter")
if not LdapService.user_exists(uid):
raise ApiError('trying_to_grant_access_to_user_not_found_in_ldap',"You are trying to grant access to "
"%s but they were not found in ldap "
"- please check to ensure it is a "
"valid uva uid"%uid)
study = db.session.query(StudyModel).filter(StudyModel.id == study_id).first()
if study is None:
raise ApiError('study_id not found', "A study with id# %d was not found"%study_id)
db.session.query(StudyAssociated).filter((StudyAssociated.study_id == study_id)&(StudyAssociated.uid ==
uid) ).delete()
newAssociate = StudyAssociated()
newAssociate.uid = uid
newAssociate.role = role
newAssociate.send_email = send_email
newAssociate.access = access
db.session.add(newAssociate)
db.commit()
return true
@staticmethod
def delete_study(study_id):
session.query(TaskEventModel).filter_by(study_id=study_id).delete()