From 28efab6af3088c1b8f1bdabb8131c0e3d09bcb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Tue, 16 Jan 2024 13:09:17 +0100 Subject: [PATCH 001/446] added setting for https forwading --- conf/template_settings.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conf/template_settings.txt b/conf/template_settings.txt index 5f01c644d..8cf15d092 100644 --- a/conf/template_settings.txt +++ b/conf/template_settings.txt @@ -192,3 +192,6 @@ CRONTAB_COMMAND_SUFFIX = "2>&1" DEFAULT_AUTO_FIELD = "django.db.models.AutoField" DATA_UPLOAD_MAX_MEMORY_SIZE = 7000000 + +# Needed when using a proxy for https forwading +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') \ No newline at end of file From f19784188f46e555a23490481953bacd3e9aada2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Oct 2023 12:04:00 +0000 Subject: [PATCH 002/446] Bump wheel from 0.37.1 to 0.38.1 in /conf Bumps [wheel](https://github.com/pypa/wheel) from 0.37.1 to 0.38.1. - [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst) - [Commits](https://github.com/pypa/wheel/compare/0.37.1...0.38.1) --- updated-dependencies: - dependency-name: wheel dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- conf/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/requirements.txt b/conf/requirements.txt index cdea7bb12..7f2e62cac 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -1,4 +1,4 @@ -wheel==0.37.1 +wheel==0.38.1 asn1crypto==1.5.0 bcrypt==4.0.1 biopython==1.79 From 52b282d4c0736fd8e1b279aee44c90d4d71712c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Nov 2023 21:39:04 +0000 Subject: [PATCH 003/446] Bump django from 4.2 to 4.2.7 in /conf Bumps [django](https://github.com/django/django) from 4.2 to 4.2.7. - [Commits](https://github.com/django/django/compare/4.2...4.2.7) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- conf/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/requirements.txt b/conf/requirements.txt index 7f2e62cac..51e25de6b 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -3,7 +3,7 @@ asn1crypto==1.5.0 bcrypt==4.0.1 biopython==1.79 cryptography==38.0.3 -Django==4.2 +Django==4.2.7 django-crispy-forms==2.0 crispy-bootstrap5==0.7 django-crontab==0.7.1 From c088706cadf98ca3e53985d9b72116966b60f0ad Mon Sep 17 00:00:00 2001 From: luissian Date: Thu, 18 Apr 2024 13:12:27 +0200 Subject: [PATCH 004/446] Resolving conflicts --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/README.md b/README.md index d585518e6..bea4aeb23 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Application servers run web applications for bioinformatics analysis (GALAXY), t - [Create iskylims database and grant permissions](#create-iskylims-database-and-grant-permissions) - [Configuration settings](#configuration-settings) - [Run installation script](#run-installation-script) +<<<<<<< HEAD - [Upgrade to iSkyLIMS version 3.0.0](#upgrade-to-iskylims-version-300) - [Pre-requisites](#pre-requisites-1) - [Clone github repository](#clone-github-repository-1) @@ -33,6 +34,19 @@ Application servers run web applications for bioinformatics analysis (GALAXY), t - [SAMBA configurarion](#samba-configurarion) - [Email verification](#email-verification) - [Configure Apache server](#configure-apache-server) +======= + - [Configure Apache server](#configure-apache-server) + - [Installation verification](#installation-verification) + - [Upgrade to iSkyLIMS version 3.0.0](#upgrade-to-iskylims-version-300) + - [Pre-requisites](#pre-requisites-1) + - [Executing the upgrade](#executing-the-upgrade) + - [Clone github repository](#clone-github-repository-1) + - [Configuration settings](#configuration-settings-1) + - [Running upgrade script](#running-upgrade-script) + - [Final configuration steps](#final-configuration-steps) + - [SAMBA configurarion](#samba-configurarion) + - [Email verification](#email-verification) +>>>>>>> 12e92359 (readme mods) - [Verification of the installation](#verification-of-the-installation) - [iSkyLIMS documentation](#iskylims-documentation) @@ -48,7 +62,10 @@ Before starting the installation make sure : - Database MySQL > 8.0 or MariaDB > 10.4 - Local server configured for sending emails - Apache server v2.4 +<<<<<<< HEAD - git > 2.34 +======= +>>>>>>> 12e92359 (readme mods) - Python > 3.8 - Connection to samba shared folder where run folders are stored (p.e galera/NGS_Data) - Dependencies: @@ -137,6 +154,17 @@ bash install.sh --install app sudo bash install.sh --install full ``` +<<<<<<< HEAD +======= +#### Configure Apache server + +Copy the apache configuration file according to your distribution inside the apache configutation directory and rename it to iskylims.conf + +#### Installation verification + +After installation is completed and apache server is up and running open you navigator typing "localhost" or the "server local IP". + +>>>>>>> 12e92359 (readme mods) ### Upgrade to iSkyLIMS version 3.0.0 If you have already iSkyLIMS on version 2.3.0 you can upgrade to the latest stable version 3.0.0. @@ -152,10 +180,19 @@ Because in this upgrade many tables in database are modified it is required that It is highly recomended that you made these backups and keep them safely in case of upgrade failure, to recover your system. +<<<<<<< HEAD #### Clone github repository We've also change the way that iSkyLIMS is installed and upgraded. From now on iskylims is downloaded in a user folder and installed elsewhere (p.e /opt/). +======= +#### Executing the upgrade + +We've also change the way that iSkyLIMS is installed and upgraded. From now on iskylims is downloaded in a user folder and installed elsewhere (p.e /opt/). + +##### Clone github repository + +>>>>>>> 12e92359 (readme mods) Open a linux terminal and move to a directory where iSkyLIMS code will be downloaded @@ -165,7 +202,11 @@ git clone https://github.com/BU-ISCIII/iSkyLIMS.git iskylims cd iskylims ``` +<<<<<<< HEAD #### Configuration settings +======= +##### Configuration settings +>>>>>>> 12e92359 (readme mods) Copy the initial setting template into a file named install_settings.txt @@ -181,7 +222,11 @@ database ,email settings and the local IP of the server where iSkyLIMS will run. sudo nano install_settings.txt ``` +<<<<<<< HEAD #### Running upgrade script +======= +##### Running upgrade script +>>>>>>> 12e92359 (readme mods) If your organization requires that dependencies / stuff that needs root are installed by a different person that install the application the you can use the install script in several steps as follows. @@ -211,6 +256,7 @@ sudo bash install.sh --upgrade dep sudo bash install.sh --upgrade full --ren_app --script drylab_service_state_migration --script rename_app_name --script rename_sample_sheet_folder --script migrate_sample_type --script migrate_optional_values --tables ``` +<<<<<<< HEAD ##### Steps not requiring root Next you need to upgrade iskylims app. Please use the command below: @@ -243,6 +289,8 @@ mysql -u iskylims -h dmysqlps.isciiides.es mysql -u iskylims -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/bk_iSkyLIMS_202310160737.sql ``` +======= +>>>>>>> 12e92359 (readme mods) ### Final configuration steps #### SAMBA configurarion @@ -260,10 +308,13 @@ mysql -u iskylims -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/b - Go to Configuration -> Email configuration - Fill the form with the needed params for your email configuration and try to send a test email. +<<<<<<< HEAD #### Configure Apache server Copy the apache configuration file according to your distribution inside the apache configutation directory and rename it to iskylims.conf +======= +>>>>>>> 12e92359 (readme mods) #### Verification of the installation Open the navigator and type "localhost" or the "server local IP" and check that iSkyLIMs is running. From 975303886a2ca339a0ad218128a9de31af4cd297 Mon Sep 17 00:00:00 2001 From: luissian Date: Thu, 18 Apr 2024 13:14:42 +0200 Subject: [PATCH 005/446] Resolving conflicts --- README.md | 51 --------------------------------------------------- image.png | Bin 0 -> 90700 bytes 2 files changed, 51 deletions(-) create mode 100755 image.png diff --git a/README.md b/README.md index bea4aeb23..d585518e6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ Application servers run web applications for bioinformatics analysis (GALAXY), t - [Create iskylims database and grant permissions](#create-iskylims-database-and-grant-permissions) - [Configuration settings](#configuration-settings) - [Run installation script](#run-installation-script) -<<<<<<< HEAD - [Upgrade to iSkyLIMS version 3.0.0](#upgrade-to-iskylims-version-300) - [Pre-requisites](#pre-requisites-1) - [Clone github repository](#clone-github-repository-1) @@ -34,19 +33,6 @@ Application servers run web applications for bioinformatics analysis (GALAXY), t - [SAMBA configurarion](#samba-configurarion) - [Email verification](#email-verification) - [Configure Apache server](#configure-apache-server) -======= - - [Configure Apache server](#configure-apache-server) - - [Installation verification](#installation-verification) - - [Upgrade to iSkyLIMS version 3.0.0](#upgrade-to-iskylims-version-300) - - [Pre-requisites](#pre-requisites-1) - - [Executing the upgrade](#executing-the-upgrade) - - [Clone github repository](#clone-github-repository-1) - - [Configuration settings](#configuration-settings-1) - - [Running upgrade script](#running-upgrade-script) - - [Final configuration steps](#final-configuration-steps) - - [SAMBA configurarion](#samba-configurarion) - - [Email verification](#email-verification) ->>>>>>> 12e92359 (readme mods) - [Verification of the installation](#verification-of-the-installation) - [iSkyLIMS documentation](#iskylims-documentation) @@ -62,10 +48,7 @@ Before starting the installation make sure : - Database MySQL > 8.0 or MariaDB > 10.4 - Local server configured for sending emails - Apache server v2.4 -<<<<<<< HEAD - git > 2.34 -======= ->>>>>>> 12e92359 (readme mods) - Python > 3.8 - Connection to samba shared folder where run folders are stored (p.e galera/NGS_Data) - Dependencies: @@ -154,17 +137,6 @@ bash install.sh --install app sudo bash install.sh --install full ``` -<<<<<<< HEAD -======= -#### Configure Apache server - -Copy the apache configuration file according to your distribution inside the apache configutation directory and rename it to iskylims.conf - -#### Installation verification - -After installation is completed and apache server is up and running open you navigator typing "localhost" or the "server local IP". - ->>>>>>> 12e92359 (readme mods) ### Upgrade to iSkyLIMS version 3.0.0 If you have already iSkyLIMS on version 2.3.0 you can upgrade to the latest stable version 3.0.0. @@ -180,19 +152,10 @@ Because in this upgrade many tables in database are modified it is required that It is highly recomended that you made these backups and keep them safely in case of upgrade failure, to recover your system. -<<<<<<< HEAD #### Clone github repository We've also change the way that iSkyLIMS is installed and upgraded. From now on iskylims is downloaded in a user folder and installed elsewhere (p.e /opt/). -======= -#### Executing the upgrade - -We've also change the way that iSkyLIMS is installed and upgraded. From now on iskylims is downloaded in a user folder and installed elsewhere (p.e /opt/). - -##### Clone github repository - ->>>>>>> 12e92359 (readme mods) Open a linux terminal and move to a directory where iSkyLIMS code will be downloaded @@ -202,11 +165,7 @@ git clone https://github.com/BU-ISCIII/iSkyLIMS.git iskylims cd iskylims ``` -<<<<<<< HEAD #### Configuration settings -======= -##### Configuration settings ->>>>>>> 12e92359 (readme mods) Copy the initial setting template into a file named install_settings.txt @@ -222,11 +181,7 @@ database ,email settings and the local IP of the server where iSkyLIMS will run. sudo nano install_settings.txt ``` -<<<<<<< HEAD #### Running upgrade script -======= -##### Running upgrade script ->>>>>>> 12e92359 (readme mods) If your organization requires that dependencies / stuff that needs root are installed by a different person that install the application the you can use the install script in several steps as follows. @@ -256,7 +211,6 @@ sudo bash install.sh --upgrade dep sudo bash install.sh --upgrade full --ren_app --script drylab_service_state_migration --script rename_app_name --script rename_sample_sheet_folder --script migrate_sample_type --script migrate_optional_values --tables ``` -<<<<<<< HEAD ##### Steps not requiring root Next you need to upgrade iskylims app. Please use the command below: @@ -289,8 +243,6 @@ mysql -u iskylims -h dmysqlps.isciiides.es mysql -u iskylims -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/bk_iSkyLIMS_202310160737.sql ``` -======= ->>>>>>> 12e92359 (readme mods) ### Final configuration steps #### SAMBA configurarion @@ -308,13 +260,10 @@ mysql -u iskylims -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/b - Go to Configuration -> Email configuration - Fill the form with the needed params for your email configuration and try to send a test email. -<<<<<<< HEAD #### Configure Apache server Copy the apache configuration file according to your distribution inside the apache configutation directory and rename it to iskylims.conf -======= ->>>>>>> 12e92359 (readme mods) #### Verification of the installation Open the navigator and type "localhost" or the "server local IP" and check that iSkyLIMs is running. diff --git a/image.png b/image.png new file mode 100755 index 0000000000000000000000000000000000000000..cac5ffb4c285dcd40ab5e7395c64143c151889ca GIT binary patch literal 90700 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 A7XSbN literal 0 HcmV?d00001 From 9190246cc0c7ce42afec282cd453b354db3e98d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Mon, 30 Oct 2023 18:03:51 +0100 Subject: [PATCH 006/446] deleted junk --- image.png | Bin 90700 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 image.png diff --git a/image.png b/image.png deleted file mode 100755 index cac5ffb4c285dcd40ab5e7395c64143c151889ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90700 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 A7XSbN From 3abe1e38edab4f7ba56b474d67ed012f2ec275ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Tue, 7 Nov 2023 16:40:02 +0100 Subject: [PATCH 007/446] modifying readme and created leame --- LEAME.md | 108 +++++++++++++++---------------------------------------- 1 file changed, 28 insertions(+), 80 deletions(-) diff --git a/LEAME.md b/LEAME.md index c68cf701a..95df52a88 100644 --- a/LEAME.md +++ b/LEAME.md @@ -7,7 +7,7 @@ La introducción de la secuenciación masiva (MS) en las instalaciones de genómica ha significado un crecimiento exponencial en la generación de datos, lo que requiere un sistema de seguimiento preciso, desde la preparación de la biblioteca hasta la generación de archivos fastq, el análisis y la entrega al investigador. El software diseñado para manejar esas tareas se llama Sistemas de Gestión de Información de Laboratorio (LIMS), y su software debe adaptarse a las necesidades particulares de su laboratorio de genómica. iSkyLIMS nace con el objetivo de ayudar con las tareas de laboratorio húmedo e implementar un flujo de trabajo que guíe a los laboratorios de genómica en sus actividades, desde la preparación de la biblioteca hasta la producción de datos, reduciendo los posibles errores asociados a la tecnología de alto rendimiento y facilitando el control de calidad de la secuenciación. Además, iSkyLIMS conecta el laboratorio húmedo con el laboratorio seco, facilitando el análisis de datos por parte de bioinformáticos. -![Imagen](img/iskylims_scheme.png) +![Imagen](https://gitlab.isciii.es/bu-isciii/iSkyLIMS/-/blob/0f535f750035d2407d55a805aef70547d8f7f967/img/iskylims_scheme.png) De acuerdo con la infraestructura existente, la secuenciación se realiza en un instrumento Illumina NextSeq. Los datos se almacenan en un dispositivo de almacenamiento masivo NetApp y los archivos fastq se generan (bcl2fastq) en un clúster de cómputo de alto rendimiento Sun Grid Engine (SGE-HPC). Los servidores de aplicaciones ejecutan aplicaciones web para el análisis bioinformático (GALAXY), la aplicación iSkyLIMS y alojan la capa de información de MySQL. El flujo de trabajo de iSkyLIMS WetLab se ocupa del seguimiento y las estadísticas de la ejecución de la secuenciación. El seguimiento de la ejecución pasa por cinco estados: "registrado", el usuario de genómica registra la nueva ejecución de la secuenciación en el sistema, el proceso esperará hasta que la ejecución se complete en la máquina y los datos se transfieran al dispositivo de almacenamiento masivo; "Envío de hoja de muestra", el archivo de hoja de muestra con la información de la ejecución de la secuenciación se copiará en la carpeta de ejecución para el proceso de bcl2fastq; "Procesamiento de datos", se procesan los archivos de parámetros de ejecución y los datos se almacenan en la base de datos; "Estadísticas en ejecución", los datos de desmultiplexación generados en el proceso de bcl2fastq se procesan y almacenan en la base de datos, "Completado", todos los datos se procesan y almacenan correctamente. Se proporcionan estadísticas por muestra, por proyecto, por ejecución y por investigación, así como informes anuales y mensuales. El flujo de trabajo de iSkyLIMS DryLab se encarga de la solicitud de servicios de bioinformática y estadísticas. El usuario solicita servicios que pueden estar asociados con una ejecución de secuenciación. Se proporciona seguimiento de estadísticas y servicios. @@ -17,17 +17,15 @@ De acuerdo con la infraestructura existente, la secuenciación se realiza en un - [Instalación de iSkyLIMS en Docker](#instalación-de-iskylims-en-docker) - [Instalación de iSkyLIMS en su servidor con Ubuntu/CentOS](#instalación-de-iskylims-en-su-servidor-con-ubuntucentos) - [Clonar el repositorio de GitHub](#clonar-el-repositorio-de-github) - - [Crear la base de datos de iSkyLIMS y otorgar permisos](#crear-la-base-de-datos-de-iskylims-y-otorgar-permisos) - - [Configuración de ajustes](#configuración-de-ajustes) - - [Ejecutar el script de instalación](#ejecutar-el-script-de-instalación) + - [Crear la base de datos de iSkyLIMS y otorgar permisos](#crear-la-base-de-datos-de-iskylims-y-otorgar-permisos) + - [Configuración de ajustes](#configuración-de-ajustes) + - [Ejecutar el script de instalación](#ejecutar-el-script-de-instalación) - [Actualización a la versión 3.0.0 de iSkyLIMS](#actualización-a-la-versión-300-de-iskylims) - [Prerrequisitos](#prerrequisitos) - - [Clonar el repositorio de GitHub](#clonar-el-repositorio-de-github-1) - - [Configuración de opciones](#configuración-de-opciones) - - [Ejecución del script de actualización](#ejecución-del-script-de-actualización) - - [Pasos que necesitan permisos de adminsitración](#pasos-que-necesitan-permisos-de-adminsitración) - - [Pasos que no necesitan de permisos de administración](#pasos-que-no-necesitan-de-permisos-de-administración) - - [Qué hacer si algo falla](#qué-hacer-si-algo-falla) + - [Ejecutar la actualización](#ejecutar-la-actualización) + - [Clonar el repositorio de GitHub](#clonar-el-repositorio-de-github-1) + - [Configuración de opciones](#configuración-de-opciones) + - [Ejecución del script de actualización](#ejecución-del-script-de-actualización) - [Pasos finales de configuración](#pasos-finales-de-configuración) - [Configuración de SAMBA](#configuración-de-samba) - [Verificación de correo electrónico](#verificación-de-correo-electrónico) @@ -44,26 +42,15 @@ Si tienes algún problema o deseas informar de algún error, por favor, publíca Antes de comenzar la instalación, asegúrate de lo siguiente: - Tienes privilegios de **sudo** para instalar los paquetes de software adicionales que iSkyLIMS necesita. -- Dependencias: - - Librerías: -``` - yum groupinstall "Development tools" - yum install zlib-devel bzip2-devel openssl-devel \ - wget httpd-devel mysql-libs sqlite sqlite-devel \ - mariadb-devel mysql-client libffi-devel \ - gnuplot cifs-utils -``` - - lsb_relase: - - RedHat/CentOS: `yum install redhat-lsb-core` - - Ubuntu: `apt install lsb-core lsb-release` - Base de datos MySQL > 8.0 o MariaDB > 10.4 - Tienes configurado un servidor local para enviar correos electrónicos. -- git > 2.34 - Tienes Apache servidor v2.4 -- Tienes Python > 3.8 (si lo compilas debes haber instalado previamente las dependecias de arriba) +- Tienes Python > 3.8 - Tienes una conexión a la carpeta compartida de Samba donde se almacenan las carpetas de ejecución (por ejemplo, galera/NGS_Data). - Dependencias: - + - lsb_release: + - RedHat/CentOS: `yum install redhat-lsb-core` + - Ubuntu: `apt install lsb-core lsb-release` ### Instalación de iSkyLIMS en Docker @@ -100,13 +87,13 @@ git clone https://github.com/BU-ISCIII/iskylims.git iskylims cd iskylims ``` -#### Crear la base de datos de iSkyLIMS y otorgar permisos +## Crear la base de datos de iSkyLIMS y otorgar permisos 1. Cree una nueva base de datos llamada "iskylims" (esto es obligatorio). 2. Cree un nuevo usuario con permisos para leer y modificar esa base de datos. 3. Anote el nombre de usuario, la contraseña y la información del servidor de la base de datos. -#### Configuración de ajustes +## Configuración de ajustes Copia la plantilla de ajustes iniciales en un archivo llamado `install_settings.txt` @@ -120,7 +107,7 @@ Abra el archivo de configuración con su editor favorito para establecer sus pro sudo nano install_settings.txt ``` -#### Ejecutar el script de instalación +## Ejecutar el script de instalación iSkyLIMS debe instalarse en el directorio "/opt". @@ -156,21 +143,23 @@ Debido a que en esta actualización se modifican muchas tablas en la base de dat - La base de datos de iSkyLIMS. - La carpeta de iSkyLIMS (carpeta de instalación completa, por ejemplo, /opt/iSkyLIMS). -Se recomienda encarecidamente que hagas estas copias de seguridad y las guardes de manera segura en caso de que la actualización falle, para poder recuperar tu sistema. Por ejemplo crea una carpeta en `/home/dadmin/backup_pro` que contenga la base de datos y la carpeta de /opt/iskylims para tenerla a mano y poder [restaurar el sistema](#qué-hacer-si-algo-falla). +Se recomienda encarecidamente que hagas estas copias de seguridad y las guardes de manera segura en caso de que la actualización falle, para poder recuperar tu sistema. -#### Clonar el repositorio de GitHub +#### Ejecutar la actualización También hemos cambiado la forma en que se instala y actualiza iSkyLIMS. A partir de ahora, iSkyLIMS se descarga en una carpeta del usuario y se instala en otro lugar (por ejemplo, /opt/). +##### Clonar el repositorio de GitHub + Abre una terminal de Linux y dirígete a un directorio donde se descargará el código de iSkyLIMS. ```bash cd < directorio distinto al directorio de instalación > -git clone https://gitlab.isciii.es/BU-ISCIII/iskylims.git iskylims +git clone https://github.com/BU-ISCIII/iSkyLIMS.git iskylims cd iskylims ``` -#### Configuración de opciones +##### Configuración de opciones Copia la plantilla de configuración inicial en un archivo llamado install_settings.txt @@ -179,76 +168,35 @@ cp conf/template_install_settings.txt install_settings.txt ``` Abre el archivo de configuración con tu editor favorito para establecer tus propios valores para la base de datos, la configuración de correo electrónico y la dirección IP local del servidor donde se ejecutará iSkyLIMS. -> Si utilizas un sistema basado en Windows para modificar el archivo, asegúrate de que el archivo se guarde con una codificación amigable para Linux, como ASCII o UTF-8. ```bash -nano install_settings.txt +sudo nano install_settings.txt ``` -#### Ejecución del script de actualización +##### Ejecución del script de actualización Si en tu organización se requiere que las dependencias u otros elementos que necesiten permisos de administrador sean instalados por una persona diferente a la que instala la aplicación, puedes utilizar el script de instalación en varios pasos de la siguiente manera. -El script te irá solicitando confirmación en algun paso, si todo está yendo bien sin errores deberás pulsar `y` o `yes` según te lo solicite. - -> Nota: Los errores: "ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'query' at line 1" son normales ya que se trata del título de las sentencias sql que no deben ejecutarse. Se puede ignorar. - -##### Pasos que necesitan permisos de adminsitración - En primer lugar, debes cambiar el nombre de la carpeta de la aplicación en la carpeta de instalación (`/opt/iSkyLIMS`): ```bash # Necesitas ser usuario root para realizar esta operación -sudo mv /opt/iSkyLIMS /opt/iskylims +mv /opt/iSkyLIMS /opt/iskylims ``` Asegúrate de que la carpeta de instalación tenga los permisos correctos para que la persona que instala la aplicación pueda escribir en esa carpeta. -```bash -# En el caso de que tengas un script para esta tarea. Necesitarás ajustar este script de acuerdo al cambio en el nombre de la ruta: /opt/iSkyLIMS a /opt/iskylims -sudo /scripts/hardening.sh -``` - En la terminal de Linux, ejecuta uno de los siguientes comandos que mejor se adapte a ti: ```bash # para actualizar solo las dependencias del software. ES NECESARIO DISPONER DE PERMISOS DE ROOT. sudo bash install.sh --upgrade dep -# PARA INSTALAR AMBAS COSAS AL MISMO TIEMPO. REQUIERE DE ROOT. SI SE VA A INSTALAR POR OTRA PERSONA SIN ROOT NO HACER ESTO. -sudo bash install.sh --upgrade full --ren_app --script drylab_service_state_migration --script rename_app_name --script rename_sample_sheet_folder --script migrate_sample_type --script migrate_optional_values --tables -``` - -##### Pasos que no necesitan de permisos de administración - -A continuación instalamos la aplicación de iskylims usando el siguiente comando: - -```bash # para actualizar la aplicación de iskylims, incluyendo los cambios necesarios para la versión en base de datos. NO ES NECESARIO DISPONER DE PERMISOS ROOT. bash install.sh --upgrade app --ren_app --script drylab_service_state_migration --script rename_app_name --script rename_sample_sheet_folder --script migrate_sample_type --script migrate_optional_values --tables -``` - -Por último, asegúrate que los permisos de la carpeta son correctos. -```bash -# En el caso de que tengas un script para esta tarea. En esta versión han cambiado algunas rutas a ficheros, es posible que tengas que ajustar el script en consecuencia. -sudo /scripts/hardening.sh -``` - -#### Qué hacer si algo falla - -Cuando actualizamos la aplicación usando el script estamos realizando varios cambios en la base de datos. Si algo falla tenemos que restaurar el estado anterior, antes de que hubiesemos realizado ninguna acción. - -Necesitamos copiar de vuelta nuestro backup de carpet ade aplicación a /opt/iSkyLIMS (o la carpeta de instalación de nuestra elección), y restaurar la base de datos realizando algo como lo siguiente: - -```bash -sudo rm -rf /opt/iskylims -sudo cp -r /home/dadmin/backup_prod/iSkyLIMS/ /opt/ -sudo /scripts/hardening.sh -mysql -u iskylims -h dmysqlps.isciiides.es -p -# drop database iskylims; -# create database iskylims; -mysql -u iskylims -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/bk_iSkyLIMS_202310160737.sql +# PARA INSTALAR AMBAS COSAS AL MISMO TIEMPO. REQUIERE DE ROOT. +sudo bash install.sh --upgrade full --ren_app --script drylab_service_state_migration --script rename_app_name --script rename_sample_sheet_folder --script migrate_sample_type --script migrate_optional_values --tables ``` ### Pasos finales de configuración @@ -270,7 +218,7 @@ mysql -u iskylims -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/b #### Configurar el servidor Apache -Copia el archivo de configuración de Apache que se encuentra en la carpeta `conf` según tu distribución dentro del directorio de configuración de Apache y cambia el nombre a iskylims.conf. Revisa cualquier requerimiento de tu sistema, se trata solo de un ejemplo. +Copia el archivo de configuración de Apache según tu distribución dentro del directorio de configuración de Apache y cambia el nombre a iskylims.conf. #### Verificación de la instalación @@ -278,7 +226,7 @@ Abre el navegador y escribe "localhost" o la "IP local del servidor" para compro También puedes verificar algunas funcionalidades mientras compruebas las conexiones de SAMBA y la base de datos usando: -- Ve a [configurationTest](https://iskylims.isciii.es/wetlab/configurationTest/) +- Ve a [prueba de configuración](https://iskylims.isciii.es/wetlab/configurationTest/) - Haz clic en Enviar - Verifica todas las pestañas para asegurarte de que cada conexión sea exitosa. - Ejecuta las 3 pruebas para cada máquina de secuenciación: MiSeq, NextSeq y NovaSeq. From 01559742567aa9e76cb1a79c0fa9f08d953932d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Wed, 8 Nov 2023 17:36:12 +0100 Subject: [PATCH 008/446] Modifications in readme and leame, addign what to do if something fails and some clarifications --- LEAME.md | 80 ++++++++++++++++++++++++++++++++++++++++--------------- README.md | 4 +-- 2 files changed, 61 insertions(+), 23 deletions(-) diff --git a/LEAME.md b/LEAME.md index 95df52a88..7fa1d2771 100644 --- a/LEAME.md +++ b/LEAME.md @@ -17,15 +17,17 @@ De acuerdo con la infraestructura existente, la secuenciación se realiza en un - [Instalación de iSkyLIMS en Docker](#instalación-de-iskylims-en-docker) - [Instalación de iSkyLIMS en su servidor con Ubuntu/CentOS](#instalación-de-iskylims-en-su-servidor-con-ubuntucentos) - [Clonar el repositorio de GitHub](#clonar-el-repositorio-de-github) - - [Crear la base de datos de iSkyLIMS y otorgar permisos](#crear-la-base-de-datos-de-iskylims-y-otorgar-permisos) - - [Configuración de ajustes](#configuración-de-ajustes) - - [Ejecutar el script de instalación](#ejecutar-el-script-de-instalación) + - [Crear la base de datos de iSkyLIMS y otorgar permisos](#crear-la-base-de-datos-de-iskylims-y-otorgar-permisos) + - [Configuración de ajustes](#configuración-de-ajustes) + - [Ejecutar el script de instalación](#ejecutar-el-script-de-instalación) - [Actualización a la versión 3.0.0 de iSkyLIMS](#actualización-a-la-versión-300-de-iskylims) - [Prerrequisitos](#prerrequisitos) - - [Ejecutar la actualización](#ejecutar-la-actualización) - - [Clonar el repositorio de GitHub](#clonar-el-repositorio-de-github-1) - - [Configuración de opciones](#configuración-de-opciones) - - [Ejecución del script de actualización](#ejecución-del-script-de-actualización) + - [Clonar el repositorio de GitHub](#clonar-el-repositorio-de-github-1) + - [Configuración de opciones](#configuración-de-opciones) + - [Ejecución del script de actualización](#ejecución-del-script-de-actualización) + - [Pasos que necesitan permisos de adminsitración](#pasos-que-necesitan-permisos-de-adminsitración) + - [Pasos que no necesitan de permisos de administración](#pasos-que-no-necesitan-de-permisos-de-administración) + - [Qué hacer si algo falla](#qué-hacer-si-algo-falla) - [Pasos finales de configuración](#pasos-finales-de-configuración) - [Configuración de SAMBA](#configuración-de-samba) - [Verificación de correo electrónico](#verificación-de-correo-electrónico) @@ -44,6 +46,7 @@ Antes de comenzar la instalación, asegúrate de lo siguiente: - Tienes privilegios de **sudo** para instalar los paquetes de software adicionales que iSkyLIMS necesita. - Base de datos MySQL > 8.0 o MariaDB > 10.4 - Tienes configurado un servidor local para enviar correos electrónicos. +- git > 2.34 - Tienes Apache servidor v2.4 - Tienes Python > 3.8 - Tienes una conexión a la carpeta compartida de Samba donde se almacenan las carpetas de ejecución (por ejemplo, galera/NGS_Data). @@ -87,13 +90,13 @@ git clone https://github.com/BU-ISCIII/iskylims.git iskylims cd iskylims ``` -## Crear la base de datos de iSkyLIMS y otorgar permisos +#### Crear la base de datos de iSkyLIMS y otorgar permisos 1. Cree una nueva base de datos llamada "iskylims" (esto es obligatorio). 2. Cree un nuevo usuario con permisos para leer y modificar esa base de datos. 3. Anote el nombre de usuario, la contraseña y la información del servidor de la base de datos. -## Configuración de ajustes +#### Configuración de ajustes Copia la plantilla de ajustes iniciales en un archivo llamado `install_settings.txt` @@ -107,7 +110,7 @@ Abra el archivo de configuración con su editor favorito para establecer sus pro sudo nano install_settings.txt ``` -## Ejecutar el script de instalación +#### Ejecutar el script de instalación iSkyLIMS debe instalarse en el directorio "/opt". @@ -145,21 +148,19 @@ Debido a que en esta actualización se modifican muchas tablas en la base de dat Se recomienda encarecidamente que hagas estas copias de seguridad y las guardes de manera segura en caso de que la actualización falle, para poder recuperar tu sistema. -#### Ejecutar la actualización +#### Clonar el repositorio de GitHub También hemos cambiado la forma en que se instala y actualiza iSkyLIMS. A partir de ahora, iSkyLIMS se descarga en una carpeta del usuario y se instala en otro lugar (por ejemplo, /opt/). -##### Clonar el repositorio de GitHub - Abre una terminal de Linux y dirígete a un directorio donde se descargará el código de iSkyLIMS. ```bash cd < directorio distinto al directorio de instalación > -git clone https://github.com/BU-ISCIII/iSkyLIMS.git iskylims +git clone https://gitlab.isciii.es/BU-ISCIII/iskylims.git iskylims cd iskylims ``` -##### Configuración de opciones +#### Configuración de opciones Copia la plantilla de configuración inicial en un archivo llamado install_settings.txt @@ -168,35 +169,72 @@ cp conf/template_install_settings.txt install_settings.txt ``` Abre el archivo de configuración con tu editor favorito para establecer tus propios valores para la base de datos, la configuración de correo electrónico y la dirección IP local del servidor donde se ejecutará iSkyLIMS. +> Si utilizas un sistema basado en Windows para modificar el archivo, asegúrate de que el archivo se guarde con una codificación amigable para Linux, como ASCII o UTF-8. ```bash -sudo nano install_settings.txt +nano install_settings.txt ``` -##### Ejecución del script de actualización +#### Ejecución del script de actualización Si en tu organización se requiere que las dependencias u otros elementos que necesiten permisos de administrador sean instalados por una persona diferente a la que instala la aplicación, puedes utilizar el script de instalación en varios pasos de la siguiente manera. +##### Pasos que necesitan permisos de adminsitración + En primer lugar, debes cambiar el nombre de la carpeta de la aplicación en la carpeta de instalación (`/opt/iSkyLIMS`): ```bash # Necesitas ser usuario root para realizar esta operación -mv /opt/iSkyLIMS /opt/iskylims +sudo mv /opt/iSkyLIMS /opt/iskylims ``` Asegúrate de que la carpeta de instalación tenga los permisos correctos para que la persona que instala la aplicación pueda escribir en esa carpeta. +```bash +# En el caso de que tengas un script para esta tarea. Necesitarás ajustar este script de acuerdo al cambio en el nombre de la ruta: /opt/iSkyLIMS a /opt/iskylims +/scripts/hardening.sh +``` + En la terminal de Linux, ejecuta uno de los siguientes comandos que mejor se adapte a ti: ```bash # para actualizar solo las dependencias del software. ES NECESARIO DISPONER DE PERMISOS DE ROOT. sudo bash install.sh --upgrade dep +# PARA INSTALAR AMBAS COSAS AL MISMO TIEMPO. REQUIERE DE ROOT. +sudo bash install.sh --upgrade full --ren_app --script drylab_service_state_migration --script rename_app_name --script rename_sample_sheet_folder --script migrate_sample_type --script migrate_optional_values --tables +``` + +##### Pasos que no necesitan de permisos de administración + +A continuación instalamos la aplicación de iskylims usando el siguiente comando: + +```bash # para actualizar la aplicación de iskylims, incluyendo los cambios necesarios para la versión en base de datos. NO ES NECESARIO DISPONER DE PERMISOS ROOT. bash install.sh --upgrade app --ren_app --script drylab_service_state_migration --script rename_app_name --script rename_sample_sheet_folder --script migrate_sample_type --script migrate_optional_values --tables +``` -# PARA INSTALAR AMBAS COSAS AL MISMO TIEMPO. REQUIERE DE ROOT. -sudo bash install.sh --upgrade full --ren_app --script drylab_service_state_migration --script rename_app_name --script rename_sample_sheet_folder --script migrate_sample_type --script migrate_optional_values --tables +Por último, asegúrate que los permisos de la carpeta son correctos. + +```bash +# En el caso de que tengas un script para esta tarea. En esta versión han cambiado algunas rutas a ficheros, es posible que tengas que ajustar el script en consecuencia. +/scripts/hardening.sh +``` + +#### Qué hacer si algo falla + +Cuando actualizamos la aplicación usando el script estamos realizando varios cambios en la base de datos. Si algo falla tenemos que restaurar el estado anterior, antes de que hubiesemos realizado ninguna acción. + +Necesitamos copiar de vuelta nuestro backup de carpet ade aplicación a /opt/iSkyLIMS (o la carpeta de instalación de nuestra elección), y restaurar la base de datos realizando algo como lo siguiente: + +```bash +sudo rm -rf /opt/iskylims +sudo cp -r /home/dadmin/backup_prod/iSkyLIMS/ /opt/ +sudo /scripts/hardening.sh +mysql -u iskylims -p'1s1yL3ms$1$1' -h dmysqlps.isciiides.es +# drop database iskylims; +# create database iskylims; +mysql -u iskylims -p'1s1yL3ms$1$1' -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/bk_iSkyLIMS_202310160737.sql ``` ### Pasos finales de configuración @@ -226,7 +264,7 @@ Abre el navegador y escribe "localhost" o la "IP local del servidor" para compro También puedes verificar algunas funcionalidades mientras compruebas las conexiones de SAMBA y la base de datos usando: -- Ve a [prueba de configuración](https://iskylims.isciii.es/wetlab/configurationTest/) +- Ve a [configurationTest](https://iskylims.isciii.es/wetlab/configurationTest/) - Haz clic en Enviar - Verifica todas las pestañas para asegurarte de que cada conexión sea exitosa. - Ejecuta las 3 pruebas para cada máquina de secuenciación: MiSeq, NextSeq y NovaSeq. diff --git a/README.md b/README.md index d585518e6..71b9c79c6 100644 --- a/README.md +++ b/README.md @@ -237,10 +237,10 @@ We need to copy back the full `/opt/iSkyLIMS` folder back to `/opt` (or your ins sudo rm -rf /opt/iskylims sudo cp -r /home/dadmin/backup_prod/iSkyLIMS/ /opt/ sudo /scripts/hardening.sh -mysql -u iskylims -h dmysqlps.isciiides.es +mysql -u iskylims -p'1s1yL3ms$1$1' -h dmysqlps.isciiides.es # drop database iskylims; # create database iskylims; -mysql -u iskylims -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/bk_iSkyLIMS_202310160737.sql +mysql -u iskylims -p'1s1yL3ms$1$1' -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/bk_iSkyLIMS_202310160737.sql ``` ### Final configuration steps From 660edb9a6382e7428f72ad2c7da8a501fc453195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Wed, 8 Nov 2023 18:06:22 +0100 Subject: [PATCH 009/446] minor modifications in readmes --- LEAME.md | 10 +++++++--- README.md | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/LEAME.md b/LEAME.md index 7fa1d2771..9c10bc6c8 100644 --- a/LEAME.md +++ b/LEAME.md @@ -179,6 +179,10 @@ nano install_settings.txt Si en tu organización se requiere que las dependencias u otros elementos que necesiten permisos de administrador sean instalados por una persona diferente a la que instala la aplicación, puedes utilizar el script de instalación en varios pasos de la siguiente manera. +El script te irá solicitando confirmación en algun paso, si todo está yendo bien sin errores deberás pulsar `y` o `yes` según te lo solicite. + +El error: + ##### Pasos que necesitan permisos de adminsitración En primer lugar, debes cambiar el nombre de la carpeta de la aplicación en la carpeta de instalación (`/opt/iSkyLIMS`): @@ -207,7 +211,7 @@ sudo bash install.sh --upgrade full --ren_app --script drylab_service_state_mig ##### Pasos que no necesitan de permisos de administración -A continuación instalamos la aplicación de iskylims usando el siguiente comando: +A continuación instalamos la aplicación de iskylims usando el siguiente comando: ```bash # para actualizar la aplicación de iskylims, incluyendo los cambios necesarios para la versión en base de datos. NO ES NECESARIO DISPONER DE PERMISOS ROOT. @@ -231,10 +235,10 @@ Necesitamos copiar de vuelta nuestro backup de carpet ade aplicación a /opt/iSk sudo rm -rf /opt/iskylims sudo cp -r /home/dadmin/backup_prod/iSkyLIMS/ /opt/ sudo /scripts/hardening.sh -mysql -u iskylims -p'1s1yL3ms$1$1' -h dmysqlps.isciiides.es +mysql -u iskylims -h dmysqlps.isciiides.es # drop database iskylims; # create database iskylims; -mysql -u iskylims -p'1s1yL3ms$1$1' -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/bk_iSkyLIMS_202310160737.sql +mysql -u iskylims -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/bk_iSkyLIMS_202310160737.sql ``` ### Pasos finales de configuración diff --git a/README.md b/README.md index 71b9c79c6..d585518e6 100644 --- a/README.md +++ b/README.md @@ -237,10 +237,10 @@ We need to copy back the full `/opt/iSkyLIMS` folder back to `/opt` (or your ins sudo rm -rf /opt/iskylims sudo cp -r /home/dadmin/backup_prod/iSkyLIMS/ /opt/ sudo /scripts/hardening.sh -mysql -u iskylims -p'1s1yL3ms$1$1' -h dmysqlps.isciiides.es +mysql -u iskylims -h dmysqlps.isciiides.es # drop database iskylims; # create database iskylims; -mysql -u iskylims -p'1s1yL3ms$1$1' -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/bk_iSkyLIMS_202310160737.sql +mysql -u iskylims -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/bk_iSkyLIMS_202310160737.sql ``` ### Final configuration steps From 18e5c842c1966c943b35b16ccf5f0cae765046f9 Mon Sep 17 00:00:00 2001 From: luissian Date: Thu, 18 Apr 2024 14:39:36 +0200 Subject: [PATCH 010/446] rebase --- wetlab/utils/crontab_update_run.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wetlab/utils/crontab_update_run.py b/wetlab/utils/crontab_update_run.py index b6d4db9d8..8ad9389a1 100644 --- a/wetlab/utils/crontab_update_run.py +++ b/wetlab/utils/crontab_update_run.py @@ -647,7 +647,13 @@ def manage_run_in_sample_sent_processing_state(conn, run_process_objs): experiment_name, ) elif run_status == "cancelled": +<<<<<<< HEAD wetlab.utils.crontab_process.handling_errors_in_run(experiment_name, 34) +======= + wetlab.utils.crontab_process.handling_errors_in_run( + experiment_name, 34 + ) +>>>>>>> 9310b3d6 (fixed bug when run cancelled) string_message = experiment_name + "was cancelled on the sequencer" wetlab.utils.common.logging_warnings(string_message, True) logger.debug( From 383660222d54db1e366991b08109db6391aa548b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Thu, 9 Nov 2023 09:47:36 +0100 Subject: [PATCH 011/446] minor fix for cancelled code --- wetlab/utils/crontab_update_run.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/wetlab/utils/crontab_update_run.py b/wetlab/utils/crontab_update_run.py index 8ad9389a1..b6d4db9d8 100644 --- a/wetlab/utils/crontab_update_run.py +++ b/wetlab/utils/crontab_update_run.py @@ -647,13 +647,7 @@ def manage_run_in_sample_sent_processing_state(conn, run_process_objs): experiment_name, ) elif run_status == "cancelled": -<<<<<<< HEAD wetlab.utils.crontab_process.handling_errors_in_run(experiment_name, 34) -======= - wetlab.utils.crontab_process.handling_errors_in_run( - experiment_name, 34 - ) ->>>>>>> 9310b3d6 (fixed bug when run cancelled) string_message = experiment_name + "was cancelled on the sequencer" wetlab.utils.common.logging_warnings(string_message, True) logger.debug( From d1ffa2285f0b4ce80fb50a63a6f0bfaf120fbc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Fri, 15 Dec 2023 14:05:51 +0100 Subject: [PATCH 012/446] fixed minor stuff in LEAME --- LEAME.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/LEAME.md b/LEAME.md index 9c10bc6c8..9cfdd98be 100644 --- a/LEAME.md +++ b/LEAME.md @@ -7,7 +7,7 @@ La introducción de la secuenciación masiva (MS) en las instalaciones de genómica ha significado un crecimiento exponencial en la generación de datos, lo que requiere un sistema de seguimiento preciso, desde la preparación de la biblioteca hasta la generación de archivos fastq, el análisis y la entrega al investigador. El software diseñado para manejar esas tareas se llama Sistemas de Gestión de Información de Laboratorio (LIMS), y su software debe adaptarse a las necesidades particulares de su laboratorio de genómica. iSkyLIMS nace con el objetivo de ayudar con las tareas de laboratorio húmedo e implementar un flujo de trabajo que guíe a los laboratorios de genómica en sus actividades, desde la preparación de la biblioteca hasta la producción de datos, reduciendo los posibles errores asociados a la tecnología de alto rendimiento y facilitando el control de calidad de la secuenciación. Además, iSkyLIMS conecta el laboratorio húmedo con el laboratorio seco, facilitando el análisis de datos por parte de bioinformáticos. -![Imagen](https://gitlab.isciii.es/bu-isciii/iSkyLIMS/-/blob/0f535f750035d2407d55a805aef70547d8f7f967/img/iskylims_scheme.png) +![Imagen](img/iskylims_scheme.png) De acuerdo con la infraestructura existente, la secuenciación se realiza en un instrumento Illumina NextSeq. Los datos se almacenan en un dispositivo de almacenamiento masivo NetApp y los archivos fastq se generan (bcl2fastq) en un clúster de cómputo de alto rendimiento Sun Grid Engine (SGE-HPC). Los servidores de aplicaciones ejecutan aplicaciones web para el análisis bioinformático (GALAXY), la aplicación iSkyLIMS y alojan la capa de información de MySQL. El flujo de trabajo de iSkyLIMS WetLab se ocupa del seguimiento y las estadísticas de la ejecución de la secuenciación. El seguimiento de la ejecución pasa por cinco estados: "registrado", el usuario de genómica registra la nueva ejecución de la secuenciación en el sistema, el proceso esperará hasta que la ejecución se complete en la máquina y los datos se transfieran al dispositivo de almacenamiento masivo; "Envío de hoja de muestra", el archivo de hoja de muestra con la información de la ejecución de la secuenciación se copiará en la carpeta de ejecución para el proceso de bcl2fastq; "Procesamiento de datos", se procesan los archivos de parámetros de ejecución y los datos se almacenan en la base de datos; "Estadísticas en ejecución", los datos de desmultiplexación generados en el proceso de bcl2fastq se procesan y almacenan en la base de datos, "Completado", todos los datos se procesan y almacenan correctamente. Se proporcionan estadísticas por muestra, por proyecto, por ejecución y por investigación, así como informes anuales y mensuales. El flujo de trabajo de iSkyLIMS DryLab se encarga de la solicitud de servicios de bioinformática y estadísticas. El usuario solicita servicios que pueden estar asociados con una ejecución de secuenciación. Se proporciona seguimiento de estadísticas y servicios. @@ -181,7 +181,7 @@ Si en tu organización se requiere que las dependencias u otros elementos que ne El script te irá solicitando confirmación en algun paso, si todo está yendo bien sin errores deberás pulsar `y` o `yes` según te lo solicite. -El error: +> Nota: Los errores: "ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'query' at line 1" son normales ya que se trata del título de las sentencias sql que no deben ejecutarse. Se puede ignorar. ##### Pasos que necesitan permisos de adminsitración @@ -196,7 +196,7 @@ Asegúrate de que la carpeta de instalación tenga los permisos correctos para q ```bash # En el caso de que tengas un script para esta tarea. Necesitarás ajustar este script de acuerdo al cambio en el nombre de la ruta: /opt/iSkyLIMS a /opt/iskylims -/scripts/hardening.sh +sudo /scripts/hardening.sh ``` En la terminal de Linux, ejecuta uno de los siguientes comandos que mejor se adapte a ti: @@ -205,7 +205,7 @@ En la terminal de Linux, ejecuta uno de los siguientes comandos que mejor se ada # para actualizar solo las dependencias del software. ES NECESARIO DISPONER DE PERMISOS DE ROOT. sudo bash install.sh --upgrade dep -# PARA INSTALAR AMBAS COSAS AL MISMO TIEMPO. REQUIERE DE ROOT. +# PARA INSTALAR AMBAS COSAS AL MISMO TIEMPO. REQUIERE DE ROOT. SI SE VA A INSTALAR POR OTRA PERSONA SIN ROOT NO HACER ESTO. sudo bash install.sh --upgrade full --ren_app --script drylab_service_state_migration --script rename_app_name --script rename_sample_sheet_folder --script migrate_sample_type --script migrate_optional_values --tables ``` @@ -222,7 +222,7 @@ Por último, asegúrate que los permisos de la carpeta son correctos. ```bash # En el caso de que tengas un script para esta tarea. En esta versión han cambiado algunas rutas a ficheros, es posible que tengas que ajustar el script en consecuencia. -/scripts/hardening.sh +sudo /scripts/hardening.sh ``` #### Qué hacer si algo falla @@ -235,7 +235,7 @@ Necesitamos copiar de vuelta nuestro backup de carpet ade aplicación a /opt/iSk sudo rm -rf /opt/iskylims sudo cp -r /home/dadmin/backup_prod/iSkyLIMS/ /opt/ sudo /scripts/hardening.sh -mysql -u iskylims -h dmysqlps.isciiides.es +mysql -u iskylims -h dmysqlps.isciiides.es -p # drop database iskylims; # create database iskylims; mysql -u iskylims -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/bk_iSkyLIMS_202310160737.sql @@ -260,7 +260,7 @@ mysql -u iskylims -h dmysqlps.isciiides.es iskylims < /home/dadmin/backup_prod/b #### Configurar el servidor Apache -Copia el archivo de configuración de Apache según tu distribución dentro del directorio de configuración de Apache y cambia el nombre a iskylims.conf. +Copia el archivo de configuración de Apache que se encuentra en la carpeta `conf` según tu distribución dentro del directorio de configuración de Apache y cambia el nombre a iskylims.conf. Revisa cualquier requerimiento de tu sistema, se trata solo de un ejemplo. #### Verificación de la instalación From 85790107793550032fa7b1cd0060492abc9f3ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Mon, 18 Dec 2023 14:00:18 +0100 Subject: [PATCH 013/446] added example for backup folder --- LEAME.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LEAME.md b/LEAME.md index 9cfdd98be..2b7bba3f5 100644 --- a/LEAME.md +++ b/LEAME.md @@ -146,7 +146,7 @@ Debido a que en esta actualización se modifican muchas tablas en la base de dat - La base de datos de iSkyLIMS. - La carpeta de iSkyLIMS (carpeta de instalación completa, por ejemplo, /opt/iSkyLIMS). -Se recomienda encarecidamente que hagas estas copias de seguridad y las guardes de manera segura en caso de que la actualización falle, para poder recuperar tu sistema. +Se recomienda encarecidamente que hagas estas copias de seguridad y las guardes de manera segura en caso de que la actualización falle, para poder recuperar tu sistema. Por ejemplo crea una carpeta en `/home/dadmin/backup_pro` que contenga la base de datos y la carpeta de /opt/iskylims para tenerla a mano y poder [restaurar el sistema](#qué-hacer-si-algo-falla). #### Clonar el repositorio de GitHub From 38dc17c186aba3a81500684c269868ee1e501edf Mon Sep 17 00:00:00 2001 From: luissian Date: Tue, 16 Apr 2024 15:47:47 +0200 Subject: [PATCH 014/446] Updated python libraries of cryptography and paramiko --- conf/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/requirements.txt b/conf/requirements.txt index 51e25de6b..308fe4e3a 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -2,7 +2,7 @@ wheel==0.38.1 asn1crypto==1.5.0 bcrypt==4.0.1 biopython==1.79 -cryptography==38.0.3 +cryptography==41.0.6 Django==4.2.7 django-crispy-forms==2.0 crispy-bootstrap5==0.7 @@ -14,7 +14,7 @@ django-cleanup==7.0.0 interop>1.1.22 mod_wsgi==4.9.4 mysqlclient==2.0.3 -paramiko==3.1.0 +paramiko==3.4.0 jsonschema==4.17.3 pysmb==1.2.9.1 django_extensions==3.2.1 From eab8b44504ae1f3369d51b7e044c56a2aa780489 Mon Sep 17 00:00:00 2001 From: luissian Date: Tue, 16 Apr 2024 16:36:54 +0200 Subject: [PATCH 015/446] fixing some liting --- clinic/utils/patient.py | 47 +++++++++++----------- clinic/utils/samples.py | 48 +++++++++++------------ clinic/views.py | 45 +++++++++++---------- core/utils/commercial_kits.py | 43 ++++++++++---------- core/utils/patient_projects.py | 6 +-- core/utils/protocols.py | 18 ++++----- drylab/utils/deliveries.py | 12 +++--- drylab/utils/stats.py | 72 +++++++++++++++++----------------- 8 files changed, 144 insertions(+), 147 deletions(-) diff --git a/clinic/utils/patient.py b/clinic/utils/patient.py index 86c0303e5..c27a12cb3 100644 --- a/clinic/utils/patient.py +++ b/clinic/utils/patient.py @@ -158,10 +158,10 @@ def display_one_patient_info(p_id, app_name): for pat_project in pat_projects: project_name = pat_project.get_project_name() - patient_info["project_information"][ - project_name - ] = core.utils.patient_projects.get_project_field_values( - pat_project.get_project_id(), patient_core_obj + patient_info["project_information"][project_name] = ( + core.utils.patient_projects.get_project_field_values( + pat_project.get_project_id(), patient_core_obj + ) ) # get other projects that patient could belongs to @@ -180,9 +180,9 @@ def display_one_patient_info(p_id, app_name): clinic_samples = clinic.models.SampleRequest.objects.filter( patient_core=patient_core_obj ) - patient_info[ - "samples_heading" - ] = clinic.clinic_config.HEADING_FOR_DISPLAY_SAMPLE_DATA_IN_PATIENT_INFO + patient_info["samples_heading"] = ( + clinic.clinic_config.HEADING_FOR_DISPLAY_SAMPLE_DATA_IN_PATIENT_INFO + ) patient_info["samples_data"] = [] for clinic_sample in clinic_samples: sample_obj = clinic_sample.get_core_sample_obj() @@ -362,20 +362,20 @@ def read_batch_patient_file(batch_file): num_rows, num_cols = patient_batch_df.shape if num_cols != 5: - error_data[ - "ERROR" - ] = clinic.clinic_config.ERROR_MESSAGE_FOR_INVALID_PATIENT_BATCH_FILE + error_data["ERROR"] = ( + clinic.clinic_config.ERROR_MESSAGE_FOR_INVALID_PATIENT_BATCH_FILE + ) return error_data for field in patient_batch_heading: if field not in patient_batch_df.columns: - error_data[ - "ERROR" - ] = clinic.clinic_config.ERROR_MESSAGE_FOR_INVALID_PATIENT_BATCH_FILE + error_data["ERROR"] = ( + clinic.clinic_config.ERROR_MESSAGE_FOR_INVALID_PATIENT_BATCH_FILE + ) return error_data if num_rows == 0: - error_data[ - "ERROR" - ] = clinic.clinic_config.ERROR_MESSAGE_FOR_EMPTY_PATIENT_BATCH_FILE + error_data["ERROR"] = ( + clinic.clinic_config.ERROR_MESSAGE_FOR_EMPTY_PATIENT_BATCH_FILE + ) return error_data p_projects = set(patient_batch_df["patientProjects"]) for p_project in p_projects: @@ -384,19 +384,18 @@ def read_batch_patient_file(batch_file): if not core.models.PatientProjects.objects.filter( project_name__exact=p_project ).exists(): - error_data[ - "ERROR" - ] = clinic.clinic_config.ERROR_MESSAGE_NOT_VALID_PROJECT_IN_BATCH_FILE + [ - p_project - ] + error_data["ERROR"] = ( + clinic.clinic_config.ERROR_MESSAGE_NOT_VALID_PROJECT_IN_BATCH_FILE + + [p_project] + ) return error_data for index, row_data in patient_batch_df.iterrows(): patient_data = {} for index in range(len(clinic.clinic_config.PATIENT_BATCH_FILE_HEADING)): - patient_data[ - clinic.clinic_config.PATIENT_BATCH_FILE_HEADING[index] - ] = row_data[patient_batch_heading[index]] + patient_data[clinic.clinic_config.PATIENT_BATCH_FILE_HEADING[index]] = ( + row_data[patient_batch_heading[index]] + ) patient_batch_data.append(patient_data) return patient_batch_data diff --git a/clinic/utils/samples.py b/clinic/utils/samples.py index de71e052f..6c372ace6 100644 --- a/clinic/utils/samples.py +++ b/clinic/utils/samples.py @@ -69,13 +69,13 @@ def analyze_and_store_patient_data(user_post, user): suspicion_data ) if stored_samples: - analyze_data[ - "stored_samples_heading" - ] = clinic.clinic_config.HEADING_FOR_STORED_PATIENT_DATA + analyze_data["stored_samples_heading"] = ( + clinic.clinic_config.HEADING_FOR_STORED_PATIENT_DATA + ) if incomplete_clinic_samples_ids: - analyze_data[ - "heading" - ] = clinic.clinic_config.ADDITIONAL_HEADING_FOR_RECORDING_SAMPLES + analyze_data["heading"] = ( + clinic.clinic_config.ADDITIONAL_HEADING_FOR_RECORDING_SAMPLES + ) analyze_data["heading_length"] = len( clinic.clinic_config.ADDITIONAL_HEADING_FOR_RECORDING_SAMPLES ) @@ -175,9 +175,9 @@ def display_one_sample_info(id): sample_info["s_name"] = core_sample_obj.get_sample_name() sample_info["sample_core_info"] = clinic_sample_obj.get_sample_core_info() - sample_info[ - "sample_core_heading" - ] = clinic.clinic_config.HEADING_FOR_DISPLAY_SAMPLE_MAIN_INFORMATION + sample_info["sample_core_heading"] = ( + clinic.clinic_config.HEADING_FOR_DISPLAY_SAMPLE_MAIN_INFORMATION + ) p_main_info = clinic_sample_obj.get_patient_information() @@ -211,9 +211,9 @@ def display_one_sample_info(id): def display_sample_list(sample_c_list): display_sample_list_info = {} - display_sample_list_info[ - "heading" - ] = clinic.clinic_config.HEADING_SEARCH_LIST_SAMPLES_CLINIC + display_sample_list_info["heading"] = ( + clinic.clinic_config.HEADING_SEARCH_LIST_SAMPLES_CLINIC + ) sample_c_data = [] for sample_c in sample_c_list: sample_c_obj = clinic.models.ClinicSampleRequest.objects.get(pk__exact=sample_c) @@ -314,9 +314,9 @@ def get_clinic_samples_defined_state(user): for sample_obj in samples_obj: sample_information.append(sample_obj.get_info_for_defined_state()) samples_in_state["sample_information"] = sample_information - samples_in_state[ - "sample_heading" - ] = clinic.clinic_config.HEADING_FOR_DISPLAY_SAMPLE_DEFINED_STATE + samples_in_state["sample_heading"] = ( + clinic.clinic_config.HEADING_FOR_DISPLAY_SAMPLE_DEFINED_STATE + ) samples_in_state["length"] = len(sample_information) return samples_in_state else: @@ -346,9 +346,9 @@ def get_clinic_samples_patient_sequencing_state(user, state): sample_obj.get_info_for_patient_sequencing_state() ) samples_in_state["sample_information"] = sample_information - samples_in_state[ - "sample_heading" - ] = clinic.clinic_config.HEADING_FOR_DISPLAY_SAMPLE_PATIENT_SEQUENCING_STATE + samples_in_state["sample_heading"] = ( + clinic.clinic_config.HEADING_FOR_DISPLAY_SAMPLE_PATIENT_SEQUENCING_STATE + ) samples_in_state["length"] = len(sample_information) return samples_in_state else: @@ -388,9 +388,9 @@ def get_clinic_samples_pending_results(user, state): c_sample.append(sample_obj.get_id()) sample_information.append(c_sample) samples_in_state["sample_information"] = sample_information - samples_in_state[ - "sample_heading" - ] = clinic.clinic_config.HEADING_FOR_DISPLAY_SAMPLE_PENDING_RESULT_STATE + samples_in_state["sample_heading"] = ( + clinic.clinic_config.HEADING_FOR_DISPLAY_SAMPLE_PENDING_RESULT_STATE + ) samples_in_state["length"] = len(sample_information) return samples_in_state else: @@ -476,9 +476,9 @@ def get_samples_clinic_in_search(data_request): def prepare_patient_form(clinic_samples_ids): patient_info = {} patient_info["data"] = [] - patient_info[ - "heading" - ] = clinic.clinic_config.ADDITIONAL_HEADING_FOR_RECORDING_SAMPLES + patient_info["heading"] = ( + clinic.clinic_config.ADDITIONAL_HEADING_FOR_RECORDING_SAMPLES + ) heading_length = len(clinic.clinic_config.ADDITIONAL_HEADING_FOR_RECORDING_SAMPLES) for clinic_s_id in clinic_samples_ids: clinic_sample_obj = get_clinic_sample_obj_from_id(clinic_s_id) diff --git a/clinic/views.py b/clinic/views.py index b4789d614..eb620a0da 100644 --- a/clinic/views.py +++ b/clinic/views.py @@ -253,9 +253,9 @@ def create_sample_projects(request): def define_extraction_molecules(request): extraction_molecules = {} - extraction_molecules[ - "extract_molecule" - ] = core.utils.samples.get_sample_objs_in_state("defined", request.user) + extraction_molecules["extract_molecule"] = ( + core.utils.samples.get_sample_objs_in_state("defined", request.user) + ) if request.method == "POST" and request.POST["action"] == "continueWithMolecule": # processing the samples selected by user pass @@ -790,32 +790,31 @@ def pending_to_update(request): request.user ) - pending[ - "patient_update" - ] = clinic.utils.samples.get_clinic_samples_patient_sequencing_state( - request.user, "Patient update" + pending["patient_update"] = ( + clinic.utils.samples.get_clinic_samples_patient_sequencing_state( + request.user, "Patient update" + ) ) - pending[ - "sequencing" - ] = clinic.utils.samples.get_clinic_samples_patient_sequencing_state( - request.user, "Sequencing" + pending["sequencing"] = ( + clinic.utils.samples.get_clinic_samples_patient_sequencing_state( + request.user, "Sequencing" + ) ) - pending[ - "pending_protocol" - ] = clinic.utils.samples.get_clinic_samples_pending_results( - request.user, "Pending protocol" + pending["pending_protocol"] = ( + clinic.utils.samples.get_clinic_samples_pending_results( + request.user, "Pending protocol" + ) ) - pending[ - "pending_results" - ] = clinic.utils.samples.get_clinic_samples_pending_results( - request.user, "Pending results" + pending["pending_results"] = ( + clinic.utils.samples.get_clinic_samples_pending_results( + request.user, "Pending results" + ) ) - pending[ - "graphic_pending_samples" - ] = clinic.utils.samples.pending_clinic_samples_for_grafic(pending).render() - + pending["graphic_pending_samples"] = ( + clinic.utils.samples.pending_clinic_samples_for_grafic(pending).render() + ) return render(request, "clinic/pendingToUpdate.html", {"pending": pending}) diff --git a/core/utils/commercial_kits.py b/core/utils/commercial_kits.py index dc7ad1585..9bb0dd774 100644 --- a/core/utils/commercial_kits.py +++ b/core/utils/commercial_kits.py @@ -137,14 +137,14 @@ def get_commercial_kit_basic_data(kit_obj): kit_data = {} if kit_obj.platform_kit_obj(): kit_data["data"] = kit_obj.get_commercial_platform_basic_data() - kit_data[ - "heading" - ] = core.core_config.HEADING_FOR_NEW_SAVED_COMMERCIAL_PLATFORM_KIT + kit_data["heading"] = ( + core.core_config.HEADING_FOR_NEW_SAVED_COMMERCIAL_PLATFORM_KIT + ) else: kit_data["data"] = kit_obj.get_commercial_protocol_basic_data() - kit_data[ - "heading" - ] = core.core_config.HEADING_FOR_NEW_SAVED_COMMERCIAL_PROTOCOL_KIT + kit_data["heading"] = ( + core.core_config.HEADING_FOR_NEW_SAVED_COMMERCIAL_PROTOCOL_KIT + ) kit_data["protocol_kit"] = True return kit_data @@ -259,18 +259,18 @@ def get_user_lot_kit_data(register_user_obj=None, expired=False): ) if len(user_exp_kit_data["data"]) > 0: - user_exp_kit_data[ - "heading" - ] = core.core_config.HEADING_FOR_USER_LOT_KIT_INVENTORY + user_exp_kit_data["heading"] = ( + core.core_config.HEADING_FOR_USER_LOT_KIT_INVENTORY + ) return user_exp_kit_data def get_lot_user_commercial_kit_basic_data(kit_obj): lot_kit_data = {} lot_kit_data["data"] = kit_obj.get_basic_data() - lot_kit_data[ - "heading" - ] = core.core_config.HEADING_FOR_LOT_USER_COMMERCIAL_KIT_BASIC_DATA + lot_kit_data["heading"] = ( + core.core_config.HEADING_FOR_LOT_USER_COMMERCIAL_KIT_BASIC_DATA + ) return lot_kit_data @@ -403,9 +403,9 @@ def get_molecule_lot_kit_in_sample(sample_id): if core.models.MoleculePreparation.objects.filter( sample__pk__exact=sample_id ).exists(): - extraction_kits[ - "molecule_heading_lot_kits" - ] = core.core_config.HEADING_FOR_DISPLAY_IN_SAMPLE_INFO_USER_KIT_DATA + extraction_kits["molecule_heading_lot_kits"] = ( + core.core_config.HEADING_FOR_DISPLAY_IN_SAMPLE_INFO_USER_KIT_DATA + ) molecule_objs = core.models.MoleculePreparation.objects.filter( sample__pk__exact=sample_id ).order_by("protocol_used") @@ -433,18 +433,17 @@ def get_user_lot_kit_data_to_display(user_lot_kit_obj): """ display_data = {} display_data["lot_kit_data"] = user_lot_kit_obj.get_basic_data() - display_data[ - "lot_kit_heading" - ] = core.core_config.HEADING_FOR_LOT_USER_COMMERCIAL_KIT_BASIC_DATA + display_data["lot_kit_heading"] = ( + core.core_config.HEADING_FOR_LOT_USER_COMMERCIAL_KIT_BASIC_DATA + ) commercial_obj = user_lot_kit_obj.get_commercial_obj() commercial_data = commercial_obj.get_commercial_protocol_basic_data() commercial_data.append(commercial_obj.get_platform_name()) commercial_data.append(commercial_obj.get_cat_number()) display_data["commercial_data"] = [commercial_data] - display_data[ - "commercial_heading" - ] = core.core_config.HEADING_FOR_COMMERCIAL_KIT_BASIC_DATA - + display_data["commercial_heading"] = ( + core.core_config.HEADING_FOR_COMMERCIAL_KIT_BASIC_DATA + ) return display_data diff --git a/core/utils/patient_projects.py b/core/utils/patient_projects.py index afee7ee48..61ad821db 100644 --- a/core/utils/patient_projects.py +++ b/core/utils/patient_projects.py @@ -76,9 +76,9 @@ def get_all_project_info(proyect_id): if core.models.PatientProjectsFields.objects.filter( patient_projects_id=project_obj ).exists(): - project_data[ - "field_heading" - ] = core.core_config.HEADING_FOR_DEFINING_PROJECT_FIELDS + project_data["field_heading"] = ( + core.core_config.HEADING_FOR_DEFINING_PROJECT_FIELDS + ) project_data["project_name"] = project_obj.get_project_name() project_fields = core.models.PatientProjectsFields.objects.filter( patient_projects_id=project_obj diff --git a/core/utils/protocols.py b/core/utils/protocols.py index 8346499ca..861366bee 100644 --- a/core/utils/protocols.py +++ b/core/utils/protocols.py @@ -66,9 +66,9 @@ def define_table_for_prot_parameters(protocol_id): prot_parameters["protocol_name"] = protocol_obj.get_name() prot_parameters["protocol_id"] = protocol_id - prot_parameters[ - "heading" - ] = core.core_config.HEADING_FOR_DEFINING_PROTOCOL_PARAMETERS + prot_parameters["heading"] = ( + core.core_config.HEADING_FOR_DEFINING_PROTOCOL_PARAMETERS + ) return prot_parameters @@ -180,9 +180,9 @@ def get_all_protocol_info(protocol_id): protocol_obj = core.models.Protocols.objects.get(pk__exact=protocol_id) if core.models.ProtocolParameters.objects.filter(protocol_id=protocol_obj).exists(): - protocol_data[ - "parameter_heading" - ] = core.core_config.HEADING_FOR_DEFINING_PROTOCOL_PARAMETERS + protocol_data["parameter_heading"] = ( + core.core_config.HEADING_FOR_DEFINING_PROTOCOL_PARAMETERS + ) protocol_data["protocol_name"] = protocol_obj.get_name() protocol_parameters = core.models.ProtocolParameters.objects.filter( protocol_id=protocol_obj @@ -231,9 +231,9 @@ def get_protocol_fields(protocol_id): parameter_data.append(protocol_parameter_obj.get_parameter_protocol_id()) parameter_list.append(parameter_data) - parameters_protocol[ - "heading" - ] = core.core_config.HEADING_FOR_MODIFY_PROTOCOL_FIELDS + parameters_protocol["heading"] = ( + core.core_config.HEADING_FOR_MODIFY_PROTOCOL_FIELDS + ) parameters_protocol["protocol_id"] = protocol_id parameters_protocol["protocol_name"] = protocol_obj.get_name() parameters_protocol["fields"] = parameter_list diff --git a/drylab/utils/deliveries.py b/drylab/utils/deliveries.py index ee5432df0..86e6f1c52 100644 --- a/drylab/utils/deliveries.py +++ b/drylab/utils/deliveries.py @@ -29,15 +29,15 @@ def prepare_delivery_form(resolution_id): resolution_id, input="id" ) if resolution_obj is not None: - delivery_data_form[ - "available_services" - ] = resolution_obj.get_available_services_ids() + delivery_data_form["available_services"] = ( + resolution_obj.get_available_services_ids() + ) delivery_data_form["resolution_id"] = resolution_id delivery_data_form["resolution_number"] = resolution_obj.get_resolution_number() - delivery_data_form[ - "pipelines_data" - ] = drylab.utils.pipelines.get_pipelines_for_resolution(resolution_obj) + delivery_data_form["pipelines_data"] = ( + drylab.utils.pipelines.get_pipelines_for_resolution(resolution_obj) + ) return delivery_data_form diff --git a/drylab/utils/stats.py b/drylab/utils/stats.py index 1df1d8768..344f6afaa 100644 --- a/drylab/utils/stats.py +++ b/drylab/utils/stats.py @@ -39,9 +39,9 @@ def create_statistics_by_user(user_id, start_date=None, end_date=None): service_objs = service_objs.filter(service_user_id__pk__exact=user_id) if len(service_objs) == 0: - stats_info[ - "ERROR" - ] = drylab.config.ERROR_NO_MATCHES_FOUND_FOR_YOUR_SERVICE_SEARCH + stats_info["ERROR"] = ( + drylab.config.ERROR_NO_MATCHES_FOUND_FOR_YOUR_SERVICE_SEARCH + ) return stats_info # create table of services @@ -103,17 +103,17 @@ def create_statistics_by_user(user_id, start_date=None, end_date=None): "Percentage of services", "Research vs all", "ocean", service_user ) - stats_info[ - "research_vs_other_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", - "research_vs_other_graph", - "580", - "300", - "research_vs_other_chart", - "json", - g_data, - ).render() + stats_info["research_vs_other_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", + "research_vs_other_graph", + "580", + "300", + "research_vs_other_chart", + "json", + g_data, + ).render() + ) # getting statistics of the created services per week service_per_week = ( @@ -133,17 +133,17 @@ def create_statistics_by_user(user_id, start_date=None, end_date=None): "ocean", format_service_per_week, ) - stats_info[ - "research_service_weeks_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "research_service_weeks_graph", - "580", - "400", - "research_service_weeks_chart", - "json", - g_data, - ).render() + stats_info["research_service_weeks_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "research_service_weeks_graph", + "580", + "400", + "research_service_weeks_chart", + "json", + g_data, + ).render() + ) # create graphic for requested service on level 2 avail_serv_level_2 = drylab.models.AvailableService.objects.filter(level=2) @@ -164,16 +164,16 @@ def create_statistics_by_user(user_id, start_date=None, end_date=None): "value", ) - stats_info[ - "research_avail_services_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "research_avail_services_graph", - "580", - "400", - "research_avail_services_chart", - "json", - g_data, - ).render() + stats_info["research_avail_services_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "research_avail_services_graph", + "580", + "400", + "research_avail_services_chart", + "json", + g_data, + ).render() + ) return stats_info From 4c0ed438686780dbdfdd5f7d6764e945e1086050 Mon Sep 17 00:00:00 2001 From: luissian Date: Tue, 16 Apr 2024 16:46:32 +0200 Subject: [PATCH 016/446] Fixing litin --- clinic/utils/patient.py | 4 +- core/utils/samples.py | 96 ++++---- drylab/utils/pipelines.py | 18 +- drylab/utils/req_services.py | 44 ++-- drylab/utils/resolutions.py | 26 +-- drylab/views.py | 74 +++--- wetlab/utils/additional_kits.py | 18 +- wetlab/utils/crontab_process.py | 6 +- wetlab/utils/library.py | 50 ++-- wetlab/utils/pool.py | 12 +- wetlab/utils/reports.py | 66 +++--- wetlab/utils/sample.py | 6 +- wetlab/utils/sequencers.py | 6 +- wetlab/utils/statistics.py | 394 +++++++++++++++++--------------- wetlab/views.py | 86 +++---- 15 files changed, 462 insertions(+), 444 deletions(-) diff --git a/clinic/utils/patient.py b/clinic/utils/patient.py index c27a12cb3..eed421778 100644 --- a/clinic/utils/patient.py +++ b/clinic/utils/patient.py @@ -385,8 +385,8 @@ def read_batch_patient_file(batch_file): project_name__exact=p_project ).exists(): error_data["ERROR"] = ( - clinic.clinic_config.ERROR_MESSAGE_NOT_VALID_PROJECT_IN_BATCH_FILE + - [p_project] + clinic.clinic_config.ERROR_MESSAGE_NOT_VALID_PROJECT_IN_BATCH_FILE + + [p_project] ) return error_data diff --git a/core/utils/samples.py b/core/utils/samples.py index 0c9a28c8e..d96ab0752 100644 --- a/core/utils/samples.py +++ b/core/utils/samples.py @@ -436,13 +436,13 @@ def save_project_data(excel_data, project_info): for field in project_info["sample_project_fields"]: field_value = {} field_value["sample_id"] = sample_id - field_value[ - "sample_project_field_id" - ] = core.models.SampleProjectsFields.objects.get( - sample_projects_id__exact=core.models.SampleProjects.objects.get( - sample_project_name__exact=project_info["sample_project_name"] - ), - sample_project_field_name__exact=field["sample_project_field_name"], + field_value["sample_project_field_id"] = ( + core.models.SampleProjectsFields.objects.get( + sample_projects_id__exact=core.models.SampleProjects.objects.get( + sample_project_name__exact=project_info["sample_project_name"] + ), + sample_project_field_name__exact=field["sample_project_field_name"], + ) ) field_value["sample_project_field_value"] = sample[ field["sample_project_field_name"] @@ -521,11 +521,11 @@ def add_molecule_protocol_parameters(data, parameters): ) for param in parameters: molecule_parameter_value = {} - molecule_parameter_value[ - "molecule_parameter_id" - ] = core.models.ProtocolParameters.objects.filter( - protocol_id=prot_obj, parameter_name__iexact=param - ).last() + molecule_parameter_value["molecule_parameter_id"] = ( + core.models.ProtocolParameters.objects.filter( + protocol_id=prot_obj, parameter_name__iexact=param + ).last() + ) molecule_parameter_value["molecule_id"] = molecule_obj molecule_parameter_value["parameter_value"] = row[param] _ = core.models.MoleculeParameterValue.objects.create_molecule_parameter_value( @@ -740,9 +740,9 @@ def create_table_pending_molecules(molecule_list): .annotate(pk=F("pk")) ) if len(molecule_data["data"]) > 0: - molecule_data[ - "molecule_heading" - ] = core.core_config.HEADING_FOR_PENDING_MOLECULES + molecule_data["molecule_heading"] = ( + core.core_config.HEADING_FOR_PENDING_MOLECULES + ) return molecule_data @@ -762,9 +762,9 @@ def define_table_for_sample_project_fields(sample_project_id): pk__exact=sample_project_id ) - sample_project_data[ - "sample_project_name" - ] = sample_project_obj.get_sample_project_name() + sample_project_data["sample_project_name"] = ( + sample_project_obj.get_sample_project_name() + ) sample_project_data["sample_project_id"] = sample_project_id sample_project_data["heading"] = core.core_config.HEADING_FOR_SAMPLE_PROJECT_FIELDS return sample_project_data @@ -838,9 +838,9 @@ def get_all_sample_information(sample_id, join_values=False): sample_information["sample_name"] = sample_obj.get_sample_name() sample_information["sample_definition"] = sample_obj.get_info_for_display() - sample_information[ - "sample_definition_heading" - ] = core.core_config.HEADING_FOR_SAMPLE_DEFINITION + sample_information["sample_definition_heading"] = ( + core.core_config.HEADING_FOR_SAMPLE_DEFINITION + ) if join_values: sample_information["sample_definition_join_value"] = list( zip( @@ -860,9 +860,9 @@ def get_all_sample_information(sample_id, join_values=False): # check if molecule information exists for the sample if core.models.MoleculePreparation.objects.filter(sample=sample_obj).exists(): molecules = core.models.MoleculePreparation.objects.filter(sample=sample_obj) - sample_information[ - "molecule_definition_heading" - ] = core.core_config.HEADING_FOR_MOLECULE_DEFINITION + sample_information["molecule_definition_heading"] = ( + core.core_config.HEADING_FOR_MOLECULE_DEFINITION + ) sample_information["molecule_definition"] = [] sample_information["molecule_parameter_values"] = [] sample_information["molecule_definition_data"] = [] @@ -950,9 +950,9 @@ def get_info_to_display_sample_project(sample_project_id): ) # collect data from project info_s_project["sample_project_id"] = sample_project_id - info_s_project[ - "sample_project_name" - ] = sample_project_obj.get_sample_project_name() + info_s_project["sample_project_name"] = ( + sample_project_obj.get_sample_project_name() + ) info_s_project["main_data"] = list( zip( core.core_config.SAMPLE_PROJECT_MAIN_DATA, @@ -1058,13 +1058,13 @@ def get_parameters_sample_project(sample_project_id): parameter_data.insert(1, "") s_project_fields_list.append(parameter_data) parameters_s_project["fields"] = s_project_fields_list - parameters_s_project[ - "heading" - ] = core.core_config.HEADING_FOR_MODIFY_SAMPLE_PROJECT_FIELDS + parameters_s_project["heading"] = ( + core.core_config.HEADING_FOR_MODIFY_SAMPLE_PROJECT_FIELDS + ) parameters_s_project["sample_project_id"] = sample_project_id - parameters_s_project[ - "sample_project_name" - ] = sample_project_obj.get_sample_project_name() + parameters_s_project["sample_project_name"] = ( + sample_project_obj.get_sample_project_name() + ) # parameters_s_project["parameter_names"] = ",".join(parameter_names) # parameters_s_project["parameter_ids"] = ",".join(parameter_ids) else: @@ -1260,15 +1260,15 @@ def get_molecule_data_and_protocol_parameters(protocol_objs): for protocol_obj, mol_ids in protocol_objs.items(): prot_name = protocol_obj.get_name() mol_data_parm[prot_name] = {} - mol_data_parm[prot_name][ - "params_type" - ] = core.utils.protocols.get_protocol_parameters_and_type(protocol_obj) + mol_data_parm[prot_name]["params_type"] = ( + core.utils.protocols.get_protocol_parameters_and_type(protocol_obj) + ) mol_data_parm[prot_name][ "fix_heading" ] = core.core_config.HEADING_FOR_MOLECULE_ADDING_PARAMETERS - mol_data_parm[prot_name][ - "lot_kit" - ] = core.utils.commercial_kits.get_lot_commercial_kits(protocol_obj) + mol_data_parm[prot_name]["lot_kit"] = ( + core.utils.commercial_kits.get_lot_commercial_kits(protocol_obj) + ) mol_data_parm[prot_name]["param_heading"] = [] prot_params = core.models.ProtocolParameters.objects.filter( protocol_id=protocol_obj, parameter_used=True @@ -1511,9 +1511,9 @@ def get_table_record_molecule(samples, apps_name): """ molecule_information = {} - molecule_information[ - "headings" - ] = core.core_config.HEADING_FOR_MOLECULE_PROTOCOL_DEFINITION + molecule_information["headings"] = ( + core.core_config.HEADING_FOR_MOLECULE_PROTOCOL_DEFINITION + ) sample_code_ids = [] valid_samples = [] for sample in samples: @@ -1605,9 +1605,9 @@ def get_type_of_sample_information(sample_type_id): ) break else: - sample_type_data[ - "ERROR" - ] = core.core_config.ERROR_TYPE_OF_SAMPLE_ID_DOES_NOT_EXISTS + sample_type_data["ERROR"] = ( + core.core_config.ERROR_TYPE_OF_SAMPLE_ID_DOES_NOT_EXISTS + ) return sample_type_data @@ -1783,9 +1783,9 @@ def record_molecule_use(from_data, app_name): if core.models.MoleculeUsedFor.objects.filter( used_for__exact=from_data["moleculeUseName"] ).exists(): - molecule_use_information[ - "ERROR" - ] = core.core_config.ERROR_MOLECULE_USE_FOR_EXISTS + molecule_use_information["ERROR"] = ( + core.core_config.ERROR_MOLECULE_USE_FOR_EXISTS + ) return molecule_use_information molecule_use_data = {} molecule_use_data["usedFor"] = from_data["moleculeUseName"] diff --git a/drylab/utils/pipelines.py b/drylab/utils/pipelines.py index 1ef2f04d6..bd757d8c8 100644 --- a/drylab/utils/pipelines.py +++ b/drylab/utils/pipelines.py @@ -101,9 +101,9 @@ def get_detail_pipeline_data(pipeline_id): pipeline_obj = drylab.models.Pipelines.objects.get(pk__exact=pipeline_id) detail_pipelines_data["pipeline_name"] = pipeline_obj.get_pipeline_name() detail_pipelines_data["pipeline_basic"] = pipeline_obj.get_pipeline_basic() - detail_pipelines_data[ - "pipeline_basic_heading" - ] = drylab.config.DISPLAY_DETAIL_PIPELINE_BASIC_INFO + detail_pipelines_data["pipeline_basic_heading"] = ( + drylab.config.DISPLAY_DETAIL_PIPELINE_BASIC_INFO + ) detail_pipelines_data["pipeline_additional_data"] = zip( drylab.config.DISPLAY_DETAIL_PIPELINE_ADDITIONAL_INFO, pipeline_obj.get_pipeline_additional(), @@ -116,9 +116,9 @@ def get_detail_pipeline_data(pipeline_id): parameter_objs = drylab.models.ParameterPipeline.objects.filter( parameter_pipeline=pipeline_obj ) - detail_pipelines_data[ - "parameter_heading" - ] = drylab.config.HEADING_PARAMETER_PIPELINE + detail_pipelines_data["parameter_heading"] = ( + drylab.config.HEADING_PARAMETER_PIPELINE + ) detail_pipelines_data["parameters"] = [] for parameter_obj in parameter_objs: detail_pipelines_data["parameters"].append( @@ -187,9 +187,9 @@ def get_pipelines_for_resolution(resolution_obj): for pipeline_obj in pipeline_objs: pipeline_data["pipelines"].append(pipeline_obj.get_pipeline_info()) if len(pipeline_data["pipelines"]) > 0: - pipeline_data[ - "heading_pipelines" - ] = drylab.config.DISPLAY_PIPELINES_USED_IN_RESOLUTION + pipeline_data["heading_pipelines"] = ( + drylab.config.DISPLAY_PIPELINES_USED_IN_RESOLUTION + ) return pipeline_data diff --git a/drylab/utils/req_services.py b/drylab/utils/req_services.py index e635bee8c..4dabb68db 100644 --- a/drylab/utils/req_services.py +++ b/drylab/utils/req_services.py @@ -357,9 +357,9 @@ def get_pending_services_info(): graphic_unit_pending_services = core.fusioncharts.fusioncharts.FusionCharts( "multilevelpie", "ex2", "535", "435", "chart-2", "json", data_source ) - pending_services_graphics[ - "graphic_pending_unit_services" - ] = graphic_unit_pending_services.render() + pending_services_graphics["graphic_pending_unit_services"] = ( + graphic_unit_pending_services.render() + ) pending_services_details["graphics"] = pending_services_graphics return pending_services_details @@ -391,9 +391,9 @@ def get_user_pending_services_info(user_name): del resolution_data[4] res_in_queued.append(resolution_data) user_pending_services_details["queued"] = res_in_queued - user_pending_services_details[ - "heading_in_queued" - ] = drylab.config.HEADING_USER_PENDING_SERVICE_QUEUED + user_pending_services_details["heading_in_queued"] = ( + drylab.config.HEADING_USER_PENDING_SERVICE_QUEUED + ) if drylab.models.Resolution.objects.filter( resolution_assigned_user__username__exact=user_name, resolution_state__state_value__exact="in_progress", @@ -407,9 +407,9 @@ def get_user_pending_services_info(user_name): del resolution_data[4] res_in_progress.append(resolution_data) user_pending_services_details["in_progress"] = res_in_progress - user_pending_services_details[ - "heading_in_progress" - ] = drylab.config.HEADING_USER_PENDING_SERVICE_QUEUED + user_pending_services_details["heading_in_progress"] = ( + drylab.config.HEADING_USER_PENDING_SERVICE_QUEUED + ) return user_pending_services_details @@ -492,26 +492,26 @@ def get_service_data(request): # get samples which have sequencing data in iSkyLIMS user_sharing_list = drylab.utils.common.get_user_sharing_list(request.user) - service_data[ - "samples_data" - ] = wetlab.utils.api.wetlab_api.get_runs_projects_samples_and_dates( - user_sharing_list + service_data["samples_data"] = ( + wetlab.utils.api.wetlab_api.get_runs_projects_samples_and_dates( + user_sharing_list + ) ) if len(service_data["samples_data"]) > 0: - service_data[ - "samples_heading" - ] = drylab.config.HEADING_SELECT_SAMPLE_IN_SERVICE + service_data["samples_heading"] = ( + drylab.config.HEADING_SELECT_SAMPLE_IN_SERVICE + ) # get the samples that are only defined without sequencing data available from iSkyLIMS - service_data[ - "sample_only_recorded" - ] = core.utils.samples.get_only_recorded_samples_and_dates() + service_data["sample_only_recorded"] = ( + core.utils.samples.get_only_recorded_samples_and_dates() + ) if len(service_data["sample_only_recorded"]) > 0: - service_data[ - "sample_only_recorded_heading" - ] = drylab.config.HEADING_SELECT_ONLY_RECORDED_SAMPLE_IN_SERVICE + service_data["sample_only_recorded_heading"] = ( + drylab.config.HEADING_SELECT_ONLY_RECORDED_SAMPLE_IN_SERVICE + ) return service_data diff --git a/drylab/utils/resolutions.py b/drylab/utils/resolutions.py index a914e069f..90d8abd85 100644 --- a/drylab/utils/resolutions.py +++ b/drylab/utils/resolutions.py @@ -216,10 +216,10 @@ def create_new_resolution(resolution_data_form): .get_resolution_full_number() ) else: - resolution_data_form[ - "resolution_full_number" - ] = get_assign_resolution_full_number( - resolution_data_form["service_id"], resolution_data_form["acronym"] + resolution_data_form["resolution_full_number"] = ( + get_assign_resolution_full_number( + resolution_data_form["service_id"], resolution_data_form["acronym"] + ) ) resolution_data_form["resolution_number"] = create_resolution_number( resolution_data_form["service_id"] @@ -355,9 +355,9 @@ def prepare_form_data_add_resolution(form_data): existing_resolution = drylab.models.Resolution.objects.filter( resolution_service_id=service_obj ).last() - resolution_form_data[ - "resolution_full_number" - ] = existing_resolution.get_resolution_full_number() + resolution_form_data["resolution_full_number"] = ( + existing_resolution.get_resolution_full_number() + ) users = django.contrib.auth.models.User.objects.filter( groups__name=drylab.config.SERVICE_MANAGER ) @@ -376,12 +376,12 @@ def prepare_form_data_add_resolution(form_data): for req_service in req_available_services_with_desc: req_available_services_id.append(req_service[0]) - resolution_form_data[ - "pipelines_data" - ] = drylab.utils.pipelines.get_all_defined_pipelines(True) - resolution_form_data[ - "pipelines_heading" - ] = drylab.config.HEADING_PIPELINES_SELECTION_IN_RESOLUTION + resolution_form_data["pipelines_data"] = ( + drylab.utils.pipelines.get_all_defined_pipelines(True) + ) + resolution_form_data["pipelines_heading"] = ( + drylab.config.HEADING_PIPELINES_SELECTION_IN_RESOLUTION + ) return resolution_form_data diff --git a/drylab/views.py b/drylab/views.py index 8f5db9411..86eca1c33 100644 --- a/drylab/views.py +++ b/drylab/views.py @@ -79,9 +79,9 @@ def configuration_email(request): if request.user.username != "admin": return redirect("/wetlab") email_conf_data = core.utils.common.get_email_data() - email_conf_data[ - "EMAIL_ISKYLIMS" - ] = drylab.utils.common.get_configuration_from_database("EMAIL_FOR_NOTIFICATIONS") + email_conf_data["EMAIL_ISKYLIMS"] = ( + drylab.utils.common.get_configuration_from_database("EMAIL_FOR_NOTIFICATIONS") + ) if request.method == "POST" and (request.POST["action"] == "emailconfiguration"): result_email = core.utils.common.send_test_email(request.POST) if result_email != "OK": @@ -488,9 +488,9 @@ def search_service(request): for center in center_availables: center_list_abbr.append(center.center_abbr) services_search_list["centers"] = center_list_abbr - services_search_list[ - "states" - ] = drylab.utils.req_services.get_available_service_states(True) + services_search_list["states"] = ( + drylab.utils.req_services.get_available_service_states(True) + ) if "wetlab" in settings.INSTALLED_APPS: services_search_list["wetlab_app"] = True @@ -1174,9 +1174,9 @@ def stats_by_services_request(request): graphic_requested_services = core.fusioncharts.fusioncharts.FusionCharts( "column3d", "ex1", "525", "350", "chart-1", "json", data_source ) - services_stats_info[ - "graphic_requested_services_per_user" - ] = graphic_requested_services.render() + services_stats_info["graphic_requested_services_per_user"] = ( + graphic_requested_services.render() + ) # preparing stats for status of the services status_services = {} @@ -1201,9 +1201,9 @@ def stats_by_services_request(request): "pie3d", "ex2", "525", "350", "chart-2", "json", data_source ) ) - services_stats_info[ - "graphic_status_requested_services" - ] = graphic_status_requested_services.render() + services_stats_info["graphic_status_requested_services"] = ( + graphic_status_requested_services.render() + ) # preparing stats for request by Area user_area_dict = {} @@ -1235,9 +1235,9 @@ def stats_by_services_request(request): graphic_area_services = core.fusioncharts.fusioncharts.FusionCharts( "column3d", "ex3", "600", "350", "chart-3", "json", data_source ) - services_stats_info[ - "graphic_area_services" - ] = graphic_area_services.render() + services_stats_info["graphic_area_services"] = ( + graphic_area_services.render() + ) # preparing stats for services request by Center user_center_dict = {} @@ -1267,9 +1267,9 @@ def stats_by_services_request(request): graphic_center_services = core.fusioncharts.fusioncharts.FusionCharts( "column3d", "ex4", "600", "350", "chart-4", "json", data_source ) - services_stats_info[ - "graphic_center_services" - ] = graphic_center_services.render() + services_stats_info["graphic_center_services"] = ( + graphic_center_services.render() + ) ################################################ # Preparing the statistics per period of time @@ -1326,9 +1326,9 @@ def stats_by_services_request(request): "mscolumn3d", "ex5", "525", "350", "chart-5", "json", data_source ) ) - services_stats_info[ - "graphic_center_services_per_time" - ] = graphic_center_services_per_time.render() + services_stats_info["graphic_center_services_per_time"] = ( + graphic_center_services_per_time.render() + ) # Preparing the statistics for Area on period of time user_area_services_period = {} @@ -1376,9 +1376,9 @@ def stats_by_services_request(request): "mscolumn3d", "ex6", "525", "350", "chart-6", "json", data_source ) ) - services_stats_info[ - "graphic_area_services_per_time" - ] = graphic_area_services_per_time.render() + services_stats_info["graphic_area_services_per_time"] = ( + graphic_area_services_per_time.render() + ) services_stats_info["period_time"] = period_of_time_selected @@ -1401,9 +1401,9 @@ def stats_by_services_request(request): graphic_req_l2_services = core.fusioncharts.fusioncharts.FusionCharts( "column3d", "ex7", "800", "375", "chart-7", "json", data_source ) - services_stats_info[ - "graphic_req_l2_services" - ] = graphic_req_l2_services.render() + services_stats_info["graphic_req_l2_services"] = ( + graphic_req_l2_services.render() + ) # statistics on Requested Level 3 Services @@ -1424,9 +1424,9 @@ def stats_by_services_request(request): graphic_req_l3_services = core.fusioncharts.fusioncharts.FusionCharts( "column3d", "ex8", "800", "375", "chart-8", "json", data_source ) - services_stats_info[ - "graphic_req_l3_services" - ] = graphic_req_l3_services.render() + services_stats_info["graphic_req_l3_services"] = ( + graphic_req_l3_services.render() + ) return render( request, @@ -1489,9 +1489,9 @@ def configuration_test(request): test_results["services"] = ("Available services", "NOK") else: test_results["services"] = ("Available services", "OK") - test_results[ - "iSkyLIMS_settings" - ] = drylab.utils.test_conf.get_iSkyLIMS_settings() + test_results["iSkyLIMS_settings"] = ( + drylab.utils.test_conf.get_iSkyLIMS_settings() + ) test_results["config_file"] = drylab.utils.test_conf.get_config_file( config_file ) @@ -1528,10 +1528,10 @@ def configuration_test(request): else: resolution_results["create_service_ok"] = "OK" resolution_number = "SRVTEST-IIER001.1" - resolution_results[ - "resolution_test" - ] = drylab.utils.test_conf.create_resolution_test( - resolution_number, service_requested + resolution_results["resolution_test"] = ( + drylab.utils.test_conf.create_resolution_test( + resolution_number, service_requested + ) ) resolution_results["create_resolution_ok"] = "OK" diff --git a/wetlab/utils/additional_kits.py b/wetlab/utils/additional_kits.py index 22c71e5bb..fbcb06b6c 100644 --- a/wetlab/utils/additional_kits.py +++ b/wetlab/utils/additional_kits.py @@ -198,9 +198,9 @@ def get_additional_kits_from_lib_prep(lib_prep_ids): for user_lot_kit_obj in user_lot_kit_objs: user_lot.append(user_lot_kit_obj.get_lot_number()) additional_kits["kit_heading"].append([kit_name, user_lot]) - additional_kits[ - "fix_heading" - ] = wetlab.config.HEADING_FIX_FOR_ASSING_ADDITIONAL_KITS + additional_kits["fix_heading"] = ( + wetlab.config.HEADING_FIX_FOR_ASSING_ADDITIONAL_KITS + ) data_length = len(wetlab.config.HEADING_FIX_FOR_ASSING_ADDITIONAL_KITS) + len( additional_kits_objs ) @@ -273,9 +273,9 @@ def get_all_additional_kit_info(protocol_id): if wetlab.models.AdditionaKitsLibPrepare.objects.filter( protocol_id=protocol_obj ).exists(): - kit_info[ - "kit_heading" - ] = wetlab.config.HEADING_ADDING_COMMERCIAL_KITS_TO_PROTOCOL + kit_info["kit_heading"] = ( + wetlab.config.HEADING_ADDING_COMMERCIAL_KITS_TO_PROTOCOL + ) kit_info["kit_data"] = [] kits = wetlab.models.AdditionaKitsLibPrepare.objects.filter( protocol_id=protocol_obj @@ -321,9 +321,9 @@ def get_additional_kits_used_in_sample(sample_id): kit_data = {} kit_data["protocols_add_kits"] = {} if wetlab.models.LibPrepare.objects.filter(sample_id__pk__exact=sample_id).exists(): - kit_data[ - "heading_add_kits" - ] = wetlab.config.HEADING_FOR_DISPLAY_ADDITIONAL_KIT_LIBRARY_PREPARATION + kit_data["heading_add_kits"] = ( + wetlab.config.HEADING_FOR_DISPLAY_ADDITIONAL_KIT_LIBRARY_PREPARATION + ) library_preparation_items = wetlab.models.LibPrepare.objects.filter( sample_id__pk__exact=sample_id ).order_by("protocol_id") diff --git a/wetlab/utils/crontab_process.py b/wetlab/utils/crontab_process.py index a5ecdedd5..d61ccac60 100644 --- a/wetlab/utils/crontab_process.py +++ b/wetlab/utils/crontab_process.py @@ -1091,9 +1091,9 @@ def parsing_run_info_and_parameter_information( index_number = ( int(run_info_read.attrib[wetlab.config.NUMBER_TAG]) - 1 ) - running_data[ - wetlab.config.READ_NUMBER_OF_CYCLES[index_number] - ] = run_info_read.attrib[wetlab.config.NUMBER_CYCLES_TAG] + running_data[wetlab.config.READ_NUMBER_OF_CYCLES[index_number]] = ( + run_info_read.attrib[wetlab.config.NUMBER_CYCLES_TAG] + ) except Exception: string_message = ( experiment_name diff --git a/wetlab/utils/library.py b/wetlab/utils/library.py index 5c4a71fbb..7f0f26aea 100644 --- a/wetlab/utils/library.py +++ b/wetlab/utils/library.py @@ -73,13 +73,13 @@ def analyze_and_store_prot_lib_param_values(form_data): len(json_data[row_index]), ): lib_parameter_value = {} - lib_parameter_value[ - "parameter_id" - ] = core.models.ProtocolParameters.objects.filter( - protocol_id=prot_obj, - parameter_name__exact=headings[p_index], - parameter_used=True, - ).last() + lib_parameter_value["parameter_id"] = ( + core.models.ProtocolParameters.objects.filter( + protocol_id=prot_obj, + parameter_name__exact=headings[p_index], + parameter_used=True, + ).last() + ) lib_parameter_value["library_id"] = library_prep_obj lib_parameter_value["parameterValue"] = json_data[row_index][p_index] wetlab.models.LibParameterValue.objects.create_library_parameter_value( @@ -202,12 +202,12 @@ def get_protocol_parameters_for_library_preparation(protocol_libs): "sample_id__sample_name", "lib_prep_code_id", "pk" ) ) - prot_lib_data[ - "param_heading_type" - ] = core.utils.protocols.get_protocol_parameters_and_type(prot_obj) - prot_lib_data[ - "fix_heading" - ] = wetlab.config.HEADING_FIX_FOR_ADDING_LIB_PROT_PARAMETERS + prot_lib_data["param_heading_type"] = ( + core.utils.protocols.get_protocol_parameters_and_type(prot_obj) + ) + prot_lib_data["fix_heading"] = ( + wetlab.config.HEADING_FIX_FOR_ADDING_LIB_PROT_PARAMETERS + ) protocol_lib_prep_data.append(prot_lib_data) return protocol_lib_prep_data @@ -460,9 +460,9 @@ def get_all_library_information(sample_id): """ library_information = {} if wetlab.models.LibPrepare.objects.filter(sample_id__pk__exact=sample_id).exists(): - library_information[ - "library_definition_heading" - ] = wetlab.config.HEADING_FOR_LIBRARY_PREPARATION_DEFINITION + library_information["library_definition_heading"] = ( + wetlab.config.HEADING_FOR_LIBRARY_PREPARATION_DEFINITION + ) library_information["library_definition"] = [] library_information["pool_information"] = [] library_preparation_items = wetlab.models.LibPrepare.objects.filter( @@ -526,9 +526,9 @@ def get_all_library_information(sample_id): ) if library_information["pool_information"]: - library_information[ - "pool_heading" - ] = wetlab.config.HEADING_FOR_DISPLAY_POOL_INFORMATION_IN_SAMPLE_INFO + library_information["pool_heading"] = ( + wetlab.config.HEADING_FOR_DISPLAY_POOL_INFORMATION_IN_SAMPLE_INFO + ) return library_information @@ -579,9 +579,9 @@ def get_lib_prep_to_add_parameters(): lib_prep_info.append(sample.get_id()) sample_info.append(lib_prep_info) lib_prep_parameters["lib_prep_info"] = sample_info - lib_prep_parameters[ - "lib_prep_heading" - ] = wetlab.config.HEADING_FOR_ADD_LIBRARY_PREPARATION_PARAMETERS + lib_prep_parameters["lib_prep_heading"] = ( + wetlab.config.HEADING_FOR_ADD_LIBRARY_PREPARATION_PARAMETERS + ) lib_prep_parameters["length"] = len(sample_info) return lib_prep_parameters @@ -645,9 +645,9 @@ def get_samples_in_lib_prep_state(): lib_prep_data.append(sample_information + molecule_data) samples_in_lib_prep["library_information"] = lib_prep_data - samples_in_lib_prep[ - "lib_prep_heading" - ] = wetlab.config.HEADING_FOR_LIBRARY_PREPARATION_STATE + samples_in_lib_prep["lib_prep_heading"] = ( + wetlab.config.HEADING_FOR_LIBRARY_PREPARATION_STATE + ) samples_in_lib_prep["length"] = len(lib_prep_data) return samples_in_lib_prep diff --git a/wetlab/utils/pool.py b/wetlab/utils/pool.py index f08d179f8..9b49e7c68 100644 --- a/wetlab/utils/pool.py +++ b/wetlab/utils/pool.py @@ -234,18 +234,18 @@ def get_info_to_display_created_pool(pool_obj): information_for_created_pool = {} information_for_created_pool["data"] = pool_obj.get_info() information_for_created_pool["pool_name"] = pool_obj.get_pool_name() - information_for_created_pool[ - "heading_pool" - ] = wetlab.config.HEADING_FOR_DISPLAY_CREATED_POOL + information_for_created_pool["heading_pool"] = ( + wetlab.config.HEADING_FOR_DISPLAY_CREATED_POOL + ) lib_prep_data = [] if wetlab.models.LibPrepare.objects.filter(pools=pool_obj).exists(): lib_prep_ids = wetlab.models.LibPrepare.objects.filter(pools=pool_obj) for lib_prep_obj in lib_prep_ids: lib_prep_data.append(lib_prep_obj.get_info_for_display_pool()) information_for_created_pool["lib_prep_data"] = lib_prep_data - information_for_created_pool[ - "heading_library_pool" - ] = wetlab.config.HEADING_FOR_DISPLAY_LIB_PREP_IN_POOL + information_for_created_pool["heading_library_pool"] = ( + wetlab.config.HEADING_FOR_DISPLAY_LIB_PREP_IN_POOL + ) return information_for_created_pool diff --git a/wetlab/utils/reports.py b/wetlab/utils/reports.py index b493f40ce..f9efaf17a 100644 --- a/wetlab/utils/reports.py +++ b/wetlab/utils/reports.py @@ -130,17 +130,17 @@ def get_annual_report(rep_year): "zune", format_sample_per_month, ) - report_data[ - "rep_sample_month_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "rep_sample_month_graph", - "600", - "350", - "rep_sample_month_chart", - "json", - g_data, - ).render() + report_data["rep_sample_month_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "rep_sample_month_graph", + "600", + "350", + "rep_sample_month_chart", + "json", + g_data, + ).render() + ) # Collecting data for previous years # ################################## @@ -195,17 +195,17 @@ def get_annual_report(rep_year): "year", "values", ) - report_data[ - "rep_comp_sample_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "rep_comp_sample_graph", - "600", - "350", - "rep_comp_sample_chart", - "json", - g_data, - ).render() + report_data["rep_comp_sample_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "rep_comp_sample_graph", + "600", + "350", + "rep_comp_sample_chart", + "json", + g_data, + ).render() + ) # Graphic chart for samples per researcher comp_sample_per_researcher = list( @@ -223,17 +223,17 @@ def get_annual_report(rep_year): "Researcher", "value", ) - report_data[ - "rep_comp_researcher_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", - "rep_comp_researcher_graph", - "600", - "350", - "rep_comp_researcher_chart", - "json", - g_data, - ).render() + report_data["rep_comp_researcher_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", + "rep_comp_researcher_graph", + "600", + "350", + "rep_comp_researcher_chart", + "json", + g_data, + ).render() + ) report_data["year"] = rep_year diff --git a/wetlab/utils/sample.py b/wetlab/utils/sample.py index f69f64f1a..726fb25e9 100644 --- a/wetlab/utils/sample.py +++ b/wetlab/utils/sample.py @@ -121,9 +121,9 @@ def get_comparation_sample_information(sample_objs): data.insert(2, sample_obj.get_run_name()) data.insert(3, stats_fl_obj.get_sample_number()) compared_data["table_data"].append(data) - compared_data[ - "table_heading" - ] = wetlab.config.HEADING_COMPARATION_SAMPLE_INFORMATION + compared_data["table_heading"] = ( + wetlab.config.HEADING_COMPARATION_SAMPLE_INFORMATION + ) return compared_data diff --git a/wetlab/utils/sequencers.py b/wetlab/utils/sequencers.py index 3ec056fe9..1d65f51dc 100644 --- a/wetlab/utils/sequencers.py +++ b/wetlab/utils/sequencers.py @@ -165,9 +165,9 @@ def get_list_sequencer_configuration(): sequencer_configuration """ sequencer_configuration = {} - sequencer_configuration[ - "platforms_used" - ] = get_platform_name_of_defined_sequencers() + sequencer_configuration["platforms_used"] = ( + get_platform_name_of_defined_sequencers() + ) if core.models.SequencingConfiguration.objects.all().exists(): sequencer_data = {} seq_conf_objs = core.models.SequencingConfiguration.objects.all().order_by( diff --git a/wetlab/utils/statistics.py b/wetlab/utils/statistics.py index 9f5de497e..fc4cc3104 100644 --- a/wetlab/utils/statistics.py +++ b/wetlab/utils/statistics.py @@ -40,9 +40,9 @@ def get_per_time_statistics(start_date, end_date): run_date__range=(start_date, end_date) ) if len(run_objs) == 0: - per_time_statistics[ - "ERROR" - ] = wetlab.config.ERROR_NOT_RUNS_FOUND_IN_SELECTED_PERIOD + per_time_statistics["ERROR"] = ( + wetlab.config.ERROR_NOT_RUNS_FOUND_IN_SELECTED_PERIOD + ) project_objs = wetlab.models.Projects.objects.filter(run_process__in=run_objs) sample_objs = wetlab.models.SamplesInProject.objects.filter( run_process_id__in=run_objs @@ -57,11 +57,17 @@ def get_per_time_statistics(start_date, end_date): g_data = core.utils.graphics.preparation_graphic_data( "Run states", "", "", "", "ocean", run_states, "sum_state", "value" ) - per_time_statistics[ - "time_state_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", "time_state_graph", "600", "300", "time_state_chart", "json", g_data - ).render() + per_time_statistics["time_state_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", + "time_state_graph", + "600", + "300", + "time_state_chart", + "json", + g_data, + ).render() + ) # Graphic chart for number of runs per weeks run_per_date = ( run_objs.annotate(year=ExtractYear("run_date")) @@ -80,17 +86,17 @@ def get_per_time_statistics(start_date, end_date): "ocean", format_run_per_date, ) - per_time_statistics[ - "time_run_weeks_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "time_run_weeks_graph", - "600", - "350", - "time_run_weeks_chart", - "json", - g_data, - ).render() + per_time_statistics["time_run_weeks_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "time_run_weeks_graph", + "600", + "350", + "time_run_weeks_chart", + "json", + g_data, + ).render() + ) # Graphic chart for projects project_per_date = ( project_objs.annotate(year=ExtractYear("run_process__run_date")) @@ -109,17 +115,17 @@ def get_per_time_statistics(start_date, end_date): "zune", format_project_per_date, ) - per_time_statistics[ - "time_project_weeks_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "time_project_weeks_graph", - "600", - "350", - "time_project_weeks_chart", - "json", - g_data, - ).render() + per_time_statistics["time_project_weeks_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "time_project_weeks_graph", + "600", + "350", + "time_project_weeks_chart", + "json", + g_data, + ).render() + ) # Graphic chart for samples per researcher sample_per_researcher = list( @@ -137,17 +143,17 @@ def get_per_time_statistics(start_date, end_date): "Researcher", "value", ) - per_time_statistics[ - "time_researcher_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", - "time_researcher_graph", - "600", - "300", - "time_researcher_chart", - "json", - g_data, - ).render() + per_time_statistics["time_researcher_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", + "time_researcher_graph", + "600", + "300", + "time_researcher_chart", + "json", + g_data, + ).render() + ) # Graphic chart for unknown barcodes # pening to fix issue 158 @@ -173,11 +179,17 @@ def get_per_time_statistics(start_date, end_date): "run_name", "q_30_value", ) - per_time_statistics[ - "time_q_30_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", "time_q_30_graph", "550", "350", "time_q_30_chart", "json", g_data - ).render() + per_time_statistics["time_q_30_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "time_q_30_graph", + "550", + "350", + "time_q_30_chart", + "json", + g_data, + ).render() + ) # chart graph for mean based on runs # #################### @@ -196,11 +208,17 @@ def get_per_time_statistics(start_date, end_date): "run_name", "mean_value", ) - per_time_statistics[ - "time_mean_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", "time_mean_graph", "550", "350", "time_mean_chart", "json", g_data - ).render() + per_time_statistics["time_mean_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "time_mean_graph", + "550", + "350", + "time_mean_chart", + "json", + g_data, + ).render() + ) # chart graph for Q > 30 based on researcher # ############################## @@ -220,17 +238,17 @@ def get_per_time_statistics(start_date, end_date): "run_name", "q_30_value", ) - per_time_statistics[ - "time_researcher_q_30_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "time_researcher_q_30_graph", - "550", - "350", - "time_researcher_q_30_chart", - "json", - g_data, - ).render() + per_time_statistics["time_researcher_q_30_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "time_researcher_q_30_graph", + "550", + "350", + "time_researcher_q_30_chart", + "json", + g_data, + ).render() + ) # chart graph for mean based on researcher # #################### @@ -249,17 +267,17 @@ def get_per_time_statistics(start_date, end_date): "run_name", "mean_value", ) - per_time_statistics[ - "time_researcher_mean_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "time_researcher_mean_graph", - "550", - "350", - "time_researcher_mean_chart", - "json", - g_data, - ).render() + per_time_statistics["time_researcher_mean_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "time_researcher_mean_graph", + "550", + "350", + "time_researcher_mean_chart", + "json", + g_data, + ).render() + ) # Table information for run data per_time_statistics["run_data"] = list( @@ -274,9 +292,9 @@ def get_per_time_statistics(start_date, end_date): ) ) ) - per_time_statistics[ - "run_table_heading" - ] = wetlab.config.HEADING_STATISTICS_FOR_TIME_RUN + per_time_statistics["run_table_heading"] = ( + wetlab.config.HEADING_STATISTICS_FOR_TIME_RUN + ) # Table information for sample data per_time_statistics["sample_data"] = list( @@ -289,9 +307,9 @@ def get_per_time_statistics(start_date, end_date): "barcode_name", ) ) - per_time_statistics[ - "sample_table_heading" - ] = wetlab.config.HEADING_STATISTICS_FOR_TIME_SAMPLE + per_time_statistics["sample_table_heading"] = ( + wetlab.config.HEADING_STATISTICS_FOR_TIME_SAMPLE + ) per_time_statistics["start_date"] = start_date per_time_statistics["end_date"] = end_date per_time_statistics["num_runs"] = len(run_objs) @@ -318,17 +336,17 @@ def get_researcher_statistics(researcher_name, start_date, end_date): """ researcher_statistics = {} if not User.objects.filter(username__icontains=researcher_name).exists(): - researcher_statistics[ - "ERROR" - ] = wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + researcher_statistics["ERROR"] = ( + wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + ) return researcher_statistics user_objs = User.objects.filter(username__icontains=researcher_name) if len(user_objs) > 1: - researcher_statistics[ - "ERROR" - ] = wetlab.config.ERROR_MANY_USER_MATCHES_FOR_INPUT_CONDITIONS + researcher_statistics["ERROR"] = ( + wetlab.config.ERROR_MANY_USER_MATCHES_FOR_INPUT_CONDITIONS + ) return researcher_statistics researcher_name = user_objs[0].username # validate date format @@ -358,9 +376,9 @@ def get_researcher_statistics(researcher_name, start_date, end_date): other_user_sample_objs = sample_objs.exclude(user_id=user_objs[0]) user_sample_objs = sample_objs.filter(user_id=user_objs[0]) if len(user_sample_objs) == 0: - researcher_statistics[ - "ERROR" - ] = wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + researcher_statistics["ERROR"] = ( + wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + ) return researcher_statistics # sample table researcher_statistics["samples"] = user_sample_objs.values_list( @@ -369,9 +387,9 @@ def get_researcher_statistics(researcher_name, start_date, end_date): "run_process_id__run_name", "run_process_id__used_sequencer__sequencer_name", ) - researcher_statistics[ - "table_heading" - ] = wetlab.config.HEADING_STATISTICS_FOR_RESEARCHER_SAMPLE + researcher_statistics["table_heading"] = ( + wetlab.config.HEADING_STATISTICS_FOR_RESEARCHER_SAMPLE + ) # pie graph percentage researcher vs others per_data_user = {} @@ -382,17 +400,17 @@ def get_researcher_statistics(researcher_name, start_date, end_date): "Percentage of samples", "Research vs all", "ocean", per_data_user ) - researcher_statistics[ - "research_vs_other_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", - "research_vs_other_graph", - "600", - "300", - "research_vs_other_chart", - "json", - g_data, - ).render() + researcher_statistics["research_vs_other_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", + "research_vs_other_graph", + "600", + "300", + "research_vs_other_chart", + "json", + g_data, + ).render() + ) # pie graph for sequencers used # ############################# @@ -405,17 +423,17 @@ def get_researcher_statistics(researcher_name, start_date, end_date): g_data = core.utils.graphics.preparation_3D_pie( "Sequencer usage", "", "ocean", sample_per_sequencer ) - researcher_statistics[ - "research_usage_sequencer_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", - "research_usage_sequencer_graph", - "600", - "300", - "research_usage_sequencer_chart", - "json", - g_data, - ).render() + researcher_statistics["research_usage_sequencer_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", + "research_usage_sequencer_graph", + "600", + "300", + "research_usage_sequencer_chart", + "json", + g_data, + ).render() + ) # chart graph for runs # #################### @@ -436,17 +454,17 @@ def get_researcher_statistics(researcher_name, start_date, end_date): "ocean", researcher_runs, ) - researcher_statistics[ - "research_run_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "research_run_graph", - "550", - "350", - "research_run_chart", - "json", - g_data, - ).render() + researcher_statistics["research_run_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "research_run_graph", + "550", + "350", + "research_run_chart", + "json", + g_data, + ).render() + ) # chart graph for projects # ######################## @@ -466,17 +484,17 @@ def get_researcher_statistics(researcher_name, start_date, end_date): "ocean", researcher_projects, ) - researcher_statistics[ - "research_project_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "research_project_graph", - "550", - "350", - "research_project_chart", - "json", - g_data, - ).render() + researcher_statistics["research_project_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "research_project_graph", + "550", + "350", + "research_project_chart", + "json", + g_data, + ).render() + ) # chart graph for Q > 30 on runs # ############################## @@ -496,17 +514,17 @@ def get_researcher_statistics(researcher_name, start_date, end_date): "run_name", "q_30_value", ) - researcher_statistics[ - "research_q_30_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "research_q_30_graph", - "550", - "350", - "research_q_30_chart", - "json", - g_data, - ).render() + researcher_statistics["research_q_30_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "research_q_30_graph", + "550", + "350", + "research_q_30_chart", + "json", + g_data, + ).render() + ) # chart graph for mean # #################### @@ -525,17 +543,17 @@ def get_researcher_statistics(researcher_name, start_date, end_date): "run_name", "mean_value", ) - researcher_statistics[ - "research_mean_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "research_mean_graph", - "550", - "350", - "research_mean_chart", - "json", - g_data, - ).render() + researcher_statistics["research_mean_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "research_mean_graph", + "550", + "350", + "research_mean_chart", + "json", + g_data, + ).render() + ) researcher_statistics["researcher_name"] = researcher_name return researcher_statistics @@ -573,9 +591,9 @@ def get_sequencer_statistics(sequencer_name, start_date, end_date): if not core.models.SequencerInLab.objects.filter( sequencer_name__iexact=sequencer_name ).exists(): - sequencer_statistics[ - "ERROR" - ] = wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + sequencer_statistics["ERROR"] = ( + wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + ) return sequencer_statistics # validate date format if start_date != "" and not wetlab.utils.common.check_valid_date_format(start_date): @@ -602,9 +620,9 @@ def get_sequencer_statistics(sequencer_name, start_date, end_date): run_objs = run_objs.filter(used_sequencer__sequencer_name__iexact=sequencer_name) if len(run_objs) == 0: - sequencer_statistics[ - "ERROR" - ] = wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + sequencer_statistics["ERROR"] = ( + wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + ) return sequencer_statistics # Chart graphic for run states @@ -623,17 +641,17 @@ def get_sequencer_statistics(sequencer_name, start_date, end_date): "sum_state", "value", ) - sequencer_statistics[ - "sequencer_state_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", - "sequencer_state_graph", - "600", - "300", - "sequencer_state_chart", - "json", - g_data, - ).render() + sequencer_statistics["sequencer_state_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", + "sequencer_state_graph", + "600", + "300", + "sequencer_state_chart", + "json", + g_data, + ).render() + ) # Chart graphic for comparation of sequencers sequencers_usage = list( @@ -651,17 +669,17 @@ def get_sequencer_statistics(sequencer_name, start_date, end_date): "sequencer_name", "value", ) - sequencer_statistics[ - "sequencer_usage_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", - "sequencer_usage_graph", - "600", - "300", - "sequencer_usage_chart", - "json", - g_data, - ).render() + sequencer_statistics["sequencer_usage_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", + "sequencer_usage_graph", + "600", + "300", + "sequencer_usage_chart", + "json", + g_data, + ).render() + ) # run table sequencer_statistics["run_table_data"] = list( @@ -676,9 +694,9 @@ def get_sequencer_statistics(sequencer_name, start_date, end_date): ) ) ) - sequencer_statistics[ - "run_table_heading" - ] = wetlab.config.HEADING_STATISTICS_FOR_SEQUENCER_RUNS + sequencer_statistics["run_table_heading"] = ( + wetlab.config.HEADING_STATISTICS_FOR_SEQUENCER_RUNS + ) sequencer_statistics["sequencer_name"] = sequencer_name return sequencer_statistics diff --git a/wetlab/views.py b/wetlab/views.py index e3e3b4f6b..91e5a1dd7 100644 --- a/wetlab/views.py +++ b/wetlab/views.py @@ -56,9 +56,9 @@ def configuration_email(request): if request.user.username != "admin": return redirect("/wetlab") email_conf_data = core.utils.common.get_email_data() - email_conf_data[ - "EMAIL_ISKYLIMS" - ] = wetlab.utils.common.get_configuration_from_database("EMAIL_FOR_NOTIFICATIONS") + email_conf_data["EMAIL_ISKYLIMS"] = ( + wetlab.utils.common.get_configuration_from_database("EMAIL_FOR_NOTIFICATIONS") + ) if request.method == "POST" and (request.POST["action"] == "emailconfiguration"): result_email = core.utils.common.send_test_email(request.POST) if result_email != "OK": @@ -147,9 +147,9 @@ def configuration_test(request): if request.method == "POST" and request.POST["action"] == "basicTest": test_results = {} config_file = os.path.join(settings.BASE_DIR, "wetlab", "config.py") - test_results[ - "iSkyLIMS_settings" - ] = wetlab.utils.test_conf.get_iSkyLIMS_settings() + test_results["iSkyLIMS_settings"] = ( + wetlab.utils.test_conf.get_iSkyLIMS_settings() + ) test_results["config_file"] = wetlab.utils.test_conf.get_config_file( config_file ) @@ -157,9 +157,9 @@ def configuration_test(request): os.path.join(settings.MEDIA_ROOT, "wetlab") ) test_results["database_access"] = wetlab.utils.test_conf.check_access_database() - test_results[ - "samba_connection" - ] = wetlab.utils.test_conf.check_samba_connection() + test_results["samba_connection"] = ( + wetlab.utils.test_conf.check_samba_connection() + ) test_results["basic_checks_ok"] = "OK" for result in test_results: @@ -1131,9 +1131,9 @@ def retry_error_run(request): previous_error_state = run_name_found.get_state_before_error() run_name_found.set_run_state(previous_error_state) detail_description = {} - detail_description[ - "information" - ] = wetlab.config.SUCCESSFUL_RUN_STATE_CHANGE_FOR_RETRY + detail_description["information"] = ( + wetlab.config.SUCCESSFUL_RUN_STATE_CHANGE_FOR_RETRY + ) return render( request, "wetlab/display_run.html", @@ -1168,9 +1168,9 @@ def skip_cancel_situation(request): run_name_found.set_run_state("Sample Sent") run_name_found.set_forced_continue_on_error() detail_description = {} - detail_description[ - "information" - ] = wetlab.config.SUCCESSFUL_RUN_STATE_CHANGE_FOR_RETRY + detail_description["information"] = ( + wetlab.config.SUCCESSFUL_RUN_STATE_CHANGE_FOR_RETRY + ) return render( request, "wetlab/skip_cancel_situation.html", @@ -3093,9 +3093,9 @@ def handling_library_preparation(request): sample_sheet_data ) ) - display_sample_sheet[ - "lib_prep_user_sample_sheet" - ] = lib_prep_sample_sheet_obj.get_user_sample_sheet_id() + display_sample_sheet["lib_prep_user_sample_sheet"] = ( + lib_prep_sample_sheet_obj.get_user_sample_sheet_id() + ) display_sample_sheet["platform"] = platform display_sample_sheet["iem_version"] = sample_sheet_data["iem_version"] if user_in_description == "TRUE": @@ -3367,9 +3367,9 @@ def repeat_library_preparation(request): "wetlab/error_page.html", {"detail_description": detail_description}, ) - detail_description[ - "information" - ] = wetlab.config.SUCCESSFUL_REUSE_MOLECULE_EXTRACTION + detail_description["information"] = ( + wetlab.config.SUCCESSFUL_REUSE_MOLECULE_EXTRACTION + ) return render( request, "wetlab/successful_page.html", @@ -3862,32 +3862,32 @@ def pending_sample_preparation(request): pending_data = core.utils.samples.pending_sample_summary(req_user, friend_list) if len(pending_data["state"]) > 0: - pending_data[ - "sample_heading" - ] = wetlab.config.HEADING_FOR_PENDING_PROCESS_SAMPLES - pending_data[ - "pending_sample_graphic" - ] = wetlab.utils.statistics.get_pending_graphic_data( - pending_data["state_number"], - "Pending samples", - "flint", - "ex1", - "500", - "500", - "chart-1", + pending_data["sample_heading"] = ( + wetlab.config.HEADING_FOR_PENDING_PROCESS_SAMPLES ) - # if wetlab manager create graphic for users on pending samples - if user_is_wetlab_manager: - pending_data[ - "pending_users_graphic" - ] = wetlab.utils.statistics.get_pending_graphic_data( - pending_data["users"], - "Users with pending samples", + pending_data["pending_sample_graphic"] = ( + wetlab.utils.statistics.get_pending_graphic_data( + pending_data["state_number"], + "Pending samples", "flint", - "ex2", + "ex1", "500", "500", - "chart-2", + "chart-1", + ) + ) + # if wetlab manager create graphic for users on pending samples + if user_is_wetlab_manager: + pending_data["pending_users_graphic"] = ( + wetlab.utils.statistics.get_pending_graphic_data( + pending_data["users"], + "Users with pending samples", + "flint", + "ex2", + "500", + "500", + "chart-2", + ) ) return render( From 3775527d2f022b19d7b46d5ef6e471444d7a337d Mon Sep 17 00:00:00 2001 From: luissian Date: Tue, 16 Apr 2024 16:48:28 +0200 Subject: [PATCH 017/446] Fixing litin second trial --- wetlab/api/utils/sample.py | 44 +++++++++++++++++++------------------- wetlab/utils/test_conf.py | 18 ++++++++-------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/wetlab/api/utils/sample.py b/wetlab/api/utils/sample.py index c32d5f6a9..35346b8e9 100644 --- a/wetlab/api/utils/sample.py +++ b/wetlab/api/utils/sample.py @@ -486,22 +486,22 @@ def summarize_samples(data): .distinct() ) for f_value in f_values: - summarize["parameters"][p_name][ - f_value - ] = core.models.SampleProjectsFieldsValue.objects.filter( - sample_project_field_id=s_project_field_obj, - sample_project_field_value__exact=f_value, - sample_id__sample_name__in=sample_list, - ).count() + summarize["parameters"][p_name][f_value] = ( + core.models.SampleProjectsFieldsValue.objects.filter( + sample_project_field_id=s_project_field_obj, + sample_project_field_value__exact=f_value, + sample_id__sample_name__in=sample_list, + ).count() + ) else: summarize["parameters"][p_name]["value"] = 0 else: - summarize["parameters"][p_name][ - "value" - ] = core.models.SampleProjectsFieldsValue.objects.filter( - sample_project_field_id=s_project_field_obj, - sample_id__sample_name__in=sample_list, - ).count() + summarize["parameters"][p_name]["value"] = ( + core.models.SampleProjectsFieldsValue.objects.filter( + sample_project_field_id=s_project_field_obj, + sample_id__sample_name__in=sample_list, + ).count() + ) summarize["parameters"][p_name][ "classification" ] = s_project_field_obj.get_classification_name() @@ -571,15 +571,15 @@ def collect_statistics_information(data): stats_data[par1_val][par2_val] = value else: for par1_val in par1_values: - stats_data[ - par1_val - ] = core.models.SampleProjectsFieldsValue.objects.filter( - sample_project_field_id__sample_projects_id=s_project_obj, - sample_project_field_id__sample_project_field_name__iexact=query_params[ - 0 - ], - sample_project_field_value=par1_val, - ).count() + stats_data[par1_val] = ( + core.models.SampleProjectsFieldsValue.objects.filter( + sample_project_field_id__sample_projects_id=s_project_obj, + sample_project_field_id__sample_project_field_name__iexact=query_params[ + 0 + ], + sample_project_field_value=par1_val, + ).count() + ) return stats_data else: # Collect info stats for all fields diff --git a/wetlab/utils/test_conf.py b/wetlab/utils/test_conf.py index d05b58df8..c67190947 100644 --- a/wetlab/utils/test_conf.py +++ b/wetlab/utils/test_conf.py @@ -197,9 +197,9 @@ def execute_test_for_testing_run(run_test_name): conn, [run_obj] ) if run_obj.get_state() == "ERROR": - run_result[ - "ERROR" - ] = "Error when processing run in Processing Run state" + run_result["ERROR"] = ( + "Error when processing run in Processing Run state" + ) break else: run_result["Processing Run"] = "OK" @@ -217,9 +217,9 @@ def execute_test_for_testing_run(run_test_name): conn, [run_obj] ) if run_obj.get_state() == "ERROR": - run_result[ - "ERROR" - ] = "Error when processing run in Processing Bcl2fastq state" + run_result["ERROR"] = ( + "Error when processing run in Processing Bcl2fastq state" + ) break else: run_result["Processing Bcl2fastq"] = "OK" @@ -228,9 +228,9 @@ def execute_test_for_testing_run(run_test_name): conn, [run_obj] ) if run_obj.get_state() == "ERROR": - run_result[ - "ERROR" - ] = "Error when processing run in Processed Bcl2fastq state" + run_result["ERROR"] = ( + "Error when processing run in Processed Bcl2fastq state" + ) break else: run_result["Processed Bcl2fastq"] = "OK" From a11bb0849aaa71caddcf1c551df19484bd373ecc Mon Sep 17 00:00:00 2001 From: luissian Date: Tue, 16 Apr 2024 16:50:34 +0200 Subject: [PATCH 018/446] Fixing litin third trial --- wetlab/utils/fetch_info.py | 94 ++++++++++++++++++++----------------- wetlab/utils/run.py | 36 +++++--------- wetlab/utils/samplesheet.py | 6 +-- 3 files changed, 65 insertions(+), 71 deletions(-) diff --git a/wetlab/utils/fetch_info.py b/wetlab/utils/fetch_info.py index 315084f8a..e0dc69d46 100644 --- a/wetlab/utils/fetch_info.py +++ b/wetlab/utils/fetch_info.py @@ -612,11 +612,11 @@ def get_information_for_incompleted_run(): "Incomplete Runs", "", "", "", "fint", runs_in_state ) - run_information[ - "incompleted_graphic" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", "ex1", "550", "400", "chart-1", "json", data_source - ).render() + run_information["incompleted_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", "ex1", "550", "400", "chart-1", "json", data_source + ).render() + ) return run_information @@ -856,11 +856,17 @@ def get_information_run(run_object): data_source = wetlab.utils.stats_graphs.column_graphic_simple( heading, sub_caption, x_axis_name, y_axis_name, theme, percent_projects ) - info_dict[ - "run_project_comparation" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", "column1", "600", "400", "column_chart1", "json", data_source - ).render() + info_dict["run_project_comparation"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "column1", + "600", + "400", + "column_chart1", + "json", + data_source, + ).render() + ) fl_data_display = [] @@ -1022,9 +1028,9 @@ def get_information_project(project_id, request): fl_summary_obj = wetlab.models.StatsFlSummary.objects.filter( project_id__exact=project_id, runprocess_id=run_obj ).last() - project_info_dict[ - "fl_summary_heading" - ] = wetlab.config.HEADING_SINGLE_PROJECT_FL_SUMMARY + project_info_dict["fl_summary_heading"] = ( + wetlab.config.HEADING_SINGLE_PROJECT_FL_SUMMARY + ) # fl_data_display.append(fl_list) project_info_dict["fl_summary_data"] = fl_summary_obj.get_fl_summary() @@ -1035,9 +1041,9 @@ def get_information_project(project_id, request): lane_summary_obj = wetlab.models.StatsLaneSummary.objects.filter( project_id__exact=project_id, runprocess_id=run_obj ) - project_info_dict[ - "lane_summary_heading" - ] = wetlab.config.HEADING_SINGLE_PROJECT_STATS_LANE + project_info_dict["lane_summary_heading"] = ( + wetlab.config.HEADING_SINGLE_PROJECT_STATS_LANE + ) project_info_dict["lane_summary_data"] = [] for lane_sum in lane_summary_obj: project_info_dict["lane_summary_data"].append( @@ -1051,9 +1057,9 @@ def get_information_project(project_id, request): sample_objs = wetlab.models.SamplesInProject.objects.filter( project_id__exact=project_id, run_process_id=run_obj ) - project_info_dict[ - "sample_heading" - ] = wetlab.config.HEADING_SINGLE_PROJECT_SAMPLES + project_info_dict["sample_heading"] = ( + wetlab.config.HEADING_SINGLE_PROJECT_SAMPLES + ) project_info_dict["sample_data"] = [] for sample_obj in sample_objs: project_info_dict["sample_data"].append( @@ -1294,9 +1300,9 @@ def get_stats_sequencer_data_from_selected_runs( sequencer_data["sequencer_name"] = sequencer sequencer_data["run_completed"] = [] sequencer_data["not_run_completed"] = [] - sequencer_data[ - "run_name_heading" - ] = wetlab.config.HEADING_FOR_STATISTICS_RUNS_BASIC_DATA + sequencer_data["run_name_heading"] = ( + wetlab.config.HEADING_FOR_STATISTICS_RUNS_BASIC_DATA + ) # get the run completed and not completed for the sequencer for run_in_seq in runs_using_sequencer["completed_run_objs"]: sequencer_data["run_completed"].append( @@ -1355,17 +1361,17 @@ def get_stats_sequencer_data_from_selected_runs( data_source = wetlab.utils.stats_graphs.column_graphic_tupla( heading, "", "", "Number of Runs", "ocean", run_time_tupla, None ) - sequencer_data[ - "sequencer_runs_per_month_graph" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "run_per_month_graph", - "500", - "400", - "chart_seq_month", - "json", - data_source, - ).render() + sequencer_data["sequencer_runs_per_month_graph"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "run_per_month_graph", + "500", + "400", + "chart_seq_month", + "json", + data_source, + ).render() + ) # get the data for run executed in other sequencers per months if ( @@ -1392,16 +1398,16 @@ def get_stats_sequencer_data_from_selected_runs( data_source = wetlab.utils.stats_graphs.column_graphic_tupla( heading, "", "", "Number of Runs", "fint", run_time_tupla, None ) - sequencer_data[ - "other_sequencers_runs_per_month_graph" - ] = core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "other_run_per_month_graph", - "500", - "400", - "chart_other_seq_month", - "json", - data_source, - ).render() + sequencer_data["other_sequencers_runs_per_month_graph"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "other_run_per_month_graph", + "500", + "400", + "chart_other_seq_month", + "json", + data_source, + ).render() + ) return sequencer_data diff --git a/wetlab/utils/run.py b/wetlab/utils/run.py index 84081a3d1..723c83a92 100644 --- a/wetlab/utils/run.py +++ b/wetlab/utils/run.py @@ -228,9 +228,9 @@ def collect_data_and_update_library_preparation_samples_for_run(data_form, user) ] projects.append(json_data[row_index][heading.index("Sample_Project")]) # keep the old value of the user sample sheet and update the library prepation - confirmation_data[ - "user_sample_sheet" - ] = lib_prep_obj.get_user_sample_sheet_obj() + confirmation_data["user_sample_sheet"] = ( + lib_prep_obj.get_user_sample_sheet_obj() + ) lib_prep_obj.update_library_preparation_with_indexes(confirmation_data) sample_sheet_data_field.append(json_data[row_index]) if record_data["platform"] == "NextSeq": @@ -684,41 +684,29 @@ def collect_lib_prep_data_for_new_run(lib_prep_ids, platform_in_pool): if platform_in_pool == "MiSeq": if single_read: if iem_version == "4": - lib_data[ - "heading" - ] = ( + lib_data["heading"] = ( wetlab.config.HEADING_FOR_COLLECT_INFO_FOR_SAMPLE_SHEET_MISEQ_SINGLE_READ_VERSION_4 ) else: - lib_data[ - "heading" - ] = ( + lib_data["heading"] = ( wetlab.config.HEADING_FOR_COLLECT_INFO_FOR_SAMPLE_SHEET_MISEQ_SINGLE_READ_VERSION_5 ) else: if iem_version == "4": - lib_data[ - "heading" - ] = ( + lib_data["heading"] = ( wetlab.config.HEADING_FOR_COLLECT_INFO_FOR_SAMPLE_SHEET_MISEQ_PAIRED_END_VERSION_4 ) else: - lib_data[ - "heading" - ] = ( + lib_data["heading"] = ( wetlab.config.HEADING_FOR_COLLECT_INFO_FOR_SAMPLE_SHEET_MISEQ_PAIRED_END_VERSION_5 ) else: if single_read: - lib_data[ - "heading" - ] = ( + lib_data["heading"] = ( wetlab.config.HEADING_FOR_COLLECT_INFO_FOR_SAMPLE_SHEET_NEXTSEQ_SINGLE_READ ) else: - lib_data[ - "heading" - ] = ( + lib_data["heading"] = ( wetlab.config.HEADING_FOR_COLLECT_INFO_FOR_SAMPLE_SHEET_NEXTSEQ_PAIRED_END ) return lib_data @@ -943,9 +931,9 @@ def get_run_user_lot_kit_used_in_sample(sample_id): kit_data = {} kit_data["run_kits_from_sample"] = {} if wetlab.models.LibPrepare.objects.filter(sample_id__pk__exact=sample_id).exists(): - kit_data[ - "heading_run_kits" - ] = wetlab.config.HEADING_FOR_DISPLAY_KIT_IN_RUN_PREPARATION + kit_data["heading_run_kits"] = ( + wetlab.config.HEADING_FOR_DISPLAY_KIT_IN_RUN_PREPARATION + ) library_preparation_items = wetlab.models.LibPrepare.objects.filter( sample_id__pk__exact=sample_id ).order_by("protocol_id") diff --git a/wetlab/utils/samplesheet.py b/wetlab/utils/samplesheet.py index dfa9b97ce..365391594 100644 --- a/wetlab/utils/samplesheet.py +++ b/wetlab/utils/samplesheet.py @@ -47,9 +47,9 @@ def validate_userid_in_user_iem_file(file_read, user_id_list): description_index = line.index("Description") continue except Exception: - users[ - "ERROR" - ] = wetlab.config.ERROR_SAMPLE_SHEET_DOES_NOT_HAVE_DESCRIPTION_FIELD + users["ERROR"] = ( + wetlab.config.ERROR_SAMPLE_SHEET_DOES_NOT_HAVE_DESCRIPTION_FIELD + ) return users else: try: From e57099caa7093cd488df0f84e8aa3052f80b4271 Mon Sep 17 00:00:00 2001 From: luissian Date: Wed, 17 Apr 2024 17:35:23 +0200 Subject: [PATCH 019/446] Implemented issue #78 Allow to search services by service type --- README.md | 12 +- drylab/templates/drylab/search_service.html | 438 +++++++++++--------- drylab/utils/req_services.py | 22 +- drylab/views.py | 17 +- install.sh | 176 +------- 5 files changed, 293 insertions(+), 372 deletions(-) diff --git a/README.md b/README.md index d585518e6..fd3db9736 100644 --- a/README.md +++ b/README.md @@ -137,9 +137,13 @@ bash install.sh --install app sudo bash install.sh --install full ``` -### Upgrade to iSkyLIMS version 3.0.0 +### Upgrade iSkyLIMS from 3.0.x to version 3.1.x -If you have already iSkyLIMS on version 2.3.0 you can upgrade to the latest stable version 3.0.0. +**IMPORTANT NOTE** + +This installation script only apply for the upgrade from version 3.0.x to 3.1.x. + +**If you have already iSkyLIMS on version 2.3.0 you must upgrade first to the stable version 3.0.0, before applying for this upgrade.** Version 3.0.0 is a major release with important upgrades in third parties dependencies like bootstrap. Also, we 've done a huge work on refactoring and variables/function renaming that affects the database. For more details about the changes see the release notes. @@ -208,7 +212,7 @@ In the linux terminal execute one of the following command that fit better to yo sudo bash install.sh --upgrade dep # to install both software. NEEDS ROOT. -sudo bash install.sh --upgrade full --ren_app --script drylab_service_state_migration --script rename_app_name --script rename_sample_sheet_folder --script migrate_sample_type --script migrate_optional_values --tables +sudo bash install.sh --upgrade full ``` ##### Steps not requiring root @@ -217,7 +221,7 @@ Next you need to upgrade iskylims app. Please use the command below: ```bash # to upgrade only iSkyLIMS application including changes required in this release. DOES NOT NEED ROOT. -bash install.sh --upgrade app --ren_app --script drylab_service_state_migration --script rename_app_name --script rename_sample_sheet_folder --script migrate_sample_type --script migrate_optional_values --tables +bash install.sh --upgrade app ``` Make sure that the installation folder has the correct permissions. diff --git a/drylab/templates/drylab/search_service.html b/drylab/templates/drylab/search_service.html index 61e30823f..ea82c9fa1 100644 --- a/drylab/templates/drylab/search_service.html +++ b/drylab/templates/drylab/search_service.html @@ -1,226 +1,258 @@ {% extends 'core/base.html' %} {% load static %} - {% block content %} - -{% include "core/cdn_table_functionality.html"%} - -{% include "drylab/menu.html" %} - -
-
- {% include 'registration/login_inline.html' %} - - {% if ERROR %} -
-
-
-
-

Result of your request

-
-
- {% for message in ERROR %} -

{{message}}

- {% endfor %} + {% include "core/cdn_table_functionality.html" %} + {% include "drylab/menu.html" %} +
+
+ {% include 'registration/login_inline.html' %} + {% if ERROR %} +
+
+
+
+

Result of your request

+
+
+ {% for message in ERROR %}

{{ message }}

{% endfor %} +
+
-
-
- {% endif %} - {% if display_multiple_services %} -
-
-
-
- Services search results -
-
- - - - - - - - - - - - - - - {% for key, values in display_multiple_services.s_list.items %} - - {% for serviceID, status, dates ,center, projects in values %} - - - {% for date in dates %} - - {% endfor %} - - - {%endfor%} - - {%endfor%} - - - - - - - - - - - - - -
Service ID StatusRecorded DateApproved DateRejected DateDelivered DateCenterProject names
{{ serviceID }} {{ status }} {{date}}{{ center }} {% for project in projects %}{{ project }}
{% endfor %}
Service Request ID StatusRecorded DateApproved DateRejected DateDelivered DateCenterProject names
+ {% endif %} + {% if display_multiple_services %} +
+
+
+
Services search results
+
+ + + + + + + + + + + + + + + {% for key, values in display_multiple_services.s_list.items %} + + {% for serviceID, status, dates ,center, projects in values %} + + + {% for date in dates %}{% endfor %} + + + {% endfor %} + + {% endfor %} + + + + + + + + + + + + + +
Service IDStatusRecorded DateApproved DateRejected DateDelivered DateCenterProject names
+ {{ serviceID }} + {{ status }}{{ date }}{{ center }} + {% for project in projects %} + {{ project }} +
+ {% endfor %} +
Service Request IDStatusRecorded DateApproved DateRejected DateDelivered DateCenterProject names
+
+
-
-
- {% else %} - -
-
-
-
Service search
-
-
-
-
-
- {% csrf_token %} - -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- {% if services_search_list.username %} - - {% else %} - - {% endif %} - -
-
- {% if services_search_list.username %} - - {% else %} - - {% endif %} - -
-
-
-
-
- {% if services_search_list.wetlab_app %} -
-
Search using wetlab information
-
+ {% else %} + +
+
+
+
+ Service search +
+
+ +
+
+
+ {% csrf_token %} + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
- - + +
- - + {% if services_search_list.username %} + + {% else %} + + {% endif %} +
- - + {% if services_search_list.username %} + + {% else %} + + {% endif %} +
- {% endif %} -
-
-
-
- -
-
- +
+
+ {% if services_search_list.wetlab_app %} +
+
Search using wetlab information
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ {% endif %} +
+
+
+
+ +
+
+ +
+
-
+
- -
+
+ {% endif %}
- {% endif %} -
-
-
- -{% endblock %} \ No newline at end of file + +{% endblock %} diff --git a/drylab/utils/req_services.py b/drylab/utils/req_services.py index 4dabb68db..b40ccc018 100644 --- a/drylab/utils/req_services.py +++ b/drylab/utils/req_services.py @@ -183,15 +183,21 @@ def delete_samples_in_service(sample_list): return deleted_sample_names -def get_children_services(all_tree_services): - """ - Description: - The function get the children available services from a query of service - Input: - all_tree_services # queryset of available service - Return: - children_service +def get_children_services(all_tree_services: list[drylab.models.AvailableService]=None)->list[list]: + """The function get a list with the children available services from the + requested query of available service. If no query is provided, it will + return all the children services from the database + + Args: + all_tree_services (list[drylab.models.AvailableService]): Queryset of + available service. Defaults to None. + + Returns: + list[list]: List with 2 values: the primary key and the description for + each children service """ + if all_tree_services is None: + all_tree_services = drylab.models.AvailableService.objects.all().order_by("description") children_services = [] for t_services in all_tree_services: if t_services.get_children(): diff --git a/drylab/views.py b/drylab/views.py index 86eca1c33..1952dab78 100644 --- a/drylab/views.py +++ b/drylab/views.py @@ -491,7 +491,9 @@ def search_service(request): services_search_list["states"] = ( drylab.utils.req_services.get_available_service_states(True) ) - + services_search_list["available_services"] = ( + drylab.utils.req_services.get_children_services() + ) if "wetlab" in settings.INSTALLED_APPS: services_search_list["wetlab_app"] = True @@ -504,6 +506,7 @@ def search_service(request): start_date = request.POST["start_date"] end_date = request.POST["end_date"] center = request.POST["service_center"] + available_services = request.POST["available_services"] service_user = request.POST["service_user"] assigned_user = request.POST["bioinfo_user"] @@ -519,6 +522,7 @@ def search_service(request): if ( service_id == "" and service_state == "" + and available_services == "" and start_date == "" and end_date == "" and center == "" @@ -575,9 +579,14 @@ def search_service(request): }, ) else: - services_found = drylab.models.Service.objects.prefetch_related( - "service_user_id", "service_state" - ).all() + if available_services != "": + services_found = drylab.models.Service.objects.prefetch_related( + "service_user_id", "service_state" + ).filter(service_available_service__id__exact=available_services) + else: + services_found = drylab.models.Service.objects.prefetch_related( + "service_user_id", "service_state" + ).all() if service_state != "": services_found = services_found.filter( diff --git a/install.sh b/install.sh index 8e07fb181..1f860b9fe 100644 --- a/install.sh +++ b/install.sh @@ -1,6 +1,6 @@ #!/bin/bash -ISKYLIMS_VERSION="3.x.x" +ISKYLIMS_VERSION="3.1.x" usage() { cat << EOF @@ -197,7 +197,7 @@ upgrade_type="full" docker=false # PARSE VARIABLE ARGUMENTS WITH getops -options=":c:s:i:u:drtkvh" +options=":c:s:i:u:dtkvh" while getopts $options opt; do case $opt in i ) @@ -229,9 +229,6 @@ while getopts $options opt; do t ) tables=true ;; - r ) - ren_app=true - ;; d ) git_branch="develop" ;; @@ -376,181 +373,54 @@ if [ $upgrade == true ]; then fi if [ "$upgrade_type" = "full" ] || [ "$upgrade_type" = "app" ]; then - - # Delete git and no copy files stuff - if [ $ren_app == true ] ; then - # remove all previous migrations and make a fake initial - # delete existing migrations file - rm -rf $INSTALL_PATH/django_utils/migrations/* - rm -rf $INSTALL_PATH/iSkyLIMS_core/migrations/* - rm -rf $INSTALL_PATH/iSkyLIMS_wetlab/migrations/* - rm -rf $INSTALL_PATH/iSkyLIMS_drylab/migrations/* - - cd $INSTALL_PATH - sed -i "s/ugettext/gettext/g" iSkyLIMS_wetlab/models.py - sed -i "s/ugettext/gettext/g" iSkyLIMS_core/forms.py - sed -i "s/ugettext/gettext/g" django_utils/forms.py - echo "activate the virtualenv" - source virtualenv/bin/activate - - echo "Create a fake initial" - python manage.py makemigrations django_utils iSkyLIMS_core \ - iSkyLIMS_wetlab iSkyLIMS_drylab - python manage.py migrate --fake-initial - - if [ -d "$INSTALL_PATH/iSkyLIMS_core" ]; then - echo "Changing app dir names in $INSTALL_PATH..." - rm -rf $INSTALL_PATH/.git $INSTALL_PATH/.github $INSTALL_PATH/.gitignore \ - $INSTALL_PATH/.Rhistory $INSTALL_PATH/docker-compose.yml $INSTALL_PATH/docker_iskylims_install.sh \ - $INSTALL_PATH/Dockerfile $INSTALL_PATH/install.sh $INSTALL_PATH/install_settings.txt - mv $INSTALL_PATH/iSkyLIMS_core $INSTALL_PATH/core - mv $INSTALL_PATH/iSkyLIMS_wetlab $INSTALL_PATH/wetlab - mv $INSTALL_PATH/iSkyLIMS_drylab $INSTALL_PATH/drylab - mv $INSTALL_PATH/iSkyLIMS_clinic $INSTALL_PATH/clinic - echo "Done changing app dir names in $INSTALL_PATH..." - fi - if [ -d "iSkyLIMS" ]; then - mv iSkyLIMS/ iskylims/ - sed -i "s/iSkyLIMS/iskylims/g" $INSTALL_PATH/iskylims/wsgi.py - sed -i "s/iSkyLIMS/iskylims/g" $INSTALL_PATH/manage.py - fi - cd - - fi # update installation by sinchronize folders echo "Copying files to installation folder" - rsync -rlv conf/ $INSTALL_PATH/conf/ + # rsync -rlv conf/ $INSTALL_PATH/conf/ rsync -rlv --fuzzy --delay-updates --delete-delay \ --exclude "logs" --exclude "documents" --exclude "migrations" --exclude "__pycache__" \ README.md LICENSE test conf core drylab clinic wetlab django_utils $INSTALL_PATH # update the settings.py and the main urls echo "Update settings and url file." - update_settings_and_urls + # update_settings_and_urls # update illumina template files.# Copy illumina sample sheet templates - mkdir -p $INSTALL_PATH/documents/wetlab/templates/ - cp $INSTALL_PATH/conf/*_template.csv $INSTALL_PATH/documents/wetlab/templates/ - cp $INSTALL_PATH/conf/samples_template.xlsx $INSTALL_PATH/documents/wetlab/templates/ + # mkdir -p $INSTALL_PATH/documents/wetlab/templates/ + # cp $INSTALL_PATH/conf/*_template.csv $INSTALL_PATH/documents/wetlab/templates/ + # cp $INSTALL_PATH/conf/samples_template.xlsx $INSTALL_PATH/documents/wetlab/templates/ # update logging configuration file - cp $INSTALL_PATH/conf/template_logging_config.ini $INSTALL_PATH/wetlab/logging_config.ini - sed -i "s@INSTALL_PATH@${INSTALL_PATH}@g" $INSTALL_PATH/wetlab/logging_config.ini + # cp $INSTALL_PATH/conf/template_logging_config.ini $INSTALL_PATH/wetlab/logging_config.ini + # sed -i "s@INSTALL_PATH@${INSTALL_PATH}@g" $INSTALL_PATH/wetlab/logging_config.ini # update the sample sheet folder and name - if [ -d "$INSTALL_PATH/documents/wetlab/SampleSheets" ]; then - echo "Updating sample sheet folder name" - mv $INSTALL_PATH/documents/wetlab/SampleSheets $INSTALL_PATH/documents/wetlab/sample_sheet - fi + # if [ -d "$INSTALL_PATH/documents/wetlab/SampleSheets" ]; then + # echo "Updating sample sheet folder name" + # mv $INSTALL_PATH/documents/wetlab/SampleSheets $INSTALL_PATH/documents/wetlab/sample_sheet + # fi - if [ -d "$INSTALL_PATH/documents/wetlab/SampleSheets4LibPrep" ]; then - echo "Updating sample sheet for libary preparationfolder name" - mv $INSTALL_PATH/documents/wetlab/SampleSheets4LibPrep $INSTALL_PATH/documents/wetlab/sample_sheets_lib_prep - fi + # if [ -d "$INSTALL_PATH/documents/wetlab/SampleSheets4LibPrep" ]; then + # echo "Updating sample sheet for libary preparationfolder name" + # mv $INSTALL_PATH/documents/wetlab/SampleSheets4LibPrep $INSTALL_PATH/documents/wetlab/sample_sheets_lib_prep + # fi cd $INSTALL_PATH echo "activate the virtualenv" source virtualenv/bin/activate - ### RENAME APP in database and migration files #### - if [ $ren_app == true ] ; then - - echo "Modifying database names and constraints..." - mysql -u $DB_USER -p$DB_PASS -D $DB_NAME -h $DB_SERVER_IP \ - -e 'UPDATE django_content_type SET app_label = REPLACE(app_label , "iSkyLIMS_core", "core") WHERE app_label like ("iSkyLIMS_%");' - # mysql -u $DB_USER -p$DB_PASS -D $DB_NAME -h $DB_SERVER_IP \ - # -e 'UPDATE django_content_type SET app_label = REPLACE(app_label , "iSkyLIMS_clinic", "clinic") WHERE app_label like ("iSkyLIMS_%");' - mysql -u $DB_USER -p$DB_PASS -D $DB_NAME -h $DB_SERVER_IP \ - -e 'UPDATE django_content_type SET app_label = REPLACE(app_label , "iSkyLIMS_wetlab", "wetlab") WHERE app_label like ("iSkyLIMS_%");' - mysql -u $DB_USER -p$DB_PASS -D $DB_NAME -h $DB_SERVER_IP \ - -e 'UPDATE django_content_type SET app_label = REPLACE(app_label , "iSkyLIMS_drylab", "drylab") WHERE app_label like ("iSkyLIMS_%");' - - mysql -u $DB_USER -p$DB_PASS -D $DB_NAME -h $DB_SERVER_IP \ - -e 'UPDATE django_migrations SET app = REPLACE(app , "iSkyLIMS_core", "core") WHERE app like ("iSkyLIMS_%");' - # mysql -u $DB_USER -p$DB_PASS -D $DB_NAME -h $DB_SERVER_IP \ - # -e 'UPDATE django_migrations SET app = REPLACE(app , "iSkyLIMS_clinic", "clinic") WHERE app like ("iSkyLIMS_%");' - mysql -u $DB_USER -p$DB_PASS -D $DB_NAME -h $DB_SERVER_IP \ - -e 'UPDATE django_migrations SET app = REPLACE(app , "iSkyLIMS_wetlab", "wetlab") WHERE app like ("iSkyLIMS_%");' - mysql -u $DB_USER -p$DB_PASS -D $DB_NAME -h $DB_SERVER_IP \ - -e 'UPDATE django_migrations SET app = REPLACE(app , "iSkyLIMS_drylab", "drylab") WHERE app like ("iSkyLIMS_%");' - echo "Renaming tables" - query_rename_table="SELECT CONCAT('RENAME TABLE ', TABLE_SCHEMA, '.', TABLE_NAME, \ - ' TO ', TABLE_SCHEMA, '.', REPLACE(TABLE_NAME, 'iSkyLIMS_', ''), ';') \ - AS query FROM information_schema.tables WHERE TABLE_SCHEMA = \"$DB_NAME\" AND TABLE_NAME LIKE 'iSkyLIMS_%';" - mysql -u $DB_USER -p$DB_PASS -h $DB_SERVER_IP -e "$query_rename_table" \ - | xargs -I % echo "mysql -u$DB_USER -p'$DB_PASS' -D $DB_NAME -h $DB_SERVER_IP -e \"% \" " | bash - echo "Renaming index" - query_rename_unique_indexes="SELECT CONCAT('ALTER TABLE ', rcu.TABLE_SCHEMA, '.', rcu.TABLE_NAME, \ - ' RENAME INDEX ', rcu.CONSTRAINT_NAME, \ - ' TO ', REPLACE(rcu.CONSTRAINT_NAME, 'iSkyLIMS_', ''), ';') \ - AS query FROM information_schema.key_column_usage rcu \ - JOIN information_schema.table_constraints tc \ - ON tc.CONSTRAINT_NAME = rcu.CONSTRAINT_NAME WHERE rcu.TABLE_SCHEMA = \"$DB_NAME\" \ - AND rcu.CONSTRAINT_NAME LIKE 'iSkyLIMS_%' AND tc.CONSTRAINT_TYPE = 'UNIQUE' \ - GROUP BY rcu.TABLE_SCHEMA, rcu.TABLE_NAME, rcu.CONSTRAINT_NAME, tc.CONSTRAINT_TYPE, \ - rcu.REFERENCED_TABLE_SCHEMA, rcu.REFERENCED_TABLE_NAME;" - mysql -u $DB_USER -p$DB_PASS -h $DB_SERVER_IP -e "$query_rename_unique_indexes" \ - | xargs -I % echo "mysql -u$DB_USER -p'$DB_PASS' -D $DB_NAME -h $DB_SERVER_IP -e \"% \" " | bash - echo "Renaming constraints" - query_rename_constraints="SELECT CONCAT('ALTER TABLE ', rcu.TABLE_SCHEMA, '.', rcu.TABLE_NAME, \ - ' DROP FOREIGN KEY ' , rcu.CONSTRAINT_NAME, ';', \ - ' ALTER TABLE ', rcu.TABLE_SCHEMA, '.', rcu.TABLE_NAME, \ - ' ADD CONSTRAINT ', REPLACE(rcu.CONSTRAINT_NAME, 'iSkyLIMS_', ''), ' ', \ - tc.CONSTRAINT_TYPE, ' (', GROUP_CONCAT(rcu.COLUMN_NAME ORDER BY rcu.ORDINAL_POSITION SEPARATOR ', '), ')', \ - IF(tc.CONSTRAINT_TYPE = 'FOREIGN KEY', \ - CONCAT(' REFERENCES ', rcu.REFERENCED_TABLE_SCHEMA, '.', REPLACE(rcu.REFERENCED_TABLE_NAME, 'iSkyLIMS_', ''), ' (', \ - GROUP_CONCAT(rcu.REFERENCED_COLUMN_NAME ORDER BY rcu.ORDINAL_POSITION SEPARATOR ', '), ') ON DELETE ', rc.DELETE_RULE), \ - ''), ';') AS query \ - FROM information_schema.key_column_usage rcu \ - LEFT JOIN information_schema.table_constraints tc ON rcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME \ - LEFT JOIN information_schema.referential_constraints rc ON rcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME \ - WHERE rcu.TABLE_SCHEMA = '$DB_NAME' AND rcu.CONSTRAINT_NAME LIKE 'iSkyLIMS_%' \ - GROUP BY rcu.TABLE_SCHEMA, rcu.TABLE_NAME, rcu.CONSTRAINT_NAME, tc.CONSTRAINT_TYPE, rcu.REFERENCED_TABLE_SCHEMA, rcu.REFERENCED_TABLE_NAME, rc.DELETE_RULE;" - mysql -u $DB_USER -p$DB_PASS -h $DB_SERVER_IP -e "$query_rename_constraints" | xargs -I % echo "mysql -u$DB_USER -p'$DB_PASS' -D $DB_NAME -h $DB_SERVER_IP -e \"% \" " | bash - - echo "Done modifying database names and constraints..." - - echo "Modifying names in migration files..." - sed -i 's/iSkyLIMS_core/core/g' */migrations/*.py - # sed -i 's/iSkyLIMS_clinic/clinic/g' */migrations/*.py - sed -i 's/iSkyLIMS_drylab/drylab/g' */migrations/*.py - sed -i 's/iSkyLIMS_wetlab/wetlab/g' */migrations/*.py - echo "Done modifying names in migration files..." - - # copy modified migration files - echo "Copying custom migration files from conf." - cp $INSTALL_PATH/conf/0002_core_migration_v3.0.0.py $INSTALL_PATH/core/migrations/0002_migration_v3_0_0.py - cp $INSTALL_PATH/conf/0002_drylab_migration_v3.0.0.py $INSTALL_PATH/drylab/migrations/0002_migration_v3_0_0.py - cp $INSTALL_PATH/conf/0002_wetlab_migration_v3.0.0.py $INSTALL_PATH/wetlab/migrations/0002_migration_v3_0_0.py - # cp conf/0002_clinic_migration_v2.3.1.py clinic/migrations/0002_migration_v2_3_1.py - cp $INSTALL_PATH/conf/0002_django_utils_migration_v3.0.0.py $INSTALL_PATH/django_utils/migrations/0002_migration_v3_0_0.py - + + echo "checking for database changes" + if python manage.py makemigrations | grep -q "No changes"; then + echo "No migration is required" + else read -p "Do you want to proceed with the migrate command? (Y/N) " -n 1 -r echo # (optional) move to a new line if [[ ! $REPLY =~ ^[Yy]$ ]] ; then echo "Exiting without running migrate command." exit 1 fi - - echo "activate the virtualenv" - source virtualenv/bin/activate echo "Running migrate..." python manage.py migrate echo "Done migrate command." - - else - echo "checking for database changes" - if python manage.py makemigrations | grep -q "No changes"; then - echo "No migration is required" - else - read -p "Do you want to proceed with the migrate command? (Y/N) " -n 1 -r - echo # (optional) move to a new line - if [[ ! $REPLY =~ ^[Yy]$ ]] ; then - echo "Exiting without running migrate command." - exit 1 - fi - echo "Running migrate..." - python manage.py migrate - echo "Done migrate command." - fi - fi + fi echo "Running collect statics..." python manage.py collectstatic From 113691bfd4ebf85ef91874d9f63f3f828dac298d Mon Sep 17 00:00:00 2001 From: luissian Date: Wed, 17 Apr 2024 17:38:34 +0200 Subject: [PATCH 020/446] litin, for implemented issue #78 Allow to search services by service type --- drylab/utils/req_services.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drylab/utils/req_services.py b/drylab/utils/req_services.py index b40ccc018..8398a55f3 100644 --- a/drylab/utils/req_services.py +++ b/drylab/utils/req_services.py @@ -183,7 +183,9 @@ def delete_samples_in_service(sample_list): return deleted_sample_names -def get_children_services(all_tree_services: list[drylab.models.AvailableService]=None)->list[list]: +def get_children_services( + all_tree_services: list[drylab.models.AvailableService] = None, +) -> list[list]: """The function get a list with the children available services from the requested query of available service. If no query is provided, it will return all the children services from the database @@ -197,7 +199,9 @@ def get_children_services(all_tree_services: list[drylab.models.AvailableService each children service """ if all_tree_services is None: - all_tree_services = drylab.models.AvailableService.objects.all().order_by("description") + all_tree_services = drylab.models.AvailableService.objects.all().order_by( + "description" + ) children_services = [] for t_services in all_tree_services: if t_services.get_children(): From 41ac9553e23ce03d2f10eaca5154ed56377f7577 Mon Sep 17 00:00:00 2001 From: luissian Date: Wed, 17 Apr 2024 20:33:19 +0200 Subject: [PATCH 021/446] partial fixing on the issue #264. User can't search service/project by sample name --- drylab/templates/drylab/search_service.html | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drylab/templates/drylab/search_service.html b/drylab/templates/drylab/search_service.html index ea82c9fa1..3af875d0f 100644 --- a/drylab/templates/drylab/search_service.html +++ b/drylab/templates/drylab/search_service.html @@ -149,30 +149,30 @@

Result of your request

-
- {% if services_search_list.username %} - - {% else %} + {% if services_search_list.username %} + + + {% else %} +
- {% endif %} - -
-
- {% if services_search_list.username %} - - {% else %} + +
+
- {% endif %} - -
+ +
+ {% endif %}
From e7868b1faf4b4446688613195afcf1f21d745549 Mon Sep 17 00:00:00 2001 From: luissian Date: Wed, 17 Apr 2024 23:01:22 +0200 Subject: [PATCH 022/446] Implemented issue #158 Unable to convert barcode count to integer --- wetlab/models.py | 2 +- .../scripts/convert_rawtop_counter_to_int.py | 15 +++++++++ wetlab/scripts/rename_sample_sheet_folder.py | 33 ------------------- wetlab/utils/crontab_process.py | 2 +- 4 files changed, 17 insertions(+), 35 deletions(-) create mode 100644 wetlab/scripts/convert_rawtop_counter_to_int.py delete mode 100644 wetlab/scripts/rename_sample_sheet_folder.py diff --git a/wetlab/models.py b/wetlab/models.py index fec88a843..20e190cab 100644 --- a/wetlab/models.py +++ b/wetlab/models.py @@ -759,7 +759,7 @@ class RawTopUnknowBarcodes(models.Model): runprocess_id = models.ForeignKey(RunProcess, on_delete=models.CASCADE) lane_number = models.CharField(max_length=4) top_number = models.CharField(max_length=4) - count = models.CharField(max_length=40) + count = models.IntegerField() sequence = models.CharField(max_length=40) generated_at = models.DateTimeField(auto_now_add=True) diff --git a/wetlab/scripts/convert_rawtop_counter_to_int.py b/wetlab/scripts/convert_rawtop_counter_to_int.py new file mode 100644 index 000000000..cf35a3ce8 --- /dev/null +++ b/wetlab/scripts/convert_rawtop_counter_to_int.py @@ -0,0 +1,15 @@ +import wetlab.models + +def run(): + """ The script implemted the issue #158 Unable to convert barcode count to + integer, to convert the counter that are in a string format, separated + by "," to int. + """ + top_bar_objs = wetlab.models.RawTopUnknowBarcodes.objects.all() + for top_bar_obj in top_bar_objs: + try: + top_bar_obj.count = int(top_bar_obj.count.replace(",", "")) + top_bar_obj.save() + except AttributeError: + continue + return \ No newline at end of file diff --git a/wetlab/scripts/rename_sample_sheet_folder.py b/wetlab/scripts/rename_sample_sheet_folder.py deleted file mode 100644 index 6f33d535a..000000000 --- a/wetlab/scripts/rename_sample_sheet_folder.py +++ /dev/null @@ -1,33 +0,0 @@ -import wetlab.models - - -""" - The script is applicable for the upgrade from 2.3.0 to 3.0.0. - Because the folder where the sample sheets were recorded have been - modified to sample_sheet. All the recorded run must update the field - of sample sheet. - Script reads RunProcess table and check if sample_sheet contains the - SampleSheet string and change it to sample_sheet. -""" - - -def run(): - # update SampleSheets - run_objs = wetlab.models.RunProcess.objects.filter( - sample_sheet__contains="SampleSheet" - ) - for run_obj in run_objs: - old_name = run_obj.get_sample_file() - new_name = old_name.replace("/SampleSheets/", "/sample_sheet/") - run_obj.set_run_sample_sheet(new_name) - # update SampleSheets4LibPrep - lib_user_objs = wetlab.models.LibUserSampleSheet.objects.filter( - sample_sheet__contains="SampleSheets4LibPrep" - ) - for lib_user_obj in lib_user_objs: - old_name = lib_user_obj.get_lib_user_sample_sheet() - new_name = old_name.replace( - "/SampleSheets4LibPrep/", "/sample_sheets_lib_prep/" - ) - lib_user_obj.set_lib_user_sample_sheet(new_name) - return diff --git a/wetlab/utils/crontab_process.py b/wetlab/utils/crontab_process.py index d61ccac60..0e6f9adca 100644 --- a/wetlab/utils/crontab_process.py +++ b/wetlab/utils/crontab_process.py @@ -3047,7 +3047,7 @@ def process_and_store_unknown_barcode_data( unknow_barcode["runprocess_id"] = run_process_obj unknow_barcode["lane_number"] = str(un_lane + 1) unknow_barcode["top_number"] = str(top_number) - unknow_barcode["count"] = "{0:,}".format(int(barcode_line["count"])) + unknow_barcode["count"] = int(barcode_line["count"]) unknow_barcode["sequence"] = barcode_line["sequence"] top_number += 1 wetlab.models.RawTopUnknowBarcodes.objects.create_unknow_barcode( From 29d4a55f54bd46a15c4e5e5c15f1cf708bd3e27f Mon Sep 17 00:00:00 2001 From: luissian Date: Wed, 17 Apr 2024 23:04:05 +0200 Subject: [PATCH 023/446] litin for implemented issue #158 Unable to convert barcode count to integer --- wetlab/scripts/convert_rawtop_counter_to_int.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wetlab/scripts/convert_rawtop_counter_to_int.py b/wetlab/scripts/convert_rawtop_counter_to_int.py index cf35a3ce8..2171c2ea0 100644 --- a/wetlab/scripts/convert_rawtop_counter_to_int.py +++ b/wetlab/scripts/convert_rawtop_counter_to_int.py @@ -1,9 +1,10 @@ import wetlab.models + def run(): - """ The script implemted the issue #158 Unable to convert barcode count to - integer, to convert the counter that are in a string format, separated - by "," to int. + """The script implemted the issue #158 Unable to convert barcode count to + integer, to convert the counter that are in a string format, separated + by "," to int. """ top_bar_objs = wetlab.models.RawTopUnknowBarcodes.objects.all() for top_bar_obj in top_bar_objs: From 5c35dbea001acca0f971cf494b0b438e396f90bb Mon Sep 17 00:00:00 2001 From: luissian Date: Wed, 17 Apr 2024 23:07:12 +0200 Subject: [PATCH 024/446] litin for implemented issue #158 Unable to convert barcode count to integer --- wetlab/scripts/convert_rawtop_counter_to_int.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wetlab/scripts/convert_rawtop_counter_to_int.py b/wetlab/scripts/convert_rawtop_counter_to_int.py index 2171c2ea0..61b6a2706 100644 --- a/wetlab/scripts/convert_rawtop_counter_to_int.py +++ b/wetlab/scripts/convert_rawtop_counter_to_int.py @@ -13,4 +13,4 @@ def run(): top_bar_obj.save() except AttributeError: continue - return \ No newline at end of file + return From db9ca876b5d9eaf6690a31088e65ffcdadf24ff9 Mon Sep 17 00:00:00 2001 From: luissian Date: Thu, 18 Apr 2024 12:27:07 +0200 Subject: [PATCH 025/446] Fixing issue #265 Service state are not in the right order on first_install_tables.json --- conf/first_install_tables.json | 41 +++++++++++++--------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/conf/first_install_tables.json b/conf/first_install_tables.json index a6982e522..a6ced091c 100644 --- a/conf/first_install_tables.json +++ b/conf/first_install_tables.json @@ -20143,16 +20143,6 @@ "show_in_stats": false } }, -{ - "model": "drylab.servicestate", - "pk": 8, - "fields": { - "state_value": "on_hold", - "state_display": "On hold", - "description": "Service is on hold, waiting for user provides more information", - "show_in_stats": true - } -}, { "model": "drylab.resolutionstates", "pk": 1, @@ -20166,36 +20156,36 @@ "model": "drylab.resolutionstates", "pk": 2, "fields": { - "state_value": "approved", - "state_display": "Approved", - "description": "Resolution request has been accepted" + "state_value": "archived", + "state_display": "Archived", + "description": null } }, { "model": "drylab.resolutionstates", "pk": 3, "fields": { - "state_value": "rejected", - "state_display": "Rejected", - "description": "Resolution can not be handle because is not in porfoloio or for any other reason" + "state_value": "approved", + "state_display": "Approved", + "description": "Resolution request has been accepted" } }, { "model": "drylab.resolutionstates", "pk": 4, "fields": { - "state_value": "queued", - "state_display": "Queued", - "description": "Resolution is the the list, waiting for a bioinformatic to be handled" + "state_value": "in_progress", + "state_display": "In Progress", + "description": "Bioinformatic is working on the Resolution" } }, { "model": "drylab.resolutionstates", "pk": 5, "fields": { - "state_value": "in_progress", - "state_display": "In Progress", - "description": "Bioinformatic is working on the Resolution" + "state_value": "rejected", + "state_display": "Rejected", + "description": "Resolution can not be handle because is not in porfoloio or for any other reason" } }, { @@ -20211,9 +20201,9 @@ "model": "drylab.resolutionstates", "pk": 7, "fields": { - "state_value": "archived", - "state_display": "Archived", - "description": null + "state_value": "queued", + "state_display": "Queued", + "description": "Resolution is the the list, waiting for a bioinformatic to be handled" } }, { @@ -20226,3 +20216,4 @@ } } ] + From 4bdf0cf63e565e1bf8b93247f32f23744084a3be Mon Sep 17 00:00:00 2001 From: luissian Date: Thu, 18 Apr 2024 13:05:54 +0200 Subject: [PATCH 026/446] fixing issue #261 Confirmation email after resolution: wrong text and email --- drylab/utils/resolutions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drylab/utils/resolutions.py b/drylab/utils/resolutions.py index 90d8abd85..63e078eeb 100644 --- a/drylab/utils/resolutions.py +++ b/drylab/utils/resolutions.py @@ -404,7 +404,7 @@ def send_resolution_creation_email(email_data): subject_tmp = drylab.config.SUBJECT_RESOLUTION_QUEUED.copy() subject_tmp.insert(1, email_data["service_number"]) subject = " ".join(subject_tmp) - if email_data["status"] == "Accepted": + if email_data["status"] == "accepted": date = email_data["date"].strftime("%d %B, %Y") body_preparation = list( map( From 2f551b4f2652a572e5093939549fac08fb14af6f Mon Sep 17 00:00:00 2001 From: luissian Date: Thu, 18 Apr 2024 17:14:10 +0200 Subject: [PATCH 027/446] Fixing the wetlab part of the issue #264, User can't search service/project by sample name --- wetlab/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wetlab/views.py b/wetlab/views.py index 91e5a1dd7..c23fa8db4 100644 --- a/wetlab/views.py +++ b/wetlab/views.py @@ -2884,7 +2884,7 @@ def display_sample_in_run(request, sample_run_id): if not wetlab.utils.common.is_wetlab_manager(request): # check if user is owner of the run or belongs to the shared user shared_user_ids = wetlab.utils.common.get_allowed_user_for_sharing(request.user) - if sample_run_obj.get_user_id not in shared_user_ids: + if sample_run_obj.get_user_id() not in shared_user_ids: return render( request, "wetlab/display_sample.html", From 1fceb83ff6992fbe7f3949deafa8c58239166515 Mon Sep 17 00:00:00 2001 From: luissian Date: Fri, 19 Apr 2024 13:13:23 +0200 Subject: [PATCH 028/446] loop for all error messages --- wetlab/templates/wetlab/create_new_run.html | 731 ++++++++++++-------- 1 file changed, 425 insertions(+), 306 deletions(-) diff --git a/wetlab/templates/wetlab/create_new_run.html b/wetlab/templates/wetlab/create_new_run.html index 3601d3081..20058a08e 100644 --- a/wetlab/templates/wetlab/create_new_run.html +++ b/wetlab/templates/wetlab/create_new_run.html @@ -1,116 +1,184 @@ {% extends "core/base.html" %} {% load static %} {% block content %} -{%include 'core/jexcel_functionality.html' %} -{% include "wetlab/menu.html" %} -
-
- {% include 'registration/login_inline.html' %} - {% if error_message %} -
-
-
-

Unable to accept your request

-
-

{{error_message}}

+ {% include 'core/jexcel_functionality.html' %} + {% include "wetlab/menu.html" %} +
+
+ {% include 'registration/login_inline.html' %} + {% if error_message %} +
+
+
+
+

Unable to accept your request

+
+
+ {% for error in error_message %}

{{ error }}

{% endfor %} +
-
- {% endif %} - {% if info_message %} -
-
-
-

Successful creation of the run

-
-

{{info_message}}

+ {% endif %} + {% if info_message %} +
+
+
+
+

Successful creation of the run

+
+
+

{{ info_message }}

+
-
- {% endif %} - {% if display_sample_information %} -
-
-
-
-

Create the new Run with pool {{display_sample_information.experiment_name}}

-
-
-
- {% csrf_token %} - - - - - - - - -
Confirm/ Update library preparation indexes to create the new Run {{display_sample_information.experiment_name}}
-
-
-
- - -
-
-
- {% if display_sample_information.instrument %} + {% endif %} + {% if display_sample_information %} +
+
+
+
+
+

Create the new Run with pool {{ display_sample_information.experiment_name }}

+
+
+
+ + {% csrf_token %} + + + + + + +
+ Confirm/ Update library preparation indexes to create the new Run {{ display_sample_information.experiment_name }} +
+
+
- - + +
- {% endif %} -
-
-
-
-
- -
-
-
-
- - +
+ {% if display_sample_information.instrument %} +
+ + +
+ {% endif %}
-
-
-
-
- - +
+
+
+ + +
+
+
+
+ + +
-
-
- - +
+
+
+ + +
- {% if display_sample_information.adapter2 %} +
- - + +
- {% endif %} + {% if display_sample_information.adapter2 %} +
+ + +
+ {% endif %} +
-
-
-
-
Samples that will be included in the run
-
-
-
-
-
- - - - -
-
+ +
+
+
-
- {% elif created_new_run %} -
-
-
-
-

Successful creation of Run {{created_new_run.exp_name}}

-
-
Run name {{created_new_run.exp_name}} is Recorded State
-
-
-
-
-

Click on the link below to check the Run information

-

{{created_new_run.exp_name}}

+ {% elif created_new_run %} +
+
+
+
+
+

Successful creation of Run {{ created_new_run.exp_name }}

+
+
+
Run name {{ created_new_run.exp_name }} is Recorded State
+
+
+
+
+

Click on the link below to check the Run information

+

+ {{ created_new_run.exp_name }} +

+
-
-
-
-
-
Click on the link to download the Sample Sheet File
- Sample Sheet File +
+
+
+
Click on the link to download the Sample Sheet File
+ Sample Sheet File +
-
+
-
+
-
- - {% else %} - - {% if display_pools_for_run.invalid_run_data.heading %} -
-
-
-

Invalid pools

-
-

Some of the samples in the following pools are missing.

-
- - - - {% for value in display_pools_for_run.invalid_run_data.heading%} - - {% endfor %} - - - - {% for p_name, p_code, number, p_id in display_pools_for_run.invalid_run_data.data %} + {% else %} + {% if display_pools_for_run.invalid_run_data.heading %} +
+
+
+
+

Invalid pools

+
+
+

Some of the samples in the following pools are missing.

+
+
{{value}}
+ - - - + {% for value in display_pools_for_run.invalid_run_data.heading %}{% endfor %} - {% endfor %} - - -
{{p_name}}{{p_code}}{{number}}{{ value }}
+ + + {% for p_name, p_code, number, p_id in display_pools_for_run.invalid_run_data.data %} + + {{ p_name }} + {{ p_code }} + {{ number }} + + {% endfor %} + + +
- -
-
- {% endif %} - {% if display_pools_for_run.run_data.heading %} -
-
-
-
-

Run defined, but not completed

-
-
- {% csrf_token %} - - {% for r_name, p_values, r_id in display_pools_for_run.run_data.data %} -
-
-
-

Run name {{r_name }}

-
- - - - {% for value in display_pools_for_run.run_data.heading %} - - {% endfor %} - - - - - {% for p_name, p_code, p_number in p_values %} + + + {% endif %} + {% if display_pools_for_run.run_data.heading %} +
+
+
+
+
+

Run defined, but not completed

+
+
+ + {% csrf_token %} + + {% for r_name, p_values, r_id in display_pools_for_run.run_data.data %} +
+
+
+
+

Run name {{ r_name }}

+
+
+
{{value}} Select Run
+ - - - - + {% for value in display_pools_for_run.run_data.heading %}{% endfor %} + - {% endfor %} - -
{{p_name}}{{p_code}}{{p_number}}{{ value }}Select Run
+ + + {% for p_name, p_code, p_number in p_values %} + + {{ p_name }} + {{ p_code }} + {{ p_number }} + + + + + {% endfor %} + + +
-
- {% endfor %} - - + {% endfor %} + + +
-
- {% endif %} - {% load user_text %} - {% if display_pools_for_run.pool_data %} -
-
-

Fill the information to create the New Run

-
- -
- -{% endblock %} + + {% endblock %} From 21c08a1ccffd17dd1c3ac0bf767595cfdacc8a90 Mon Sep 17 00:00:00 2001 From: luissian Date: Fri, 19 Apr 2024 13:16:25 +0200 Subject: [PATCH 029/446] re-organize the info for LibraryPool --- wetlab/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wetlab/admin.py b/wetlab/admin.py index ae484d5bd..c0b8d5f08 100644 --- a/wetlab/admin.py +++ b/wetlab/admin.py @@ -33,11 +33,11 @@ class LibParameterValueAdmin(admin.ModelAdmin): class LibraryPoolAdmin(admin.ModelAdmin): list_display = ( - "register_user", - "pool_state", "pool_name", + "pool_state", "platform", "pool_code_id", + "register_user", "run_process_id", ) From cb9a603f3855b6fd68264f934a0de0ed19bf4e4e Mon Sep 17 00:00:00 2001 From: luissian Date: Fri, 19 Apr 2024 13:22:23 +0200 Subject: [PATCH 030/446] added missing div --- wetlab/templates/wetlab/create_new_run.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wetlab/templates/wetlab/create_new_run.html b/wetlab/templates/wetlab/create_new_run.html index 20058a08e..f52f7b302 100644 --- a/wetlab/templates/wetlab/create_new_run.html +++ b/wetlab/templates/wetlab/create_new_run.html @@ -312,7 +312,7 @@

Run name {{ r_name }}

- {% for p_name, p_code, p_number in p_values %} + {% for p_name, p_code, p_number in p_values %} {{ p_name }} {{ p_code }} @@ -499,6 +499,7 @@

Not available Pools

- {% endcomment %} - - {% else %} -
-
-
-
-

Statistics per Investigator

-
-
- {% csrf_token %} - -
+ + {% endcomment %} + {% elif researcher_lab_statistics.type == "lab" %} + {% else %} +
+
+
+
+ {% if request.user|has_group:"WetlabManager" %} +
+

Statistics per Investigator or Laboratory

+
+ {% else %} +
+

Statistics per Investigator

+
+ {% endif %} +
+ + {% csrf_token %} + {% if request.user|has_group:"WetlabManager" %} - - +
+ + +
+
+ + +
{% else %} - - +
+ + +
{% endif %} - -
-
-
-
- - +
+
+
+ + +
-
-
-
- - +
+
+ + +
-
- {% if request.user|has_group:"WetlabManager" %} -

Fields marked in Red are mandatory

- {% endif %} - - - -
+ + + +
+
-
-
- {% endif %} -
- - + {% endblock %} diff --git a/wetlab/utils/statistics.py b/wetlab/utils/statistics.py index fc4cc3104..6011d3ce7 100644 --- a/wetlab/utils/statistics.py +++ b/wetlab/utils/statistics.py @@ -317,246 +317,308 @@ def get_per_time_statistics(start_date, end_date): return per_time_statistics -def get_researcher_statistics(researcher_name, start_date, end_date): +def get_researcher_lab_statistics( + researcher_name: str, lab_name: str, start_date: str, end_date: str +) -> dict: """_summary_ - Parameters - ---------- - researcher_name : str - _description_ - start_date : str - _description_ - end_date : str, - _description_, by default None + Args: + researcher_name (str): user name of the researcher + lab_name (str): laboratory/institution name + start_date (str): start date for the statistics + end_date (str): end date for the statistics - Returns - ------- - _type_ - _description_ + Returns: + dict: _description_ """ - researcher_statistics = {} - if not User.objects.filter(username__icontains=researcher_name).exists(): - researcher_statistics["ERROR"] = ( - wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + + def _sequenced_samples_stats( + user_seq_sample_objs: list, + other_user_seq_sample_objs: list, + research_lab_statistics: dict, + ) -> dict: + """_summary_ + + Args: + user_seq_sample_objs (list): list of sequenced samples for the researcher + other_user_seq_sample_objs (list): list of sequenced samples for other researchers + research_lab_statistics (dict): dictionary with the statistics + + Returns: + dict: _description_ + """ + # Collect data for the sequenced sample + research_lab_statistics["seq_samples"] = user_seq_sample_objs.values_list( + "sample_name", + "project_id__project_name", + "run_process_id__run_name", + "run_process_id__used_sequencer__sequencer_name", + ) + research_lab_statistics["seq_table_heading"] = ( + wetlab.config.HEADING_STATISTICS_FOR_SECUENCED_RESEARCHER_SAMPLE ) - return researcher_statistics - user_objs = User.objects.filter(username__icontains=researcher_name) + # pie graph sequencing samples percentage researcher vs others + seq_sample_count = { + researcher_name: user_seq_sample_objs.count(), + "all researchers": other_user_seq_sample_objs.count(), + } + g_data = core.utils.graphics.preparation_3D_pie( + "Percentage of samples", "Research vs all", "ocean", seq_sample_count + ) + research_lab_statistics["seq_sample_research_vs_other_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", + "seq_sample_research_vs_other_graph", + "600", + "300", + "seq_sample_research_vs_other_chart", + "json", + g_data, + ).render() + ) - if len(user_objs) > 1: - researcher_statistics["ERROR"] = ( - wetlab.config.ERROR_MANY_USER_MATCHES_FOR_INPUT_CONDITIONS + # pie graph for sequencers used for sequencing samples + seq_objs = core.models.SequencerInLab.objects.all() + sample_per_sequencer = {} + for seq_obj in seq_objs: + sample_per_sequencer[seq_obj.get_sequencer_name()] = ( + user_seq_sample_objs.filter( + run_process_id__used_sequencer=seq_obj + ).count() + ) + g_data = core.utils.graphics.preparation_3D_pie( + "Sequencer usage", "", "ocean", sample_per_sequencer + ) + research_lab_statistics["research_usage_sequencer_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "pie3d", + "research_usage_sequencer_graph", + "600", + "300", + "research_usage_sequencer_chart", + "json", + g_data, + ).render() + ) + + # chart graph for runs + researcher_runs = {} + runs = list( + user_seq_sample_objs.values_list( + "run_process_id__run_name", flat=True + ).distinct() + ) + for run in runs: + researcher_runs[run] = user_seq_sample_objs.filter( + run_process_id__run_name__exact=run + ).count() + + g_data = core.utils.graphics.preparation_graphic_data( + "Number of samples per run", + "", + "Run name", + "Number of samples", + "ocean", + researcher_runs, + ) + research_lab_statistics["research_run_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "research_run_graph", + "550", + "350", + "research_run_chart", + "json", + g_data, + ).render() + ) + + # chart graph for projects + researcher_projects = {} + projects = list( + user_seq_sample_objs.values_list( + "project_id__project_name", flat=True + ).distinct() + ) + for project in projects: + researcher_projects[project] = user_seq_sample_objs.filter( + project_id__project_name__exact=project + ).count() + g_data = core.utils.graphics.preparation_graphic_data( + "Number of samples per project", + "", + "Project name", + "Number of samples", + "ocean", + researcher_projects, + ) + research_lab_statistics["research_project_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "research_project_graph", + "550", + "350", + "research_project_chart", + "json", + g_data, + ).render() + ) + + # chart graph for Q > 30 on runs + researcher_q_30 = list( + user_seq_sample_objs.values(run_name=F("run_process_id__run_name")) + .annotate(q_30_value=Avg("quality_q30")) + .order_by("run_process_id__run_name") + ) + + g_data = core.utils.graphics.preparation_graphic_data( + "Percentage of samples with Q > 30", + "", + "Run name", + "Percentage of Q>30", + "ocean", + researcher_q_30, + "run_name", + "q_30_value", + ) + research_lab_statistics["research_q_30_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "research_q_30_graph", + "550", + "350", + "research_q_30_chart", + "json", + g_data, + ).render() + ) + + # chart graph for mean + researcher_mean = list( + user_seq_sample_objs.values(run_name=F("run_process_id__run_name")) + .annotate(mean_value=Avg("mean_quality")) + .order_by("run_process_id__run_name") + ) + g_data = core.utils.graphics.preparation_graphic_data( + "Qualiy mean of samples per run", + "", + "Run name", + "Quality mean", + "ocean", + researcher_mean, + "run_name", + "mean_value", ) - return researcher_statistics - researcher_name = user_objs[0].username + research_lab_statistics["research_mean_graphic"] = ( + core.fusioncharts.fusioncharts.FusionCharts( + "column3d", + "research_mean_graph", + "550", + "350", + "research_mean_chart", + "json", + g_data, + ).render() + ) + return research_lab_statistics + # validate date format if start_date != "" and not wetlab.utils.common.check_valid_date_format(start_date): - researcher_statistics["ERROR"] = wetlab.config.ERROR_INVALID_FORMAT_FOR_DATES - return researcher_statistics + research_lab_statistics["ERROR"] = wetlab.config.ERROR_INVALID_FORMAT_FOR_DATES + return research_lab_statistics if end_date != "" and not wetlab.utils.common.check_valid_date_format(start_date): - researcher_statistics["ERROR"] = wetlab.config.ERROR_INVALID_FORMAT_FOR_DATES - return researcher_statistics + research_lab_statistics["ERROR"] = wetlab.config.ERROR_INVALID_FORMAT_FOR_DATES + return research_lab_statistics - # check if start and end date are present in the form + # Filter samples used for sequencing and for preparing library, based on the dates if start_date != "" and end_date != "": - sample_objs = wetlab.models.SamplesInProject.objects.filter( + seq_sample_objs = wetlab.models.SamplesInProject.objects.filter( run_process_id__run_date__range=(start_date, end_date) ) + rec_sample_objs = core.models.Samples.objects.filter( + generated_at__range=(start_date, end_date) + ) elif start_date != "": - sample_objs = wetlab.models.SamplesInProject.objects.filter( + seq_sample_objs = wetlab.models.SamplesInProject.objects.filter( run_process_id__run_date__gte=start_date ) + rec_sample_objs = core.models.Samples.objects.filter( + generated_at__gte=start_date + ) elif end_date != "": - sample_objs = wetlab.models.SamplesInProject.objects.filter( + seq_sample_objs = wetlab.models.SamplesInProject.objects.filter( run_process_id__run_date__lte=end_date ) + rec_sample_objs = core.models.Samples.objects.filter(generated_at__lte=end_date) else: - sample_objs = wetlab.models.SamplesInProject.objects.all() - - other_user_sample_objs = sample_objs.exclude(user_id=user_objs[0]) - user_sample_objs = sample_objs.filter(user_id=user_objs[0]) - if len(user_sample_objs) == 0: - researcher_statistics["ERROR"] = ( - wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS - ) - return researcher_statistics - # sample table - researcher_statistics["samples"] = user_sample_objs.values_list( - "sample_name", - "project_id__project_name", - "run_process_id__run_name", - "run_process_id__used_sequencer__sequencer_name", - ) - researcher_statistics["table_heading"] = ( - wetlab.config.HEADING_STATISTICS_FOR_RESEARCHER_SAMPLE - ) - - # pie graph percentage researcher vs others - per_data_user = {} - per_data_user[researcher_name] = user_sample_objs.count() - per_data_user["all researchers"] = other_user_sample_objs.count() - # heading, sub_title, axis_x_description, axis_y_description, theme, source_data - g_data = core.utils.graphics.preparation_3D_pie( - "Percentage of samples", "Research vs all", "ocean", per_data_user - ) - - researcher_statistics["research_vs_other_graphic"] = ( - core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", - "research_vs_other_graph", - "600", - "300", - "research_vs_other_chart", - "json", - g_data, - ).render() - ) - - # pie graph for sequencers used - # ############################# - seq_objs = core.models.SequencerInLab.objects.all() - sample_per_sequencer = {} - for seq_obj in seq_objs: - sample_per_sequencer[seq_obj.get_sequencer_name()] = user_sample_objs.filter( - run_process_id__used_sequencer=seq_obj - ).count() - g_data = core.utils.graphics.preparation_3D_pie( - "Sequencer usage", "", "ocean", sample_per_sequencer - ) - researcher_statistics["research_usage_sequencer_graphic"] = ( - core.fusioncharts.fusioncharts.FusionCharts( - "pie3d", - "research_usage_sequencer_graph", - "600", - "300", - "research_usage_sequencer_chart", - "json", - g_data, - ).render() - ) - - # chart graph for runs - # #################### - researcher_runs = {} - runs = list( - user_sample_objs.values_list("run_process_id__run_name", flat=True).distinct() - ) - for run in runs: - researcher_runs[run] = user_sample_objs.filter( - run_process_id__run_name__exact=run - ).count() + seq_sample_objs = wetlab.models.SamplesInProject.objects.all() + rec_sample_objs = core.models.Samples.objects.all() + + research_lab_statistics = {} + if researcher_name != "": + researcher_name = researcher_name.strip() + # check if the researcher exists + if not User.objects.filter(username__icontains=researcher_name).exists(): + research_lab_statistics["ERROR"] = ( + wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + ) + return research_lab_statistics + user_objs = User.objects.filter(username__icontains=researcher_name) - g_data = core.utils.graphics.preparation_graphic_data( - "Number of samples per run", - "", - "Run name", - "Number of samples", - "ocean", - researcher_runs, - ) - researcher_statistics["research_run_graphic"] = ( - core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "research_run_graph", - "550", - "350", - "research_run_chart", - "json", - g_data, - ).render() - ) + if len(user_objs) > 1: + research_lab_statistics["ERROR"] = ( + wetlab.config.ERROR_MANY_USER_MATCHES_FOR_INPUT_CONDITIONS + ) + return research_lab_statistics + # get the user name of the researcher + research_lab_statistics["researcher_name"] = user_objs[0].username - # chart graph for projects - # ######################## - researcher_projects = {} - projects = list( - user_sample_objs.values_list("project_id__project_name", flat=True).distinct() - ) - for project in projects: - researcher_projects[project] = user_sample_objs.filter( - project_id__project_name__exact=project - ).count() - g_data = core.utils.graphics.preparation_graphic_data( - "Number of samples per project", - "", - "Project name", - "Number of samples", - "ocean", - researcher_projects, - ) - researcher_statistics["research_project_graphic"] = ( - core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "research_project_graph", - "550", - "350", - "research_project_chart", - "json", - g_data, - ).render() - ) + # get the sequenced samples for the researcher + other_user_seq_sample_objs = seq_sample_objs.exclude(user_id=user_objs[0]) + user_seq_sample_objs = seq_sample_objs.filter(user_id=user_objs[0]) - # chart graph for Q > 30 on runs - # ############################## - researcher_q_30 = list( - user_sample_objs.values(run_name=F("run_process_id__run_name")) - .annotate(q_30_value=Avg("quality_q30")) - .order_by("run_process_id__run_name") - ) + # get the library preparation samples for the researcher + other_user_rec_sample_objs = rec_sample_objs.exclude(sample_user=user_objs[0]) + user_rec_sample_objs = rec_sample_objs.filter(sample_user=user_objs[0]) + import pdb - g_data = core.utils.graphics.preparation_graphic_data( - "Percentage of samples with Q > 30", - "", - "Run name", - "Percentage of Q>30", - "ocean", - researcher_q_30, - "run_name", - "q_30_value", - ) - researcher_statistics["research_q_30_graphic"] = ( - core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "research_q_30_graph", - "550", - "350", - "research_q_30_chart", - "json", - g_data, - ).render() - ) + pdb.set_trace() - # chart graph for mean - # #################### - researcher_mean = list( - user_sample_objs.values(run_name=F("run_process_id__run_name")) - .annotate(mean_value=Avg("mean_quality")) - .order_by("run_process_id__run_name") - ) - g_data = core.utils.graphics.preparation_graphic_data( - "Qualiy mean of samples per run", - "", - "Run name", - "Quality mean", - "ocean", - researcher_mean, - "run_name", - "mean_value", - ) - researcher_statistics["research_mean_graphic"] = ( - core.fusioncharts.fusioncharts.FusionCharts( - "column3d", - "research_mean_graph", - "550", - "350", - "research_mean_chart", - "json", - g_data, - ).render() - ) - researcher_statistics["researcher_name"] = researcher_name + if len(user_seq_sample_objs) == 0 and len(user_rec_sample_objs) == 0: + research_lab_statistics["ERROR"] = ( + wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + ) + return research_lab_statistics + research_lab_statistics["type"] = "researcher" + if len(user_seq_sample_objs) > 0: + research_lab_statistics = _sequenced_samples_stats( + user_seq_sample_objs, + other_user_seq_sample_objs, + research_lab_statistics, + ) + if len(user_rec_sample_objs) > 0: + research_lab_statistics["rec_samples"] = user_rec_sample_objs.values_list( + "sample_name", + "unique_sample_id", + "sample_type__sample_type", + "species__species_name", + "sample_state__sample_state_name", + "sample_project__sample_project_name", + ) + research_lab_statistics["rec_table_heading"] = ( + wetlab.config.HEADING_STATISTICS_FOR_RECORDED_RESEARCHER_SAMPLE + ) + else: + lab_sample_objs = seq_sample_objs.filter(lab_request_id__exact=lab_name) + if len(lab_sample_objs) == 0: + research_lab_statistics["ERROR"] = ( + wetlab.config.ERROR_NO_MATCHES_FOR_INPUT_CONDITIONS + ) + return research_lab_statistics + research_lab_statistics["type"] = "lab" - return researcher_statistics + return research_lab_statistics def get_pending_graphic_data( diff --git a/wetlab/views.py b/wetlab/views.py index c23fa8db4..481b25805 100644 --- a/wetlab/views.py +++ b/wetlab/views.py @@ -1602,12 +1602,18 @@ def stats_per_researcher(request): r_name = request.POST["researchername"] start_date = request.POST["startdate"] end_date = request.POST["enddate"] + lab_name = request.POST["labname"] if "labname" in request.POST else None + # return the form if empty values + if lab_name == "" and lab_name is None: + return render(request, "wetlab/stats_per_researcher.html") - researcher_statistics = wetlab.utils.statistics.get_researcher_statistics( - r_name, start_date, end_date + researcher_lab_statistics = ( + wetlab.utils.statistics.get_researcher_lab_statistics( + r_name, lab_name, start_date, end_date + ) ) - if "ERROR" in researcher_statistics: - error_message = researcher_statistics["ERROR"] + if "ERROR" in researcher_lab_statistics: + error_message = researcher_lab_statistics["ERROR"] return render( request, "wetlab/stats_per_researcher.html", @@ -1617,7 +1623,7 @@ def stats_per_researcher(request): return render( request, "wetlab/stats_per_researcher.html", - {"researcher_statistics": researcher_statistics}, + {"researcher_lab_statistics": researcher_lab_statistics}, ) else: return render(request, "wetlab/stats_per_researcher.html") From 12fd70c95602f024778067b8ba95914da89903fc Mon Sep 17 00:00:00 2001 From: luissian Date: Sun, 5 May 2024 16:38:38 +0200 Subject: [PATCH 055/446] added recorded samples vs other researchers --- .../wetlab/stats_per_researcher.html | 174 +++++++++++------- wetlab/utils/statistics.py | 19 ++ 2 files changed, 125 insertions(+), 68 deletions(-) diff --git a/wetlab/templates/wetlab/stats_per_researcher.html b/wetlab/templates/wetlab/stats_per_researcher.html index f7d8de9d5..64e25346a 100644 --- a/wetlab/templates/wetlab/stats_per_researcher.html +++ b/wetlab/templates/wetlab/stats_per_researcher.html @@ -94,46 +94,48 @@

Statistics results for Investigator {{ researcher_lab_st {% endif %}

}); {% endif %} {% if molecules_availables.molecule_heading %} - // excel for pending molecules + // excel for pending Extraction data var data2 = [{% for values in molecules_availables.data %} [{% for value in values %}'{{value}}',{% endfor %}],{% endfor %} ]; - var table2 = jexcel(document.getElementById('pending_molecules'), { + var table2 = jexcel(document.getElementById('pending_extraction'), { data:data2, columns: [{% for values in molecules_availables.molecule_heading %} {% if forloop.last %} @@ -439,13 +439,29 @@

Not molecule uses have been defined yet

pagination:20, csvFileName:'molecule_use', }); + // Function to check if at least one checkbox in the "select sample" column is set to true + function isAnySelectedColumnChecked() { + var table_data2 = table2.getData(); + // Assuming the "Select sample" column is the third column (index 5) + for (var i = 0; i < table_data2.length; i++) { + if (table_data2[i][5] === true) { // Check if "Select sample" column (index 5) is true + return true; + } + } + return false; + } // send form for molecule in use $(document).ready(function () { $("#selectedOwnerMolecules").submit(function (e) { + if (!isAnySelectedColumnChecked()) { + e.preventDefault(); // Prevent form submission + alert('You must select at least one sample before submitting.'); + return false; + } var table_data2 = table2.getData() var data_json = JSON.stringify(table_data2) $("").attr("type", "hidden") - .attr("name", "pending_molecules") + .attr("name", "pending_extraction") .attr("value", data_json) .appendTo("#selectedOwnerMolecules"); $("#btnSubmit").attr("disabled", true); diff --git a/wetlab/templates/wetlab/modify_sample_project_fields.html b/wetlab/templates/wetlab/modify_sample_project_fields.html index 97cbfc730..82a68113c 100644 --- a/wetlab/templates/wetlab/modify_sample_project_fields.html +++ b/wetlab/templates/wetlab/modify_sample_project_fields.html @@ -134,9 +134,9 @@

Modify sample project fields for {{ sample_project_field.sample_project_name // Function to check if at least one checkbox in the "Used" column is set to true function isAnyUsedColumnChecked() { var table_data1 = table1.getData(); - // Assuming the "Used" column is the third column (index 2) + // Assuming the "Used" column is the third column (index 3) for (var i = 0; i < table_data1.length; i++) { - if (table_data1[i][2] === true) { // Check if "Used" column (index 2) is true + if (table_data1[i][3] === true) { // Check if "Used" column (index 3) is true return true; } } diff --git a/wetlab/views.py b/wetlab/views.py index 53622e3cd..d34b7f394 100644 --- a/wetlab/views.py +++ b/wetlab/views.py @@ -3284,7 +3284,7 @@ def handling_molecules(request): heading = core.core_config.HEADING_FOR_PENDING_MOLECULES.copy() heading.insert(-1, "s_id") molecules, _ = core.utils.samples.get_selection_from_excel_data( - request.POST["pending_molecules"], heading, "Select Molecule", "s_id" + request.POST["pending_extraction"], heading, "Select Molecule", "s_id" ) if len(molecules) == 0: return redirect("handling_molecules") From 6cf9b254988a085b014c46422d0138f1408f6328 Mon Sep 17 00:00:00 2001 From: luissian Date: Fri, 27 Sep 2024 20:40:15 +0200 Subject: [PATCH 160/446] Solved issue #305 Not validated when date in sample project fields contatins also the time --- core/utils/samples.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/utils/samples.py b/core/utils/samples.py index e1e231b9f..c7fedbf32 100644 --- a/core/utils/samples.py +++ b/core/utils/samples.py @@ -413,6 +413,10 @@ def validate_project_data(project_data, project_name, sample_validation=False): sample_dict["Validation error"].append(" ".join(error_cause)) """ if field_type == "Date" and sample[field_name] != "": + # if field contains also time, then removed it + sample[field_name] = re.sub( + r"\s\d{2}:\d{2}:\d{2}", "", sample[field_name] + ) try: datetime.datetime.strptime(sample[field_name], "%Y-%m-%d") except Exception: From 2cad3c55326fe5b815830046846b7aedc8e34300 Mon Sep 17 00:00:00 2001 From: luissian Date: Fri, 27 Sep 2024 21:03:10 +0200 Subject: [PATCH 161/446] Solved issue #318 Duplicate sample when selecting it for to do next after extraction --- core/utils/samples.py | 4 +++- wetlab/views.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/utils/samples.py b/core/utils/samples.py index c7fedbf32..3e7b22f7e 100644 --- a/core/utils/samples.py +++ b/core/utils/samples.py @@ -702,7 +702,9 @@ def create_table_molecule_pending_use(sample_list, app_name): use_type["data"] = list( core.models.MoleculePreparation.objects.filter( molecule_used_for=None, sample__in=sample_list - ).values_list("sample__sample_name", "molecule_code_id", "pk") + ) + .exclude(state__molecule_state_name="Completed") + .values_list("sample__sample_name", "molecule_code_id", "pk") ) if len(use_type["data"]) > 0: if core.models.MoleculeUsedFor.objects.filter( diff --git a/wetlab/views.py b/wetlab/views.py index d34b7f394..57983ecd1 100644 --- a/wetlab/views.py +++ b/wetlab/views.py @@ -3371,7 +3371,6 @@ def handling_molecules(request): molecule_use_defined = core.utils.samples.check_if_molecule_use_defined( __package__ ) - return render( request, "wetlab/handling_molecules.html", From 2a51945712e80ca09847390d1ae2143654756958 Mon Sep 17 00:00:00 2001 From: luissian Date: Fri, 27 Sep 2024 23:02:11 +0200 Subject: [PATCH 162/446] solved issue #319 incorrect state set for molecule after filling the extraction data --- conf/first_install_tables.json | 17 +++++++++++--- core/core_config.py | 6 ++--- core/models.py | 3 ++- core/utils/samples.py | 10 ++++----- .../templates/wetlab/handling_molecules.html | 22 +++++++++---------- wetlab/views.py | 3 ++- 6 files changed, 37 insertions(+), 24 deletions(-) diff --git a/conf/first_install_tables.json b/conf/first_install_tables.json index cb3c1345c..9df3f5ead 100644 --- a/conf/first_install_tables.json +++ b/conf/first_install_tables.json @@ -324,21 +324,32 @@ "model": "core.statesformolecule", "pk": 3, "fields": { - "molecule_state_name": "Defined" + "molecule_state_name": "defined", + "molecule_state_display": "Defined" } }, { "model": "core.statesformolecule", "pk": 4, "fields": { - "molecule_state_name": "Assigned Protocol" + "molecule_state_name": "assigned_parameters", + "molecule_state_display": "Assigned Parameters" } }, { "model": "core.statesformolecule", "pk": 5, "fields": { - "molecule_state_name": "Completed" + "molecule_state_name": "completed", + "molecule_state_display": "Completed" + } +}, +{ + "model": "core.statesformolecule", + "pk": 6, + "fields": { + "molecule_state_name": "sent_external", + "molecule_state_display": "Sent to external service" } }, { diff --git a/core/core_config.py b/core/core_config.py index 1c5d034a4..d41b21573 100644 --- a/core/core_config.py +++ b/core/core_config.py @@ -22,7 +22,7 @@ ] HEADING_FOR_MOLECULE_ADDING_PARAMETERS = [ "Sample", - "Molecule Code ID", + "Extraction Code ID", "Lot Commercial Kit", ] @@ -74,8 +74,8 @@ HEADING_FOR_SELECTING_MOLECULE_USE = [ "Sample Name", - "Molecule CodeID", - "Molecule use for", + "Extraction CodeID", + "Sample continue on", ] # ################ PROTOCOL PARAMETER SETTINGS ############################## diff --git a/core/models.py b/core/models.py index 32d32ff21..5c51efb64 100644 --- a/core/models.py +++ b/core/models.py @@ -372,6 +372,7 @@ def get_id(self): class StatesForMolecule(models.Model): molecule_state_name = models.CharField(max_length=50) + molecule_state_display = models.CharField(max_length=80, null=True, blank=True) class Meta: db_table = "core_states_for_molecule" @@ -1495,7 +1496,7 @@ def create_molecule(self, molecule_data): protocol_used=protocol_used_obj, sample=molecule_data["sample"], molecule_type=molecule_used_obj, - state=StatesForMolecule.objects.get(molecule_state_name__exact="Defined"), + state=StatesForMolecule.objects.get(molecule_state_name__exact="defined"), molecule_code_id=molecule_data["molecule_code_id"], molecule_extraction_date=molecule_data["molecule_extraction_date"], extraction_type=molecule_data["extraction_type"], diff --git a/core/utils/samples.py b/core/utils/samples.py index 3e7b22f7e..3c67d8a2d 100644 --- a/core/utils/samples.py +++ b/core/utils/samples.py @@ -565,7 +565,7 @@ def add_molecule_protocol_parameters(data, parameters): molecule_parameter_value ) - molecule_obj.set_state("Completed") + molecule_obj.set_state("assigned_parameters") # Update sample state sample_obj = molecule_obj.get_sample_obj() sample_obj.set_state("Pending for use") @@ -701,10 +701,10 @@ def create_table_molecule_pending_use(sample_list, app_name): use_type = {} use_type["data"] = list( core.models.MoleculePreparation.objects.filter( - molecule_used_for=None, sample__in=sample_list - ) - .exclude(state__molecule_state_name="Completed") - .values_list("sample__sample_name", "molecule_code_id", "pk") + molecule_used_for=None, + sample__in=sample_list, + state__molecule_state_name="assigned_parameters", + ).values_list("sample__sample_name", "molecule_code_id", "pk") ) if len(use_type["data"]) > 0: if core.models.MoleculeUsedFor.objects.filter( diff --git a/wetlab/templates/wetlab/handling_molecules.html b/wetlab/templates/wetlab/handling_molecules.html index 84a9f4e7e..89f5270d7 100644 --- a/wetlab/templates/wetlab/handling_molecules.html +++ b/wetlab/templates/wetlab/handling_molecules.html @@ -108,15 +108,15 @@

Information missing

-

Update molecules data with defined parameters

+

Update sample extraction data with defined parameters

+ name="addExtractionParameters" + id="addExtractionParameters"> {% csrf_token %} - + {% for prot, value_dict in molecule_parameters.items %} Protocol: {{ prot }}

-

Molecules have been updated with required parameters.

+

Samples have been updated with Extraction required parameters.

@@ -208,7 +208,7 @@

Molecules are updated with their use.

type="button" role="tab" aria-controls="n_samples" - aria-selected="true">New Samples + aria-selected="true">Recorded Samples @@ -190,7 +190,7 @@

Molecules are updated with their use.

+ onclick="location.href ='/wetlab/manageLibraryPreparation' ;" />
@@ -247,7 +247,7 @@

Select the molecules to add Extraction information

{% if sample_availables.sample_heading %} Select the Samples to add Pending extraction information
{% if molecules_availables %} @@ -335,7 +335,7 @@

Select the use that will be given for the sample

{% if molecule_use_defined %} {% if pending_to_use.heading %} diff --git a/wetlab/templates/wetlab/menu.html b/wetlab/templates/wetlab/menu.html index f78d15aee..931d6da7a 100644 --- a/wetlab/templates/wetlab/menu.html +++ b/wetlab/templates/wetlab/menu.html @@ -99,10 +99,10 @@ Record Samples
  • - Handling Molecules + Manage Molecules
  • - Handling Library Preparation + Manage Library Preparation
  • Pending Sample Preparation diff --git a/wetlab/urls.py b/wetlab/urls.py index f2b820df8..be602122b 100644 --- a/wetlab/urls.py +++ b/wetlab/urls.py @@ -130,12 +130,12 @@ name="display_user_lot_kit", ), path( - "handlingLibraryPreparation", - wetlab.views.handling_library_preparation, - name="handling_library_preparation", + "manageLibraryPreparation", + wetlab.views.manage_library_preparation, + name="manage_library_preparation", ), path( - "handlingMolecules", wetlab.views.handling_molecules, name="handling_molecules" + "manageMolecules", wetlab.views.manage_molecules, name="manage_molecules" ), path("initialSettings", wetlab.views.initial_settings, name="initial_settings"), path( diff --git a/wetlab/utils/crontab_process.py b/wetlab/utils/crontab_process.py index 3033496ef..dfdb6390a 100644 --- a/wetlab/utils/crontab_process.py +++ b/wetlab/utils/crontab_process.py @@ -435,7 +435,7 @@ def check_sequencer_run_is_completed( int(conversion_attributes.create_time) ).strftime("%Y-%m-%d %H:%M:%S") logger.debug( - "%s : End function for handling NextSeq run with exception", + "%s : End function for manage NextSeq run with exception", experiment_name, ) return "completed", run_completion_date @@ -540,7 +540,7 @@ def copy_sample_sheet_to_remote_folder( + run_folder ) wetlab.utils.common.logging_errors(string_message, True, False) - handling_errors_in_run(experiment_name, "23") + manage_errors_in_run(experiment_name, "23") logger.debug( "%s : End function for copy_sample_sheet_to_remote_folder with exception", experiment_name, @@ -915,7 +915,7 @@ def get_samba_shared_folder(): ) -def handling_errors_in_run(experiment_name, error_code): +def manage_errors_in_run(experiment_name, error_code): """ Description: Function will manage the error situation where the run must be @@ -927,7 +927,7 @@ def handling_errors_in_run(experiment_name, error_code): True """ logger = logging.getLogger(__name__) - logger.debug("%s : Starting function handling_errors_in_run", experiment_name) + logger.debug("%s : Starting function manage_errors_in_run", experiment_name) logger.info("%s : Set run to ERROR state", experiment_name) if wetlab.models.RunProcess.objects.filter( run_name__exact=experiment_name @@ -941,7 +941,7 @@ def handling_errors_in_run(experiment_name, error_code): logger.info( "%s : experiment name is not defined yet in database", experiment_name ) - logger.debug("%s : End function handling_errors_in_run", experiment_name) + logger.debug("%s : End function manage_errors_in_run", experiment_name) return True diff --git a/wetlab/utils/crontab_update_run.py b/wetlab/utils/crontab_update_run.py index 645e6d593..5bb82f6e7 100644 --- a/wetlab/utils/crontab_update_run.py +++ b/wetlab/utils/crontab_update_run.py @@ -148,7 +148,7 @@ def search_update_new_runs(request_reason): run_process_obj = wetlab.utils.crontab_process.get_run_process_obj_or_create_if_not_exists( new_run ) - wetlab.utils.crontab_process.handling_errors_in_run(new_run, "21") + wetlab.utils.crontab_process.manage_errors_in_run(new_run, "21") else: logger.debug( "%s : RunParameter not in folder run. Allowing more time", @@ -175,7 +175,7 @@ def search_update_new_runs(request_reason): new_run + " : Experiment name field was not found in file" ) wetlab.utils.common.logging_errors(string_message, False, False) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( new_run, "7" ) else: @@ -217,7 +217,7 @@ def search_update_new_runs(request_reason): run_process_obj = wetlab.utils.crontab_process.get_run_process_obj_or_create_if_not_exists( experiment_name ) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, "20" ) # cleaning up the RunParameter in local temporaty file @@ -320,7 +320,7 @@ def search_update_new_runs(request_reason): + " : Description field does not contains userid or userid is not defined in iskylims." ) logger.info(string_message) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, "1" ) continue @@ -358,7 +358,7 @@ def search_update_new_runs(request_reason): + new_run ) wetlab.utils.common.logging_errors(string_message, True, False) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, "23" ) logger.debug( @@ -487,7 +487,7 @@ def manage_run_in_recorded_state(conn, run_process_objs): get_remote_sample_sheet waiting_time_expired logging_errors - handling_errors_in_run + manage_errors_in_run assign_projects_to_run assign_used_library_in_run store_sample_sheet_if_not_defined_in_run @@ -543,7 +543,7 @@ def manage_run_in_recorded_state(conn, run_process_objs): + run_folder ) wetlab.utils.common.logging_errors(string_message, False, False) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, 19 ) logger.debug( @@ -595,7 +595,7 @@ def manage_run_in_sample_sent_processing_state(conn, run_process_objs): Functions: check_log_for_run_completions waiting_time_expired - handling_errors_in_run + manage_errors_in_run Return: None """ @@ -604,7 +604,7 @@ def manage_run_in_sample_sent_processing_state(conn, run_process_objs): for run_process_obj in run_process_objs: experiment_name = run_process_obj.get_run_name() logger.info( - "%s : Start handling in manage_run_in_sample_sent_processing_state function", + "%s : Start manage in manage_run_in_sample_sent_processing_state function", experiment_name, ) platform = run_process_obj.get_run_platform() @@ -613,7 +613,7 @@ def manage_run_in_sample_sent_processing_state(conn, run_process_objs): experiment_name + " : Used sequencer or the platform is not defined" ) wetlab.utils.common.logging_errors(string_message, False, False) - wetlab.utils.crontab_process.handling_errors_in_run(experiment_name, 24) + wetlab.utils.crontab_process.manage_errors_in_run(experiment_name, 24) logger.info( "%s ERROR in manage_run_in_sample_sent_processing_state function", experiment_name, @@ -651,7 +651,7 @@ def manage_run_in_sample_sent_processing_state(conn, run_process_objs): elif run_status == "cancelled": run_process_obj.set_run_state("cancelled") run_process_obj.set_run_completion_date(run_completion_date) - wetlab.utils.crontab_process.handling_errors_in_run(experiment_name, 34) + wetlab.utils.crontab_process.manage_errors_in_run(experiment_name, 34) string_message = experiment_name + "was cancelled on the sequencer" wetlab.utils.common.logging_warnings(string_message, True) logger.debug( @@ -673,7 +673,7 @@ def manage_run_in_sample_sent_processing_state(conn, run_process_objs): + " is not defined in wetlab.config.py file (on PLATFORM_WAY_TO_CHECK_RUN_COMPLETION variable) " ) wetlab.utils.common.logging_errors(string_message, False, False) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, run_status["ERROR"] ) logger.debug( @@ -709,7 +709,7 @@ def manage_run_in_sample_sent_processing_state(conn, run_process_objs): + run_folder ) wetlab.utils.common.logging_errors(string_message, False, False) - wetlab.utils.crontab_process.handling_errors_in_run(experiment_name, 9) + wetlab.utils.crontab_process.manage_errors_in_run(experiment_name, 9) logger.debug( "%s : End manage_run_in_sample_sent_processing_state function", experiment_name, @@ -732,7 +732,7 @@ def manage_run_in_processed_run_state(conn, run_process_objs): Functions: delete_existing_run_metrics_table_processed get_run_metric_files - handling_errors_in_run + manage_errors_in_run delete_run_metric_files parsing_run_metrics_files create_run_metric_graphics @@ -744,7 +744,7 @@ def manage_run_in_processed_run_state(conn, run_process_objs): for run_process_obj in run_process_objs: experiment_name = run_process_obj.get_run_name() logger.info( - "%s : Start handling in manage_run_in_processed_run_state function", + "%s : Start manage in manage_run_in_processed_run_state function", experiment_name, ) run_folder = ( @@ -786,7 +786,7 @@ def manage_run_in_processed_run_state(conn, run_process_objs): experiment_name + " : Unable to collect all files for run metrics" ) wetlab.utils.common.logging_errors(string_message, True, False) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, run_metric_files["ERROR"] ) wetlab.utils.crontab_process.delete_run_metric_files(experiment_name) @@ -834,7 +834,7 @@ def manage_run_in_processed_run_state(conn, run_process_objs): experiment_name + " : Unable to save graphics for run metrics" ) wetlab.utils.common.logging_errors(string_message, True, False) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, run_graphics["ERROR"] ) wetlab.utils.crontab_process.delete_existing_run_metrics_table_processed( @@ -880,7 +880,7 @@ def manage_run_in_processing_bcl2fastq_state(conn, run_process_objs): for run_process_obj in run_process_objs: experiment_name = run_process_obj.get_run_name() logger.info( - "%s : Start handling in manage_run_in_processing_bcl2fastq_state function", + "%s : Start manage in manage_run_in_processing_bcl2fastq_state function", experiment_name, ) run_folder = wetlab.models.RunningParameters.objects.get( @@ -914,7 +914,7 @@ def manage_run_in_processing_bcl2fastq_state(conn, run_process_objs): + " : Aborting the process. No Run completion date was defined." ) wetlab.utils.common.logging_errors(string_message, True, False) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, 30 ) logger.debug( @@ -937,7 +937,7 @@ def manage_run_in_processing_bcl2fastq_state(conn, run_process_objs): + run_folder ) wetlab.utils.common.logging_errors(string_message, True, False) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, bcl2fastq_finish_date["ERROR"] ) continue @@ -948,7 +948,7 @@ def manage_run_in_processing_bcl2fastq_state(conn, run_process_objs): + run_folder ) wetlab.utils.common.logging_errors(string_message, True, False) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, bcl2fastq_finish_date["ERROR"] ) continue @@ -956,7 +956,7 @@ def manage_run_in_processing_bcl2fastq_state(conn, run_process_objs): run_process_obj.set_run_state("processed_bcl2fastq") logger.info("%s : Updated to Processed Bcl2Fastq state", experiment_name) logger.info( - "%s : End handling in manage_run_in_processing_bcl2fastq_state function", + "%s : End manage in manage_run_in_processing_bcl2fastq_state function", experiment_name, ) logger.debug(" End function manage_run_in_processing_bcl2fastq_state") @@ -993,7 +993,7 @@ def manage_run_in_processed_bcl2fastq_state(conn, run_process_objs): for run_process_obj in run_process_objs: experiment_name = run_process_obj.get_run_name() logger.info( - "%s : Start handling in manage_run_in_processed_bcl2fastq_state function", + "%s : Start manage in manage_run_in_processed_bcl2fastq_state function", experiment_name, ) run_param_obj = wetlab.models.RunningParameters.objects.get( @@ -1014,7 +1014,7 @@ def manage_run_in_processed_bcl2fastq_state(conn, run_process_objs): + " : Aborting the process. Unable to reach demultiplexing files " ) wetlab.utils.common.logging_errors(string_message, True, False) - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, demux_files["ERROR"] ) continue @@ -1028,7 +1028,7 @@ def manage_run_in_processed_bcl2fastq_state(conn, run_process_objs): + " : Sequencer used in the run has not defined the number of lanes" ) wetlab.utils.common.logging_errors(string_message, True, False) - wetlab.utils.crontab_process.handling_errors_in_run(experiment_name, 32) + wetlab.utils.crontab_process.manage_errors_in_run(experiment_name, 32) logger.debug( "%s : Aborting the process. Number of lanes not defined on the sequencer", experiment_name, @@ -1096,7 +1096,7 @@ def manage_run_in_processed_bcl2fastq_state(conn, run_process_objs): ) wetlab.utils.common.logging_errors(string_message, True, False) if key_error.args[0] == 33: - wetlab.utils.crontab_process.handling_errors_in_run( + wetlab.utils.crontab_process.manage_errors_in_run( experiment_name, "33" ) else: @@ -1128,7 +1128,7 @@ def manage_run_in_processed_bcl2fastq_state(conn, run_process_objs): experiment_name + " : Error when fetching the disk utilization" ) wetlab.utils.common.logging_errors(string_message, True, False) - wetlab.utils.crontab_process.handling_errors_in_run(experiment_name, "17") + wetlab.utils.crontab_process.manage_errors_in_run(experiment_name, "17") logger.debug( "%s : End function manage_run_in_processed_bcl2fast2_run with error", experiment_name, diff --git a/wetlab/utils/run.py b/wetlab/utils/run.py index 761e257e2..80f539d62 100644 --- a/wetlab/utils/run.py +++ b/wetlab/utils/run.py @@ -813,7 +813,7 @@ def get_pool_info(pools_to_update): HEADING_FOR_SELECTING_POOLS HEADING_FOR_INCOMPLETED_SELECTION_POOLS Functions: - get_lot_reagent_commercial_kits # located at core/utils/handling_commercial_kits + get_lot_reagent_commercial_kits # located at core/utils/manage_commercial_kits Return: pool_info """ diff --git a/wetlab/views.py b/wetlab/views.py index 4e1401ee2..9acb70419 100644 --- a/wetlab/views.py +++ b/wetlab/views.py @@ -2984,7 +2984,7 @@ def display_type_of_sample(request, sample_type_id): @login_required -def handling_library_preparation(request): +def manage_library_preparation(request): if wetlab.utils.common.is_wetlab_manager(request): samples_in_lib_prep = wetlab.utils.library.get_samples_for_library_preparation() else: @@ -2999,7 +2999,7 @@ def handling_library_preparation(request): if len(samples_in_lib_prep_protocol) == 0: return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", {"stored_lib_prep": samples_in_lib_prep}, ) library_preparation_objs = ( @@ -3014,7 +3014,7 @@ def handling_library_preparation(request): ) return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", {"lib_prep_protocol_parameters": lib_prep_protocol_parameters}, ) @@ -3025,7 +3025,7 @@ def handling_library_preparation(request): error_message = "No selected library preparation was chosen" return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", { "samples_in_lib_prep": samples_in_lib_prep, "error_message": error_message, @@ -3044,7 +3044,7 @@ def handling_library_preparation(request): request.session["lib_prep_protocol_parameters"] = lib_prep_protocol_parameters return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", {"lib_prep_protocol_parameters": lib_prep_protocol_parameters}, ) @@ -3061,7 +3061,7 @@ def handling_library_preparation(request): ) return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", { "error_message": error_message, "lib_prep_protocol_parameters": lib_prep_protocol_parameters, @@ -3069,7 +3069,7 @@ def handling_library_preparation(request): ) return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", {"stored_params": stored_params}, ) @@ -3087,7 +3087,7 @@ def handling_library_preparation(request): data["ERROR"].append(wetlab.config.ERROR_UNABLE_TO_DELETE_USER_FILE) return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", { "error_message": data["ERROR"], "samples_in_lib_prep": request.session.get("samples_in_lib_prep"), @@ -3109,7 +3109,7 @@ def handling_library_preparation(request): ) return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", { "error_message": user_id_in_s_sheet["ERROR"], "samples_in_lib_prep": request.session.get( @@ -3129,7 +3129,7 @@ def handling_library_preparation(request): ) return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", { "error_message": valid_data["ERROR"], "samples_in_lib_prep": request.session.get("samples_in_lib_prep"), @@ -3159,7 +3159,7 @@ def handling_library_preparation(request): display_sample_sheet["user_list"] = wetlab.utils.common.get_userid_list() return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", {"display_sample_sheet": display_sample_sheet}, ) @@ -3172,7 +3172,7 @@ def handling_library_preparation(request): if "ERROR" in store_data_result: return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", { "error_message": store_data_result["ERROR"], "samples_in_lib_prep": request.session.get("samples_in_lib_prep"), @@ -3181,7 +3181,7 @@ def handling_library_preparation(request): stored_index = "True" return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", {"stored_index": stored_index}, ) @@ -3192,7 +3192,7 @@ def handling_library_preparation(request): ) return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", {"additional_kits": additional_kits}, ) @@ -3213,24 +3213,24 @@ def handling_library_preparation(request): additional_kits["data"] = json.loads(request.POST["protocol_data"]) return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", {"error_message": error_message, "additional_kits": additional_kits}, ) return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", {"stored_additional_kits": stored_additional_kits}, ) else: return render( request, - "wetlab/handling_library_preparation.html", + "wetlab/manage_library_preparation.html", {"samples_in_lib_prep": samples_in_lib_prep}, ) -def handling_molecules(request): +def manage_molecules(request): if request.method == "POST" and request.POST["action"] == "selectedMolecules": # If no samples are selected , call again this function to display again the sample list heading = core.core_config.HEADING_FOR_DEFINED_SAMPLES.copy() @@ -3239,20 +3239,20 @@ def handling_molecules(request): request.POST["selected_samples"], heading, "To be included", "s_id" ) if len(samples) == 0: - return redirect("handling_molecules") + return redirect("manage_molecules") molecule_protocol = core.utils.samples.get_table_record_molecule( samples, __package__ ) if "ERROR" in molecule_protocol: return render( request, - "wetlab/handling_molecules.html", + "wetlab/manage_molecules.html", {"error_message": "There was no valid sample selected "}, ) return render( request, - "wetlab/handling_molecules.html", + "wetlab/manage_molecules.html", {"molecule_protocol": molecule_protocol}, ) @@ -3266,14 +3266,14 @@ def handling_molecules(request): request.POST["molecule_data"], heading, None, "s_id" ) if len(samples) == 0: - return redirect("handling_molecules") + return redirect("manage_molecules") molecule_recorded = core.utils.samples.record_extract_protocol( samples, excel_data, heading, request.user, __package__ ) if "incomplete" in molecule_recorded: return render( request, - "wetlab/handling_molecules.html", + "wetlab/manage_molecules.html", {"molecule_recorded": molecule_recorded}, ) @@ -3285,13 +3285,13 @@ def handling_molecules(request): if len(molecule_parameters) == 0: return render( request, - "wetlab/handling_molecules.html", + "wetlab/manage_molecules.html", {"molecule_parameters_updated": True}, ) protocol_list = ";".join(list(molecule_parameters.keys())) return render( request, - "wetlab/handling_molecules.html", + "wetlab/manage_molecules.html", { "molecule_parameters": molecule_parameters, "protocol_list": protocol_list, @@ -3307,7 +3307,7 @@ def handling_molecules(request): request.POST["pending_extraction"], heading, "Select Molecule", "s_id" ) if len(molecules) == 0: - return redirect("handling_molecules") + return redirect("manage_molecules") protocols = core.utils.samples.group_molecules_by_protocol(molecules) molecule_parameters = ( @@ -3316,7 +3316,7 @@ def handling_molecules(request): protocol_list = ";".join(list(molecule_parameters.keys())) return render( request, - "wetlab/handling_molecules.html", + "wetlab/manage_molecules.html", { "molecule_parameters": molecule_parameters, "protocol_list": protocol_list, @@ -3341,7 +3341,7 @@ def handling_molecules(request): return render( request, - "wetlab/handling_molecules.html", + "wetlab/manage_molecules.html", {"molecule_parameters_updated": True}, ) @@ -3352,11 +3352,11 @@ def handling_molecules(request): request.POST["sample_continues_on"], heading, "Sample continues on", "m_id" ) if len(molecules) == 0: - return redirect("handling_molecules") + return redirect("manage_molecules") molecule_use = core.utils.samples.set_molecule_use(select_use, __package__) return render( request, - "wetlab/handling_molecules.html", + "wetlab/manage_molecules.html", {"molecule_use": molecule_use}, ) @@ -3396,7 +3396,7 @@ def handling_molecules(request): return render( request, - "wetlab/handling_molecules.html", + "wetlab/manage_molecules.html", { "sample_availables": sample_availables, "molecules_availables": molecules_availables, @@ -3460,7 +3460,7 @@ def repeat_molecule_extraction(request): return render( request, - "wetlab/handling_molecules.html", + "wetlab/manage_molecules.html", {"molecule_protocol": molecule_protocol}, ) # return to the main page because the page was not requested for the right page From 60ebe432316b4f80eafe5b97bb9ea891e4b7e4bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Tue, 28 Oct 2025 13:33:35 +0100 Subject: [PATCH 256/446] rename file --- .../wetlab/{handling_molecules.html => manage_molecules.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename wetlab/templates/wetlab/{handling_molecules.html => manage_molecules.html} (100%) diff --git a/wetlab/templates/wetlab/handling_molecules.html b/wetlab/templates/wetlab/manage_molecules.html similarity index 100% rename from wetlab/templates/wetlab/handling_molecules.html rename to wetlab/templates/wetlab/manage_molecules.html From 12395ea5db904ad36a447b0ab13d5e12a2c6e298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Tue, 28 Oct 2025 13:35:27 +0100 Subject: [PATCH 257/446] rename file --- ...g_library_preparation.html => manage_library_preparation.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename wetlab/templates/wetlab/{handling_library_preparation.html => manage_library_preparation.html} (100%) diff --git a/wetlab/templates/wetlab/handling_library_preparation.html b/wetlab/templates/wetlab/manage_library_preparation.html similarity index 100% rename from wetlab/templates/wetlab/handling_library_preparation.html rename to wetlab/templates/wetlab/manage_library_preparation.html From c0eb62830f0f683e502c25d123708de427a163ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Tue, 28 Oct 2025 14:18:08 +0100 Subject: [PATCH 258/446] fix error when fetching create time attribute from samba --- wetlab/utils/common.py | 36 +++++++++++---- wetlab/utils/crontab_update_run.py | 71 +++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 28 deletions(-) diff --git a/wetlab/utils/common.py b/wetlab/utils/common.py index c2590ad4b..05b3e31e3 100644 --- a/wetlab/utils/common.py +++ b/wetlab/utils/common.py @@ -4,7 +4,7 @@ import re import socket import traceback -from datetime import datetime +from datetime import datetime, timezone from logging.config import fileConfig from django.contrib.auth.models import User @@ -79,18 +79,38 @@ def check_valid_date_format(date): def get_samba_atribute_data(conn, shared_folder, remote_path, attribute=None): - """_summary_ + """ + Fetch Samba metadata for a remote path and optionally return a specific attribute. + Time-based attributes are returned as timezone-aware UTC datetimes when possible. Args: - conn (_type_): _description_ - shared_folder (_type_): _description_ - remote_path (_type_): _description_ - attribute (_type_, optional): _description_. Defaults to None. + conn (SMBConnection): Active Samba connection used to query metadata. + shared_folder (str): Name of the Samba share that contains the target path. + remote_path (str): Path inside the share whose attributes are requested. + attribute (str, optional): Specific attribute name to return (for example, + `create_time`). Defaults to None, which returns the full attributes object. + + Returns: + Any: Requested attribute value (converted to UTC datetime for time-based fields) + or the full attributes object when `attribute` is None. Returns None if the + requested attribute is not available. """ attributes = conn.getAttributes(shared_folder, remote_path) if attribute is not None: - atr_field = "attributes." + attribute - return eval(atr_field) + if not hasattr(attributes, attribute): + return None + attr_value = getattr(attributes, attribute) + if isinstance(attr_value, datetime): + return ( + attr_value + if attr_value.tzinfo is not None + else attr_value.replace(tzinfo=timezone.utc) + ) + if attribute in {"create_time", "last_access_time", "last_write_time"} and isinstance( + attr_value, (int, float) + ): + return datetime.fromtimestamp(attr_value, tz=timezone.utc) + return attr_value return attributes diff --git a/wetlab/utils/crontab_update_run.py b/wetlab/utils/crontab_update_run.py index 5bb82f6e7..cd71cc98e 100644 --- a/wetlab/utils/crontab_update_run.py +++ b/wetlab/utils/crontab_update_run.py @@ -13,6 +13,29 @@ import wetlab.utils.samplesheet +def _as_utc_datetime(value): + """ + Normalize Samba timestamps into timezone-aware UTC datetimes. + + Args: + value (datetime.datetime | int | float | None): Timestamp or datetime obtained + from Samba metadata. + + Returns: + datetime.datetime | None: Timezone-aware UTC datetime when conversion succeeds, + otherwise None. + """ + if isinstance(value, datetime.datetime): + return ( + value.astimezone(datetime.timezone.utc) + if value.tzinfo is not None + else value.replace(tzinfo=datetime.timezone.utc) + ) + if isinstance(value, (int, float)): + return datetime.datetime.fromtimestamp(value, tz=datetime.timezone.utc) + return None + + def get_list_processed_runs(): """ Description: @@ -121,17 +144,22 @@ def search_update_new_runs(request_reason): experiment_name = "Experiment name NOT FOUND" # check the run folder creation date to allow more time before # setting the run to error - f_created_date = int( + created_time = _as_utc_datetime( wetlab.utils.common.get_samba_atribute_data( conn, wetlab.utils.crontab_process.get_samba_shared_folder(), new_run, "create_time", - ).timestamp() + ) ) - time_to_check = datetime.datetime.fromtimestamp( - f_created_date, tz=datetime.timezone.utc - ).date() + if created_time is None: + logger.warning( + "%s : Unable to determine creation time for run folder %s", + experiment_name, + new_run, + ) + created_time = datetime.datetime.now(datetime.timezone.utc) + time_to_check = created_time.date() max_time_for_run_parameters = ( wetlab.models.ConfigSetting.objects.filter( configuration_name__exact="MAXIMUM_TIME_WAIT_RUN_PARAMETERS" @@ -175,9 +203,7 @@ def search_update_new_runs(request_reason): new_run + " : Experiment name field was not found in file" ) wetlab.utils.common.logging_errors(string_message, False, False) - wetlab.utils.crontab_process.manage_errors_in_run( - new_run, "7" - ) + wetlab.utils.crontab_process.manage_errors_in_run(new_run, "7") else: string_message = ( new_run + " : Ignoring test folder " + experiment_name @@ -217,9 +243,7 @@ def search_update_new_runs(request_reason): run_process_obj = wetlab.utils.crontab_process.get_run_process_obj_or_create_if_not_exists( experiment_name ) - wetlab.utils.crontab_process.manage_errors_in_run( - experiment_name, "20" - ) + wetlab.utils.crontab_process.manage_errors_in_run(experiment_name, "20") # cleaning up the RunParameter in local temporaty file logger.debug("%s : Deleting RunParameter file", experiment_name) os.remove(l_run_parameter) @@ -757,13 +781,22 @@ def manage_run_in_processed_run_state(conn, run_process_objs): run_process_obj, experiment_name ) # Check run_folder time creation - f_created_date = wetlab.utils.common.get_samba_atribute_data( - conn, - wetlab.utils.crontab_process.get_samba_shared_folder(), - run_folder, - "create_time", + created_time = _as_utc_datetime( + wetlab.utils.common.get_samba_atribute_data( + conn, + wetlab.utils.crontab_process.get_samba_shared_folder(), + run_folder, + "create_time", + ) ) - time_to_check = datetime.datetime.utcfromtimestamp(f_created_date).date() + if created_time is None: + logger.warning( + "%s : Unable to determine creation time for run folder %s", + experiment_name, + run_folder, + ) + created_time = datetime.datetime.now(datetime.timezone.utc) + time_to_check = created_time.date() # Check maximum time for waiting run metric files max_time_for_run_parameters = ( wetlab.models.ConfigSetting.objects.filter( @@ -1096,9 +1129,7 @@ def manage_run_in_processed_bcl2fastq_state(conn, run_process_objs): ) wetlab.utils.common.logging_errors(string_message, True, False) if key_error.args[0] == 33: - wetlab.utils.crontab_process.manage_errors_in_run( - experiment_name, "33" - ) + wetlab.utils.crontab_process.manage_errors_in_run(experiment_name, "33") else: string_message = ( experiment_name From fa93e00009cc2095e90de87464dfd49d8bcf5595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Tue, 4 Nov 2025 12:24:43 +0100 Subject: [PATCH 259/446] fix state names in crontab and test processes --- wetlab/utils/crontab_update_run.py | 3 +-- wetlab/utils/test_conf.py | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/wetlab/utils/crontab_update_run.py b/wetlab/utils/crontab_update_run.py index cd71cc98e..c2b797528 100644 --- a/wetlab/utils/crontab_update_run.py +++ b/wetlab/utils/crontab_update_run.py @@ -307,7 +307,6 @@ def search_update_new_runs(request_reason): if isinstance(running_parameters["run_date"], datetime.datetime): run_process_obj.set_run_date(running_parameters["run_date"]) logger.info("%s : Sequencer stored on database", experiment_name) - if run_process_obj.get_sample_file() == "": # Fetch sample Sheet from remote server l_sample_sheet_path = ( @@ -396,7 +395,7 @@ def search_update_new_runs(request_reason): logger.info( "%s : RunParameters information stored on database", experiment_name ) - run_process_obj.set_run_state("Sample Sent") + run_process_obj.set_run_state("sample_sent") logger.info("Clossing SAMBA connection") conn.close() diff --git a/wetlab/utils/test_conf.py b/wetlab/utils/test_conf.py index ca11b4edd..27a1115a1 100644 --- a/wetlab/utils/test_conf.py +++ b/wetlab/utils/test_conf.py @@ -167,7 +167,7 @@ def execute_test_for_testing_run(run_test_name): conn = wetlab.utils.common.open_samba_connection() # Execute 6 times to be sure it has completed all steps - state_run_test = ["sample_sent", "processed_run", "processed_bcl2fastq"] + state_run_test = ["Sample sent", "Processed run", "Processed Bcl2fastq", "Completed"] for state_run in state_run_test: run_result[state_run] = "NOK" if not wetlab.models.RunProcess.objects.filter( @@ -178,7 +178,7 @@ def execute_test_for_testing_run(run_test_name): run_obj = wetlab.models.RunProcess.objects.filter( run_name__exact=run_test_name ).last() - for step in range(6): + for _ in range(6): state = run_obj.get_state() if state == "error": run_result["ERROR"] = "error" @@ -191,7 +191,7 @@ def execute_test_for_testing_run(run_test_name): run_result["ERROR"] = "Error when processing run in Sample Sent state" break else: - run_result["sample_sent"] = "OK" + run_result["Sample sent"] = "OK" elif state == "processing_run": wetlab.utils.crontab_update_run.manage_run_in_sample_sent_processing_state( conn, [run_obj] @@ -202,7 +202,7 @@ def execute_test_for_testing_run(run_test_name): ) break else: - run_result["Processing Run"] = "OK" + run_result["Processing run"] = "OK" elif state == "processed_run": wetlab.utils.crontab_update_run.manage_run_in_processed_run_state( conn, [run_obj] @@ -211,7 +211,7 @@ def execute_test_for_testing_run(run_test_name): run_result["ERROR"] = "Error when processing run in Processed Run state" break else: - run_result["Processed Run"] = "OK" + run_result["Processed run"] = "OK" elif state == "processing_bcl2fastq": wetlab.utils.crontab_update_run.manage_run_in_processing_bcl2fastq_state( conn, [run_obj] From 9382d326dfd7c400e99a339bfa80fe4f78072f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Monz=C3=B3n?= Date: Tue, 4 Nov 2025 13:03:04 +0100 Subject: [PATCH 260/446] fix template adding remove spaces custom tag --- wetlab/templates/wetlab/create_pool.html | 105 ++++++++++++----------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/wetlab/templates/wetlab/create_pool.html b/wetlab/templates/wetlab/create_pool.html index 3925777da..88307e752 100644 --- a/wetlab/templates/wetlab/create_pool.html +++ b/wetlab/templates/wetlab/create_pool.html @@ -1,5 +1,6 @@ {% extends "core/base.html" %} {% load static %} +{% load replace_spaces %} {% block content %} {% include "wetlab/menu.html" %} {% include 'core/jexcel_functionality.html' %} @@ -157,57 +158,61 @@

    The following Samples are ready to be included inside a