A major refactor of how we search and store files, as there was a lot of confusing bits in here.

From an API point of view you can do the following (and only the following)

/files?workflow_spec_id=x
* You can find all files associated with a workflow_spec_id, and add a file with a workflow_spec_id
/files?workflow_id=x
* You can find all files associated with a workflow_id, and add a file that is directly associated with the workflow
/files?workflow_id=x&form_field_key=y
* You can find all files associated with a form element on a running workflow, and add a new file.
   Note: you can add multiple files to the same form_field_key, IF they have different file names. If the same name, the original file is archived,
   and the new file takes its place.

The study endpoints always return a list of the file metadata associated with the study.  Removed /studies-files, but there is an
endpoint called

/studies/all  - that returns all the studies in the system, and does include their files.

On a deeper level:
 The File model no longer contains:
  - study_id,
  - task_id,
  - form_field_key

Instead, if the file is associated with workflow - then that is the one way it is connected to the study, and we use this relationship to find files for a study.
A file is never associated with a task_id, as these change when the workflow is reloaded.
The form_field_key must match the irb_doc_code, so when requesting files for a form field, we just look up the irb_doc_code.
This commit is contained in:
Dan Funk 2020-05-28 08:27:26 -04:00
parent 560263d1a3
commit cd7f67ab48
14 changed files with 164 additions and 220 deletions

View File

@ -82,7 +82,7 @@ paths:
# /v1.0/study
/study:
get:
operationId: crc.api.study.all_studies
operationId: crc.api.study.user_studies
summary: Provides a list of studies related to the current user.
tags:
- Studies
@ -109,11 +109,13 @@ paths:
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Study"
/study-files:
/study/all:
get:
operationId: crc.api.study.all_studies_and_files
summary: Provides a list of studies with submitted files
operationId: crc.api.study.all_studies
summary: Provides a list of studies
tags:
- Studies
responses:
@ -122,6 +124,8 @@ paths:
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Study"
/study/{study_id}:
parameters:
@ -353,24 +357,12 @@ paths:
description: The unique id of a workflow specification
schema:
type: string
- name: study_id
in: query
required: false
description: The unique id of a study
schema:
type: integer
- name: workflow_id
in: query
required: false
description: The unique id of a workflow instance
schema:
type: integer
- name: task_id
in: query
required: false
description: The unique id of a workflow task
schema:
type: string
- name: form_field_key
in: query
required: false

View File

@ -5,18 +5,20 @@ from flask import send_file
from crc import session
from crc.api.common import ApiError
from crc.models.file import FileModelSchema, FileModel, FileDataModel
from crc.models.file import FileModelSchema, FileModel
from crc.models.workflow import WorkflowSpecModel
from crc.services.file_service import FileService
def get_files(workflow_spec_id=None, study_id=None, workflow_id=None, task_id=None, form_field_key=None):
if all(v is None for v in [workflow_spec_id, study_id, workflow_id, task_id, form_field_key]):
def get_files(workflow_spec_id=None, workflow_id=None, form_field_key=None):
if all(v is None for v in [workflow_spec_id, workflow_id, form_field_key]):
raise ApiError('missing_parameter',
'Please specify at least one of workflow_spec_id, study_id, '
'workflow_id, and task_id for this file in the HTTP parameters')
'Please specify either a workflow_spec_id or a '
'workflow_id with an optional form_field_key')
results = FileService.get_files(workflow_spec_id, study_id, workflow_id, task_id, form_field_key)
results = FileService.get_files(workflow_spec_id=workflow_spec_id,
workflow_id=workflow_id,
irb_doc_code=form_field_key)
return FileModelSchema(many=True).dump(results)
@ -25,25 +27,21 @@ def get_reference_files():
return FileModelSchema(many=True).dump(results)
def add_file(workflow_spec_id=None, study_id=None, workflow_id=None, task_id=None, form_field_key=None):
all_none = all(v is None for v in [workflow_spec_id, study_id, workflow_id, task_id, form_field_key])
missing_some = (workflow_spec_id is None) and (None in [study_id, workflow_id, form_field_key])
if all_none or missing_some:
raise ApiError('missing_parameter',
'Please specify either a workflow_spec_id or all 3 of study_id, '
'workflow_id, and field_id for this file in the HTTP parameters')
if 'file' not in connexion.request.files:
raise ApiError('invalid_file',
'Expected a file named "file" in the multipart form request')
def add_file(workflow_spec_id=None, workflow_id=None, form_field_key=None):
file = connexion.request.files['file']
if workflow_spec_id:
if workflow_id:
if form_field_key is None:
raise ApiError('invalid_workflow_file',
'When adding a workflow related file, you must specify a form_field_key')
file_model = FileService.add_workflow_file(workflow_id=workflow_id, irb_doc_code=form_field_key,
name=file.filename, content_type=file.content_type,
binary_data=file.stream.read())
elif workflow_spec_id:
workflow_spec = session.query(WorkflowSpecModel).filter_by(id=workflow_spec_id).first()
file_model = FileService.add_workflow_spec_file(workflow_spec, file.filename, file.content_type,
file.stream.read())
else:
file_model = FileService.add_form_field_file(study_id, workflow_id, task_id, form_field_key, file.filename,
file.content_type, file.stream.read())
raise ApiError("invalid_file", "You must supply either a workflow spec id or a workflow_id and form_field_key.")
return FileModelSchema().dump(file_model)

View File

@ -6,7 +6,7 @@ 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 StudySchema, StudyFilesSchema, StudyModel, Study
from crc.models.study import StudySchema, StudyModel, Study
from crc.services.study_service import StudyService
@ -65,7 +65,7 @@ def delete_study(study_id):
raise ApiError(code="study_integrity_error", message=message)
def all_studies():
def user_studies():
"""Returns all the studies associated with the current user. """
StudyService.synch_with_protocol_builder_if_enabled(g.user)
studies = StudyService.get_studies_for_user(g.user)
@ -73,8 +73,8 @@ def all_studies():
return results
def all_studies_and_files():
"""Returns all studies with submitted files"""
studies = StudyService.get_studies_with_files()
results = StudyFilesSchema(many=True).dump(studies)
def all_studies():
"""Returns all studies (regardless of user) with submitted files"""
studies = StudyService.get_all_studies_with_files()
results = StudySchema(many=True).dump(studies)
return results

View File

@ -78,10 +78,7 @@ class FileModel(db.Model):
primary_process_id = db.Column(db.String, nullable=True) # An id in the xml of BPMN documents, critical for primary BPMN.
workflow_spec_id = db.Column(db.String, db.ForeignKey('workflow_spec.id'), nullable=True)
workflow_id = db.Column(db.Integer, db.ForeignKey('workflow.id'), nullable=True)
study_id = db.Column(db.Integer, db.ForeignKey('study.id'), nullable=True)
task_id = db.Column(db.String, nullable=True)
irb_doc_code = db.Column(db.String, nullable=True) # Code reference to the irb_documents.xlsx reference file.
form_field_key = db.Column(db.String, nullable=True)
latest_version = db.Column(db.Integer, default=0)

View File

@ -40,10 +40,6 @@ class StudyModel(db.Model):
if self.on_hold:
self.protocol_builder_status = ProtocolBuilderStatus.HOLD
def files(self):
_files = FileModel.query.filter_by(workflow_id=self.workflow[0].id)
return _files
class WorkflowMetadata(object):
def __init__(self, id, name, display_name, description, spec_version, category_id, state: WorkflowState, status: WorkflowStatus,
@ -122,7 +118,7 @@ class Study(object):
self.ind_number = ind_number
self.categories = categories
self.warnings = []
self.files = []
@classmethod
def from_model(cls, study_model: StudyModel):
@ -153,6 +149,7 @@ class StudySchema(ma.Schema):
hsr_number = fields.String(allow_none=True)
sponsor = fields.String(allow_none=True)
ind_number = fields.String(allow_none=True)
files = fields.List(fields.Nested(SimpleFileSchema), dump_only=True)
class Meta:
model = Study
@ -165,14 +162,3 @@ class StudySchema(ma.Schema):
"""Can load the basic study data for updates to the database, but categories are write only"""
return Study(**data)
class StudyFilesSchema(ma.Schema):
files = fields.Method('_files')
class Meta:
model = Study
additional = ["id", "title", "last_updated", "primary_investigator_id"]
def _files(self, obj):
return [file.name for file in obj.files()]

View File

@ -36,10 +36,7 @@ Takes two arguments:
final_document_stream = self.process_template(task, study_id, workflow, *args, **kwargs)
file_name = args[0]
irb_doc_code = args[1]
FileService.add_task_file(study_id=study_id,
workflow_id=workflow_id,
workflow_spec_id=workflow.workflow_spec_id,
task_id=task.id,
FileService.add_workflow_file(workflow_id=workflow_id,
name=file_name,
content_type=CONTENT_TYPES['docx'],
binary_data=final_document_stream.read(),

View File

@ -10,7 +10,7 @@ from pandas import ExcelFile
from crc import session
from crc.api.common import ApiError
from crc.models.file import FileType, FileDataModel, FileModel, LookupFileModel, LookupDataModel
from crc.models.workflow import WorkflowSpecModel
from crc.models.workflow import WorkflowSpecModel, WorkflowModel
from crc.services.workflow_processor import WorkflowProcessor
@ -40,31 +40,27 @@ class FileService(object):
return code in df['code'].values
@staticmethod
def add_form_field_file(study_id, workflow_id, task_id, form_field_key, name, content_type, binary_data):
"""Create a new file and associate it with a user task form field within a workflow.
Please note that the form_field_key MUST be a known file in the irb_documents.xslx reference document."""
if not FileService.is_allowed_document(form_field_key):
def add_workflow_file(workflow_id, irb_doc_code, name, content_type, binary_data):
"""Create a new file and associate it with the workflow
Please note that the irb_doc_code MUST be a known file in the irb_documents.xslx reference document."""
if not FileService.is_allowed_document(irb_doc_code):
raise ApiError("invalid_form_field_key",
"When uploading files, the form field id must match a known document in the "
"irb_docunents.xslx reference file. This code is not found in that file '%s'" % form_field_key)
"irb_docunents.xslx reference file. This code is not found in that file '%s'" % irb_doc_code)
"""Assure this is unique to the workflow, task, and document code AND the Name
Because we will allow users to upload multiple files for the same form field
in some cases """
file_model = session.query(FileModel)\
.filter(FileModel.workflow_id == workflow_id)\
.filter(FileModel.task_id == str(task_id))\
.filter(FileModel.name == name)\
.filter(FileModel.irb_doc_code == form_field_key).first()
.filter(FileModel.irb_doc_code == irb_doc_code).first()
if not file_model:
file_model = FileModel(
study_id=study_id,
workflow_id=workflow_id,
task_id=task_id,
name=name,
form_field_key=form_field_key,
irb_doc_code=form_field_key
irb_doc_code=irb_doc_code
)
return FileService.update_file(file_model, binary_data, content_type)
@ -85,28 +81,6 @@ class FileService(object):
df = df.set_index(index_column)
return json.loads(df.to_json(orient='index'))
@staticmethod
def add_task_file(study_id, workflow_id, workflow_spec_id, task_id, name, content_type, binary_data,
irb_doc_code=None):
"""Assure this is unique to the workflow, task, and document code. Disregard name."""
file_model = session.query(FileModel)\
.filter(FileModel.workflow_id == workflow_id)\
.filter(FileModel.task_id == str(task_id))\
.filter(FileModel.irb_doc_code == irb_doc_code).first()
if not file_model:
"""Create a new file and associate it with an executing task within a workflow."""
file_model = FileModel(
study_id=study_id,
workflow_id=workflow_id,
workflow_spec_id=workflow_spec_id,
task_id=task_id,
name=name,
irb_doc_code=irb_doc_code
)
return FileService.update_file(file_model, binary_data, content_type)
@staticmethod
def get_workflow_files(workflow_id):
"""Returns all the file models associated with a running workflow."""
@ -179,32 +153,29 @@ class FileService(object):
return file_model
@staticmethod
def get_files(workflow_spec_id=None,
study_id=None, workflow_id=None, task_id=None, form_field_key=None,
def get_files_for_study(study_id, irb_doc_code=None):
query = session.query(FileModel).\
join(WorkflowModel).\
filter(WorkflowModel.study_id == study_id)
if irb_doc_code:
query = query.filter(FileModel.irb_doc_code == irb_doc_code)
return query.all()
@staticmethod
def get_files(workflow_spec_id=None, workflow_id=None,
name=None, is_reference=False, irb_doc_code=None):
query = session.query(FileModel).filter_by(is_reference=is_reference)
if workflow_spec_id:
query = query.filter_by(workflow_spec_id=workflow_spec_id)
if all(v is None for v in [study_id, workflow_id, task_id, form_field_key]):
query = query.filter_by(
study_id=None,
workflow_id=None,
task_id=None,
form_field_key=None,
)
else:
if study_id:
query = query.filter_by(study_id=study_id)
if workflow_id:
elif workflow_id:
query = query.filter_by(workflow_id=workflow_id)
if task_id:
query = query.filter_by(task_id=str(task_id))
if form_field_key:
query = query.filter_by(form_field_key=form_field_key)
if name:
query = query.filter_by(name=name)
if irb_doc_code:
query = query.filter_by(irb_doc_code=irb_doc_code)
elif is_reference:
query = query.filter_by(is_reference=True)
if name:
query = query.filter_by(name=name)
results = query.all()
return results

View File

@ -33,10 +33,15 @@ class StudyService(object):
return studies
@staticmethod
def get_studies_with_files():
def get_all_studies_with_files():
"""Returns a list of all studies"""
db_studies = session.query(StudyModel).all()
return db_studies
studies = []
for s in db_studies:
study = Study.from_model(s)
study.files = FileService.get_files_for_study(study.id)
studies.append(study)
return studies
@staticmethod
def get_study(study_id, study_model: StudyModel = None):
@ -48,6 +53,7 @@ class StudyService(object):
study = Study.from_model(study_model)
study.categories = StudyService.get_categories()
workflow_metas = StudyService.__get_workflow_metas(study_id)
study.files = FileService.get_files_for_study(study.id)
# Calling this line repeatedly is very very slow. It creates the
# master spec and runs it.
@ -150,17 +156,15 @@ class StudyService(object):
doc['display_name'] = ' / '.join(name_list)
# For each file, get associated workflow status
doc_files = FileService.get_files(study_id=study_id, irb_doc_code=code)
doc_files = FileService.get_files_for_study(study_id=study_id, irb_doc_code=code)
doc['count'] = len(doc_files)
doc['files'] = []
for file in doc_files:
doc['files'].append({'file_id': file.id,
'task_id': file.task_id,
'workflow_id': file.workflow_id,
'workflow_spec_id': file.workflow_spec_id})
'workflow_id': file.workflow_id})
# update the document status to match the status of the workflow it is in.
if not 'status' in doc or doc['status'] is None:
if 'status' not in doc or doc['status'] is None:
workflow: WorkflowModel = session.query(WorkflowModel).filter_by(id=file.workflow_id).first()
doc['status'] = workflow.status.value

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96a17d9" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_96a17d9" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
<bpmn:process id="Process_93a29b3" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0637d8i</bpmn:outgoing>
@ -27,7 +27,7 @@
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_1i7hk1a</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_11c35oq</bpmn:outgoing>
<bpmn:script>CompleteTemplate Letter.docx AncillaryDocument.CoCApplication</bpmn:script>
<bpmn:script>CompleteTemplate Letter.docx AD_CoCApp</bpmn:script>
</bpmn:scriptTask>
<bpmn:endEvent id="EndEvent_0evb22x">
<bpmn:incoming>SequenceFlow_11c35oq</bpmn:incoming>
@ -36,30 +36,30 @@
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_93a29b3">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0637d8i_di" bpmnElement="SequenceFlow_0637d8i">
<di:waypoint x="215" y="117" />
<di:waypoint x="265" y="117" />
<bpmndi:BPMNEdge id="SequenceFlow_11c35oq_di" bpmnElement="SequenceFlow_11c35oq">
<di:waypoint x="565" y="117" />
<di:waypoint x="665" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="UserTask_02o51o8_di" bpmnElement="task_gather_information">
<dc:Bounds x="265" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1i7hk1a_di" bpmnElement="SequenceFlow_1i7hk1a">
<di:waypoint x="365" y="117" />
<di:waypoint x="465" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0637d8i_di" bpmnElement="SequenceFlow_0637d8i">
<di:waypoint x="215" y="117" />
<di:waypoint x="265" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="UserTask_02o51o8_di" bpmnElement="task_gather_information">
<dc:Bounds x="265" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="ScriptTask_0xjh8x4_di" bpmnElement="task_generate_document">
<dc:Bounds x="465" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_0evb22x_di" bpmnElement="EndEvent_0evb22x">
<dc:Bounds x="665" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_11c35oq_di" bpmnElement="SequenceFlow_11c35oq">
<di:waypoint x="565" y="117" />
<di:waypoint x="665" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -37,9 +37,7 @@ class TestApprovalsService(BaseTest):
ApprovalService.add_approval(study_id=workflow.study_id, workflow_id=workflow.id, approver_uid="dhf8r")
irb_code_1 = "UVACompl_PRCAppr" # The first file referenced in pb required docs.
FileService.add_task_file(study_id=workflow.study_id, workflow_id=workflow.id,
workflow_spec_id=workflow.workflow_spec_id,
task_id=task.id,
FileService.add_workflow_file(workflow_id=workflow.id,
name="anything.png", content_type="text",
binary_data=b'5678', irb_doc_code=irb_code_1)
@ -59,22 +57,16 @@ class TestApprovalsService(BaseTest):
irb_code_1 = "UVACompl_PRCAppr" # The first file referenced in pb required docs.
irb_code_2 = "NonUVAIRB_AssuranceForm" # The second file in above.
# Add a task file to the workflow.
FileService.add_task_file(study_id=workflow.study_id, workflow_id=workflow.id,
workflow_spec_id=workflow.workflow_spec_id,
task_id=task.id,
FileService.add_workflow_file(workflow_id=workflow.id,
name="anything.png", content_type="text",
binary_data=b'5678', irb_doc_code=irb_code_1)
# Add a two form field files with the same irb_code, but
FileService.add_form_field_file(study_id=workflow.study_id, workflow_id=workflow.id,
task_id=task.id,
form_field_key=irb_code_2,
# Add a two form field files with the same irb_code, but different names
FileService.add_workflow_file(workflow_id=workflow.id,
name="anything.png", content_type="text",
binary_data=b'1234')
FileService.add_form_field_file(study_id=workflow.study_id, workflow_id=workflow.id,
form_field_key=irb_code_2,
task_id=task.id,
binary_data=b'1234', irb_doc_code=irb_code_2)
FileService.add_workflow_file(workflow_id=workflow.id,
name="another_anything.png", content_type="text",
binary_data=b'5678')
binary_data=b'5678', irb_doc_code=irb_code_2)
# Workflow hash should look be id[1]-id[1]-id[1]
@ -85,9 +77,8 @@ class TestApprovalsService(BaseTest):
# Replace last file
# should now be id[1]-id[1]-id[2]
FileService.add_form_field_file(study_id=workflow.study_id, workflow_id=workflow.id,
form_field_key=irb_code_2,
task_id=task.id,
FileService.add_workflow_file(workflow_id=workflow.id,
irb_doc_code=irb_code_2,
name="another_anything.png", content_type="text",
binary_data=b'9999')
self.assertRegexpMatches(ApprovalService._generate_workflow_hash(latest_files), "\d+\[1\]-\d+\[1\]-\d+\[2\]")

View File

@ -13,15 +13,11 @@ class TestFileService(BaseTest):
processor = WorkflowProcessor(workflow)
task = processor.next_task()
irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs.
FileService.add_task_file(study_id=workflow.study_id, workflow_id=workflow.id,
workflow_spec_id=workflow.workflow_spec_id,
task_id=task.id,
FileService.add_workflow_file(workflow_id=workflow.id,
name="anything.png", content_type="text",
binary_data=b'1234', irb_doc_code=irb_code)
# Add the file again with different data
FileService.add_task_file(study_id=workflow.study_id, workflow_id=workflow.id,
workflow_spec_id=workflow.workflow_spec_id,
task_id=task.id,
FileService.add_workflow_file(workflow_id=workflow.id,
name="anything.png", content_type="text",
binary_data=b'5678', irb_doc_code=irb_code)
@ -36,15 +32,13 @@ class TestFileService(BaseTest):
processor = WorkflowProcessor(workflow)
task = processor.next_task()
irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs.
FileService.add_form_field_file(study_id=workflow.study_id, workflow_id=workflow.id,
task_id=task.id,
form_field_key=irb_code,
FileService.add_workflow_file(workflow_id=workflow.id,
irb_doc_code=irb_code,
name="anything.png", content_type="text",
binary_data=b'1234')
# Add the file again with different data
FileService.add_form_field_file(study_id=workflow.study_id, workflow_id=workflow.id,
form_field_key=irb_code,
task_id=task.id,
FileService.add_workflow_file(workflow_id=workflow.id,
irb_doc_code=irb_code,
name="anything.png", content_type="text",
binary_data=b'5678')
@ -59,15 +53,13 @@ class TestFileService(BaseTest):
processor = WorkflowProcessor(workflow)
task = processor.next_task()
irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs.
FileService.add_form_field_file(study_id=workflow.study_id, workflow_id=workflow.id,
task_id=task.id,
form_field_key=irb_code,
FileService.add_workflow_file(workflow_id=workflow.id,
irb_doc_code=irb_code,
name="anything.png", content_type="text",
binary_data=b'1234')
# Add the file again with different data
FileService.add_form_field_file(study_id=workflow.study_id, workflow_id=workflow.id,
form_field_key=irb_code,
task_id=task.id,
FileService.add_workflow_file(workflow_id=workflow.id,
irb_doc_code=irb_code,
name="a_different_thing.png", content_type="text",
binary_data=b'5678')
file_models = FileService.get_workflow_files(workflow_id=workflow.id)

View File

@ -1,7 +1,7 @@
import io
import json
from datetime import datetime
from unittest.mock import patch
from tests.base_test import BaseTest
from crc import session
from crc.models.file import FileModel, FileType, FileModelSchema, FileDataModel
@ -9,7 +9,6 @@ from crc.models.workflow import WorkflowSpecModel
from crc.services.file_service import FileService
from crc.services.workflow_processor import WorkflowProcessor
from example_data import ExampleDataLoader
from tests.base_test import BaseTest
class TestFilesApi(BaseTest):

View File

@ -2,6 +2,8 @@ import json
from datetime import datetime
from unittest.mock import patch
from tests.base_test import BaseTest
from crc import db, app
from crc.models.protocol_builder import ProtocolBuilderStatus
from crc.models.study import StudyModel
@ -12,7 +14,6 @@ from crc.services.file_service import FileService
from crc.services.study_service import StudyService
from crc.services.workflow_processor import WorkflowProcessor
from example_data import ExampleDataLoader
from tests.base_test import BaseTest
class TestStudyService(BaseTest):
@ -143,9 +144,7 @@ class TestStudyService(BaseTest):
# Add a document to the study with the correct code.
workflow = self.create_workflow('docx')
irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs.
FileService.add_task_file(study_id=workflow.study_id, workflow_id=workflow.id,
workflow_spec_id=workflow.workflow_spec_id,
task_id="fakingthisout",
FileService.add_workflow_file(workflow_id=workflow.id,
name="anything.png", content_type="text",
binary_data=b'1234', irb_doc_code=irb_code)
@ -156,13 +155,31 @@ class TestStudyService(BaseTest):
self.assertIsNotNone(docs["UVACompl_PRCAppr"]['files'][0])
self.assertIsNotNone(docs["UVACompl_PRCAppr"]['files'][0]['file_id'])
self.assertEquals(workflow.id, docs["UVACompl_PRCAppr"]['files'][0]['workflow_id'])
self.assertEquals(workflow.workflow_spec_id, docs["UVACompl_PRCAppr"]['files'][0]['workflow_spec_id'])
# 'file_id': 123,
# 'task_id': 'abcdef14236890',
# 'workflow_id': 456,
# 'workflow_spec_id': 'irb_api_details',
# 'status': 'complete',
def test_get_all_studies(self):
user = self.create_user_with_study_and_workflow()
# Add a document to the study with the correct code.
workflow1 = self.create_workflow('docx')
workflow2 = self.create_workflow('empty_workflow')
# Add files to both workflows.
FileService.add_workflow_file(workflow_id=workflow1.id,
name="anything.png", content_type="text",
binary_data=b'1234', irb_doc_code="UVACompl_PRCAppr" )
FileService.add_workflow_file(workflow_id=workflow1.id,
name="anything.png", content_type="text",
binary_data=b'1234', irb_doc_code="AD_Consent_Model")
FileService.add_workflow_file(workflow_id=workflow2.id,
name="anything.png", content_type="text",
binary_data=b'1234', irb_doc_code="UVACompl_PRCAppr" )
studies = StudyService().get_all_studies_with_files()
self.assertEquals(1, len(studies))
self.assertEquals(3, len(studies[0].files))
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators') # mock_docs
def test_get_personnel(self, mock_docs):

View File

@ -223,10 +223,10 @@ class TestWorkflowProcessor(BaseTest):
self._populate_form_with_random_data(task)
processor.complete_task(task)
files = session.query(FileModel).filter_by(study_id=study.id, workflow_id=processor.get_workflow_id()).all()
files = session.query(FileModel).filter_by(workflow_id=processor.get_workflow_id()).all()
self.assertEqual(0, len(files))
processor.do_engine_steps()
files = session.query(FileModel).filter_by(study_id=study.id, workflow_id=processor.get_workflow_id()).all()
files = session.query(FileModel).filter_by(workflow_id=processor.get_workflow_id()).all()
self.assertEqual(1, len(files), "The task should create a new file.")
file_data = session.query(FileDataModel).filter(FileDataModel.file_model_id == files[0].id).first()
self.assertIsNotNone(file_data.data)