about summary refs log tree commit diff
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
parent046cf17b9981fc1e75c35acf547cb143a52dc9d9 (diff)
downloadgn-uploader-b2a5013e192c2116442253bf274f80fa2ae8dd58.tar.gz
Enable editing of a phenotype's numeric values.
-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>