Merge pull request #46 from sartography/update-study-details-fields-257

Update study details fields 257
This commit is contained in:
Dan Funk 2021-04-02 18:56:26 -04:00 committed by GitHub
commit 60da05df4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 207 additions and 19 deletions

View File

@ -0,0 +1,42 @@
"""empty message
Revision ID: 6cd52db404f6
Revises: 9493f29b0898
Create Date: 2021-03-24 10:04:28.215143
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6cd52db404f6'
down_revision = '9493f29b0898'
branch_labels = None
depends_on = None
def upgrade():
op.add_column('study_details', sa.Column('IS_SPONSOR_TRACKING', sa.Integer(), nullable=True))
op.add_column('study_details', sa.Column('SPONSOR_TRACKING', sa.Integer(), nullable=True))
op.add_column('study_details', sa.Column('IS_DSMB', sa.Integer(), nullable=True))
op.add_column('study_details', sa.Column('IS_COMPLETE_NON_IRB_REGULATORY', sa.Integer(), nullable=True))
op.add_column('study_details', sa.Column('IS_INSIDE_CONTRACT', sa.Integer(), nullable=True))
op.add_column('study_details', sa.Column('IS_CODED_RESEARCH', sa.Integer(), nullable=True))
op.add_column('study_details', sa.Column('IS_OUTSIDE_SPONSOR', sa.Integer(), nullable=True))
op.add_column('study_details', sa.Column('IS_UVA_COLLABANALYSIS', sa.Integer(), nullable=True))
op.drop_column('study_details', 'SPONSORS_PROTOCOL_REVISION_DATE')
op.add_column('study_details', sa.Column('SPONSORS_PROTOCOL_REVISION_DATE', sa.Date(), nullable=True))
def downgrade():
op.drop_column('study_details', 'IS_SPONSOR_TRACKING')
op.drop_column('study_details', 'SPONSOR_TRACKING')
op.drop_column('study_details', 'IS_DSMB')
op.drop_column('study_details', 'IS_COMPLETE_NON_IRB_REGULATORY')
op.drop_column('study_details', 'IS_INSIDE_CONTRACT')
op.drop_column('study_details', 'IS_CODED_RESEARCH')
op.drop_column('study_details', 'IS_OUTSIDE_SPONSOR')
op.drop_column('study_details', 'IS_UVA_COLLABANALYSIS')
op.drop_column('study_details', 'SPONSORS_PROTOCOL_REVISION_DATE')
op.add_column('study_details', sa.Column('SPONSORS_PROTOCOL_REVISION_DATE', sa.Integer(), nullable=True))

View File

@ -0,0 +1,30 @@
"""empty message
Revision ID: 93a1e2ce38dc
Revises: 6cd52db404f6
Create Date: 2021-03-29 15:06:32.100287
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '93a1e2ce38dc'
down_revision = '6cd52db404f6'
branch_labels = None
depends_on = None
def upgrade():
op.drop_column('study_details', 'DSMB')
op.add_column('study_details', sa.Column('DSMB', sa.VARCHAR, nullable=True))
op.add_column('study_details', sa.Column('REVIEW_TYPE', sa.Integer, nullable=True))
op.add_column('study_details', sa.Column('REVIEWTYPENAME', sa.VARCHAR, nullable=True))
def downgrade():
op.drop_column('study_details', 'DSMB')
op.add_column('study_details', sa.Column('DSMB', sa.Integer, nullable=True))
op.drop_column('study_details', 'REVIEW_TYPE')
op.drop_column('study_details', 'REVIEWTYPENAME')

View File

@ -1,8 +1,8 @@
import csv
import datetime
import os
import re
import yaml
from datetime import date
from io import TextIOWrapper
import connexion
from flask_cors import CORS
@ -453,26 +453,78 @@ def _update_study(study, form):
db.session.commit()
def _allowed_file(filename):
allowed_extensions = ['csv', 'xls', 'xlsx']
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in allowed_extensions
def process_csv_study_details(study_id, csv_file):
study_details = db.session.query(StudyDetails).filter(StudyDetails.STUDYID == study_id).first()
csv_file.seek(0)
data = csv.DictReader(TextIOWrapper(csv_file))
for row in data:
if data.line_num == 2:
for key in row.keys():
if key != 'STUDYID':
if hasattr(study_details, key):
if key in row.keys():
val = row[key]
print(key, val)
if key == 'SPONSORS_PROTOCOL_REVISION_DATE':
print(type(val))
if val == '':
val = None
if val == 'null':
val = None
setattr(study_details, key, val)
session.add(study_details)
session.commit()
@app.route('/study_details/<study_id>', methods=['GET', 'POST'])
def study_details(study_id):
study_details = db.session.query(StudyDetails).filter(StudyDetails.STUDYID == study_id).first()
if not study_details:
study_details = StudyDetails(STUDYID=study_id)
form = StudyDetailsForm(request.form, obj=study_details)
if request.method == 'GET':
action = BASE_HREF + "/study_details/" + study_id
title = "Edit Study Details for Study #" + study_id
details = "Numeric fields can be 1 for true, 0 or false, or Null if not applicable."
action = BASE_HREF + "/study_details/" + study_id
title = "Edit Study Details for Study #" + study_id
details = "Numeric fields can be 1 for true, 0 or false, or Null if not applicable."
if request.method == 'POST':
form.populate_obj(study_details)
db.session.add(study_details)
db.session.commit()
flash('Study updated successfully!', 'success')
return redirect_home()
# update study details with csv file
if 'file' in request.files:
file = request.files['file']
if file.filename == '':
flash('No file selected', 'failure')
return redirect(action)
elif file and _allowed_file(file.filename):
process_csv_study_details(study_id, file)
flash('CSV file uploaded', 'success')
return redirect_home()
else:
flash('There was a problem processing your file', 'failure')
return redirect(action)
# update study details from the form
elif form.validate():
form.populate_obj(study_details)
db.session.add(study_details)
db.session.commit()
flash('Study updated successfully!', 'success')
return redirect_home()
# display the study details form
return render_template(
'form.html',
form=form,
action=action,
csv_action=action,
title=title,
details=details,
description_map=description_map,

View File

@ -10,7 +10,7 @@ class StudyForm(FlaskForm):
STUDYID = HiddenField()
TITLE = StringField('Title', [validators.DataRequired()])
NETBADGEID = StringField('User UVA Computing Id', [validators.DataRequired()])
requirements = SelectMultipleField("Requirements",
requirements = SelectMultipleField("Documents",
render_kw={'class': 'multi'},
choices=[(rd.AUXDOCID, rd.AUXDOC) for rd in RequiredDocument.all()])
HSRNUMBER = StringField('HSR Number')
@ -36,6 +36,26 @@ class StudySponsorForm(FlaskForm):
class StudyDetailsForm(ModelForm, FlaskForm):
class Meta:
model = StudyDetails
only = ['IS_IND', 'IND_1', 'IND_2', 'IND_3', 'IS_UVA_IND', 'IS_IDE', 'IDE',
'IS_UVA_IDE', 'IS_CHART_REVIEW', 'IS_RADIATION', 'GCRC_NUMBER', 'IS_GCRC',
'IS_PRC_DSMP', 'IS_PRC', 'PRC_NUMBER', 'IS_IBC', 'IBC_NUMBER',
'IS_SPONSOR_TRACKING', 'SPONSOR_TRACKING', 'SPONSORS_PROTOCOL_REVISION_DATE',
'IS_SPONSOR_MONITORING', 'IS_DSMB', 'IS_COMPLETE_NON_IRB_REGULATORY',
'IS_AUX', 'IS_SPONSOR', 'IS_GRANT', 'IS_COMMITTEE_CONFLICT', 'DSMB',
'DSMB_FREQUENCY', 'IS_DB', 'IS_UVA_DB', 'IS_CENTRAL_REG_DB',
'IS_CONSENT_WAIVER', 'IS_HGT', 'IS_GENE_TRANSFER', 'IS_TISSUE_BANKING',
'IS_SURROGATE_CONSENT', 'IS_ADULT_PARTICIPANT', 'IS_MINOR_PARTICIPANT',
'IS_MINOR', 'IS_BIOMEDICAL', 'IS_QUALITATIVE', 'IS_PI_SCHOOL',
'IS_PRISONERS_POP', 'IS_PREGNANT_POP', 'IS_FETUS_POP',
'IS_MENTAL_IMPAIRMENT_POP', 'IS_ELDERLY_POP', 'IS_OTHER_VULNERABLE_POP',
'OTHER_VULNERABLE_DESC', 'IS_MULTI_SITE', 'IS_UVA_LOCATION',
'NON_UVA_LOCATION', 'MULTI_SITE_LOCATIONS', 'IS_OUTSIDE_CONTRACT',
'IS_UVA_PI_MULTI', 'IS_NOT_PRC_WAIVER', 'IS_INSIDE_CONTRACT',
'IS_CANCER_PATIENT', 'UPLOAD_COMPLETE', 'IS_FUNDING_SOURCE',
'IS_CODED_RESEARCH', 'IS_OUTSIDE_SPONSOR', 'IS_PI_INITIATED',
'IS_ENGAGED_RESEARCH', 'IS_APPROVED_DEVICE', 'IS_FINANCIAL_CONFLICT',
'IS_NOT_CONSENT_WAIVER', 'IS_FOR_CANCER_CENTER', 'IS_REVIEW_BY_CENTRAL_IRB',
'IRBREVIEWERADMIN', 'IS_UVA_COLLABANALYSIS', 'REVIEW_TYPE', 'REVIEWTYPENAME']
class ConfirmDeleteForm(FlaskForm):
@ -100,7 +120,7 @@ class StudyTable(Table):
NETBADGEID = Col('User')
DATE_MODIFIED = DatetimeCol('Last Update', "medium")
Q_COMPLETE = BoolCol('Complete?')
requirements = NestedTableCol('Requirements', RequirementsTable)
requirements = NestedTableCol('Documents', RequirementsTable)
investigators = NestedTableCol('Investigators', InvestigatorsTable)
sponsors = NestedTableCol('Sponsors', SponsorsTable)
delete = LinkCol(

View File

@ -100,7 +100,6 @@ class Investigator(db.Model):
Investigator(INVESTIGATORTYPE="AS_C", INVESTIGATORTYPEFULL="Additional Study Coordinators"),
Investigator(INVESTIGATORTYPE="DEPT_CH", INVESTIGATORTYPEFULL="Department Chair"),
Investigator(INVESTIGATORTYPE="IRBC", INVESTIGATORTYPEFULL="IRB Coordinator"),
Investigator(INVESTIGATORTYPE="SCI", INVESTIGATORTYPEFULL="Scientific Contact"),
]
return types
@ -137,11 +136,9 @@ class RequiredDocument(db.Model):
RequiredDocument(AUXDOCID=21, AUXDOC="New Medical Device Form"),
RequiredDocument(AUXDOCID=22, AUXDOC="SOM CTO Review regarding need for IDE"),
RequiredDocument(AUXDOCID=23, AUXDOC="SOM CTO Review regarding need for IND"),
RequiredDocument(AUXDOCID=24, AUXDOC="InfoSec Approval"),
RequiredDocument(AUXDOCID=25, AUXDOC="Scientific Pre-review Documentation"),
RequiredDocument(AUXDOCID=26, AUXDOC="IBC Number"),
RequiredDocument(AUXDOCID=32, AUXDOC="IDS - Investigational Drug Service Approval"),
RequiredDocument(AUXDOCID=33, AUXDOC="Data Security Plan"),
RequiredDocument(AUXDOCID=36, AUXDOC="RDRC Approval "),
RequiredDocument(AUXDOCID=40, AUXDOC="SBS/IRB Approval-FERPA"),
RequiredDocument(AUXDOCID=41, AUXDOC="HIRE Standard Radiation Language"),
@ -185,13 +182,13 @@ class StudyDetails(db.Model):
PRC_NUMBER = db.Column(db.String, nullable=True)
IS_IBC = db.Column(db.Integer, nullable=True)
IBC_NUMBER = db.Column(db.String, nullable=True)
SPONSORS_PROTOCOL_REVISION_DATE = db.Column(db.Integer, nullable=True)
SPONSORS_PROTOCOL_REVISION_DATE = db.Column(db.Date, nullable=True)
IS_SPONSOR_MONITORING = db.Column(db.Integer, nullable=True)
IS_AUX = db.Column(db.Integer, nullable=True)
IS_SPONSOR = db.Column(db.Integer, nullable=True)
IS_GRANT = db.Column(db.Integer, nullable=True)
IS_COMMITTEE_CONFLICT = db.Column(db.Integer, nullable=True)
DSMB = db.Column(db.Integer, nullable=True)
DSMB = db.Column(db.String, nullable=True)
DSMB_FREQUENCY = db.Column(db.Integer, nullable=True)
IS_DB = db.Column(db.Integer, nullable=True)
IS_UVA_DB = db.Column(db.Integer, nullable=True)
@ -232,6 +229,17 @@ class StudyDetails(db.Model):
IS_FOR_CANCER_CENTER = db.Column(db.Integer, nullable=True)
IS_REVIEW_BY_CENTRAL_IRB = db.Column(db.Integer, nullable=True)
IRBREVIEWERADMIN = db.Column(db.String, nullable=True)
IS_SPONSOR_TRACKING = db.Column(db.Integer, nullable=True)
SPONSOR_TRACKING = db.Column(db.Integer, nullable=True)
IS_DSMB = db.Column(db.Integer, nullable=True)
IS_COMPLETE_NON_IRB_REGULATORY = db.Column(db.Integer, nullable=True)
IS_INSIDE_CONTRACT = db.Column(db.Integer, nullable=True)
IS_CODED_RESEARCH = db.Column(db.Integer, nullable=True)
IS_OUTSIDE_SPONSOR = db.Column(db.Integer, nullable=True)
IS_UVA_COLLABANALYSIS = db.Column(db.Integer, nullable=True)
REVIEW_TYPE = db.Column(db.Integer, nullable=True)
REVIEWTYPENAME = db.Column(db.String, nullable=True)
class StudyDetailsSchema(ma.SQLAlchemyAutoSchema):

View File

@ -14,6 +14,16 @@
<body>
<h2>{{ title }}</h2>
<p>{{ details|safe }}</p>
{% if 'Edit Study Details' in title %}
<div>Upload CSV</div>
<form action="{{ csv_action }}" method="post" enctype=multipart/form-data>
{{ form.csrf_token() }}
<input type=file name=file>
<input type=submit value=Upload label="Upload CSV">
</form>
{% endif %}
<form action="{{ action }}" method="post">
{{ form.csrf_token() }}

View File

@ -0,0 +1,2 @@
STUDYID,UVA_STUDY_TRACKING,IS_IND,IND_1,IND_2,IND_3,IS_UVA_IND,IS_IDE,IDE,IS_UVA_IDE,IS_CHART_REVIEW,IS_RADIATION,GCRC_NUMBER,IS_GCRC,IS_PRC_DSMP,IS_PRC,PRC_NUMBER,IS_IBC,IBC_NUMBER,IS_SPONSOR_TRACKING,SPONSOR_TRACKING,SPONSORS_PROTOCOL_REVISION_DATE,IS_SPONSOR_MONITORING,IS_DSMB,IS_COMPLETE_NON_IRB_REGULATORY,IS_AUX,IS_SPONSOR,IS_GRANT,IS_COMMITTEE_CONFLICT,DSMB,DSMB_FREQUENCY,IS_DB,IS_UVA_DB,IS_CENTRAL_REG_DB,IS_CONSENT_WAIVER,IS_HGT,IS_GENE_TRANSFER,IS_TISSUE_BANKING,IS_SURROGATE_CONSENT,IS_ADULT_PARTICIPANT,IS_MINOR_PARTICIPANT,IS_MINOR,IS_BIOMEDICAL,IS_QUALITATIVE,IS_PI_SCHOOL,IS_PRISONERS_POP,IS_PREGNANT_POP,IS_FETUS_POP,IS_MENTAL_IMPAIRMENT_POP,IS_ELDERLY_POP,IS_OTHER_VULNERABLE_POP,OTHER_VULNERABLE_DESC,IS_MULTI_SITE,IS_UVA_LOCATION,NON_UVA_LOCATION,MULTI_SITE_LOCATIONS,IS_OUTSIDE_CONTRACT,IS_UVA_PI_MULTI,IS_NOT_PRC_WAIVER,IS_INSIDE_CONTRACT,IS_CANCER_PATIENT,UPLOAD_COMPLETE,IS_FUNDING_SOURCE,IS_CODED_RESEARCH,IS_OUTSIDE_SPONSOR,IS_PI_INITIATED,IS_ENGAGED_RESEARCH,IS_APPROVED_DEVICE,IS_FINANCIAL_CONFLICT,IS_NOT_CONSENT_WAIVER,IS_FOR_CANCER_CENTER,IS_REVIEW_BY_CENTRAL_IRB,IRBREVIEWERADMIN,IS_UVA_COLLABANALYSIS
15370,,0,abc,def,ghi,,1,1234,1,0,,,,1,,,,,1,123456,2019-01-01T00:00:00,1,1,,1,1,1,0,UVA Cancer Center,4,,,1,0,0,0,,,,,,1,0,1,,,,,,,,1,0,"Martha Jefferson Hospital, Augusta Health Medical Center, Rockingham Memorial Hospital",,1,,,1,1,,,,1,1,,1,1,0,,1,,1
1 STUDYID UVA_STUDY_TRACKING IS_IND IND_1 IND_2 IND_3 IS_UVA_IND IS_IDE IDE IS_UVA_IDE IS_CHART_REVIEW IS_RADIATION GCRC_NUMBER IS_GCRC IS_PRC_DSMP IS_PRC PRC_NUMBER IS_IBC IBC_NUMBER IS_SPONSOR_TRACKING SPONSOR_TRACKING SPONSORS_PROTOCOL_REVISION_DATE IS_SPONSOR_MONITORING IS_DSMB IS_COMPLETE_NON_IRB_REGULATORY IS_AUX IS_SPONSOR IS_GRANT IS_COMMITTEE_CONFLICT DSMB DSMB_FREQUENCY IS_DB IS_UVA_DB IS_CENTRAL_REG_DB IS_CONSENT_WAIVER IS_HGT IS_GENE_TRANSFER IS_TISSUE_BANKING IS_SURROGATE_CONSENT IS_ADULT_PARTICIPANT IS_MINOR_PARTICIPANT IS_MINOR IS_BIOMEDICAL IS_QUALITATIVE IS_PI_SCHOOL IS_PRISONERS_POP IS_PREGNANT_POP IS_FETUS_POP IS_MENTAL_IMPAIRMENT_POP IS_ELDERLY_POP IS_OTHER_VULNERABLE_POP OTHER_VULNERABLE_DESC IS_MULTI_SITE IS_UVA_LOCATION NON_UVA_LOCATION MULTI_SITE_LOCATIONS IS_OUTSIDE_CONTRACT IS_UVA_PI_MULTI IS_NOT_PRC_WAIVER IS_INSIDE_CONTRACT IS_CANCER_PATIENT UPLOAD_COMPLETE IS_FUNDING_SOURCE IS_CODED_RESEARCH IS_OUTSIDE_SPONSOR IS_PI_INITIATED IS_ENGAGED_RESEARCH IS_APPROVED_DEVICE IS_FINANCIAL_CONFLICT IS_NOT_CONSENT_WAIVER IS_FOR_CANCER_CENTER IS_REVIEW_BY_CENTRAL_IRB IRBREVIEWERADMIN IS_UVA_COLLABANALYSIS
2 15370 0 abc def ghi 1 1234 1 0 1 1 123456 2019-01-01T00:00:00 1 1 1 1 1 0 UVA Cancer Center 4 1 0 0 0 1 0 1 1 0 Martha Jefferson Hospital, Augusta Health Medical Center, Rockingham Memorial Hospital 1 1 1 1 1 1 1 0 1 1

View File

@ -25,7 +25,7 @@ class Sanity_Check_Test(unittest.TestCase):
@classmethod
def tearDownClass(cls):
pass
db.drop_all()
def setUp(self):
ExampleDataLoader().clean_db()
@ -164,3 +164,27 @@ class Sanity_Check_Test(unittest.TestCase):
study = self.add_study(title=title)
self.assertEqual(title, study.TITLE)
# def test_update_study_from_csv(self):
# study = self.add_study()
# f = open('tests/data/ExampleStudyID15370.csv', 'rb')
# r = self.app.post(f'/study_details/{study.STUDYID}', data={'file': [f]}, follow_redirects=False)
#
# print(r)
# print('test_update_study_from_csv')
# def test_study_details_validation(self):
#
# test_study = self.add_study()
# data = {'IS_IND': 1, 'IND_1': 1234}
# r = self.app.post(f'/study_details/{test_study.STUDYID}', data=data, follow_redirects=False)
# self.assertNotIn('Form did not validate!', r.data.decode('utf-8'))
#
# print('test_study_details_validation')
#
# def test_study_details_validation_fail(self):
# test_study = self.add_study()
# data = {'IS_IND': 1, 'IND_1': 1234, 'IS_IDE': 'b'}
# r = self.app.post(f'/study_details/{test_study.STUDYID}', data=data, follow_redirects=False)
# self.assertIn('Form did not validate!', r.data.decode('utf-8'))
#
# print('test_study_details_validation_fail')