From a84fe11fc32be6d12c0759205ae4220c9ca65287 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Tue, 21 Jul 2020 11:52:25 +0200 Subject: [PATCH 01/40] Add preferred transcripts parameter --- ExonCov.py | 2 +- ExonCov/cli.py | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/ExonCov.py b/ExonCov.py index b5975b9..96ea354 100755 --- a/ExonCov.py +++ b/ExonCov.py @@ -18,7 +18,7 @@ db_manager.add_command('migrate', MigrateCommand) db_manager.add_command('stats', cli.PrintStats()) db_manager.add_command('panel_genes', cli.PrintPanelGenesTable()) -db_manager.add_command('transcript_gene', cli.PrintTranscripts()) +db_manager.add_command('gene_transcripts', cli.PrintTranscripts()) db_manager.add_command('import_alias_table', cli.ImportAliasTable()) db_manager.add_command('export_panel_bed', cli.PrintPanelBed()) #db_manager.add_command('load_design', cli.LoadDesign()) diff --git a/ExonCov/cli.py b/ExonCov/cli.py index 332b42b..736c61f 100644 --- a/ExonCov/cli.py +++ b/ExonCov/cli.py @@ -65,18 +65,23 @@ def run(self): class PrintTranscripts(Command): - """Print tab delimited transcript / gene table""" + """Print tab delimited gene / transcript table""" - def run(self): - print('{transcript}\t{gene}'.format(transcript='Transcript', gene='Gene')) + option_list = ( + Option('-p', '--preferred_transcripts', dest='preferred_transcripts', default=False, action='store_true', help="Print preferred transcripts only"), + ) - transcripts = Transcript.query.options(joinedload('gene')) + def run(self, preferred_transcripts): + print('{gene}\t{transcript}'.format( gene='Gene', transcript='Transcript')) - for transcript in transcripts: - print('{transcript}\t{gene}'.format( - transcript=transcript.name, - gene=transcript.gene - )) + genes = Gene.query.options(joinedload('transcripts')) + + for gene in genes: + if preferred_transcripts: + print('{gene}\t{transcript}'.format(gene=gene.id, transcript=gene.default_transcript.name)) + else: + for transcript in gene.transcripts: + print('{gene}\t{transcript}'.format(gene=gene.id,transcript=transcript.name)) class ImportBam(Command): From d691767545638a29d0cc39ec5e8c37c147b07f23 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Wed, 2 Sep 2020 18:20:14 +0200 Subject: [PATCH 02/40] Add metadata. --- ExonCov/admin_views.py | 10 ++--- ExonCov/cli.py | 2 + ExonCov/models.py | 10 +++++ ExonCov/templates/macros/tables.html | 10 ++--- ExonCov/templates/panel.html | 9 +++++ ExonCov/templates/panel_version.html | 1 + ExonCov/templates/sample.html | 3 +- ExonCov/templates/sample_gene.html | 1 + ExonCov/templates/sample_inactive_panels.html | 3 +- ExonCov/templates/sample_panel.html | 4 +- ExonCov/templates/sample_transcript.html | 1 + ExonCov/templates/samples.html | 2 + ExonCov/views.py | 2 + migrations/versions/dda6242a627d_metadata.py | 40 +++++++++++++++++++ 14 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 migrations/versions/dda6242a627d_metadata.py diff --git a/ExonCov/admin_views.py b/ExonCov/admin_views.py index dfe0889..abc15be 100644 --- a/ExonCov/admin_views.py +++ b/ExonCov/admin_views.py @@ -33,17 +33,17 @@ def _handle_view(self, name, **kwargs): class PanelAdminView(CustomModelView): """Panel admin view.""" - column_list = ['name', 'versions'] + column_list = ['name', 'versions', 'disease_description_eng', 'clinical_geneticist', 'staff_member', 'comments'] column_searchable_list = ['name'] - form_columns = ['name'] + form_columns = ['name', 'disease_description_nl', 'disease_description_eng', 'comments' ,'patientfolder_alissa', 'clinical_geneticist', 'staff_member'] class PanelVersionAdminView(CustomModelView): """Panel version admin view.""" column_searchable_list = ['panel_name'] - form_columns = ['panel', 'version_year', 'version_revision', 'comments', 'active', 'validated', 'transcripts'] + form_columns = ['panel', 'version_year', 'version_revision', 'comments', 'coverage_requirement_15', 'active', 'validated', 'transcripts'] form_ajax_refs = { 'transcripts': { 'fields': ['name', 'gene_id'], @@ -121,11 +121,11 @@ class ExonAdminView(CustomModelView): class SampleAdminView(CustomModelView): """Sample admin view.""" - column_list = ['name', 'project', 'sequencing_runs', 'import_date'] + column_list = ['name', 'type', 'project', 'sequencing_runs', 'import_date'] column_sortable_list = ['name', 'import_date'] column_searchable_list = ['name'] - form_columns = ['name', 'project', 'sequencing_runs', 'import_date', 'file_name', 'import_command', 'exon_measurement_file'] + form_columns = ['name', 'type', 'project', 'sequencing_runs', 'import_date', 'file_name', 'import_command', 'exon_measurement_file'] form_ajax_refs = { 'project': { 'fields': ['name'], diff --git a/ExonCov/cli.py b/ExonCov/cli.py index 736c61f..7bbe728 100644 --- a/ExonCov/cli.py +++ b/ExonCov/cli.py @@ -89,6 +89,7 @@ class ImportBam(Command): option_list = ( Option('project_name'), + Option('sample_type', choices=['WES', 'WGS', 'RNA']), Option('bam'), Option('-b', '--exon_bed', dest='exon_bed_file', default=app.config['EXON_BED_FILE']), Option('-t', '--threads', dest='threads', default=1), @@ -158,6 +159,7 @@ def run(self, bam, project_name, exon_bed_file, threads, overwrite, print_output sample = Sample( name=sample_name, project=sample_project, + type=sample_type, file_name=bam, import_command=sambamba_command, sequencing_runs=sequencing_runs.values(), diff --git a/ExonCov/models.py b/ExonCov/models.py index 2251f92..d4ddda5 100644 --- a/ExonCov/models.py +++ b/ExonCov/models.py @@ -147,6 +147,12 @@ class Panel(db.Model): __tablename__ = 'panels' name = db.Column(db.String(50), primary_key=True) + disease_description_eng = db.Column(db.String(255)) + disease_description_nl = db.Column(db.String(255)) + patientfolder_alissa = db.Column(db.String(255)) + clinical_geneticist = db.Column(db.String(255)) + staff_member = db.Column(db.String(255)) + comments = db.Column(db.Text()) versions = db.relationship('PanelVersion', back_populates='panel', order_by="PanelVersion.id") @@ -172,6 +178,8 @@ class PanelVersion(db.Model): active = db.Column(db.Boolean, index=True, default=False) validated = db.Column(db.Boolean, index=True, default=False) comments = db.Column(db.Text()) + coverage_requirement_15 = db.Column(db.Float, default=0.99) + user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=False, index=True) panel_name = db.Column(db.String(50), db.ForeignKey('panels.name'), nullable=False, index=True) @@ -249,6 +257,7 @@ class Sample(db.Model): import_command = db.Column(db.Text(), nullable=False) project_id = db.Column(db.Integer(), db.ForeignKey('sample_projects.id'), nullable=False, index=True) exon_measurement_file = db.Column(db.Text(), nullable=False) + type = db.Column(db.String(255), nullable=False) transcript_measurements = db.relationship('TranscriptMeasurement', cascade="all,delete", back_populates='sample') project = db.relationship('SampleProject', back_populates='samples') @@ -294,6 +303,7 @@ class SampleProject(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), unique=True, nullable=False, index=True) + type = db.Column(db.String(255), nullable=False) samples = db.relationship('Sample', back_populates='project') diff --git a/ExonCov/templates/macros/tables.html b/ExonCov/templates/macros/tables.html index 03f15bd..ca004ca 100644 --- a/ExonCov/templates/macros/tables.html +++ b/ExonCov/templates/macros/tables.html @@ -1,7 +1,7 @@ -{% macro render_measurment_td(measurement, type) %} -{% if measurement|float > 98 or type == 'measurement_mean_coverage' %} - {{measurement|float|round(2)}} -{% else %} +{% macro render_measurment_td(measurement, type, panel=None) %} +{% if panel and type == 'measurement_percentage15' and measurement|float < panel.coverage_requirement_15 %} {{measurement|float|round(2)}} +{% else %} + {{measurement|float|round(2)}} {% endif %} -{% endmacro %} +{% endmacro %} \ No newline at end of file diff --git a/ExonCov/templates/panel.html b/ExonCov/templates/panel.html index 77aa3cc..cde2961 100644 --- a/ExonCov/templates/panel.html +++ b/ExonCov/templates/panel.html @@ -10,6 +10,15 @@ {% endblock %} {% block body %} +
+
Disease description
{{ panel.disease_description_eng }}
+
Ziekteomschrijving
{{ panel.disease_description_nl }}
+
Comments
{{ panel.comments }}
+
Alissa
{{ panel.patientfolder_alissa }}
+
Clinical geneticist
{{ panel.clinical_geneticist }}
+
Staff member
{{ panel.staff_member }}
+
+ {{ render_sample_datatable_form(filter=false) }} diff --git a/ExonCov/templates/panel_version.html b/ExonCov/templates/panel_version.html index 0ebe866..7ac1b88 100644 --- a/ExonCov/templates/panel_version.html +++ b/ExonCov/templates/panel_version.html @@ -14,6 +14,7 @@
Active
{{ render_bool_glyph(panel.active) }}
Validated
{{ render_bool_glyph(panel.validated) }}
+
Minimal % 15x
{{ panel.coverage_requirement_15 }}
{% if current_user.has_role('panel_admin') %}
Comments
{{ panel.comments }}
{% endif %}
diff --git a/ExonCov/templates/sample.html b/ExonCov/templates/sample.html index 3efa2da..45146e6 100644 --- a/ExonCov/templates/sample.html +++ b/ExonCov/templates/sample.html @@ -9,6 +9,7 @@
Sample
{{ sample.name }}
Project
{{ sample.project.name }}
+
Type
{{ sample.type }}
Sequencing runs
    {% for run in sample.sequencing_runs %}
  • {{ run }}
  • {% endfor %}
@@ -61,7 +62,7 @@ {% for type in measurement_types %} - {{ render_measurment_td(panels[panel][type], type) }} + {{ render_measurment_td(panels[panel][type], type, panels[panel]) }} {% endfor %} {% endfor %} diff --git a/ExonCov/templates/sample_gene.html b/ExonCov/templates/sample_gene.html index 286d377..b7493ed 100644 --- a/ExonCov/templates/sample_gene.html +++ b/ExonCov/templates/sample_gene.html @@ -9,6 +9,7 @@
Sample
{{ sample.name }}
Project
{{ sample.project.name }}
+
Type
{{ sample.type }}
Sequencing runs
    {% for run in sample.sequencing_runs %}
  • {{ run }}
  • {% endfor %}
Gene
{{ gene.id }}
Preferred transcript
{{ gene.default_transcript }}
diff --git a/ExonCov/templates/sample_inactive_panels.html b/ExonCov/templates/sample_inactive_panels.html index 5d0e273..460cf93 100644 --- a/ExonCov/templates/sample_inactive_panels.html +++ b/ExonCov/templates/sample_inactive_panels.html @@ -9,6 +9,7 @@
Sample
{{ sample.name }}
Project
{{ sample.project.name }}
+
Type
{{ sample.type }}
Sequencing runs
    {% for run in sample.sequencing_runs %}
  • {{ run }}
  • {% endfor %}
@@ -35,7 +36,7 @@
{% for type in measurement_types %} - {{ render_measurment_td(panels[panel][type], type) }} + {{ render_measurment_td(panels[panel][type], type, panels[panel]) }} {% endfor %} {% endfor %} diff --git a/ExonCov/templates/sample_panel.html b/ExonCov/templates/sample_panel.html index 2885fff..336f42d 100644 --- a/ExonCov/templates/sample_panel.html +++ b/ExonCov/templates/sample_panel.html @@ -9,8 +9,10 @@
Sample
{{ sample.name }}
Project
{{ sample.project.name }}
+
Type
{{ sample.type }}
Sequencing runs
    {% for run in sample.sequencing_runs %}
  • {{ run }}
  • {% endfor %}
Panel
{{ panel.name_version }}
+
Minimal % 15x
{{ panel.coverage_requirement_15 }}
@@ -42,7 +44,7 @@ {% for type in measurement_types %} - {{ render_measurment_td(measurement[type], type) }} + {{ render_measurment_td(measurement[type], type, panel) }} {% endfor %} {% endfor %} diff --git a/ExonCov/templates/sample_transcript.html b/ExonCov/templates/sample_transcript.html index 951e9ba..3df4db1 100644 --- a/ExonCov/templates/sample_transcript.html +++ b/ExonCov/templates/sample_transcript.html @@ -9,6 +9,7 @@
Sample
{{ sample.name }}
Project
{{ sample.project.name }}
+
Type
{{ sample.type }}
Sequencing runs
    {% for run in sample.sequencing_runs %}
  • {{ run }}
  • {% endfor %}
Gene
{{ transcript.gene_id }}
Transcript
{{ transcript.name }}
diff --git a/ExonCov/templates/samples.html b/ExonCov/templates/samples.html index b08a9e3..d2e1e56 100644 --- a/ExonCov/templates/samples.html +++ b/ExonCov/templates/samples.html @@ -24,6 +24,7 @@
+ @@ -33,6 +34,7 @@ + {% endfor %} diff --git a/ExonCov/views.py b/ExonCov/views.py index 18785d0..7f850db 100644 --- a/ExonCov/views.py +++ b/ExonCov/views.py @@ -67,6 +67,7 @@ def sample(id): panels[panel.id] = { 'len': transcript_measurement.len, 'name_version': panel.name_version, + 'coverage_requirement_15': panel.coverage_requirement_15 } for measurement_type in measurement_types: panels[panel.id][measurement_type] = transcript_measurement[measurement_type] @@ -99,6 +100,7 @@ def sample_inactive_panels(id): panels[panel.id] = { 'len': transcript_measurement.len, 'name_version': panel.name_version, + 'coverage_requirement_15': panel.coverage_requirement_15 } for measurement_type in measurement_types: panels[panel.id][measurement_type] = transcript_measurement[measurement_type] diff --git a/migrations/versions/dda6242a627d_metadata.py b/migrations/versions/dda6242a627d_metadata.py new file mode 100644 index 0000000..86df025 --- /dev/null +++ b/migrations/versions/dda6242a627d_metadata.py @@ -0,0 +1,40 @@ +"""metadata + +Revision ID: dda6242a627d +Revises: debc98d30e52 +Create Date: 2020-08-28 16:50:12.096178 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'dda6242a627d' +down_revision = 'debc98d30e52' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('panel_versions', sa.Column('coverage_requirement_15', sa.Float(), nullable=True)) + op.add_column('panels', sa.Column('clinical_geneticist', sa.String(length=255), nullable=True)) + op.add_column('panels', sa.Column('comments', sa.Text(), nullable=True)) + op.add_column('panels', sa.Column('disease_description_eng', sa.String(length=255), nullable=True)) + op.add_column('panels', sa.Column('disease_description_nl', sa.String(length=255), nullable=True)) + op.add_column('panels', sa.Column('patientfolder_alissa', sa.String(length=255), nullable=True)) + op.add_column('panels', sa.Column('staff_member', sa.String(length=255), nullable=True)) + op.add_column('sample_projects', sa.Column('type', sa.String(length=255), nullable=False)) + op.add_column('samples', sa.Column('type', sa.String(length=255), nullable=False)) + + +def downgrade(): + op.drop_column('samples', 'type') + op.drop_column('sample_projects', 'type') + op.drop_column('panels', 'staff_member') + op.drop_column('panels', 'patientfolder_alissa') + op.drop_column('panels', 'disease_description_nl') + op.drop_column('panels', 'disease_description_eng') + op.drop_column('panels', 'comments') + op.drop_column('panels', 'clinical_geneticist') + op.drop_column('panel_versions', 'coverage_requirement_15') From 80f41512ed2c21067cad9946dafec987279d4d3d Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Thu, 10 Sep 2020 14:42:01 +0200 Subject: [PATCH 03/40] Add sample_type to run(). --- ExonCov/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ExonCov/cli.py b/ExonCov/cli.py index 7bbe728..d005419 100644 --- a/ExonCov/cli.py +++ b/ExonCov/cli.py @@ -98,7 +98,7 @@ class ImportBam(Command): Option('--temp', dest='temp_path', default=None), ) - def run(self, bam, project_name, exon_bed_file, threads, overwrite, print_output, temp_path): + def run(self, project_name, sample_type, bam, exon_bed_file, threads, overwrite, print_output, temp_path): try: bam_file = pysam.AlignmentFile(bam, "rb") except IOError as e: From dabe3135c5f123561bb3e3cc0a48f08ed2c9e82c Mon Sep 17 00:00:00 2001 From: rernst Date: Fri, 2 Oct 2020 14:56:36 +0200 Subject: [PATCH 04/40] Allow partial sample name to search samples --- ExonCov/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ExonCov/cli.py b/ExonCov/cli.py index 736c61f..2560f7f 100644 --- a/ExonCov/cli.py +++ b/ExonCov/cli.py @@ -312,7 +312,7 @@ class SearchSample(Command): ) def run(self, sample_name): - samples = Sample.query.filter_by(name=sample_name).all() + samples = Sample.query.filter(Sample.name.like('%{0}%'.format(sample_name))).all() print("Sample ID\tSample Name\tProject\tSequencing Runs\tCustom Panels") for sample in samples: From 88c322711d4ba08455c9d02ade8dfb9e7e38daac Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Wed, 6 Jan 2021 17:00:56 +0100 Subject: [PATCH 05/40] Fix sample upload. --- ExonCov/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ExonCov/cli.py b/ExonCov/cli.py index d005419..1890a6d 100644 --- a/ExonCov/cli.py +++ b/ExonCov/cli.py @@ -134,7 +134,8 @@ def run(self, project_name, sample_type, bam, exon_bed_file, threads, overwrite, sample_project, sample_project_exists = utils.get_one_or_create( db.session, SampleProject, - name=project_name + name=project_name, + type='' ) # returns object and exists bool # Look for sample in database From 0461477c9b55b5fbf4c77f5f6c26160994b8475c Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Wed, 6 Jan 2021 17:02:19 +0100 Subject: [PATCH 06/40] Add Minimal % 15x edit to panel forms --- ExonCov/forms.py | 4 +++- ExonCov/templates/panel_update.html | 1 + ExonCov/templates/panel_update_confirm.html | 1 + ExonCov/templates/panel_version_edit.html | 2 ++ ExonCov/views.py | 11 +++++++++-- 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ExonCov/forms.py b/ExonCov/forms.py index e6a6a91..1ff8a4e 100644 --- a/ExonCov/forms.py +++ b/ExonCov/forms.py @@ -4,7 +4,7 @@ from flask_wtf import FlaskForm from flask_security.forms import RegisterForm from wtforms.ext.sqlalchemy.fields import QuerySelectMultipleField, QuerySelectField -from wtforms.fields import SelectField, TextAreaField, StringField, BooleanField +from wtforms.fields import SelectField, TextAreaField, StringField, BooleanField, FloatField from wtforms import validators from .models import Sample, SampleSet, Gene, GeneAlias, PanelVersion, Panel @@ -181,6 +181,7 @@ class UpdatePanelForm(FlaskForm): """Update Panel form.""" gene_list = TextAreaField('Gene list', description="List of genes seperated by newline, space, ',' or ';'.", validators=[validators.InputRequired()]) + coverage_requirement_15 = FloatField('Minimal % 15x') comments = TextAreaField('Comments', description="Provide a short description.", validators=[validators.InputRequired()]) confirm = BooleanField('Confirm') transcript = [] # Filled in validate function @@ -212,6 +213,7 @@ class PanelVersionEditForm(FlaskForm): comments = TextAreaField('Comments', description="Provide a short description.", validators=[validators.InputRequired()]) active = BooleanField('Active') validated = BooleanField('Validated') + coverage_requirement_15 = FloatField('Minimal % 15x') class CustomPanelValidateForm(FlaskForm): diff --git a/ExonCov/templates/panel_update.html b/ExonCov/templates/panel_update.html index 1e2869f..aeafca7 100644 --- a/ExonCov/templates/panel_update.html +++ b/ExonCov/templates/panel_update.html @@ -8,6 +8,7 @@
{{ form.csrf_token }} {{ render_field(form.gene_list, rows="12") }} + {{ render_field(form.coverage_requirement_15) }} {{ render_field(form.comments, rows="2") }}
diff --git a/ExonCov/templates/panel_update_confirm.html b/ExonCov/templates/panel_update_confirm.html index 1bd004c..2961b33 100644 --- a/ExonCov/templates/panel_update_confirm.html +++ b/ExonCov/templates/panel_update_confirm.html @@ -41,6 +41,7 @@

Confirm panel update

{{ form.csrf_token }} {{ form.gene_list(rows="12", hidden=True) }} + {{ form.coverage_requirement_15(hidden=True) }} {{ form.comments(rows="2", hidden=True) }} {{ render_inline_checkbox(form.confirm) }} diff --git a/ExonCov/templates/panel_version_edit.html b/ExonCov/templates/panel_version_edit.html index 0b2fa71..8b0eb44 100644 --- a/ExonCov/templates/panel_version_edit.html +++ b/ExonCov/templates/panel_version_edit.html @@ -11,7 +11,9 @@ {{ form.csrf_token }} {{ render_checkbox(form.active) }} {{ render_checkbox(form.validated) }} + {{ render_field(form.coverage_requirement_15) }} {{ render_field(form.comments, rows="2") }} +
diff --git a/ExonCov/views.py b/ExonCov/views.py index 7f850db..55d3ab1 100644 --- a/ExonCov/views.py +++ b/ExonCov/views.py @@ -209,7 +209,7 @@ def panel_update(name): panel_last_version = panel.last_version genes = '\n'.join([transcript.gene_id for transcript in panel_last_version.transcripts]) - update_panel_form = UpdatePanelForm(gene_list=genes) + update_panel_form = UpdatePanelForm(gene_list=genes, coverage_requirement_15=panel_last_version.coverage_requirement_15) if update_panel_form.validate_on_submit(): transcripts = update_panel_form.transcripts @@ -233,6 +233,7 @@ def panel_update(name): version_year=year, version_revision=revision, transcripts=transcripts, + coverage_requirement_15=update_panel_form.coverage_requirement_15.data, comments=update_panel_form.data['comments'], user=current_user ) @@ -285,12 +286,18 @@ def panel_version(id): def panel_version_edit(id): """Set validation status to true.""" panel = PanelVersion.query.get_or_404(id) - form = PanelVersionEditForm(active=panel.active, validated=panel.validated, comments=panel.comments) + form = PanelVersionEditForm( + active=panel.active, + validated=panel.validated, + comments=panel.comments, + coverage_requirement_15=panel.coverage_requirement_15 + ) if form.validate_on_submit(): panel.active = form.active.data panel.validated = form.validated.data panel.comments = form.comments.data + panel.coverage_requirement_15 = form.coverage_requirement_15.data db.session.add(panel) db.session.commit() return redirect(url_for('panel_version', id=panel.id)) From dfd50efc21d199d9ac0a836813a2c073ce9854b9 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Wed, 6 Jan 2021 18:54:18 +0100 Subject: [PATCH 07/40] Add panel edit page (metadata) and update panel verion update page. --- ExonCov/forms.py | 17 ++++- ExonCov/models.py | 2 +- ExonCov/templates/panel.html | 7 +- ExonCov/templates/panel_edit.html | 25 ++++++++ ExonCov/templates/panel_new.html | 5 ++ ...nel_update.html => panel_new_version.html} | 2 +- ...rm.html => panel_new_version_confirm.html} | 2 +- ExonCov/views.py | 64 ++++++++++++++++--- 8 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 ExonCov/templates/panel_edit.html rename ExonCov/templates/{panel_update.html => panel_new_version.html} (84%) rename ExonCov/templates/{panel_update_confirm.html => panel_new_version_confirm.html} (92%) diff --git a/ExonCov/forms.py b/ExonCov/forms.py index 1ff8a4e..a46c03b 100644 --- a/ExonCov/forms.py +++ b/ExonCov/forms.py @@ -148,6 +148,11 @@ class CreatePanelForm(FlaskForm): name = StringField('Name', validators=[validators.InputRequired()]) gene_list = TextAreaField('Gene list', description="List of genes seperated by newline, space, ',' or ';'.", validators=[validators.InputRequired()]) + disease_description_eng = StringField('Disease description', validators=[validators.InputRequired()]) + disease_description_nl = StringField('Ziekteomschrijving', validators=[validators.InputRequired()]) + patientfolder_alissa = StringField('Alissa', validators=[validators.InputRequired()]) + clinical_geneticist = StringField('Clinical geneticist', validators=[validators.InputRequired()]) + staff_member = StringField('Staff member', validators=[validators.InputRequired()]) comments = TextAreaField('Comments', description="Provide a short description.", validators=[validators.InputRequired()]) transcript = [] # Filled in validate function @@ -177,7 +182,7 @@ def validate(self): return True -class UpdatePanelForm(FlaskForm): +class PanelNewVersionForm(FlaskForm): """Update Panel form.""" gene_list = TextAreaField('Gene list', description="List of genes seperated by newline, space, ',' or ';'.", validators=[validators.InputRequired()]) @@ -207,6 +212,16 @@ def validate(self): return True +class PanelEditForm(FlaskForm): + """Panel edit form.""" + disease_description_eng = StringField('Disease description', validators=[validators.InputRequired()]) + disease_description_nl = StringField('Ziekteomschrijving', validators=[validators.InputRequired()]) + patientfolder_alissa = StringField('Alissa', validators=[validators.InputRequired()]) + clinical_geneticist = StringField('Clinical geneticist', validators=[validators.InputRequired()]) + staff_member = StringField('Staff member', validators=[validators.InputRequired()]) + comments = TextAreaField('Comments', description="Provide a short description.", validators=[validators.InputRequired()]) + + class PanelVersionEditForm(FlaskForm): """PanelVersion edit form.""" diff --git a/ExonCov/models.py b/ExonCov/models.py index d4ddda5..918b3da 100644 --- a/ExonCov/models.py +++ b/ExonCov/models.py @@ -178,7 +178,7 @@ class PanelVersion(db.Model): active = db.Column(db.Boolean, index=True, default=False) validated = db.Column(db.Boolean, index=True, default=False) comments = db.Column(db.Text()) - coverage_requirement_15 = db.Column(db.Float, default=0.99) + coverage_requirement_15 = db.Column(db.Float, default=99) user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=False, index=True) panel_name = db.Column(db.String(50), db.ForeignKey('panels.name'), nullable=False, index=True) diff --git a/ExonCov/templates/panel.html b/ExonCov/templates/panel.html index cde2961..f771597 100644 --- a/ExonCov/templates/panel.html +++ b/ExonCov/templates/panel.html @@ -5,7 +5,8 @@ {% block header %} Gene panel - {{ panel.name }} {% if current_user.has_role('panel_admin') %} - + + {% endif %} {% endblock %} @@ -13,10 +14,10 @@
Disease description
{{ panel.disease_description_eng }}
Ziekteomschrijving
{{ panel.disease_description_nl }}
-
Comments
{{ panel.comments }}
Alissa
{{ panel.patientfolder_alissa }}
Clinical geneticist
{{ panel.clinical_geneticist }}
Staff member
{{ panel.staff_member }}
+
Comments
{{ panel.comments }}
{{ render_sample_datatable_form(filter=false) }} @@ -28,6 +29,7 @@
+ @@ -37,6 +39,7 @@ + {% endfor %} diff --git a/ExonCov/templates/panel_edit.html b/ExonCov/templates/panel_edit.html new file mode 100644 index 0000000..9a27eb2 --- /dev/null +++ b/ExonCov/templates/panel_edit.html @@ -0,0 +1,25 @@ +{% from "macros/forms.html" import render_field, render_checkbox %} +{% extends 'base.html' %} + +{% block header %} +Gene panel - {{ panel.name }} +{% endblock %} + +{% block body %} +
+ + {{ form.csrf_token }} + {{ render_field(form.disease_description_eng) }} + {{ render_field(form.disease_description_nl) }} + {{ render_field(form.patientfolder_alissa) }} + {{ render_field(form.clinical_geneticist,) }} + {{ render_field(form.staff_member) }} + {{ render_field(form.comments, rows="2") }} +
+
+ +
+
+ +
+{% endblock %} diff --git a/ExonCov/templates/panel_new.html b/ExonCov/templates/panel_new.html index fd48d32..bc20244 100644 --- a/ExonCov/templates/panel_new.html +++ b/ExonCov/templates/panel_new.html @@ -8,6 +8,11 @@ {{ form.csrf_token }} {{ render_select2_field(form.name) }} + {{ render_field(form.disease_description_eng) }} + {{ render_field(form.disease_description_nl) }} + {{ render_field(form.patientfolder_alissa) }} + {{ render_field(form.clinical_geneticist,) }} + {{ render_field(form.staff_member) }} {{ render_field(form.gene_list, rows="12") }} {{ render_field(form.comments, rows="2") }} diff --git a/ExonCov/templates/panel_update.html b/ExonCov/templates/panel_new_version.html similarity index 84% rename from ExonCov/templates/panel_update.html rename to ExonCov/templates/panel_new_version.html index aeafca7..54585b5 100644 --- a/ExonCov/templates/panel_update.html +++ b/ExonCov/templates/panel_new_version.html @@ -5,7 +5,7 @@ {% block body %}
- + {{ form.csrf_token }} {{ render_field(form.gene_list, rows="12") }} {{ render_field(form.coverage_requirement_15) }} diff --git a/ExonCov/templates/panel_update_confirm.html b/ExonCov/templates/panel_new_version_confirm.html similarity index 92% rename from ExonCov/templates/panel_update_confirm.html rename to ExonCov/templates/panel_new_version_confirm.html index 2961b33..5b95786 100644 --- a/ExonCov/templates/panel_update_confirm.html +++ b/ExonCov/templates/panel_new_version_confirm.html @@ -38,7 +38,7 @@

Removed genes

Confirm panel update

- + {{ form.csrf_token }} {{ form.gene_list(rows="12", hidden=True) }} {{ form.coverage_requirement_15(hidden=True) }} diff --git a/ExonCov/views.py b/ExonCov/views.py index 55d3ab1..09c13f0 100644 --- a/ExonCov/views.py +++ b/ExonCov/views.py @@ -11,8 +11,14 @@ import pysam from ExonCov import app, db -from .models import Sample, SampleProject, SampleSet, SequencingRun, PanelVersion, Panel, CustomPanel, Gene, Transcript, TranscriptMeasurement, panels_transcripts -from .forms import MeasurementTypeForm, CustomPanelForm, CustomPanelNewForm, CustomPanelValidateForm, SampleForm, CreatePanelForm, UpdatePanelForm, PanelVersionEditForm +from .models import ( + Sample, SampleProject, SampleSet, SequencingRun, PanelVersion, Panel, CustomPanel, Gene, Transcript, + TranscriptMeasurement, panels_transcripts +) +from .forms import ( + MeasurementTypeForm, CustomPanelForm, CustomPanelNewForm, CustomPanelValidateForm, SampleForm, + CreatePanelForm, PanelNewVersionForm, PanelEditForm, PanelVersionEditForm +) from .utils import weighted_average @@ -200,16 +206,16 @@ def panel(name): return render_template('panel.html', panel=panel) -@app.route('/panel//update', methods=['GET', 'POST']) +@app.route('/panel//new_version', methods=['GET', 'POST']) @login_required @roles_required('panel_admin') -def panel_update(name): - """Update panel page.""" +def panel_new_version(name): + """Create new panel version page.""" panel = Panel.query.filter_by(name=name).options(joinedload('versions').joinedload('transcripts')).first_or_404() panel_last_version = panel.last_version genes = '\n'.join([transcript.gene_id for transcript in panel_last_version.transcripts]) - update_panel_form = UpdatePanelForm(gene_list=genes, coverage_requirement_15=panel_last_version.coverage_requirement_15) + update_panel_form = PanelNewVersionForm(gene_list=genes, coverage_requirement_15=panel_last_version.coverage_requirement_15) if update_panel_form.validate_on_submit(): transcripts = update_panel_form.transcripts @@ -241,9 +247,38 @@ def panel_update(name): db.session.commit() return redirect(url_for('panel', name=panel.name)) else: - return render_template('panel_update_confirm.html', form=update_panel_form, panel=panel_last_version, year=year, revision=revision) + return render_template('panel_new_version_confirm.html', form=update_panel_form, panel=panel_last_version, year=year, revision=revision) - return render_template('panel_update.html', form=update_panel_form, panel=panel_last_version) + return render_template('panel_new_version.html', form=update_panel_form, panel=panel_last_version) + + +@app.route('/panel//edit', methods=['GET', 'POST']) +@roles_required('panel_admin') +def panel_edit(name): + """Set validation status to true.""" + panel = Panel.query.filter_by(name=name).first_or_404() + panel_edit_form = PanelEditForm( + comments=panel.comments, + disease_description_eng=panel.disease_description_eng, + disease_description_nl=panel.disease_description_nl, + patientfolder_alissa=panel.patientfolder_alissa, + clinical_geneticist=panel.clinical_geneticist, + staff_member=panel.staff_member, + ) + + if panel_edit_form.validate_on_submit(): + panel.comments = panel_edit_form.comments.data + panel.disease_description_eng = panel_edit_form.disease_description_eng.data + panel.disease_description_nl = panel_edit_form.disease_description_nl.data + panel.patientfolder_alissa = panel_edit_form.patientfolder_alissa.data + panel.clinical_geneticist = panel_edit_form.clinical_geneticist.data + panel.staff_member = panel_edit_form.staff_member.data + + db.session.add(panel) + db.session.commit() + return redirect(url_for('panel', name=panel.name)) + + return render_template('panel_edit.html', form=panel_edit_form, panel=panel) @app.route('/panel/new', methods=['GET', 'POST']) @@ -257,12 +292,21 @@ def panel_new(): panel_name = new_panel_form.data['name'] transcripts = new_panel_form.transcripts - new_panel = Panel(name=panel_name) + new_panel = Panel( + name=panel_name, + comments=new_panel_form.comments.data, + disease_description_eng=new_panel_form.disease_description_eng.data, + disease_description_nl=new_panel_form.disease_description_nl.data, + patientfolder_alissa=new_panel_form.patientfolder_alissa.data, + clinical_geneticist=new_panel_form.clinical_geneticist.data, + staff_member=new_panel_form.staff_member.data + ) + new_panel_version = PanelVersion( panel_name=panel_name, version_year=time.strftime('%y'), version_revision=1, transcripts=transcripts, - comments=new_panel_form.data['comments'], + comments=new_panel_form.comments.data, user=current_user ) From 68d871c935cb3845a14cc09dd1dc7a64f6edef71 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Thu, 7 Jan 2021 11:49:21 +0100 Subject: [PATCH 08/40] Show cov req when creating new panel. --- ExonCov/forms.py | 3 ++- ExonCov/models.py | 2 +- ExonCov/templates/panel_new.html | 1 + ExonCov/views.py | 4 +++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ExonCov/forms.py b/ExonCov/forms.py index a46c03b..4880f82 100644 --- a/ExonCov/forms.py +++ b/ExonCov/forms.py @@ -148,6 +148,7 @@ class CreatePanelForm(FlaskForm): name = StringField('Name', validators=[validators.InputRequired()]) gene_list = TextAreaField('Gene list', description="List of genes seperated by newline, space, ',' or ';'.", validators=[validators.InputRequired()]) + coverage_requirement_15 = FloatField('Minimal % 15x', default=99, validators=[validators.InputRequired()]) disease_description_eng = StringField('Disease description', validators=[validators.InputRequired()]) disease_description_nl = StringField('Ziekteomschrijving', validators=[validators.InputRequired()]) patientfolder_alissa = StringField('Alissa', validators=[validators.InputRequired()]) @@ -183,7 +184,7 @@ def validate(self): class PanelNewVersionForm(FlaskForm): - """Update Panel form.""" + """New panel version form.""" gene_list = TextAreaField('Gene list', description="List of genes seperated by newline, space, ',' or ';'.", validators=[validators.InputRequired()]) coverage_requirement_15 = FloatField('Minimal % 15x') diff --git a/ExonCov/models.py b/ExonCov/models.py index 918b3da..901366d 100644 --- a/ExonCov/models.py +++ b/ExonCov/models.py @@ -178,7 +178,7 @@ class PanelVersion(db.Model): active = db.Column(db.Boolean, index=True, default=False) validated = db.Column(db.Boolean, index=True, default=False) comments = db.Column(db.Text()) - coverage_requirement_15 = db.Column(db.Float, default=99) + coverage_requirement_15 = db.Column(db.Float) user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=False, index=True) panel_name = db.Column(db.String(50), db.ForeignKey('panels.name'), nullable=False, index=True) diff --git a/ExonCov/templates/panel_new.html b/ExonCov/templates/panel_new.html index bc20244..c903970 100644 --- a/ExonCov/templates/panel_new.html +++ b/ExonCov/templates/panel_new.html @@ -14,6 +14,7 @@ {{ render_field(form.clinical_geneticist,) }} {{ render_field(form.staff_member) }} {{ render_field(form.gene_list, rows="12") }} + {{ render_field(form.coverage_requirement_15) }} {{ render_field(form.comments, rows="2") }}
diff --git a/ExonCov/views.py b/ExonCov/views.py index 09c13f0..a8a2301 100644 --- a/ExonCov/views.py +++ b/ExonCov/views.py @@ -305,7 +305,9 @@ def panel_new(): new_panel_version = PanelVersion( panel_name=panel_name, version_year=time.strftime('%y'), - version_revision=1, transcripts=transcripts, + version_revision=1, + transcripts=transcripts, + coverage_requirement_15=new_panel_form.coverage_requirement_15.data, comments=new_panel_form.comments.data, user=current_user ) From 1fb0ddc460e5a63180512200be705e8fae0d4d5a Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Tue, 9 Feb 2021 12:11:10 +0100 Subject: [PATCH 09/40] Add event logger for panel + panel_version models --- ExonCov/__init__.py | 17 ++++- ExonCov/admin_views.py | 62 +++++++++++++------ ExonCov/models.py | 22 ++++++- ExonCov/utils.py | 17 +++++ .../f6911f9c19ef_add_event_logs_table.py | 34 ++++++++++ 5 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 migrations/versions/f6911f9c19ef_add_event_logs_table.py diff --git a/ExonCov/__init__.py b/ExonCov/__init__.py index 472c994..32b7c21 100644 --- a/ExonCov/__init__.py +++ b/ExonCov/__init__.py @@ -6,7 +6,8 @@ from flask_security import Security, SQLAlchemyUserDatastore from flask_debugtoolbar import DebugToolbarExtension -from utils import url_for_other_page + +from utils import url_for_other_page, event_logger # Setup APP app = Flask(__name__) @@ -37,3 +38,17 @@ def security_context_processor(): h=flask_admin.helpers, get_url=url_for ) + + +@db.event.listens_for(models.Panel, "after_insert") +@db.event.listens_for(models.PanelVersion, "after_insert") +def after_update(mapper, connection, target): + event_data = dict(target.__dict__) + event_logger(connection, models.EventLog, target.__class__.__name__, 'insert', event_data) + + +@db.event.listens_for(models.Panel, "after_update") +@db.event.listens_for(models.PanelVersion, "after_update") +def after_insert(mapper, connection, target): + event_data = dict(target.__dict__) + event_logger(connection, models.EventLog, target.__class__.__name__, 'update', event_data) diff --git a/ExonCov/admin_views.py b/ExonCov/admin_views.py index abc15be..7601c0a 100644 --- a/ExonCov/admin_views.py +++ b/ExonCov/admin_views.py @@ -4,7 +4,7 @@ from flask_security import current_user from . import db, admin -from .models import Exon, Transcript, Gene, Panel, PanelVersion, CustomPanel, Sample, SampleProject, SampleSet, SequencingRun, User, Role +import models class CustomModelView(ModelView): @@ -36,14 +36,20 @@ class PanelAdminView(CustomModelView): column_list = ['name', 'versions', 'disease_description_eng', 'clinical_geneticist', 'staff_member', 'comments'] column_searchable_list = ['name'] - form_columns = ['name', 'disease_description_nl', 'disease_description_eng', 'comments' ,'patientfolder_alissa', 'clinical_geneticist', 'staff_member'] + form_columns = [ + 'name', 'disease_description_nl', 'disease_description_eng', 'comments', 'patientfolder_alissa', + 'clinical_geneticist', 'staff_member' + ] class PanelVersionAdminView(CustomModelView): """Panel version admin view.""" column_searchable_list = ['panel_name'] - form_columns = ['panel', 'version_year', 'version_revision', 'comments', 'coverage_requirement_15', 'active', 'validated', 'transcripts'] + form_columns = [ + 'panel', 'version_year', 'version_revision', 'comments', 'coverage_requirement_15', 'active', 'validated', + 'transcripts' + ] form_ajax_refs = { 'transcripts': { 'fields': ['name', 'gene_id'], @@ -57,7 +63,10 @@ class CustomPanelAdminView(CustomModelView): column_list = ['id', 'created_by', 'date', 'research_number', 'comments', 'validated', 'validated_by', 'validated_date'] column_searchable_list = ['id', 'comments', 'research_number'] - form_columns = ['created_by', 'date', 'research_number', 'comments', 'validated', 'validated_by', 'validated_date', 'samples', 'transcripts'] + form_columns = [ + 'created_by', 'date', 'research_number', 'comments', 'validated', 'validated_by', 'validated_date', + 'samples', 'transcripts' + ] form_ajax_refs = { 'transcripts': { @@ -125,7 +134,10 @@ class SampleAdminView(CustomModelView): column_sortable_list = ['name', 'import_date'] column_searchable_list = ['name'] - form_columns = ['name', 'type', 'project', 'sequencing_runs', 'import_date', 'file_name', 'import_command', 'exon_measurement_file'] + form_columns = [ + 'name', 'type', 'project', 'sequencing_runs', 'import_date', 'file_name', 'import_command', + 'exon_measurement_file' + ] form_ajax_refs = { 'project': { 'fields': ['name'], @@ -167,29 +179,41 @@ class SequencingRunAdminView(CustomModelView): form_columns = ['name', 'platform_unit'] -class UserAdmin(CustomModelView): +class UserAdminView(CustomModelView): """User admin model.""" can_create = False column_list = ('first_name', 'last_name', 'email', 'roles', 'active') + column_searchable_list = ['first_name', 'last_name', 'email'] form_columns = ('first_name', 'last_name', 'email', 'roles', 'active', 'password') -# Link view classes and models -admin.add_view(PanelAdminView(Panel, db.session)) -admin.add_view(PanelVersionAdminView(PanelVersion, db.session)) -admin.add_view(CustomPanelAdminView(CustomPanel, db.session)) +class EventLogAdminView(CustomModelView): + """EventLog admin model.""" + column_filters = ['table', 'action', 'modified_on'] + column_searchable_list = ['data'] -admin.add_view(SampleAdminView(Sample, db.session)) -admin.add_view(SampleProjectAdminView(SampleProject, db.session)) -admin.add_view(SampleSetAdminView(SampleSet, db.session)) -admin.add_view(SequencingRunAdminView(SequencingRun, db.session)) + can_create = False + can_edit = False + can_view_details = True -admin.add_view(GeneAdminView(Gene, db.session)) -admin.add_view(TranscriptAdminView(Transcript, db.session)) -admin.add_view(ExonAdminView(Exon, db.session)) -admin.add_view(UserAdmin(User, db.session)) -admin.add_view(CustomModelView(Role, db.session)) +# Link view classes and models +admin.add_view(PanelAdminView(models.Panel, db.session)) +admin.add_view(PanelVersionAdminView(models.PanelVersion, db.session)) +admin.add_view(CustomPanelAdminView(models.CustomPanel, db.session)) + +admin.add_view(SampleAdminView(models.Sample, db.session)) +admin.add_view(SampleProjectAdminView(models.SampleProject, db.session)) +admin.add_view(SampleSetAdminView(models.SampleSet, db.session)) +admin.add_view(SequencingRunAdminView(models.SequencingRun, db.session)) + +admin.add_view(GeneAdminView(models.Gene, db.session)) +admin.add_view(TranscriptAdminView(models.Transcript, db.session)) +admin.add_view(ExonAdminView(models.Exon, db.session)) + +admin.add_view(UserAdminView(models.User, db.session)) +admin.add_view(CustomModelView(models.Role, db.session)) +admin.add_view(EventLogAdminView(models.EventLog, db.session)) diff --git a/ExonCov/models.py b/ExonCov/models.py index 901366d..ec34073 100644 --- a/ExonCov/models.py +++ b/ExonCov/models.py @@ -113,7 +113,11 @@ class Gene(db.Model): __tablename__ = 'genes' id = db.Column(db.String(50, collation='utf8_bin'), primary_key=True) # hgnc - default_transcript_id = db.Column(db.Integer(), db.ForeignKey('transcripts.id', name='default_transcript_foreign_key'), index=True) + default_transcript_id = db.Column( + db.Integer(), + db.ForeignKey('transcripts.id', name='default_transcript_foreign_key'), + index=True + ) default_transcript = db.relationship('Transcript', foreign_keys=[default_transcript_id]) @@ -179,7 +183,7 @@ class PanelVersion(db.Model): validated = db.Column(db.Boolean, index=True, default=False) comments = db.Column(db.Text()) coverage_requirement_15 = db.Column(db.Float) - + user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=False, index=True) panel_name = db.Column(db.String(50), db.ForeignKey('panels.name'), nullable=False, index=True) @@ -370,6 +374,20 @@ def __getitem__(self, item): return getattr(self, item) +class EventLog(db.Model): + """Store database events""" + __tablename__ = 'event_logs' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer(), db.ForeignKey('user.id'), nullable=False, index=True) + table = db.Column(db.String(255), nullable=False) + action = db.Column(db.String(255), nullable=False) + modified_on = db.Column(db.DateTime, default=datetime.datetime.now) + data = db.Column(db.JSON(), nullable=False) + + user = db.relationship('User') + + class User(db.Model, UserMixin): """User model.""" diff --git a/ExonCov/utils.py b/ExonCov/utils.py index 33cfb52..4b074ad 100644 --- a/ExonCov/utils.py +++ b/ExonCov/utils.py @@ -1,5 +1,6 @@ """Utility functions.""" from flask import request, url_for +from flask_login import current_user from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.exc import IntegrityError @@ -45,3 +46,19 @@ def url_for_other_page(page): def weighted_average(values, weights): return sum(x * y for x, y in zip(values, weights)) / sum(weights) + + +def event_logger(connection, log_model, model_name, action, event_data): + # cleanup event data + for item in ['_sa_instance_state', 'user', 'transcripts']: + event_data.pop(item, None) + + connection.execute( + log_model.__table__.insert(), + { + 'user_id': current_user.id, + 'table': model_name, + 'action': action, + 'data': event_data + } + ) diff --git a/migrations/versions/f6911f9c19ef_add_event_logs_table.py b/migrations/versions/f6911f9c19ef_add_event_logs_table.py new file mode 100644 index 0000000..93dabae --- /dev/null +++ b/migrations/versions/f6911f9c19ef_add_event_logs_table.py @@ -0,0 +1,34 @@ +"""Add event_logs table + +Revision ID: f6911f9c19ef +Revises: dda6242a627d +Create Date: 2021-01-07 17:02:28.427704 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f6911f9c19ef' +down_revision = 'dda6242a627d' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + 'event_logs', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('table', sa.String(length=255), nullable=False), + sa.Column('action', sa.String(length=255), nullable=False), + sa.Column('modified_on', sa.DateTime(), nullable=True), + sa.Column('data', sa.JSON(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + + +def downgrade(): + op.drop_table('event_logs') From 54263536fc21428b850a00b43d5e1093f24044e4 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Tue, 9 Feb 2021 14:34:49 +0100 Subject: [PATCH 10/40] Fix eventlogger. --- ExonCov/utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ExonCov/utils.py b/ExonCov/utils.py index 4b074ad..c55cdab 100644 --- a/ExonCov/utils.py +++ b/ExonCov/utils.py @@ -49,9 +49,12 @@ def weighted_average(values, weights): def event_logger(connection, log_model, model_name, action, event_data): - # cleanup event data - for item in ['_sa_instance_state', 'user', 'transcripts']: - event_data.pop(item, None) + # Cleanup and transform item to str + for item in event_data.keys(): + if item in ['_sa_instance_state', 'user', 'transcripts', 'panel']: + event_data.pop(item, None) + else: + event_data[item] = str(event_data[item]) connection.execute( log_model.__table__.insert(), From 0d837f768d3b4cc57cc2e37a51c9dd428ec6fe2f Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Tue, 9 Feb 2021 14:35:27 +0100 Subject: [PATCH 11/40] Update sample_projects type --- ExonCov/admin_views.py | 6 +++--- ExonCov/models.py | 7 +++++-- ExonCov/templates/sample.html | 2 +- ExonCov/templates/sample_gene.html | 2 +- ExonCov/templates/sample_panel.html | 2 +- ExonCov/templates/sample_transcript.html | 2 +- ExonCov/templates/samples.html | 2 +- migrations/versions/dda6242a627d_metadata.py | 2 +- 8 files changed, 14 insertions(+), 11 deletions(-) diff --git a/ExonCov/admin_views.py b/ExonCov/admin_views.py index 7601c0a..cda48b6 100644 --- a/ExonCov/admin_views.py +++ b/ExonCov/admin_views.py @@ -152,10 +152,10 @@ class SampleAdminView(CustomModelView): class SampleProjectAdminView(CustomModelView): """SequencingRun admin view.""" - column_list = ['name'] - column_searchable_list = ['name'] + column_list = ['name', 'type'] + column_searchable_list = ['name', 'type'] - form_columns = ['name'] + form_columns = ['name', 'type'] class SampleSetAdminView(CustomModelView): diff --git a/ExonCov/models.py b/ExonCov/models.py index ec34073..715e6f2 100644 --- a/ExonCov/models.py +++ b/ExonCov/models.py @@ -307,7 +307,7 @@ class SampleProject(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), unique=True, nullable=False, index=True) - type = db.Column(db.String(255), nullable=False) + type = db.Column(db.String(255)) samples = db.relationship('Sample', back_populates='project') @@ -315,7 +315,10 @@ def __repr__(self): return "SampleProject({0})".format(str(self)) def __str__(self): - return self.name + if self.type: + return '{0} ({1})'.format(self.name, self.type) + else: + return self.name class SequencingRun(db.Model): diff --git a/ExonCov/templates/sample.html b/ExonCov/templates/sample.html index 45146e6..c397c8e 100644 --- a/ExonCov/templates/sample.html +++ b/ExonCov/templates/sample.html @@ -8,7 +8,7 @@
Sample
{{ sample.name }}
-
Project
{{ sample.project.name }}
+
Project
{{ sample.project }}
Type
{{ sample.type }}
Sequencing runs
    {% for run in sample.sequencing_runs %}
  • {{ run }}
  • {% endfor %}
diff --git a/ExonCov/templates/sample_gene.html b/ExonCov/templates/sample_gene.html index b7493ed..e7c73a1 100644 --- a/ExonCov/templates/sample_gene.html +++ b/ExonCov/templates/sample_gene.html @@ -8,7 +8,7 @@
Sample
{{ sample.name }}
-
Project
{{ sample.project.name }}
+
Project
{{ sample.project }}
Type
{{ sample.type }}
Sequencing runs
    {% for run in sample.sequencing_runs %}
  • {{ run }}
  • {% endfor %}
Gene
{{ gene.id }}
diff --git a/ExonCov/templates/sample_panel.html b/ExonCov/templates/sample_panel.html index 336f42d..818bdc5 100644 --- a/ExonCov/templates/sample_panel.html +++ b/ExonCov/templates/sample_panel.html @@ -8,7 +8,7 @@
Sample
{{ sample.name }}
-
Project
{{ sample.project.name }}
+
Project
{{ sample.project }}
Type
{{ sample.type }}
Sequencing runs
    {% for run in sample.sequencing_runs %}
  • {{ run }}
  • {% endfor %}
Panel
{{ panel.name_version }}
diff --git a/ExonCov/templates/sample_transcript.html b/ExonCov/templates/sample_transcript.html index 3df4db1..fcb28f8 100644 --- a/ExonCov/templates/sample_transcript.html +++ b/ExonCov/templates/sample_transcript.html @@ -8,7 +8,7 @@
Sample
{{ sample.name }}
-
Project
{{ sample.project.name }}
+
Project
{{ sample.project }}
Type
{{ sample.type }}
Sequencing runs
    {% for run in sample.sequencing_runs %}
  • {{ run }}
  • {% endfor %}
Gene
{{ transcript.gene_id }}
diff --git a/ExonCov/templates/samples.html b/ExonCov/templates/samples.html index d2e1e56..3bebc52 100644 --- a/ExonCov/templates/samples.html +++ b/ExonCov/templates/samples.html @@ -32,7 +32,7 @@ {% for sample in samples.items %}
- + diff --git a/migrations/versions/dda6242a627d_metadata.py b/migrations/versions/dda6242a627d_metadata.py index 86df025..07ff869 100644 --- a/migrations/versions/dda6242a627d_metadata.py +++ b/migrations/versions/dda6242a627d_metadata.py @@ -24,7 +24,7 @@ def upgrade(): op.add_column('panels', sa.Column('disease_description_nl', sa.String(length=255), nullable=True)) op.add_column('panels', sa.Column('patientfolder_alissa', sa.String(length=255), nullable=True)) op.add_column('panels', sa.Column('staff_member', sa.String(length=255), nullable=True)) - op.add_column('sample_projects', sa.Column('type', sa.String(length=255), nullable=False)) + op.add_column('sample_projects', sa.Column('type', sa.String(length=255), nullable=True)) op.add_column('samples', sa.Column('type', sa.String(length=255), nullable=False)) From 94859954c978235e09c5ad96b03f7d823d616cb4 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Tue, 9 Feb 2021 15:28:00 +0100 Subject: [PATCH 12/40] Update clinical contacts field --- ExonCov/admin_views.py | 4 ++-- ExonCov/forms.py | 4 ++-- ExonCov/models.py | 2 +- ExonCov/templates/panel.html | 2 +- ExonCov/templates/panel_edit.html | 2 +- ExonCov/templates/panel_new.html | 2 +- ExonCov/views.py | 6 +++--- migrations/versions/dda6242a627d_metadata.py | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ExonCov/admin_views.py b/ExonCov/admin_views.py index cda48b6..3107ac5 100644 --- a/ExonCov/admin_views.py +++ b/ExonCov/admin_views.py @@ -33,12 +33,12 @@ def _handle_view(self, name, **kwargs): class PanelAdminView(CustomModelView): """Panel admin view.""" - column_list = ['name', 'versions', 'disease_description_eng', 'clinical_geneticist', 'staff_member', 'comments'] + column_list = ['name', 'versions', 'disease_description_eng', 'clinic_contact', 'staff_member', 'comments'] column_searchable_list = ['name'] form_columns = [ 'name', 'disease_description_nl', 'disease_description_eng', 'comments', 'patientfolder_alissa', - 'clinical_geneticist', 'staff_member' + 'clinic_contact', 'staff_member' ] diff --git a/ExonCov/forms.py b/ExonCov/forms.py index 4880f82..31d6144 100644 --- a/ExonCov/forms.py +++ b/ExonCov/forms.py @@ -152,7 +152,7 @@ class CreatePanelForm(FlaskForm): disease_description_eng = StringField('Disease description', validators=[validators.InputRequired()]) disease_description_nl = StringField('Ziekteomschrijving', validators=[validators.InputRequired()]) patientfolder_alissa = StringField('Alissa', validators=[validators.InputRequired()]) - clinical_geneticist = StringField('Clinical geneticist', validators=[validators.InputRequired()]) + clinic_contact = StringField('Clinic contact(s)', validators=[validators.InputRequired()]) staff_member = StringField('Staff member', validators=[validators.InputRequired()]) comments = TextAreaField('Comments', description="Provide a short description.", validators=[validators.InputRequired()]) transcript = [] # Filled in validate function @@ -218,7 +218,7 @@ class PanelEditForm(FlaskForm): disease_description_eng = StringField('Disease description', validators=[validators.InputRequired()]) disease_description_nl = StringField('Ziekteomschrijving', validators=[validators.InputRequired()]) patientfolder_alissa = StringField('Alissa', validators=[validators.InputRequired()]) - clinical_geneticist = StringField('Clinical geneticist', validators=[validators.InputRequired()]) + clinic_contact = StringField('Clinic contact(s)', validators=[validators.InputRequired()]) staff_member = StringField('Staff member', validators=[validators.InputRequired()]) comments = TextAreaField('Comments', description="Provide a short description.", validators=[validators.InputRequired()]) diff --git a/ExonCov/models.py b/ExonCov/models.py index 715e6f2..a2820c9 100644 --- a/ExonCov/models.py +++ b/ExonCov/models.py @@ -154,7 +154,7 @@ class Panel(db.Model): disease_description_eng = db.Column(db.String(255)) disease_description_nl = db.Column(db.String(255)) patientfolder_alissa = db.Column(db.String(255)) - clinical_geneticist = db.Column(db.String(255)) + clinic_contact = db.Column(db.String(255)) staff_member = db.Column(db.String(255)) comments = db.Column(db.Text()) diff --git a/ExonCov/templates/panel.html b/ExonCov/templates/panel.html index f771597..f3aa9f6 100644 --- a/ExonCov/templates/panel.html +++ b/ExonCov/templates/panel.html @@ -15,7 +15,7 @@
Disease description
{{ panel.disease_description_eng }}
Ziekteomschrijving
{{ panel.disease_description_nl }}
Alissa
{{ panel.patientfolder_alissa }}
-
Clinical geneticist
{{ panel.clinical_geneticist }}
+
Clinic contact(s)
{{ panel.clinic_contact }}
Staff member
{{ panel.staff_member }}
Comments
{{ panel.comments }}
diff --git a/ExonCov/templates/panel_edit.html b/ExonCov/templates/panel_edit.html index 9a27eb2..4535df7 100644 --- a/ExonCov/templates/panel_edit.html +++ b/ExonCov/templates/panel_edit.html @@ -12,7 +12,7 @@ {{ render_field(form.disease_description_eng) }} {{ render_field(form.disease_description_nl) }} {{ render_field(form.patientfolder_alissa) }} - {{ render_field(form.clinical_geneticist,) }} + {{ render_field(form.clinic_contact) }} {{ render_field(form.staff_member) }} {{ render_field(form.comments, rows="2") }}
diff --git a/ExonCov/templates/panel_new.html b/ExonCov/templates/panel_new.html index c903970..f77aa35 100644 --- a/ExonCov/templates/panel_new.html +++ b/ExonCov/templates/panel_new.html @@ -11,7 +11,7 @@ {{ render_field(form.disease_description_eng) }} {{ render_field(form.disease_description_nl) }} {{ render_field(form.patientfolder_alissa) }} - {{ render_field(form.clinical_geneticist,) }} + {{ render_field(form.clinic_contact) }} {{ render_field(form.staff_member) }} {{ render_field(form.gene_list, rows="12") }} {{ render_field(form.coverage_requirement_15) }} diff --git a/ExonCov/views.py b/ExonCov/views.py index a8a2301..1cac5ad 100644 --- a/ExonCov/views.py +++ b/ExonCov/views.py @@ -262,7 +262,7 @@ def panel_edit(name): disease_description_eng=panel.disease_description_eng, disease_description_nl=panel.disease_description_nl, patientfolder_alissa=panel.patientfolder_alissa, - clinical_geneticist=panel.clinical_geneticist, + clinic_contact=panel.clinic_contact, staff_member=panel.staff_member, ) @@ -271,7 +271,7 @@ def panel_edit(name): panel.disease_description_eng = panel_edit_form.disease_description_eng.data panel.disease_description_nl = panel_edit_form.disease_description_nl.data panel.patientfolder_alissa = panel_edit_form.patientfolder_alissa.data - panel.clinical_geneticist = panel_edit_form.clinical_geneticist.data + panel.clinic_contact = panel_edit_form.clinic_contact.data panel.staff_member = panel_edit_form.staff_member.data db.session.add(panel) @@ -298,7 +298,7 @@ def panel_new(): disease_description_eng=new_panel_form.disease_description_eng.data, disease_description_nl=new_panel_form.disease_description_nl.data, patientfolder_alissa=new_panel_form.patientfolder_alissa.data, - clinical_geneticist=new_panel_form.clinical_geneticist.data, + clinic_contact=new_panel_form.clinic_contact.data, staff_member=new_panel_form.staff_member.data ) diff --git a/migrations/versions/dda6242a627d_metadata.py b/migrations/versions/dda6242a627d_metadata.py index 07ff869..18b9bbd 100644 --- a/migrations/versions/dda6242a627d_metadata.py +++ b/migrations/versions/dda6242a627d_metadata.py @@ -18,7 +18,7 @@ def upgrade(): op.add_column('panel_versions', sa.Column('coverage_requirement_15', sa.Float(), nullable=True)) - op.add_column('panels', sa.Column('clinical_geneticist', sa.String(length=255), nullable=True)) + op.add_column('panels', sa.Column('clinic_contact', sa.String(length=255), nullable=True)) op.add_column('panels', sa.Column('comments', sa.Text(), nullable=True)) op.add_column('panels', sa.Column('disease_description_eng', sa.String(length=255), nullable=True)) op.add_column('panels', sa.Column('disease_description_nl', sa.String(length=255), nullable=True)) @@ -36,5 +36,5 @@ def downgrade(): op.drop_column('panels', 'disease_description_nl') op.drop_column('panels', 'disease_description_eng') op.drop_column('panels', 'comments') - op.drop_column('panels', 'clinical_geneticist') + op.drop_column('panels', 'clinic_contact') op.drop_column('panel_versions', 'coverage_requirement_15') From 9ae1630c6d13b8d5b347613c2f62a3db4c9411ae Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Tue, 9 Feb 2021 16:41:06 +0100 Subject: [PATCH 13/40] Create random sample set, sovles #22 --- ExonCov.py | 3 ++- ExonCov/cli.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/ExonCov.py b/ExonCov.py index 96ea354..049a961 100755 --- a/ExonCov.py +++ b/ExonCov.py @@ -14,6 +14,7 @@ manager.add_command('search_sample', cli.SearchSample()) manager.add_command('remove_sample', cli.RemoveSample()) manager.add_command('check_samples', cli.CheckSamples()) +manager.add_command('create_sample_set', cli.CreateSampleSet()) db_manager.add_command('migrate', MigrateCommand) db_manager.add_command('stats', cli.PrintStats()) @@ -21,7 +22,7 @@ db_manager.add_command('gene_transcripts', cli.PrintTranscripts()) db_manager.add_command('import_alias_table', cli.ImportAliasTable()) db_manager.add_command('export_panel_bed', cli.PrintPanelBed()) -#db_manager.add_command('load_design', cli.LoadDesign()) +# db_manager.add_command('load_design', cli.LoadDesign()) if __name__ == "__main__": manager.run() diff --git a/ExonCov/cli.py b/ExonCov/cli.py index 1890a6d..e231c2d 100644 --- a/ExonCov/cli.py +++ b/ExonCov/cli.py @@ -7,17 +7,20 @@ import os import shlex import urllib +import datetime from flask_script import Command, Option from sqlalchemy import func from sqlalchemy.orm import joinedload from sqlalchemy.exc import IntegrityError +from sqlalchemy.sql.expression import func import tempfile import shutil import pysam from . import app, db, utils -from .models import Gene, GeneAlias, Transcript, Exon, SequencingRun, Sample, SampleProject, TranscriptMeasurement, Panel, PanelVersion, CustomPanel +from .models import Gene, GeneAlias, Transcript, Exon, SequencingRun, Sample, SampleProject, TranscriptMeasurement, Panel +from .models import PanelVersion, CustomPanel, SampleSet from .utils import weighted_average @@ -72,7 +75,7 @@ class PrintTranscripts(Command): ) def run(self, preferred_transcripts): - print('{gene}\t{transcript}'.format( gene='Gene', transcript='Transcript')) + print('{gene}\t{transcript}'.format(gene='Gene', transcript='Transcript')) genes = Gene.query.options(joinedload('transcripts')) @@ -81,7 +84,7 @@ def run(self, preferred_transcripts): print('{gene}\t{transcript}'.format(gene=gene.id, transcript=gene.default_transcript.name)) else: for transcript in gene.transcripts: - print('{gene}\t{transcript}'.format(gene=gene.id,transcript=transcript.name)) + print('{gene}\t{transcript}'.format(gene=gene.id, transcript=transcript.name)) class ImportBam(Command): @@ -365,6 +368,57 @@ def run(self): print("No errors found.") +class CreateSampleSet(Command): + """Create (random) sample set.""" + + option_list = ( + Option('name'), + Option('-d', '--max_days', dest='max_days', type=int, default=180), + Option('-s', '--sample_filter', dest='sample_filter', default=''), + Option('-t', '--sample_type', dest='sample_type', default='WES'), + Option('-n', '--sample_number', dest='sample_number', type=int, default=100), + ) + + def run(self, name, max_days, sample_filter, sample_type, sample_number): + description = '{0} random {1} samples. Maximum age: {2} days. Sample name filter: {3}'.format(sample_number, sample_type, max_days, sample_filter) + filter_date = datetime.date.today() - datetime.timedelta(days=max_days) + + samples = Sample.query.filter(Sample.name.like('%{0}%'.format(sample_filter))).filter_by(type=sample_type).order_by(func.rand()) + sample_count = 0 + + sample_set = SampleSet( + name=name, + description=description, + ) + + for sample in samples: + if sample.import_date > filter_date and not sample.project.type: # Do not use samples with 'special' project type (validation etc) + sample_set.samples.append(sample) + sample_count += 1 + + if sample_count >= sample_number: + break + + if len(sample_set.samples) != sample_number: + print 'Not enough samples found to create sample set, found {0} samples.'.format(len(sample_set.samples)) + else: + print 'Creating new random sample set:' + print '\tName: {0}'.format(sample_set.name) + print '\tDescription: {0}'.format(sample_set.description) + print '\tSamples:' + for sample in sample_set.samples: + print '\t\t{0}\t{1}\t{2}\t{3}'.format(sample.name, sample.project, sample.type, sample.import_date) + + confirmation = '' + while confirmation not in ['y', 'n']: + confirmation = raw_input('Please check samples and press [Y/y] to continue or [N/n] to abort. ').lower() + + if confirmation == 'y': + db.session.add(sample_set) + db.session.commit() + print 'Sample set created, make sure to activate it via the admin page.' + + class ImportAliasTable(Command): """Import gene aliases from HGNC (ftp://ftp.ebi.ac.uk/pub/databases/genenames/new/tsv/hgnc_complete_set.txt)""" From a6f64ecc015b5655f3dc4427403b50de6b20f1f6 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Tue, 9 Feb 2021 18:25:08 +0100 Subject: [PATCH 14/40] Replace json with txt as column type --- ExonCov/models.py | 2 +- ExonCov/utils.py | 4 +++- migrations/versions/f6911f9c19ef_add_event_logs_table.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ExonCov/models.py b/ExonCov/models.py index a2820c9..b64d738 100644 --- a/ExonCov/models.py +++ b/ExonCov/models.py @@ -386,7 +386,7 @@ class EventLog(db.Model): table = db.Column(db.String(255), nullable=False) action = db.Column(db.String(255), nullable=False) modified_on = db.Column(db.DateTime, default=datetime.datetime.now) - data = db.Column(db.JSON(), nullable=False) + data = db.Column(db.Text(), nullable=False) user = db.relationship('User') diff --git a/ExonCov/utils.py b/ExonCov/utils.py index c55cdab..3b8a9e0 100644 --- a/ExonCov/utils.py +++ b/ExonCov/utils.py @@ -1,4 +1,6 @@ """Utility functions.""" +import json + from flask import request, url_for from flask_login import current_user from sqlalchemy.orm.exc import NoResultFound @@ -62,6 +64,6 @@ def event_logger(connection, log_model, model_name, action, event_data): 'user_id': current_user.id, 'table': model_name, 'action': action, - 'data': event_data + 'data': json.dumps(event_data) } ) diff --git a/migrations/versions/f6911f9c19ef_add_event_logs_table.py b/migrations/versions/f6911f9c19ef_add_event_logs_table.py index 93dabae..ca29280 100644 --- a/migrations/versions/f6911f9c19ef_add_event_logs_table.py +++ b/migrations/versions/f6911f9c19ef_add_event_logs_table.py @@ -24,7 +24,7 @@ def upgrade(): sa.Column('table', sa.String(length=255), nullable=False), sa.Column('action', sa.String(length=255), nullable=False), sa.Column('modified_on', sa.DateTime(), nullable=True), - sa.Column('data', sa.JSON(), nullable=False), + sa.Column('data', sa.Text(), nullable=False), sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), sa.PrimaryKeyConstraint('id') ) From 31d04e157cd6f0debfbc65e4e24321dd9a23f25b Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Fri, 19 Feb 2021 11:51:20 +0100 Subject: [PATCH 15/40] Add core gene model and edit pages. --- ExonCov/admin_views.py | 6 +- ExonCov/forms.py | 105 +++++++++++++++--- ExonCov/models.py | 7 ++ ExonCov/templates/panel_new.html | 1 + ExonCov/templates/panel_new_version.html | 1 + .../templates/panel_new_version_confirm.html | 1 + ExonCov/templates/panel_version.html | 7 ++ ExonCov/templates/panel_version_edit.html | 3 +- ExonCov/views.py | 12 +- .../68b4dcb164af_add_core_genes_table.py | 35 ++++++ 10 files changed, 160 insertions(+), 18 deletions(-) create mode 100644 migrations/versions/68b4dcb164af_add_core_genes_table.py diff --git a/ExonCov/admin_views.py b/ExonCov/admin_views.py index 3107ac5..3eede40 100644 --- a/ExonCov/admin_views.py +++ b/ExonCov/admin_views.py @@ -48,12 +48,16 @@ class PanelVersionAdminView(CustomModelView): form_columns = [ 'panel', 'version_year', 'version_revision', 'comments', 'coverage_requirement_15', 'active', 'validated', - 'transcripts' + 'transcripts', 'core_genes' ] form_ajax_refs = { 'transcripts': { 'fields': ['name', 'gene_id'], 'page_size': 10 + }, + 'core_genes': { + 'fields': ['id'], + 'page_size': 10 } } diff --git a/ExonCov/forms.py b/ExonCov/forms.py index 31d6144..34917e7 100644 --- a/ExonCov/forms.py +++ b/ExonCov/forms.py @@ -26,22 +26,35 @@ def all_panels(): return PanelVersion.query.filter_by(active=True).filter_by(validated=True).all() +def get_gene(gene_id): + """Find gene or gene aliases.""" + error = '' + gene = Gene.query.get(gene_id) + + # Find possible gene alias if gene not found + if not gene: + gene_aliases = GeneAlias.query.filter_by(id=gene_id).all() + if gene_aliases: + error = 'Unkown gene: {0}. Possible aliases: {1}. Please check before using alias.'.format( + gene_id, + ', '.join([gene_alias.gene_id for gene_alias in gene_aliases]) + ) + else: + error = 'Unknown gene: {0}.'.format(gene_id) + + return gene, error + + def parse_gene_list(gene_list, transcripts=[]): """Parse data from gene_list form field""" errors = [] for gene_id in re.split('[\n\r,;\t ]+', gene_list): gene_id = gene_id.strip() if gene_id: - gene = Gene.query.get(gene_id) - if gene is None: - gene_aliases = GeneAlias.query.filter_by(id=gene_id).all() - if gene_aliases: - errors.append('Unkown gene: {0}. Possible aliases: {1}. Please check before using alias.'.format( - gene_id, - ', '.join([gene_alias.gene_id for gene_alias in gene_aliases]) - )) - else: - errors.append('Unknown gene: {0}.'.format(gene_id)) + gene, error = get_gene(gene_id) + + if not gene: + errors.append(error) elif gene.default_transcript in transcripts: errors.append('Multiple entries for gene: {0}.'.format(gene_id)) else: @@ -49,6 +62,22 @@ def parse_gene_list(gene_list, transcripts=[]): return errors, transcripts +def parse_core_gene_list(gene_list, genes=[]): + """Parse data from gene_list form field""" + errors = [] + for gene_id in re.split('[\n\r,;\t ]+', gene_list): + gene_id = gene_id.strip() + if gene_id: + gene, error = get_gene(gene_id) + if not gene: + errors.append(error) + elif gene in genes: + errors.append('Multiple entries for gene: {0}.'.format(gene_id)) + else: + genes.append(gene) + return errors, genes + + class SampleForm(FlaskForm): """Query samples by run or samplename field""" sample = StringField('Sample') @@ -148,6 +177,7 @@ class CreatePanelForm(FlaskForm): name = StringField('Name', validators=[validators.InputRequired()]) gene_list = TextAreaField('Gene list', description="List of genes seperated by newline, space, ',' or ';'.", validators=[validators.InputRequired()]) + core_gene_list = TextAreaField('Core gene list', description="List of core genes seperated by newline, space, ',' or ';'.") coverage_requirement_15 = FloatField('Minimal % 15x', default=99, validators=[validators.InputRequired()]) disease_description_eng = StringField('Disease description', validators=[validators.InputRequired()]) disease_description_nl = StringField('Ziekteomschrijving', validators=[validators.InputRequired()]) @@ -155,7 +185,9 @@ class CreatePanelForm(FlaskForm): clinic_contact = StringField('Clinic contact(s)', validators=[validators.InputRequired()]) staff_member = StringField('Staff member', validators=[validators.InputRequired()]) comments = TextAreaField('Comments', description="Provide a short description.", validators=[validators.InputRequired()]) + transcript = [] # Filled in validate function + core_genes = [] def validate(self): """Extra validation, used to validate gene list and panel name.""" @@ -167,11 +199,21 @@ def validate(self): if self.gene_list.data: # Parse gene_list - errors, self.transcripts = parse_gene_list(self.gene_list.data, self.transcripts) + errors, self.transcripts = parse_gene_list(self.gene_list.data, self.transcripts ) if errors: self.gene_list.errors.extend(errors) return False + if self.core_gene_list.data: + # Parse gene_list + errors, self.core_genes = parse_core_gene_list(self.core_gene_list.data, self.core_genes) + for gene in self.core_genes: + if gene.id not in self.gene_list.data: + errors.append('Core gene {0} not found in gene list.'.format(gene.id)) + if errors: + self.core_gene_list.errors.extend(errors) + return False + if self.name.data: panel = Panel.query.filter_by(name=self.name.data).first() if panel: @@ -187,15 +229,21 @@ class PanelNewVersionForm(FlaskForm): """New panel version form.""" gene_list = TextAreaField('Gene list', description="List of genes seperated by newline, space, ',' or ';'.", validators=[validators.InputRequired()]) + core_gene_list = TextAreaField('Core gene list', description="List of core genes seperated by newline, space, ',' or ';'.") coverage_requirement_15 = FloatField('Minimal % 15x') comments = TextAreaField('Comments', description="Provide a short description.", validators=[validators.InputRequired()]) confirm = BooleanField('Confirm') - transcript = [] # Filled in validate function + + # Filled in validate function + transcript = [] + core_genes = [] def validate(self): """Extra validation, used to validate gene list.""" - # Default validation as defined in field validators - self.transcripts = [] # Reset transcripts on validation + + # Reset before validation + self.transcripts = [] + self.core_genes = [] if not FlaskForm.validate(self): return False @@ -207,6 +255,16 @@ def validate(self): self.gene_list.errors.extend(errors) return False + if self.core_gene_list.data: + # Parse gene_list + errors, self.core_genes = parse_core_gene_list(self.core_gene_list.data, self.core_genes) + for gene in self.core_genes: + if gene.id not in self.gene_list.data: + errors.append('Core gene {0} not found in gene list.'.format(gene.id)) + if errors: + self.core_gene_list.errors.extend(errors) + return False + if self.gene_list.errors: return False @@ -230,6 +288,25 @@ class PanelVersionEditForm(FlaskForm): active = BooleanField('Active') validated = BooleanField('Validated') coverage_requirement_15 = FloatField('Minimal % 15x') + core_gene_list = TextAreaField('Core gene list', description="List of core genes seperated by newline, space, ',' or ';'.") + + core_genes = [] + + def validate(self): + """Extra validation, used to validate core gene list.""" + self.core_genes = [] # Reset core_genes on validation + + if not FlaskForm.validate(self): + return False + + if self.core_gene_list.data: + # Parse gene_list + errors, self.core_genes = parse_core_gene_list(self.core_gene_list.data, self.core_genes) + if errors: + self.core_gene_list.errors.extend(errors) + return False + + return True class CustomPanelValidateForm(FlaskForm): diff --git a/ExonCov/models.py b/ExonCov/models.py index b64d738..dd53ff6 100644 --- a/ExonCov/models.py +++ b/ExonCov/models.py @@ -22,6 +22,12 @@ db.Column('transcript_id', db.ForeignKey('transcripts.id'), primary_key=True) ) +panels_core_genes = db.Table( + 'panels_core_genes', + db.Column('panel_id', db.ForeignKey('panel_versions.id'), primary_key=True), + db.Column('gene_id', db.ForeignKey('genes.id'), primary_key=True) +) + custom_panels_transcripts = db.Table( 'custom_panels_transcripts', db.Column('custom_panel_id', db.ForeignKey('custom_panels.id'), primary_key=True), @@ -190,6 +196,7 @@ class PanelVersion(db.Model): panel = db.relationship('Panel', back_populates='versions') user = db.relationship('User') transcripts = db.relationship('Transcript', secondary=panels_transcripts, back_populates='panels') + core_genes = db.relationship('Gene', secondary=panels_core_genes) def __repr__(self): return "PanelVersion({0})".format(self.name_version) diff --git a/ExonCov/templates/panel_new.html b/ExonCov/templates/panel_new.html index f77aa35..d20e6a5 100644 --- a/ExonCov/templates/panel_new.html +++ b/ExonCov/templates/panel_new.html @@ -14,6 +14,7 @@ {{ render_field(form.clinic_contact) }} {{ render_field(form.staff_member) }} {{ render_field(form.gene_list, rows="12") }} + {{ render_field(form.core_gene_list, rows="12") }} {{ render_field(form.coverage_requirement_15) }} {{ render_field(form.comments, rows="2") }} diff --git a/ExonCov/templates/panel_new_version.html b/ExonCov/templates/panel_new_version.html index 54585b5..a948dd3 100644 --- a/ExonCov/templates/panel_new_version.html +++ b/ExonCov/templates/panel_new_version.html @@ -8,6 +8,7 @@ {{ form.csrf_token }} {{ render_field(form.gene_list, rows="12") }} + {{ render_field(form.core_gene_list, rows="12") }} {{ render_field(form.coverage_requirement_15) }} {{ render_field(form.comments, rows="2") }}
diff --git a/ExonCov/templates/panel_new_version_confirm.html b/ExonCov/templates/panel_new_version_confirm.html index 5b95786..d154038 100644 --- a/ExonCov/templates/panel_new_version_confirm.html +++ b/ExonCov/templates/panel_new_version_confirm.html @@ -41,6 +41,7 @@

Confirm panel update

{{ form.csrf_token }} {{ form.gene_list(rows="12", hidden=True) }} + {{ form.core_gene_list(rows="12", hidden=True) }} {{ form.coverage_requirement_15(hidden=True) }} {{ form.comments(rows="2", hidden=True) }} {{ render_inline_checkbox(form.confirm) }} diff --git a/ExonCov/templates/panel_version.html b/ExonCov/templates/panel_version.html index 7ac1b88..d946bbd 100644 --- a/ExonCov/templates/panel_version.html +++ b/ExonCov/templates/panel_version.html @@ -35,7 +35,11 @@ {% for transcript in panel.transcripts %}
+ {% if transcript.gene in panel.core_genes %} + + {% else %} + {% endif %} @@ -44,6 +48,9 @@ {% endfor %}
{{ panels[panel]['name_version'] }} {{ panels[panel]['len'] }}
{{ panels[panel]['name_version'] }} {{ panels[panel]['len'] }}
{{ transcript.end }} {{ transcript.exon_count }}
Sample Project Sequencing runsType Date
{{ sample.name }} {{ sample.project.name }}
    {% for run in sample.sequencing_runs %}
  • {{ run }}
  • {% endfor %}
{{ sample.type }} {{ sample.import_date }}
# Genes Active ValidatedMinimal % 15x
{{ panel_version.gene_count }} {{ render_bool_glyph(panel_version.active) }} {{ render_bool_glyph(panel_version.validated) }}{{ panel_version.coverage_requirement_15 }}
{{ sample.name }}{{ sample.project.name }}{{ sample.project }}
    {% for run in sample.sequencing_runs %}
  • {{ run }}
  • {% endfor %}
{{ sample.type }} {{ sample.import_date }}
{{ transcript.name }}{{ transcript.gene.id }} *{{ transcript.gene.id }}{{ transcript.chr }} {{ transcript.start }} {{ transcript.end }}
+{% if panel.core_genes %} +* Core gene +{% endif %} {% endblock %} {% block custom_javascript %} diff --git a/ExonCov/templates/panel_version_edit.html b/ExonCov/templates/panel_version_edit.html index 8b0eb44..ced06d3 100644 --- a/ExonCov/templates/panel_version_edit.html +++ b/ExonCov/templates/panel_version_edit.html @@ -12,8 +12,9 @@ {{ render_checkbox(form.active) }} {{ render_checkbox(form.validated) }} {{ render_field(form.coverage_requirement_15) }} + {{ render_field(form.core_gene_list, rows="12") }} {{ render_field(form.comments, rows="2") }} - +
diff --git a/ExonCov/views.py b/ExonCov/views.py index 1cac5ad..2076c21 100644 --- a/ExonCov/views.py +++ b/ExonCov/views.py @@ -215,7 +215,8 @@ def panel_new_version(name): panel_last_version = panel.last_version genes = '\n'.join([transcript.gene_id for transcript in panel_last_version.transcripts]) - update_panel_form = PanelNewVersionForm(gene_list=genes, coverage_requirement_15=panel_last_version.coverage_requirement_15) + core_genes = '\n'.join([gene.id for gene in panel_last_version.core_genes]) + update_panel_form = PanelNewVersionForm(gene_list=genes, core_gene_list=core_genes, coverage_requirement_15=panel_last_version.coverage_requirement_15) if update_panel_form.validate_on_submit(): transcripts = update_panel_form.transcripts @@ -239,6 +240,7 @@ def panel_new_version(name): version_year=year, version_revision=revision, transcripts=transcripts, + core_genes=update_panel_form.core_genes, coverage_requirement_15=update_panel_form.coverage_requirement_15.data, comments=update_panel_form.data['comments'], user=current_user @@ -291,6 +293,7 @@ def panel_new(): if new_panel_form.validate_on_submit(): panel_name = new_panel_form.data['name'] transcripts = new_panel_form.transcripts + core_genes = new_panel_form.core_genes new_panel = Panel( name=panel_name, @@ -307,6 +310,7 @@ def panel_new(): version_year=time.strftime('%y'), version_revision=1, transcripts=transcripts, + core_genes=core_genes, coverage_requirement_15=new_panel_form.coverage_requirement_15.data, comments=new_panel_form.comments.data, user=current_user @@ -332,11 +336,14 @@ def panel_version(id): def panel_version_edit(id): """Set validation status to true.""" panel = PanelVersion.query.get_or_404(id) + core_genes = '\n'.join([gene.id for gene in panel.core_genes]) + form = PanelVersionEditForm( active=panel.active, validated=panel.validated, comments=panel.comments, - coverage_requirement_15=panel.coverage_requirement_15 + coverage_requirement_15=panel.coverage_requirement_15, + core_gene_list=core_genes ) if form.validate_on_submit(): @@ -344,6 +351,7 @@ def panel_version_edit(id): panel.validated = form.validated.data panel.comments = form.comments.data panel.coverage_requirement_15 = form.coverage_requirement_15.data + panel.core_genes = form.core_genes db.session.add(panel) db.session.commit() return redirect(url_for('panel_version', id=panel.id)) diff --git a/migrations/versions/68b4dcb164af_add_core_genes_table.py b/migrations/versions/68b4dcb164af_add_core_genes_table.py new file mode 100644 index 0000000..a0d47e4 --- /dev/null +++ b/migrations/versions/68b4dcb164af_add_core_genes_table.py @@ -0,0 +1,35 @@ +"""Add core genes table + +Revision ID: 68b4dcb164af +Revises: f6911f9c19ef +Create Date: 2021-02-18 12:07:43.405371 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '68b4dcb164af' +down_revision = 'f6911f9c19ef' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + 'panels_core_genes', + sa.Column('panel_id', sa.Integer(), nullable=False), + sa.Column('gene_id', sa.String(length=50, collation='utf8_bin'), nullable=False), + sa.ForeignKeyConstraint(['gene_id'], ['genes.id'], ), + sa.ForeignKeyConstraint(['panel_id'], ['panel_versions.id'], ), + sa.PrimaryKeyConstraint('panel_id', 'gene_id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('panels_core_genes') + # ### end Alembic commands ### From e84b52bcbab0bdc6a3c06ba13360acfe680d3eaa Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Fri, 19 Feb 2021 12:47:52 +0100 Subject: [PATCH 16/40] Update QC colors, use panel cov req and core gene --- ExonCov/templates/custom_panel.html | 10 ++++---- ExonCov/templates/custom_panel_gene.html | 10 ++++---- ExonCov/templates/macros/tables.html | 17 ++++++++++++- ExonCov/templates/sample.html | 4 ++-- ExonCov/templates/sample_gene.html | 4 ++-- ExonCov/templates/sample_inactive_panels.html | 4 ++-- ExonCov/templates/sample_panel.html | 18 +++++++++++--- ExonCov/templates/sample_set.html | 10 ++++---- ExonCov/templates/sample_set_gene.html | 10 ++++---- ExonCov/templates/sample_set_panel.html | 24 ++++++++++++++----- ExonCov/templates/sample_transcript.html | 2 +- 11 files changed, 76 insertions(+), 37 deletions(-) diff --git a/ExonCov/templates/custom_panel.html b/ExonCov/templates/custom_panel.html index 6bc7e64..f3eb0fd 100644 --- a/ExonCov/templates/custom_panel.html +++ b/ExonCov/templates/custom_panel.html @@ -1,5 +1,5 @@ {% from "macros/forms.html" import measurement_type_form %} -{% from "macros/tables.html" import render_measurment_td %} +{% from "macros/tables.html" import render_measurment_td, render_gene_measurment_td %} {% from "macros/typography.html" import render_bool_glyph %} {% extends 'base.html' %} @@ -78,11 +78,11 @@

Gene Statistics

{{ transcript.name }} {{ transcript.gene.id }} - {{ render_measurment_td(transcript_measurements[transcript]['mean']) }} - {{ render_measurment_td(transcript_measurements[transcript]['min']) }} - {{ render_measurment_td(transcript_measurements[transcript]['max']) }} + {{ render_gene_measurment_td(transcript_measurements[transcript]['mean'], measurement_type[0]) }} + {{ render_gene_measurment_td(transcript_measurements[transcript]['min'], measurement_type[0]) }} + {{ render_gene_measurment_td(transcript_measurements[transcript]['max'], measurement_type[0]) }} {% for sample in custom_panel.samples %} - {{ render_measurment_td(transcript_measurements[transcript][sample]) }} + {{ render_gene_measurment_td(transcript_measurements[transcript][sample], measurement_type[0]) }} {% endfor %} {% endfor %} diff --git a/ExonCov/templates/custom_panel_gene.html b/ExonCov/templates/custom_panel_gene.html index 91a6b12..2507fad 100644 --- a/ExonCov/templates/custom_panel_gene.html +++ b/ExonCov/templates/custom_panel_gene.html @@ -1,5 +1,5 @@ {% from "macros/forms.html" import measurement_type_form %} -{% from "macros/tables.html" import render_measurment_td %} +{% from "macros/tables.html" import render_gene_measurment_td %} {% extends 'base.html' %} {% block header %}Custom gene panel - {{ gene.id }} {% endblock %} @@ -42,11 +42,11 @@

Transcript Statistics

{% else %}
{{ transcript.name }} {% endif %} - {{ render_measurment_td(transcript_measurements[transcript]['mean']) }} - {{ render_measurment_td(transcript_measurements[transcript]['min']) }} - {{ render_measurment_td(transcript_measurements[transcript]['max']) }} + {{ render_gene_measurment_td(transcript_measurements[transcript]['mean'], measurement_type[0]) }} + {{ render_gene_measurment_td(transcript_measurements[transcript]['min'], measurement_type[0]) }} + {{ render_gene_measurment_td(transcript_measurements[transcript]['max'], measurement_type[0]) }} {% for sample in custom_panel.samples %} - {{ render_measurment_td(transcript_measurements[transcript][sample]) }} + {{ render_gene_measurment_td(transcript_measurements[transcript][sample], measurement_type[0]) }} {% endfor %} {% endfor %} diff --git a/ExonCov/templates/macros/tables.html b/ExonCov/templates/macros/tables.html index ca004ca..e267092 100644 --- a/ExonCov/templates/macros/tables.html +++ b/ExonCov/templates/macros/tables.html @@ -1,7 +1,22 @@ -{% macro render_measurment_td(measurement, type, panel=None) %} +{% macro render_measurment_td(measurement) %} + {{measurement|float|round(2)}} +{% endmacro %} + + +{% macro render_panel_measurment_td(measurement, type, panel=None) %} {% if panel and type == 'measurement_percentage15' and measurement|float < panel.coverage_requirement_15 %} {{measurement|float|round(2)}} {% else %} {{measurement|float|round(2)}} {% endif %} +{% endmacro %} + +{% macro render_gene_measurment_td(measurement, type, core_gene=False) %} +{% if type == 'measurement_percentage15' and core_gene and measurement|float < 100 %} + {{measurement|float|round(2)}} +{% elif type == 'measurement_percentage15' and not core_gene and measurement|float < 95 %} + {{measurement|float|round(2)}} +{% else %} + {{measurement|float|round(2)}} +{% endif %} {% endmacro %} \ No newline at end of file diff --git a/ExonCov/templates/sample.html b/ExonCov/templates/sample.html index c397c8e..e04c93b 100644 --- a/ExonCov/templates/sample.html +++ b/ExonCov/templates/sample.html @@ -1,4 +1,4 @@ -{% from "macros/tables.html" import render_measurment_td %} +{% from "macros/tables.html" import render_measurment_td, render_panel_measurment_td %} {% from "macros/forms.html" import render_sample_datatable_form %} {% extends 'base.html' %} @@ -62,7 +62,7 @@ {{ panels[panel]['name_version'] }} {{ panels[panel]['len'] }} {% for type in measurement_types %} - {{ render_measurment_td(panels[panel][type], type, panels[panel]) }} + {{ render_panel_measurment_td(panels[panel][type], type, panels[panel]) }} {% endfor %} {% endfor %} diff --git a/ExonCov/templates/sample_gene.html b/ExonCov/templates/sample_gene.html index e7c73a1..999bb97 100644 --- a/ExonCov/templates/sample_gene.html +++ b/ExonCov/templates/sample_gene.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% from "macros/tables.html" import render_measurment_td %} +{% from "macros/tables.html" import render_gene_measurment_td %} {% from "macros/forms.html" import render_sample_datatable_form %} {% block header %}{{ gene.id }} - {{ sample.name }}{% endblock %} @@ -48,7 +48,7 @@ {{ transcript.exon_count }} {{ measurement.len }} {% for type in measurement_types %} - {{ render_measurment_td(measurement[type], type) }} + {{ render_gene_measurment_td(measurement[type], type) }} {% endfor %} {% endfor %} diff --git a/ExonCov/templates/sample_inactive_panels.html b/ExonCov/templates/sample_inactive_panels.html index 460cf93..f289bf7 100644 --- a/ExonCov/templates/sample_inactive_panels.html +++ b/ExonCov/templates/sample_inactive_panels.html @@ -1,4 +1,4 @@ -{% from "macros/tables.html" import render_measurment_td %} +{% from "macros/tables.html" import render_panel_measurment_td %} {% from "macros/forms.html" import render_sample_datatable_form %} {% extends 'base.html' %} @@ -36,7 +36,7 @@ {{ panels[panel]['name_version'] }} {{ panels[panel]['len'] }} {% for type in measurement_types %} - {{ render_measurment_td(panels[panel][type], type, panels[panel]) }} + {{ render_panel_measurment_td(panels[panel][type], type, panels[panel]) }} {% endfor %} {% endfor %} diff --git a/ExonCov/templates/sample_panel.html b/ExonCov/templates/sample_panel.html index 818bdc5..910186a 100644 --- a/ExonCov/templates/sample_panel.html +++ b/ExonCov/templates/sample_panel.html @@ -1,5 +1,5 @@ {% extends 'base.html' %} -{% from "macros/tables.html" import render_measurment_td %} +{% from "macros/tables.html" import render_gene_measurment_td %} {% from "macros/forms.html" import render_sample_datatable_form %} {% block header %}{{ panel.name_version }} - {{ sample.name }}{% endblock %} @@ -36,20 +36,32 @@ {% for transcript_measurement in transcript_measurements %} {% set transcript = transcript_measurement[0] %} {% set measurement = transcript_measurement[1] %} + {% if transcript.gene in panel.core_genes %} + {% set core_gene = True %} + {% else %} + {% set core_gene = False %} + {% endif %} {{ transcript.name }} - {{ transcript.gene_id }} + {% if core_gene %} + {{ transcript.gene_id }} * + {% else %} + {{ transcript.gene_id }} + {% endif %} {{ transcript.chr }} {{ transcript.start }} {{ transcript.end }} {{ transcript.exon_count }} {% for type in measurement_types %} - {{ render_measurment_td(measurement[type], type, panel) }} + {{ render_gene_measurment_td(measurement[type], type, core_gene) }} {% endfor %} {% endfor %} +{% if panel.core_genes %} + * Core gene +{% endif %} {% endblock %} {% block custom_javascript %} diff --git a/ExonCov/templates/sample_set.html b/ExonCov/templates/sample_set.html index 624db67..445ab82 100644 --- a/ExonCov/templates/sample_set.html +++ b/ExonCov/templates/sample_set.html @@ -1,5 +1,5 @@ {% from "macros/forms.html" import measurement_type_form, render_sample_datatable_form %} -{% from "macros/tables.html" import render_measurment_td %} +{% from "macros/tables.html" import render_panel_measurment_td %} {% extends 'base.html' %} {% block header %}Sample set - {{ sample_set.name }}{% endblock %} @@ -31,11 +31,11 @@ {% for panel in panels_measurements %} {{ panel }} - {{ render_measurment_td(panels_measurements[panel]['mean']) }} - {{ render_measurment_td(panels_measurements[panel]['min']) }} - {{ render_measurment_td(panels_measurements[panel]['max']) }} + {{ render_panel_measurment_td(panels_measurements[panel]['mean'], measurement_type[0], panel) }} + {{ render_panel_measurment_td(panels_measurements[panel]['min'], measurement_type[0], panel) }} + {{ render_panel_measurment_td(panels_measurements[panel]['max'], measurement_type[0], panel) }} {% for sample in sample_set.samples %} - {{ render_measurment_td(panels_measurements[panel]['samples'][sample]['measurement']) }} + {{ render_panel_measurment_td(panels_measurements[panel]['samples'][sample]['measurement'], measurement_type[0], panel) }} {% endfor %} {% endfor %} diff --git a/ExonCov/templates/sample_set_gene.html b/ExonCov/templates/sample_set_gene.html index 1e9af85..47daa77 100644 --- a/ExonCov/templates/sample_set_gene.html +++ b/ExonCov/templates/sample_set_gene.html @@ -1,5 +1,5 @@ {% from "macros/forms.html" import measurement_type_form, render_sample_datatable_form %} -{% from "macros/tables.html" import render_measurment_td %} +{% from "macros/tables.html" import render_gene_measurment_td %} {% extends 'base.html' %} {% block header %}Sample set - {{ sample_set.name }}{% endblock %} @@ -38,11 +38,11 @@ {% else %} {{ transcript.name }} {% endif %} - {{ render_measurment_td(transcript_measurements[transcript]['mean']) }} - {{ render_measurment_td(transcript_measurements[transcript]['min']) }} - {{ render_measurment_td(transcript_measurements[transcript]['max']) }} + {{ render_gene_measurment_td(transcript_measurements[transcript]['mean'], measurement_type[0]) }} + {{ render_gene_measurment_td(transcript_measurements[transcript]['min'], measurement_type[0]) }} + {{ render_gene_measurment_td(transcript_measurements[transcript]['max'], measurement_type[0]) }} {% for sample in sample_set.samples %} - {{ render_measurment_td(transcript_measurements[transcript][sample]) }} + {{ render_gene_measurment_td(transcript_measurements[transcript][sample], measurement_type[0]) }} {% endfor %} {% endfor %} diff --git a/ExonCov/templates/sample_set_panel.html b/ExonCov/templates/sample_set_panel.html index ca57edd..f7b2f01 100644 --- a/ExonCov/templates/sample_set_panel.html +++ b/ExonCov/templates/sample_set_panel.html @@ -1,5 +1,5 @@ {% from "macros/forms.html" import measurement_type_form, render_sample_datatable_form %} -{% from "macros/tables.html" import render_measurment_td %} +{% from "macros/tables.html" import render_gene_measurment_td %} {% extends 'base.html' %} {% block header %}Sample set - {{ sample_set.name }}{% endblock %} @@ -31,19 +31,31 @@ {% for transcript in transcript_measurements %} + {% if transcript.gene in panel.core_genes %} + {% set core_gene = True %} + {% else %} + {% set core_gene = False %} + {% endif %} {{ transcript.name }} - {{ transcript.gene.id }} - {{ render_measurment_td(transcript_measurements[transcript]['mean']) }} - {{ render_measurment_td(transcript_measurements[transcript]['min']) }} - {{ render_measurment_td(transcript_measurements[transcript]['max']) }} + {% if core_gene %} + {{ transcript.gene.id }} * + {% else %} + {{ transcript.gene.id }} + {% endif %} + {{ render_gene_measurment_td(transcript_measurements[transcript]['mean'], measurement_type[0], core_gene) }} + {{ render_gene_measurment_td(transcript_measurements[transcript]['min'], measurement_type[0], core_gene) }} + {{ render_gene_measurment_td(transcript_measurements[transcript]['max'], measurement_type[0], core_gene) }} {% for sample in sample_set.samples %} - {{ render_measurment_td(transcript_measurements[transcript][sample]) }} + {{ render_gene_measurment_td(transcript_measurements[transcript][sample], measurement_type[0], core_gene) }} {% endfor %} {% endfor %} +{% if panel.core_genes %} + * Core gene +{% endif %} {% endblock %} {% block custom_javascript %} diff --git a/ExonCov/templates/sample_transcript.html b/ExonCov/templates/sample_transcript.html index fcb28f8..93b8675 100644 --- a/ExonCov/templates/sample_transcript.html +++ b/ExonCov/templates/sample_transcript.html @@ -38,7 +38,7 @@ {{ exon_measurement.end }} {{ exon_measurement.len }} {% for type in measurement_types %} - {{ render_measurment_td(exon_measurement[type], type) }} + {{ render_measurment_td(exon_measurement[type]) }} {% endfor %} {% endfor %} From 51e55e7628d63423cde0f0f9f21f448c73ce0061 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Fri, 19 Feb 2021 14:21:57 +0100 Subject: [PATCH 17/40] Don't cleanup transcripts or panel --- ExonCov/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ExonCov/utils.py b/ExonCov/utils.py index 3b8a9e0..9e454a6 100644 --- a/ExonCov/utils.py +++ b/ExonCov/utils.py @@ -53,7 +53,7 @@ def weighted_average(values, weights): def event_logger(connection, log_model, model_name, action, event_data): # Cleanup and transform item to str for item in event_data.keys(): - if item in ['_sa_instance_state', 'user', 'transcripts', 'panel']: + if item in ['_sa_instance_state', 'user']: event_data.pop(item, None) else: event_data[item] = str(event_data[item]) From 72e1c9a920ac24dc7a5a8f3778a1fbcc99961bf4 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Fri, 19 Feb 2021 15:52:14 +0100 Subject: [PATCH 18/40] Join core genes --- ExonCov/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ExonCov/views.py b/ExonCov/views.py index 2076c21..afd5686 100644 --- a/ExonCov/views.py +++ b/ExonCov/views.py @@ -125,7 +125,7 @@ def sample_inactive_panels(id): def sample_panel(sample_id, panel_id): """Sample panel page.""" sample = Sample.query.options(joinedload('sequencing_runs')).options(joinedload('project')).get_or_404(sample_id) - panel = PanelVersion.query.get_or_404(panel_id) + panel = PanelVersion.query.options(joinedload('core_genes')).get_or_404(panel_id) measurement_types = { 'measurement_mean_coverage': 'Mean coverage', @@ -134,7 +134,7 @@ def sample_panel(sample_id, panel_id): 'measurement_percentage30': '>30' } - transcript_measurements = db.session.query(Transcript, TranscriptMeasurement).join(panels_transcripts).filter(panels_transcripts.columns.panel_id == panel.id).join(TranscriptMeasurement).filter_by(sample_id=sample.id).options(joinedload(Transcript.exons, innerjoin=True)).all() + transcript_measurements = db.session.query(Transcript, TranscriptMeasurement).join(panels_transcripts).filter(panels_transcripts.columns.panel_id == panel.id).join(TranscriptMeasurement).filter_by(sample_id=sample.id).options(joinedload(Transcript.exons, innerjoin=True)).options(joinedload(Transcript.gene)).all() return render_template('sample_panel.html', sample=sample, panel=panel, transcript_measurements=transcript_measurements, measurement_types=measurement_types) From 339969a300d4b03d4564194e6d9b334ad4298391 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Mon, 22 Feb 2021 13:35:08 +0100 Subject: [PATCH 19/40] Merge multiple model imports. --- ExonCov/cli.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ExonCov/cli.py b/ExonCov/cli.py index e231c2d..01e4f23 100644 --- a/ExonCov/cli.py +++ b/ExonCov/cli.py @@ -19,8 +19,10 @@ import pysam from . import app, db, utils -from .models import Gene, GeneAlias, Transcript, Exon, SequencingRun, Sample, SampleProject, TranscriptMeasurement, Panel -from .models import PanelVersion, CustomPanel, SampleSet +from .models import ( + Gene, GeneAlias, Transcript, Exon, SequencingRun, Sample, SampleProject, TranscriptMeasurement, Panel, + PanelVersion, CustomPanel, SampleSet +) from .utils import weighted_average From 03ff3446c02f2c9ecb0fe6689d79b55e604fb704 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Mon, 22 Feb 2021 13:35:19 +0100 Subject: [PATCH 20/40] Remove trailing space --- ExonCov/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ExonCov/forms.py b/ExonCov/forms.py index 34917e7..c781d28 100644 --- a/ExonCov/forms.py +++ b/ExonCov/forms.py @@ -199,7 +199,7 @@ def validate(self): if self.gene_list.data: # Parse gene_list - errors, self.transcripts = parse_gene_list(self.gene_list.data, self.transcripts ) + errors, self.transcripts = parse_gene_list(self.gene_list.data, self.transcripts) if errors: self.gene_list.errors.extend(errors) return False From 548a10c3c15eefd0ad015666cf5c5e01eed4b02a Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Mon, 22 Feb 2021 13:54:22 +0100 Subject: [PATCH 21/40] Explain disabled LoadDesign function. --- ExonCov.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ExonCov.py b/ExonCov.py index 049a961..281e4d9 100755 --- a/ExonCov.py +++ b/ExonCov.py @@ -22,7 +22,7 @@ db_manager.add_command('gene_transcripts', cli.PrintTranscripts()) db_manager.add_command('import_alias_table', cli.ImportAliasTable()) db_manager.add_command('export_panel_bed', cli.PrintPanelBed()) -# db_manager.add_command('load_design', cli.LoadDesign()) +# db_manager.add_command('load_design', cli.LoadDesign()) # Disabled, can be used to setup a new database from scratch if __name__ == "__main__": manager.run() From a3cc2b37b1b0a1d1a1a6cab2c87cd34fb90c372d Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Mon, 22 Feb 2021 14:25:05 +0100 Subject: [PATCH 22/40] Update based on review comments --- ExonCov/cli.py | 14 +++++++++++--- ExonCov/forms.py | 30 ++++++++++++++++++------------ ExonCov/views.py | 33 +++++++++++++++++++++++++++++---- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/ExonCov/cli.py b/ExonCov/cli.py index 01e4f23..0e47e96 100644 --- a/ExonCov/cli.py +++ b/ExonCov/cli.py @@ -382,10 +382,17 @@ class CreateSampleSet(Command): ) def run(self, name, max_days, sample_filter, sample_type, sample_number): - description = '{0} random {1} samples. Maximum age: {2} days. Sample name filter: {3}'.format(sample_number, sample_type, max_days, sample_filter) + description = '{0} random {1} samples. Maximum age: {2} days. Sample name filter: {3}'.format( + sample_number, sample_type, max_days, sample_filter + ) filter_date = datetime.date.today() - datetime.timedelta(days=max_days) - samples = Sample.query.filter(Sample.name.like('%{0}%'.format(sample_filter))).filter_by(type=sample_type).order_by(func.rand()) + samples = ( + Sample.query + .filter(Sample.name.like('%{0}%'.format(sample_filter))) + .filter_by(type=sample_type) + .order_by(func.rand()) + ) sample_count = 0 sample_set = SampleSet( @@ -394,7 +401,8 @@ def run(self, name, max_days, sample_filter, sample_type, sample_number): ) for sample in samples: - if sample.import_date > filter_date and not sample.project.type: # Do not use samples with 'special' project type (validation etc) + # Do not use samples with 'special' project type (validation etc) + if sample.import_date > filter_date and not sample.project.type: sample_set.samples.append(sample) sample_count += 1 diff --git a/ExonCov/forms.py b/ExonCov/forms.py index c781d28..bb5c9de 100644 --- a/ExonCov/forms.py +++ b/ExonCov/forms.py @@ -176,7 +176,11 @@ class CreatePanelForm(FlaskForm): """Create Panel form.""" name = StringField('Name', validators=[validators.InputRequired()]) - gene_list = TextAreaField('Gene list', description="List of genes seperated by newline, space, ',' or ';'.", validators=[validators.InputRequired()]) + gene_list = TextAreaField( + 'Gene list', + description="List of genes seperated by newline, space, ',' or ';'.", + validators=[validators.InputRequired()] + ) core_gene_list = TextAreaField('Core gene list', description="List of core genes seperated by newline, space, ',' or ';'.") coverage_requirement_15 = FloatField('Minimal % 15x', default=99, validators=[validators.InputRequired()]) disease_description_eng = StringField('Disease description', validators=[validators.InputRequired()]) @@ -190,9 +194,11 @@ class CreatePanelForm(FlaskForm): core_genes = [] def validate(self): - """Extra validation, used to validate gene list and panel name.""" - # Default validation as defined in field validators - self.transcripts = [] # Reset transcripts on validation + """Additional validation, used to validate gene list and panel name.""" + + # Reset on validation + self.transcripts = [] + self.core_genes = [] if not FlaskForm.validate(self): return False @@ -202,7 +208,6 @@ def validate(self): errors, self.transcripts = parse_gene_list(self.gene_list.data, self.transcripts) if errors: self.gene_list.errors.extend(errors) - return False if self.core_gene_list.data: # Parse gene_list @@ -212,14 +217,13 @@ def validate(self): errors.append('Core gene {0} not found in gene list.'.format(gene.id)) if errors: self.core_gene_list.errors.extend(errors) - return False if self.name.data: panel = Panel.query.filter_by(name=self.name.data).first() if panel: self.name.errors.append('Panel already exists, use the update button on the panel page to create a new version.') - if self.gene_list.errors or self.name.errors: + if self.gene_list.errors or self.core_gene_list.errors or self.name.errors: return False return True @@ -228,7 +232,11 @@ def validate(self): class PanelNewVersionForm(FlaskForm): """New panel version form.""" - gene_list = TextAreaField('Gene list', description="List of genes seperated by newline, space, ',' or ';'.", validators=[validators.InputRequired()]) + gene_list = TextAreaField( + 'Gene list', + description="List of genes seperated by newline, space, ',' or ';'.", + validators=[validators.InputRequired()] + ) core_gene_list = TextAreaField('Core gene list', description="List of core genes seperated by newline, space, ',' or ';'.") coverage_requirement_15 = FloatField('Minimal % 15x') comments = TextAreaField('Comments', description="Provide a short description.", validators=[validators.InputRequired()]) @@ -239,7 +247,7 @@ class PanelNewVersionForm(FlaskForm): core_genes = [] def validate(self): - """Extra validation, used to validate gene list.""" + """Additional validation, used to validate gene list.""" # Reset before validation self.transcripts = [] @@ -253,7 +261,6 @@ def validate(self): errors, self.transcripts = parse_gene_list(self.gene_list.data, self.transcripts) if errors: self.gene_list.errors.extend(errors) - return False if self.core_gene_list.data: # Parse gene_list @@ -263,9 +270,8 @@ def validate(self): errors.append('Core gene {0} not found in gene list.'.format(gene.id)) if errors: self.core_gene_list.errors.extend(errors) - return False - if self.gene_list.errors: + if self.gene_list.errors or self.core_gene_list.errors: return False return True diff --git a/ExonCov/views.py b/ExonCov/views.py index afd5686..cba459b 100644 --- a/ExonCov/views.py +++ b/ExonCov/views.py @@ -134,9 +134,24 @@ def sample_panel(sample_id, panel_id): 'measurement_percentage30': '>30' } - transcript_measurements = db.session.query(Transcript, TranscriptMeasurement).join(panels_transcripts).filter(panels_transcripts.columns.panel_id == panel.id).join(TranscriptMeasurement).filter_by(sample_id=sample.id).options(joinedload(Transcript.exons, innerjoin=True)).options(joinedload(Transcript.gene)).all() + transcript_measurements = ( + db.session.query(Transcript, TranscriptMeasurement) + .join(panels_transcripts) + .filter(panels_transcripts.columns.panel_id == panel.id) + .join(TranscriptMeasurement) + .filter_by(sample_id=sample.id) + .options(joinedload(Transcript.exons, innerjoin=True)) + .options(joinedload(Transcript.gene)) + .all() + ) - return render_template('sample_panel.html', sample=sample, panel=panel, transcript_measurements=transcript_measurements, measurement_types=measurement_types) + return render_template( + 'sample_panel.html', + sample=sample, + panel=panel, + transcript_measurements=transcript_measurements, + measurement_types=measurement_types + ) @app.route('/sample//transcript/') @@ -216,7 +231,11 @@ def panel_new_version(name): genes = '\n'.join([transcript.gene_id for transcript in panel_last_version.transcripts]) core_genes = '\n'.join([gene.id for gene in panel_last_version.core_genes]) - update_panel_form = PanelNewVersionForm(gene_list=genes, core_gene_list=core_genes, coverage_requirement_15=panel_last_version.coverage_requirement_15) + update_panel_form = PanelNewVersionForm( + gene_list=genes, + core_gene_list=core_genes, + coverage_requirement_15=panel_last_version.coverage_requirement_15 + ) if update_panel_form.validate_on_submit(): transcripts = update_panel_form.transcripts @@ -249,7 +268,13 @@ def panel_new_version(name): db.session.commit() return redirect(url_for('panel', name=panel.name)) else: - return render_template('panel_new_version_confirm.html', form=update_panel_form, panel=panel_last_version, year=year, revision=revision) + return render_template( + 'panel_new_version_confirm.html', + form=update_panel_form, + panel=panel_last_version, + year=year, + revision=revision + ) return render_template('panel_new_version.html', form=update_panel_form, panel=panel_last_version) From 6426a8083f8021e9542350792b869660543facad Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Mon, 22 Feb 2021 17:27:35 +0100 Subject: [PATCH 23/40] Add min input length, Fix #28 --- ExonCov/templates/custom_panel_new.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ExonCov/templates/custom_panel_new.html b/ExonCov/templates/custom_panel_new.html index b3664a8..d399681 100644 --- a/ExonCov/templates/custom_panel_new.html +++ b/ExonCov/templates/custom_panel_new.html @@ -26,8 +26,9 @@ {% block custom_javascript %} {% endblock %} From a83254cc8a138e90799433d9597b850d28360140 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Wed, 24 Feb 2021 15:59:49 +0100 Subject: [PATCH 24/40] Add Panel/Gene search to sampleset view, solve #24 --- ExonCov/forms.py | 42 ++++++++++++++++++++++++++++++ ExonCov/templates/sample_sets.html | 28 +++++++++++++++++++- ExonCov/views.py | 14 +++++++--- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/ExonCov/forms.py b/ExonCov/forms.py index bb5c9de..8886c2b 100644 --- a/ExonCov/forms.py +++ b/ExonCov/forms.py @@ -319,3 +319,45 @@ class CustomPanelValidateForm(FlaskForm): """Custom Panel set validation status form.""" confirm = BooleanField('Confirm', validators=[validators.InputRequired()]) + + +class SampleSetPanelGeneForm(FlaskForm): + """SampleSet Form to query a specific panel or gene.""" + sample_set = QuerySelectField( + 'Sample sets', + query_factory=active_sample_sets, + allow_blank=True, + blank_text='None', + validators=[validators.InputRequired()] + ) + panel = QuerySelectField('Panel', query_factory=all_panels, allow_blank=True, blank_text='None') + gene = StringField('Gene') + + gene_id = '' + + def validate(self): + """Extra validation to parse panel / gene selection""" + self.gene_id = '' + valid_form = True + + if not FlaskForm.validate(self): + valid_form = False + + if not self.sample_set.data: + message = 'Select a sample_set' + self.sample_set.errors.append(message) + valid_form = False + + if (self.panel.data and self.gene.data) or (not self.panel.data and not self.gene.data): + message = 'Select a panel or gene.' + self.panel.errors.append(message) + self.gene.errors.append(message) + valid_form = False + elif self.gene.data: + gene, error = get_gene(self.gene.data) + if error: + self.gene.errors.append(error) + else: + self.gene_id = gene.id + + return valid_form diff --git a/ExonCov/templates/sample_sets.html b/ExonCov/templates/sample_sets.html index de73d7d..60c4751 100644 --- a/ExonCov/templates/sample_sets.html +++ b/ExonCov/templates/sample_sets.html @@ -1,4 +1,4 @@ -{% from "macros/forms.html" import render_sample_datatable_form %} +{% from "macros/forms.html" import render_field, render_select2_field %} {% extends 'base.html' %} {% block header %}Sample sets{% endblock %} @@ -24,4 +24,30 @@ {% endfor %} + + +
+ + {{ form.csrf_token }} + {{ render_select2_field(form.sample_set) }} + {{ render_select2_field(form.panel) }} + {{ render_field(form.gene) }} + +
+
+ +
+
+ +
{% endblock %} + +{% block custom_javascript %} + +{% endblock %} \ No newline at end of file diff --git a/ExonCov/views.py b/ExonCov/views.py index cba459b..20f022e 100644 --- a/ExonCov/views.py +++ b/ExonCov/views.py @@ -17,7 +17,7 @@ ) from .forms import ( MeasurementTypeForm, CustomPanelForm, CustomPanelNewForm, CustomPanelValidateForm, SampleForm, - CreatePanelForm, PanelNewVersionForm, PanelEditForm, PanelVersionEditForm + CreatePanelForm, PanelNewVersionForm, PanelEditForm, PanelVersionEditForm, SampleSetPanelGeneForm ) from .utils import weighted_average @@ -599,14 +599,22 @@ def custom_panel_validated(id): return render_template('custom_panel_validate_confirm.html', form=custom_panel_validate_form, custom_panel=custom_panel) -@app.route('/sample_set') +@app.route('/sample_set', methods=['GET', 'POST']) @login_required @roles_required('panel_admin') def sample_sets(): """Sample sets page.""" sample_sets = SampleSet.query.options(joinedload('samples')).filter_by(active=True).all() - return render_template('sample_sets.html', sample_sets=sample_sets) + panel_gene_form = SampleSetPanelGeneForm() + + if panel_gene_form.validate_on_submit(): + if panel_gene_form.panel.data: + return redirect(url_for('sample_set_panel', sample_set_id=panel_gene_form.sample_set.data.id, panel_id=panel_gene_form.panel.data.id)) + elif panel_gene_form.gene_id: + return redirect(url_for('sample_set_gene', sample_set_id=panel_gene_form.sample_set.data.id, gene_id=panel_gene_form.gene_id)) + + return render_template('sample_sets.html', sample_sets=sample_sets, form=panel_gene_form) @app.route('/sample_set/', methods=['GET', 'POST']) From acd71bd46d3d50071d0cf3f661668fb667c8d388 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Thu, 25 Feb 2021 16:02:29 +0100 Subject: [PATCH 25/40] Update form label --- ExonCov/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ExonCov/forms.py b/ExonCov/forms.py index 8886c2b..09f4148 100644 --- a/ExonCov/forms.py +++ b/ExonCov/forms.py @@ -324,7 +324,7 @@ class CustomPanelValidateForm(FlaskForm): class SampleSetPanelGeneForm(FlaskForm): """SampleSet Form to query a specific panel or gene.""" sample_set = QuerySelectField( - 'Sample sets', + 'Sample set', query_factory=active_sample_sets, allow_blank=True, blank_text='None', From 7430160bef404c8bce7febaabf1078bd92a65bd4 Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Tue, 2 Mar 2021 14:00:09 +0100 Subject: [PATCH 26/40] Update sampleset cli to skip merge samples. --- ExonCov/cli.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/ExonCov/cli.py b/ExonCov/cli.py index eb1e76d..7a6a325 100644 --- a/ExonCov/cli.py +++ b/ExonCov/cli.py @@ -401,8 +401,13 @@ def run(self, name, max_days, sample_filter, sample_type, sample_number): ) for sample in samples: - # Do not use samples with 'special' project type (validation etc) - if sample.import_date > filter_date and not sample.project.type: + # Filter sampels: import date, 'special' project type (validation etc), Merge samples, + if ( + sample.import_date > filter_date + and not sample.project.type + and len(sample.sequencing_runs) == 1 + and sample.sequencing_runs[0].platform_unit in sample.project.name + ): sample_set.samples.append(sample) sample_count += 1 @@ -417,7 +422,13 @@ def run(self, name, max_days, sample_filter, sample_type, sample_number): print '\tDescription: {0}'.format(sample_set.description) print '\tSamples:' for sample in sample_set.samples: - print '\t\t{0}\t{1}\t{2}\t{3}'.format(sample.name, sample.project, sample.type, sample.import_date) + print '\t\t{0}\t{1}\t{2}\t{3}\t{4}'.format( + sample.name, + sample.type, + sample.project, + sample.sequencing_runs, + sample.import_date + ) confirmation = '' while confirmation not in ['y', 'n']: From e913052b6b63770aa6efbcb1d74af32672fcab9d Mon Sep 17 00:00:00 2001 From: Robert Ernst Date: Tue, 2 Mar 2021 14:07:08 +0100 Subject: [PATCH 27/40] Open samplesets to all users. --- ExonCov/templates/navbar.html | 2 +- ExonCov/views.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/ExonCov/templates/navbar.html b/ExonCov/templates/navbar.html index 599569e..c4a3efa 100644 --- a/ExonCov/templates/navbar.html +++ b/ExonCov/templates/navbar.html @@ -13,7 +13,7 @@