aboutsummaryrefslogtreecommitdiff
path: root/uploader
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2025-01-27 15:58:42 -0600
committerFrederick Muriuki Muriithi2025-01-27 16:04:00 -0600
commitb2a5013e192c2116442253bf274f80fa2ae8dd58 (patch)
tree1c130138aca1a4a6b92bf7b78c94d30a2b66fabe /uploader
parent046cf17b9981fc1e75c35acf547cb143a52dc9d9 (diff)
downloadgn-uploader-b2a5013e192c2116442253bf274f80fa2ae8dd58.tar.gz
Enable editing of a phenotype's numeric values.
Diffstat (limited to 'uploader')
-rw-r--r--uploader/phenotypes/views.py115
-rw-r--r--uploader/templates/phenotypes/edit-phenotype.html41
2 files changed, 135 insertions, 21 deletions
diff --git a/uploader/phenotypes/views.py b/uploader/phenotypes/views.py
index b53b296..d283e47 100644
--- a/uploader/phenotypes/views.py
+++ b/uploader/phenotypes/views.py
@@ -622,6 +622,88 @@ def update_phenotype_metadata(conn, metadata: dict):
return cursor.rowcount
+def update_phenotype_values(conn, values):
+ with conn.cursor() as cursor:
+ cursor.executemany(
+ "UPDATE PublishData SET value=%(new)s "
+ "WHERE Id=%(data_id)s AND StrainId=%(strain_id)s",
+ tuple(item for item in values if item["new"] is not None))
+ cursor.executemany(
+ "DELETE FROM PublishData "
+ "WHERE Id=%(data_id)s AND StrainId=%(strain_id)s",
+ tuple(item for item in values if item["new"] is None))
+ return len(values)
+ return 0
+
+
+def update_phenotype_se(conn, serrs):
+ with conn.cursor() as cursor:
+ cursor.executemany(
+ "INSERT INTO PublishSE(DataId, StrainId, error) "
+ "VALUES(%(data_id)s, %(strain_id)s, %(new)s) "
+ "ON DUPLICATE KEY UPDATE error=VALUES(error)",
+ tuple(item for item in serrs if item["new"] is not None))
+ cursor.executemany(
+ "DELETE FROM PublishSE "
+ "WHERE DataId=%(data_id)s AND StrainId=%(strain_id)s",
+ tuple(item for item in serrs if item["new"] is None))
+ return len(serrs)
+ return 0
+
+
+def update_phenotype_n(conn, counts):
+ with conn.cursor() as cursor:
+ cursor.executemany(
+ "INSERT INTO NStrain(DataId, StrainId, count) "
+ "VALUES(%(data_id)s, %(strain_id)s, %(new)s) "
+ "ON DUPLICATE KEY UPDATE count=VALUES(count)",
+ tuple(item for item in counts if item["new"] is not None))
+ cursor.executemany(
+ "DELETE FROM NStrain "
+ "WHERE DataId=%(data_id)s AND StrainId=%(strain_id)s",
+ tuple(item for item in counts if item["new"] is None))
+ return len(counts)
+
+ return 0
+
+
+def update_phenotype_data(conn, data: dict):
+ """Update the numeric data for a phenotype."""
+ def __organise_by_dataid_and_strainid__(acc, current):
+ _key, dataid, strainid = current[0].split("::")
+ _keysrc, _keytype = _key.split("-")
+ newkey = f"{dataid}::{strainid}"
+ newitem = acc.get(newkey, {})
+ newitem[_keysrc] = newitem.get(_keysrc, {})
+ newitem[_keysrc][_keytype] = current[1]
+ return {**acc, newkey: newitem}
+
+ def __separate_items__(acc, row):
+ key, val = row
+ return ({**acc[0], key: {**val["value"], "changed?": (not val["value"]["new"] == val["value"]["original"])}},
+ {**acc[1], key: {**val["se"] , "changed?": (not val["se"]["new"] == val["se"]["original"])}},
+ {**acc[2], key: {**val["n"] , "changed?": (not val["n"]["new"] == val["n"]["original"])}})
+
+ values, serrs, counts = tuple(
+ tuple({
+ "data_id": row[0].split("::")[0],
+ "strain_id": row[0].split("::")[1],
+ "new": row[1]["new"]
+ } for row in item)
+ for item in (
+ filter(lambda val: val[1]["changed?"], item.items())
+ for item in reduce(
+ __separate_items__,
+ reduce(__organise_by_dataid_and_strainid__,
+ data.items(),
+ {}).items(),
+ ({}, {}, {}))))
+
+ return (update_phenotype_values(conn, values),
+ update_phenotype_se(conn, serrs),
+ update_phenotype_n(conn, counts))
+
+
@phenotypesbp.route(
"<int:species_id>/populations/<int:population_id>/phenotypes/datasets"
"/<int:dataset_id>/phenotype/<int:xref_id>/edit",
@@ -706,26 +788,41 @@ def edit_phenotype_data(
).either(__fail__, lambda args: __render__(**args))
## POST
+ _change = False
match request.form.get("submit", "invalid-action"):
case "update basic metadata":
- if update_phenotype_metadata(conn, {
+ _change = update_phenotype_metadata(conn, {
+ key: value.strip() if bool(value.strip()) else None
+ for key, value in request.form.items()
+ if key not in ("submit",)
+ })
+ msg = "Basic metadata was updated successfully."
+ case "update data":
+ _update = update_phenotype_data(conn, {
key: value.strip() if bool(value.strip()) else None
for key, value in request.form.items()
if key not in ("submit",)
- }):
- flash("Basic metadata updated.", "alert-success")
- else:
- flash("There were no changes to the basic metadata",
- "alert-info")
- case "update data":
- flash("NOT IMPLEMENTED: Would update data", "alert-success")
+ })
+ msg = (f"{_update[0]} value rows, {_update[1]} standard-error "
+ f"rows and {_update[2]} 'N' rows were updated.")
+ _change = any(item != 0 for item in _update)
case "update publication":
flash("NOT IMPLEMENTED: Would update publication data.", "alert-success")
case _:
flash("Invalid phenotype editing action.", "alert-danger")
+ if _change:
+ flash(msg, "alert-success")
+ return redirect(url_for(
+ "species.populations.phenotypes.view_phenotype",
+ species_id=species["SpeciesId"],
+ population_id=population["Id"],
+ dataset_id=dataset["Id"],
+ xref_id=xref_id))
+
+ flash("No change was made by the user.", "alert-info")
return redirect(url_for(
- "species.populations.phenotypes.view_phenotype",
+ "species.populations.phenotypes.edit_phenotype_data",
species_id=species["SpeciesId"],
population_id=population["Id"],
dataset_id=dataset["Id"],
diff --git a/uploader/templates/phenotypes/edit-phenotype.html b/uploader/templates/phenotypes/edit-phenotype.html
index ddb5aae..260d032 100644
--- a/uploader/templates/phenotypes/edit-phenotype.html
+++ b/uploader/templates/phenotypes/edit-phenotype.html
@@ -129,7 +129,15 @@
<div class="row">
<h3 class="subheading">phenotype data</h3>
- <form id="frm-edit-phenotype-data" class="form-horizontal">
+ <form id="frm-edit-phenotype-data"
+ class="form-horizontal"
+ method="POST"
+ action="{{url_for(
+ 'species.populations.phenotypes.edit_phenotype_data',
+ species_id=species.SpeciesId,
+ population_id=population.Id,
+ dataset_id=dataset.Id,
+ xref_id=xref_id)}}">
<div style="max-height: 23.37em;overflow-y: scroll;">
<table class="table table-striped table-responsive table-form-table">
<thead style="position: sticky; top: 0;">
@@ -151,22 +159,31 @@
<td>{{item.StrainName}}</td>
<td>
<input type="text"
- name="value::{{item.DataId}}::{{item.StrainId}}"
+ name="value-new::{{item.DataId}}::{{item.StrainId}}"
value="{{item.value}}"
- data-original-value="{{item.value}}"
- class="form-control" /></td>
+ class="form-control" />
+ <input type="hidden"
+ name="value-original::{{item.DataId}}::{{item.StrainId}}"
+ value="{{item.value}}" /></td>
{%if population.Family in families_with_se_and_n%}
<td>
<input type="text"
- name="se::{{item.DataId}}::{{item.StrainId}}"
+ name="se-new::{{item.DataId}}::{{item.StrainId}}"
value="{{item.error or ''}}"
data-original-value="{{item.error or ''}}"
- class="form-control" /></td>
- <td><input type="text"
- name="n::{{item.DataId}}::{{item.StrainId}}"
- value="{{item.count or ''}}"
- data-original-value="{{item.count or "-"}}"
- class="form-control" /></td>
+ class="form-control" />
+ <input type="hidden"
+ name="se-original::{{item.DataId}}::{{item.StrainId}}"
+ value="{{item.error or ''}}" /></td>
+ <td>
+ <input type="text"
+ name="n-new::{{item.DataId}}::{{item.StrainId}}"
+ value="{{item.count or ''}}"
+ data-original-value="{{item.count or "-"}}"
+ class="form-control" />
+ <input type="hidden"
+ name="n-original::{{item.DataId}}::{{item.StrainId}}"
+ value="{{item.count or ''}}" /></td>
{%endif%}
</tr>
{%endfor%}
@@ -177,7 +194,7 @@
<div class="col-sm-offset-2 col-sm-10">
<input type="submit"
name="submit"
- class="btn btn-primary not-implemented"
+ class="btn btn-primary"
value="update data" />
</div>
</div>