about summary refs log tree commit diff
path: root/uploader
diff options
context:
space:
mode:
Diffstat (limited to 'uploader')
-rw-r--r--uploader/publications/views.py29
-rw-r--r--uploader/route_utils.py20
-rw-r--r--uploader/static/css/theme.css5
-rw-r--r--uploader/templates/phenotypes/sui-view-dataset.html8
-rw-r--r--uploader/templates/publications/sui-base.html9
-rw-r--r--uploader/templates/publications/sui-create-publication.html200
-rw-r--r--uploader/templates/publications/sui-delete-publication.html95
-rw-r--r--uploader/templates/publications/sui-edit-publication.html203
-rw-r--r--uploader/templates/publications/sui-index.html109
-rw-r--r--uploader/templates/publications/sui-view-publication.html80
-rw-r--r--uploader/templates/sui-index.html200
11 files changed, 851 insertions, 107 deletions
diff --git a/uploader/publications/views.py b/uploader/publications/views.py
index 4ec832f..f0ec95a 100644
--- a/uploader/publications/views.py
+++ b/uploader/publications/views.py
@@ -10,6 +10,8 @@ from flask import (
     render_template,
     current_app as app)
 
+from uploader.sui import sui_template
+
 from uploader.flask_extensions import url_for
 from uploader.authorisation import require_login
 from uploader.route_utils import redirect_to_next
@@ -30,7 +32,7 @@ pubbp = Blueprint("publications", __name__)
 @require_login
 def index():
     """Index page for publications."""
-    return render_template("publications/index.html")
+    return render_template(sui_template("publications/index.html"))
 
 
 @pubbp.route("/list", methods=["GET"])
@@ -72,7 +74,7 @@ def view_publication(publication_id: int):
             return redirect(url_for('publications.index'))
 
         return render_template(
-            "publications/view-publication.html",
+            sui_template("publications/view-publication.html"),
             publication=publication,
             linked_phenotypes=tuple(fetch_publication_phenotypes(
                 conn, publication_id)))
@@ -82,13 +84,21 @@ def view_publication(publication_id: int):
 @require_login
 def create_publication():
     """Create a new publication."""
+    _get_args = {
+        key: request.args[key]
+        for key in ("species_id", "population_id", "dataset_id", "return_to")
+        if bool(request.args.get(key))
+    }
+
     if request.method == "GET":
-        return render_template("publications/create-publication.html")
+        return render_template(
+            sui_template("publications/create-publication.html"),
+            get_args=_get_args)
     form = request.form
     authors = form.get("publication-authors").encode("utf8")
     if authors is None or authors == "":
         flash("The publication's author(s) MUST be provided!", "alert alert-danger")
-        return redirect(url_for("publications.create", **request.args))
+        return redirect(url_for("publications.create"))
 
     with database_connection(app.config["SQL_URI"]) as conn:
         publications = create_new_publications(conn, ({
@@ -106,7 +116,7 @@ def create_publication():
         return redirect(url_for(
             request.args.get("return_to") or "publications.view_publication",
             publication_id=publications[0]["publication_id"],
-            **request.args))
+            **_get_args))
 
     flash("Publication creation failed!", "alert alert-danger")
     app.logger.debug("Failed to create the new publication.", exc_info=True)
@@ -120,7 +130,7 @@ def edit_publication(publication_id: int):
     with database_connection(app.config["SQL_URI"]) as conn:
         if request.method == "GET":
             return render_template(
-                "publications/edit-publication.html",
+                sui_template("publications/edit-publication.html"),
                 publication=fetch_publication_by_id(conn, publication_id),
                 linked_phenotypes=tuple(fetch_publication_phenotypes(
                     conn, publication_id)),
@@ -171,15 +181,16 @@ def delete_publication(publication_id: int):
             flash("Cannot delete publication with linked phenotypes!",
                   "alert-warning")
             return redirect(url_for(
-                "publications.view_publication", publication_id=publication_id))
+                sui_template("publications.view_publication"),
+                publication_id=publication_id))
 
         if request.method == "GET":
             return render_template(
-                "publications/delete-publication.html",
+                sui_template("publications/delete-publication.html"),
                 publication=publication,
                 linked_phenotypes=linked_phenotypes,
                 publication_id=publication_id)
 
         delete_publications(conn, (publication,))
         flash("Deleted the publication successfully.", "alert-success")
-        return render_template("publications/delete-publication-success.html")
+        return redirect(url_for("publications.index"))
diff --git a/uploader/route_utils.py b/uploader/route_utils.py
index 4449475..fa63233 100644
--- a/uploader/route_utils.py
+++ b/uploader/route_utils.py
@@ -56,15 +56,17 @@ def generic_select_population(
 def redirect_to_next(default: dict):
     """Redirect to the next uri if specified, else redirect to default."""
     assert "uri" in default, "You must provide at least the 'uri' value."
-    try:
-        next_page = base64_decode_to_dict(request.args.get("next"))
-        _uri = next_page["uri"]
-        next_page.pop("uri")
-        return redirect(url_for(_uri, **next_page))
-    except (TypeError, JSONDecodeError) as _err:
-        logger.debug("We could not decode the next value '%s'",
-                     next_page,
-                     exc_info=True)
+    _next = request.args.get("next")
+    if bool(_next):
+        try:
+            next_page = base64_decode_to_dict(_next)
+            _uri = next_page["uri"]
+            next_page.pop("uri")
+            return redirect(url_for(_uri, **next_page))
+        except (TypeError, JSONDecodeError) as _err:
+            logger.debug("We could not decode the next value '%s'",
+                         next_page,
+                         exc_info=True)
 
     return redirect(url_for(
         default["uri"],
diff --git a/uploader/static/css/theme.css b/uploader/static/css/theme.css
index 99b7af3..bdac745 100644
--- a/uploader/static/css/theme.css
+++ b/uploader/static/css/theme.css
@@ -83,3 +83,8 @@ table.dataTable tbody tr.selected td {
     padding-bottom: 0.2em;
     border-bottom: solid gray 1px;
 }
+
+
+.breadcrumb-item {
+    text-transform: Capitalize;
+}
diff --git a/uploader/templates/phenotypes/sui-view-dataset.html b/uploader/templates/phenotypes/sui-view-dataset.html
index 6a26004..f858c4e 100644
--- a/uploader/templates/phenotypes/sui-view-dataset.html
+++ b/uploader/templates/phenotypes/sui-view-dataset.html
@@ -53,14 +53,6 @@
        title="Add a bunch of phenotypes"
        class="btn btn-primary">Add phenotypes</a>
   </div>
-  
-  <div class="col">
-    <a href="#"
-       title="List all existing publications for this population."
-       class="btn btn-primary not-implemented">view publications</a>
-    <!-- Maybe, actually filter publications by population? -->
-    <!-- Provide other features for publications on loaded page. -->
-  </div>
 
   <div class="col">
     <form id="frm-recompute-phenotype-means"
diff --git a/uploader/templates/publications/sui-base.html b/uploader/templates/publications/sui-base.html
new file mode 100644
index 0000000..64e41ef
--- /dev/null
+++ b/uploader/templates/publications/sui-base.html
@@ -0,0 +1,9 @@
+{%extends "sui-base.html"%}
+
+{%block breadcrumbs%}
+{{super()}}
+<li class="breadcrumb-item">
+  <a href="{{url_for('publications.index')}}"
+     title="Manage publications">Publications</a>
+</li>
+{%endblock%}
diff --git a/uploader/templates/publications/sui-create-publication.html b/uploader/templates/publications/sui-create-publication.html
new file mode 100644
index 0000000..81edca6
--- /dev/null
+++ b/uploader/templates/publications/sui-create-publication.html
@@ -0,0 +1,200 @@
+{%extends "publications/sui-base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}View Publication{%endblock%}
+
+{%block breadcrumbs%}
+{{super()}}
+<li class="breadcrumb-item">
+  <a href="{{url_for('publications.create_publication', **get_args)}}"
+     title="Manage publications">create publication</a>
+</li>
+{%endblock%}
+
+
+{%block contents%}
+{{flash_all_messages()}}
+
+<div class="row">
+  <form id="frm-create-publication"
+        method="POST"
+        action="{{url_for('publications.create_publication', **get_args)}}"
+        class="form-horizontal">
+
+    <div class="row mb-3">
+      <label for="txt-pubmed-id" class="col-sm-2 col-form-label">
+        PubMed ID</label>
+      <div class="col-sm-10">
+        <div class="input-group">
+          <input type="text"
+                 id="txt-pubmed-id"
+                 name="pubmed-id"
+                 class="form-control"/>
+          <div class="input-group-text">
+            <button class="btn btn-outline-primary"
+                    id="btn-search-pubmed-id">search</button>
+          </div>
+        </div>
+        <span id="search-pubmed-id-error"
+              class="form-text text-muted text-danger visually-hidden">
+        </span>
+        <span class="form-text text-muted">This is the publication's ID on
+          <a href="https://pubmed.ncbi.nlm.nih.gov/"
+             title="Link to NCBI's PubMed service">NCBI's Pubmed Service</a>
+        </span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="txt-publication-title" class="col-sm-2 col-form-label">
+        Title</label>
+      <div class="col-sm-10">
+        <input type="text"
+               id="txt-publication-title"
+               name="publication-title"
+               class="form-control" />
+        <span class="form-text text-muted">Provide the publication's title here.</span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="txt-publication-authors" class="col-sm-2 col-form-label">
+        Authors</label>
+      <div class="col-sm-10">
+        <input type="text"
+               id="txt-publication-authors"
+               name="publication-authors"
+               required="required"
+               class="form-control" />
+        <span class="form-text text-muted">
+          A publication <strong>MUST</strong> have an author. You <em>must</em>
+          provide a value for the authors field.
+        </span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="txt-publication-journal" class="col-sm-2 col-form-label">
+        Journal</label>
+      <div class="col-sm-10">
+        <input type="text"
+               id="txt-publication-journal"
+               name="publication-journal"
+               class="form-control" />
+        <span class="form-text text-muted">Provide the name journal where the
+          publication was done, here.</span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="select-publication-month"
+             class="col-sm-2 col-form-label">
+        Month</label>
+      <div class="col-sm-4">
+        <select class="form-control"
+                id="select-publication-month"
+                name="publication-month">
+          <option value="">Select a month</option>
+          <option value="january">January</option>
+          <option value="february">February</option>
+          <option value="march">March</option>
+          <option value="april">April</option>
+          <option value="may">May</option>
+          <option value="june">June</option>
+          <option value="july">July</option>
+          <option value="august">August</option>
+          <option value="september">September</option>
+          <option value="october">October</option>
+          <option value="november">November</option>
+          <option value="december">December</option>
+        </select>
+        <span class="form-text text-muted">Month of publication</span>
+      </div>
+
+      <label for="txt-publication-year"
+             class="col-sm-2 col-form-label">
+        Year</label>
+      <div class="col-sm-4">
+        <input type="number"
+               id="txt-publication-year"
+               name="publication-year"
+               class="form-control"
+               min="1960" />
+        <span class="form-text text-muted">Year of publication</span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="txt-publication-volume"
+             class="col-sm-2 col-form-label">
+        Volume</label>
+      <div class="col-sm-4">
+        <input type="text"
+               id="txt-publication-volume"
+               name="publication-volume"
+               class="form-control">
+        <span class="form-text text-muted">Journal volume</span>
+      </div>
+
+      <label for="txt-publication-pages"
+             class="col-sm-2 col-form-label">
+        Pages</label>
+      <div class="col-sm-4">
+        <input type="text"
+               id="txt-publication-pages"
+               name="publication-pages"
+               class="form-control" />
+        <span class="form-text text-muted">Journal pages for the publication</span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="txt-abstract" class="col-sm-2 col-form-label">Abstract</label>
+      <div class="col-sm-10">
+        <textarea id="txt-publication-abstract"
+                  name="publication-abstract"
+                  class="form-control"
+                  rows="7"></textarea>
+      </div>
+    </div>
+
+    <div class="row">
+      <div class="col">
+        <input type="submit"
+               class="btn btn-primary"
+               value="create publication" />
+      </div>
+      <div class="col">
+        <input type="reset" class="btn btn-danger" value="reset form" />
+      </div>
+    </div>
+
+</form>
+</div>
+
+{%endblock%}
+
+
+{%block javascript%}
+<script type="text/javascript" src="/static/js/pubmed.js"></script>
+<script type="text/javascript">
+  $(function() {
+      $("#btn-search-pubmed-id").on("click", (event) => {
+          event.preventDefault();
+          var search_button = event.target;
+          var pubmed_id = $("#txt-pubmed-id").val().trim();
+          remove_class($("#txt-pubmed-id").parent(), "has-error");
+          if(pubmed_id == "") {
+              add_class($("#txt-pubmed-id").parent(), "has-error");
+              return false;
+          }
+
+          search_button.disabled = true;
+          // Fetch publication details
+          fetch_publication_details(pubmed_id,
+                                    [() => {search_button.disabled = false;}]);
+          return false;
+      });
+  });
+</script>
+{%endblock%}
diff --git a/uploader/templates/publications/sui-delete-publication.html b/uploader/templates/publications/sui-delete-publication.html
new file mode 100644
index 0000000..436f2c1
--- /dev/null
+++ b/uploader/templates/publications/sui-delete-publication.html
@@ -0,0 +1,95 @@
+{%extends "publications/sui-base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}Delete Publication{%endblock%}
+
+{%block breadcrumbs%}
+{{super()}}
+<li class="breadcrumb-item">
+  <a href="{{url_for('publications.delete_publication',
+           publication_id=publication.Id)}}"
+     title="Manage publications">delete publication</a>
+</li>
+{%endblock%}
+
+
+{%block contents%}
+{{flash_all_messages()}}
+<div class="row">
+  <p>You are about to delete the publication with the following details:</p>
+</div>
+
+<div class="row">
+  <table class="table">
+    <tr>
+      <th>Linked Phenotypes</th>
+      <td>{{linked_phenotypes | count}}</td>
+    </tr>
+    <tr>
+      <th>PubMed</th>
+      <td>
+        {%if publication.PubMed_ID%}
+        <a href="https://pubmed.ncbi.nlm.nih.gov/{{publication.PubMed_ID}}/"
+           target="_blank">{{publication.PubMed_ID}}</a>
+        {%else%}
+        —
+        {%endif%}
+      </td>
+    </tr>
+    <tr>
+      <th>Title</th>
+      <td>{{publication.Title or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Authors</th>
+      <td>{{publication.Authors or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Journal</th>
+      <td>{{publication.Journal or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Published</th>
+      <td>{{publication.Month or ""}} {{publication.Year or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Volume</th>
+      <td>{{publication.Volume or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Pages</th>
+      <td>{{publication.Pages or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Abstract</th>
+      <td>
+        {%for line in (publication.Abstract or "—").replace("\r\n", "<br />").replace("\n", "<br />").split("<br />")%}
+        <p>{{line}}</p>
+        {%endfor%}
+      </td>
+    </tr>
+  </table>
+</div>
+
+<div class="row">
+  <p>If you are sure that is what you want, click the button below to delete the
+    publication</p>
+  <p class="form-text text-small">
+    <small>You will not be able to recover the data if you click
+      delete below.</small></p>
+
+  <form action="{{url_for('publications.delete_publication', publication_id=publication_id)}}"
+        method="POST">
+    <div class="form-group">
+      <input type="submit" value="delete" class="btn btn-danger" />
+    </div>
+  </form>
+</div>
+{%endblock%}
+
+
+{%block javascript%}
+<script type="text/javascript">
+  $(function() {});
+</script>
+{%endblock%}
diff --git a/uploader/templates/publications/sui-edit-publication.html b/uploader/templates/publications/sui-edit-publication.html
new file mode 100644
index 0000000..847b020
--- /dev/null
+++ b/uploader/templates/publications/sui-edit-publication.html
@@ -0,0 +1,203 @@
+{%extends "publications/sui-base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}Edit Publication{%endblock%}
+
+{%block breadcrumbs%}
+{{super()}}
+<li class="breadcrumb-item">
+  <a href="{{url_for('publications.edit_publication',
+           publication_id=publication.Id)}}"
+     title="Edit the publication's details">edit publication</a>
+</li>
+{%endblock%}
+
+
+{%block contents%}
+{{flash_all_messages()}}
+
+<div class="row">
+  <form id="frm-create-publication"
+        method="POST"
+        action="{{url_for('publications.edit_publication',
+                publication_id=publication_id,
+                next=request.args.get('next', ''))}}"
+        class="form-horizontal">
+
+    <div class="row mb-3">
+      <label for="txt-pubmed-id" class="col-sm-2 col-form-label">
+        PubMed ID</label>
+      <div class="col-sm-10">
+        <div class="input-group">
+          <input type="text"
+                 id="txt-pubmed-id"
+                 name="pubmed-id"
+                 value="{{publication.PubMed_ID or ''}}"
+                 class="form-control" />
+          <div class="input-group-text">
+            <button class="btn btn-outline-primary"
+                    id="btn-search-pubmed-id">search</button>
+          </div>
+        </div>
+        <span id="search-pubmed-id-error"
+              class="form-text text-muted text-danger visually-hidden">
+        </span>
+        <span class="form-text text-muted">This is the publication's ID on
+          <a href="https://pubmed.ncbi.nlm.nih.gov/"
+             title="Link to NCBI's PubMed service">NCBI's Pubmed Service</a>
+        </span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="txt-publication-title" class="col-sm-2 col-form-label">
+        Title</label>
+      <div class="col-sm-10">
+        <input type="text"
+               id="txt-publication-title"
+               name="publication-title"
+               value="{{publication.Title}}"
+               class="form-control" />
+        <span class="form-text text-muted">Provide the publication's title here.</span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="txt-publication-authors" class="col-sm-2 col-form-label">
+        Authors</label>
+      <div class="col-sm-10">
+        <input type="text"
+               id="txt-publication-authors"
+               name="publication-authors"
+               value="{{publication.Authors}}"
+               required="required"
+               class="form-control" />
+        <span class="form-text text-muted">
+          A publication <strong>MUST</strong> have an author. You <em>must</em>
+          provide a value for the authors field.
+        </span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="txt-publication-journal" class="col-sm-2 col-form-label">
+        Journal</label>
+      <div class="col-sm-10">
+        <input type="text"
+               id="txt-publication-journal"
+               name="publication-journal"
+               value="{{publication.Journal}}"
+               class="form-control" />
+        <span class="form-text text-muted">Provide the name journal where the
+          publication was done, here.</span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="select-publication-month"
+             class="col-sm-2 col-form-label">
+        Month</label>
+      <div class="col-sm-4">
+        <select class="form-control"
+                id="select-publication-month"
+                name="publication-month">
+          <option value="">Select a month</option>
+          {%for month in ("january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"):%}
+          <option value="{{month}}"
+                  {%if publication.Month | lower == month %}
+                  selected="selected"
+                  {%endif%}>
+            {{month | title}}
+          </option>
+          {%endfor%}
+        </select>
+        <span class="form-text text-muted">Month of publication</span>
+      </div>
+
+      <label for="txt-publication-year"
+             class="col-sm-2 col-form-label">
+        Year</label>
+      <div class="col-sm-4">
+        <input type="number"
+               id="txt-publication-year"
+               name="publication-year"
+               value="{{publication.Year}}"
+               class="form-control"
+               min="1960" />
+        <span class="form-text text-muted">Year of publication</span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="txt-publication-volume"
+             class="col-sm-2 col-form-label">
+        Volume</label>
+      <div class="col-sm-4">
+        <input type="text"
+               id="txt-publication-volume"
+               name="publication-volume"
+               value="{{publication.Volume}}"
+               class="form-control">
+        <span class="form-text text-muted">Journal volume</span>
+      </div>
+
+      <label for="txt-publication-pages"
+             class="col-sm-2 col-form-label">
+        Pages</label>
+      <div class="col-sm-4">
+        <input type="text"
+               id="txt-publication-pages"
+               name="publication-pages"
+               value="{{publication.Pages}}"
+               class="form-control" />
+        <span class="form-text text-muted">Journal pages for the publication</span>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <label for="txt-abstract" class="col-sm-2 col-form-label">Abstract</label>
+      <div class="col-sm-10">
+        <textarea id="txt-publication-abstract"
+                  name="publication-abstract"
+                  class="form-control"
+                  rows="7">{{publication.Abstract or ""}}</textarea>
+      </div>
+    </div>
+
+    <div class="row mb-3">
+      <div class="col-sm-2"></div>
+      <div class="col-sm-8">
+        <input type="submit" class="btn btn-primary" value="Save" />
+        <input type="reset" class="btn btn-danger" />
+      </div>
+    </div>
+
+</form>
+</div>
+
+{%endblock%}
+
+
+{%block javascript%}
+<script type="text/javascript" src="/static/js/pubmed.js"></script>
+<script type="text/javascript">
+  $(function() {
+      $("#btn-search-pubmed-id").on("click", (event) => {
+          event.preventDefault();
+          var search_button = event.target;
+          var pubmed_id = $("#txt-pubmed-id").val().trim();
+          remove_class($("#txt-pubmed-id").parent(), "has-error");
+          if(pubmed_id == "") {
+              add_class($("#txt-pubmed-id").parent(), "has-error");
+              return false;
+          }
+
+          search_button.disabled = true;
+          // Fetch publication details
+          fetch_publication_details(pubmed_id,
+                                    [() => {search_button.disabled = false;}]);
+          return false;
+      });
+  });
+</script>
+{%endblock%}
diff --git a/uploader/templates/publications/sui-index.html b/uploader/templates/publications/sui-index.html
new file mode 100644
index 0000000..e405dd1
--- /dev/null
+++ b/uploader/templates/publications/sui-index.html
@@ -0,0 +1,109 @@
+{%extends "publications/sui-base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}Publications{%endblock%}
+
+
+{%block contents%}
+{{flash_all_messages()}}
+
+<div class="row" style="padding-bottom: 1em;">
+  <div class="col">
+    <a href="{{url_for('publications.create_publication')}}"
+       class="btn btn-primary"
+       title="Create a new publication.">
+      add new publication</a>
+  </div>
+</div>
+
+<div class="row">
+  <p>Click on title to view more details and to edit details for that
+    publication.</p>
+</div>
+
+<div class="row">
+  <table id="tbl-list-publications" class="table compact stripe">
+    <thead>
+      <tr>
+        <th>#</th>
+        <th>PubMed ID</th>
+        <th>Title</th>
+        <th>Authors</th>
+      </tr>
+    </thead>
+
+    <tbody></tbody>
+  </table>
+</div>
+{%endblock%}
+
+
+{%block javascript%}
+<script type="text/javascript" src="/static/js/urls.js"></script>
+
+<script type="text/javascript">
+  $(function() {
+      var publicationsDataTable = buildDataTable(
+          "#tbl-list-publications",
+          [],
+          [
+              {data: "index"},
+              {
+                  searchable: true,
+                  data: (pub) => {
+                      if(pub.PubMed_ID) {
+                          return `<a href="https://pubmed.ncbi.nlm.nih.gov/` +
+                              `${pub.PubMed_ID}/" target="_blank" ` +
+                              `title="Link to publication on NCBI.">` +
+                              `${pub.PubMed_ID}</a>`;
+                      }
+                      return "";
+                  }
+              },
+              {
+                  searchable: true,
+                  data: (pub) => {
+                      var title = "⸻";
+                      if(pub.Title) {
+                          title = pub.Title
+                      }
+                      url=buildURLFromCurrentURL(
+                          `/publications/view/${pub.Id}`);
+                      return `<a href="${url}" target="_blank" ` +
+                          `title="Link to view publication details">` +
+                          `${title}</a>`;
+                  }
+              },
+              {
+                  searchable: true,
+                  data: (pub) => {
+                      authors = pub.Authors.split(",").map(
+                          (item) => {return item.trim();});
+                      if(authors.length > 1) {
+                          return authors[0] + ", et. al.";
+                      }
+                      return authors[0];
+                  }
+              }
+          ],
+          {
+              serverSide: true,
+              ajax: {
+                  url: "/publications/list",
+                  dataSrc: "publications"
+              },
+              scrollY: 700,
+              scroller: true,
+              scrollCollapse: true,
+              paging: true,
+              deferRender: true,
+              layout: {
+                  topStart: "info",
+                  topEnd: "search",
+                  bottomStart: "pageLength",
+                  bottomEnd: false
+              }
+          });
+  });
+</script>
+{%endblock%}
diff --git a/uploader/templates/publications/sui-view-publication.html b/uploader/templates/publications/sui-view-publication.html
new file mode 100644
index 0000000..740fc37
--- /dev/null
+++ b/uploader/templates/publications/sui-view-publication.html
@@ -0,0 +1,80 @@
+{%extends "publications/sui-base.html"%}
+{%from "flash_messages.html" import flash_all_messages%}
+
+{%block title%}View Publication{%endblock%}
+
+
+{%block contents%}
+{{flash_all_messages()}}
+
+<div class="row">
+  <table class="table">
+    <tr>
+      <th>Linked Phenotypes</th>
+      <td>{{linked_phenotypes | count}}</td>
+    </tr>
+    <tr>
+      <th>PubMed</th>
+      <td>
+        {%if publication.PubMed_ID%}
+        <a href="https://pubmed.ncbi.nlm.nih.gov/{{publication.PubMed_ID}}/"
+           target="_blank">{{publication.PubMed_ID}}</a>
+        {%else%}
+        —
+        {%endif%}
+      </td>
+    </tr>
+    <tr>
+      <th>Title</th>
+      <td>{{publication.Title or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Authors</th>
+      <td>{{publication.Authors or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Journal</th>
+      <td>{{publication.Journal or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Published</th>
+      <td>{{publication.Month or ""}} {{publication.Year or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Volume</th>
+      <td>{{publication.Volume or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Pages</th>
+      <td>{{publication.Pages or "—"}}</td>
+    </tr>
+    <tr>
+      <th>Abstract</th>
+      <td>
+        {%for line in (publication.Abstract or "—").replace("\r\n", "<br />").replace("\n", "<br />").split("<br />")%}
+        <p>{{line}}</p>
+        {%endfor%}
+      </td>
+    </tr>
+  </table>
+</div>
+
+<div class="row">
+  <div>
+  <a href="{{url_for('publications.edit_publication', publication_id=publication.Id)}}"
+     title="Edit details for this publication."
+     class="btn btn-primary">Edit</a>
+  {%if linked_phenotypes | length == 0%}
+  <a href="{{url_for('publications.delete_publication', publication_id=publication.Id)}}"
+     class="btn btn-danger">delete</a>
+  {%endif%}
+  </div>
+</div>
+{%endblock%}
+
+
+{%block javascript%}
+<script type="text/javascript">
+  $(function() {});
+</script>
+{%endblock%}
diff --git a/uploader/templates/sui-index.html b/uploader/templates/sui-index.html
index 888823f..b93bf40 100644
--- a/uploader/templates/sui-index.html
+++ b/uploader/templates/sui-index.html
@@ -13,16 +13,16 @@
 
 {%macro add_form_buttons()%}
 <div class="row form-buttons">
-      <div class="col">
-        <input type="submit"
-               class="btn btn-primary"
-               value="use selected species" />
-      </div>
-      <div class="col">
-        <a href="{{url_for('species.create_species', return_to='base.index')}}"
-           class="btn btn-outline-primary"
-           title="Create a new species.">Create a new Species</a>
-      </div>
+  <div class="col">
+    <input type="submit"
+           class="btn btn-primary"
+           value="use selected species" />
+  </div>
+  <div class="col">
+    <a href="{{url_for('species.create_species', return_to='base.index')}}"
+       class="btn btn-outline-primary"
+       title="Create a new species.">Create a new Species</a>
+  </div>
 </div>
 {%endmacro%}
 
@@ -31,93 +31,131 @@
 {%if user_logged_in()%}
 
 <div class="row">
-  <div class="row">
-    <h2 class="heading">Species</h2>
-
-    <p>Select the species you want to work with.</p>
-  </div>
+  <ul class="nav nav-tabs" id="index-actions">
+    <li class="nav-item presentation">
+      <button class="nav-link active"
+              id="upload-data-tab"
+              data-bs-toggle="tab"
+              data-bs-target="#upload-data-content"
+              type="button"
+              role="tab"
+              aria-controls="upload-data-content"
+              aria-selected="false">Upload Data</button></li>
+    <li class="nav-item presentation">
+      <button class="nav-link"
+              id="publications-tab"
+              data-bs-toggle="tab"
+              data-bs-target="#publications-content"
+              type="button"
+              role="tab"
+              aria-controls="publications-content"
+              aria-selected="true">Publications</button></li>
+  </ul>
 </div>
 
 <div class="row">
-  <form method="GET" action="{{url_for('base.index')}}" class="form-horizontal">
-    {{add_http_feature_flags()}}
-
-    {{add_form_buttons()}}
-
-    {%if species | length != 0%}
-    <div style="margin-top:1em;">
-      <table id="tbl-select-species" class="table compact stripe"
-             data-species-list='{{species | tojson}}'>
-        <thead>
-          <tr>
-            <th></th>
-            <th>Species Name</th>
-          </tr>
-        </thead>
-
-        <tbody></tbody>
-      </table>
-    </div>
+  <div class="tab-content" id="upload-data-tabs-content">
+    <div class="tab-pane fade show active"
+         id="upload-data-content"
+         role="tabpanel"
+         aria-labelledby="upload-data-content-tab">
+      <h2 class="heading">Species</h2>
 
-    {%else%}
+      <p>Select the species you want to work with.</p>
 
-    <label class="control-label" for="rdo-cant-find-species">
-      <input id="rdo-cant-find-species" type="radio" name="species_id"
-             value="CREATE-SPECIES" />
-      There are no species to select from. Create the first one.</label>
+      <form method="GET" action="{{url_for('base.index')}}" class="form-horizontal">
+        {{add_http_feature_flags()}}
 
-    <div class="col-sm-offset-10 col-sm-2">
-      <input type="submit"
-             class="btn btn-primary col-sm-offset-1"
-             value="continue" />
-    </div>
+        {{add_form_buttons()}}
 
-    {%endif%}
+        {%if species | length != 0%}
+        <div style="margin-top:1em;">
+          <table id="tbl-select-species" class="table compact stripe"
+                 data-species-list='{{species | tojson}}'>
+            <thead>
+              <tr>
+                <th></th>
+                <th>Species Name</th>
+              </tr>
+            </thead>
 
-    {{add_form_buttons()}}
+            <tbody></tbody>
+          </table>
+        </div>
 
-  </form>
-</div>
+        {%else%}
 
-{%else%}
+        <label class="control-label" for="rdo-cant-find-species">
+          <input id="rdo-cant-find-species" type="radio" name="species_id"
+                 value="CREATE-SPECIES" />
+          There are no species to select from. Create the first one.</label>
 
-<div class="row">
-  <p>The Genenetwork Uploader (<em>gn-uploader</em>) enables upload of new data
-    into the Genenetwork System. It provides Quality Control over data, and
-    guidance in case you data does not meet the standards for acceptance.</p>
-  <p>
-    <a href="{{authserver_authorise_uri()}}"
-       title="Sign in to the system"
-       class="btn btn-primary">Sign in</a>
-    to get started.</p>
-</div>
-{%endif%}
+        <div class="col-sm-offset-10 col-sm-2">
+          <input type="submit"
+                 class="btn btn-primary col-sm-offset-1"
+                 value="continue" />
+        </div>
 
-{%endblock%}
+        {%endif%}
 
+        {{add_form_buttons()}}
 
+      </form>
+    </div>
 
-{%block sidebarcontents%}
-<div class="row">
-  <p>The data in Genenetwork is related to one species or another. Use the form
-    provided to select from existing species, or click on the
-    "Create a New Species" button if you cannot find the species you want to
-    work with.</p>
-</div>
-<div class="row">
-  <form id="frm-quick-navigation">
-    <legend>Quick Navigation</legend>
-    <div class="form-group">
-      <label for="fqn-species-id">Species</label>
-      <select name="species_id">
-        <option value="">Select species</option>
-      </select>
+    <div class="tab-pane fade"
+         id="publications-content"
+         role="tabpanel"
+         aria-labelledby="publications-content-tab">
+      <p>View, edit and delete existing publications, and add new
+        publications by clicking on the button below.</p>
+
+      <a href="{{url_for('publications.index')}}"
+         title="Manage publications."
+         class="btn btn-primary">manage publications</a>
     </div>
-  </form>
+  </div>
 </div>
-{%endblock%}
+
+  {%else%}
+
+  <div class="row">
+    <p>The Genenetwork Uploader (<em>gn-uploader</em>) enables upload of new data
+      into the Genenetwork System. It provides Quality Control over data, and
+      guidance in case you data does not meet the standards for acceptance.</p>
+    <p>
+      <a href="{{authserver_authorise_uri()}}"
+         title="Sign in to the system"
+         class="btn btn-primary">Sign in</a>
+      to get started.</p>
+  </div>
+  {%endif%}
+
+  {%endblock%}
+
+
+
+  {%block sidebarcontents%}
+  <div class="row">
+    <p>The data in Genenetwork is related to one species or another. Use the form
+      provided to select from existing species, or click on the
+      "Create a New Species" button if you cannot find the species you want to
+      work with.</p>
+  </div>
+  <div class="row">
+    <form id="frm-quick-navigation">
+      <legend>Quick Navigation</legend>
+      <div class="form-group">
+        <label for="fqn-species-id">Species</label>
+        <select name="species_id">
+          <option value="">Select species</option>
+        </select>
+      </div>
+    </form>
+  </div>
+  {%endblock%}
 
 
-{%block javascript%}
-<script type="text/javascript" src="/static/js/species.js"></script>
-{%endblock%}
+  {%block javascript%}
+  <script type="text/javascript" src="/static/js/species.js"></script>
+  {%endblock%}