2019-12-31 21:32:47 +00:00
|
|
|
import enum
|
2020-04-22 19:37:02 +00:00
|
|
|
from typing import cast
|
2019-12-31 21:32:47 +00:00
|
|
|
|
2020-05-29 00:03:50 +00:00
|
|
|
from marshmallow import INCLUDE, EXCLUDE
|
2019-12-31 21:32:47 +00:00
|
|
|
from marshmallow_enum import EnumField
|
2020-03-16 17:37:31 +00:00
|
|
|
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
2020-05-07 17:57:24 +00:00
|
|
|
from sqlalchemy import func, Index
|
2020-03-04 18:40:25 +00:00
|
|
|
from sqlalchemy.dialects.postgresql import UUID
|
2020-05-29 05:39:39 +00:00
|
|
|
from sqlalchemy.orm import deferred
|
2019-12-31 21:32:47 +00:00
|
|
|
|
2020-05-20 21:10:22 +00:00
|
|
|
from crc import db, ma
|
2019-12-31 21:32:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
class FileType(enum.Enum):
|
2020-05-29 00:03:50 +00:00
|
|
|
bpmn = "bpmn"
|
2020-02-05 22:23:37 +00:00
|
|
|
csv = 'csv'
|
2019-12-31 21:32:47 +00:00
|
|
|
dmn = "dmn"
|
2020-02-05 22:23:37 +00:00
|
|
|
doc = "doc"
|
2020-02-04 19:26:53 +00:00
|
|
|
docx = "docx"
|
|
|
|
gif = 'gif'
|
|
|
|
jpg = 'jpg'
|
2020-02-05 22:23:37 +00:00
|
|
|
md = 'md'
|
2020-02-04 19:26:53 +00:00
|
|
|
pdf = 'pdf'
|
|
|
|
png = 'png'
|
2020-02-05 22:23:37 +00:00
|
|
|
ppt = 'ppt'
|
|
|
|
pptx = 'pptx'
|
|
|
|
rtf = 'rtf'
|
2020-02-04 19:26:53 +00:00
|
|
|
svg = "svg"
|
2020-02-05 22:23:37 +00:00
|
|
|
svg_xml = "svg+xml"
|
|
|
|
txt = 'txt'
|
|
|
|
xls = 'xls'
|
2020-02-04 19:26:53 +00:00
|
|
|
xlsx = 'xlsx'
|
2020-02-05 22:23:37 +00:00
|
|
|
xml = 'xml'
|
2020-02-04 19:26:53 +00:00
|
|
|
zip = 'zip'
|
2019-12-31 21:32:47 +00:00
|
|
|
|
2020-02-18 15:14:03 +00:00
|
|
|
|
2020-03-04 18:40:25 +00:00
|
|
|
CONTENT_TYPES = {
|
|
|
|
"bpmn": "text/xml",
|
|
|
|
"csv": "text/csv",
|
|
|
|
"dmn": "text/xml",
|
|
|
|
"doc": "application/msword",
|
|
|
|
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
|
|
"gif": "image/gif",
|
|
|
|
"jpg": "image/jpeg",
|
|
|
|
"md" : "text/plain",
|
|
|
|
"pdf": "application/pdf",
|
|
|
|
"png": "image/png",
|
|
|
|
"ppt": "application/vnd.ms-powerpoint",
|
|
|
|
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
|
|
"rtf": "application/rtf",
|
|
|
|
"svg": "image/svg+xml",
|
|
|
|
"svg_xml": "image/svg+xml",
|
|
|
|
"txt": "text/plain",
|
|
|
|
"xls": "application/vnd.ms-excel",
|
|
|
|
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
|
|
"xml": "application/xml",
|
|
|
|
"zip": "application/zip"
|
|
|
|
}
|
|
|
|
|
2020-05-29 00:03:50 +00:00
|
|
|
|
2019-12-31 21:32:47 +00:00
|
|
|
class FileDataModel(db.Model):
|
|
|
|
__tablename__ = 'file_data'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
2020-03-04 18:40:25 +00:00
|
|
|
md5_hash = db.Column(UUID(as_uuid=True), unique=False, nullable=False)
|
2020-05-29 05:39:39 +00:00
|
|
|
data = deferred(db.Column(db.LargeBinary)) # Don't load it unless you have to.
|
2020-03-04 18:40:25 +00:00
|
|
|
version = db.Column(db.Integer, default=0)
|
2020-05-29 00:03:50 +00:00
|
|
|
date_created = db.Column(db.DateTime(timezone=True), default=func.now())
|
2019-12-31 21:32:47 +00:00
|
|
|
file_model_id = db.Column(db.Integer, db.ForeignKey('file.id'))
|
2020-05-29 00:03:50 +00:00
|
|
|
file_model = db.relationship("FileModel", foreign_keys=[file_model_id])
|
2019-12-31 21:32:47 +00:00
|
|
|
|
2020-01-03 16:44:24 +00:00
|
|
|
|
2019-12-31 21:32:47 +00:00
|
|
|
class FileModel(db.Model):
|
|
|
|
__tablename__ = 'file'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
name = db.Column(db.String)
|
|
|
|
type = db.Column(db.Enum(FileType))
|
2020-03-13 18:56:46 +00:00
|
|
|
is_status = db.Column(db.Boolean)
|
2019-12-31 21:32:47 +00:00
|
|
|
content_type = db.Column(db.String)
|
2020-03-13 19:03:57 +00:00
|
|
|
is_reference = db.Column(db.Boolean, nullable=False, default=False) # A global reference file.
|
2020-03-20 12:21:21 +00:00
|
|
|
primary = db.Column(db.Boolean, nullable=False, default=False) # Is this the primary BPMN in a workflow?
|
2020-04-17 17:30:32 +00:00
|
|
|
primary_process_id = db.Column(db.String, nullable=True) # An id in the xml of BPMN documents, critical for primary BPMN.
|
2020-02-04 14:57:02 +00:00
|
|
|
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)
|
2020-03-19 21:13:30 +00:00
|
|
|
irb_doc_code = db.Column(db.String, nullable=True) # Code reference to the irb_documents.xlsx reference file.
|
2020-06-03 21:34:27 +00:00
|
|
|
# A request was made to delete the file, but we can't because there are
|
|
|
|
# active approvals or running workflows that depend on it. So we archive
|
|
|
|
# it instead, hide it in the interface.
|
2020-06-04 14:09:36 +00:00
|
|
|
archived = db.Column(db.Boolean, default=False, nullable=False)
|
2019-12-31 21:32:47 +00:00
|
|
|
|
2020-05-29 00:03:50 +00:00
|
|
|
class File(object):
|
|
|
|
@classmethod
|
2020-06-01 01:15:40 +00:00
|
|
|
def from_models(cls, model: FileModel, data_model: FileDataModel, doc_dictionary):
|
2020-05-29 00:03:50 +00:00
|
|
|
instance = cls()
|
|
|
|
instance.id = model.id
|
|
|
|
instance.name = model.name
|
|
|
|
instance.is_status = model.is_status
|
|
|
|
instance.is_reference = model.is_reference
|
|
|
|
instance.content_type = model.content_type
|
|
|
|
instance.primary = model.primary
|
|
|
|
instance.primary_process_id = model.primary_process_id
|
|
|
|
instance.workflow_spec_id = model.workflow_spec_id
|
|
|
|
instance.workflow_id = model.workflow_id
|
|
|
|
instance.irb_doc_code = model.irb_doc_code
|
|
|
|
instance.type = model.type
|
2020-06-01 01:15:40 +00:00
|
|
|
if model.irb_doc_code and model.irb_doc_code in doc_dictionary:
|
|
|
|
instance.category = "/".join(filter(None, [doc_dictionary[model.irb_doc_code]['category1'],
|
|
|
|
doc_dictionary[model.irb_doc_code]['category2'],
|
|
|
|
doc_dictionary[model.irb_doc_code]['category3']]))
|
|
|
|
instance.description = doc_dictionary[model.irb_doc_code]['description']
|
|
|
|
instance.download_name = ".".join([instance.category, model.type.value])
|
|
|
|
else:
|
|
|
|
instance.category = ""
|
|
|
|
instance.description = ""
|
2020-05-29 00:03:50 +00:00
|
|
|
if data_model:
|
|
|
|
instance.last_modified = data_model.date_created
|
|
|
|
instance.latest_version = data_model.version
|
|
|
|
else:
|
|
|
|
instance.last_modified = None
|
|
|
|
instance.latest_version = None
|
|
|
|
return instance
|
2020-05-23 19:08:17 +00:00
|
|
|
|
2020-03-16 17:37:31 +00:00
|
|
|
class FileModelSchema(SQLAlchemyAutoSchema):
|
2019-12-31 21:32:47 +00:00
|
|
|
class Meta:
|
|
|
|
model = FileModel
|
2020-03-16 17:37:31 +00:00
|
|
|
load_instance = True
|
|
|
|
include_relationships = True
|
2020-01-13 22:52:37 +00:00
|
|
|
include_fk = True # Includes foreign keys
|
2020-05-29 00:03:50 +00:00
|
|
|
unknown = EXCLUDE
|
|
|
|
type = EnumField(FileType)
|
|
|
|
|
|
|
|
|
|
|
|
class FileSchema(ma.Schema):
|
|
|
|
class Meta:
|
|
|
|
model = File
|
|
|
|
fields = ["id", "name", "is_status", "is_reference", "content_type",
|
|
|
|
"primary", "primary_process_id", "workflow_spec_id", "workflow_id",
|
2020-06-01 01:15:40 +00:00
|
|
|
"irb_doc_code", "last_modified", "latest_version", "type", "categories",
|
|
|
|
"description", "category", "description", "download_name"]
|
2020-05-29 00:03:50 +00:00
|
|
|
unknown = INCLUDE
|
2019-12-31 21:32:47 +00:00
|
|
|
type = EnumField(FileType)
|
2020-04-22 19:37:02 +00:00
|
|
|
|
|
|
|
|
|
|
|
class LookupFileModel(db.Model):
|
2020-05-29 05:39:39 +00:00
|
|
|
"""Gives us a quick way to tell what kind of lookup is set on a form field.
|
|
|
|
Connected to the file data model, so that if a new version of the same file is
|
|
|
|
created, we can update the listing."""
|
|
|
|
#fixme: What happens if they change the file associated with a lookup field?
|
2020-04-22 19:37:02 +00:00
|
|
|
__tablename__ = 'lookup_file'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
2020-05-29 05:39:39 +00:00
|
|
|
workflow_spec_id = db.Column(db.String)
|
|
|
|
field_id = db.Column(db.String)
|
|
|
|
is_ldap = db.Column(db.Boolean) # Allows us to run an ldap query instead of a db lookup.
|
2020-04-22 19:37:02 +00:00
|
|
|
file_data_model_id = db.Column(db.Integer, db.ForeignKey('file_data.id'))
|
2020-05-29 05:39:39 +00:00
|
|
|
dependencies = db.relationship("LookupDataModel", lazy="select", backref="lookup_file_model", cascade="all, delete, delete-orphan")
|
2020-04-22 19:37:02 +00:00
|
|
|
|
|
|
|
class LookupDataModel(db.Model):
|
|
|
|
__tablename__ = 'lookup_data'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
lookup_file_model_id = db.Column(db.Integer, db.ForeignKey('lookup_file.id'))
|
|
|
|
value = db.Column(db.String)
|
|
|
|
label = db.Column(db.String)
|
|
|
|
# In the future, we might allow adding an additional "search" column if we want to search things not in label.
|
|
|
|
data = db.Column(db.JSON) # all data for the row is stored in a json structure here, but not searched presently.
|
|
|
|
|
|
|
|
# Assure there is a searchable index on the label column, so we can get fast results back.
|
|
|
|
# query with:
|
|
|
|
# search_results = LookupDataModel.query.filter(LookupDataModel.label.match("INTERNAL")).all()
|
|
|
|
|
|
|
|
__table_args__ = (
|
|
|
|
Index(
|
|
|
|
'ix_lookupdata_tsv',
|
2020-04-23 16:05:08 +00:00
|
|
|
func.to_tsvector('simple', label), # Use simple, not english to keep stop words in place.
|
2020-04-22 19:37:02 +00:00
|
|
|
postgresql_using='gin'
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2020-04-22 23:40:40 +00:00
|
|
|
|
|
|
|
class LookupDataSchema(SQLAlchemyAutoSchema):
|
|
|
|
class Meta:
|
|
|
|
model = LookupDataModel
|
|
|
|
load_instance = True
|
|
|
|
include_relationships = False
|
|
|
|
include_fk = False # Includes foreign keys
|
|
|
|
|
2020-05-20 21:10:22 +00:00
|
|
|
|
|
|
|
class SimpleFileSchema(ma.Schema):
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = FileModel
|
|
|
|
fields = ["name"]
|