Midway through disaster of changing table names.
This commit is contained in:
@@ -1,46 +0,0 @@
|
|||||||
"""Adding in processes
|
|
||||||
|
|
||||||
Revision ID: 10c47a04559d
|
|
||||||
Revises: 94289d4e63e6
|
|
||||||
Create Date: 2024-01-05 13:25:02.468436
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '10c47a04559d'
|
|
||||||
down_revision = '94289d4e63e6'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('_process',
|
|
||||||
sa.Column('id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('name', sa.String(length=64), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.create_table('_equipmentroles_processes',
|
|
||||||
sa.Column('process_id', sa.INTEGER(), nullable=True),
|
|
||||||
sa.Column('equipmentroles_id', sa.INTEGER(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['equipmentroles_id'], ['_equipment_roles.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['process_id'], ['_process.id'], )
|
|
||||||
)
|
|
||||||
op.create_table('_submissiontypes_processes',
|
|
||||||
sa.Column('process_id', sa.INTEGER(), nullable=True),
|
|
||||||
sa.Column('equipmentroles_id', sa.INTEGER(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['equipmentroles_id'], ['_submission_types.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['process_id'], ['_process.id'], )
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('_submissiontypes_processes')
|
|
||||||
op.drop_table('_equipmentroles_processes')
|
|
||||||
op.drop_table('_process')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
"""SubmissionReagentAssociations added
|
|
||||||
|
|
||||||
Revision ID: 238c3c3e5863
|
|
||||||
Revises: 2684f065037c
|
|
||||||
Create Date: 2023-12-05 12:57:17.446606
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '238c3c3e5863'
|
|
||||||
down_revision = '2684f065037c'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_reagents_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('comments', sa.String(length=1024), nullable=True))
|
|
||||||
batch_op.alter_column('reagent_id',
|
|
||||||
existing_type=sa.INTEGER(),
|
|
||||||
nullable=False)
|
|
||||||
batch_op.alter_column('submission_id',
|
|
||||||
existing_type=sa.INTEGER(),
|
|
||||||
nullable=False)
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_reagents_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.alter_column('submission_id',
|
|
||||||
existing_type=sa.INTEGER(),
|
|
||||||
nullable=True)
|
|
||||||
batch_op.alter_column('reagent_id',
|
|
||||||
existing_type=sa.INTEGER(),
|
|
||||||
nullable=True)
|
|
||||||
batch_op.drop_column('comments')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
"""link controls with Bacterial Culture Samples
|
|
||||||
|
|
||||||
Revision ID: 2684f065037c
|
|
||||||
Revises: 7e7b6eeca468
|
|
||||||
Create Date: 2023-12-05 10:29:31.126732
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '2684f065037c'
|
|
||||||
down_revision = '7e7b6eeca468'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_control_samples', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('sample_id', sa.INTEGER(), nullable=True))
|
|
||||||
batch_op.create_foreign_key('cont_BCS_id', '_samples', ['sample_id'], ['id'], ondelete='SET NULL')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_control_samples', schema=None) as batch_op:
|
|
||||||
batch_op.drop_constraint('cont_BCS_id', type_='foreignkey')
|
|
||||||
batch_op.drop_column('sample_id')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
"""Adding role tag to SubmissionEquipmentAssociation
|
|
||||||
|
|
||||||
Revision ID: 30aab47d6f12
|
|
||||||
Revises: bc7a74476609
|
|
||||||
Create Date: 2023-12-27 09:00:26.262904
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '30aab47d6f12'
|
|
||||||
down_revision = 'bc7a74476609'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
# op.drop_table('_alembic_tmp__equipment')
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('role', sa.String(length=64), nullable=True))
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.drop_column('role')
|
|
||||||
|
|
||||||
# op.create_table('_alembic_tmp__equipment',
|
|
||||||
# sa.Column('id', sa.INTEGER(), nullable=False),
|
|
||||||
# sa.Column('name', sa.VARCHAR(length=64), nullable=True),
|
|
||||||
# sa.Column('nickname', sa.VARCHAR(length=64), nullable=True),
|
|
||||||
# sa.Column('asset_number', sa.VARCHAR(length=16), nullable=True),
|
|
||||||
# sa.PrimaryKeyConstraint('id')
|
|
||||||
# )
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
"""Adding in Equipment
|
|
||||||
|
|
||||||
Revision ID: 36a47d8837ca
|
|
||||||
Revises: 238c3c3e5863
|
|
||||||
Create Date: 2023-12-12 09:16:09.559753
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '36a47d8837ca'
|
|
||||||
down_revision = '238c3c3e5863'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('_equipment',
|
|
||||||
sa.Column('id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('name', sa.String(length=64), nullable=True),
|
|
||||||
sa.Column('nickname', sa.String(length=64), nullable=True),
|
|
||||||
sa.Column('asset_number', sa.String(length=16), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.create_table('_submissiontype_equipment',
|
|
||||||
sa.Column('equipment_id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('submission_id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('uses', sa.JSON(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['equipment_id'], ['_equipment.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['submission_id'], ['_submission_types.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('equipment_id', 'submission_id')
|
|
||||||
)
|
|
||||||
op.create_table('_equipment_submissions',
|
|
||||||
sa.Column('equipment_id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('submission_id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('comments', sa.String(length=1024), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['equipment_id'], ['_equipment.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['submission_id'], ['_submissions.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('equipment_id', 'submission_id')
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.drop_table('_equipment_submissions')
|
|
||||||
op.drop_table('_submissiontype_equipment')
|
|
||||||
op.drop_table('_equipment')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
"""Adding times to equipSubAssoc
|
|
||||||
|
|
||||||
Revision ID: 3e94fecbbe91
|
|
||||||
Revises: cd5c225b5d2a
|
|
||||||
Create Date: 2023-12-15 09:38:33.931976
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '3e94fecbbe91'
|
|
||||||
down_revision = 'cd5c225b5d2a'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('start_time', sa.TIMESTAMP(), nullable=True))
|
|
||||||
batch_op.add_column(sa.Column('end_time', sa.TIMESTAMP(), nullable=True))
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.drop_column('end_time')
|
|
||||||
batch_op.drop_column('start_time')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
"""Adding SubmissionType to KitReagentTypeAssociations
|
|
||||||
|
|
||||||
Revision ID: 4606a7be32e8
|
|
||||||
Revises: 67fa77849024
|
|
||||||
Create Date: 2024-01-08 09:04:46.917615
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '4606a7be32e8'
|
|
||||||
down_revision = '67fa77849024'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_reagenttypes_kittypes', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('submission_type_id', sa.INTEGER(), nullable=False))
|
|
||||||
batch_op.create_foreign_key("st_kt_rt_assoc", '_submission_types', ['submission_type_id'], ['id'])
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_reagenttypes_kittypes', schema=None) as batch_op:
|
|
||||||
batch_op.drop_constraint(None, type_='foreignkey')
|
|
||||||
batch_op.drop_column('submission_type_id')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
"""Adjusting process-submissionequipassoc
|
|
||||||
|
|
||||||
Revision ID: 67fa77849024
|
|
||||||
Revises: e08a69a0f381
|
|
||||||
Create Date: 2024-01-05 15:06:24.305945
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '67fa77849024'
|
|
||||||
down_revision = 'e08a69a0f381'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('process_id', sa.INTEGER(), nullable=True))
|
|
||||||
# batch_op.drop_constraint(None, type_='foreignkey')
|
|
||||||
batch_op.create_foreign_key('SEA_Process_id', '_process', ['process_id'], ['id'], ondelete='SET NULL')
|
|
||||||
batch_op.drop_column('process')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('process', sa.VARCHAR(length=64), nullable=True))
|
|
||||||
batch_op.drop_constraint('SEA_Process_id', type_='foreignkey')
|
|
||||||
batch_op.create_foreign_key(None, '_process', ['process'], ['id'], ondelete='SET NULL')
|
|
||||||
batch_op.drop_column('process_id')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
"""Adding equipment clustering
|
|
||||||
|
|
||||||
Revision ID: 761baf9d7842
|
|
||||||
Revises: 3e94fecbbe91
|
|
||||||
Create Date: 2023-12-18 14:31:21.533319
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '761baf9d7842'
|
|
||||||
down_revision = '3e94fecbbe91'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('cluster_name', sa.String(length=16), nullable=True))
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment', schema=None) as batch_op:
|
|
||||||
batch_op.drop_column('cluster_name')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
"""add templates to submission types
|
|
||||||
|
|
||||||
Revision ID: 7e7b6eeca468
|
|
||||||
Revises:
|
|
||||||
Create Date: 2023-11-23 08:07:51.103392
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '7e7b6eeca468'
|
|
||||||
down_revision = None
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_submission_types', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('template_file', sa.BLOB(), nullable=True))
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_submission_types', schema=None) as batch_op:
|
|
||||||
batch_op.drop_column('template_file')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
"""Updating primary key for SubmissionEquipmentAssociation
|
|
||||||
|
|
||||||
Revision ID: 94289d4e63e6
|
|
||||||
Revises: 30aab47d6f12
|
|
||||||
Create Date: 2024-01-03 15:14:21.156127
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = '94289d4e63e6'
|
|
||||||
down_revision = '30aab47d6f12'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.alter_column('role',
|
|
||||||
existing_type=sa.VARCHAR(length=64),
|
|
||||||
nullable=False)
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.alter_column('role',
|
|
||||||
existing_type=sa.VARCHAR(length=64),
|
|
||||||
nullable=True)
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
"""Adding equipment roles
|
|
||||||
|
|
||||||
Revision ID: bc7a74476609
|
|
||||||
Revises: 761baf9d7842
|
|
||||||
Create Date: 2023-12-21 10:44:23.520392
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
from sqlalchemy.dialects import sqlite
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'bc7a74476609'
|
|
||||||
down_revision = '761baf9d7842'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('_equipment_roles',
|
|
||||||
sa.Column('id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('name', sa.String(length=32), nullable=True),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
op.create_table('_equipmentroles_equipment',
|
|
||||||
sa.Column('equipment_id', sa.INTEGER(), nullable=True),
|
|
||||||
sa.Column('equipmentroles_id', sa.INTEGER(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['equipment_id'], ['_equipment.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['equipmentroles_id'], ['_equipment_roles.id'], )
|
|
||||||
)
|
|
||||||
op.create_table('_submissiontype_equipmentrole',
|
|
||||||
sa.Column('equipmentrole_id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('submissiontype_id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('uses', sa.JSON(), nullable=True),
|
|
||||||
sa.Column('static', sa.INTEGER(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['equipmentrole_id'], ['_equipment_roles.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['submissiontype_id'], ['_submission_types.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('equipmentrole_id', 'submissiontype_id')
|
|
||||||
)
|
|
||||||
op.drop_table('_submissiontype_equipment')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
op.create_table('_submissiontype_equipment',
|
|
||||||
sa.Column('equipment_id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('submissiontype_id', sa.INTEGER(), nullable=False),
|
|
||||||
sa.Column('uses', sqlite.JSON(), nullable=True),
|
|
||||||
sa.Column('static', sa.INTEGER(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['equipment_id'], ['_equipment.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['submissiontype_id'], ['_submission_types.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('equipment_id', 'submissiontype_id')
|
|
||||||
)
|
|
||||||
op.drop_table('_submissiontype_equipmentrole')
|
|
||||||
op.drop_table('_equipmentroles_equipment')
|
|
||||||
op.drop_table('_equipment_roles')
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
"""Adding static option to equipSTASsoc
|
|
||||||
|
|
||||||
Revision ID: cd11db3794ed
|
|
||||||
Revises: 36a47d8837ca
|
|
||||||
Create Date: 2023-12-12 14:47:20.924443
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'cd11db3794ed'
|
|
||||||
down_revision = '36a47d8837ca'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_submissiontype_equipment', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('static', sa.INTEGER(), nullable=True))
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_submissiontype_equipment', schema=None) as batch_op:
|
|
||||||
batch_op.drop_column('static')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
"""Adding process to equipSubAssoc
|
|
||||||
|
|
||||||
Revision ID: cd5c225b5d2a
|
|
||||||
Revises: cd11db3794ed
|
|
||||||
Create Date: 2023-12-15 09:13:23.492512
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'cd5c225b5d2a'
|
|
||||||
down_revision = 'cd11db3794ed'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.add_column(sa.Column('process', sa.String(length=64), nullable=True))
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.drop_column('process')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
"""Attaching Process to SubmissionEquipmentAssociation
|
|
||||||
|
|
||||||
Revision ID: e08a69a0f381
|
|
||||||
Revises: 10c47a04559d
|
|
||||||
Create Date: 2024-01-05 14:50:55.681167
|
|
||||||
|
|
||||||
"""
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
|
||||||
revision = 'e08a69a0f381'
|
|
||||||
down_revision = '10c47a04559d'
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.create_foreign_key('SEA_Process_id', '_process', ['process'], ['id'], ondelete='SET NULL')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
|
||||||
with op.batch_alter_table('_equipment_submissions', schema=None) as batch_op:
|
|
||||||
batch_op.drop_constraint('SEA_Process_id', type_='foreignkey')
|
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
|
||||||
@@ -4,7 +4,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
# Version of the realpython-reader package
|
# Version of the realpython-reader package
|
||||||
__project__ = "submissions"
|
__project__ = "submissions"
|
||||||
__version__ = "202401.1b"
|
__version__ = "202401.2b"
|
||||||
__author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"}
|
__author__ = {"name":"Landon Wark", "email":"Landon.Wark@phac-aspc.gc.ca"}
|
||||||
__copyright__ = "2022-2024, Government of Canada"
|
__copyright__ = "2022-2024, Government of Canada"
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ class BaseClass(Base):
|
|||||||
|
|
||||||
__table_args__ = {'extend_existing': True}
|
__table_args__ = {'extend_existing': True}
|
||||||
|
|
||||||
|
@declared_attr
|
||||||
|
def __tablename__(cls):
|
||||||
|
return f"_{cls.__name__.lower()}"
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def __database_session__(cls):
|
def __database_session__(cls):
|
||||||
if not 'pytest' in sys.modules:
|
if not 'pytest' in sys.modules:
|
||||||
@@ -45,6 +49,15 @@ class BaseClass(Base):
|
|||||||
from test_settings import ctx
|
from test_settings import ctx
|
||||||
return ctx.backup_path
|
return ctx.backup_path
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
logger.debug(f"Saving {self}")
|
||||||
|
try:
|
||||||
|
self.__database_session__.add(self)
|
||||||
|
self.__database_session__.commit()
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(f"Problem saving object: {e}")
|
||||||
|
self.__database_session__.rollback()
|
||||||
|
|
||||||
from .controls import *
|
from .controls import *
|
||||||
# import order must go: orgs, kit, subs due to circular import issues
|
# import order must go: orgs, kit, subs due to circular import issues
|
||||||
from .organizations import *
|
from .organizations import *
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class ControlType(BaseClass):
|
|||||||
"""
|
"""
|
||||||
Base class of a control archetype.
|
Base class of a control archetype.
|
||||||
"""
|
"""
|
||||||
__tablename__ = '_control_types'
|
# __tablename__ = '_control_types'
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(255), unique=True) #: controltype name (e.g. MCS)
|
name = Column(String(255), unique=True) #: controltype name (e.g. MCS)
|
||||||
@@ -75,7 +75,7 @@ class Control(BaseClass):
|
|||||||
Base class of a control sample.
|
Base class of a control sample.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = '_control_samples'
|
# __tablename__ = '_control_samples'
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
parent_id = Column(String, ForeignKey("_control_types.id", name="fk_control_parent_id")) #: primary key of control type
|
parent_id = Column(String, ForeignKey("_control_types.id", name="fk_control_parent_id")) #: primary key of control type
|
||||||
@@ -265,6 +265,3 @@ class Control(BaseClass):
|
|||||||
pass
|
pass
|
||||||
return query_return(query=query, limit=limit)
|
return query_return(query=query, limit=limit)
|
||||||
|
|
||||||
def save(self):
|
|
||||||
self.__database_session__.add(self)
|
|
||||||
self.__database_session__.commit()
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from typing import List
|
|||||||
from pandas import ExcelFile
|
from pandas import ExcelFile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from . import Base, BaseClass, Organization
|
from . import Base, BaseClass, Organization
|
||||||
from tools import Settings
|
|
||||||
|
|
||||||
logger = logging.getLogger(f'submissions.{__name__}')
|
logger = logging.getLogger(f'submissions.{__name__}')
|
||||||
|
|
||||||
@@ -32,11 +31,19 @@ equipmentroles_equipment = Table(
|
|||||||
extend_existing=True
|
extend_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
equipment_processes = Table(
|
||||||
|
"_equipment_processes",
|
||||||
|
Base.metadata,
|
||||||
|
Column("process_id", INTEGER, ForeignKey("_process.id")),
|
||||||
|
Column("equipment_id", INTEGER, ForeignKey("_equipment.id")),
|
||||||
|
extend_existing=True
|
||||||
|
)
|
||||||
|
|
||||||
equipmentroles_processes = Table(
|
equipmentroles_processes = Table(
|
||||||
"_equipmentroles_processes",
|
"_equipmentroles_processes",
|
||||||
Base.metadata,
|
Base.metadata,
|
||||||
Column("process_id", INTEGER, ForeignKey("_process.id")),
|
Column("process_id", INTEGER, ForeignKey("_process.id")),
|
||||||
Column("equipmentroles_id", INTEGER, ForeignKey("_equipment_roles.id")),
|
Column("equipmentrole_id", INTEGER, ForeignKey("_equipment_roles.id")),
|
||||||
extend_existing=True
|
extend_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,16 +55,24 @@ submissiontypes_processes = Table(
|
|||||||
extend_existing=True
|
extend_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
kittypes_processes = Table(
|
||||||
|
"_kittypes_processes",
|
||||||
|
Base.metadata,
|
||||||
|
Column("process_id", INTEGER, ForeignKey("_process.id")),
|
||||||
|
Column("kit_id", INTEGER, ForeignKey("_kits.id")),
|
||||||
|
extend_existing=True
|
||||||
|
)
|
||||||
|
|
||||||
class KitType(BaseClass):
|
class KitType(BaseClass):
|
||||||
"""
|
"""
|
||||||
Base of kits used in submission processing
|
Base of kits used in submission processing
|
||||||
"""
|
"""
|
||||||
__tablename__ = "_kits"
|
# __tablename__ = "_kits"
|
||||||
# __table_args__ = {'extend_existing': True}
|
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(64), unique=True) #: name of kit
|
name = Column(String(64), unique=True) #: name of kit
|
||||||
submissions = relationship("BasicSubmission", back_populates="extraction_kit") #: submissions this kit was used for
|
submissions = relationship("BasicSubmission", back_populates="extraction_kit") #: submissions this kit was used for
|
||||||
|
processes = relationship("Process", back_populates="kit_types", secondary=kittypes_processes)
|
||||||
|
|
||||||
kit_reagenttype_associations = relationship(
|
kit_reagenttype_associations = relationship(
|
||||||
"KitTypeReagentTypeAssociation",
|
"KitTypeReagentTypeAssociation",
|
||||||
@@ -87,7 +102,7 @@ class KitType(BaseClass):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
required (bool, optional): If true only return required types. Defaults to False.
|
required (bool, optional): If true only return required types. Defaults to False.
|
||||||
submission_type (str | None, optional): Submission type to narrow results. Defaults to None.
|
submission_type (str | Submissiontype | None, optional): Submission type to narrow results. Defaults to None.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: List of reagent types
|
list: List of reagent types
|
||||||
@@ -109,12 +124,13 @@ class KitType(BaseClass):
|
|||||||
Creates map of locations in excel workbook for a SubmissionType
|
Creates map of locations in excel workbook for a SubmissionType
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
use (str): Submissiontype.name
|
use (str | SubmissionType): Submissiontype.name
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Dictionary containing information locations.
|
dict: Dictionary containing information locations.
|
||||||
"""
|
"""
|
||||||
map = {}
|
map = {}
|
||||||
|
# Account for submission_type variable type.
|
||||||
match submission_type:
|
match submission_type:
|
||||||
case str():
|
case str():
|
||||||
assocs = [item for item in self.kit_reagenttype_associations if item.submission_type.name==submission_type]
|
assocs = [item for item in self.kit_reagenttype_associations if item.submission_type.name==submission_type]
|
||||||
@@ -125,7 +141,6 @@ class KitType(BaseClass):
|
|||||||
case _:
|
case _:
|
||||||
raise ValueError(f"Wrong variable type: {type(submission_type)} used!")
|
raise ValueError(f"Wrong variable type: {type(submission_type)} used!")
|
||||||
# Get all KitTypeReagentTypeAssociation for SubmissionType
|
# Get all KitTypeReagentTypeAssociation for SubmissionType
|
||||||
# assocs = [item for item in self.kit_reagenttype_associations if item.submission_type==submission_type]
|
|
||||||
for assoc in assocs:
|
for assoc in assocs:
|
||||||
try:
|
try:
|
||||||
map[assoc.reagent_type.name] = assoc.uses
|
map[assoc.reagent_type.name] = assoc.uses
|
||||||
@@ -133,7 +148,6 @@ class KitType(BaseClass):
|
|||||||
continue
|
continue
|
||||||
# Get SubmissionType info map
|
# Get SubmissionType info map
|
||||||
try:
|
try:
|
||||||
# st_assoc = [item for item in self.used_for if use == item.name][0]
|
|
||||||
map['info'] = st_assoc.info_map
|
map['info'] = st_assoc.info_map
|
||||||
except IndexError as e:
|
except IndexError as e:
|
||||||
map['info'] = {}
|
map['info'] = {}
|
||||||
@@ -152,7 +166,7 @@ class KitType(BaseClass):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (str, optional): Name of desired kit (returns single instance). Defaults to None.
|
name (str, optional): Name of desired kit (returns single instance). Defaults to None.
|
||||||
used_for (str | models.Submissiontype | None, optional): Submission type the kit is used for. Defaults to None.
|
used_for (str | Submissiontype | None, optional): Submission type the kit is used for. Defaults to None.
|
||||||
id (int | None, optional): Kit id in the database. Defaults to None.
|
id (int | None, optional): Kit id in the database. Defaults to None.
|
||||||
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
||||||
|
|
||||||
@@ -190,17 +204,13 @@ class KitType(BaseClass):
|
|||||||
|
|
||||||
@check_authorization
|
@check_authorization
|
||||||
def save(self, ctx:Settings):
|
def save(self, ctx:Settings):
|
||||||
"""
|
super().save()
|
||||||
Add this instance to database and commit
|
|
||||||
"""
|
|
||||||
self.__database_session__.add(self)
|
|
||||||
self.__database_session__.commit()
|
|
||||||
|
|
||||||
class ReagentType(BaseClass):
|
class ReagentType(BaseClass):
|
||||||
"""
|
"""
|
||||||
Base of reagent type abstract
|
Base of reagent type abstract
|
||||||
"""
|
"""
|
||||||
__tablename__ = "_reagent_types"
|
# __tablename__ = "_reagent_types"
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(64)) #: name of reagent type
|
name = Column(String(64)) #: name of reagent type
|
||||||
@@ -282,129 +292,15 @@ class ReagentType(BaseClass):
|
|||||||
from backend.validators.pydant import PydReagent
|
from backend.validators.pydant import PydReagent
|
||||||
return PydReagent(lot=None, type=self.name, name=self.name, expiry=date.today())
|
return PydReagent(lot=None, type=self.name, name=self.name, expiry=date.today())
|
||||||
|
|
||||||
# class KitTypeReagentTypeAssociation(BaseClass):
|
@check_authorization
|
||||||
# """
|
def save(self, ctx:Settings):
|
||||||
# table containing reagenttype/kittype associations
|
super().save()
|
||||||
# DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
|
||||||
# """
|
|
||||||
# __tablename__ = "_reagenttypes_kittypes"
|
|
||||||
|
|
||||||
# reagent_types_id = Column(INTEGER, ForeignKey("_reagent_types.id"), primary_key=True) #: id of associated reagent type
|
|
||||||
# kits_id = Column(INTEGER, ForeignKey("_kits.id"), primary_key=True) #: id of associated reagent type
|
|
||||||
# submission_type_id = (Column(INTEGER), ForeignKey("_submission_types.id"), primary_key=True)
|
|
||||||
# uses = Column(JSON) #: map to location on excel sheets of different submission types
|
|
||||||
# required = Column(INTEGER) #: whether the reagent type is required for the kit (Boolean 1 or 0)
|
|
||||||
# last_used = Column(String(32)) #: last used lot number of this type of reagent
|
|
||||||
|
|
||||||
# kit_type = relationship(KitType, back_populates="kit_reagenttype_associations") #: relationship to associated kit
|
|
||||||
|
|
||||||
# # reference to the "ReagentType" object
|
|
||||||
# reagent_type = relationship(ReagentType, back_populates="reagenttype_kit_associations") #: relationship to associated reagent type
|
|
||||||
|
|
||||||
# submission_type = relationship(SubmissionType, back_populates="submissiontype_kit_rt_associations")
|
|
||||||
|
|
||||||
# def __init__(self, kit_type=None, reagent_type=None, uses=None, required=1):
|
|
||||||
# # logger.debug(f"Parameters: Kit={kit_type}, RT={reagent_type}, Uses={uses}, Required={required}")
|
|
||||||
# self.kit_type = kit_type
|
|
||||||
# self.reagent_type = reagent_type
|
|
||||||
# self.uses = uses
|
|
||||||
# self.required = required
|
|
||||||
|
|
||||||
# def __repr__(self) -> str:
|
|
||||||
# return f"<KitTypeReagentTypeAssociation({self.kit_type} & {self.reagent_type})>"
|
|
||||||
|
|
||||||
# @validates('required')
|
|
||||||
# def validate_age(self, key, value):
|
|
||||||
# """
|
|
||||||
# Ensures only 1 & 0 used in 'required'
|
|
||||||
|
|
||||||
# Args:
|
|
||||||
# key (str): name of attribute
|
|
||||||
# value (_type_): value of attribute
|
|
||||||
|
|
||||||
# Raises:
|
|
||||||
# ValueError: Raised if bad value given
|
|
||||||
|
|
||||||
# Returns:
|
|
||||||
# _type_: value
|
|
||||||
# """
|
|
||||||
# if not 0 <= value < 2:
|
|
||||||
# raise ValueError(f'Invalid required value {value}. Must be 0 or 1.')
|
|
||||||
# return value
|
|
||||||
|
|
||||||
# @validates('reagenttype')
|
|
||||||
# def validate_reagenttype(self, key, value):
|
|
||||||
# """
|
|
||||||
# Ensures reagenttype is an actual ReagentType
|
|
||||||
|
|
||||||
# Args:
|
|
||||||
# key (str)): name of attribute
|
|
||||||
# value (_type_): value of attribute
|
|
||||||
|
|
||||||
# Raises:
|
|
||||||
# ValueError: raised if reagenttype is not a ReagentType
|
|
||||||
|
|
||||||
# Returns:
|
|
||||||
# _type_: ReagentType
|
|
||||||
# """
|
|
||||||
# if not isinstance(value, ReagentType):
|
|
||||||
# raise ValueError(f'{value} is not a reagenttype')
|
|
||||||
# return value
|
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# @setup_lookup
|
|
||||||
# def query(cls,
|
|
||||||
# kit_type:KitType|str|None=None,
|
|
||||||
# reagent_type:ReagentType|str|None=None,
|
|
||||||
# limit:int=0
|
|
||||||
# ) -> KitTypeReagentTypeAssociation|List[KitTypeReagentTypeAssociation]:
|
|
||||||
# """
|
|
||||||
# Lookup junction of ReagentType and KitType
|
|
||||||
|
|
||||||
# Args:
|
|
||||||
# kit_type (models.KitType | str | None): KitType of interest.
|
|
||||||
# reagent_type (models.ReagentType | str | None): ReagentType of interest.
|
|
||||||
# limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
|
||||||
|
|
||||||
# Returns:
|
|
||||||
# models.KitTypeReagentTypeAssociation|List[models.KitTypeReagentTypeAssociation]: Junction of interest.
|
|
||||||
# """
|
|
||||||
# query: Query = cls.__database_session__.query(cls)
|
|
||||||
# match kit_type:
|
|
||||||
# case KitType():
|
|
||||||
# query = query.filter(cls.kit_type==kit_type)
|
|
||||||
# case str():
|
|
||||||
# query = query.join(KitType).filter(KitType.name==kit_type)
|
|
||||||
# case _:
|
|
||||||
# pass
|
|
||||||
# match reagent_type:
|
|
||||||
# case ReagentType():
|
|
||||||
# query = query.filter(cls.reagent_type==reagent_type)
|
|
||||||
# case str():
|
|
||||||
# query = query.join(ReagentType).filter(ReagentType.name==reagent_type)
|
|
||||||
# case _:
|
|
||||||
# pass
|
|
||||||
# if kit_type != None and reagent_type != None:
|
|
||||||
# limit = 1
|
|
||||||
# return query_return(query=query, limit=limit)
|
|
||||||
|
|
||||||
# def save(self) -> Report:
|
|
||||||
# """
|
|
||||||
# Adds this instance to the database and commits.
|
|
||||||
|
|
||||||
# Returns:
|
|
||||||
# Report: Result of save action
|
|
||||||
# """
|
|
||||||
# report = Report()
|
|
||||||
# self.__database_session__.add(self)
|
|
||||||
# self.__database_session__.commit()
|
|
||||||
# return report
|
|
||||||
|
|
||||||
class Reagent(BaseClass):
|
class Reagent(BaseClass):
|
||||||
"""
|
"""
|
||||||
Concrete reagent instance
|
Concrete reagent instance
|
||||||
"""
|
"""
|
||||||
__tablename__ = "_reagents"
|
# __tablename__ = "_reagents"
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
type = relationship("ReagentType", back_populates="instances", secondary=reagenttypes_reagents) #: joined parent reagent type
|
type = relationship("ReagentType", back_populates="instances", secondary=reagenttypes_reagents) #: joined parent reagent type
|
||||||
@@ -412,7 +308,6 @@ class Reagent(BaseClass):
|
|||||||
name = Column(String(64)) #: reagent name
|
name = Column(String(64)) #: reagent name
|
||||||
lot = Column(String(64)) #: lot number of reagent
|
lot = Column(String(64)) #: lot number of reagent
|
||||||
expiry = Column(TIMESTAMP) #: expiry date - extended by eol_ext of parent programmatically
|
expiry = Column(TIMESTAMP) #: expiry date - extended by eol_ext of parent programmatically
|
||||||
# submissions = relationship("BasicSubmission", back_populates="reagents", uselist=True) #: submissions this reagent is used in
|
|
||||||
|
|
||||||
reagent_submission_associations = relationship(
|
reagent_submission_associations = relationship(
|
||||||
"SubmissionReagentAssociation",
|
"SubmissionReagentAssociation",
|
||||||
@@ -497,6 +392,7 @@ class Reagent(BaseClass):
|
|||||||
def query(cls,
|
def query(cls,
|
||||||
reagent_type:str|ReagentType|None=None,
|
reagent_type:str|ReagentType|None=None,
|
||||||
lot_number:str|None=None,
|
lot_number:str|None=None,
|
||||||
|
name:str|None=None,
|
||||||
limit:int=0
|
limit:int=0
|
||||||
) -> Reagent|List[Reagent]:
|
) -> Reagent|List[Reagent]:
|
||||||
"""
|
"""
|
||||||
@@ -505,6 +401,7 @@ class Reagent(BaseClass):
|
|||||||
Args:
|
Args:
|
||||||
reagent_type (str | models.ReagentType | None, optional): Reagent type. Defaults to None.
|
reagent_type (str | models.ReagentType | None, optional): Reagent type. Defaults to None.
|
||||||
lot_number (str | None, optional): Reagent lot number. Defaults to None.
|
lot_number (str | None, optional): Reagent lot number. Defaults to None.
|
||||||
|
name (str | None, optional): Reagent name. Defaults to None.
|
||||||
limit (int, optional): limit of results returned. Defaults to 0.
|
limit (int, optional): limit of results returned. Defaults to 0.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -521,6 +418,12 @@ class Reagent(BaseClass):
|
|||||||
query = query.filter(cls.type.contains(reagent_type))
|
query = query.filter(cls.type.contains(reagent_type))
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
|
match name:
|
||||||
|
case str():
|
||||||
|
logger.debug(f"Looking up reagent by name: {name}")
|
||||||
|
query = query.filter(cls.name==name)
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
match lot_number:
|
match lot_number:
|
||||||
case str():
|
case str():
|
||||||
logger.debug(f"Looking up reagent by lot number: {lot_number}")
|
logger.debug(f"Looking up reagent by lot number: {lot_number}")
|
||||||
@@ -531,18 +434,11 @@ class Reagent(BaseClass):
|
|||||||
pass
|
pass
|
||||||
return query_return(query=query, limit=limit)
|
return query_return(query=query, limit=limit)
|
||||||
|
|
||||||
def save(self):
|
|
||||||
"""
|
|
||||||
Add this instance to the database and commit
|
|
||||||
"""
|
|
||||||
self.__database_session__.add(self)
|
|
||||||
self.__database_session__.commit()
|
|
||||||
|
|
||||||
class Discount(BaseClass):
|
class Discount(BaseClass):
|
||||||
"""
|
"""
|
||||||
Relationship table for client labs for certain kits.
|
Relationship table for client labs for certain kits.
|
||||||
"""
|
"""
|
||||||
__tablename__ = "_discounts"
|
# __tablename__ = "_discounts"
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
kit = relationship("KitType") #: joined parent reagent type
|
kit = relationship("KitType") #: joined parent reagent type
|
||||||
@@ -604,17 +500,20 @@ class Discount(BaseClass):
|
|||||||
pass
|
pass
|
||||||
return query.all()
|
return query.all()
|
||||||
|
|
||||||
|
@check_authorization
|
||||||
|
def save(self, ctx:Settings):
|
||||||
|
super().save()
|
||||||
|
|
||||||
class SubmissionType(BaseClass):
|
class SubmissionType(BaseClass):
|
||||||
"""
|
"""
|
||||||
Abstract of types of submissions.
|
Abstract of types of submissions.
|
||||||
"""
|
"""
|
||||||
__tablename__ = "_submission_types"
|
# __tablename__ = "_submission_types"
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(128), unique=True) #: name of submission type
|
name = Column(String(128), unique=True) #: name of submission type
|
||||||
info_map = Column(JSON) #: Where basic information is found in the excel workbook corresponding to this type.
|
info_map = Column(JSON) #: Where basic information is found in the excel workbook corresponding to this type.
|
||||||
instances = relationship("BasicSubmission", backref="submission_type") #: Concrete instances of this type.
|
instances = relationship("BasicSubmission", backref="submission_type") #: Concrete instances of this type.
|
||||||
# regex = Column(String(512))
|
|
||||||
template_file = Column(BLOB) #: Blank form for this type stored as binary.
|
template_file = Column(BLOB) #: Blank form for this type stored as binary.
|
||||||
processes = relationship("Process", back_populates="submission_types", secondary=submissiontypes_processes)
|
processes = relationship("Process", back_populates="submission_types", secondary=submissiontypes_processes)
|
||||||
|
|
||||||
@@ -664,20 +563,25 @@ class SubmissionType(BaseClass):
|
|||||||
output = []
|
output = []
|
||||||
for item in self.submissiontype_equipmentrole_associations:
|
for item in self.submissiontype_equipmentrole_associations:
|
||||||
map = item.uses
|
map = item.uses
|
||||||
map['role'] = item.equipment_role.name
|
if map == None:
|
||||||
|
map = {}
|
||||||
|
try:
|
||||||
|
map['role'] = item.equipment_role.name
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
output.append(map)
|
output.append(map)
|
||||||
return output
|
return output
|
||||||
# return [item.uses for item in self.submissiontype_equipmentrole_associations]
|
# return [item.uses for item in self.submissiontype_equipmentrole_associations]
|
||||||
|
|
||||||
def get_equipment(self) -> List['PydEquipmentRole']:
|
def get_equipment(self, extraction_kit:str|KitType|None=None) -> List['PydEquipmentRole']:
|
||||||
return [item.to_pydantic(submission_type=self) for item in self.equipment]
|
return [item.to_pydantic(submission_type=self, extraction_kit=extraction_kit) for item in self.equipment]
|
||||||
|
|
||||||
def get_processes_for_role(self, equipment_role:str|EquipmentRole):
|
def get_processes_for_role(self, equipment_role:str|EquipmentRole, kit:str|KitType|None=None):
|
||||||
match equipment_role:
|
match equipment_role:
|
||||||
case str():
|
case str():
|
||||||
relevant = [item.get_all_processes() for item in self.submissiontype_equipmentrole_associations if item.equipment_role.name==equipment_role]
|
relevant = [item.get_all_processes(kit) for item in self.submissiontype_equipmentrole_associations if item.equipment_role.name==equipment_role]
|
||||||
case EquipmentRole():
|
case EquipmentRole():
|
||||||
relevant = [item.get_all_processes() for item in self.submissiontype_equipmentrole_associations if item.equipment_role==equipment_role]
|
relevant = [item.get_all_processes(kit) for item in self.submissiontype_equipmentrole_associations if item.equipment_role==equipment_role]
|
||||||
case _:
|
case _:
|
||||||
raise TypeError(f"Type {type(equipment_role)} is not allowed")
|
raise TypeError(f"Type {type(equipment_role)} is not allowed")
|
||||||
return list(set([item for items in relevant for item in items if item != None ]))
|
return list(set([item for items in relevant for item in items if item != None ]))
|
||||||
@@ -728,7 +632,7 @@ class SubmissionTypeKitTypeAssociation(BaseClass):
|
|||||||
"""
|
"""
|
||||||
Abstract of relationship between kits and their submission type.
|
Abstract of relationship between kits and their submission type.
|
||||||
"""
|
"""
|
||||||
__tablename__ = "_submissiontypes_kittypes"
|
# __tablename__ = "_submissiontypes_kittypes"
|
||||||
|
|
||||||
submission_types_id = Column(INTEGER, ForeignKey("_submission_types.id"), primary_key=True) #: id of joined submission type
|
submission_types_id = Column(INTEGER, ForeignKey("_submission_types.id"), primary_key=True) #: id of joined submission type
|
||||||
kits_id = Column(INTEGER, ForeignKey("_kits.id"), primary_key=True) #: id of joined kit
|
kits_id = Column(INTEGER, ForeignKey("_kits.id"), primary_key=True) #: id of joined kit
|
||||||
@@ -801,7 +705,7 @@ class KitTypeReagentTypeAssociation(BaseClass):
|
|||||||
table containing reagenttype/kittype associations
|
table containing reagenttype/kittype associations
|
||||||
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
||||||
"""
|
"""
|
||||||
__tablename__ = "_reagenttypes_kittypes"
|
# __tablename__ = "_reagenttypes_kittypes"
|
||||||
|
|
||||||
reagent_types_id = Column(INTEGER, ForeignKey("_reagent_types.id"), primary_key=True) #: id of associated reagent type
|
reagent_types_id = Column(INTEGER, ForeignKey("_reagent_types.id"), primary_key=True) #: id of associated reagent type
|
||||||
kits_id = Column(INTEGER, ForeignKey("_kits.id"), primary_key=True) #: id of associated reagent type
|
kits_id = Column(INTEGER, ForeignKey("_kits.id"), primary_key=True) #: id of associated reagent type
|
||||||
@@ -902,22 +806,9 @@ class KitTypeReagentTypeAssociation(BaseClass):
|
|||||||
limit = 1
|
limit = 1
|
||||||
return query_return(query=query, limit=limit)
|
return query_return(query=query, limit=limit)
|
||||||
|
|
||||||
def save(self) -> Report:
|
|
||||||
"""
|
|
||||||
Adds this instance to the database and commits.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Report: Result of save action
|
|
||||||
"""
|
|
||||||
report = Report()
|
|
||||||
self.__database_session__.add(self)
|
|
||||||
self.__database_session__.commit()
|
|
||||||
return report
|
|
||||||
|
|
||||||
|
|
||||||
class SubmissionReagentAssociation(BaseClass):
|
class SubmissionReagentAssociation(BaseClass):
|
||||||
|
|
||||||
__tablename__ = "_reagents_submissions"
|
# __tablename__ = "_reagents_submissions"
|
||||||
|
|
||||||
reagent_id = Column(INTEGER, ForeignKey("_reagents.id"), primary_key=True) #: id of associated sample
|
reagent_id = Column(INTEGER, ForeignKey("_reagents.id"), primary_key=True) #: id of associated sample
|
||||||
submission_id = Column(INTEGER, ForeignKey("_submissions.id"), primary_key=True)
|
submission_id = Column(INTEGER, ForeignKey("_submissions.id"), primary_key=True)
|
||||||
@@ -961,8 +852,6 @@ class SubmissionReagentAssociation(BaseClass):
|
|||||||
# logger.debug(f"Filtering query with reagent: {reagent}")
|
# logger.debug(f"Filtering query with reagent: {reagent}")
|
||||||
reagent = Reagent.query(lot_number=reagent)
|
reagent = Reagent.query(lot_number=reagent)
|
||||||
query = query.filter(cls.reagent==reagent)
|
query = query.filter(cls.reagent==reagent)
|
||||||
# logger.debug([item.reagent.lot for item in query.all()])
|
|
||||||
# query = query.join(Reagent).filter(Reagent.lot==reagent)
|
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
# logger.debug(f"Result of query after reagent: {query.all()}")
|
# logger.debug(f"Result of query after reagent: {query.all()}")
|
||||||
@@ -976,7 +865,6 @@ class SubmissionReagentAssociation(BaseClass):
|
|||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
# logger.debug(f"Result of query after submission: {query.all()}")
|
# logger.debug(f"Result of query after submission: {query.all()}")
|
||||||
# limit = query.count()
|
|
||||||
return query_return(query=query, limit=limit)
|
return query_return(query=query, limit=limit)
|
||||||
|
|
||||||
def to_sub_dict(self, extraction_kit):
|
def to_sub_dict(self, extraction_kit):
|
||||||
@@ -989,13 +877,14 @@ class Equipment(BaseClass):
|
|||||||
# Currently abstract until ready to implement
|
# Currently abstract until ready to implement
|
||||||
# __abstract__ = True
|
# __abstract__ = True
|
||||||
|
|
||||||
__tablename__ = "_equipment"
|
# __tablename__ = "_equipment"
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True)
|
id = Column(INTEGER, primary_key=True)
|
||||||
name = Column(String(64))
|
name = Column(String(64))
|
||||||
nickname = Column(String(64))
|
nickname = Column(String(64))
|
||||||
asset_number = Column(String(16))
|
asset_number = Column(String(16))
|
||||||
roles = relationship("EquipmentRole", back_populates="instances", secondary=equipmentroles_equipment)
|
roles = relationship("EquipmentRole", back_populates="instances", secondary=equipmentroles_equipment)
|
||||||
|
processes = relationship("Process", back_populates="equipment", secondary=equipment_processes)
|
||||||
|
|
||||||
equipment_submission_associations = relationship(
|
equipment_submission_associations = relationship(
|
||||||
"SubmissionEquipmentAssociation",
|
"SubmissionEquipmentAssociation",
|
||||||
@@ -1008,10 +897,30 @@ class Equipment(BaseClass):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Equipment({self.name})>"
|
return f"<Equipment({self.name})>"
|
||||||
|
|
||||||
def get_processes(self, submission_type:SubmissionType):
|
def to_dict(self, processes:bool=False):
|
||||||
processes = [assoc.process for assoc in self.equipment_submission_associations if assoc.submission.submission_type_name==submission_type.name]
|
if not processes:
|
||||||
|
return {k:v for k,v in self.__dict__.items() if k != 'processes'}
|
||||||
|
else:
|
||||||
|
return {k:v for k,v in self.__dict__.items()}
|
||||||
|
|
||||||
|
def get_processes(self, submission_type:SubmissionType, extraction_kit:str|KitType|None=None):
|
||||||
|
processes = [process for process in self.processes if submission_type in process.submission_types]
|
||||||
|
match extraction_kit:
|
||||||
|
case str():
|
||||||
|
processes = [process for process in processes if extraction_kit in [kit.name for kit in process.kit_types]]
|
||||||
|
case KitType():
|
||||||
|
processes = [process for process in processes if extraction_kit in process.kit_types]
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
processes = [process.name for process in processes]
|
||||||
|
# try:
|
||||||
|
assert all([isinstance(process, str) for process in processes])
|
||||||
|
# except AssertionError as e:
|
||||||
|
# logger.error(processes)
|
||||||
|
# raise e
|
||||||
if len(processes) == 0:
|
if len(processes) == 0:
|
||||||
processes = ['']
|
processes = ['']
|
||||||
|
# logger.debug(f"Processes: {processes}")
|
||||||
return processes
|
return processes
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1043,10 +952,10 @@ class Equipment(BaseClass):
|
|||||||
pass
|
pass
|
||||||
return query_return(query=query, limit=limit)
|
return query_return(query=query, limit=limit)
|
||||||
|
|
||||||
def to_pydantic(self, submission_type:SubmissionType):
|
def to_pydantic(self, submission_type:SubmissionType, extraction_kit:str|KitType|None=None):
|
||||||
from backend.validators.pydant import PydEquipment
|
from backend.validators.pydant import PydEquipment
|
||||||
# return PydEquipment(process=self.get_processes(submission_type=submission_type), role=None, **self.__dict__)
|
return PydEquipment(processes=self.get_processes(submission_type=submission_type, extraction_kit=extraction_kit), role=None, **self.to_dict(processes=False))
|
||||||
return PydEquipment(process=None, role=None, **self.__dict__)
|
# return PydEquipment(process=None, role=None, **self.__dict__)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
self.__database_session__.add(self)
|
self.__database_session__.add(self)
|
||||||
@@ -1064,12 +973,12 @@ class Equipment(BaseClass):
|
|||||||
|
|
||||||
class EquipmentRole(BaseClass):
|
class EquipmentRole(BaseClass):
|
||||||
|
|
||||||
__tablename__ = "_equipment_roles"
|
# __tablename__ = "_equipment_roles"
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True)
|
id = Column(INTEGER, primary_key=True)
|
||||||
name = Column(String(32))
|
name = Column(String(32))
|
||||||
instances = relationship("Equipment", back_populates="roles", secondary=equipmentroles_equipment)
|
instances = relationship("Equipment", back_populates="roles", secondary=equipmentroles_equipment)
|
||||||
processes = relationship("Process", back_populates="equipment_roles", secondary=equipmentroles_processes)
|
processes = relationship("Process", back_populates='equipment_roles', secondary=equipmentroles_processes)
|
||||||
|
|
||||||
equipmentrole_submissiontype_associations = relationship(
|
equipmentrole_submissiontype_associations = relationship(
|
||||||
"SubmissionTypeEquipmentRoleAssociation",
|
"SubmissionTypeEquipmentRoleAssociation",
|
||||||
@@ -1082,11 +991,23 @@ class EquipmentRole(BaseClass):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<EquipmentRole({self.name})>"
|
return f"<EquipmentRole({self.name})>"
|
||||||
|
|
||||||
def to_pydantic(self, submission_type:SubmissionType):
|
def to_dict(self):
|
||||||
|
output = {}
|
||||||
|
for key, value in self.__dict__.items():
|
||||||
|
match key:
|
||||||
|
case "processes":
|
||||||
|
pass
|
||||||
|
case _:
|
||||||
|
value = value
|
||||||
|
output[key] = value
|
||||||
|
return output
|
||||||
|
|
||||||
|
def to_pydantic(self, submission_type:SubmissionType, extraction_kit:str|KitType|None=None):
|
||||||
from backend.validators.pydant import PydEquipmentRole
|
from backend.validators.pydant import PydEquipmentRole
|
||||||
equipment = [item.to_pydantic(submission_type=submission_type) for item in self.instances]
|
equipment = [item.to_pydantic(submission_type=submission_type, extraction_kit=extraction_kit) for item in self.instances]
|
||||||
pyd_dict = self.__dict__
|
# processes = [item.name for item in self.processes]
|
||||||
pyd_dict['processes'] = self.get_processes(submission_type=submission_type)
|
pyd_dict = self.to_dict()
|
||||||
|
pyd_dict['processes'] = self.get_processes(submission_type=submission_type, extraction_kit=extraction_kit)
|
||||||
return PydEquipmentRole(equipment=equipment, **pyd_dict)
|
return PydEquipmentRole(equipment=equipment, **pyd_dict)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1107,31 +1028,36 @@ class EquipmentRole(BaseClass):
|
|||||||
pass
|
pass
|
||||||
return query_return(query=query, limit=limit)
|
return query_return(query=query, limit=limit)
|
||||||
|
|
||||||
def get_processes(self, submission_type:str|SubmissionType|None) -> List[Process]:
|
def get_processes(self, submission_type:str|SubmissionType|None, extraction_kit:str|KitType|None=None) -> List[Process]:
|
||||||
if isinstance(submission_type, str):
|
if isinstance(submission_type, str):
|
||||||
submission_type = SubmissionType.query(name=submission_type)
|
submission_type = SubmissionType.query(name=submission_type)
|
||||||
|
# assert all([isinstance(process, Process) for process in self.processes])
|
||||||
|
# logger.debug(self.processes)
|
||||||
if submission_type != None:
|
if submission_type != None:
|
||||||
output = [process.name for process in self.processes if submission_type in process.submission_types]
|
# for process in self.processes:
|
||||||
|
# logger.debug(f"Process: {type(process)}: {process}")
|
||||||
|
processes = [process for process in self.processes if submission_type in process.submission_types]
|
||||||
else:
|
else:
|
||||||
output = [process.name for process in self.processes]
|
processes = self.processes
|
||||||
|
match extraction_kit:
|
||||||
|
case str():
|
||||||
|
processes = [item for item in processes if extraction_kit in [kit.name for kit in item.kit_type]]
|
||||||
|
case KitType():
|
||||||
|
processes = [item for item in processes if extraction_kit in [kit for kit in item.kit_type]]
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
output = [item.name for item in processes]
|
||||||
if len(output) == 0:
|
if len(output) == 0:
|
||||||
return ['']
|
return ['']
|
||||||
else:
|
else:
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def save(self):
|
|
||||||
try:
|
|
||||||
self.__database_session__.add(self)
|
|
||||||
self.__database_session__.commit()
|
|
||||||
except:
|
|
||||||
self.__database_session__.rollback()
|
|
||||||
|
|
||||||
class SubmissionEquipmentAssociation(BaseClass):
|
class SubmissionEquipmentAssociation(BaseClass):
|
||||||
|
|
||||||
# Currently abstract until ready to implement
|
# Currently abstract until ready to implement
|
||||||
# __abstract__ = True
|
# __abstract__ = True
|
||||||
|
|
||||||
__tablename__ = "_equipment_submissions"
|
# __tablename__ = "_equipment_submissions"
|
||||||
|
|
||||||
equipment_id = Column(INTEGER, ForeignKey("_equipment.id"), primary_key=True) #: id of associated equipment
|
equipment_id = Column(INTEGER, ForeignKey("_equipment.id"), primary_key=True) #: id of associated equipment
|
||||||
submission_id = Column(INTEGER, ForeignKey("_submissions.id"), primary_key=True) #: id of associated submission
|
submission_id = Column(INTEGER, ForeignKey("_submissions.id"), primary_key=True) #: id of associated submission
|
||||||
@@ -1144,14 +1070,14 @@ class SubmissionEquipmentAssociation(BaseClass):
|
|||||||
|
|
||||||
submission = relationship("BasicSubmission", back_populates="submission_equipment_associations") #: associated submission
|
submission = relationship("BasicSubmission", back_populates="submission_equipment_associations") #: associated submission
|
||||||
|
|
||||||
equipment = relationship(Equipment, back_populates="equipment_submission_associations") #: associated submission
|
equipment = relationship(Equipment, back_populates="equipment_submission_associations") #: associated equipment
|
||||||
|
|
||||||
def __init__(self, submission, equipment):
|
def __init__(self, submission, equipment):
|
||||||
self.submission = submission
|
self.submission = submission
|
||||||
self.equipment = equipment
|
self.equipment = equipment
|
||||||
|
|
||||||
def to_sub_dict(self) -> dict:
|
def to_sub_dict(self) -> dict:
|
||||||
output = dict(name=self.equipment.name, asset_number=self.equipment.asset_number, comment=self.comments, process=self.process.name, role=self.role, nickname=self.equipment.nickname)
|
output = dict(name=self.equipment.name, asset_number=self.equipment.asset_number, comment=self.comments, processes=[self.process.name], role=self.role, nickname=self.equipment.nickname)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
@@ -1162,7 +1088,7 @@ class SubmissionTypeEquipmentRoleAssociation(BaseClass):
|
|||||||
|
|
||||||
# __abstract__ = True
|
# __abstract__ = True
|
||||||
|
|
||||||
__tablename__ = "_submissiontype_equipmentrole"
|
# __tablename__ = "_submissiontype_equipmentrole"
|
||||||
|
|
||||||
equipmentrole_id = Column(INTEGER, ForeignKey("_equipment_roles.id"), primary_key=True) #: id of associated equipment
|
equipmentrole_id = Column(INTEGER, ForeignKey("_equipment_roles.id"), primary_key=True) #: id of associated equipment
|
||||||
submissiontype_id = Column(INTEGER, ForeignKey("_submission_types.id"), primary_key=True) #: id of associated submission
|
submissiontype_id = Column(INTEGER, ForeignKey("_submission_types.id"), primary_key=True) #: id of associated submission
|
||||||
@@ -1192,25 +1118,38 @@ class SubmissionTypeEquipmentRoleAssociation(BaseClass):
|
|||||||
raise ValueError(f'Invalid required value {value}. Must be 0 or 1.')
|
raise ValueError(f'Invalid required value {value}. Must be 0 or 1.')
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def get_all_processes(self):
|
def get_all_processes(self, extraction_kit:KitType|str|None=None):
|
||||||
processes = [equipment.get_processes(self.submission_type) for equipment in self.equipment_role.instances]
|
processes = [equipment.get_processes(self.submission_type) for equipment in self.equipment_role.instances]
|
||||||
|
# flatten list
|
||||||
processes = [item for items in processes for item in items if item != None ]
|
processes = [item for items in processes for item in items if item != None ]
|
||||||
|
match extraction_kit:
|
||||||
|
case str():
|
||||||
|
processes = [item for item in processes if extraction_kit in [kit.name for kit in item.kit_type]]
|
||||||
|
case KitType():
|
||||||
|
processes = [item for item in processes if extraction_kit in [kit for kit in item.kit_type]]
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
return processes
|
return processes
|
||||||
|
|
||||||
@check_authorization
|
@check_authorization
|
||||||
def save(self, ctx:Settings):
|
def save(self, ctx:Settings):
|
||||||
self.__database_session__.add(self)
|
# self.__database_session__.add(self)
|
||||||
self.__database_session__.commit()
|
# self.__database_session__.commit()
|
||||||
|
super().save()
|
||||||
|
|
||||||
class Process(BaseClass):
|
class Process(BaseClass):
|
||||||
|
"""
|
||||||
__tablename__ = "_process"
|
A Process is a method used by a piece of equipment.
|
||||||
|
"""
|
||||||
|
# __tablename__ = "_process"
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True)
|
id = Column(INTEGER, primary_key=True)
|
||||||
name = Column(String(64))
|
name = Column(String(64))
|
||||||
submission_types = relationship("SubmissionType", back_populates='processes', secondary=submissiontypes_processes)
|
submission_types = relationship("SubmissionType", back_populates='processes', secondary=submissiontypes_processes)
|
||||||
|
equipment = relationship("Equipment", back_populates='processes', secondary=equipment_processes)
|
||||||
equipment_roles = relationship("EquipmentRole", back_populates='processes', secondary=equipmentroles_processes)
|
equipment_roles = relationship("EquipmentRole", back_populates='processes', secondary=equipmentroles_processes)
|
||||||
submissions = relationship("SubmissionEquipmentAssociation", backref='process')
|
submissions = relationship("SubmissionEquipmentAssociation", backref='process')
|
||||||
|
kit_types = relationship("KitType", back_populates='processes', secondary=kittypes_processes)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<Process({self.name})"
|
return f"<Process({self.name})"
|
||||||
@@ -1227,124 +1166,3 @@ class Process(BaseClass):
|
|||||||
pass
|
pass
|
||||||
return query_return(query=query, limit=limit)
|
return query_return(query=query, limit=limit)
|
||||||
|
|
||||||
def save(self):
|
|
||||||
try:
|
|
||||||
self.__database_session__.add(self)
|
|
||||||
self.__database_session__.commit()
|
|
||||||
except:
|
|
||||||
self.__database_session__.rollback()
|
|
||||||
|
|
||||||
# class KitTypeReagentTypeAssociation(BaseClass):
|
|
||||||
# """
|
|
||||||
# table containing reagenttype/kittype associations
|
|
||||||
# DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
|
||||||
# """
|
|
||||||
# __tablename__ = "_reagenttypes_kittypes"
|
|
||||||
|
|
||||||
# reagent_types_id = Column(INTEGER, ForeignKey("_reagent_types.id"), primary_key=True) #: id of associated reagent type
|
|
||||||
# kits_id = Column(INTEGER, ForeignKey("_kits.id"), primary_key=True) #: id of associated reagent type
|
|
||||||
# uses = Column(JSON) #: map to location on excel sheets of different submission types
|
|
||||||
# required = Column(INTEGER) #: whether the reagent type is required for the kit (Boolean 1 or 0)
|
|
||||||
# last_used = Column(String(32)) #: last used lot number of this type of reagent
|
|
||||||
|
|
||||||
# kit_type = relationship(KitType, back_populates="kit_reagenttype_associations") #: relationship to associated kit
|
|
||||||
|
|
||||||
# # reference to the "ReagentType" object
|
|
||||||
# reagent_type = relationship(ReagentType, back_populates="reagenttype_kit_associations") #: relationship to associated reagent type
|
|
||||||
|
|
||||||
# def __init__(self, kit_type=None, reagent_type=None, uses=None, required=1):
|
|
||||||
# # logger.debug(f"Parameters: Kit={kit_type}, RT={reagent_type}, Uses={uses}, Required={required}")
|
|
||||||
# self.kit_type = kit_type
|
|
||||||
# self.reagent_type = reagent_type
|
|
||||||
# self.uses = uses
|
|
||||||
# self.required = required
|
|
||||||
|
|
||||||
# def __repr__(self) -> str:
|
|
||||||
# return f"<KitTypeReagentTypeAssociation({self.kit_type} & {self.reagent_type})>"
|
|
||||||
|
|
||||||
# @validates('required')
|
|
||||||
# def validate_age(self, key, value):
|
|
||||||
# """
|
|
||||||
# Ensures only 1 & 0 used in 'required'
|
|
||||||
|
|
||||||
# Args:
|
|
||||||
# key (str): name of attribute
|
|
||||||
# value (_type_): value of attribute
|
|
||||||
|
|
||||||
# Raises:
|
|
||||||
# ValueError: Raised if bad value given
|
|
||||||
|
|
||||||
# Returns:
|
|
||||||
# _type_: value
|
|
||||||
# """
|
|
||||||
# if not 0 <= value < 2:
|
|
||||||
# raise ValueError(f'Invalid required value {value}. Must be 0 or 1.')
|
|
||||||
# return value
|
|
||||||
|
|
||||||
# @validates('reagenttype')
|
|
||||||
# def validate_reagenttype(self, key, value):
|
|
||||||
# """
|
|
||||||
# Ensures reagenttype is an actual ReagentType
|
|
||||||
|
|
||||||
# Args:
|
|
||||||
# key (str)): name of attribute
|
|
||||||
# value (_type_): value of attribute
|
|
||||||
|
|
||||||
# Raises:
|
|
||||||
# ValueError: raised if reagenttype is not a ReagentType
|
|
||||||
|
|
||||||
# Returns:
|
|
||||||
# _type_: ReagentType
|
|
||||||
# """
|
|
||||||
# if not isinstance(value, ReagentType):
|
|
||||||
# raise ValueError(f'{value} is not a reagenttype')
|
|
||||||
# return value
|
|
||||||
|
|
||||||
# @classmethod
|
|
||||||
# @setup_lookup
|
|
||||||
# def query(cls,
|
|
||||||
# kit_type:KitType|str|None=None,
|
|
||||||
# reagent_type:ReagentType|str|None=None,
|
|
||||||
# limit:int=0
|
|
||||||
# ) -> KitTypeReagentTypeAssociation|List[KitTypeReagentTypeAssociation]:
|
|
||||||
# """
|
|
||||||
# Lookup junction of ReagentType and KitType
|
|
||||||
|
|
||||||
# Args:
|
|
||||||
# kit_type (models.KitType | str | None): KitType of interest.
|
|
||||||
# reagent_type (models.ReagentType | str | None): ReagentType of interest.
|
|
||||||
# limit (int, optional): Maximum number of results to return (0 = all). Defaults to 0.
|
|
||||||
|
|
||||||
# Returns:
|
|
||||||
# models.KitTypeReagentTypeAssociation|List[models.KitTypeReagentTypeAssociation]: Junction of interest.
|
|
||||||
# """
|
|
||||||
# query: Query = cls.__database_session__.query(cls)
|
|
||||||
# match kit_type:
|
|
||||||
# case KitType():
|
|
||||||
# query = query.filter(cls.kit_type==kit_type)
|
|
||||||
# case str():
|
|
||||||
# query = query.join(KitType).filter(KitType.name==kit_type)
|
|
||||||
# case _:
|
|
||||||
# pass
|
|
||||||
# match reagent_type:
|
|
||||||
# case ReagentType():
|
|
||||||
# query = query.filter(cls.reagent_type==reagent_type)
|
|
||||||
# case str():
|
|
||||||
# query = query.join(ReagentType).filter(ReagentType.name==reagent_type)
|
|
||||||
# case _:
|
|
||||||
# pass
|
|
||||||
# if kit_type != None and reagent_type != None:
|
|
||||||
# limit = 1
|
|
||||||
# return query_return(query=query, limit=limit)
|
|
||||||
|
|
||||||
# def save(self) -> Report:
|
|
||||||
# """
|
|
||||||
# Adds this instance to the database and commits.
|
|
||||||
|
|
||||||
# Returns:
|
|
||||||
# Report: Result of save action
|
|
||||||
# """
|
|
||||||
# report = Report()
|
|
||||||
# self.__database_session__.add(self)
|
|
||||||
# self.__database_session__.commit()
|
|
||||||
# return report
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class Organization(BaseClass):
|
|||||||
"""
|
"""
|
||||||
Base of organization
|
Base of organization
|
||||||
"""
|
"""
|
||||||
__tablename__ = "_organizations"
|
# __tablename__ = "_organizations"
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(64)) #: organization name
|
name = Column(String(64)) #: organization name
|
||||||
@@ -73,14 +73,13 @@ class Organization(BaseClass):
|
|||||||
Args:
|
Args:
|
||||||
ctx (Settings): Settings object passed down from GUI. Necessary to check authorization
|
ctx (Settings): Settings object passed down from GUI. Necessary to check authorization
|
||||||
"""
|
"""
|
||||||
ctx.database_session.add(self)
|
super().save()
|
||||||
ctx.database_session.commit()
|
|
||||||
|
|
||||||
class Contact(BaseClass):
|
class Contact(BaseClass):
|
||||||
"""
|
"""
|
||||||
Base of Contact
|
Base of Contact
|
||||||
"""
|
"""
|
||||||
__tablename__ = "_contacts"
|
# __tablename__ = "_contacts"
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
name = Column(String(64)) #: contact name
|
name = Column(String(64)) #: contact name
|
||||||
|
|||||||
@@ -13,23 +13,24 @@ from json.decoder import JSONDecodeError
|
|||||||
from sqlalchemy.ext.associationproxy import association_proxy
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
from . import BaseClass
|
from . import BaseClass, Equipment
|
||||||
from tools import check_not_nan, row_map, query_return, setup_lookup, jinja_template_loading
|
from tools import check_not_nan, row_map, query_return, setup_lookup, jinja_template_loading
|
||||||
from datetime import datetime, date, time
|
from datetime import datetime, date, time
|
||||||
from typing import List
|
from typing import List, Any
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
from dateutil.parser._parser import ParserError
|
from dateutil.parser._parser import ParserError
|
||||||
from sqlalchemy.exc import OperationalError as AlcOperationalError, IntegrityError as AlcIntegrityError, StatementError
|
from sqlalchemy.exc import OperationalError as AlcOperationalError, IntegrityError as AlcIntegrityError, StatementError
|
||||||
from sqlite3 import OperationalError as SQLOperationalError, IntegrityError as SQLIntegrityError
|
from sqlite3 import OperationalError as SQLOperationalError, IntegrityError as SQLIntegrityError
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(f"submissions.{__name__}")
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
class BasicSubmission(BaseClass):
|
class BasicSubmission(BaseClass):
|
||||||
"""
|
"""
|
||||||
Concrete of basic submission which polymorphs into BacterialCulture and Wastewater
|
Concrete of basic submission which polymorphs into BacterialCulture and Wastewater
|
||||||
"""
|
"""
|
||||||
__tablename__ = "_submissions"
|
# __tablename__ = "_submissions"
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
rsl_plate_num = Column(String(32), unique=True, nullable=False) #: RSL name (e.g. RSL-22-0012)
|
rsl_plate_num = Column(String(32), unique=True, nullable=False) #: RSL name (e.g. RSL-22-0012)
|
||||||
@@ -96,7 +97,7 @@ class BasicSubmission(BaseClass):
|
|||||||
"""
|
"""
|
||||||
return f"{self.rsl_plate_num} - {self.submitter_plate_num}"
|
return f"{self.rsl_plate_num} - {self.submitter_plate_num}"
|
||||||
|
|
||||||
def to_dict(self, full_data:bool=False) -> dict:
|
def to_dict(self, full_data:bool=False, backup:bool=False) -> dict:
|
||||||
"""
|
"""
|
||||||
Constructs dictionary used in submissions summary
|
Constructs dictionary used in submissions summary
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ class BasicSubmission(BaseClass):
|
|||||||
logger.error(f"We got an error retrieving reagents: {e}")
|
logger.error(f"We got an error retrieving reagents: {e}")
|
||||||
reagents = None
|
reagents = None
|
||||||
# samples = [item.sample.to_sub_dict(submission_rsl=self.rsl_plate_num) for item in self.submission_sample_associations]
|
# samples = [item.sample.to_sub_dict(submission_rsl=self.rsl_plate_num) for item in self.submission_sample_associations]
|
||||||
samples = [item.to_sub_dict() for item in self.submission_sample_associations]
|
samples = self.adjust_to_dict_samples(backup=backup)
|
||||||
try:
|
try:
|
||||||
equipment = [item.to_sub_dict() for item in self.submission_equipment_associations]
|
equipment = [item.to_sub_dict() for item in self.submission_equipment_associations]
|
||||||
if len(equipment) == 0:
|
if len(equipment) == 0:
|
||||||
@@ -255,18 +256,7 @@ class BasicSubmission(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
list: list of htipick dictionaries for each sample
|
list: list of htipick dictionaries for each sample
|
||||||
"""
|
"""
|
||||||
output_list = []
|
output_list = [assoc.to_hitpick() for assoc in self.submission_sample_associations]
|
||||||
for assoc in self.submission_sample_associations:
|
|
||||||
samp = assoc.sample.to_hitpick(submission_rsl=self.rsl_plate_num)
|
|
||||||
if samp != None:
|
|
||||||
if plate_number != None:
|
|
||||||
samp['plate_number'] = plate_number
|
|
||||||
samp['row'] = assoc.row
|
|
||||||
samp['column'] = assoc.column
|
|
||||||
samp['plate_name'] = self.rsl_plate_num
|
|
||||||
output_list.append(samp)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
return output_list
|
return output_list
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -548,7 +538,7 @@ class BasicSubmission(BaseClass):
|
|||||||
result = assoc.save()
|
result = assoc.save()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def to_pydantic(self):
|
def to_pydantic(self, backup:bool=False):
|
||||||
"""
|
"""
|
||||||
Converts this instance into a PydSubmission
|
Converts this instance into a PydSubmission
|
||||||
|
|
||||||
@@ -556,7 +546,7 @@ class BasicSubmission(BaseClass):
|
|||||||
PydSubmission: converted object.
|
PydSubmission: converted object.
|
||||||
"""
|
"""
|
||||||
from backend.validators import PydSubmission, PydSample, PydReagent, PydEquipment
|
from backend.validators import PydSubmission, PydSample, PydReagent, PydEquipment
|
||||||
dicto = self.to_dict(full_data=True)
|
dicto = self.to_dict(full_data=True, backup=backup)
|
||||||
logger.debug(f"Backup dictionary: {pformat(dicto)}")
|
logger.debug(f"Backup dictionary: {pformat(dicto)}")
|
||||||
# dicto['filepath'] = Path(tempfile.TemporaryFile().name)
|
# dicto['filepath'] = Path(tempfile.TemporaryFile().name)
|
||||||
new_dict = {}
|
new_dict = {}
|
||||||
@@ -567,7 +557,11 @@ class BasicSubmission(BaseClass):
|
|||||||
case "samples":
|
case "samples":
|
||||||
new_dict[key] = [PydSample(**sample) for sample in dicto['samples']]
|
new_dict[key] = [PydSample(**sample) for sample in dicto['samples']]
|
||||||
case "equipment":
|
case "equipment":
|
||||||
new_dict[key] = [PydEquipment(**equipment) for equipment in dicto['equipment']]
|
# logger.debug(f"\n\nEquipment: {dicto['equipment']}\n\n")
|
||||||
|
try:
|
||||||
|
new_dict[key] = [PydEquipment(**equipment) for equipment in dicto['equipment']]
|
||||||
|
except TypeError as e:
|
||||||
|
logger.error(f"Possible no equipment error: {e}")
|
||||||
case "Plate Number":
|
case "Plate Number":
|
||||||
new_dict['rsl_plate_num'] = dict(value=value, missing=True)
|
new_dict['rsl_plate_num'] = dict(value=value, missing=True)
|
||||||
case "Submitter Plate Number":
|
case "Submitter Plate Number":
|
||||||
@@ -582,25 +576,6 @@ class BasicSubmission(BaseClass):
|
|||||||
# sys.exit()
|
# sys.exit()
|
||||||
return PydSubmission(**new_dict)
|
return PydSubmission(**new_dict)
|
||||||
|
|
||||||
def backup(self, fname:Path, full_backup:bool=True):
|
|
||||||
"""
|
|
||||||
Exports xlsx and yml info files for this instance.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fname (Path): Filename of xlsx file.
|
|
||||||
"""
|
|
||||||
if full_backup:
|
|
||||||
backup = self.to_dict(full_data=True)
|
|
||||||
try:
|
|
||||||
with open(self.__backup_path__.joinpath(fname.with_suffix(".yml")), "w") as f:
|
|
||||||
yaml.dump(backup, f)
|
|
||||||
except KeyError as e:
|
|
||||||
logger.error(f"Problem saving yml backup file: {e}")
|
|
||||||
pyd = self.to_pydantic()
|
|
||||||
wb = pyd.autofill_excel()
|
|
||||||
wb = pyd.autofill_samples(wb)
|
|
||||||
wb.save(filename=fname.with_suffix(".xlsx"))
|
|
||||||
|
|
||||||
def save(self, original:bool=True):
|
def save(self, original:bool=True):
|
||||||
"""
|
"""
|
||||||
Adds this instance to database and commits.
|
Adds this instance to database and commits.
|
||||||
@@ -610,24 +585,7 @@ class BasicSubmission(BaseClass):
|
|||||||
"""
|
"""
|
||||||
if original:
|
if original:
|
||||||
self.uploaded_by = getuser()
|
self.uploaded_by = getuser()
|
||||||
self.__database_session__.add(self)
|
super().save()
|
||||||
self.__database_session__.commit()
|
|
||||||
|
|
||||||
def delete(self):
|
|
||||||
"""
|
|
||||||
Performs backup and deletes this instance from database.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
e: Raised in something goes wrong.
|
|
||||||
"""
|
|
||||||
fname = self.__backup_path__.joinpath(f"{self.rsl_plate_num}-backup({date.today().strftime('%Y%m%d')})")
|
|
||||||
self.backup(fname=fname)
|
|
||||||
self.__database_session__.delete(self)
|
|
||||||
try:
|
|
||||||
self.__database_session__.commit()
|
|
||||||
except (SQLIntegrityError, SQLOperationalError, AlcIntegrityError, AlcOperationalError) as e:
|
|
||||||
self.__database_session__.rollback()
|
|
||||||
raise e
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@setup_lookup
|
@setup_lookup
|
||||||
@@ -782,15 +740,121 @@ class BasicSubmission(BaseClass):
|
|||||||
def get_used_equipment(self) -> List[str]:
|
def get_used_equipment(self) -> List[str]:
|
||||||
return [item.role for item in self.submission_equipment_associations]
|
return [item.role for item in self.submission_equipment_associations]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def adjust_autofill_samples(cls, samples:List[Any]) -> List[Any]:
|
||||||
|
logger.info(f"Hello from {cls.__mapper_args__['polymorphic_identity']} sampler")
|
||||||
|
return samples
|
||||||
|
|
||||||
|
def adjust_to_dict_samples(self, backup:bool=False):
|
||||||
|
logger.debug(f"Hello from {self.__class__.__name__} dictionary sample adjuster.")
|
||||||
|
return [item.to_sub_dict() for item in self.submission_sample_associations]
|
||||||
|
|
||||||
|
# Custom context events for the ui
|
||||||
|
|
||||||
|
def custom_context_events(self):
|
||||||
|
names = ["Delete", "Details", "Add Comment", "Add Equipment", "Export"]
|
||||||
|
funcs = [self.delete, self.show_details, self.add_comment, self.add_equipment, self.backup]
|
||||||
|
dicto = {item[0]:item[1] for item in zip(names, funcs)}
|
||||||
|
return dicto
|
||||||
|
|
||||||
|
def delete(self, obj=None):
|
||||||
|
"""
|
||||||
|
Performs backup and deletes this instance from database.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
e: Raised in something goes wrong.
|
||||||
|
"""
|
||||||
|
logger.debug("Hello from delete")
|
||||||
|
fname = self.__backup_path__.joinpath(f"{self.rsl_plate_num}-backup({date.today().strftime('%Y%m%d')})")
|
||||||
|
self.backup(fname=fname, full_backup=True)
|
||||||
|
self.__database_session__.delete(self)
|
||||||
|
try:
|
||||||
|
self.__database_session__.commit()
|
||||||
|
except (SQLIntegrityError, SQLOperationalError, AlcIntegrityError, AlcOperationalError) as e:
|
||||||
|
self.__database_session__.rollback()
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def show_details(self, obj):
|
||||||
|
logger.debug("Hello from details")
|
||||||
|
from frontend.widgets.submission_details import SubmissionDetails
|
||||||
|
dlg = SubmissionDetails(parent=obj, sub=self)
|
||||||
|
if dlg.exec():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_comment(self, obj):
|
||||||
|
from frontend.widgets.submission_details import SubmissionComment
|
||||||
|
dlg = SubmissionComment(parent=obj, submission=self)
|
||||||
|
if dlg.exec():
|
||||||
|
comment = dlg.parse_form()
|
||||||
|
try:
|
||||||
|
# For some reason .append results in new comment being ignored, so have to concatenate lists.
|
||||||
|
self.comment = self.comment + comment
|
||||||
|
except (AttributeError, TypeError) as e:
|
||||||
|
logger.error(f"Hit error ({e}) creating comment")
|
||||||
|
self.comment = comment
|
||||||
|
logger.debug(self.comment)
|
||||||
|
self.save(original=False)
|
||||||
|
# logger.debug(f"Save result: {result}")
|
||||||
|
|
||||||
|
def add_equipment(self, obj):
|
||||||
|
# submission_type = submission.submission_type_name
|
||||||
|
from frontend.widgets.equipment_usage import EquipmentUsage
|
||||||
|
dlg = EquipmentUsage(parent=obj, submission_type=self.submission_type_name, submission=self)
|
||||||
|
if dlg.exec():
|
||||||
|
equipment = dlg.parse_form()
|
||||||
|
logger.debug(f"We've got equipment: {equipment}")
|
||||||
|
for equip in equipment:
|
||||||
|
# e = Equipment.query(name=equip.name)
|
||||||
|
# assoc = SubmissionEquipmentAssociation(submission=submission, equipment=e)
|
||||||
|
# process = Process.query(name=equip.processes)
|
||||||
|
# assoc.process = process
|
||||||
|
# assoc.role = equip.role
|
||||||
|
_, assoc = equip.toSQL(submission=self)
|
||||||
|
# submission.submission_equipment_associations.append(assoc)
|
||||||
|
logger.debug(f"Appending SubmissionEquipmentAssociation: {assoc}")
|
||||||
|
# submission.save()
|
||||||
|
assoc.save()
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def backup(self, obj=None, fname:Path|None=None, full_backup:bool=False):
|
||||||
|
"""
|
||||||
|
Exports xlsx and yml info files for this instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fname (Path): Filename of xlsx file.
|
||||||
|
"""
|
||||||
|
logger.debug("Hello from backup.")
|
||||||
|
if fname == None:
|
||||||
|
from frontend.widgets.functions import select_save_file
|
||||||
|
from backend.validators import RSLNamer
|
||||||
|
abbreviation = self.get_abbreviation()
|
||||||
|
file_data = dict(rsl_plate_num=self.rsl_plate_num, submission_type=self.submission_type_name, submitted_date=self.submitted_date, abbreviation=abbreviation)
|
||||||
|
fname = select_save_file(default_name=RSLNamer.construct_new_plate_name(data=file_data), extension="xlsx", obj=obj)
|
||||||
|
if full_backup:
|
||||||
|
backup = self.to_dict(full_data=True)
|
||||||
|
try:
|
||||||
|
with open(self.__backup_path__.joinpath(fname.with_suffix(".yml")), "w") as f:
|
||||||
|
yaml.dump(backup, f)
|
||||||
|
except KeyError as e:
|
||||||
|
logger.error(f"Problem saving yml backup file: {e}")
|
||||||
|
pyd = self.to_pydantic(backup=True)
|
||||||
|
wb = pyd.autofill_excel()
|
||||||
|
wb = pyd.autofill_samples(wb)
|
||||||
|
wb = pyd.autofill_equipment(wb)
|
||||||
|
wb.save(filename=fname.with_suffix(".xlsx"))
|
||||||
|
|
||||||
# Below are the custom submission types
|
# Below are the custom submission types
|
||||||
|
|
||||||
class BacterialCulture(BasicSubmission):
|
class BacterialCulture(BasicSubmission):
|
||||||
"""
|
"""
|
||||||
derivative submission type from BasicSubmission
|
derivative submission type from BasicSubmission
|
||||||
"""
|
"""
|
||||||
# id = Column(INTEGER, ForeignKey('basicsubmission.id'), primary_key=True)
|
id = Column(INTEGER, ForeignKey('_basicsubmission.id'), primary_key=True)
|
||||||
controls = relationship("Control", back_populates="submission", uselist=True) #: A control sample added to submission
|
controls = relationship("Control", back_populates="submission", uselist=True) #: A control sample added to submission
|
||||||
__mapper_args__ = {"polymorphic_identity": "Bacterial Culture", "polymorphic_load": "inline"}
|
__mapper_args__ = dict(polymorphic_identity="Bacterial Culture",
|
||||||
|
polymorphic_load="inline",
|
||||||
|
inherit_condition=(id == BasicSubmission.id))
|
||||||
|
|
||||||
def to_dict(self, full_data:bool=False) -> dict:
|
def to_dict(self, full_data:bool=False) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -804,6 +868,10 @@ class BacterialCulture(BasicSubmission):
|
|||||||
output['controls'] = [item.to_sub_dict() for item in self.controls]
|
output['controls'] = [item.to_sub_dict() for item in self.controls]
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_abbreviation(cls):
|
||||||
|
return "BC"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def custom_platemap(cls, xl: pd.ExcelFile, plate_map: pd.DataFrame) -> pd.DataFrame:
|
def custom_platemap(cls, xl: pd.ExcelFile, plate_map: pd.DataFrame) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
@@ -853,7 +921,7 @@ class BacterialCulture(BasicSubmission):
|
|||||||
Extends parent
|
Extends parent
|
||||||
"""
|
"""
|
||||||
from backend.validators import RSLNamer
|
from backend.validators import RSLNamer
|
||||||
data['abbreviation'] = "BC"
|
data['abbreviation'] = cls.get_abbreviation()
|
||||||
outstr = super().enforce_name(instr=instr, data=data)
|
outstr = super().enforce_name(instr=instr, data=data)
|
||||||
# def construct(data:dict|None=None) -> str:
|
# def construct(data:dict|None=None) -> str:
|
||||||
# """
|
# """
|
||||||
@@ -932,10 +1000,12 @@ class Wastewater(BasicSubmission):
|
|||||||
"""
|
"""
|
||||||
derivative submission type from BasicSubmission
|
derivative submission type from BasicSubmission
|
||||||
"""
|
"""
|
||||||
# id = Column(INTEGER, ForeignKey('basicsubmission.id'), primary_key=True)
|
id = Column(INTEGER, ForeignKey('_basicsubmission.id'), primary_key=True)
|
||||||
ext_technician = Column(String(64))
|
ext_technician = Column(String(64))
|
||||||
pcr_technician = Column(String(64))
|
pcr_technician = Column(String(64))
|
||||||
__mapper_args__ = {"polymorphic_identity": "Wastewater", "polymorphic_load": "inline"}
|
__mapper_args__ = __mapper_args__ = dict(polymorphic_identity="Wastewater",
|
||||||
|
polymorphic_load="inline",
|
||||||
|
inherit_condition=(id == BasicSubmission.id))
|
||||||
|
|
||||||
def to_dict(self, full_data:bool=False) -> dict:
|
def to_dict(self, full_data:bool=False) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -952,6 +1022,10 @@ class Wastewater(BasicSubmission):
|
|||||||
output['Technician'] = f"Enr: {self.technician}, Ext: {self.ext_technician}, PCR: {self.pcr_technician}"
|
output['Technician'] = f"Enr: {self.technician}, Ext: {self.ext_technician}, PCR: {self.pcr_technician}"
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_abbreviation(cls):
|
||||||
|
return "WW"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_info(cls, input_dict:dict, xl:pd.ExcelFile|None=None) -> dict:
|
def parse_info(cls, input_dict:dict, xl:pd.ExcelFile|None=None) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -1012,7 +1086,7 @@ class Wastewater(BasicSubmission):
|
|||||||
Extends parent
|
Extends parent
|
||||||
"""
|
"""
|
||||||
from backend.validators import RSLNamer
|
from backend.validators import RSLNamer
|
||||||
data['abbreviation'] = "WW"
|
data['abbreviation'] = cls.get_abbreviation()
|
||||||
outstr = super().enforce_name(instr=instr, data=data)
|
outstr = super().enforce_name(instr=instr, data=data)
|
||||||
# def construct(data:dict|None=None):
|
# def construct(data:dict|None=None):
|
||||||
# if "submitted_date" in data.keys():
|
# if "submitted_date" in data.keys():
|
||||||
@@ -1066,13 +1140,21 @@ class Wastewater(BasicSubmission):
|
|||||||
# return "(?P<Wastewater>RSL(?:-|_)?WW(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(_|-)?\d?([^_0123456789\s]|$)R?\d?)?)"
|
# return "(?P<Wastewater>RSL(?:-|_)?WW(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(_|-)?\d?([^_0123456789\s]|$)R?\d?)?)"
|
||||||
return "(?P<Wastewater>RSL(?:-|_)?WW(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(_|-)?\d?([^_0123456789\s]|$)?R?\d?)?)"
|
return "(?P<Wastewater>RSL(?:-|_)?WW(?:-|_)?20\d{2}-?\d{2}-?\d{2}(?:(_|-)?\d?([^_0123456789\s]|$)?R?\d?)?)"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def adjust_autofill_samples(cls, samples: List[Any]) -> List[Any]:
|
||||||
|
samples = super().adjust_autofill_samples(samples)
|
||||||
|
return [item for item in samples if not item.submitter_id.startswith("EN")]
|
||||||
|
|
||||||
class WastewaterArtic(BasicSubmission):
|
class WastewaterArtic(BasicSubmission):
|
||||||
"""
|
"""
|
||||||
derivative submission type for artic wastewater
|
derivative submission type for artic wastewater
|
||||||
"""
|
"""
|
||||||
# id = Column(INTEGER, ForeignKey('basicsubmission.id'), primary_key=True)
|
id = Column(INTEGER, ForeignKey('_basicsubmission.id'), primary_key=True)
|
||||||
__mapper_args__ = {"polymorphic_identity": "Wastewater Artic", "polymorphic_load": "inline"}
|
__mapper_args__ = dict(polymorphic_identity="Wastewater Artic",
|
||||||
|
polymorphic_load="inline",
|
||||||
|
inherit_condition=(id == BasicSubmission.id))
|
||||||
artic_technician = Column(String(64))
|
artic_technician = Column(String(64))
|
||||||
|
dna_core_submission_number = Column(String(64))
|
||||||
|
|
||||||
def calculate_base_cost(self):
|
def calculate_base_cost(self):
|
||||||
"""
|
"""
|
||||||
@@ -1093,6 +1175,10 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Calculation error: {e}")
|
logger.error(f"Calculation error: {e}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_abbreviation(cls):
|
||||||
|
return "AR"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_samples(cls, input_dict: dict) -> dict:
|
def parse_samples(cls, input_dict: dict) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -1109,15 +1195,45 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
# Because generate_sample_object needs the submitter_id and the artic has the "({origin well})"
|
# Because generate_sample_object needs the submitter_id and the artic has the "({origin well})"
|
||||||
# at the end, this has to be done here. No moving to sqlalchemy object :(
|
# at the end, this has to be done here. No moving to sqlalchemy object :(
|
||||||
input_dict['submitter_id'] = re.sub(r"\s\(.+\)\s?$", "", str(input_dict['submitter_id'])).strip()
|
input_dict['submitter_id'] = re.sub(r"\s\(.+\)\s?$", "", str(input_dict['submitter_id'])).strip()
|
||||||
|
if "ENC" in input_dict['submitter_id']:
|
||||||
|
input_dict['submitter_id'] = cls.en_adapter(input_str=input_dict['submitter_id'])
|
||||||
return input_dict
|
return input_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def en_adapter(cls, input_str) -> str:
|
||||||
|
processed = re.sub(r"[A-Z]", "", input_str)
|
||||||
|
try:
|
||||||
|
en_num = re.search(r"\-\d{1}$", processed).group()
|
||||||
|
processed = processed.replace(en_num, "", -1)
|
||||||
|
except AttributeError:
|
||||||
|
en_num = "1"
|
||||||
|
en_num = en_num.strip("-")
|
||||||
|
logger.debug(f"Processed after en-num: {processed}")
|
||||||
|
try:
|
||||||
|
plate_num = re.search(r"\-\d{1}$", processed).group()
|
||||||
|
processed = processed.replace(plate_num, "", -1)
|
||||||
|
except AttributeError:
|
||||||
|
plate_num = "1"
|
||||||
|
plate_num = plate_num.strip("-")
|
||||||
|
logger.debug(f"Processed after plate-num: {processed}")
|
||||||
|
day = re.search(r"\d{2}$", processed).group()
|
||||||
|
processed = processed.replace(day, "", -1)
|
||||||
|
logger.debug(f"Processed after day: {processed}")
|
||||||
|
month = re.search(r"\d{2}$", processed).group()
|
||||||
|
processed = processed.replace(month, "", -1)
|
||||||
|
processed = processed.replace("--", "")
|
||||||
|
logger.debug(f"Processed after month: {processed}")
|
||||||
|
year = re.search(r'^(?:\d{2})?\d{2}', processed).group()
|
||||||
|
year = f"20{year}"
|
||||||
|
return f"EN{year}{month}{day}-{en_num}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def enforce_name(cls, instr:str, data:dict|None=None) -> str:
|
def enforce_name(cls, instr:str, data:dict|None=None) -> str:
|
||||||
"""
|
"""
|
||||||
Extends parent
|
Extends parent
|
||||||
"""
|
"""
|
||||||
from backend.validators import RSLNamer
|
from backend.validators import RSLNamer
|
||||||
data['abbreviation'] = "AR"
|
data['abbreviation'] = cls.get_abbreviation()
|
||||||
outstr = super().enforce_name(instr=instr, data=data)
|
outstr = super().enforce_name(instr=instr, data=data)
|
||||||
# def construct(data:dict|None=None):
|
# def construct(data:dict|None=None):
|
||||||
# today = datetime.now()
|
# today = datetime.now()
|
||||||
@@ -1240,6 +1356,36 @@ class WastewaterArtic(BasicSubmission):
|
|||||||
worksheet.cell(row=iii, column=jjj, value=value)
|
worksheet.cell(row=iii, column=jjj, value=value)
|
||||||
return input_excel
|
return input_excel
|
||||||
|
|
||||||
|
def adjust_to_dict_samples(self, backup:bool=False):
|
||||||
|
logger.debug(f"Hello from {self.__class__.__name__} dictionary sample adjuster.")
|
||||||
|
if backup:
|
||||||
|
output = []
|
||||||
|
for assoc in self.submission_sample_associations:
|
||||||
|
dicto = assoc.to_sub_dict()
|
||||||
|
old_sub = assoc.sample.get_previous_ww_submission(current_artic_submission=self)
|
||||||
|
try:
|
||||||
|
dicto['plate_name'] = old_sub.rsl_plate_num
|
||||||
|
except AttributeError:
|
||||||
|
dicto['plate_name'] = ""
|
||||||
|
old_assoc = WastewaterAssociation.query(submission=old_sub, sample=assoc.sample, limit=1)
|
||||||
|
dicto['well'] = f"{row_map[old_assoc.row]}{old_assoc.column}"
|
||||||
|
output.append(dicto)
|
||||||
|
else:
|
||||||
|
output = super().adjust_to_dict_samples(backup=False)
|
||||||
|
return output
|
||||||
|
|
||||||
|
def custom_context_events(self):
|
||||||
|
events = super().custom_context_events()
|
||||||
|
events['Gel Box'] = self.gel_box
|
||||||
|
return events
|
||||||
|
|
||||||
|
def gel_box(self, obj):
|
||||||
|
from frontend.widgets.gel_checker import GelBox
|
||||||
|
dlg = GelBox(parent=obj)
|
||||||
|
if dlg.exec():
|
||||||
|
output = dlg.parse_form()
|
||||||
|
print(output)
|
||||||
|
|
||||||
# Sample Classes
|
# Sample Classes
|
||||||
|
|
||||||
class BasicSample(BaseClass):
|
class BasicSample(BaseClass):
|
||||||
@@ -1247,7 +1393,7 @@ class BasicSample(BaseClass):
|
|||||||
Base of basic sample which polymorphs into BCSample and WWSample
|
Base of basic sample which polymorphs into BCSample and WWSample
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "_samples"
|
# __tablename__ = "_samples"
|
||||||
|
|
||||||
id = Column(INTEGER, primary_key=True) #: primary key
|
id = Column(INTEGER, primary_key=True) #: primary key
|
||||||
submitter_id = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
submitter_id = Column(String(64), nullable=False, unique=True) #: identification from submitter
|
||||||
@@ -1295,6 +1441,18 @@ class BasicSample(BaseClass):
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<{self.sample_type.replace('_', ' ').title().replace(' ', '')}({self.submitter_id})>"
|
return f"<{self.sample_type.replace('_', ' ').title().replace(' ', '')}({self.submitter_id})>"
|
||||||
|
|
||||||
|
def to_sub_dict(self, submission_rsl:str) -> dict:
|
||||||
|
"""
|
||||||
|
gui friendly dictionary, extends parent method.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above
|
||||||
|
"""
|
||||||
|
sample = {}
|
||||||
|
sample['submitter_id'] = self.submitter_id
|
||||||
|
sample['sample_type'] = self.sample_type
|
||||||
|
return sample
|
||||||
|
|
||||||
def set_attribute(self, name:str, value):
|
def set_attribute(self, name:str, value):
|
||||||
"""
|
"""
|
||||||
Custom attribute setter
|
Custom attribute setter
|
||||||
@@ -1308,59 +1466,6 @@ class BasicSample(BaseClass):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.error(f"Attribute {name} not found")
|
logger.error(f"Attribute {name} not found")
|
||||||
|
|
||||||
def to_sub_dict(self, submission_rsl:str|BasicSubmission) -> dict:
|
|
||||||
"""
|
|
||||||
Returns a dictionary of locations.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
submission_rsl (str): Submission RSL number.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: 'well' and sample submitter_id as 'name'
|
|
||||||
"""
|
|
||||||
match submission_rsl:
|
|
||||||
case BasicSubmission():
|
|
||||||
assoc = [item for item in self.sample_submission_associations if item.submission==submission_rsl][0]
|
|
||||||
case str():
|
|
||||||
assoc = [item for item in self.sample_submission_associations if item.submission.rsl_plate_num==submission_rsl][0]
|
|
||||||
sample = {}
|
|
||||||
try:
|
|
||||||
sample['well'] = f"{row_map[assoc.row]}{assoc.column}"
|
|
||||||
except KeyError as e:
|
|
||||||
logger.error(f"Unable to find row {assoc.row} in row_map.")
|
|
||||||
sample['well'] = None
|
|
||||||
sample['name'] = self.submitter_id
|
|
||||||
sample['submitter_id'] = self.submitter_id
|
|
||||||
sample['sample_type'] = self.sample_type
|
|
||||||
if isinstance(assoc.row, list):
|
|
||||||
sample['row'] = assoc.row[0]
|
|
||||||
else:
|
|
||||||
sample['row'] = assoc.row
|
|
||||||
if isinstance(assoc.column, list):
|
|
||||||
sample['column'] = assoc.column[0]
|
|
||||||
else:
|
|
||||||
sample['column'] = assoc.column
|
|
||||||
return sample
|
|
||||||
|
|
||||||
def to_hitpick(self, submission_rsl:str|None=None) -> dict|None:
|
|
||||||
"""
|
|
||||||
Outputs a dictionary usable for html plate maps.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: dictionary of sample id, row and column in elution plate
|
|
||||||
"""
|
|
||||||
# Since there is no PCR, negliable result is necessary.
|
|
||||||
# assoc = [item for item in self.sample_submission_associations if item.submission.rsl_plate_num==submission_rsl][0]
|
|
||||||
fields = self.to_sub_dict(submission_rsl=submission_rsl)
|
|
||||||
env = jinja_template_loading()
|
|
||||||
template = env.get_template("tooltip.html")
|
|
||||||
tooltip_text = template.render(fields=fields)
|
|
||||||
# tooltip_text = f"""
|
|
||||||
# Sample name: {self.submitter_id}<br>
|
|
||||||
# Well: {row_map[assoc.row]}{assoc.column}
|
|
||||||
# """
|
|
||||||
return dict(name=self.submitter_id[:10], positive=False, tooltip=tooltip_text)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_subclasses(cls, attrs:dict|None=None, sample_type:str|None=None) -> BasicSample:
|
def find_subclasses(cls, attrs:dict|None=None, sample_type:str|None=None) -> BasicSample:
|
||||||
"""
|
"""
|
||||||
@@ -1507,11 +1612,6 @@ class BasicSample(BaseClass):
|
|||||||
logger.debug(f"Creating instance: {instance}")
|
logger.debug(f"Creating instance: {instance}")
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def save(self):
|
|
||||||
# raise AttributeError(f"Save not implemented for {self.__class__}")
|
|
||||||
self.__database_session__.add(self)
|
|
||||||
self.__database_session__.commit()
|
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
raise AttributeError(f"Delete not implemented for {self.__class__}")
|
raise AttributeError(f"Delete not implemented for {self.__class__}")
|
||||||
|
|
||||||
@@ -1521,7 +1621,7 @@ class WastewaterSample(BasicSample):
|
|||||||
"""
|
"""
|
||||||
Derivative wastewater sample
|
Derivative wastewater sample
|
||||||
"""
|
"""
|
||||||
# id = Column(INTEGER, ForeignKey('basicsample.id'), primary_key=True)
|
id = Column(INTEGER, ForeignKey('_basicsample.id'), primary_key=True)
|
||||||
ww_processing_num = Column(String(64)) #: wastewater processing number
|
ww_processing_num = Column(String(64)) #: wastewater processing number
|
||||||
ww_full_sample_id = Column(String(64)) #: full id given by entrics
|
ww_full_sample_id = Column(String(64)) #: full id given by entrics
|
||||||
rsl_number = Column(String(64)) #: rsl plate identification number
|
rsl_number = Column(String(64)) #: rsl plate identification number
|
||||||
@@ -1529,46 +1629,21 @@ class WastewaterSample(BasicSample):
|
|||||||
received_date = Column(TIMESTAMP) #: Date sample received
|
received_date = Column(TIMESTAMP) #: Date sample received
|
||||||
notes = Column(String(2000)) #: notes from submission form
|
notes = Column(String(2000)) #: notes from submission form
|
||||||
sample_location = Column(String(8)) #: location on 24 well plate
|
sample_location = Column(String(8)) #: location on 24 well plate
|
||||||
__mapper_args__ = {"polymorphic_identity": "Wastewater Sample", "polymorphic_load": "inline"}
|
__mapper_args__ = dict(polymorphic_identity="Wastewater Sample",
|
||||||
|
polymorphic_load="inline",
|
||||||
|
inherit_condition=(id == BasicSample.id))
|
||||||
|
|
||||||
def to_hitpick(self, submission_rsl:str) -> dict|None:
|
def to_sub_dict(self, submission_rsl:str) -> dict:
|
||||||
"""
|
"""
|
||||||
Outputs a dictionary usable for html plate maps. Extends parent method.
|
gui friendly dictionary, extends parent method.
|
||||||
|
|
||||||
Args:
|
|
||||||
submission_rsl (str): rsl_plate_num of the submission
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict|None: dict: dictionary of sample id, row and column in elution plate
|
dict: well location and name (sample id, organism) NOTE: keys must sync with WWSample to_sub_dict above
|
||||||
"""
|
"""
|
||||||
sample = super().to_hitpick(submission_rsl=submission_rsl)
|
sample = super().to_sub_dict(submission_rsl=submission_rsl)
|
||||||
assoc = [item for item in self.sample_submission_associations if item.submission.rsl_plate_num==submission_rsl][0]
|
sample['ww_processing_num'] = self.ww_processing_num
|
||||||
# if either n1 or n2 is positive, include this sample
|
|
||||||
try:
|
|
||||||
sample['positive'] = any(["positive" in item for item in [assoc.n1_status, assoc.n2_status]])
|
|
||||||
except (TypeError, AttributeError) as e:
|
|
||||||
logger.error(f"Couldn't check positives for {self.rsl_number}. Looks like there isn't PCR data.")
|
|
||||||
try:
|
|
||||||
sample['tooltip'] += f"<br>- ct N1: {'{:.2f}'.format(assoc.ct_n1)} ({assoc.n1_status})<br>- ct N2: {'{:.2f}'.format(assoc.ct_n2)} ({assoc.n2_status})"
|
|
||||||
except (TypeError, AttributeError) as e:
|
|
||||||
logger.error(f"Couldn't set tooltip for {self.rsl_number}. Looks like there isn't PCR data.")
|
|
||||||
return sample
|
return sample
|
||||||
|
|
||||||
def get_recent_ww_submission(self) -> Wastewater:
|
|
||||||
"""
|
|
||||||
Gets most recent associated wastewater submission
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Wastewater: Most recent wastewater submission
|
|
||||||
"""
|
|
||||||
results = [sub for sub in self.submissions if isinstance(sub, Wastewater)]
|
|
||||||
if len(results) > 1:
|
|
||||||
results = results.sort(key=lambda sub: sub.submitted_date)
|
|
||||||
try:
|
|
||||||
return results[0]
|
|
||||||
except IndexError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_sample(cls, input_dict: dict) -> dict:
|
def parse_sample(cls, input_dict: dict) -> dict:
|
||||||
output_dict = super().parse_sample(input_dict)
|
output_dict = super().parse_sample(input_dict)
|
||||||
@@ -1592,34 +1667,27 @@ class WastewaterSample(BasicSample):
|
|||||||
del output_dict['collection_date']
|
del output_dict['collection_date']
|
||||||
return output_dict
|
return output_dict
|
||||||
|
|
||||||
def to_sub_dict(self, submission_rsl: str | BasicSubmission) -> dict:
|
def get_previous_ww_submission(self, current_artic_submission:WastewaterArtic):
|
||||||
sample = super().to_sub_dict(submission_rsl)
|
# assocs = [assoc for assoc in self.sample_submission_associations if assoc.submission.submission_type_name=="Wastewater"]
|
||||||
if self.ww_processing_num != None:
|
subs = self.submissions[:self.submissions.index(current_artic_submission)]
|
||||||
sample['ww_processing_num'] = self.ww_processing_num
|
subs = [sub for sub in subs if sub.submission_type_name=="Wastewater"]
|
||||||
else:
|
logger.debug(f"Submissions up to current artic submission: {subs}")
|
||||||
sample['ww_processing_num'] = self.submitter_id
|
|
||||||
try:
|
try:
|
||||||
assoc = [item for item in self.sample_submission_associations if item.submission.submission_type_name=="Wastewater"][-1]
|
return subs[-1]
|
||||||
except:
|
except IndexError:
|
||||||
assoc = None
|
return None
|
||||||
if assoc != None:
|
|
||||||
try:
|
|
||||||
sample['ct'] = f"{assoc.ct_n1:.2f}, {assoc.ct_n2:.2f}"
|
|
||||||
except TypeError:
|
|
||||||
sample['ct'] = "None, None"
|
|
||||||
sample['source_plate'] = assoc.submission.rsl_plate_num
|
|
||||||
sample['source_well'] = f"{row_map[assoc.row]}{assoc.column}"
|
|
||||||
return sample
|
|
||||||
|
|
||||||
class BacterialCultureSample(BasicSample):
|
class BacterialCultureSample(BasicSample):
|
||||||
"""
|
"""
|
||||||
base of bacterial culture sample
|
base of bacterial culture sample
|
||||||
"""
|
"""
|
||||||
# id = Column(INTEGER, ForeignKey('basicsample.id'), primary_key=True)
|
id = Column(INTEGER, ForeignKey('_basicsample.id'), primary_key=True)
|
||||||
organism = Column(String(64)) #: bacterial specimen
|
organism = Column(String(64)) #: bacterial specimen
|
||||||
concentration = Column(String(16)) #: sample concentration
|
concentration = Column(String(16)) #: sample concentration
|
||||||
control = relationship("Control", back_populates="sample", uselist=False)
|
control = relationship("Control", back_populates="sample", uselist=False)
|
||||||
__mapper_args__ = {"polymorphic_identity": "Bacterial Culture Sample", "polymorphic_load": "inline"}
|
__mapper_args__ = dict(polymorphic_identity="Bacterial Culture Sample",
|
||||||
|
polymorphic_load="inline",
|
||||||
|
inherit_condition=(id == BasicSample.id))
|
||||||
|
|
||||||
def to_sub_dict(self, submission_rsl:str) -> dict:
|
def to_sub_dict(self, submission_rsl:str) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -1632,15 +1700,18 @@ class BacterialCultureSample(BasicSample):
|
|||||||
sample['name'] = self.submitter_id
|
sample['name'] = self.submitter_id
|
||||||
sample['organism'] = self.organism
|
sample['organism'] = self.organism
|
||||||
sample['concentration'] = self.concentration
|
sample['concentration'] = self.concentration
|
||||||
return sample
|
|
||||||
|
|
||||||
def to_hitpick(self, submission_rsl: str | None = None) -> dict | None:
|
|
||||||
sample = super().to_hitpick(submission_rsl)
|
|
||||||
if self.control != None:
|
if self.control != None:
|
||||||
sample['colour'] = [0,128,0]
|
sample['colour'] = [0,128,0]
|
||||||
sample['tooltip'] += f"<br>- Control: {self.control.controltype.name} - {self.control.controltype.targets}"
|
sample['tooltip'] = f"Control: {self.control.controltype.name} - {self.control.controltype.targets}"
|
||||||
return sample
|
return sample
|
||||||
|
|
||||||
|
# def to_hitpick(self, submission_rsl: str | None = None) -> dict | None:
|
||||||
|
# sample = super().to_hitpick(submission_rsl)
|
||||||
|
# if self.control != None:
|
||||||
|
# sample['colour'] = [0,128,0]
|
||||||
|
# sample['tooltip'] += f"<br>- Control: {self.control.controltype.name} - {self.control.controltype.targets}"
|
||||||
|
# return sample
|
||||||
|
|
||||||
# Submission to Sample Associations
|
# Submission to Sample Associations
|
||||||
|
|
||||||
class SubmissionSampleAssociation(BaseClass):
|
class SubmissionSampleAssociation(BaseClass):
|
||||||
@@ -1649,7 +1720,7 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
DOC: https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "_submission_sample"
|
# __tablename__ = "_submission_sample"
|
||||||
|
|
||||||
sample_id = Column(INTEGER, ForeignKey("_samples.id"), nullable=False) #: id of associated sample
|
sample_id = Column(INTEGER, ForeignKey("_samples.id"), nullable=False) #: id of associated sample
|
||||||
submission_id = Column(INTEGER, ForeignKey("_submissions.id"), primary_key=True) #: id of associated submission
|
submission_id = Column(INTEGER, ForeignKey("_submissions.id"), primary_key=True) #: id of associated submission
|
||||||
@@ -1688,7 +1759,12 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
Returns:
|
Returns:
|
||||||
dict: Updated dictionary with row, column and well updated
|
dict: Updated dictionary with row, column and well updated
|
||||||
"""
|
"""
|
||||||
|
# Get sample info
|
||||||
sample = self.sample.to_sub_dict(submission_rsl=self.submission)
|
sample = self.sample.to_sub_dict(submission_rsl=self.submission)
|
||||||
|
# sample = {}
|
||||||
|
sample['name'] = self.sample.submitter_id
|
||||||
|
# sample['submitter_id'] = self.sample.submitter_id
|
||||||
|
# sample['sample_type'] = self.sample.sample_type
|
||||||
sample['row'] = self.row
|
sample['row'] = self.row
|
||||||
sample['column'] = self.column
|
sample['column'] = self.column
|
||||||
try:
|
try:
|
||||||
@@ -1696,6 +1772,33 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
logger.error(f"Unable to find row {self.row} in row_map.")
|
logger.error(f"Unable to find row {self.row} in row_map.")
|
||||||
sample['well'] = None
|
sample['well'] = None
|
||||||
|
sample['plate_name'] = self.submission.rsl_plate_num
|
||||||
|
sample['positive'] = False
|
||||||
|
|
||||||
|
return sample
|
||||||
|
|
||||||
|
def to_hitpick(self) -> dict|None:
|
||||||
|
"""
|
||||||
|
Outputs a dictionary usable for html plate maps.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: dictionary of sample id, row and column in elution plate
|
||||||
|
"""
|
||||||
|
# Since there is no PCR, negliable result is necessary.
|
||||||
|
# assoc = [item for item in self.sample_submission_associations if item.submission.rsl_plate_num==submission_rsl][0]
|
||||||
|
sample = self.to_sub_dict()
|
||||||
|
env = jinja_template_loading()
|
||||||
|
template = env.get_template("tooltip.html")
|
||||||
|
tooltip_text = template.render(fields=sample)
|
||||||
|
try:
|
||||||
|
tooltip_text += sample['tooltip']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
# tooltip_text = f"""
|
||||||
|
# Sample name: {self.submitter_id}<br>
|
||||||
|
# Well: {row_map[fields['row']]}{fields['column']}
|
||||||
|
# """
|
||||||
|
sample.update(dict(name=self.sample.submitter_id[:10], tooltip=tooltip_text))
|
||||||
return sample
|
return sample
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1831,14 +1934,6 @@ class SubmissionSampleAssociation(BaseClass):
|
|||||||
instance = used_cls(submission=submission, sample=sample, **kwargs)
|
instance = used_cls(submission=submission, sample=sample, **kwargs)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def save(self):
|
|
||||||
"""
|
|
||||||
Adds this instance to the database and commits.
|
|
||||||
"""
|
|
||||||
self.__database_session__.add(self)
|
|
||||||
self.__database_session__.commit()
|
|
||||||
return None
|
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
raise AttributeError(f"Delete not implemented for {self.__class__}")
|
raise AttributeError(f"Delete not implemented for {self.__class__}")
|
||||||
|
|
||||||
@@ -1846,10 +1941,32 @@ class WastewaterAssociation(SubmissionSampleAssociation):
|
|||||||
"""
|
"""
|
||||||
Derivative custom Wastewater/Submission Association... fancy.
|
Derivative custom Wastewater/Submission Association... fancy.
|
||||||
"""
|
"""
|
||||||
|
sample_id = Column(INTEGER, ForeignKey('_submissionsampleassociation.sample_id'), primary_key=True)
|
||||||
|
submission_id = Column(INTEGER, ForeignKey('_submissionsampleassociation.submission_id'), primary_key=True)
|
||||||
ct_n1 = Column(FLOAT(2)) #: AKA ct for N1
|
ct_n1 = Column(FLOAT(2)) #: AKA ct for N1
|
||||||
ct_n2 = Column(FLOAT(2)) #: AKA ct for N2
|
ct_n2 = Column(FLOAT(2)) #: AKA ct for N2
|
||||||
n1_status = Column(String(32)) #: positive or negative for N1
|
n1_status = Column(String(32)) #: positive or negative for N1
|
||||||
n2_status = Column(String(32)) #: positive or negative for N2
|
n2_status = Column(String(32)) #: positive or negative for N2
|
||||||
pcr_results = Column(JSON) #: imported PCR status from QuantStudio
|
pcr_results = Column(JSON) #: imported PCR status from QuantStudio
|
||||||
|
|
||||||
__mapper_args__ = {"polymorphic_identity": "Wastewater Association", "polymorphic_load": "inline"}
|
# __mapper_args__ = {"polymorphic_identity": "Wastewater Association", "polymorphic_load": "inline"}
|
||||||
|
__mapper_args__ = dict(polymorphic_identity="Wastewater Association",
|
||||||
|
polymorphic_load="inline",
|
||||||
|
inherit_condition=(sample_id == SubmissionSampleAssociation.sample_id))
|
||||||
|
|
||||||
|
def to_sub_dict(self) -> dict:
|
||||||
|
sample = super().to_sub_dict()
|
||||||
|
sample['ct'] = f"({self.ct_n1}, {self.ct_n2})"
|
||||||
|
try:
|
||||||
|
sample['positive'] = any(["positive" in item for item in [self.n1_status, self.n2_status]])
|
||||||
|
except (TypeError, AttributeError) as e:
|
||||||
|
logger.error(f"Couldn't check positives for {self.sample.rsl_number}. Looks like there isn't PCR data.")
|
||||||
|
return sample
|
||||||
|
|
||||||
|
def to_hitpick(self) -> dict | None:
|
||||||
|
sample = super().to_hitpick()
|
||||||
|
try:
|
||||||
|
sample['tooltip'] += f"<br>- ct N1: {'{:.2f}'.format(self.ct_n1)} ({self.n1_status})<br>- ct N2: {'{:.2f}'.format(self.ct_n2)} ({self.n2_status})"
|
||||||
|
except (TypeError, AttributeError) as e:
|
||||||
|
logger.error(f"Couldn't set tooltip for {self.sample.rsl_number}. Looks like there isn't PCR data.")
|
||||||
|
return sample
|
||||||
|
|||||||
@@ -503,7 +503,12 @@ class EquipmentParser(object):
|
|||||||
|
|
||||||
def get_asset_number(self, input:str) -> str:
|
def get_asset_number(self, input:str) -> str:
|
||||||
regex = Equipment.get_regex()
|
regex = Equipment.get_regex()
|
||||||
return regex.search(input).group().strip("-")
|
logger.debug(f"Using equipment regex: {regex} on {input}")
|
||||||
|
try:
|
||||||
|
return regex.search(input).group().strip("-")
|
||||||
|
except AttributeError:
|
||||||
|
return input
|
||||||
|
|
||||||
|
|
||||||
def parse_equipment(self):
|
def parse_equipment(self):
|
||||||
logger.debug(f"Equipment parser going into parsing: {pformat(self.__dict__)}")
|
logger.debug(f"Equipment parser going into parsing: {pformat(self.__dict__)}")
|
||||||
@@ -512,7 +517,10 @@ class EquipmentParser(object):
|
|||||||
# logger.debug(f"Sheets: {sheets}")
|
# logger.debug(f"Sheets: {sheets}")
|
||||||
for sheet in self.xl.sheet_names:
|
for sheet in self.xl.sheet_names:
|
||||||
df = self.xl.parse(sheet, header=None, dtype=object)
|
df = self.xl.parse(sheet, header=None, dtype=object)
|
||||||
relevant = [item for item in self.map if item['sheet']==sheet]
|
try:
|
||||||
|
relevant = [item for item in self.map if item['sheet']==sheet]
|
||||||
|
except (TypeError, KeyError):
|
||||||
|
continue
|
||||||
# logger.debug(f"Relevant equipment: {pformat(relevant)}")
|
# logger.debug(f"Relevant equipment: {pformat(relevant)}")
|
||||||
previous_asset = ""
|
previous_asset = ""
|
||||||
for equipment in relevant:
|
for equipment in relevant:
|
||||||
@@ -524,7 +532,10 @@ class EquipmentParser(object):
|
|||||||
asset = self.get_asset_number(input=asset)
|
asset = self.get_asset_number(input=asset)
|
||||||
eq = Equipment.query(asset_number=asset)
|
eq = Equipment.query(asset_number=asset)
|
||||||
process = df.iat[equipment['process']['row']-1, equipment['process']['column']-1]
|
process = df.iat[equipment['process']['row']-1, equipment['process']['column']-1]
|
||||||
output.append(PydEquipment(name=eq.name, process=process, role=equipment['role'], asset_number=asset, nickname=eq.nickname))
|
try:
|
||||||
|
output.append(PydEquipment(name=eq.name, processes=[process], role=equipment['role'], asset_number=asset, nickname=eq.nickname))
|
||||||
|
except AttributeError:
|
||||||
|
logger.error(f"Unable to add {eq} to PydEquipment list.")
|
||||||
# logger.debug(f"Here is the output so far: {pformat(output)}")
|
# logger.debug(f"Here is the output so far: {pformat(output)}")
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|||||||
@@ -113,12 +113,15 @@ class RSLNamer(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def construct_new_plate_name(cls, data:dict) -> str:
|
def construct_new_plate_name(cls, data:dict) -> str:
|
||||||
if "submitted_date" in data.keys():
|
if "submitted_date" in data.keys():
|
||||||
if data['submitted_date']['value'] != None:
|
if isinstance(data['submitted_date'], dict):
|
||||||
today = data['submitted_date']['value']
|
if data['submitted_date']['value'] != None:
|
||||||
|
today = data['submitted_date']['value']
|
||||||
|
else:
|
||||||
|
today = datetime.now()
|
||||||
else:
|
else:
|
||||||
today = datetime.now()
|
today = data['submitted_date']
|
||||||
else:
|
else:
|
||||||
today = re.search(r"\d{4}(_|-)?\d{2}(_|-)?\d{2}", instr)
|
today = re.search(r"\d{4}(_|-)?\d{2}(_|-)?\d{2}", data['rsl_plate_num'])
|
||||||
try:
|
try:
|
||||||
today = parse(today.group())
|
today = parse(today.group())
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class PydReagent(BaseModel):
|
|||||||
if self.model_extra != None:
|
if self.model_extra != None:
|
||||||
self.__dict__.update(self.model_extra)
|
self.__dict__.update(self.model_extra)
|
||||||
logger.debug(f"Reagent SQL constructor is looking up type: {self.type}, lot: {self.lot}")
|
logger.debug(f"Reagent SQL constructor is looking up type: {self.type}, lot: {self.lot}")
|
||||||
reagent = Reagent.query(lot_number=self.lot)
|
reagent = Reagent.query(lot_number=self.lot, name=self.name)
|
||||||
logger.debug(f"Result: {reagent}")
|
logger.debug(f"Result: {reagent}")
|
||||||
if reagent == None:
|
if reagent == None:
|
||||||
reagent = Reagent()
|
reagent = Reagent()
|
||||||
@@ -209,6 +209,56 @@ class PydSample(BaseModel, extra='allow'):
|
|||||||
instance.metadata.session.rollback()
|
instance.metadata.session.rollback()
|
||||||
return instance, out_associations, report
|
return instance, out_associations, report
|
||||||
|
|
||||||
|
class PydEquipment(BaseModel, extra='ignore'):
|
||||||
|
|
||||||
|
asset_number: str
|
||||||
|
name: str
|
||||||
|
nickname: str|None
|
||||||
|
processes: List[str]|None
|
||||||
|
role: str|None
|
||||||
|
|
||||||
|
@field_validator('processes', mode='before')
|
||||||
|
@classmethod
|
||||||
|
def make_empty_list(cls, value):
|
||||||
|
# logger.debug(f"Pydantic value: {value}")
|
||||||
|
if value == None:
|
||||||
|
value = ['']
|
||||||
|
if len(value)==0:
|
||||||
|
value=['']
|
||||||
|
return value
|
||||||
|
|
||||||
|
# def toForm(self, parent):
|
||||||
|
# from frontend.widgets.equipment_usage import EquipmentCheckBox
|
||||||
|
# return EquipmentCheckBox(parent=parent, equipment=self)
|
||||||
|
|
||||||
|
def toSQL(self, submission:BasicSubmission|str=None):
|
||||||
|
if isinstance(submission, str):
|
||||||
|
submission = BasicSubmission.query(rsl_number=submission)
|
||||||
|
equipment = Equipment.query(asset_number=self.asset_number)
|
||||||
|
if equipment == None:
|
||||||
|
return
|
||||||
|
if submission != None:
|
||||||
|
assoc = SubmissionEquipmentAssociation(submission=submission, equipment=equipment)
|
||||||
|
process = Process.query(name=self.processes[0])
|
||||||
|
if process == None:
|
||||||
|
from frontend.widgets.pop_ups import QuestionAsker
|
||||||
|
dlg = QuestionAsker(title="Add Process?", message=f"Unable to find {self.processes[0]} in the database.\nWould you like to add it?")
|
||||||
|
if dlg.exec():
|
||||||
|
kit = submission.extraction_kit
|
||||||
|
submission_type = submission.submission_type
|
||||||
|
process = Process(name=self.processes[0])
|
||||||
|
process.kit_types.append(kit)
|
||||||
|
process.submission_types.append(submission_type)
|
||||||
|
process.equipment.append(equipment)
|
||||||
|
process.save()
|
||||||
|
assoc.process = process
|
||||||
|
assoc.role = self.role
|
||||||
|
# equipment.equipment_submission_associations.append(assoc)
|
||||||
|
equipment.equipment_submission_associations.append(assoc)
|
||||||
|
else:
|
||||||
|
assoc = None
|
||||||
|
return equipment, assoc
|
||||||
|
|
||||||
class PydSubmission(BaseModel, extra='allow'):
|
class PydSubmission(BaseModel, extra='allow'):
|
||||||
filepath: Path
|
filepath: Path
|
||||||
submission_type: dict|None
|
submission_type: dict|None
|
||||||
@@ -453,8 +503,10 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
for equip in self.equipment:
|
for equip in self.equipment:
|
||||||
equip, association = equip.toSQL(submission=instance)
|
equip, association = equip.toSQL(submission=instance)
|
||||||
if association != None:
|
if association != None:
|
||||||
|
association.save()
|
||||||
logger.debug(f"Equipment association SQL object to be added to submission: {association.__dict__}")
|
logger.debug(f"Equipment association SQL object to be added to submission: {association.__dict__}")
|
||||||
instance.submission_equipment_associations.append(association)
|
instance.submission_equipment_associations.append(association)
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
try:
|
try:
|
||||||
instance.set_attribute(key=key, value=value)
|
instance.set_attribute(key=key, value=value)
|
||||||
@@ -570,9 +622,7 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
logger.debug(f"New reagents: {new_reagents}")
|
logger.debug(f"New reagents: {new_reagents}")
|
||||||
logger.debug(f"New info: {new_info}")
|
logger.debug(f"New info: {new_info}")
|
||||||
# get list of sheet names
|
# get list of sheet names
|
||||||
sheets = workbook.sheetnames
|
for sheet in workbook.sheetnames:
|
||||||
# logger.debug(workbook.sheetnames)
|
|
||||||
for sheet in sheets:
|
|
||||||
# open sheet
|
# open sheet
|
||||||
worksheet=workbook[sheet]
|
worksheet=workbook[sheet]
|
||||||
# Get relevant reagents for that sheet
|
# Get relevant reagents for that sheet
|
||||||
@@ -613,11 +663,14 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
logger.debug(f"Workbook sheets: {workbook.sheetnames}")
|
logger.debug(f"Workbook sheets: {workbook.sheetnames}")
|
||||||
worksheet = workbook[sample_info["lookup_table"]['sheet']]
|
worksheet = workbook[sample_info["lookup_table"]['sheet']]
|
||||||
samples = sorted(self.samples, key=attrgetter('column', 'row'))
|
samples = sorted(self.samples, key=attrgetter('column', 'row'))
|
||||||
|
custom_sampler = BasicSubmission.find_polymorphic_subclass(polymorphic_identity=self.submission_type).adjust_autofill_samples
|
||||||
|
samples = custom_sampler(samples=samples)
|
||||||
logger.debug(f"Samples: {pformat(samples)}")
|
logger.debug(f"Samples: {pformat(samples)}")
|
||||||
# Fail safe against multiple instances of the same sample
|
# Fail safe against multiple instances of the same sample
|
||||||
for iii, sample in enumerate(samples, start=1):
|
for iii, sample in enumerate(samples, start=1):
|
||||||
row = sample_info['lookup_table']['start_row'] + iii
|
row = sample_info['lookup_table']['start_row'] + iii
|
||||||
fields = [field for field in list(sample.model_fields.keys()) + list(sample.model_extra.keys()) if field in sample_info['sample_columns'].keys()]
|
fields = [field for field in list(sample.model_fields.keys()) + list(sample.model_extra.keys()) if field in sample_info['sample_columns'].keys()]
|
||||||
|
|
||||||
for field in fields:
|
for field in fields:
|
||||||
column = sample_info['sample_columns'][field]
|
column = sample_info['sample_columns'][field]
|
||||||
value = getattr(sample, field)
|
value = getattr(sample, field)
|
||||||
@@ -631,6 +684,42 @@ class PydSubmission(BaseModel, extra='allow'):
|
|||||||
worksheet.cell(row=row, column=column, value=value)
|
worksheet.cell(row=row, column=column, value=value)
|
||||||
return workbook
|
return workbook
|
||||||
|
|
||||||
|
def autofill_equipment(self, workbook:Workbook) -> Workbook:
|
||||||
|
equipment_map = SubmissionType.query(name=self.submission_type['value']).construct_equipment_map()
|
||||||
|
logger.debug(f"Equipment map: {equipment_map}")
|
||||||
|
# See if all equipment has a location map
|
||||||
|
# If not, create a new sheet to store them in.
|
||||||
|
if not all([len(item.keys()) > 1 for item in equipment_map]):
|
||||||
|
logger.warning("Creating 'Equipment' sheet to hold unmapped equipment")
|
||||||
|
workbook.create_sheet("Equipment")
|
||||||
|
equipment = []
|
||||||
|
for ii, equip in enumerate(self.equipment, start=1):
|
||||||
|
loc = [item for item in equipment_map if item['role'] == equip.role][0]
|
||||||
|
try:
|
||||||
|
loc['name']['value'] = equip.name
|
||||||
|
loc['process']['value'] = equip.processes[0]
|
||||||
|
except KeyError:
|
||||||
|
loc['name'] = dict(row=ii, column=2)
|
||||||
|
loc['process'] = dict(row=ii, column=3)
|
||||||
|
loc['name']['value'] = equip.name
|
||||||
|
loc['process']['value'] = equip.processes[0]
|
||||||
|
loc['sheet'] = "Equipment"
|
||||||
|
equipment.append(loc)
|
||||||
|
logger.debug(f"Using equipment: {equipment}")
|
||||||
|
for sheet in workbook.sheetnames:
|
||||||
|
logger.debug(f"Looking at: {sheet}")
|
||||||
|
worksheet = workbook[sheet]
|
||||||
|
relevant = [item for item in equipment if item['sheet'] == sheet]
|
||||||
|
for rel in relevant:
|
||||||
|
match sheet:
|
||||||
|
case "Equipment":
|
||||||
|
worksheet.cell(row=rel['name']['row'], column=1, value=rel['role'])
|
||||||
|
case _:
|
||||||
|
pass
|
||||||
|
worksheet.cell(row=rel['name']['row'], column=rel['name']['column'], value=rel['name']['value'])
|
||||||
|
worksheet.cell(row=rel['process']['row'], column=rel['process']['column'], value=rel['process']['value'])
|
||||||
|
return workbook
|
||||||
|
|
||||||
def construct_filename(self) -> str:
|
def construct_filename(self) -> str:
|
||||||
"""
|
"""
|
||||||
Creates filename for this instance
|
Creates filename for this instance
|
||||||
@@ -774,42 +863,6 @@ class PydKit(BaseModel):
|
|||||||
[item.toSQL(instance) for item in self.reagent_types]
|
[item.toSQL(instance) for item in self.reagent_types]
|
||||||
return instance, report
|
return instance, report
|
||||||
|
|
||||||
class PydEquipment(BaseModel, extra='ignore'):
|
|
||||||
|
|
||||||
asset_number: str
|
|
||||||
name: str
|
|
||||||
nickname: str|None
|
|
||||||
process: str|None
|
|
||||||
role: str|None
|
|
||||||
|
|
||||||
# @field_validator('process')
|
|
||||||
# @classmethod
|
|
||||||
# def remove_dupes(cls, value):
|
|
||||||
# if isinstance(value, list):
|
|
||||||
# return list(set(value))
|
|
||||||
# else:
|
|
||||||
# return value
|
|
||||||
|
|
||||||
# def toForm(self, parent):
|
|
||||||
# from frontend.widgets.equipment_usage import EquipmentCheckBox
|
|
||||||
# return EquipmentCheckBox(parent=parent, equipment=self)
|
|
||||||
|
|
||||||
def toSQL(self, submission:BasicSubmission|str=None):
|
|
||||||
if isinstance(submission, str):
|
|
||||||
submission = BasicSubmission.query(rsl_number=submission)
|
|
||||||
equipment = Equipment.query(asset_number=self.asset_number)
|
|
||||||
if equipment == None:
|
|
||||||
return
|
|
||||||
if submission != None:
|
|
||||||
assoc = SubmissionEquipmentAssociation(submission=submission, equipment=equipment)
|
|
||||||
assoc.process = self.process
|
|
||||||
assoc.role = self.role
|
|
||||||
# equipment.equipment_submission_associations.append(assoc)
|
|
||||||
equipment.equipment_submission_associations.append(assoc)
|
|
||||||
else:
|
|
||||||
assoc = None
|
|
||||||
return equipment, assoc
|
|
||||||
|
|
||||||
class PydEquipmentRole(BaseModel):
|
class PydEquipmentRole(BaseModel):
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
@@ -10,17 +10,14 @@ logger = logging.getLogger(f"submissions.{__name__}")
|
|||||||
|
|
||||||
class EquipmentUsage(QDialog):
|
class EquipmentUsage(QDialog):
|
||||||
|
|
||||||
def __init__(self, parent, submission_type:SubmissionType|str, submission:BasicSubmission) -> QDialog:
|
def __init__(self, parent, submission:BasicSubmission) -> QDialog:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
self.submission = submission
|
||||||
self.setWindowTitle("Equipment Checklist")
|
self.setWindowTitle("Equipment Checklist")
|
||||||
self.used_equipment = submission.get_used_equipment()
|
self.used_equipment = self.submission.get_used_equipment()
|
||||||
|
self.kit = self.submission.extraction_kit
|
||||||
logger.debug(f"Existing equipment: {self.used_equipment}")
|
logger.debug(f"Existing equipment: {self.used_equipment}")
|
||||||
if isinstance(submission_type, str):
|
self.opt_equipment = submission.submission_type.get_equipment()
|
||||||
self.submission_type = SubmissionType.query(name=submission_type)
|
|
||||||
else:
|
|
||||||
self.submission_type = submission_type
|
|
||||||
# self.static_equipment = submission_type.get_equipment()
|
|
||||||
self.opt_equipment = self.submission_type.get_equipment()
|
|
||||||
logger.debug(f"EquipmentRoles: {self.opt_equipment}")
|
logger.debug(f"EquipmentRoles: {self.opt_equipment}")
|
||||||
self.layout = QVBoxLayout()
|
self.layout = QVBoxLayout()
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
@@ -31,20 +28,44 @@ class EquipmentUsage(QDialog):
|
|||||||
self.buttonBox = QDialogButtonBox(QBtn)
|
self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
self.buttonBox.accepted.connect(self.accept)
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
self.buttonBox.rejected.connect(self.reject)
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
label = self.LabelRow(parent=self)
|
||||||
|
self.layout.addWidget(label)
|
||||||
for eq in self.opt_equipment:
|
for eq in self.opt_equipment:
|
||||||
self.layout.addWidget(eq.toForm(parent=self, submission_type=self.submission_type, used=self.used_equipment))
|
widg = eq.toForm(parent=self, submission_type=self.submission.submission_type, used=self.used_equipment)
|
||||||
|
self.layout.addWidget(widg)
|
||||||
|
widg.update_processes()
|
||||||
self.layout.addWidget(self.buttonBox)
|
self.layout.addWidget(self.buttonBox)
|
||||||
|
|
||||||
def parse_form(self):
|
def parse_form(self):
|
||||||
output = []
|
output = []
|
||||||
for widget in self.findChildren(QWidget):
|
for widget in self.findChildren(QWidget):
|
||||||
match widget:
|
match widget:
|
||||||
case (EquipmentCheckBox()|RoleComboBox()) :
|
case RoleComboBox() :
|
||||||
output.append(widget.parse_form())
|
if widget.check.isChecked():
|
||||||
|
output.append(widget.parse_form())
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return [item for item in output if item != None]
|
return [item for item in output if item != None]
|
||||||
|
|
||||||
|
class LabelRow(QWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.layout = QHBoxLayout()
|
||||||
|
self.check = QCheckBox()
|
||||||
|
self.layout.addWidget(self.check)
|
||||||
|
self.check.stateChanged.connect(self.check_all)
|
||||||
|
for item in ["Role", "Equipment", "Process"]:
|
||||||
|
l = QLabel(item)
|
||||||
|
l.setMaximumWidth(200)
|
||||||
|
l.setMinimumWidth(200)
|
||||||
|
self.layout.addWidget(l)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
def check_all(self):
|
||||||
|
for object in self.parent().findChildren(QCheckBox):
|
||||||
|
object.setChecked(self.check.isChecked())
|
||||||
|
|
||||||
class EquipmentCheckBox(QWidget):
|
class EquipmentCheckBox(QWidget):
|
||||||
|
|
||||||
def __init__(self, parent, equipment:PydEquipment) -> None:
|
def __init__(self, parent, equipment:PydEquipment) -> None:
|
||||||
@@ -56,7 +77,6 @@ class EquipmentCheckBox(QWidget):
|
|||||||
self.check = QCheckBox()
|
self.check = QCheckBox()
|
||||||
if equipment.static:
|
if equipment.static:
|
||||||
self.check.setChecked(True)
|
self.check.setChecked(True)
|
||||||
# self.check.setEnabled(False)
|
|
||||||
if equipment.nickname != None:
|
if equipment.nickname != None:
|
||||||
text = f"{equipment.name} ({equipment.nickname})"
|
text = f"{equipment.name} ({equipment.nickname})"
|
||||||
else:
|
else:
|
||||||
@@ -87,28 +107,42 @@ class RoleComboBox(QWidget):
|
|||||||
else:
|
else:
|
||||||
self.check.setChecked(True)
|
self.check.setChecked(True)
|
||||||
self.box = QComboBox()
|
self.box = QComboBox()
|
||||||
self.box.setMaximumWidth(125)
|
self.box.setMaximumWidth(200)
|
||||||
self.box.setMinimumWidth(125)
|
self.box.setMinimumWidth(200)
|
||||||
self.box.addItems([item.name for item in role.equipment])
|
self.box.addItems([item.name for item in role.equipment])
|
||||||
|
self.box.currentTextChanged.connect(self.update_processes)
|
||||||
# self.check = QCheckBox()
|
# self.check = QCheckBox()
|
||||||
# self.layout.addWidget(label)
|
# self.layout.addWidget(label)
|
||||||
self.process = QComboBox()
|
self.process = QComboBox()
|
||||||
self.process.setMaximumWidth(125)
|
self.process.setMaximumWidth(200)
|
||||||
self.process.setMinimumWidth(125)
|
self.process.setMinimumWidth(200)
|
||||||
self.process.setEditable(True)
|
self.process.setEditable(True)
|
||||||
# self.process.addItems(submission_type.get_processes_for_role(equipment_role=role.name))
|
# self.process.addItems(submission_type.get_processes_for_role(equipment_role=role.name))
|
||||||
self.process.addItems(role.processes)
|
# self.process.addItems(role.processes)
|
||||||
self.layout.addWidget(self.check)
|
self.layout.addWidget(self.check)
|
||||||
self.layout.addWidget(QLabel(f"{role.name}:"))
|
label = QLabel(f"{role.name}:")
|
||||||
|
label.setMinimumWidth(200)
|
||||||
|
label.setMaximumWidth(200)
|
||||||
|
label.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||||
|
self.layout.addWidget(label)
|
||||||
self.layout.addWidget(self.box)
|
self.layout.addWidget(self.box)
|
||||||
self.layout.addWidget(self.process)
|
self.layout.addWidget(self.process)
|
||||||
# self.layout.addWidget(self.check)
|
# self.layout.addWidget(self.check)
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
|
# self.update_processes()
|
||||||
|
|
||||||
|
def update_processes(self):
|
||||||
|
equip = self.box.currentText()
|
||||||
|
logger.debug(f"Updating equipment: {equip}")
|
||||||
|
equip2 = [item for item in self.role.equipment if item.name==equip][0]
|
||||||
|
logger.debug(f"Using: {equip2}")
|
||||||
|
self.process.clear()
|
||||||
|
self.process.addItems([item for item in equip2.processes if item in self.role.processes])
|
||||||
|
|
||||||
def parse_form(self) -> str|None:
|
def parse_form(self) -> str|None:
|
||||||
eq = Equipment.query(name=self.box.currentText())
|
eq = Equipment.query(name=self.box.currentText())
|
||||||
if self.check:
|
# if self.check.isChecked():
|
||||||
return PydEquipment(name=eq.name, process=self.process.currentText(), role=self.role.name, asset_number=eq.asset_number, nickname=eq.nickname)
|
return PydEquipment(name=eq.name, processes=[self.process.currentText()], role=self.role.name, asset_number=eq.asset_number, nickname=eq.nickname)
|
||||||
else:
|
# else:
|
||||||
return None
|
# return None
|
||||||
|
|
||||||
93
src/submissions/frontend/widgets/gel_checker.py
Normal file
93
src/submissions/frontend/widgets/gel_checker.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# import required modules
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
from PyQt6.QtWidgets import *
|
||||||
|
import sys
|
||||||
|
from PyQt6.QtWidgets import QWidget
|
||||||
|
import numpy as np
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from PyQt6.QtGui import *
|
||||||
|
from PyQt6.QtCore import *
|
||||||
|
from PIL import Image
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Main window class
|
||||||
|
class GelBox(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__(parent)
|
||||||
|
# setting title
|
||||||
|
self.setWindowTitle("PyQtGraph")
|
||||||
|
# setting geometry
|
||||||
|
self.setGeometry(100, 100, 600, 500)
|
||||||
|
# icon
|
||||||
|
icon = QIcon("skin.png")
|
||||||
|
# setting icon to the window
|
||||||
|
self.setWindowIcon(icon)
|
||||||
|
# calling method
|
||||||
|
self.UiComponents()
|
||||||
|
# showing all the widgets
|
||||||
|
# self.show()
|
||||||
|
|
||||||
|
# method for components
|
||||||
|
def UiComponents(self):
|
||||||
|
# widget = QWidget()
|
||||||
|
# setting configuration options
|
||||||
|
pg.setConfigOptions(antialias=True)
|
||||||
|
# creating image view object
|
||||||
|
self.imv = pg.ImageView()
|
||||||
|
img = np.array(Image.open("C:\\Users\\lwark\\Desktop\\PLATE1_17012024_103607AM_1_4x26.jpg").rotate(-90).transpose(Image.FLIP_LEFT_RIGHT))
|
||||||
|
self.imv.setImage(img)#, xvals=np.linspace(1., 3., data.shape[0]))
|
||||||
|
layout = QGridLayout()
|
||||||
|
# setting this layout to the widget
|
||||||
|
# widget.setLayout(layout)
|
||||||
|
# plot window goes on right side, spanning 3 rows
|
||||||
|
layout.addWidget(self.imv, 0, 0,20,20)
|
||||||
|
# setting this widget as central widget of the main window
|
||||||
|
self.form = ControlsForm(parent=self)
|
||||||
|
layout.addWidget(self.form,21,1,1,4)
|
||||||
|
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||||
|
self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
layout.addWidget(self.buttonBox, 21, 5, 1, 1)#, alignment=Qt.AlignmentFlag.AlignTop)
|
||||||
|
# self.buttonBox.clicked.connect(self.submit)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def parse_form(self):
|
||||||
|
return self.form.parse_form()
|
||||||
|
|
||||||
|
|
||||||
|
class ControlsForm(QWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent) -> None:
|
||||||
|
super().__init__(parent)
|
||||||
|
self.layout = QGridLayout()
|
||||||
|
columns = []
|
||||||
|
rows = []
|
||||||
|
for iii, item in enumerate(["Negative Control Key", "Description", "Results - 65 C", "Results - 63 C", "Results - Spike"]):
|
||||||
|
label = QLabel(item)
|
||||||
|
self.layout.addWidget(label, 0, iii,1,1)
|
||||||
|
if iii > 1:
|
||||||
|
columns.append(item)
|
||||||
|
for iii, item in enumerate(["RSL-NTC", "ENC-NTC", "NTC"], start=1):
|
||||||
|
label = QLabel(item)
|
||||||
|
self.layout.addWidget(label, iii, 0, 1, 1)
|
||||||
|
rows.append(item)
|
||||||
|
for iii, item in enumerate(["Processing Negative (PBS)", "Extraction Negative (Extraction buffers ONLY)", "Artic no-template control (mastermix ONLY)"], start=1):
|
||||||
|
label = QLabel(item)
|
||||||
|
self.layout.addWidget(label, iii, 1, 1, 1)
|
||||||
|
for iii in range(3):
|
||||||
|
for jjj in range(3):
|
||||||
|
widge = QLineEdit()
|
||||||
|
widge.setObjectName(f"{rows[iii]} : {columns[jjj]}")
|
||||||
|
self.layout.addWidget(widge, iii+1, jjj+2, 1, 1)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
def parse_form(self):
|
||||||
|
dicto = {}
|
||||||
|
for le in self.findChildren(QLineEdit):
|
||||||
|
label = [item.strip() for item in le.objectName().split(" : ")]
|
||||||
|
if label[0] not in dicto.keys():
|
||||||
|
dicto[label[0]] = {}
|
||||||
|
dicto[label[0]][label[1]] = le.text()
|
||||||
|
return dicto
|
||||||
198
src/submissions/frontend/widgets/submission_details.py
Normal file
198
src/submissions/frontend/widgets/submission_details.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
from PyQt6.QtWidgets import (QDialog, QScrollArea, QPushButton, QVBoxLayout, QMessageBox,
|
||||||
|
QLabel, QDialogButtonBox, QToolBar, QTextEdit)
|
||||||
|
from PyQt6.QtGui import QAction, QPixmap
|
||||||
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
from PyQt6 import QtPrintSupport
|
||||||
|
from backend.db.models import BasicSubmission
|
||||||
|
from ..visualizations import make_plate_barcode, make_plate_map, make_plate_map_html
|
||||||
|
from tools import check_if_app, jinja_template_loading
|
||||||
|
from .functions import select_save_file
|
||||||
|
from io import BytesIO
|
||||||
|
from xhtml2pdf import pisa
|
||||||
|
import logging, base64
|
||||||
|
from getpass import getuser
|
||||||
|
from datetime import datetime
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"submissions.{__name__}")
|
||||||
|
|
||||||
|
env = jinja_template_loading()
|
||||||
|
|
||||||
|
class SubmissionDetails(QDialog):
|
||||||
|
"""
|
||||||
|
a window showing text details of submission
|
||||||
|
"""
|
||||||
|
def __init__(self, parent, sub:BasicSubmission) -> None:
|
||||||
|
|
||||||
|
super().__init__(parent)
|
||||||
|
# self.ctx = ctx
|
||||||
|
try:
|
||||||
|
self.app = parent.parent().parent().parent().parent().parent().parent()
|
||||||
|
except AttributeError:
|
||||||
|
self.app = None
|
||||||
|
self.setWindowTitle("Submission Details")
|
||||||
|
# create scrollable interior
|
||||||
|
interior = QScrollArea()
|
||||||
|
interior.setParent(self)
|
||||||
|
# sub = BasicSubmission.query(id=id)
|
||||||
|
self.base_dict = sub.to_dict(full_data=True)
|
||||||
|
logger.debug(f"Submission details data:\n{pformat({k:v for k,v in self.base_dict.items() if k != 'samples'})}")
|
||||||
|
# don't want id
|
||||||
|
del self.base_dict['id']
|
||||||
|
logger.debug(f"Creating barcode.")
|
||||||
|
if not check_if_app():
|
||||||
|
self.base_dict['barcode'] = base64.b64encode(make_plate_barcode(self.base_dict['Plate Number'], width=120, height=30)).decode('utf-8')
|
||||||
|
logger.debug(f"Hitpicking plate...")
|
||||||
|
self.plate_dicto = sub.hitpick_plate()
|
||||||
|
logger.debug(f"Making platemap...")
|
||||||
|
self.base_dict['platemap'] = make_plate_map_html(self.plate_dicto)
|
||||||
|
self.template = env.get_template("submission_details.html")
|
||||||
|
self.html = self.template.render(sub=self.base_dict)
|
||||||
|
webview = QWebEngineView()
|
||||||
|
webview.setMinimumSize(900, 500)
|
||||||
|
webview.setMaximumSize(900, 500)
|
||||||
|
webview.setHtml(self.html)
|
||||||
|
self.layout = QVBoxLayout()
|
||||||
|
interior.resize(900, 500)
|
||||||
|
interior.setWidget(webview)
|
||||||
|
self.setFixedSize(900, 500)
|
||||||
|
# button to export a pdf version
|
||||||
|
btn = QPushButton("Export PDF")
|
||||||
|
btn.setParent(self)
|
||||||
|
btn.setFixedWidth(900)
|
||||||
|
btn.clicked.connect(self.export)
|
||||||
|
|
||||||
|
def export(self):
|
||||||
|
"""
|
||||||
|
Renders submission to html, then creates and saves .pdf file to user selected file.
|
||||||
|
"""
|
||||||
|
fname = select_save_file(obj=self, default_name=self.base_dict['Plate Number'], extension="pdf")
|
||||||
|
del self.base_dict['platemap']
|
||||||
|
export_map = make_plate_map(self.plate_dicto)
|
||||||
|
image_io = BytesIO()
|
||||||
|
try:
|
||||||
|
export_map.save(image_io, 'JPEG')
|
||||||
|
except AttributeError:
|
||||||
|
logger.error(f"No plate map found")
|
||||||
|
self.base_dict['export_map'] = base64.b64encode(image_io.getvalue()).decode('utf-8')
|
||||||
|
self.html2 = self.template.render(sub=self.base_dict)
|
||||||
|
try:
|
||||||
|
with open(fname, "w+b") as f:
|
||||||
|
pisa.CreatePDF(self.html2, dest=f)
|
||||||
|
except PermissionError as e:
|
||||||
|
logger.error(f"Error saving pdf: {e}")
|
||||||
|
msg = QMessageBox()
|
||||||
|
msg.setText("Permission Error")
|
||||||
|
msg.setInformativeText(f"Looks like {fname.__str__()} is open.\nPlease close it and try again.")
|
||||||
|
msg.setWindowTitle("Permission Error")
|
||||||
|
msg.exec()
|
||||||
|
|
||||||
|
class BarcodeWindow(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, rsl_num:str):
|
||||||
|
super().__init__()
|
||||||
|
# set the title
|
||||||
|
self.setWindowTitle("Image")
|
||||||
|
self.layout = QVBoxLayout()
|
||||||
|
# setting the geometry of window
|
||||||
|
self.setGeometry(0, 0, 400, 300)
|
||||||
|
# creating label
|
||||||
|
self.label = QLabel()
|
||||||
|
self.img = make_plate_barcode(rsl_num)
|
||||||
|
self.pixmap = QPixmap()
|
||||||
|
self.pixmap.loadFromData(self.img)
|
||||||
|
# adding image to label
|
||||||
|
self.label.setPixmap(self.pixmap)
|
||||||
|
# show all the widgets]
|
||||||
|
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||||
|
self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
self.layout.addWidget(self.label)
|
||||||
|
self.layout.addWidget(self.buttonBox)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
self._createActions()
|
||||||
|
self._createToolBar()
|
||||||
|
self._connectActions()
|
||||||
|
|
||||||
|
def _createToolBar(self):
|
||||||
|
"""
|
||||||
|
adds items to menu bar
|
||||||
|
"""
|
||||||
|
toolbar = QToolBar("My main toolbar")
|
||||||
|
toolbar.addAction(self.printAction)
|
||||||
|
|
||||||
|
|
||||||
|
def _createActions(self):
|
||||||
|
"""
|
||||||
|
creates actions
|
||||||
|
"""
|
||||||
|
self.printAction = QAction("&Print", self)
|
||||||
|
|
||||||
|
|
||||||
|
def _connectActions(self):
|
||||||
|
"""
|
||||||
|
connect menu and tool bar item to functions
|
||||||
|
"""
|
||||||
|
self.printAction.triggered.connect(self.print_barcode)
|
||||||
|
|
||||||
|
|
||||||
|
def print_barcode(self):
|
||||||
|
"""
|
||||||
|
Sends barcode image to printer.
|
||||||
|
"""
|
||||||
|
printer = QtPrintSupport.QPrinter()
|
||||||
|
dialog = QtPrintSupport.QPrintDialog(printer)
|
||||||
|
if dialog.exec():
|
||||||
|
self.handle_paint_request(printer, self.pixmap.toImage())
|
||||||
|
|
||||||
|
|
||||||
|
def handle_paint_request(self, printer:QtPrintSupport.QPrinter, im):
|
||||||
|
logger.debug(f"Hello from print handler.")
|
||||||
|
painter = QPainter(printer)
|
||||||
|
image = QPixmap.fromImage(im)
|
||||||
|
painter.drawPixmap(120, -20, image)
|
||||||
|
painter.end()
|
||||||
|
|
||||||
|
class SubmissionComment(QDialog):
|
||||||
|
"""
|
||||||
|
a window for adding comment text to a submission
|
||||||
|
"""
|
||||||
|
def __init__(self, parent, submission:BasicSubmission) -> None:
|
||||||
|
|
||||||
|
super().__init__(parent)
|
||||||
|
# self.ctx = ctx
|
||||||
|
try:
|
||||||
|
self.app = parent.parent().parent().parent().parent().parent().parent
|
||||||
|
print(f"App: {self.app}")
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
self.submission = submission
|
||||||
|
self.setWindowTitle(f"{self.submission.rsl_plate_num} Submission Comment")
|
||||||
|
# create text field
|
||||||
|
self.txt_editor = QTextEdit(self)
|
||||||
|
self.txt_editor.setReadOnly(False)
|
||||||
|
self.txt_editor.setText("Add Comment")
|
||||||
|
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||||
|
self.buttonBox = QDialogButtonBox(QBtn)
|
||||||
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
self.layout = QVBoxLayout()
|
||||||
|
self.setFixedSize(400, 300)
|
||||||
|
self.layout.addWidget(self.txt_editor)
|
||||||
|
self.layout.addWidget(self.buttonBox, alignment=Qt.AlignmentFlag.AlignBottom)
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
|
def parse_form(self):
|
||||||
|
"""
|
||||||
|
Adds comment to submission object.
|
||||||
|
"""
|
||||||
|
commenter = getuser()
|
||||||
|
comment = self.txt_editor.toPlainText()
|
||||||
|
dt = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")
|
||||||
|
full_comment = [{"name":commenter, "time": dt, "text": comment}]
|
||||||
|
logger.debug(f"Full comment: {full_comment}")
|
||||||
|
return full_comment
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ from PyQt6.QtWidgets import (
|
|||||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel
|
from PyQt6.QtCore import Qt, QAbstractTableModel, QSortFilterProxyModel
|
||||||
from PyQt6.QtGui import QAction, QCursor, QPixmap, QPainter
|
from PyQt6.QtGui import QAction, QCursor, QPixmap, QPainter
|
||||||
from backend.db.models import BasicSubmission, Equipment, SubmissionEquipmentAssociation, Process
|
from backend.db.models import BasicSubmission, Equipment
|
||||||
from backend.excel import make_report_html, make_report_xlsx
|
from backend.excel import make_report_html, make_report_xlsx
|
||||||
from tools import check_if_app, Report, Result, jinja_template_loading, get_first_blank_df_row, row_map
|
from tools import check_if_app, Report, Result, jinja_template_loading, get_first_blank_df_row, row_map
|
||||||
from xhtml2pdf import pisa
|
from xhtml2pdf import pisa
|
||||||
@@ -95,8 +95,8 @@ class SubmissionsSheet(QTableView):
|
|||||||
self.resizeColumnsToContents()
|
self.resizeColumnsToContents()
|
||||||
self.resizeRowsToContents()
|
self.resizeRowsToContents()
|
||||||
self.setSortingEnabled(True)
|
self.setSortingEnabled(True)
|
||||||
|
# self.doubleClicked.connect(self.show_details)
|
||||||
self.doubleClicked.connect(self.show_details)
|
self.doubleClicked.connect(lambda x: BasicSubmission.query(id=x.sibling(x.row(), 0).data()).show_details(self))
|
||||||
|
|
||||||
def setData(self) -> None:
|
def setData(self) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -114,16 +114,16 @@ class SubmissionsSheet(QTableView):
|
|||||||
proxyModel.setSourceModel(pandasModel(self.data))
|
proxyModel.setSourceModel(pandasModel(self.data))
|
||||||
self.setModel(proxyModel)
|
self.setModel(proxyModel)
|
||||||
|
|
||||||
def show_details(self) -> None:
|
# def show_details(self, submission:BasicSubmission) -> None:
|
||||||
"""
|
# """
|
||||||
creates detailed data to show in seperate window
|
# creates detailed data to show in seperate window
|
||||||
"""
|
# """
|
||||||
logger.debug(f"Sheet.app: {self.app}")
|
# logger.debug(f"Sheet.app: {self.app}")
|
||||||
index = (self.selectionModel().currentIndex())
|
# # index = (self.selectionModel().currentIndex())
|
||||||
value = index.sibling(index.row(),0).data()
|
# # value = index.sibling(index.row(),0).data()
|
||||||
dlg = SubmissionDetails(parent=self, id=value)
|
# dlg = SubmissionDetails(parent=self, sub=submission)
|
||||||
if dlg.exec():
|
# if dlg.exec():
|
||||||
pass
|
# pass
|
||||||
|
|
||||||
def create_barcode(self) -> None:
|
def create_barcode(self) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -154,38 +154,47 @@ class SubmissionsSheet(QTableView):
|
|||||||
Args:
|
Args:
|
||||||
event (_type_): the item of interest
|
event (_type_): the item of interest
|
||||||
"""
|
"""
|
||||||
|
id = self.selectionModel().currentIndex()
|
||||||
|
id = id.sibling(id.row(),0).data()
|
||||||
|
submission = BasicSubmission.query(id=id)
|
||||||
self.menu = QMenu(self)
|
self.menu = QMenu(self)
|
||||||
renameAction = QAction('Delete', self)
|
# renameAction = QAction('Delete', self)
|
||||||
detailsAction = QAction('Details', self)
|
# detailsAction = QAction('Details', self)
|
||||||
# barcodeAction = QAction("Print Barcode", self)
|
# commentAction = QAction("Add Comment", self)
|
||||||
commentAction = QAction("Add Comment", self)
|
# equipAction = QAction("Add Equipment", self)
|
||||||
backupAction = QAction("Backup", self)
|
# backupAction = QAction("Export", self)
|
||||||
equipAction = QAction("Add Equipment", self)
|
# renameAction.triggered.connect(lambda: self.delete_item(submission))
|
||||||
# hitpickAction = QAction("Hitpicks", self)
|
# detailsAction.triggered.connect(lambda: self.show_details(submission))
|
||||||
renameAction.triggered.connect(lambda: self.delete_item(event))
|
# commentAction.triggered.connect(lambda: self.add_comment(submission))
|
||||||
detailsAction.triggered.connect(lambda: self.show_details())
|
# backupAction.triggered.connect(lambda: self.regenerate_submission_form(submission))
|
||||||
# barcodeAction.triggered.connect(lambda: self.create_barcode())
|
# equipAction.triggered.connect(lambda: self.add_equipment(submission))
|
||||||
commentAction.triggered.connect(lambda: self.add_comment())
|
# self.menu.addAction(detailsAction)
|
||||||
backupAction.triggered.connect(lambda: self.regenerate_submission_form())
|
# self.menu.addAction(renameAction)
|
||||||
equipAction.triggered.connect(lambda: self.add_equipment())
|
# self.menu.addAction(commentAction)
|
||||||
# hitpickAction.triggered.connect(lambda: self.hit_pick())
|
# self.menu.addAction(backupAction)
|
||||||
self.menu.addAction(detailsAction)
|
# self.menu.addAction(equipAction)
|
||||||
self.menu.addAction(renameAction)
|
self.con_actions = submission.custom_context_events()
|
||||||
# self.menu.addAction(barcodeAction)
|
for k in self.con_actions.keys():
|
||||||
self.menu.addAction(commentAction)
|
logger.debug(f"Adding {k}")
|
||||||
self.menu.addAction(backupAction)
|
action = QAction(k, self)
|
||||||
self.menu.addAction(equipAction)
|
action.triggered.connect(lambda _, action_name=k: self.triggered_action(action_name=action_name))
|
||||||
# self.menu.addAction(hitpickAction)
|
self.menu.addAction(action)
|
||||||
# add other required actions
|
# add other required actions
|
||||||
self.menu.popup(QCursor.pos())
|
self.menu.popup(QCursor.pos())
|
||||||
|
|
||||||
|
def triggered_action(self, action_name:str):
|
||||||
|
logger.debug(f"Action: {action_name}")
|
||||||
|
logger.debug(f"Responding with {self.con_actions[action_name]}")
|
||||||
|
func = self.con_actions[action_name]
|
||||||
|
func(obj=self)
|
||||||
|
|
||||||
def add_equipment(self):
|
def add_equipment(self):
|
||||||
index = (self.selectionModel().currentIndex())
|
index = (self.selectionModel().currentIndex())
|
||||||
value = index.sibling(index.row(),0).data()
|
value = index.sibling(index.row(),0).data()
|
||||||
self.add_equipment_function(rsl_plate_id=value)
|
self.add_equipment_function(rsl_plate_id=value)
|
||||||
|
|
||||||
def add_equipment_function(self, rsl_plate_id):
|
def add_equipment_function(self, submission:BasicSubmission):
|
||||||
submission = BasicSubmission.query(id=rsl_plate_id)
|
# submission = BasicSubmission.query(id=rsl_plate_id)
|
||||||
submission_type = submission.submission_type_name
|
submission_type = submission.submission_type_name
|
||||||
dlg = EquipmentUsage(parent=self, submission_type=submission_type, submission=submission)
|
dlg = EquipmentUsage(parent=self, submission_type=submission_type, submission=submission)
|
||||||
if dlg.exec():
|
if dlg.exec():
|
||||||
@@ -193,29 +202,33 @@ class SubmissionsSheet(QTableView):
|
|||||||
logger.debug(f"We've got equipment: {equipment}")
|
logger.debug(f"We've got equipment: {equipment}")
|
||||||
for equip in equipment:
|
for equip in equipment:
|
||||||
e = Equipment.query(name=equip.name)
|
e = Equipment.query(name=equip.name)
|
||||||
assoc = SubmissionEquipmentAssociation(submission=submission, equipment=e)
|
# assoc = SubmissionEquipmentAssociation(submission=submission, equipment=e)
|
||||||
process = Process.query(name=equip.process)
|
# process = Process.query(name=equip.processes)
|
||||||
assoc.process = process
|
# assoc.process = process
|
||||||
assoc.role = equip.role
|
# assoc.role = equip.role
|
||||||
|
_, assoc = equip.toSQL(submission=submission)
|
||||||
# submission.submission_equipment_associations.append(assoc)
|
# submission.submission_equipment_associations.append(assoc)
|
||||||
logger.debug(f"Appending SubmissionEquipmentAssociation: {assoc}")
|
logger.debug(f"Appending SubmissionEquipmentAssociation: {assoc}")
|
||||||
# submission.save()
|
# submission.save()
|
||||||
assoc.save()
|
assoc.save()
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
def delete_item(self, event):
|
def delete_item(self, submission:BasicSubmission):
|
||||||
"""
|
"""
|
||||||
Confirms user deletion and sends id to backend for deletion.
|
Confirms user deletion and sends id to backend for deletion.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (_type_): the item of interest
|
event (_type_): the item of interest
|
||||||
"""
|
"""
|
||||||
index = (self.selectionModel().currentIndex())
|
# index = (self.selectionModel().currentIndex())
|
||||||
value = index.sibling(index.row(),0).data()
|
# value = index.sibling(index.row(),0).data()
|
||||||
logger.debug(index)
|
# logger.debug(index)
|
||||||
msg = QuestionAsker(title="Delete?", message=f"Are you sure you want to delete {index.sibling(index.row(),1).data()}?\n")
|
# msg = QuestionAsker(title="Delete?", message=f"Are you sure you want to delete {index.sibling(index.row(),1).data()}?\n")
|
||||||
|
msg = QuestionAsker(title="Delete?", message=f"Are you sure you want to delete {submission.rsl_plate_num}?\n")
|
||||||
if msg.exec():
|
if msg.exec():
|
||||||
# delete_submission(id=value)
|
# delete_submission(id=value)
|
||||||
BasicSubmission.query(id=value).delete()
|
submission.delete()
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
self.setData()
|
self.setData()
|
||||||
@@ -424,221 +437,11 @@ class SubmissionsSheet(QTableView):
|
|||||||
writer.close()
|
writer.close()
|
||||||
self.report.add_result(report)
|
self.report.add_result(report)
|
||||||
|
|
||||||
def regenerate_submission_form(self):
|
def regenerate_submission_form(self, submission:BasicSubmission):
|
||||||
index = (self.selectionModel().currentIndex())
|
# index = (self.selectionModel().currentIndex())
|
||||||
value = index.sibling(index.row(),0).data()
|
# value = index.sibling(index.row(),0).data()
|
||||||
logger.debug(index)
|
# logger.debug(index)
|
||||||
# msg = QuestionAsker(title="Delete?", message=f"Are you sure you want to delete {index.sibling(index.row(),1).data()}?\n")
|
# sub = BasicSubmission.query(id=value)
|
||||||
# if msg.exec():
|
fname = select_save_file(self, default_name=submission.to_pydantic().construct_filename(), extension="xlsx")
|
||||||
# delete_submission(id=value)
|
submission.backup(fname=fname, full_backup=False)
|
||||||
sub = BasicSubmission.query(id=value)
|
|
||||||
fname = select_save_file(self, default_name=sub.to_pydantic().construct_filename(), extension="xlsx")
|
|
||||||
sub.backup(fname=fname, full_backup=False)
|
|
||||||
|
|
||||||
class SubmissionDetails(QDialog):
|
|
||||||
"""
|
|
||||||
a window showing text details of submission
|
|
||||||
"""
|
|
||||||
def __init__(self, parent, id:int) -> None:
|
|
||||||
|
|
||||||
super().__init__(parent)
|
|
||||||
# self.ctx = ctx
|
|
||||||
try:
|
|
||||||
self.app = parent.parent().parent().parent().parent().parent().parent()
|
|
||||||
except AttributeError:
|
|
||||||
self.app = None
|
|
||||||
self.setWindowTitle("Submission Details")
|
|
||||||
# create scrollable interior
|
|
||||||
interior = QScrollArea()
|
|
||||||
interior.setParent(self)
|
|
||||||
# get submision from db
|
|
||||||
# sub = lookup_submissions(ctx=ctx, id=id)
|
|
||||||
sub = BasicSubmission.query(id=id)
|
|
||||||
logger.debug(f"Submission details data:\n{pformat(sub.to_dict())}")
|
|
||||||
self.base_dict = sub.to_dict(full_data=True)
|
|
||||||
# don't want id
|
|
||||||
del self.base_dict['id']
|
|
||||||
logger.debug(f"Creating barcode.")
|
|
||||||
if not check_if_app():
|
|
||||||
self.base_dict['barcode'] = base64.b64encode(make_plate_barcode(self.base_dict['Plate Number'], width=120, height=30)).decode('utf-8')
|
|
||||||
logger.debug(f"Hitpicking plate...")
|
|
||||||
self.plate_dicto = sub.hitpick_plate()
|
|
||||||
logger.debug(f"Making platemap...")
|
|
||||||
self.base_dict['platemap'] = make_plate_map_html(self.plate_dicto)
|
|
||||||
# logger.debug(f"Platemap: {self.base_dict['platemap']}")
|
|
||||||
# logger.debug(f"platemap: {platemap}")
|
|
||||||
# image_io = BytesIO()
|
|
||||||
# try:
|
|
||||||
# platemap.save(image_io, 'JPEG')
|
|
||||||
# except AttributeError:
|
|
||||||
# logger.error(f"No plate map found for {sub.rsl_plate_num}")
|
|
||||||
# self.base_dict['platemap'] = base64.b64encode(image_io.getvalue()).decode('utf-8')
|
|
||||||
self.template = env.get_template("submission_details.html")
|
|
||||||
self.html = self.template.render(sub=self.base_dict)
|
|
||||||
webview = QWebEngineView()
|
|
||||||
webview.setMinimumSize(900, 500)
|
|
||||||
webview.setMaximumSize(900, 500)
|
|
||||||
webview.setHtml(self.html)
|
|
||||||
self.layout = QVBoxLayout()
|
|
||||||
interior.resize(900, 500)
|
|
||||||
interior.setWidget(webview)
|
|
||||||
self.setFixedSize(900, 500)
|
|
||||||
# button to export a pdf version
|
|
||||||
btn = QPushButton("Export PDF")
|
|
||||||
btn.setParent(self)
|
|
||||||
btn.setFixedWidth(900)
|
|
||||||
btn.clicked.connect(self.export)
|
|
||||||
|
|
||||||
def export(self):
|
|
||||||
"""
|
|
||||||
Renders submission to html, then creates and saves .pdf file to user selected file.
|
|
||||||
"""
|
|
||||||
# try:
|
|
||||||
# home_dir = Path(self.ctx.directory_path).joinpath(f"Submission_Details_{self.base_dict['Plate Number']}.pdf").resolve().__str__()
|
|
||||||
# except FileNotFoundError:
|
|
||||||
# home_dir = Path.home().resolve().__str__()
|
|
||||||
# fname = Path(QFileDialog.getSaveFileName(self, "Save File", home_dir, filter=".pdf")[0])
|
|
||||||
# if fname.__str__() == ".":
|
|
||||||
# logger.debug("Saving pdf was cancelled.")
|
|
||||||
# return
|
|
||||||
fname = select_save_file(obj=self, default_name=self.base_dict['Plate Number'], extension="pdf")
|
|
||||||
del self.base_dict['platemap']
|
|
||||||
export_map = make_plate_map(self.plate_dicto)
|
|
||||||
image_io = BytesIO()
|
|
||||||
try:
|
|
||||||
export_map.save(image_io, 'JPEG')
|
|
||||||
except AttributeError:
|
|
||||||
logger.error(f"No plate map found")
|
|
||||||
self.base_dict['export_map'] = base64.b64encode(image_io.getvalue()).decode('utf-8')
|
|
||||||
self.html2 = self.template.render(sub=self.base_dict)
|
|
||||||
try:
|
|
||||||
with open(fname, "w+b") as f:
|
|
||||||
pisa.CreatePDF(self.html2, dest=f)
|
|
||||||
except PermissionError as e:
|
|
||||||
logger.error(f"Error saving pdf: {e}")
|
|
||||||
msg = QMessageBox()
|
|
||||||
msg.setText("Permission Error")
|
|
||||||
msg.setInformativeText(f"Looks like {fname.__str__()} is open.\nPlease close it and try again.")
|
|
||||||
msg.setWindowTitle("Permission Error")
|
|
||||||
msg.exec()
|
|
||||||
|
|
||||||
class BarcodeWindow(QDialog):
|
|
||||||
|
|
||||||
def __init__(self, rsl_num:str):
|
|
||||||
super().__init__()
|
|
||||||
# set the title
|
|
||||||
self.setWindowTitle("Image")
|
|
||||||
self.layout = QVBoxLayout()
|
|
||||||
# setting the geometry of window
|
|
||||||
self.setGeometry(0, 0, 400, 300)
|
|
||||||
# creating label
|
|
||||||
self.label = QLabel()
|
|
||||||
self.img = make_plate_barcode(rsl_num)
|
|
||||||
self.pixmap = QPixmap()
|
|
||||||
self.pixmap.loadFromData(self.img)
|
|
||||||
# adding image to label
|
|
||||||
self.label.setPixmap(self.pixmap)
|
|
||||||
# show all the widgets]
|
|
||||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
|
||||||
self.buttonBox = QDialogButtonBox(QBtn)
|
|
||||||
self.buttonBox.accepted.connect(self.accept)
|
|
||||||
self.buttonBox.rejected.connect(self.reject)
|
|
||||||
self.layout.addWidget(self.label)
|
|
||||||
self.layout.addWidget(self.buttonBox)
|
|
||||||
self.setLayout(self.layout)
|
|
||||||
self._createActions()
|
|
||||||
self._createToolBar()
|
|
||||||
self._connectActions()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _createToolBar(self):
|
|
||||||
"""
|
|
||||||
adds items to menu bar
|
|
||||||
"""
|
|
||||||
toolbar = QToolBar("My main toolbar")
|
|
||||||
toolbar.addAction(self.printAction)
|
|
||||||
|
|
||||||
|
|
||||||
def _createActions(self):
|
|
||||||
"""
|
|
||||||
creates actions
|
|
||||||
"""
|
|
||||||
self.printAction = QAction("&Print", self)
|
|
||||||
|
|
||||||
|
|
||||||
def _connectActions(self):
|
|
||||||
"""
|
|
||||||
connect menu and tool bar item to functions
|
|
||||||
"""
|
|
||||||
self.printAction.triggered.connect(self.print_barcode)
|
|
||||||
|
|
||||||
|
|
||||||
def print_barcode(self):
|
|
||||||
"""
|
|
||||||
Sends barcode image to printer.
|
|
||||||
"""
|
|
||||||
printer = QtPrintSupport.QPrinter()
|
|
||||||
dialog = QtPrintSupport.QPrintDialog(printer)
|
|
||||||
if dialog.exec():
|
|
||||||
self.handle_paint_request(printer, self.pixmap.toImage())
|
|
||||||
|
|
||||||
|
|
||||||
def handle_paint_request(self, printer:QtPrintSupport.QPrinter, im):
|
|
||||||
logger.debug(f"Hello from print handler.")
|
|
||||||
painter = QPainter(printer)
|
|
||||||
image = QPixmap.fromImage(im)
|
|
||||||
painter.drawPixmap(120, -20, image)
|
|
||||||
painter.end()
|
|
||||||
|
|
||||||
class SubmissionComment(QDialog):
|
|
||||||
"""
|
|
||||||
a window for adding comment text to a submission
|
|
||||||
"""
|
|
||||||
def __init__(self, parent, rsl:str) -> None:
|
|
||||||
|
|
||||||
super().__init__(parent)
|
|
||||||
# self.ctx = ctx
|
|
||||||
try:
|
|
||||||
self.app = parent.parent().parent().parent().parent().parent().parent
|
|
||||||
print(f"App: {self.app}")
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
self.rsl = rsl
|
|
||||||
self.setWindowTitle(f"{self.rsl} Submission Comment")
|
|
||||||
# create text field
|
|
||||||
self.txt_editor = QTextEdit(self)
|
|
||||||
self.txt_editor.setReadOnly(False)
|
|
||||||
self.txt_editor.setText("Add Comment")
|
|
||||||
QBtn = QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
|
||||||
self.buttonBox = QDialogButtonBox(QBtn)
|
|
||||||
self.buttonBox.accepted.connect(self.accept)
|
|
||||||
self.buttonBox.rejected.connect(self.reject)
|
|
||||||
self.layout = QVBoxLayout()
|
|
||||||
self.setFixedSize(400, 300)
|
|
||||||
self.layout.addWidget(self.txt_editor)
|
|
||||||
self.layout.addWidget(self.buttonBox, alignment=Qt.AlignmentFlag.AlignBottom)
|
|
||||||
self.setLayout(self.layout)
|
|
||||||
|
|
||||||
def add_comment(self):
|
|
||||||
"""
|
|
||||||
Adds comment to submission object.
|
|
||||||
"""
|
|
||||||
commenter = getuser()
|
|
||||||
comment = self.txt_editor.toPlainText()
|
|
||||||
dt = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")
|
|
||||||
|
|
||||||
full_comment = [{"name":commenter, "time": dt, "text": comment}]
|
|
||||||
logger.debug(f"Full comment: {full_comment}")
|
|
||||||
sub = BasicSubmission.query(rsl_number=self.rsl)
|
|
||||||
try:
|
|
||||||
# For some reason .append results in new comment being ignores, so have to concatenate lists.
|
|
||||||
sub.comment = sub.comment + full_comment
|
|
||||||
except (AttributeError, TypeError) as e:
|
|
||||||
logger.error(f"Hit error {e} creating comment")
|
|
||||||
sub.comment = full_comment
|
|
||||||
# logger.debug(sub.comment)
|
|
||||||
sub.save(original=False)
|
|
||||||
# logger.debug(f"Save result: {result}")
|
|
||||||
|
|
||||||
|
|
||||||
@@ -323,21 +323,21 @@ class SubmissionFormContainer(QWidget):
|
|||||||
# reset form
|
# reset form
|
||||||
self.form.setParent(None)
|
self.form.setParent(None)
|
||||||
# logger.debug(f"All attributes of obj: {pformat(self.__dict__)}")
|
# logger.debug(f"All attributes of obj: {pformat(self.__dict__)}")
|
||||||
wkb = self.pyd.autofill_excel()
|
# wkb = self.pyd.autofill_excel()
|
||||||
if wkb != None:
|
# if wkb != None:
|
||||||
fname = select_save_file(obj=self, default_name=self.pyd.construct_filename(), extension="xlsx")
|
# fname = select_save_file(obj=self, default_name=self.pyd.construct_filename(), extension="xlsx")
|
||||||
try:
|
# try:
|
||||||
wkb.save(filename=fname.__str__())
|
# wkb.save(filename=fname.__str__())
|
||||||
except PermissionError:
|
# except PermissionError:
|
||||||
logger.error("Hit a permission error when saving workbook. Cancelled?")
|
# logger.error("Hit a permission error when saving workbook. Cancelled?")
|
||||||
if hasattr(self.pyd, 'csv'):
|
# if hasattr(self.pyd, 'csv'):
|
||||||
dlg = QuestionAsker("Export CSV?", "Would you like to export the csv file?")
|
# dlg = QuestionAsker("Export CSV?", "Would you like to export the csv file?")
|
||||||
if dlg.exec():
|
# if dlg.exec():
|
||||||
fname = select_save_file(self, f"{self.pyd.construct_filename()}.csv", extension="csv")
|
# fname = select_save_file(self, f"{self.pyd.construct_filename()}.csv", extension="csv")
|
||||||
try:
|
# try:
|
||||||
self.pyd.csv.to_csv(fname.__str__(), index=False)
|
# self.pyd.csv.to_csv(fname.__str__(), index=False)
|
||||||
except PermissionError:
|
# except PermissionError:
|
||||||
logger.debug(f"Could not get permissions to {fname}. Possibly the request was cancelled.")
|
# logger.debug(f"Could not get permissions to {fname}. Possibly the request was cancelled.")
|
||||||
self.report.add_result(report)
|
self.report.add_result(report)
|
||||||
|
|
||||||
def export_csv_function(self, fname:Path|None=None):
|
def export_csv_function(self, fname:Path|None=None):
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
{% if sub['equipment'] %}
|
{% if sub['equipment'] %}
|
||||||
<h3><u>Equipment:</u></h3>
|
<h3><u>Equipment:</u></h3>
|
||||||
<p>{% for item in sub['equipment'] %}
|
<p>{% for item in sub['equipment'] %}
|
||||||
<b>{{ item['role'] }}:</b> {{ item['name'] }}({{ item['asset_number'] }}): {{ item['process']|replace('\n\t', '<br> ') }}<br>
|
<b>{{ item['role'] }}:</b> {{ item['name'] }} ({{ item['asset_number'] }}): {{ item['processes'][0]|replace('\n\t', '<br> ') }}<br>
|
||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if sub['samples'] %}
|
{% if sub['samples'] %}
|
||||||
@@ -97,9 +97,9 @@
|
|||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if sub['comments'] %}
|
{% if sub['comment'] %}
|
||||||
<h3><u>Comments:</u></h3>
|
<h3><u>Comments:</u></h3>
|
||||||
<p>{% for entry in sub['comments'] %}
|
<p>{% for entry in sub['comment'] %}
|
||||||
<b>{{ entry['name'] }}:</b><br> {{ entry['text'] }}<br>- {{ entry['time'] }}<br>
|
<b>{{ entry['name'] }}:</b><br> {{ entry['text'] }}<br>- {{ entry['time'] }}<br>
|
||||||
{% endfor %}</p>
|
{% endfor %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -391,6 +391,21 @@ def check_authorization(func):
|
|||||||
return dict(code=1, message="This user does not have permission for this function.", status="warning")
|
return dict(code=1, message="This user does not have permission for this function.", status="warning")
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
# def check_authorization(user:str):
|
||||||
|
# def decorator(function):
|
||||||
|
# def wrapper(*args, **kwargs):
|
||||||
|
# # funny_stuff()
|
||||||
|
# # print(argument)
|
||||||
|
# power_users =
|
||||||
|
# if user in ctx.power_users:
|
||||||
|
# result = function(*args, **kwargs)
|
||||||
|
# else:
|
||||||
|
# logger.error(f"User {getpass.getuser()} is not authorized for this function.")
|
||||||
|
# result = dict(code=1, message="This user does not have permission for this function.", status="warning")
|
||||||
|
# return result
|
||||||
|
# return wrapper
|
||||||
|
# return decorator
|
||||||
|
|
||||||
def check_if_app() -> bool:
|
def check_if_app() -> bool:
|
||||||
"""
|
"""
|
||||||
Checks if the program is running from pyinstaller compiled
|
Checks if the program is running from pyinstaller compiled
|
||||||
@@ -461,6 +476,12 @@ class Result(BaseModel):
|
|||||||
msg: str
|
msg: str
|
||||||
status: Literal["NoIcon", "Question", "Information", "Warning", "Critical"] = Field(default="NoIcon")
|
status: Literal["NoIcon", "Question", "Information", "Warning", "Critical"] = Field(default="NoIcon")
|
||||||
|
|
||||||
|
|
||||||
|
@field_validator('status', mode='before')
|
||||||
|
@classmethod
|
||||||
|
def to_title(cls, value:str):
|
||||||
|
return value.title()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Result({self.owner})"
|
return f"Result({self.owner})"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user