diff options
Diffstat (limited to 'uploader')
-rw-r--r-- | uploader/__init__.py | 8 | ||||
-rw-r--r-- | uploader/publications/datatables.py | 52 | ||||
-rw-r--r-- | uploader/publications/models.py | 10 | ||||
-rw-r--r-- | uploader/publications/views.py | 29 | ||||
-rw-r--r-- | uploader/static/css/styles.css | 6 | ||||
-rw-r--r-- | uploader/templates/phenotypes/add-phenotypes-base.html | 43 |
6 files changed, 112 insertions, 36 deletions
diff --git a/uploader/__init__.py b/uploader/__init__.py index 97b9af5..8b49ad5 100644 --- a/uploader/__init__.py +++ b/uploader/__init__.py @@ -67,7 +67,13 @@ def setup_logging(app: Flask) -> Flask: def setup_modules_logging(app_logger): """Setup module-level loggers to the same log-level as the application.""" loglevel = logging.getLevelName(app_logger.getEffectiveLevel()) - logging.getLogger("uploader.publications.models").setLevel(loglevel) + + def __setup__(logger_name): + _logger = logging.getLogger(logger_name) + _logger.setLevel(loglevel) + + __setup__("uploader.publications.models") + __setup__("uploader.publications.datatables") def create_app(config: Optional[dict] = None): diff --git a/uploader/publications/datatables.py b/uploader/publications/datatables.py new file mode 100644 index 0000000..e07fafd --- /dev/null +++ b/uploader/publications/datatables.py @@ -0,0 +1,52 @@ +"""Fetch data for datatables.""" +import logging +from typing import Optional + +from MySQLdb.cursors import DictCursor + +from gn_libs.mysqldb import Connection, debug_query + +logger = logging.getLogger(__name__) + +def fetch_publications( + conn: Connection, + search: Optional[str] = None, + offset: int = 0, + limit: int = -1 +) -> tuple[dict, int, int, int]: + """Fetch publications from the database.""" + _query = "SELECT * FROM Publication" + _count_query = "SELECT COUNT(*) FROM Publication" + _params = None + _where_clause = "" + _limit_clause = "" + if search is not None and bool(search): + _where_clause = ("WHERE PubMed_ID LIKE %s " + "OR Authors LIKE %s " + "OR Title LIKE %s") + _params = (f"%{search}%",) * 3 + + if limit > 0: + _limit_clause = f"LIMIT {limit} OFFSET {offset}" + + with conn.cursor(cursorclass=DictCursor) as cursor: + cursor.execute("SELECT COUNT(*) FROM Publication") + _total_rows = int(cursor.fetchone()["COUNT(*)"]) + + cursor.execute(f"{_count_query} {_where_clause}", _params) + debug_query(cursor, logger) + _result = cursor.fetchone() + _total_filtered = int(_result["COUNT(*)"] if bool(_result) else 0) + + cursor.execute(f"{_query} {_where_clause} {_limit_clause}", _params) + debug_query(cursor, logger) + _current_filtered = tuple( + {**dict(row), "index": idx} + for idx, row + in enumerate(cursor.fetchall(), start=offset+1)) + + return ( + _current_filtered, + len(_current_filtered), + _total_filtered, + _total_rows) diff --git a/uploader/publications/models.py b/uploader/publications/models.py index 2b0339b..b199991 100644 --- a/uploader/publications/models.py +++ b/uploader/publications/models.py @@ -1,6 +1,6 @@ """Module to handle persistence and retrieval of publication to/from MariaDB""" import logging -from typing import Iterable +from typing import Iterable, Optional from MySQLdb.cursors import DictCursor @@ -69,14 +69,6 @@ def update_publications(conn: Connection , publications: tuple[dict, ...]) -> tu return tuple() -def fetch_publications(conn: Connection) -> Iterable[dict]: - """Fetch publications from the database.""" - with conn.cursor(cursorclass=DictCursor) as cursor: - cursor.execute("SELECT * FROM Publication") - for row in cursor.fetchall(): - yield dict(row) - - def fetch_publication_by_id(conn: Connection, publication_id: int) -> dict: """Fetch a specific publication from the database.""" with conn.cursor(cursorclass=DictCursor) as cursor: diff --git a/uploader/publications/views.py b/uploader/publications/views.py index ebb8740..0608a35 100644 --- a/uploader/publications/views.py +++ b/uploader/publications/views.py @@ -1,6 +1,7 @@ """Endpoints for publications""" import json +from MySQLdb.cursors import DictCursor from gn_libs.mysqldb import database_connection from flask import ( flash, @@ -14,11 +15,12 @@ from flask import ( from uploader.authorisation import require_login from .models import ( - fetch_publications, fetch_publication_by_id, create_new_publications, fetch_publication_phenotypes) +from .datatables import fetch_publications + from gn_libs.debug import __pk__ pubbp = Blueprint("publications", __name__) @@ -35,12 +37,25 @@ def index(): @pubbp.route("/list", methods=["GET"]) @require_login def list_publications(): - with database_connection(app.config["SQL_URI"]) as conn: + # request breakdown: + # https://datatables.net/manual/server-side + _page = int(request.args.get("draw")) + _length = int(request.args.get("length") or '-1') + _start = int(request.args.get("start") or '0') + _search = request.args["search[value]"] + with (database_connection(app.config["SQL_URI"]) as conn, + conn.cursor(cursorclass=DictCursor) as cursor): + _publications, _current_rows, _totalfiltered, _totalrows = fetch_publications( + conn, + _search, + offset=_start, + limit=_length) + return json.dumps({ - "publications": tuple({ - **row, "index": idx - } for idx,row in enumerate( - fetch_publications(conn), start=1)), + "draw": _page, + "recordsTotal": _totalrows, + "recordsFiltered": _totalfiltered, + "publications": _publications, "status": "success" }) @@ -71,7 +86,7 @@ def create_publication(): with database_connection(app.config["SQL_URI"]) as conn: publications = create_new_publications(conn, ({ - "pubmed_id": form.get("pubmed-id"), + "pubmed_id": form.get("pubmed-id") or None, "abstract": form.get("publication-abstract").encode("utf8") or None, "authors": authors, "title": form.get("publication-title").encode("utf8") or None, diff --git a/uploader/static/css/styles.css b/uploader/static/css/styles.css index 826ac41..df50dec 100644 --- a/uploader/static/css/styles.css +++ b/uploader/static/css/styles.css @@ -179,3 +179,9 @@ table.dataTable thead th, table.dataTable tfoot th{ table.dataTable tbody tr.selected td { background-color: #ffee99 !important; } + +.form-group { + margin-bottom: 2em; + padding-bottom: 0.2em; + border-bottom: solid gray 1px; +} diff --git a/uploader/templates/phenotypes/add-phenotypes-base.html b/uploader/templates/phenotypes/add-phenotypes-base.html index a7aaeb0..01cd0fe 100644 --- a/uploader/templates/phenotypes/add-phenotypes-base.html +++ b/uploader/templates/phenotypes/add-phenotypes-base.html @@ -92,48 +92,53 @@ [ {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 ""; + 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 - } - return `<a href="/publications/view/${pub.Id}" ` + + var title = "⸻"; + if(pub.Title) { + title = pub.Title + } + return `<a href="/publications/view/${pub.Id}" ` + `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]; + 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" }, select: "single", + paging: true, scrollY: 700, - paging: false, deferRender: true, + scroller: true, layout: { topStart: "info", topEnd: "search" |