Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*.sqlite3
*.sqlite
last_sync
trees/

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
39 changes: 39 additions & 0 deletions app/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import csv
import os
from typing import Optional
from app import __version__, get_db_last_sync
from flask import (
Flask,
Expand Down Expand Up @@ -39,6 +40,14 @@
app.secret_key = os.urandom(12)

IS_DEV_SITE = os.environ.get("MARC_DEV", "").lower() == "true"
MARC_TREE_FP = os.environ.get("MARC_TREE_FP")


def treefile_for_species(species_name: str) -> Optional[str]:
if not species_name:
return None
slug = "_".join(species_name.lower().split())
return f"{slug}.treefile"


@app.context_processor
Expand Down Expand Up @@ -380,6 +389,36 @@ def show_antimicrobial(antimicrobial_id: int):
)


@app.route("/species/<path:species_name>")
def show_species(species_name: str):
assignment_count = (
db.session.query(func.count(TaxonomicAssignment.id))
.filter(TaxonomicAssignment.classification == species_name)
.scalar()
)
treefile_name = treefile_for_species(species_name)
tree_content = None
tree_path = None
tree_error = None
if MARC_TREE_FP and treefile_name:
candidate = Path(MARC_TREE_FP) / treefile_name
tree_path = str(candidate)
if candidate.is_file():
try:
tree_content = candidate.read_text()
except Exception as exc:
tree_error = str(exc)
return render_template(
"show_species.html",
species_name=species_name,
assignment_count=assignment_count,
tree_path=tree_path,
tree_content=tree_content,
tree_error=tree_error,
tree_root=MARC_TREE_FP,
)


@app.route("/assembly/<int:assembly_id>")
def show_assembly(assembly_id: int):
assemblies = get_assemblies(db.session, id=assembly_id)
Expand Down
11 changes: 10 additions & 1 deletion app/templates/browse_isolates.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ <h2>Isolates</h2>
$(document).ready(function () {
/* Initialize the DataTable with server-side processing */
const isolateUrl = "{{ url_for('show_isolate', isolate_id='') }}";
const speciesUrl = "{{ url_for('show_species', species_name='') }}";
const renderCollectionYear = (value, type) => {
if (!value) {
return type === 'sort' || type === 'type' ? null : '';
Expand Down Expand Up @@ -59,7 +60,15 @@ <h2>Isolates</h2>
{ data: 'received_date', render: function(value, type) { return renderCollectionYear(value, type); } },
{ data: 'subject_id' },
{ data: 'specimen_id' },
{ data: 'suspected_organism' }
{ data: 'suspected_organism', render: function(value, type) {
if (type !== 'display') {
return value;
}
if (!value) {
return '';
}
return '<a href="' + speciesUrl + encodeURIComponent(value) + '">' + value + '</a>';
} }
]);

/* Move search box to bottom of summary area */
Expand Down
11 changes: 10 additions & 1 deletion app/templates/browse_taxonomic_assignments.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,20 @@ <h2>Taxonomic Assignments</h2>
<script type="text/javascript">
$(document).ready(function () {
const assignmentUrl = "{{ url_for('show_taxonomic_assignment', assembly_id=0)[:-1] }}";
const speciesUrl = "{{ url_for('show_species', species_name='') }}";
const oTable = createServerDataTable('#taxonomic_assignments', '{{ url_for('api_taxonomic_assignments') }}', [
{ data: 'assembly_id', render: function(d) { return '<a href="' + assignmentUrl + d + '">' + d + '</a>'; } },
{ data: 'isolate_id' },
{ data: 'tool' },
{ data: 'classification' },
{ data: 'classification', render: function(value, type) {
if (type !== 'display') {
return value;
}
if (!value) {
return '';
}
return '<a href="' + speciesUrl + encodeURIComponent(value) + '">' + value + '</a>';
} },
{ data: 'comment' },
]);

Expand Down
10 changes: 9 additions & 1 deletion app/templates/show_assembly.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,15 @@ <h3 class="h5 mb-0">Taxonomic Assignments</h3>
<tbody>
{% for assignment in assignments %}
<tr>
<td>{{ assignment.classification or '—' }}</td>
<td>
{% if assignment.classification %}
<a href="{{ url_for('show_species', species_name=assignment.classification) }}">
{{ assignment.classification }}
</a>
{% else %}
{% endif %}
</td>
<td>{{ assignment.comment or '—' }}</td>
<td>{{ assignment.tool or '—' }}</td>
</tr>
Expand Down
10 changes: 9 additions & 1 deletion app/templates/show_isolate.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ <h2 class="h4 mb-0">Isolate {{ isolate.sample_id }}</h2>
<dd class="col-sm-8">{{ isolate.specimen_id }}</dd>

<dt class="col-sm-4">Suspected Organism</dt>
<dd class="col-sm-8">{{ isolate.suspected_organism or '—' }}</dd>
<dd class="col-sm-8">
{% if isolate.suspected_organism %}
<a href="{{ url_for('show_species', species_name=isolate.suspected_organism) }}">
{{ isolate.suspected_organism }}
</a>
{% else %}
{% endif %}
</dd>

<dt class="col-sm-4">Special Collection</dt>
<dd class="col-sm-8">{{ isolate.special_collection or '—' }}</dd>
Expand Down
64 changes: 64 additions & 0 deletions app/templates/show_species.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{% extends 'base.html' %}

{% block head %}
<link
href="https://cdn.jsdelivr.net/npm/phylotree@2.4.0/dist/phylotree.min.css"
rel="stylesheet"
integrity="sha384-aiMpATZTEuYHJmBVtYfxx+TOz295Gf4nfxFQrb+0jLomP6U3EFtcVv5sHsbpDOkk"
crossorigin="anonymous"
>
{% endblock %}

{% block body %}
<div class="container py-4">
<a class="btn btn-link ps-0" href="{{ url_for('browse_taxonomic_assignments') }}">
<i class="bi bi-arrow-left"></i> Back to taxonomic assignments
</a>

<div class="card shadow-sm mb-4">
<div class="card-header bg-primary text-white">
<h2 class="h4 mb-0">{{ species_name }}</h2>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-4">Matching taxonomic assignments</dt>
<dd class="col-sm-8">{{ assignment_count }}</dd>
</dl>
</div>
</div>

<div class="card shadow-sm">
<div class="card-header bg-light">
<h3 class="h5 mb-0">Tree</h3>
</div>
<div class="card-body">
{% if tree_error %}
<p class="text-danger mb-0">Unable to read treefile: {{ tree_error }}</p>
{% elif tree_content %}
<p class="text-muted small mb-2">Source: {{ tree_path.rsplit('/', 1)[-1] }}</p>
<div id="tree-container" class="border rounded bg-white" style="min-height: 500px;"></div>
<script src="https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js" integrity="sha384-CjloA8y00+1SDAUkjs099PVfnY2KmDC2BZnws9kh8D/lX1s46w6EPhpXdqMfjK6i" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.7/underscore-min.js" integrity="sha384-pSOYJz6tr3k8mV502/FWpqSv7+V4JzjQc8uhWMOZE1SoCFLcJCSfxMFOE6hEd/hT" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js" integrity="sha384-H6KKS1H1WwuERMSm+54dYLzjg0fKqRK5ZRyASdbrI/lwrCc6bXEmtGYr5SwvP1pZ" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/phylotree@2.4.0/dist/phylotree.min.js" integrity="sha384-34Lu7wnDpakXRJnVFjFOd2/2rWjTA18GPeCh8LgBT007VUc43HtcbFilygn/rwFT" crossorigin="anonymous"></script>
<script>
const newick = {{ tree_content | tojson }};
console.log(newick);
const tree = new phylotree.phylotree(newick);
tree.render({
container: "#tree-container",
width: document.querySelector("#tree-container").clientWidth || 800,
height: 500,
"align-tips": true,
zoom: true
});
</script>
{% elif tree_root %}
<p class="mb-0">No treefile found for this species.</p>
{% else %}
<p class="mb-0">Set the MARC_TREE_FP environment variable to load treefiles.</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}
10 changes: 9 additions & 1 deletion app/templates/show_taxonomic_assignment.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ <h2 class="h4 mb-0">Taxonomic Assignment for Assembly {{ assembly_id }}</h2>
<dd class="col-sm-8">{{ assignment.tool or '—' }}</dd>

<dt class="col-sm-4">Classification</dt>
<dd class="col-sm-8">{{ assignment.classification or '—' }}</dd>
<dd class="col-sm-8">
{% if assignment.classification %}
<a href="{{ url_for('show_species', species_name=assignment.classification) }}">
{{ assignment.classification }}
</a>
{% else %}
{% endif %}
</dd>

<dt class="col-sm-4">Comment</dt>
<dd class="col-sm-8">{{ assignment.comment or '—' }}</dd>
Expand Down