2020-05-22 22:25:00 +00:00
|
|
|
import enum
|
2020-05-25 21:40:24 +00:00
|
|
|
import marshmallow
|
2020-05-22 22:25:00 +00:00
|
|
|
|
|
|
|
from marshmallow import INCLUDE
|
2020-05-23 19:08:17 +00:00
|
|
|
from sqlalchemy import func
|
2020-05-22 22:25:00 +00:00
|
|
|
|
2020-05-26 17:29:24 +00:00
|
|
|
from ldap3.core.exceptions import LDAPSocketOpenError
|
|
|
|
|
2020-05-22 22:25:00 +00:00
|
|
|
from crc import db, ma
|
2020-05-25 23:30:16 +00:00
|
|
|
from crc.api.common import ApiError
|
2020-05-23 19:08:17 +00:00
|
|
|
from crc.models.file import FileModel
|
2020-05-22 22:25:00 +00:00
|
|
|
from crc.models.study import StudyModel
|
|
|
|
from crc.models.workflow import WorkflowModel
|
2020-05-25 23:30:16 +00:00
|
|
|
from crc.services.ldap_service import LdapService
|
2020-05-22 22:25:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ApprovalStatus(enum.Enum):
|
|
|
|
WAITING = "WAITING" # no one has done jack.
|
|
|
|
APPROVED = "APPROVED" # approved by the reviewer
|
|
|
|
DECLINED = "DECLINED" # rejected by the reviewer
|
|
|
|
CANCELED = "CANCELED" # The document was replaced with a new version and this review is no longer needed.
|
|
|
|
|
|
|
|
|
2020-05-23 19:08:17 +00:00
|
|
|
class ApprovalFile(db.Model):
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
file_id = db.Column(db.Integer, db.ForeignKey(FileModel.id), nullable=False)
|
|
|
|
approval_id = db.Column(db.Integer, db.ForeignKey("approval.id"), nullable=False)
|
|
|
|
file_version = db.Column(db.Integer, nullable=False)
|
|
|
|
|
|
|
|
approval = db.relationship("ApprovalModel")
|
|
|
|
file = db.relationship(FileModel)
|
|
|
|
|
|
|
|
|
2020-05-22 22:25:00 +00:00
|
|
|
class ApprovalModel(db.Model):
|
|
|
|
__tablename__ = 'approval'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
study_id = db.Column(db.Integer, db.ForeignKey(StudyModel.id), nullable=False)
|
2020-05-23 02:04:11 +00:00
|
|
|
study = db.relationship(StudyModel, backref='approval', cascade='all,delete')
|
2020-05-22 22:25:00 +00:00
|
|
|
workflow_id = db.Column(db.Integer, db.ForeignKey(WorkflowModel.id), nullable=False)
|
2020-05-24 05:53:48 +00:00
|
|
|
workflow = db.relationship(WorkflowModel)
|
2020-05-22 22:25:00 +00:00
|
|
|
approver_uid = db.Column(db.String) # Not linked to user model, as they may not have logged in yet.
|
|
|
|
status = db.Column(db.String)
|
2020-05-27 18:06:32 +00:00
|
|
|
message = db.Column(db.String, default='')
|
2020-05-23 19:08:17 +00:00
|
|
|
date_created = db.Column(db.DateTime(timezone=True), default=func.now())
|
|
|
|
version = db.Column(db.Integer) # Incremented integer, so 1,2,3 as requests are made.
|
|
|
|
workflow_hash = db.Column(db.String) # A hash of the workflow at the moment the approval was created.
|
|
|
|
|
|
|
|
approval_files = db.relationship(ApprovalFile, back_populates="approval")
|
|
|
|
|
2020-05-22 22:25:00 +00:00
|
|
|
|
|
|
|
class Approval(object):
|
|
|
|
|
2020-05-25 21:40:24 +00:00
|
|
|
def __init__(self, **kwargs):
|
|
|
|
self.__dict__.update(kwargs)
|
|
|
|
|
2020-05-22 22:25:00 +00:00
|
|
|
@classmethod
|
|
|
|
def from_model(cls, model: ApprovalModel):
|
2020-05-25 21:40:24 +00:00
|
|
|
# TODO: Reduce the code by iterating over model's dict keys
|
2020-05-22 22:25:00 +00:00
|
|
|
instance = cls()
|
|
|
|
instance.id = model.id
|
2020-05-24 05:53:48 +00:00
|
|
|
instance.study_id = model.study_id
|
|
|
|
instance.workflow_id = model.workflow_id
|
2020-05-23 19:08:17 +00:00
|
|
|
instance.version = model.version
|
2020-05-22 22:25:00 +00:00
|
|
|
instance.approver_uid = model.approver_uid
|
|
|
|
instance.status = model.status
|
2020-05-24 05:53:48 +00:00
|
|
|
instance.message = model.message
|
|
|
|
instance.date_created = model.date_created
|
|
|
|
instance.version = model.version
|
|
|
|
instance.workflow_hash = model.workflow_hash
|
|
|
|
instance.title = ''
|
2020-05-22 22:25:00 +00:00
|
|
|
if model.study:
|
|
|
|
instance.title = model.study.title
|
|
|
|
|
2020-05-27 18:06:32 +00:00
|
|
|
instance.approver = {}
|
2020-05-25 23:30:16 +00:00
|
|
|
try:
|
2020-05-26 17:29:24 +00:00
|
|
|
ldap_service = LdapService()
|
2020-05-27 18:06:32 +00:00
|
|
|
principal_investigator_id = model.study.primary_investigator_id
|
|
|
|
user_info = ldap_service.user_info(principal_investigator_id)
|
2020-05-26 17:29:24 +00:00
|
|
|
except (ApiError, LDAPSocketOpenError) as exception:
|
2020-05-25 23:30:16 +00:00
|
|
|
user_info = None
|
2020-05-27 18:06:32 +00:00
|
|
|
instance.approver['display_name'] = 'Primary Investigator details'
|
|
|
|
instance.approver['department'] = 'currently not available'
|
2020-05-25 23:30:16 +00:00
|
|
|
|
|
|
|
if user_info:
|
2020-05-27 18:06:32 +00:00
|
|
|
# TODO: Rename approver to primary investigator
|
2020-05-25 23:30:16 +00:00
|
|
|
instance.approver['uid'] = model.approver_uid
|
|
|
|
instance.approver['display_name'] = user_info.display_name
|
|
|
|
instance.approver['title'] = user_info.title
|
|
|
|
instance.approver['department'] = user_info.department
|
2020-05-24 05:53:48 +00:00
|
|
|
|
|
|
|
instance.associated_files = []
|
|
|
|
for approval_file in model.approval_files:
|
|
|
|
associated_file = {}
|
|
|
|
associated_file['id'] = approval_file.file.id
|
|
|
|
associated_file['name'] = approval_file.file.name
|
|
|
|
associated_file['content_type'] = approval_file.file.content_type
|
|
|
|
instance.associated_files.append(associated_file)
|
|
|
|
|
|
|
|
return instance
|
|
|
|
|
2020-05-27 18:06:32 +00:00
|
|
|
def update_model(self, approval_model: ApprovalModel):
|
|
|
|
approval_model.status = self.status
|
|
|
|
approval_model.message = self.message
|
2020-05-25 21:40:24 +00:00
|
|
|
|
2020-05-22 22:25:00 +00:00
|
|
|
|
|
|
|
class ApprovalSchema(ma.Schema):
|
|
|
|
class Meta:
|
|
|
|
model = Approval
|
2020-05-24 05:53:48 +00:00
|
|
|
fields = ["id", "study_id", "workflow_id", "version", "title",
|
2020-05-27 18:06:32 +00:00
|
|
|
"version", "status", "message", "approver", "associated_files"]
|
2020-05-22 22:25:00 +00:00
|
|
|
unknown = INCLUDE
|
|
|
|
|
2020-05-25 21:40:24 +00:00
|
|
|
@marshmallow.post_load
|
|
|
|
def make_approval(self, data, **kwargs):
|
|
|
|
"""Loads the basic approval data for updates to the database"""
|
|
|
|
return Approval(**data)
|
2020-05-24 05:53:48 +00:00
|
|
|
|
2020-05-22 22:25:00 +00:00
|
|
|
# Carlos: Here is the data structure I was trying to imagine.
|
|
|
|
# If I were to continue down my current traing of thought, I'd create
|
|
|
|
# another class called just "Approval" that can take an ApprovalModel from the
|
|
|
|
# database and construct a data structure like this one, that can
|
|
|
|
# be provided to the API at an /approvals endpoint with GET and PUT
|
|
|
|
# dat = { "approvals": [
|
|
|
|
# {"id": 1,
|
|
|
|
# "study_id": 20,
|
|
|
|
# "workflow_id": 454,
|
|
|
|
# "study_title": "Dan Funk (dhf8r)", # Really it's just the name of the Principal Investigator
|
|
|
|
# "workflow_version": "21",
|
|
|
|
# "approver": { # Pulled from ldap
|
|
|
|
# "uid": "bgb22",
|
|
|
|
# "display_name": "Billy Bob (bgb22)",
|
|
|
|
# "title": "E42:He's a hoopy frood",
|
|
|
|
# "department": "E0:EN-Eng Study of Parallel Universes",
|
|
|
|
# },
|
|
|
|
# "files": [
|
|
|
|
# {
|
|
|
|
# "id": 124,
|
|
|
|
# "name": "ResearchRestart.docx",
|
|
|
|
# "content_type": "docx-something-whatever"
|
|
|
|
# }
|
|
|
|
# ]
|
|
|
|
# }
|
|
|
|
# ...
|
|
|
|
# ]
|