From d9f46de161c3da83d7399e636a2e7ebcb935c27b Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Thu, 1 Apr 2021 12:39:09 -0400 Subject: [PATCH 01/10] The Q_COMPLETE form field on the new/edit study page is changing from boolean to option select. Model Changes: Delete Q_COMPLETE column from study table. Add Q_COMPLETE to study table as relationship to new IRBStatus table Add IRBStatus table. Add IRBStatusSchema for API JSON --- pb/models.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pb/models.py b/pb/models.py index bb99a90..9a127e7 100644 --- a/pb/models.py +++ b/pb/models.py @@ -67,8 +67,8 @@ class Study(db.Model): HSRNUMBER = db.Column(db.String()) TITLE = db.Column(db.Text(), nullable=False) NETBADGEID = db.Column(db.String(), nullable=False) - Q_COMPLETE = db.Column(db.Boolean, nullable=True) DATE_MODIFIED = db.Column(db.DateTime(timezone=True), default=func.now()) + Q_COMPLETE = db.relationship("IRBStatus", backref="study", lazy='dynamic') requirements = db.relationship("RequiredDocument", backref="study", lazy='dynamic') investigators = db.relationship("Investigator", backref="study", lazy='dynamic') study_details = db.relationship("StudyDetails", uselist=False, backref="study") @@ -163,6 +163,24 @@ class RequiredDocumentSchema(ma.Schema): fields = ("AUXDOCID", "AUXDOC") +class IRBStatus(db.Model): + STUDYID = db.Column(db.Integer, db.ForeignKey('study.STUDYID'), primary_key=True) + STATUS = db.Column(db.String(), nullable=False, default="") + DETAIL = db.Column(db.String(), nullable=False, default="") + + @staticmethod + def all(): + status = [IRBStatus(STATUS="Error", DETAIL="Study ID does not exist."), + IRBStatus(STATUS="Error", DETAIL="General study errors. UVA Study Tracking Number is missing or not formatted correctly."), + IRBStatus(STATUS="No Error", DETAIL="Passed validation.")] + return status + + +class IRBStatusSchema(ma.Schema): + class Meta: + fields = ("STATUS", "DETAIL") + + class StudyDetails(db.Model): STUDYID = db.Column(db.Integer, db.ForeignKey('study.STUDYID'), primary_key=True) IS_IND = db.Column(db.Integer, nullable=True) From 762c29f9519ec2e0fc97590b38c4918a8660a8c4 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Thu, 1 Apr 2021 12:40:59 -0400 Subject: [PATCH 02/10] The Q_COMPLETE form field on the new/edit study page is changing from boolean to option select. Import new IRBStatus model Change Q_COMPLETE form field to SelectField --- pb/forms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pb/forms.py b/pb/forms.py index e5ea96e..98ac508 100644 --- a/pb/forms.py +++ b/pb/forms.py @@ -3,7 +3,7 @@ from flask_wtf import FlaskForm from wtforms import SelectMultipleField, StringField, BooleanField, SelectField, validators, HiddenField from wtforms_alchemy import ModelForm -from pb.models import RequiredDocument, Investigator, StudyDetails +from pb.models import RequiredDocument, Investigator, StudyDetails, IRBStatus class StudyForm(FlaskForm): @@ -14,8 +14,8 @@ class StudyForm(FlaskForm): render_kw={'class': 'multi'}, choices=[(rd.AUXDOCID, rd.AUXDOC) for rd in RequiredDocument.all()]) HSRNUMBER = StringField('HSR Number') - Q_COMPLETE = BooleanField('Complete in Protocol Builder?', default='checked', - false_values=(False, 'false', 0, '0')) + Q_COMPLETE = SelectField("IRBStatus", + choices=[((q.STATUS, q.DETAIL), q.DETAIL) for q in IRBStatus.all()]) class InvestigatorForm(FlaskForm): From 8d0382ab5a17369d3a900642b129bc3bf8c3dbc1 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Thu, 1 Apr 2021 12:45:41 -0400 Subject: [PATCH 03/10] The Q_COMPLETE form field on the new/edit study page is changing from boolean to option select. Also adding API endpoint to check the IRB status Added check_study method for API endpoint Added 'No Error' as default for new Q_COMPLETE select form field Added code to seed the current status to the form Added code to process the status information from the form and enter into irb_status table --- pb/__init__.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/pb/__init__.py b/pb/__init__.py index 556ac5f..29f080a 100644 --- a/pb/__init__.py +++ b/pb/__init__.py @@ -41,6 +41,11 @@ def sponsors(studyid): return StudySponsorSchema(many=True).dump(sponsors) +def check_study(studyid): + irb_status = db.session.query(IRBStatus).filter(IRBStatus.STUDYID == studyid).first() + return IRBStatusSchema().dump(irb_status) + + def get_form(id, requirement_code): return @@ -157,7 +162,7 @@ def site_map(): # ************************** from pb.forms import StudyForm, StudyTable, InvestigatorForm, StudyDetailsForm, ConfirmDeleteForm, StudySponsorForm from pb.models import Study, RequiredDocument, Investigator, StudySchema, RequiredDocumentSchema, InvestigatorSchema, \ - StudyDetails, StudyDetailsSchema, StudySponsor, Sponsor, SponsorSchema, StudySponsorSchema + StudyDetails, StudyDetailsSchema, StudySponsor, Sponsor, SponsorSchema, StudySponsorSchema, IRBStatus, IRBStatusSchema from pb.ldap.ldap_service import LdapService @@ -201,6 +206,8 @@ def new_study(): flash('Study created successfully!', 'success') return redirect_home() + # set default first time + form.Q_COMPLETE.data = "('No Error', 'Passed validation.')" return render_template( 'form.html', form=form, @@ -220,8 +227,10 @@ def edit_study(study_id): title = "Edit Study #" + study_id if study.requirements: form.requirements.data = list(map(lambda r: r.AUXDOCID, list(study.requirements))) - if study.Q_COMPLETE: - form.Q_COMPLETE.checked = True + if study.Q_COMPLETE and study.Q_COMPLETE.first(): + form.Q_COMPLETE.data = "('" + study.Q_COMPLETE.first().STATUS + "', '" + study.Q_COMPLETE.first().DETAIL + "')" + else: + form.Q_COMPLETE.data = "('No Error', 'Passed validation.')" if request.method == 'POST': _update_study(study, form) flash('Study updated successfully!', 'success') @@ -441,7 +450,6 @@ def _update_study(study, form): study.TITLE = form.TITLE.data study.NETBADGEID = form.NETBADGEID.data study.DATE_MODIFIED = datetime.datetime.now() - study.Q_COMPLETE = form.Q_COMPLETE.data study.HSRNUMBER = form.HSRNUMBER.data for r in form.requirements: @@ -449,6 +457,18 @@ def _update_study(study, form): requirement = RequiredDocument(AUXDOCID=r.data, AUXDOC=r.label.text, study=study) db.session.add(requirement) + q_data = eval(form.Q_COMPLETE.data) + if q_data: + q_data_status = q_data[0] + q_data_detail = q_data[1] + q_status = db.session.query(IRBStatus).filter(IRBStatus.STUDYID == study.STUDYID).first() + if q_status: + q_status.STATUS = q_data_status + q_status.DETAIL = q_data_detail + else: + q_status = IRBStatus(STATUS=q_data_status, DETAIL=q_data_detail, STUDYID=study.STUDYID) + db.session.add(q_status) + db.session.add(study) db.session.commit() From b663a4ec384626aed1d49c14c897ac87197e1477 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Thu, 1 Apr 2021 12:46:15 -0400 Subject: [PATCH 04/10] Added new API endpoint to check IRB Status for study --- pb/api.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pb/api.yml b/pb/api.yml index ff153cb..d55365a 100644 --- a/pb/api.yml +++ b/pb/api.yml @@ -133,6 +133,27 @@ paths: application/json: schema: $ref: "#/components/schemas/StudyDetail" + /check_study/{studyid}: + parameters: + - name: studyid + in: path + required: true + description: The id of the study. + schema: + type: integer + format: int32 + get: + tags: + - CR-Connect + operationId: pb.check_study + summary: IRB Status about a particular study. + responses: + 200: + description: Details about the protocol + content: + application/json: + schema: + $ref: "#/components/schemas/StudyDetail" components: schemas: Study: From c9695cc867b94ba5c5e936ff4a7d2b25593951c6 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Thu, 1 Apr 2021 12:47:27 -0400 Subject: [PATCH 05/10] Migration for current DB changes --- migrations/versions/119c1269ee7c_.py | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 migrations/versions/119c1269ee7c_.py diff --git a/migrations/versions/119c1269ee7c_.py b/migrations/versions/119c1269ee7c_.py new file mode 100644 index 0000000..fb36719 --- /dev/null +++ b/migrations/versions/119c1269ee7c_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: 119c1269ee7c +Revises: 93a1e2ce38dc +Create Date: 2021-03-31 12:30:19.645458 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '119c1269ee7c' +down_revision = '93a1e2ce38dc' +branch_labels = None +depends_on = None + + +def upgrade(): + op.drop_column('study', 'Q_COMPLETE') + op.create_table('irb_status', + sa.Column('STUDYID', sa.Integer(), nullable=False), + sa.Column('STATUS', sa.String(), nullable=False, default=''), + sa.Column('DETAIL', sa.String(), nullable=False, default=''), + sa.ForeignKeyConstraint(['STUDYID'], ['study.STUDYID'],), + sa.PrimaryKeyConstraint('STUDYID') + ) + + + +def downgrade(): + op.drop_table('irb_status') + # op.drop_column('study', 'Q_COMPLETE') + op.add_column('study', sa.Column('Q_COMPLETE', sa.Boolean(), nullable=True)) From 74b6dde9b216b55d75fa9e4e883b917341220669 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Thu, 1 Apr 2021 14:11:59 -0400 Subject: [PATCH 06/10] Added component schema for IRBStatus Modified Q_COMPLETE property definition for study schema --- pb/api.yml | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/pb/api.yml b/pb/api.yml index d55365a..b06eda6 100644 --- a/pb/api.yml +++ b/pb/api.yml @@ -153,7 +153,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/StudyDetail" + $ref: "#/components/schemas/IRBStatus" components: schemas: Study: @@ -178,10 +178,10 @@ components: example: jfg6n description: The UVA Id of of the principle investigator for the study. Q_COMPLETE: - type: number - enum: [0,1] - example: 0 - description: If 1, then this study is complete in the Protocol Builder, and is ready for processing by CR Connect. + type: select + format: string + example: No Error + description: Study Status DATE_MODIFIED: type: string format: date_time @@ -254,6 +254,21 @@ components: type: string example: Principal Investigator description: A human readable descriptive string of the INVESTIGATORTYPE. + IRBStatus: + type: object + properties: + STUDYID: + type: number + example: 12345 + description: The study id from the Protocol Builder + STATUS: + type: string + example: No Error + description: The study status + DETAIL: + type: string + example: Passed Validation + description: Detail about the study status StudyDetail: type: object properties: From f23e4e307f03bb65d47d8e0fb8c85cd399c54ff7 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Thu, 1 Apr 2021 14:12:23 -0400 Subject: [PATCH 07/10] removed commented line --- migrations/versions/119c1269ee7c_.py | 1 - 1 file changed, 1 deletion(-) diff --git a/migrations/versions/119c1269ee7c_.py b/migrations/versions/119c1269ee7c_.py index fb36719..94d3edb 100644 --- a/migrations/versions/119c1269ee7c_.py +++ b/migrations/versions/119c1269ee7c_.py @@ -30,5 +30,4 @@ def upgrade(): def downgrade(): op.drop_table('irb_status') - # op.drop_column('study', 'Q_COMPLETE') op.add_column('study', sa.Column('Q_COMPLETE', sa.Boolean(), nullable=True)) From 15dc53f95738a030ae8095158a65691f42fd6462 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Fri, 2 Apr 2021 11:48:09 -0400 Subject: [PATCH 08/10] Removed Q_COMPLETE from the StudySchema fields, because it is no longer a column. It is a relationship now. --- pb/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pb/models.py b/pb/models.py index 9a127e7..f1f8564 100644 --- a/pb/models.py +++ b/pb/models.py @@ -79,7 +79,7 @@ class StudySchema(ma.Schema): class Meta: # Fields to expose fields = ("STUDYID", "HSRNUMBER", "TITLE", "NETBADGEID", - "Q_COMPLETE", "DATE_MODIFIED") + "DATE_MODIFIED") class Investigator(db.Model): From 4e2b79741fd889a09a2491f1fe2d693d726953f3 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Tue, 6 Apr 2021 12:23:13 -0400 Subject: [PATCH 09/10] Tests were failing because forms were not sending Q_COMPLETE data. Added mocked Q_COMPLETE data to `add_study` and `test_add_and_edit_study`. --- tests/test_sanity.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_sanity.py b/tests/test_sanity.py index 8b20d77..1335a02 100644 --- a/tests/test_sanity.py +++ b/tests/test_sanity.py @@ -57,6 +57,8 @@ class Sanity_Check_Test(unittest.TestCase): for r in form.requirements: form.data['requirements'].append(r.data) + form.Q_COMPLETE.data = "('No Error', 'Passed validation.')" + r = self.app.post('/new_study', data=form.data, follow_redirects=False) assert r.status_code == 302 added_study = Study.query.filter(Study.TITLE == study_title).first() @@ -79,6 +81,7 @@ class Sanity_Check_Test(unittest.TestCase): for r in form_2.requirements: form_2.data['requirements'].append(r.data) + form_2.Q_COMPLETE.data = "('No Error', 'Passed validation.')" r_2 = self.app.post('/study/%i' % added_study.STUDYID, data=form_2.data, follow_redirects=False) assert r_2.status_code == 302 From 2c780c90288cfc7ab956d575601bcad83bce21a1 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Tue, 6 Apr 2021 12:25:21 -0400 Subject: [PATCH 10/10] Q_COMPLETE is not a column in the study table anymore. Removed Q_COMPLETE from the API Study component schema --- pb/api.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pb/api.yml b/pb/api.yml index b06eda6..35ad8a9 100644 --- a/pb/api.yml +++ b/pb/api.yml @@ -177,11 +177,6 @@ components: format: string example: jfg6n description: The UVA Id of of the principle investigator for the study. - Q_COMPLETE: - type: select - format: string - example: No Error - description: Study Status DATE_MODIFIED: type: string format: date_time @@ -385,7 +380,7 @@ components: example: true description: Will this study be monitored by a Data and Safety Monitoring Board? DSMB_FREQUENCY: - type: String + type: string example: A lot description: UNDOCUMENTED. IS_DB: