Skip to content

Commit

Permalink
Added search for genomes + isolation sources/countries
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalaldo committed Dec 13, 2024
2 parents 3ac0760 + bddc07f commit a6535e9
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 5 deletions.
10 changes: 10 additions & 0 deletions django_project/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@
name="pathway_info_genes_json",
),
path("search/", search_views.search_results, name="search_results"),
path(
"search/genomes_json/",
search_views.genomes_json,
name="search_genomes_json",
),
path(
"search/genes_json/",
search_views.gene_annotation_json,
Expand All @@ -168,6 +173,11 @@
search_views.download_search_pathway_csv,
name="download_search_pathway_csv",
),
path(
"search/genomes/csv/",
search_views.download_search_genomes_csv,
name="download_search_genomes_csv",
),
path(
"search/genes/csv/",
search_views.download_search_genes_csv,
Expand Down
94 changes: 93 additions & 1 deletion search/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.template import loader
from organisms.models import Organisms
from pangenome_analyses.models import GeneAnnotations
from gene_function.models import PathwayInfo
from gene_function.models import PathwayInfo, GenomeInfo
from common import csv_export
import json, time, re

Expand Down Expand Up @@ -187,6 +187,51 @@ def download_search_genes_csv(request):
return response


def download_search_genomes_csv(request):
q_orig = request.GET.get("q")
q = clean_query(q_orig)
genome_keys = [
"pangenome_analysis",
"genome_id",
"strain",
"phylo_group",
"gc_content",
"country",
"broad_context",
"local_context",
"extra_context",
"isolation_source",
]
if len(q) >= 2 and not re.search(
q, "Missing", flags=re.IGNORECASE
): # Prevent too many results:
genomes = GenomeInfo.objects.aggregate(
GenomeInfo.get_genome_and_isolation_info_pipeline({})
+ build_multi_search_aggregation(
q, ["genome_id", "strain", "country", "iso_cat", "isolation_source"]
)
+ [
{
"$addFields": {
"broad_context": {"$arrayElemAt": ["$iso_cat", 0]},
"local_context": {"$arrayElemAt": ["$iso_cat", 1]},
"extra_context": {"$slice": ["$iso_cat", 2, 10]},
}
},
{"$project": {gk: int(gk != "_id") for gk in ["_id"] + genome_keys}},
]
)
else:
genomes = []
downloaded_file_name = (
"Search__genomes__" + time.strftime("%Y-%m-%d_%H-%M") + ".csv"
)
response = csv_export.dict_writer_response(
downloaded_file_name, genome_keys, genomes
)
return response


# JSON data for gene datatable
def gene_annotation_json(request):
q_orig = str(request.GET["q"])
Expand Down Expand Up @@ -215,3 +260,50 @@ def gene_annotation_json(request):
else:
genes = []
return JsonResponse({"results": genes})

# JSON data for genome datatable
def genomes_json(request):
q_orig = str(request.GET["q"])
q = clean_query(q_orig)

genome_keys = [
"pangenome_analysis",
"genome_id",
"strain",
"phylo_group",
"gc_content",
"country",
"broad_context",
"local_context",
"extra_context",
"isolation_source",
]
if len(q) >= 2 and not re.search(
q, "Missing", flags=re.IGNORECASE
): # Prevent too many results
genomes = GenomeInfo.objects.aggregate(
GenomeInfo.get_genome_and_isolation_info_pipeline({})
+ build_multi_search_aggregation(
q, ["genome_id", "strain", "country", "iso_cat", "isolation_source"]
)
+ [
{
"$addFields": {
"broad_context": {"$arrayElemAt": ["$iso_cat", 0]},
"local_context": {"$arrayElemAt": ["$iso_cat", 1]},
"extra_context": {"$slice": ["$iso_cat", 2, 10]},
}
},
{"$project": {gk: int(gk != "_id") for gk in ["_id"] + genome_keys}},
]
)
genomes = [
[
(str(g.get(gk, None)) if gk == "strain" else g.get(gk, None))
for gk in genome_keys
]
for g in genomes
]
else:
genomes = []
return JsonResponse({"results": genomes})
27 changes: 23 additions & 4 deletions templates/search/search_results.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
// and optionally expands a row in the "Genes" table if the text found is in the "Protein" column: ----
function highlightText(searchTerm) {
// Get all elements potentially containing text: ----
const elements = document.querySelectorAll('#families_list a, #species_list a, #genes_table a, .protein div, .pfams div, #pathways_list a, .pathway_name');
const elements = document.querySelectorAll('#families_list a, #species_list a, #genes_table a, .protein div, .pfams div, #pathways_list a, .pathway_name, .genome_id a, .ncbi_strain, .country, .broad_context, .local_context, .extra_context, .isolation_source');
// Loop through each of the elements: ----
elements.forEach((element) => {
const text = element.innerText;
Expand Down Expand Up @@ -64,8 +64,8 @@
<div class="row g-3">
<div class="col-12">
<h4 id="search_no_results" class="d-none display-5 p-2 ps-4">No search results found for <b>"{{ request.GET.q }}"</b></h4>
<h4 id="search_any_results" class="display-5 p-2 ps-4">Showing {% if families_results %}<a href="#families" class="btn btn-outline-primary">family</a>&nbsp;{% endif %}{% if species_results %}<a href="#species" class="btn btn-outline-primary">species</a>&nbsp;{% endif %}{% if pathways_results %}<a href="#pathways" class="btn btn-outline-primary">pathway</a>&nbsp;{% endif %}<a id="genes-button" href="#genes" class="btn btn-outline-primary disabled"><span id="genes-spinner" class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span>gene</a>&nbsp; search results for <b>"{{ request.GET.q }}"</b>
<span id="no-results-list"{% if no_results_list %}{% else %} class="d-none"{% endif %}>; No results found in {{ no_results_list | join:', '}}<span id="no-results-list-genes" class="d-none">, genes</span>.</span></h4></div>
<h4 id="search_any_results" class="display-5 p-2 ps-4">Showing {% if families_results %}<a href="#families" class="btn btn-outline-primary">family</a>&nbsp;{% endif %}{% if species_results %}<a href="#species" class="btn btn-outline-primary">species</a>&nbsp;{% endif %}{% if pathways_results %}<a href="#pathways" class="btn btn-outline-primary">pathway</a>&nbsp;{% endif %}<a id="genomes-button" href="#genomes" class="btn btn-outline-primary disabled"><span id="genomes-spinner" class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span>genome</a>&nbsp;<a id="genes-button" href="#genes" class="btn btn-outline-primary disabled"><span id="genes-spinner" class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span>gene</a>&nbsp; search results for <b>"{{ request.GET.q }}"</b>
<span id="no-results-list"{% if no_results_list %}{% else %} class="d-none"{% endif %}>; No results found in {{ no_results_list | join:', '}}<span id="no-results-list-genomes" class="d-none">, genomes</span><span id="no-results-list-genes" class="d-none">, genes</span>.</span></h4></div>
</div>
<div class="row gy-3">
{% if families_results %}
Expand All @@ -77,6 +77,9 @@ <h4 id="search_any_results" class="display-5 p-2 ps-4">Showing {% if families_re
{% if pathways_results %}
{% include "search/search_results_pathways.html" with panel_name="search_pathways" panel_title="<a id='pathways'>Pathways</a>" panel_class="col-lg-5" %}
{% endif %}
{% if "genomes" in no_results_list %}{% else %}
{% include "search/search_results_genomes.html" with panel_name="search_genomes" panel_title="<a id='genomes'>Genomes</a>" panel_class="col-lg-12 opacity-50" panel_wide_class="col-lg-12 opacity-50" %}
{% endif %}
{% if "genes" in no_results_list %}{% else %}
{% include "search/search_results_genes.html" with panel_name="search_genes" panel_title="<a id='genes'>Genes</a>" panel_class="col-lg-12 opacity-50" panel_wide_class="col-lg-12 opacity-50" %}
{% endif %}
Expand All @@ -99,7 +102,23 @@ <h4 id="search_any_results" class="display-5 p-2 ps-4">Showing {% if families_re
$('#genes-button').toggleClass('d-none', true);
$('#no-results-list').toggleClass('d-none', false);
$('#no-results-list-genes').toggleClass('d-none', false);
if (no_results_list.includes('families') && no_results_list.includes('species') && no_results_list.includes('pathways') && no_results_list.includes('genes')) {
if (no_results_list.includes('families') && no_results_list.includes('species') && no_results_list.includes('pathways') && no_results_list.includes('genes') && no_results_list.includes('genomes')) {
$('#search_any_results').toggleClass('d-none', true);
$('#search_no_results').toggleClass('d-none', false);
}
}
}
function update_results_overview_with_genomes(genomes_found) {
$('#search_genomes_panel').toggleClass('opacity-50', false);
$('#genomes-spinner').toggleClass('d-none', true);
$('#genomes-button').toggleClass('disabled', false);
if (!genomes_found) {
no_results_list.push('genomes');
$('#search_genomes_panel').toggleClass('d-none', true);
$('#genomes-button').toggleClass('d-none', true);
$('#no-results-list').toggleClass('d-none', false);
$('#no-results-list-genomes').toggleClass('d-none', false);
if (no_results_list.includes('families') && no_results_list.includes('species') && no_results_list.includes('pathways') && no_results_list.includes('genes') && no_results_list.includes('genomes')) {
$('#search_any_results').toggleClass('d-none', true);
$('#search_no_results').toggleClass('d-none', false);
}
Expand Down
181 changes: 181 additions & 0 deletions templates/search/search_results_genomes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
{% extends "components/panel_universal.html" %}
{% block panel_header %}
<form action="{% url 'download_search_genomes_csv' %}" target="_blank" method="GET" style="display: inline">
<input type="hidden" name="q" value="{{ request.GET.q }}" />
<button class="btn btn-outline-dark btn-sm" type="submit"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" style="display: inline; position: relative; top: -2px;" fill="currentColor" class="bi bi-filetype-csv" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M14 4.5V14a2 2 0 0 1-2 2h-1v-1h1a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5zM3.517 14.841a1.13 1.13 0 0 0 .401.823q.195.162.478.252.284.091.665.091.507 0 .859-.158.354-.158.539-.44.187-.284.187-.656 0-.336-.134-.56a1 1 0 0 0-.375-.357 2 2 0 0 0-.566-.21l-.621-.144a1 1 0 0 1-.404-.176.37.37 0 0 1-.144-.299q0-.234.185-.384.188-.152.512-.152.214 0 .37.068a.6.6 0 0 1 .246.181.56.56 0 0 1 .12.258h.75a1.1 1.1 0 0 0-.2-.566 1.2 1.2 0 0 0-.5-.41 1.8 1.8 0 0 0-.78-.152q-.439 0-.776.15-.337.149-.527.421-.19.273-.19.639 0 .302.122.524.124.223.352.367.228.143.539.213l.618.144q.31.073.463.193a.39.39 0 0 1 .152.326.5.5 0 0 1-.085.29.56.56 0 0 1-.255.193q-.167.07-.413.07-.175 0-.32-.04a.8.8 0 0 1-.248-.115.58.58 0 0 1-.255-.384zM.806 13.693q0-.373.102-.633a.87.87 0 0 1 .302-.399.8.8 0 0 1 .475-.137q.225 0 .398.097a.7.7 0 0 1 .272.26.85.85 0 0 1 .12.381h.765v-.072a1.33 1.33 0 0 0-.466-.964 1.4 1.4 0 0 0-.489-.272 1.8 1.8 0 0 0-.606-.097q-.534 0-.911.223-.375.222-.572.632-.195.41-.196.979v.498q0 .568.193.976.197.407.572.626.375.217.914.217.439 0 .785-.164t.55-.454a1.27 1.27 0 0 0 .226-.674v-.076h-.764a.8.8 0 0 1-.118.363.7.7 0 0 1-.272.25.9.9 0 0 1-.401.087.85.85 0 0 1-.478-.132.83.83 0 0 1-.299-.392 1.7 1.7 0 0 1-.102-.627zm8.239 2.238h-.953l-1.338-3.999h.917l.896 3.138h.038l.888-3.138h.879z"/>
</svg></button>
</form>
{% endblock %}
{% block panel_content %}
<table id="genomes_table" class="table table-hover display" style="width: 100%;">
<thead>
<tr>
<th>Genome</th>
<th>Stain (NCBI)</th>
<th>Phylo Group</th>
<th>%GC</th>
<th>Country</th>
<th>Broad Context</th>
<th>Local Context</th>
<th>Extra Context</th>
<th>Isolation Source</th>
</tr>
</thead>
<tbody id="tbody">
</tbody>
<tfoot>
<tr>
<th>Genome</th>
<th>Stain (NCBI)</th>
<th>Phylo Group</th>
<th>%GC</th>
<th>Country</th>
<th>Broad Context</th>
<th>Local Context</th>
<th>Extra Context</th>
<th>Isolation Source</th>
</tr>
</tfoot>
</table>
<script>
var genomes_table = null;

$(document).ready(async function () {
genomes_table = $('#genomes_table').DataTable({
ajax: {
url: '{% url "search_genomes_json" %}?q={{ q }}',
dataSrc: 'results',
},
autoWidth: true,
fixedColumns: true,
scrollX: true,
lengthMenu: [
[10, 20, 30, 40, 50, 100, -1],
[10, 20, 30, 40, 50, 100, 'All']
],
order: [],
columns: [
{
data: '1',
render: function (data, type, row) {
return '<a href="{% url 'genome_info' %}?species=' + encodeURIComponent(row[0]) + '&genome_id=' + encodeURIComponent(data) + '">' + data + '</a>';
},
className: 'genome_id',
},
{
data: '2',
className: 'ncbi_strain',
},
{
data: '3',
},
{
data: '4',
render: function (data, type, row) {
return (data*100).toFixed(2)
},
},
{
data: '5',
render: function (data, type, row) {
if (data == "?") {
return "-"
} else {
return data
}
},
className: 'country',
},
{
data: '6',
className: 'broad_context',
render: function (data, type, row) {
if (data == "?" || data == "Missing" || data == "missing") {
return "-"
} else {
return data
}
},
},
{
data: '7',
className: 'local_context',
render: function (data, type, row) {
if (data == "?" || data == "Missing" || data == "missing") {
return "-"
} else {
return data
}
},
},
{
data: '8',
className: 'extra_context',
render: function (data, type, row) {
if (data == "?" || data == "Missing" || data == "missing") {
return "-"
} else {
return data
}
},
},
{
data: '9',
className: 'isolation_source',
render: function (data, type, row) {
if (data == "?" || data == "Missing" || data == "missing") {
return "-"
} else {
return data
}
},
}
],
language: {
loadingRecords: '<span class="spinner-border" aria-hidden="true"></span>',
zeroRecords: 'Result not found.'
},
initComplete: function () {
this.api()
.columns()
.every(function () {
var column = this;
var title = column.footer().textContent;

// Create input element and add event listener
$('<input type="text" class="form-control" placeholder="' + title + '" />')
.appendTo($(column.footer()).empty())
.on('keyup change clear', function () {
if (column.search() !== this.value) {
column.search(this.value).draw();
}
});
});

let queryString = window.location.search;
let urlParams = new URLSearchParams(queryString);
let q = urlParams.get('q')
highlightText(q);
}
});

genomes_table.on("draw", function () {
let queryString = window.location.search;
let urlParams = new URLSearchParams(queryString);
let q = urlParams.get('q')
highlightText(q);
});
genomes_table.on('xhr.dt', function (e, settings, json, xhr) {
let genomesFound = json.results.length > 0;
update_results_overview_with_genomes(genomesFound);
});

// Expand row on click
$('#genomes_table tbody').on('click', 'td', function () {
const row = $(this).closest('tr');
row.toggleClass('expanded-row');
});
});
</script>
{% endblock %}

0 comments on commit a6535e9

Please sign in to comment.