From f11a8a37c4319846f68d0085c6a3609c5f9cd04e Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 6 Dec 2023 11:01:01 +0000 Subject: [PATCH 01/18] swap to ssl --- docker-compose-no-postgres.yml | 26 ++++++++++++++++++++++++++ nginx.default.template | 6 ++++-- pkpdapp/pkpdapp/settings.py | 18 +++++++----------- 3 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 docker-compose-no-postgres.yml diff --git a/docker-compose-no-postgres.yml b/docker-compose-no-postgres.yml new file mode 100644 index 00000000..21194eb9 --- /dev/null +++ b/docker-compose-no-postgres.yml @@ -0,0 +1,26 @@ +version: "2" +volumes: + db: +services: + rabbitmq: + image: rabbitmq:3-management-alpine + env_file: + - ./.env.prod + ports: + - 5672:5672 + - 15672:15672 + restart: unless-stopped + app: + image: pkpdapp + depends_on: + - rabbitmq + build: + dockerfile: Dockerfile + context: . + ports: + - "80:${PORT}" + restart: unless-stopped + env_file: + - ./.env.prod + + diff --git a/nginx.default.template b/nginx.default.template index e8e91819..9a4569b9 100644 --- a/nginx.default.template +++ b/nginx.default.template @@ -7,8 +7,10 @@ upstream app_server { } server { - listen $PORT; - server_name pkpdapp.herokuapp.com; + listen $PORT ssl; + server_name pkpdapp.com; + ssl_certificate /etc/ssl/pkpdapp.com.crt; + ssl_certificate_key /etc/ssl/pkpdapp.com.key; # javascript frontend location / { diff --git a/pkpdapp/pkpdapp/settings.py b/pkpdapp/pkpdapp/settings.py index d1eee793..e7059354 100644 --- a/pkpdapp/pkpdapp/settings.py +++ b/pkpdapp/pkpdapp/settings.py @@ -241,15 +241,10 @@ ] CORS_ALLOWED_ORIGINS = [ - "http://127.0.0.1:3000", - "http://127.0.0.1:3001", - "http://127.0.0.1:3002", - "http://127.0.0.1:3003", - "http://127.0.0.1:3004", - "http://localhost:3000", + f"https://{os.environ.get('HOST_NAME', 'localhost')}:3000", ] -CORS_ALLOW_ALL_ORIGINS = True +CORS_ALLOW_ALL_ORIGINS = False CORS_EXPOSE_HEADERS = ['Content-Type', 'X-CSRFToken'] CORS_ALLOW_CREDENTIALS = True @@ -275,18 +270,19 @@ X_FRAME_OPTIONS = 'SAMEORIGIN' + # WSGI_APPLICATION = 'bamad.wsgi.application' # authentication cookie settings CSRF_COOKIE_SAMESITE = 'Lax' SESSION_COOKIE_SAMESITE = 'Lax' -CSRF_COOKIE_HTTPONLY = False +CSRF_COOKIE_HTTPONLY = True SESSION_COOKIE_HTTPONLY = True # PROD ONLY -# CSRF_COOKIE_SECURE = True -# SESSION_COOKIE_SECURE = True - +if not DEBUG: + CSRF_COOKIE_SECURE = True + SESSION_COOKIE_SECURE = True WSGI_APPLICATION = 'pkpdapp.wsgi.application' From fe66e84bebff04e4e1ec2b1616cafc4322807dfb Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 6 Dec 2023 11:35:26 +0000 Subject: [PATCH 02/18] copy ssl certs --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 8629e783..b71a60ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,6 +61,8 @@ RUN chown -R www-data:www-data /var/lib/nginx /run /tmp # server setup files COPY nginx.default.template . COPY start-server.sh . +# copy any ssl certificates +COPY /etc/ssl/pkpdapp.com.* /etc/ssl/ RUN chown -R www-data:www-data nginx.default.template start-server.sh # run as www-data From b12c2909141698a7d91c5bc36b86fe6ba190d1cc Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 6 Dec 2023 13:21:19 +0000 Subject: [PATCH 03/18] map volume to certs --- Dockerfile | 2 -- docker-compose.yml | 2 ++ nginx.default.template | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index b71a60ba..8629e783 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,8 +61,6 @@ RUN chown -R www-data:www-data /var/lib/nginx /run /tmp # server setup files COPY nginx.default.template . COPY start-server.sh . -# copy any ssl certificates -COPY /etc/ssl/pkpdapp.com.* /etc/ssl/ RUN chown -R www-data:www-data nginx.default.template start-server.sh # run as www-data diff --git a/docker-compose.yml b/docker-compose.yml index 79ee686e..4923fd13 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,6 +31,8 @@ services: context: . ports: - "80:${PORT}" + volumes: + - .certs:/etc/ssl/pkpdapp restart: unless-stopped env_file: - ./.env.prod diff --git a/nginx.default.template b/nginx.default.template index 9a4569b9..22096d4f 100644 --- a/nginx.default.template +++ b/nginx.default.template @@ -9,8 +9,8 @@ upstream app_server { server { listen $PORT ssl; server_name pkpdapp.com; - ssl_certificate /etc/ssl/pkpdapp.com.crt; - ssl_certificate_key /etc/ssl/pkpdapp.com.key; + ssl_certificate /etc/ssl/pkpdapp/pkpdapp.com.crt; + ssl_certificate_key /etc/ssl/pkpdapp/pkpdapp.com.key; # javascript frontend location / { From efa898d7f00302df0be0de0761060a33e8813c78 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 6 Dec 2023 14:34:21 +0000 Subject: [PATCH 04/18] map certificates and fix nginx template --- docker-compose.yml | 6 ++++-- nginx.default.template | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4923fd13..1d3b0de5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,9 +30,11 @@ services: dockerfile: Dockerfile context: . ports: - - "80:${PORT}" + - 443:443 + - 80:80 volumes: - - .certs:/etc/ssl/pkpdapp + - ${PWD}/.certs/pkpdapp.key:/etc/ssl/pkpdapp.key + - ${PWD}/.certs/pkpdapp.crt:/etc/ssl/pkpdapp.crt restart: unless-stopped env_file: - ./.env.prod diff --git a/nginx.default.template b/nginx.default.template index 22096d4f..211a6e25 100644 --- a/nginx.default.template +++ b/nginx.default.template @@ -7,10 +7,10 @@ upstream app_server { } server { - listen $PORT ssl; + listen 443 ssl; server_name pkpdapp.com; - ssl_certificate /etc/ssl/pkpdapp/pkpdapp.com.crt; - ssl_certificate_key /etc/ssl/pkpdapp/pkpdapp.com.key; + ssl_certificate /etc/ssl/pkpdapp.crt; + ssl_certificate_key /etc/ssl/pkpdapp.key; # javascript frontend location / { From eded480b7b1556f784eed1b0c21c5dae2e121f77 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Fri, 8 Dec 2023 11:19:02 +0000 Subject: [PATCH 05/18] add optional user and admin ldap groups --- .env.prod | 2 ++ pkpdapp/pkpdapp/settings.py | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/.env.prod b/.env.prod index 712b46e0..bebaeeef 100644 --- a/.env.prod +++ b/.env.prod @@ -21,3 +21,5 @@ AUTH_LDAP_BIND_DN=cn=read-only-admin,dc=example,dc=com AUTH_LDAP_BIND_PASSWORD=password AUTH_LDAP_SEARCH_BASE=ou=mathematicians,dc=example,dc=com AUTH_LDAP_SEARCH_FILTER=(uid=%(user)s) +AUTH_LDAP_USER_GROUP=cn=user,ou=groups,dc=example,dc=com +AUTH_LDAP_ADMIN_GROUP=cn=admin,ou=groups,dc=example,dc=com \ No newline at end of file diff --git a/pkpdapp/pkpdapp/settings.py b/pkpdapp/pkpdapp/settings.py index e7059354..8b97f485 100644 --- a/pkpdapp/pkpdapp/settings.py +++ b/pkpdapp/pkpdapp/settings.py @@ -118,6 +118,17 @@ 'ldap://ldap.forumsys.com:389' ) + user_group = os.environ.get('AUTH_LDAP_USER_GROUP', None) + admin_group = os.environ.get('AUTH_LDAP_ADMIN_GROUP', None) + + if user_group is not None: + AUTH_LDAP_REQUIRE_GROUP = user_group + + if admin_group is not None: + AUTH_LDAP_USER_FLAGS_BY_GROUP = { + "is_superuser": admin_group + } + use_direct_bind = bool(int(os.environ.get('AUTH_LDAP_DIRECT_BIND', '0'))) if use_direct_bind: AUTH_LDAP_USER_DN_TEMPLATE = os.environ.get( From 21edd913d422059d17bbcdf510e0d6c22eb0ba13 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Fri, 8 Dec 2023 11:52:53 +0000 Subject: [PATCH 06/18] title in comfortaa font --- frontend-v2/package.json | 1 + frontend-v2/src/features/main/Sidebar.tsx | 6 ++++-- frontend-v2/yarn.lock | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend-v2/package.json b/frontend-v2/package.json index 094e8501..7546081b 100644 --- a/frontend-v2/package.json +++ b/frontend-v2/package.json @@ -5,6 +5,7 @@ "dependencies": { "@emotion/react": "^11.10.8", "@emotion/styled": "^11.10.8", + "@fontsource/comfortaa": "^5.0.18", "@mui/icons-material": "^5.11.16", "@mui/material": "^5.12.2", "@reduxjs/toolkit": "^1.8.1", diff --git a/frontend-v2/src/features/main/Sidebar.tsx b/frontend-v2/src/features/main/Sidebar.tsx index 93bbdad8..a3d562c5 100644 --- a/frontend-v2/src/features/main/Sidebar.tsx +++ b/frontend-v2/src/features/main/Sidebar.tsx @@ -37,6 +37,8 @@ import VaccinesIcon from "@mui/icons-material/Vaccines"; import SsidChartIcon from "@mui/icons-material/SsidChart"; import ContactSupportIcon from "@mui/icons-material/ContactSupport"; import TableViewIcon from "@mui/icons-material/TableView"; +import "@fontsource/comfortaa"; // Defaults to weight 400 + const drawerWidth = 240; @@ -192,8 +194,8 @@ export default function Sidebar() { > - - PK/PD Simulator {project && ` - ${project.name}`} + + pkpd explorer{project && ` - ${project.name}`} dispatch(logout())} color="inherit"> diff --git a/frontend-v2/yarn.lock b/frontend-v2/yarn.lock index 7cd0b6b9..2cbd3e91 100644 --- a/frontend-v2/yarn.lock +++ b/frontend-v2/yarn.lock @@ -1506,6 +1506,11 @@ resolved "https://registry.yarnpkg.com/@exodus/schemasafe/-/schemasafe-1.3.0.tgz#731656abe21e8e769a7f70a4d833e6312fe59b7f" integrity sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw== +"@fontsource/comfortaa@^5.0.18": + version "5.0.18" + resolved "https://registry.yarnpkg.com/@fontsource/comfortaa/-/comfortaa-5.0.18.tgz#3424c932dd8ac27f02ae568197c98686aa7d6767" + integrity sha512-0Goub97HAU4AsAZvR8x7C4uSmBqqkBBEf6m1wFzThZIy+dA6wyqUW8n7uKfElMPcc8ZemI5XMXslW5SdnNJa2Q== + "@hapi/hoek@^9.0.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" From 1c96b352eed808e9606f86ad6e1f5c60d29037e0 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Fri, 8 Dec 2023 16:20:22 +0000 Subject: [PATCH 07/18] add disclaimer on login --- frontend-v2/src/features/login/login.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend-v2/src/features/login/login.tsx b/frontend-v2/src/features/login/login.tsx index 674daf00..d9a4256f 100644 --- a/frontend-v2/src/features/login/login.tsx +++ b/frontend-v2/src/features/login/login.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { Button, @@ -9,6 +9,8 @@ import { CssBaseline, Stack, Alert, + FormControlLabel, + Checkbox, } from "@mui/material"; import { ReactComponent as PkpdAppIcon } from "../../logo_pkpdapp_with_text.svg"; import TextField from "../../components/TextField"; @@ -35,7 +37,7 @@ const Login: React.FC = ({ onLogin, isLoading, errorMessage }) => { }; return ( - +
@@ -68,6 +70,17 @@ const Login: React.FC = ({ onLogin, isLoading, errorMessage }) => { {errorMessage && {errorMessage}}
+ + I acknowledge that I am bound by confidentiality obligations imposed + through my employment or contractual agreement with Roche in connection + with my access to confidential information, including PKPD Explorer and + its contents. By entering PKPD Explorer, I confirm that I understand + that my activities within PKPD Explorer may be monitored consistent with + local law, and all contents and passwords are confidential information, + and that unauthorized disclosure or use of such confidential information + may result in disciplinary action including termination of my employment + or services and/or legal action based on local law." +
); }; From 80c4da911e6c5ae0247f2546c894a613decea643 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Fri, 8 Dec 2023 16:22:58 +0000 Subject: [PATCH 08/18] save value to parameters --- frontend-v2/src/features/simulation/SimulationSliderView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend-v2/src/features/simulation/SimulationSliderView.tsx b/frontend-v2/src/features/simulation/SimulationSliderView.tsx index 4fc88cc7..ddd58fae 100644 --- a/frontend-v2/src/features/simulation/SimulationSliderView.tsx +++ b/frontend-v2/src/features/simulation/SimulationSliderView.tsx @@ -135,7 +135,7 @@ const SimulationSliderView: React.FC = ({
- + From 7aae173710650baffc36286923af20f51feeeb2c Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Fri, 8 Dec 2023 17:10:38 +0000 Subject: [PATCH 09/18] sort protocols by name --- frontend-v2/src/features/trial/Protocols.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frontend-v2/src/features/trial/Protocols.tsx b/frontend-v2/src/features/trial/Protocols.tsx index c4cb508d..381d77c9 100644 --- a/frontend-v2/src/features/trial/Protocols.tsx +++ b/frontend-v2/src/features/trial/Protocols.tsx @@ -57,6 +57,15 @@ const Protocols: React.FC = () => { (protocol) => protocol.variables.length > 0, ); + // sort protocols alphabetically by name + filteredProtocols?.sort((a, b) => { + if (a.name < b.name) { + return -1; + } else { + return 1; + } + }); + return ( From 71673da092f522d85f98bd3ab58bdea23b108373 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Fri, 8 Dec 2023 17:22:02 +0000 Subject: [PATCH 10/18] always ask for C1 in output --- frontend-v2/src/features/simulation/Simulations.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend-v2/src/features/simulation/Simulations.tsx b/frontend-v2/src/features/simulation/Simulations.tsx index ba821b1d..c789fb97 100644 --- a/frontend-v2/src/features/simulation/Simulations.tsx +++ b/frontend-v2/src/features/simulation/Simulations.tsx @@ -74,7 +74,7 @@ const getSimulateInput = ( for (const plot of simulation?.plots || []) { for (const y_axis of plot.y_axes) { const variable = variables?.find((v) => v.id === y_axis.variable); - if (variable && !outputs.includes(variable.name)) { + if (variable && !outputs.includes(variable.qname)) { outputs.push(variable.qname); } } @@ -85,6 +85,15 @@ const getSimulateInput = ( (v) => v.name === "time" || v.name === "t", ); outputs.push(timeVariable?.qname || "time"); + + // for some reason we need to ask for concentration or myokit produces a kink in the output + const alwaysAsk = ["PKCompartment.C1"]; + for (const v of alwaysAsk) { + const variable = variables?.find((vv) => vv.qname === v); + if (variable && !outputs.includes(variable.qname)) { + outputs.push(variable.qname); + } + } return { variables: simulateVariables, outputs, From b39b12de3c150695a1a7628c9bbc1b8641065d31 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 13 Dec 2023 17:13:55 +0000 Subject: [PATCH 11/18] fill out production instructions in readme --- .env.prod | 9 ++-- README.md | 88 +++++++++++++++++++++++++++++----- docker-compose-no-postgres.yml | 26 ---------- 3 files changed, 80 insertions(+), 43 deletions(-) delete mode 100644 docker-compose-no-postgres.yml diff --git a/.env.prod b/.env.prod index bebaeeef..c365451d 100644 --- a/.env.prod +++ b/.env.prod @@ -1,12 +1,6 @@ -PORT=8020 DEBUG=1 HOST_NAME=monkshood SECRET_KEY=aLargeRandomSecretKey -EMAIL_HOST=in-v3.mailjet.com -EMAIL_PORT=25 -EMAIL_HOST_USER=email_username -EMAIL_HOST_PASSWORD=email_password -DEFAULT_FROM_EMAIL=sender@mydomain.com POSTGRES_PASSWORD=sekret2 DATABASE_URL=postgres://postgres:sekret2@postgres:5432/postgres @@ -15,11 +9,14 @@ RABBITMQ_DEFAULT_PASS=guest AUTH_LDAP_USE=0 AUTH_LDAP_SERVER_URI=ldap://ldap.forumsys.com:389 + AUTH_LDAP_DIRECT_BIND=1 AUTH_LDAP_BIND_DN_TEMPLATE=uid=%(user)s,dc=example,dc=com + AUTH_LDAP_BIND_DN=cn=read-only-admin,dc=example,dc=com AUTH_LDAP_BIND_PASSWORD=password AUTH_LDAP_SEARCH_BASE=ou=mathematicians,dc=example,dc=com AUTH_LDAP_SEARCH_FILTER=(uid=%(user)s) + AUTH_LDAP_USER_GROUP=cn=user,ou=groups,dc=example,dc=com AUTH_LDAP_ADMIN_GROUP=cn=admin,ou=groups,dc=example,dc=com \ No newline at end of file diff --git a/README.md b/README.md index 91dbfb32..7bcd8a30 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,86 @@ PKPDApp is an open source web-based application to explore, analyse and model the pharmacokinetics and pharmacodynamics of chemical compounds. The app is currently under heavy development, however a preliminary version is being deployed with Heroku and can be found under https://pkpdapp.herokuapp.com/. +## Installation - production + +Use these instructions for deploying the application in a production environment. Instructions for developers can be found below. + +### `.env.prod` file + +The configuration of the production application is stored in the `.env.prod` file. Edit this file and variables to correspond to your particular setup. The variables are described below: + +- `DEBUG`: set to 0 for production +- `HOST_NAME`: the host name of the application +- `SECRET_KEY`: a large random string used for cryptographic signing +- `POSTGRES_PASSWORD`: password for postgres database specified in `docker-compose.yml` (not needed if using your own database) +- `DATABASE_URL`: URL of a postgres database (e.g. postgres://username:password@postgres:5432/postgres). Do not alter this if you are using the + postgres service in the `docker-compose.yml` file. + +- `RABBITMQ_DEFAULT_USER`: username for rabbitmq server (optional) +- `RABBITMQ_DEFAULT_PASS`: password for rabbitmq server (optional) + +The following variables are used for LDAP authentication (optional): + +- `AUTH_LDAP_USE`: set to 1 to use LDAP authentication +- `AUTH_LDAP_SERVER_URI`: URI of LDAP server (e.g. ldap://ldap.forumsys.com:389) + +For direct binding: + +- `AUTH_LDAP_DIRECT_BIND`: set to 1 to bind directly to LDAP server (see [here](https://django-auth-ldap.readthedocs.io/en/latest/authentication.html#direct-bind) +- `AUTH_LDAP_BIND_DN_TEMPLATE`: template for direct binding (e.g. `uid=%(user)s,dc=example,dc=com`) + +For search/bind, connecting to the LDAP server either anonymously or with a fixed account and searching for the distinguished name of the authenticating user. + +- `AUTH_LDAP_BIND_DN`: distinguished name of an authorized user (e.g. `cn=read-only-admin,dc=example,dc=com`) +- `AUTH_LDAP_BIND_PASSWORD`: password for the authorized user +- `AUTH_LDAP_SEARCH_BASE`: where to perform the search (e.g. `ou=mathematicians,dc=example,dc=com`) +- `AUTH_LDAP_SEARCH_FILTER`: search filter based on authenticated username (`uid=%(user)s`) +- `AUTH_LDAP_USER_GROUP`: (optional) authentication will only succeed if user is in this LDAP group (e.g. `cn=user,ou=groups,dc=example,dc=com`). If not set, then any user in the search base will be authenticated. +- `AUTH_LDAP_ADMIN_GROUP`: (optional) user must be in this LDAP group to be a superuser (e.g. `cn=admin,ou=groups,dc=example,dc=com`). If not set, then no user will be a superuser. + + +### SSL Certificate + +The application uses SSL certificates for HTTPS. You will need to supply your +own SSL certificate and key. These should be placed in the `.certs/` directory +in the root folder (this folder needs to be created) and named `pkpdapp.crt` and +`pkpdapp.key` respectively. + +### PostgreSQL database + +The application uses a PostgreSQL database. You can either supply your own +database and set the `DATABASE_URL` variable in the `.env.prod` file, or the +`docker-compose.yml` file contains a postgres service that can be used, along +with the `DATABASE_URL` variable already set in the `.env.prod` file. + +If you are using your own database, you can delete the postgres service from the +`docker-compose.yml` file. + +### Containers + +The application is deployed using docker containers and docker-compose, so you will need to install these. + +You can build a docker image and run the image inside the container with commands below (run these commands in the root directory of the repository): + +```bash +$ docker-compose build +$ docker-compose up +``` + +You should be able to see the web application at [127.0.0.1](127.0.0.1). + +To leave the container running in the background, use + +```bash +$ docker-compose up -d +``` + ## Installation - development If you are interested in developing PKPDApp with us, or just run the app locally, you can clone the repository and follow the installation instructions below. +NOTE: the current version of the frontend does not currently use the rabbitmq server, so you can skip the installation of rabbitmq for the moment (later iterations of the frontend will use the rabbitmq server). + ### Django backend 1. Install sundials, python dev libraries and rabbitmq server @@ -56,7 +132,7 @@ cd pkpdapp python manage.py migrate ``` -5. Run RabbitMQ +5. Run RabbitMQ (optional) ```bash celery -A pkpdapp worker --loglevel=INFO @@ -105,16 +181,6 @@ yarn start You should be able to see the pkpd web app at [127.0.0.1:3000](127.0.0.1:3000). -## Installation - production - -Alternatively you can build a docker image and run the image inside the container with commands below. - -```bash -$ docker-compose build -$ docker-compose up -``` - -You should be able to see the web application at [127.0.0.1](127.0.0.1). ## Code testing diff --git a/docker-compose-no-postgres.yml b/docker-compose-no-postgres.yml deleted file mode 100644 index 21194eb9..00000000 --- a/docker-compose-no-postgres.yml +++ /dev/null @@ -1,26 +0,0 @@ -version: "2" -volumes: - db: -services: - rabbitmq: - image: rabbitmq:3-management-alpine - env_file: - - ./.env.prod - ports: - - 5672:5672 - - 15672:15672 - restart: unless-stopped - app: - image: pkpdapp - depends_on: - - rabbitmq - build: - dockerfile: Dockerfile - context: . - ports: - - "80:${PORT}" - restart: unless-stopped - env_file: - - ./.env.prod - - From ad9bf1779c2b5a0f9529d872b3c448b0c16e43ee Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 13 Dec 2023 18:00:05 +0000 Subject: [PATCH 12/18] add groups search --- .env.prod | 1 + pkpdapp/pkpdapp/settings.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/.env.prod b/.env.prod index c365451d..a675a575 100644 --- a/.env.prod +++ b/.env.prod @@ -18,5 +18,6 @@ AUTH_LDAP_BIND_PASSWORD=password AUTH_LDAP_SEARCH_BASE=ou=mathematicians,dc=example,dc=com AUTH_LDAP_SEARCH_FILTER=(uid=%(user)s) +AUTH_LDAP_GROUP_SEARCH=ou=groups,dc=example,dc=com AUTH_LDAP_USER_GROUP=cn=user,ou=groups,dc=example,dc=com AUTH_LDAP_ADMIN_GROUP=cn=admin,ou=groups,dc=example,dc=com \ No newline at end of file diff --git a/pkpdapp/pkpdapp/settings.py b/pkpdapp/pkpdapp/settings.py index 8b97f485..cb7634b7 100644 --- a/pkpdapp/pkpdapp/settings.py +++ b/pkpdapp/pkpdapp/settings.py @@ -20,6 +20,7 @@ import dj_database_url import ldap from django_auth_ldap.config import LDAPSearch +from django_auth_ldap.config import LDAPSearch, GroupOfNamesType # Set BASE_DIR to two directories up BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -121,6 +122,13 @@ user_group = os.environ.get('AUTH_LDAP_USER_GROUP', None) admin_group = os.environ.get('AUTH_LDAP_ADMIN_GROUP', None) + + if user_group is not None and admin_group is not None: + AUTH_LDAP_GROUP_SEARCH = LDAPSearch( + "ou=groups,dc=example,dc=com", ldap.SCOPE_SUBTREE, "(objectClass=groupOfNames)" + ) + AUTH_LDAP_GROUP_TYPE = GroupOfNamesType() + if user_group is not None: AUTH_LDAP_REQUIRE_GROUP = user_group From 67a30c7095b8af654afe9d5d6174000b1757d889 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 13 Dec 2023 18:02:23 +0000 Subject: [PATCH 13/18] actually use group search --- pkpdapp/pkpdapp/settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkpdapp/pkpdapp/settings.py b/pkpdapp/pkpdapp/settings.py index cb7634b7..710be8f8 100644 --- a/pkpdapp/pkpdapp/settings.py +++ b/pkpdapp/pkpdapp/settings.py @@ -121,18 +121,18 @@ user_group = os.environ.get('AUTH_LDAP_USER_GROUP', None) admin_group = os.environ.get('AUTH_LDAP_ADMIN_GROUP', None) + group_search = os.environ.get('AUTH_LDAP_GROUP_SEARCH', None) - - if user_group is not None and admin_group is not None: + if group_search is not None: AUTH_LDAP_GROUP_SEARCH = LDAPSearch( "ou=groups,dc=example,dc=com", ldap.SCOPE_SUBTREE, "(objectClass=groupOfNames)" ) AUTH_LDAP_GROUP_TYPE = GroupOfNamesType() - if user_group is not None: + if user_group is not None and group_search is not None: AUTH_LDAP_REQUIRE_GROUP = user_group - if admin_group is not None: + if admin_group is not None and group_search is not None: AUTH_LDAP_USER_FLAGS_BY_GROUP = { "is_superuser": admin_group } From f400a79e6969e3435d036b8a046d2fa5636e2be1 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 13 Dec 2023 18:03:50 +0000 Subject: [PATCH 14/18] and again --- pkpdapp/pkpdapp/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkpdapp/pkpdapp/settings.py b/pkpdapp/pkpdapp/settings.py index 710be8f8..f54527cc 100644 --- a/pkpdapp/pkpdapp/settings.py +++ b/pkpdapp/pkpdapp/settings.py @@ -125,7 +125,7 @@ if group_search is not None: AUTH_LDAP_GROUP_SEARCH = LDAPSearch( - "ou=groups,dc=example,dc=com", ldap.SCOPE_SUBTREE, "(objectClass=groupOfNames)" + group_search, ldap.SCOPE_SUBTREE, "(objectClass=groupOfNames)" ) AUTH_LDAP_GROUP_TYPE = GroupOfNamesType() From e4a24d43218f90cf2d96d62a2cce639d837b03c9 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 13 Dec 2023 20:20:16 +0000 Subject: [PATCH 15/18] admin for ldap --- nginx.default.template | 14 ++++++++++++++ pkpdapp/pkpdapp/settings.py | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/nginx.default.template b/nginx.default.template index 211a6e25..d630bd66 100644 --- a/nginx.default.template +++ b/nginx.default.template @@ -60,6 +60,20 @@ server { proxy_buffering off; } + # or /static/admin + location /admin { + proxy_pass http://app_server/static/admin; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_redirect http://app_server/static/admin $scheme://$http_host; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_read_timeout 20d; + proxy_buffering off; + } + # or /backend -> /static location /backend { proxy_pass http://app_server/static; diff --git a/pkpdapp/pkpdapp/settings.py b/pkpdapp/pkpdapp/settings.py index f54527cc..b3387151 100644 --- a/pkpdapp/pkpdapp/settings.py +++ b/pkpdapp/pkpdapp/settings.py @@ -134,7 +134,8 @@ if admin_group is not None and group_search is not None: AUTH_LDAP_USER_FLAGS_BY_GROUP = { - "is_superuser": admin_group + "is_staff": admin_group, + "is_superuser": admin_group, } use_direct_bind = bool(int(os.environ.get('AUTH_LDAP_DIRECT_BIND', '0'))) From e956c99d83495ed0dabb02ae931ddd0b02fd8712 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 13 Dec 2023 20:22:26 +0000 Subject: [PATCH 16/18] flake8 --- pkpdapp/pkpdapp/settings.py | 383 +++++++++++++++++------------------- 1 file changed, 179 insertions(+), 204 deletions(-) diff --git a/pkpdapp/pkpdapp/settings.py b/pkpdapp/pkpdapp/settings.py index b3387151..c00f9552 100644 --- a/pkpdapp/pkpdapp/settings.py +++ b/pkpdapp/pkpdapp/settings.py @@ -19,67 +19,64 @@ import os import dj_database_url import ldap -from django_auth_ldap.config import LDAPSearch from django_auth_ldap.config import LDAPSearch, GroupOfNamesType # Set BASE_DIR to two directories up BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # setup automatic pk column for models -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - 'level': 'DEBUG', - 'formatter': 'simple' + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "formatter": "simple", + }, + "file": { + "class": "logging.handlers.RotatingFileHandler", + "filename": os.path.join(BASE_DIR, "logfile.log"), + "maxBytes": 1024 * 1024 * 5, # 5 MB + "backupCount": 5, + "level": "DEBUG", + "formatter": "verbose", }, - 'file': { - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': os.path.join(BASE_DIR, 'logfile.log'), - 'maxBytes': 1024 * 1024 * 5, # 5 MB - 'backupCount': 5, - 'level': 'DEBUG', - 'formatter': 'verbose' - } }, - 'loggers': { - 'django': { - 'handlers': ['console', 'file'], - 'level': 'INFO', - 'propagate': False, + "loggers": { + "django": { + "handlers": ["console", "file"], + "level": "INFO", + "propagate": False, }, - 'pkpdapp': { - 'handlers': ['console', 'file'], - 'level': 'DEBUG', - 'propagate': False, + "pkpdapp": { + "handlers": ["console", "file"], + "level": "DEBUG", + "propagate": False, }, }, - 'formatters': { - 'simple': { - 'format': '%(levelname)s %(message)s' + "formatters": { + "simple": {"format": "%(levelname)s %(message)s"}, + "verbose": { + "format": "%(asctime)s %(levelname)s %(module)s %(process)d %(thread)d %(message)s" # noqa: E501 }, - 'verbose': { - 'format': '%(asctime)s %(levelname)s %(module)s %(process)d %(thread)d %(message)s' # noqa: E501 - } - } + }, } # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ -SECRET_KEY = os.environ.get("SECRET_KEY", default='foo') +SECRET_KEY = os.environ.get("SECRET_KEY", default="foo") DEBUG = int(os.environ.get("DEBUG", default=0)) -ALLOWED_HOSTS = [os.environ.get('HOST_NAME', 'localhost'), '127.0.0.1'] +ALLOWED_HOSTS = [os.environ.get("HOST_NAME", "localhost"), "127.0.0.1"] if DEBUG: - ALLOWED_HOSTS.append('testserver') + ALLOWED_HOSTS.append("testserver") # Application definition - to use any of those you need to run `manage.py @@ -87,41 +84,38 @@ INSTALLED_APPS = [ # standard Django apps - 'django.contrib.admin', - 'django.contrib.admindocs', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - + "django.contrib.admin", + "django.contrib.admindocs", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", # external apps - 'dpd_static_support', - 'django_extensions', - 'djoser', - 'rest_framework', - 'rest_framework.authtoken', - 'corsheaders', - 'drf_spectacular', - + "dpd_static_support", + "django_extensions", + "djoser", + "rest_framework", + "rest_framework.authtoken", + "corsheaders", + "drf_spectacular", # internal apps - 'pkpdapp', + "pkpdapp", ] -use_ldap = bool(int(os.environ.get('AUTH_LDAP_USE', '0'))) +use_ldap = bool(int(os.environ.get("AUTH_LDAP_USE", "0"))) if use_ldap: AUTHENTICATION_BACKENDS = [ "django_auth_ldap.backend.LDAPBackend", "django.contrib.auth.backends.ModelBackend", ] AUTH_LDAP_SERVER_URI = os.environ.get( - 'AUTH_LDAP_SERVER_URI', - 'ldap://ldap.forumsys.com:389' + "AUTH_LDAP_SERVER_URI", "ldap://ldap.forumsys.com:389" ) - user_group = os.environ.get('AUTH_LDAP_USER_GROUP', None) - admin_group = os.environ.get('AUTH_LDAP_ADMIN_GROUP', None) - group_search = os.environ.get('AUTH_LDAP_GROUP_SEARCH', None) + user_group = os.environ.get("AUTH_LDAP_USER_GROUP", None) + admin_group = os.environ.get("AUTH_LDAP_ADMIN_GROUP", None) + group_search = os.environ.get("AUTH_LDAP_GROUP_SEARCH", None) if group_search is not None: AUTH_LDAP_GROUP_SEARCH = LDAPSearch( @@ -131,133 +125,123 @@ if user_group is not None and group_search is not None: AUTH_LDAP_REQUIRE_GROUP = user_group - + if admin_group is not None and group_search is not None: AUTH_LDAP_USER_FLAGS_BY_GROUP = { "is_staff": admin_group, "is_superuser": admin_group, } - use_direct_bind = bool(int(os.environ.get('AUTH_LDAP_DIRECT_BIND', '0'))) + use_direct_bind = bool(int(os.environ.get("AUTH_LDAP_DIRECT_BIND", "0"))) if use_direct_bind: AUTH_LDAP_USER_DN_TEMPLATE = os.environ.get( - 'AUTH_LDAP_BIND_DN_TEMPLATE', - 'uid=%(user)s,dc=example,dc=com' + "AUTH_LDAP_BIND_DN_TEMPLATE", "uid=%(user)s,dc=example,dc=com" ) else: AUTH_LDAP_BIND_DN = os.environ.get( - 'AUTH_LDAP_BIND_DN', - 'cn=read-only-admin,dc=example,dc=com' - ) - AUTH_LDAP_BIND_PASSWORD = os.environ.get( - 'AUTH_LDAP_BIND_PASSWORD', - 'password' + "AUTH_LDAP_BIND_DN", "cn=read-only-admin,dc=example,dc=com" ) + AUTH_LDAP_BIND_PASSWORD = os.environ.get("AUTH_LDAP_BIND_PASSWORD", "password") AUTH_LDAP_USER_SEARCH = LDAPSearch( os.environ.get( - 'AUTH_LDAP_SEARCH_BASE', - 'ou=mathematicians,dc=example,dc=com' + "AUTH_LDAP_SEARCH_BASE", "ou=mathematicians,dc=example,dc=com" ), ldap.SCOPE_SUBTREE, - os.environ.get( - 'AUTH_LDAP_SEARCH_FILTER', - "(uid=%(user)s)" - ), + os.environ.get("AUTH_LDAP_SEARCH_FILTER", "(uid=%(user)s)"), ) DJOSER = { - 'PASSWORD_RESET_CONFIRM_URL': 'reset-password/{uid}/{token}', - 'ACTIVATION_URL': 'activate/{uid}/{token}', - 'SEND_ACTIVATION_EMAIL': True, - 'SEND_CONFIRMATION_EMAIL': True, - 'PASSWORD_CHANGED_EMAIL_CONFIRMATION': True, - 'SERIALIZERS': {}, - 'PERMISSIONS': { - 'activation': ['rest_framework.permissions.AllowAny'], - 'password_reset': ['rest_framework.permissions.AllowAny'], - 'password_reset_confirm': ['rest_framework.permissions.AllowAny'], - 'set_password': ['djoser.permissions.CurrentUserOrAdmin'], - 'username_reset': ['rest_framework.permissions.AllowAny'], - 'username_reset_confirm': ['rest_framework.permissions.AllowAny'], - 'set_username': ['djoser.permissions.CurrentUserOrAdmin'], - 'user_create': ['rest_framework.permissions.AllowAny'], - 'user_delete': ['djoser.permissions.CurrentUserOrAdmin'], - 'user': ['djoser.permissions.CurrentUserOrAdmin'], - 'user_list': ['djoser.permissions.CurrentUserOrAdmin'], - 'token_create': ['rest_framework.permissions.AllowAny'], - 'token_destroy': ['rest_framework.permissions.IsAuthenticated'], + "PASSWORD_RESET_CONFIRM_URL": "reset-password/{uid}/{token}", + "ACTIVATION_URL": "activate/{uid}/{token}", + "SEND_ACTIVATION_EMAIL": True, + "SEND_CONFIRMATION_EMAIL": True, + "PASSWORD_CHANGED_EMAIL_CONFIRMATION": True, + "SERIALIZERS": {}, + "PERMISSIONS": { + "activation": ["rest_framework.permissions.AllowAny"], + "password_reset": ["rest_framework.permissions.AllowAny"], + "password_reset_confirm": ["rest_framework.permissions.AllowAny"], + "set_password": ["djoser.permissions.CurrentUserOrAdmin"], + "username_reset": ["rest_framework.permissions.AllowAny"], + "username_reset_confirm": ["rest_framework.permissions.AllowAny"], + "set_username": ["djoser.permissions.CurrentUserOrAdmin"], + "user_create": ["rest_framework.permissions.AllowAny"], + "user_delete": ["djoser.permissions.CurrentUserOrAdmin"], + "user": ["djoser.permissions.CurrentUserOrAdmin"], + "user_list": ["djoser.permissions.CurrentUserOrAdmin"], + "token_create": ["rest_framework.permissions.AllowAny"], + "token_destroy": ["rest_framework.permissions.IsAuthenticated"], }, } # django rest framework library REST_FRAMEWORK = { - 'DEFAULT_RENDERER_CLASSES': [ - 'rest_framework.renderers.JSONRenderer', + "DEFAULT_RENDERER_CLASSES": [ + "rest_framework.renderers.JSONRenderer", ], - 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework.authentication.SessionAuthentication', + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework.authentication.SessionAuthentication", ], - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticated', + "DEFAULT_PERMISSION_CLASSES": [ + "rest_framework.permissions.IsAuthenticated", ], - 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", } SPECTACULAR_SETTINGS = { - 'TITLE': 'PKPDApp API', - 'DESCRIPTION': 'The API for the PKPDApp', - 'VERSION': '1.0.0', - 'SERVE_INCLUDE_SCHEMA': False, + "TITLE": "PKPDApp API", + "DESCRIPTION": "The API for the PKPDApp", + "VERSION": "1.0.0", + "SERVE_INCLUDE_SCHEMA": False, } -CRISPY_TEMPLATE_PACK = 'bootstrap4' +CRISPY_TEMPLATE_PACK = "bootstrap4" MARKDOWNIFY_MARKDOWN_EXTENSIONS = [ - 'mdx_math', + "mdx_math", ] MARKDOWNIFY_WHITELIST_TAGS = [ - 'a', - 'abbr', - 'acronym', - 'b', - 'blockquote', - 'em', - 'i', - 'li', - 'ol', - 'p', - 'strong', - 'ul', - 'h', - 'script', + "a", + "abbr", + "acronym", + "b", + "blockquote", + "em", + "i", + "li", + "ol", + "p", + "strong", + "ul", + "h", + "script", ] MARKDOWNIFY_WHITELIST_ATTRS = [ - 'href', - 'src', - 'alt', - 'type', + "href", + "src", + "alt", + "type", ] MARKDOWNIFY_WHITELIST_STYLES = [ - 'color', - 'font-weight', + "color", + "font-weight", ] MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", - 'django.middleware.security.SecurityMiddleware', - 'whitenoise.middleware.WhiteNoiseMiddleware', - - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] CORS_ALLOWED_ORIGINS = [ @@ -266,64 +250,64 @@ CORS_ALLOW_ALL_ORIGINS = False -CORS_EXPOSE_HEADERS = ['Content-Type', 'X-CSRFToken'] +CORS_EXPOSE_HEADERS = ["Content-Type", "X-CSRFToken"] CORS_ALLOW_CREDENTIALS = True -ROOT_URLCONF = 'pkpdapp.urls' +ROOT_URLCONF = "pkpdapp.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'pkpdapp', 'templates')], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "pkpdapp", "templates")], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -X_FRAME_OPTIONS = 'SAMEORIGIN' +X_FRAME_OPTIONS = "SAMEORIGIN" # WSGI_APPLICATION = 'bamad.wsgi.application' # authentication cookie settings -CSRF_COOKIE_SAMESITE = 'Lax' -SESSION_COOKIE_SAMESITE = 'Lax' +CSRF_COOKIE_SAMESITE = "Lax" +SESSION_COOKIE_SAMESITE = "Lax" CSRF_COOKIE_HTTPONLY = True SESSION_COOKIE_HTTPONLY = True # PROD ONLY -if not DEBUG: +if not DEBUG: CSRF_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True -WSGI_APPLICATION = 'pkpdapp.wsgi.application' +WSGI_APPLICATION = "pkpdapp.wsgi.application" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - 'OPTIONS': { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), + "OPTIONS": { # 'timeout': 20, - } + }, } } -DATABASE_URL = os.environ.get('DATABASE_URL') +DATABASE_URL = os.environ.get("DATABASE_URL") db_from_env = dj_database_url.config( default=DATABASE_URL, conn_max_age=500, ssl_require=False ) -DATABASES['default'].update(db_from_env) +DATABASES["default"].update(db_from_env) # Password validation @@ -331,21 +315,17 @@ AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': - 'django.contrib.auth.password_validation.' - 'UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation." + "UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.' - 'MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation." "MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.' - 'CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation." "CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.' - 'NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation." "NumericPasswordValidator", }, ] @@ -353,9 +333,9 @@ # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -364,71 +344,66 @@ USE_TZ = True # Media files (such as data sets and model files) -MEDIA_URL = '/media/' -MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +MEDIA_URL = "/media/" +MEDIA_ROOT = os.path.join(BASE_DIR, "media") # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ -STATIC_URL = '/static/' -STATIC_ROOT = os.path.join(BASE_DIR, 'static') +STATIC_URL = "/static/" +STATIC_ROOT = os.path.join(BASE_DIR, "static") -STATICFILES_DIRS = [ -] +STATICFILES_DIRS = [] # Staticfiles finders for locating dash app assets and related files STATICFILES_FINDERS = [ # Django default finders - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", ] # Forever cachable files and compression support -STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" # Redirect to home URL after login (Default redirects to /accounts/profile/) -LOGIN_REDIRECT_URL = '/' +LOGIN_REDIRECT_URL = "/" EMAIL_HOST = os.environ.get("EMAIL_HOST", default=None) if EMAIL_HOST is None: - EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" else: - EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" -EMAIL_PORT = os.environ.get("EMAIL_PORT", default='foo') -EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", default='foo') -EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", default='foo') -DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", - default='webmaster@localhost') +EMAIL_PORT = os.environ.get("EMAIL_PORT", default="foo") +EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", default="foo") +EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", default="foo") +DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", default="webmaster@localhost") CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': '127.0.0.1:11211', + "default": { + "BACKEND": "django.core.cache.backends.memcached.MemcachedCache", + "LOCATION": "127.0.0.1:11211", } } -DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", - default='webmaster@localhost') +DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", default="webmaster@localhost") CLOUDAMQP_URL = os.environ.get("CLOUDAMQP_URL", default=None) if CLOUDAMQP_URL is None: CELERY_BROKER_URL = [ - 'amqp://', - 'amqp://{}:{}@rabbitmq:5672'.format( - os.environ.get("RABBITMQ_DEFAULT_USER", - default='guest'), - os.environ.get("RABBITMQ_DEFAULT_PASS", - default='guest') - ) + "amqp://", + "amqp://{}:{}@rabbitmq:5672".format( + os.environ.get("RABBITMQ_DEFAULT_USER", default="guest"), + os.environ.get("RABBITMQ_DEFAULT_PASS", default="guest"), + ), ] else: CELERY_BROKER_URL = CLOUDAMQP_URL CELERY_BROKER_TRANSPORT_OPTIONS = { - 'max_retries': 3, - 'interval_start': 0, - 'interval_step': 0.2, - 'interval_max': 0.5, + "max_retries": 3, + "interval_start": 0, + "interval_step": 0.2, + "interval_max": 0.5, } From 5a3d672ed3bb211dd8e6d2d489ea20308e65ae14 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 13 Dec 2023 20:25:30 +0000 Subject: [PATCH 17/18] log info --- pkpdapp/pkpdapp/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkpdapp/pkpdapp/settings.py b/pkpdapp/pkpdapp/settings.py index c00f9552..fbe9ec58 100644 --- a/pkpdapp/pkpdapp/settings.py +++ b/pkpdapp/pkpdapp/settings.py @@ -53,7 +53,7 @@ }, "pkpdapp": { "handlers": ["console", "file"], - "level": "DEBUG", + "level": "INFO", "propagate": False, }, }, From 000331c4c850e61e49ec7c88cf51cae8bbde6a12 Mon Sep 17 00:00:00 2001 From: martinjrobins Date: Wed, 13 Dec 2023 20:30:00 +0000 Subject: [PATCH 18/18] fix nginx --- nginx.default.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx.default.template b/nginx.default.template index d630bd66..195578db 100644 --- a/nginx.default.template +++ b/nginx.default.template @@ -61,7 +61,7 @@ server { } # or /static/admin - location /admin { + location /static/admin { proxy_pass http://app_server/static/admin; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr;