Updates study model to better match Protocol Builder. Deletes all migrations and starts over, since Alembic has problems migrating changes to existing column constraints.
This commit is contained in:
parent
0cc59d0974
commit
4534b0c2df
|
@ -91,10 +91,10 @@
|
|||
},
|
||||
"billiard": {
|
||||
"hashes": [
|
||||
"sha256:26fd494dc3251f8ce1f5559744f18aeed427fdaf29a75d7baae26752a5d3816f",
|
||||
"sha256:f4e09366653aa3cb3ae8ed16423f9ba1665ff426f087bcdbbed86bf3664fe02c"
|
||||
"sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede",
|
||||
"sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a"
|
||||
],
|
||||
"version": "==3.6.2.0"
|
||||
"version": "==3.6.3.0"
|
||||
},
|
||||
"blinker": {
|
||||
"hashes": [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import List
|
||||
from typing import List, Optional, Union, Tuple, Dict
|
||||
|
||||
from connexion import NoContent
|
||||
from flask import g
|
||||
|
@ -8,19 +8,14 @@ from crc.api.common import ApiError, ApiErrorSchema
|
|||
from crc.api.workflow import __get_workflow_api_model
|
||||
from crc.models.protocol_builder import ProtocolBuilderStatus, ProtocolBuilderStudy
|
||||
from crc.models.study import StudyModelSchema, StudyModel
|
||||
from crc.models.workflow import WorkflowModel, WorkflowApiSchema, WorkflowSpecModel
|
||||
from crc.models.workflow import WorkflowModel, WorkflowApiSchema, WorkflowSpecModel, WorkflowApi
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def all_studies():
|
||||
user = g.user
|
||||
""":type: crc.models.user.UserModel"""
|
||||
|
||||
update_from_protocol_builder()
|
||||
db_studies = session.query(StudyModel).filter_by(user_uid=user.uid).all()
|
||||
return StudyModelSchema(many=True).dump(db_studies)
|
||||
return update_from_protocol_builder()
|
||||
|
||||
|
||||
@auth.login_required
|
||||
|
@ -47,10 +42,11 @@ def update_study(study_id, body):
|
|||
error = ApiError('unknown_study', 'The study "' + study_id + '" is not recognized.')
|
||||
return ApiErrorSchema.dump(error), 404
|
||||
|
||||
study = StudyModelSchema().load(body, session=session, instance=study)
|
||||
schema = StudyModelSchema()
|
||||
study = schema.load(body, session=session, instance=study, partial=True)
|
||||
session.add(study)
|
||||
session.commit()
|
||||
return StudyModelSchema().dump(study)
|
||||
return schema.dump(study)
|
||||
|
||||
|
||||
@auth.login_required
|
||||
|
@ -71,39 +67,49 @@ def update_from_protocol_builder():
|
|||
""":type: crc.models.user.UserModel"""
|
||||
|
||||
# Get studies matching this user from Protocol Builder
|
||||
pb_studies: List[ProtocolBuilderStudy] = ProtocolBuilderService.get_studies(user.uid)
|
||||
pb_studies: List[ProtocolBuilderStudy] = get_user_pb_studies()
|
||||
|
||||
# Get studies from the database
|
||||
db_studies = session.query(StudyModel).filter_by(user_uid=user.uid).all()
|
||||
db_study_ids = list(map(lambda s: s.id, db_studies))
|
||||
pb_study_ids = list(map(lambda s: s.id, pb_studies))
|
||||
pb_study_ids = list(map(lambda s: s['STUDYID'], pb_studies))
|
||||
|
||||
# Add studies from Protocol Builder that aren't in the database yet
|
||||
for pb_study in pb_studies:
|
||||
if pb_study['HSRNUMBER'] not in db_study_ids:
|
||||
status = ProtocolBuilderStatus.complete._value_ if pb_study[
|
||||
'Q_COMPLETE'] else ProtocolBuilderStatus.in_process._value_
|
||||
add_study({
|
||||
'id': pb_study['HSRNUMBER'],
|
||||
'title': pb_study['TITLE'],
|
||||
'protocol_builder_status': status,
|
||||
'user_uid': pb_study['NETBADGEID'],
|
||||
'last_updated': pb_study['DATE_MODIFIED']
|
||||
})
|
||||
|
||||
# Update studies with latest data from Protocol Builder
|
||||
if pb_study['STUDYID'] in db_study_ids:
|
||||
update_study(pb_study['STUDYID'], map_pb_study_to_study(pb_study))
|
||||
|
||||
# Add studies from Protocol Builder that aren't in the database yet
|
||||
else:
|
||||
new_study = map_pb_study_to_study(pb_study)
|
||||
add_study(new_study)
|
||||
|
||||
# Mark studies as inactive that are no longer in Protocol Builder
|
||||
for study_id in db_study_ids:
|
||||
if study_id not in pb_study_ids:
|
||||
update_study(study_id=study_id, body={'inactive': True})
|
||||
|
||||
# Return updated studies
|
||||
updated_studies = session.query(StudyModel).filter_by(user_uid=user.uid).all()
|
||||
results = StudyModelSchema(many=True).dump(updated_studies)
|
||||
return results
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def post_update_study_from_protocol_builder(study_id):
|
||||
"""Update a single study based on data received from
|
||||
the protocol builder."""
|
||||
|
||||
# todo: Actually get data from an external service here
|
||||
pb_studies: List[ProtocolBuilderStudy] = get_user_pb_studies()
|
||||
for pb_study in pb_studies:
|
||||
if pb_study['STUDYID'] == study_id:
|
||||
return update_study(study_id, map_pb_study_to_study(pb_study))
|
||||
|
||||
return NoContent, 304
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def get_study_workflows(study_id):
|
||||
workflow_models = session.query(WorkflowModel).filter_by(study_id=study_id).all()
|
||||
api_models = []
|
||||
|
@ -115,6 +121,7 @@ def get_study_workflows(study_id):
|
|||
return schema.dump(api_models)
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def add_workflow_to_study(study_id, body):
|
||||
workflow_spec_model = session.query(WorkflowSpecModel).filter_by(id=body["id"]).first()
|
||||
if workflow_spec_model is None:
|
||||
|
@ -122,3 +129,38 @@ def add_workflow_to_study(study_id, body):
|
|||
return ApiErrorSchema.dump(error), 404
|
||||
processor = WorkflowProcessor.create(study_id, workflow_spec_model.id)
|
||||
return WorkflowApiSchema().dump(__get_workflow_api_model(processor))
|
||||
|
||||
|
||||
@auth.login_required
|
||||
def get_user_pb_studies() -> List[ProtocolBuilderStudy]:
|
||||
"""Get studies from Protocol Builder matching the given user"""
|
||||
|
||||
user = g.user
|
||||
""":type: crc.models.user.UserModel"""
|
||||
|
||||
return ProtocolBuilderService.get_studies(user.uid)
|
||||
|
||||
|
||||
def map_pb_study_to_study(pb_study):
|
||||
"""Translates the given dict of ProtocolBuilderStudy properties to dict of StudyModel attributes"""
|
||||
prop_map = {
|
||||
'STUDYID': 'id',
|
||||
'HSRNUMBER': 'hsr_number',
|
||||
'TITLE': 'title',
|
||||
'NETBADGEID': 'user_uid',
|
||||
'DATE_MODIFIED': 'last_updated',
|
||||
}
|
||||
study_info = {}
|
||||
|
||||
# Translate Protocol Builder property names to Study attributes
|
||||
for k, v in pb_study.items():
|
||||
if k in prop_map:
|
||||
study_info[prop_map[k]] = v
|
||||
|
||||
if pb_study['Q_COMPLETE']:
|
||||
study_info['protocol_builder_status'] = ProtocolBuilderStatus.complete._value_
|
||||
else:
|
||||
study_info['protocol_builder_status'] = ProtocolBuilderStatus.in_process._value_
|
||||
|
||||
return study_info
|
||||
|
||||
|
|
|
@ -12,12 +12,14 @@ class StudyModel(db.Model):
|
|||
title = db.Column(db.String)
|
||||
last_updated = db.Column(db.DateTime(timezone=True), default=func.now())
|
||||
protocol_builder_status = db.Column(db.Enum(ProtocolBuilderStatus))
|
||||
primary_investigator_id = db.Column(db.String)
|
||||
sponsor = db.Column(db.String)
|
||||
ind_number = db.Column(db.String)
|
||||
user_uid = db.Column(db.String, db.ForeignKey('user.uid'), nullable=True)
|
||||
investigator_uids = db.Column(db.ARRAY(db.String))
|
||||
primary_investigator_id = db.Column(db.String, nullable=True)
|
||||
sponsor = db.Column(db.String, nullable=True)
|
||||
hsr_number = db.Column(db.String, nullable=True)
|
||||
ind_number = db.Column(db.String, nullable=True)
|
||||
user_uid = db.Column(db.String, db.ForeignKey('user.uid'), nullable=False)
|
||||
investigator_uids = db.Column(db.ARRAY(db.String), nullable=True)
|
||||
inactive = db.Column(db.Boolean, default=False)
|
||||
requirements = db.Column(db.ARRAY(db.Integer), nullable=True)
|
||||
|
||||
|
||||
class StudyModelSchema(ModelSchema):
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 0a6e0b829398
|
||||
Revises: ad5483cb7f3b
|
||||
Create Date: 2020-02-20 15:42:16.473470
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0a6e0b829398'
|
||||
down_revision = 'ad5483cb7f3b'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('user', sa.Column('affiliation', sa.String(), nullable=True))
|
||||
op.add_column('user', sa.Column('eppn', sa.String(), nullable=True))
|
||||
op.add_column('user', sa.Column('first_name', sa.String(), nullable=True))
|
||||
op.add_column('user', sa.Column('last_name', sa.String(), nullable=True))
|
||||
op.add_column('user', sa.Column('title', sa.String(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('user', 'title')
|
||||
op.drop_column('user', 'last_name')
|
||||
op.drop_column('user', 'first_name')
|
||||
op.drop_column('user', 'eppn')
|
||||
op.drop_column('user', 'affiliation')
|
||||
# ### end Alembic commands ###
|
|
@ -1,28 +0,0 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 2c88e49d0ffc
|
||||
Revises: 726d09a4fa0c
|
||||
Create Date: 2020-02-27 11:17:01.768161
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2c88e49d0ffc'
|
||||
down_revision = '726d09a4fa0c'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('study', sa.Column('inactive', sa.Boolean(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('study', 'inactive')
|
||||
# ### end Alembic commands ###
|
|
@ -1,32 +0,0 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 726d09a4fa0c
|
||||
Revises: 0a6e0b829398
|
||||
Create Date: 2020-02-26 16:35:05.854328
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '726d09a4fa0c'
|
||||
down_revision = '0a6e0b829398'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('study', sa.Column('investigator_uids', sa.ARRAY(sa.String()), nullable=True))
|
||||
op.add_column('study', sa.Column('user_uid', sa.String(), nullable=True))
|
||||
op.create_foreign_key(None, 'study', 'user', ['user_uid'], ['uid'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_constraint(None, 'study', type_='foreignkey')
|
||||
op.drop_column('study', 'user_uid')
|
||||
op.drop_column('study', 'investigator_uids')
|
||||
# ### end Alembic commands ###
|
|
@ -1,35 +0,0 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: ad5483cb7f3b
|
||||
Revises: 02fcf09d9085
|
||||
Create Date: 2020-02-19 11:59:09.948767
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ad5483cb7f3b'
|
||||
down_revision = '02fcf09d9085'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('user',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uid', sa.String(), nullable=True),
|
||||
sa.Column('email_address', sa.String(), nullable=True),
|
||||
sa.Column('display_name', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uid')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('user')
|
||||
# ### end Alembic commands ###
|
|
@ -1,8 +1,8 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 02fcf09d9085
|
||||
Revises:
|
||||
Create Date: 2020-02-05 17:18:35.324675
|
||||
Revision ID: cb3a03c10a0e
|
||||
Revises:
|
||||
Create Date: 2020-02-28 11:12:56.150837
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
|||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '02fcf09d9085'
|
||||
revision = 'cb3a03c10a0e'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
@ -18,15 +18,18 @@ depends_on = None
|
|||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('study',
|
||||
op.create_table('user',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('uid', sa.String(), nullable=True),
|
||||
sa.Column('email_address', sa.String(), nullable=True),
|
||||
sa.Column('display_name', sa.String(), nullable=True),
|
||||
sa.Column('affiliation', sa.String(), nullable=True),
|
||||
sa.Column('eppn', sa.String(), nullable=True),
|
||||
sa.Column('first_name', sa.String(), nullable=True),
|
||||
sa.Column('last_name', sa.String(), nullable=True),
|
||||
sa.Column('title', sa.String(), nullable=True),
|
||||
sa.Column('last_updated', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('protocol_builder_status', sa.Enum('out_of_date', 'in_process', 'complete', 'updating', name='protocolbuilderstatus'), nullable=True),
|
||||
sa.Column('primary_investigator_id', sa.String(), nullable=True),
|
||||
sa.Column('sponsor', sa.String(), nullable=True),
|
||||
sa.Column('ind_number', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uid')
|
||||
)
|
||||
op.create_table('workflow_spec',
|
||||
sa.Column('id', sa.String(), nullable=False),
|
||||
|
@ -36,6 +39,22 @@ def upgrade():
|
|||
sa.Column('primary_process_id', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('study',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('title', sa.String(), nullable=True),
|
||||
sa.Column('last_updated', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('protocol_builder_status', sa.Enum('out_of_date', 'in_process', 'complete', 'updating', name='protocolbuilderstatus'), nullable=True),
|
||||
sa.Column('primary_investigator_id', sa.String(), nullable=True),
|
||||
sa.Column('sponsor', sa.String(), nullable=True),
|
||||
sa.Column('hsr_number', sa.String(), nullable=True),
|
||||
sa.Column('ind_number', sa.String(), nullable=True),
|
||||
sa.Column('user_uid', sa.String(), nullable=False),
|
||||
sa.Column('investigator_uids', sa.ARRAY(sa.String()), nullable=True),
|
||||
sa.Column('inactive', sa.Boolean(), nullable=True),
|
||||
sa.Column('requirements', sa.ARRAY(sa.Integer()), nullable=True),
|
||||
sa.ForeignKeyConstraint(['user_uid'], ['user.uid'], ),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('workflow',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('bpmn_workflow_json', sa.JSON(), nullable=True),
|
||||
|
@ -79,6 +98,7 @@ def downgrade():
|
|||
op.drop_table('file_data')
|
||||
op.drop_table('file')
|
||||
op.drop_table('workflow')
|
||||
op.drop_table('workflow_spec')
|
||||
op.drop_table('study')
|
||||
op.drop_table('workflow_spec')
|
||||
op.drop_table('user')
|
||||
# ### end Alembic commands ###
|
|
@ -27,6 +27,7 @@ class TestStudyApi(BaseTest):
|
|||
"primary_investigator_id": "tricia.marie.mcmillan@heartofgold.edu",
|
||||
"sponsor": "Sirius Cybernetics Corporation",
|
||||
"ind_number": "567890",
|
||||
"user_uid": "dhf8r",
|
||||
}
|
||||
rv = self.app.post('/v1.0/study',
|
||||
content_type="application/json",
|
||||
|
@ -41,6 +42,7 @@ class TestStudyApi(BaseTest):
|
|||
self.assertEqual(study["primary_investigator_id"], db_study.primary_investigator_id)
|
||||
self.assertEqual(study["sponsor"], db_study.sponsor)
|
||||
self.assertEqual(study["ind_number"], db_study.ind_number)
|
||||
self.assertEqual(study["user_uid"], db_study.user_uid)
|
||||
|
||||
def test_update_study(self):
|
||||
self.load_example_data()
|
||||
|
|
|
@ -13,8 +13,12 @@ class TestTasksApi(BaseTest):
|
|||
def create_workflow(self, workflow_name):
|
||||
study = session.query(StudyModel).first()
|
||||
spec = session.query(WorkflowSpecModel).filter_by(id=workflow_name).first()
|
||||
self.app.post('/v1.0/study/%i/workflows' % study.id, content_type="application/json",
|
||||
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
|
||||
rv = self.app.post(
|
||||
'/v1.0/study/%i/workflows' % study.id,
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json",
|
||||
data=json.dumps(WorkflowSpecModelSchema().dump(spec)))
|
||||
self.assert_success(rv)
|
||||
workflow = session.query(WorkflowModel).filter_by(study_id=study.id, workflow_spec_id=workflow_name).first()
|
||||
return workflow
|
||||
|
||||
|
@ -183,4 +187,4 @@ class TestTasksApi(BaseTest):
|
|||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual("EndEvent_0u1cgrf", workflow_api.next_task['name'])
|
||||
self.assertIsNotNone(workflow_api.next_task['documentation'])
|
||||
self.assertTrue("norris" in workflow_api.next_task['documentation'])
|
||||
self.assertTrue("norris" in workflow_api.next_task['documentation'])
|
||||
|
|
Loading…
Reference in New Issue