diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..86c1b9c7 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,32 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.231.6/containers/docker-existing-dockerfile +{ + "name": "Existing Dockerfile", + + // Sets the run context to one level up instead of the .devcontainer folder. + "context": "..", + + // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. + "dockerFile": "../Dockerfile", + + // Set *default* container specific settings.json values on container create. + "settings": {}, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [] + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Uncomment the next line to run commands after the container is created - for example installing curl. + // "postCreateCommand": "apt-get update && apt-get install -y curl", + + // Uncomment when using a ptrace-based debugger like C++, Go, and Rust + // "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], + + // Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker. + // "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ], + + // Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root. + // "remoteUser": "vscode" +} diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml new file mode 100644 index 00000000..8ebcd872 --- /dev/null +++ b/.docker/docker-compose.yml @@ -0,0 +1,141 @@ +version: '3' + +services: + nginx: + build: ./nginx + ports: + - "80:80" + volumes: + - staticvol:/opt/app/static + - mediavol:/opt/app/mediafiles + depends_on: + - "dev" + + database: + image: "postgres" + environment: + - POSTGRES_DB=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + volumes: + - db:/var/lib/postgresql/data + ports: + - "5432:5432" + + dev: + build: + context: . + dockerfile: Dockerfile + entrypoint: "./build-dev.sh" + volumes: + - .:/build + - staticvol:/opt/app/static + - mediavol:/opt/app/mediafiles + ports: + - "8081:8081" + environment: + - DEBUG=True + - QLDB_ENABLED="FALSE" + - qldb_name="fEMR-OnChain-Test" + - ADMIN_NAME="" + - ADMIN_EMAIL="" + - EMAIL_HOST="" + - EMAIL_PORT="" + - EMAIL_USERNAME="" + - EMAIL_PASSWORD="" + - DEFAULT_FROM_EMAIL="" + - SERVER_EMAIL="" + - ENVIRONMENT="LOCAL" + - SECRET_KEY=2HY>fXi!dQ&(9Vf.XghCa;L6G=Ul4r-Bwqh>ae0RG3vIh1ZJ%T + - POSTGRES_DB=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASS=postgres + - POSTGRES_NAME=database + - ENVIRONMENT=LOCAL + depends_on: + - "database" + - "cache" + + celery_beat: + build: + context: . + dockerfile: Dockerfile-celery + volumes: + - .:/build + entrypoint: "./build-celery-beat.sh" + environment: + - DEBUG=True + - QLDB_ENABLED="FALSE" + - qldb_name="fEMR-OnChain-Test" + - ADMIN_NAME="" + - ADMIN_EMAIL="" + - EMAIL_HOST="" + - EMAIL_PORT="" + - EMAIL_USERNAME="" + - EMAIL_PASSWORD="" + - DEFAULT_FROM_EMAIL="" + - SERVER_EMAIL="" + - ENVIRONMENT="LOCAL" + - SECRET_KEY=2HY>fXi!dQ&(9Vf.XghCa;L6G=Ul4r-Bwqh>ae0RG3vIh1ZJ%T + - CELERY_BROKER=redis://redis:6379/0 + - CELERY_BACKEND=redis://redis:6379/0 + - POSTGRES_DB=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASS=postgres + - POSTGRES_NAME=database + - ENVIRONMENT=LOCAL + depends_on: + - "dev" + - "redis" + - "database" + + celery: + build: + context: . + dockerfile: Dockerfile-celery + volumes: + - .:/build + entrypoint: "./build-celery.sh" + environment: + - DEBUG=True + - QLDB_ENABLED="FALSE" + - qldb_name="fEMR-OnChain-Test" + - ADMIN_NAME="" + - ADMIN_EMAIL="" + - EMAIL_HOST="" + - EMAIL_PORT="" + - EMAIL_USERNAME="" + - EMAIL_PASSWORD="" + - DEFAULT_FROM_EMAIL="" + - SERVER_EMAIL="" + - ENVIRONMENT="LOCAL" + - SECRET_KEY=2HY>fXi!dQ&(9Vf.XghCa;L6G=Ul4r-Bwqh>ae0RG3vIh1ZJ%T + - CELERY_BROKER=redis://redis:6379/0 + - CELERY_BACKEND=redis://redis:6379/0 + - POSTGRES_DB=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASS=postgres + - POSTGRES_NAME=database + - ENVIRONMENT=LOCAL + depends_on: + - "dev" + - "redis" + - "database" + + redis: + image: redis:6-alpine + ports: + - "6379:6379" + + cache: + image: memcached + ports: + - "11211:11211" + entrypoint: + - memcached + - -m 64 + +volumes: + db: + staticvol: + mediavol: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c4e24c71..cc83e104 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,6 +31,7 @@ jobs: python-version: 3.9 - name: Install dependencies run: | + sudo apt install libmemcached-dev python -m pip install --upgrade pip pip install -r requirements.txt - name: Lint with flake8 diff --git a/.gitignore b/.gitignore index a2ed0186..efa184a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ +export/ env/ .vs/ venv/ diff --git a/Dockerfile b/Dockerfile index 256a4778..d8a05641 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:latest +FROM amd64/ubuntu:latest RUN DEBIAN_FRONTEND=noninteractive \ apt-get update -y && \ @@ -10,10 +10,9 @@ RUN DEBIAN_FRONTEND=noninteractive \ libmemcached-dev \ virtualenv libpq-dev -y && \ apt-get upgrade -y -RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - && \ +RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ apt-get install nodejs -y RUN pip install sphinx -RUN apt-get update && apt-get install -y dos2unix COPY requirements.txt /opt/app/requirements.txt RUN mkdir /opt/app/static diff --git a/Dockerfile-celery b/Dockerfile-celery index aef35300..b886fe70 100755 --- a/Dockerfile-celery +++ b/Dockerfile-celery @@ -1,4 +1,4 @@ -FROM ubuntu:latest +FROM amd64/ubuntu:latest RUN DEBIAN_FRONTEND=noninteractive \ apt-get update -y && \ @@ -10,10 +10,9 @@ RUN DEBIAN_FRONTEND=noninteractive \ libmemcached-dev \ virtualenv libpq-dev -y && \ apt-get upgrade -y -RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - && \ +RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ apt-get install nodejs -y RUN pip install sphinx -RUN apt-get update && apt-get install -y dos2unix COPY requirements.txt /opt/app/requirements.txt WORKDIR /opt/app @@ -22,4 +21,4 @@ RUN pip3 install -r requirements.txt EXPOSE 8081 ARG FOO -COPY . /opt/app \ No newline at end of file +COPY . /opt/app diff --git a/app_mr b/app_mr index 1441c88a..5e50aa42 160000 --- a/app_mr +++ b/app_mr @@ -1 +1 @@ -Subproject commit 1441c88a566301cf521566a282f8773a9bb0a6ce +Subproject commit 5e50aa42ded35dbf4cd9cd85866cdbfefca5e331 diff --git a/build-celery-beat.sh b/build-celery-beat.sh new file mode 100755 index 00000000..7cc57a02 --- /dev/null +++ b/build-celery-beat.sh @@ -0,0 +1,3 @@ +#!/bin/bash +/opt/app/build.sh all +celery --app=femr_onchain beat --loglevel=info \ No newline at end of file diff --git a/build.sh b/build.sh index e30a2656..e4ea3375 100755 --- a/build.sh +++ b/build.sh @@ -17,10 +17,7 @@ export ENVIRONMENT=$ENVIRONMENT function all() { migrate pip3 install -r requirements.txt - python3 -m safety check -r requirements.txt - python3 manage.py check static - run_tests pushd ./main/static/main/js || exit npm install popd || exit @@ -106,7 +103,7 @@ function check() { # that PyLint can't see. clear && \ black . && \ - ./build.sh test && \ + run_tests && \ pylint main app_mr clinic_messages --disable=E1101,W0613,R0903,C0301,C0114,C0115,C0116,R0801 } @@ -157,7 +154,6 @@ setup) ;; init-all-run) - check all setup gunicorn_run diff --git a/clinic_messages b/clinic_messages index fd6c292b..b7b85ae7 160000 --- a/clinic_messages +++ b/clinic_messages @@ -1 +1 @@ -Subproject commit fd6c292b7197bc9b75ba3b75f236052c41a34216 +Subproject commit b7b85ae7de994ef013f49391f070f455bfca9a0f diff --git a/devspace.yaml b/devspace.yaml deleted file mode 100755 index e0292281..00000000 --- a/devspace.yaml +++ /dev/null @@ -1,369 +0,0 @@ -version: v1beta11 - -images: - celery: - image: celery - dockerfile: Dockerfile-celery - context: . - dev: - image: dev - dockerfile: Dockerfile - context: . - nginx: - image: nginx - dockerfile: nginx/Dockerfile - context: nginx - -deployments: -- name: cache - helm: - componentChart: true - values: - replicas: 1 - autoScaling: - horizontal: - maxReplicas: 25 - averageCPU: 500m - averageMemory: 1Gi - rollingUpdate: - enabled: true - maxSurge: "50%" - maxUnavailable: "30%" - partition: 1 - containers: - - command: - - memcached - - -m 64 - image: memcached - name: cache-container - service: - ports: - - containerPort: 11211 - port: 11211 - protocol: TCP -- name: database - helm: - componentChart: true - values: - replicas: 1 - autoScaling: - horizontal: - maxReplicas: 25 - averageCPU: 500m - averageMemory: 1Gi - rollingUpdate: - enabled: true - maxSurge: "50%" - maxUnavailable: "30%" - partition: 1 - containers: - - env: - - name: POSTGRES_DB - value: postgres - - name: POSTGRES_PASSWORD - value: postgres - - name: POSTGRES_USER - value: postgres - image: postgres - name: database-container - volumeMounts: - - containerPath: /var/lib/postgresql/data - volume: - name: db - readOnly: false - service: - ports: - - containerPort: 5432 - port: 5432 - protocol: TCP - volumes: - - name: db - size: 10Gi -- name: dev - helm: - componentChart: true - values: - replicas: 1 - autoScaling: - horizontal: - maxReplicas: 25 - averageCPU: 500m - averageMemory: 1Gi - rollingUpdate: - enabled: true - maxSurge: "50%" - maxUnavailable: "30%" - partition: 1 - containers: - - env: - - name: ENVIRONMENT - value: LOCAL - - name: POSTGRES_DB - value: postgres - - name: POSTGRES_NAME - value: database - - name: POSTGRES_PASS - value: postgres - - name: POSTGRES_USER - value: postgres - image: dev - name: dev-container - volumeMounts: - - containerPath: /opt/app/static - volume: - name: staticvol - readOnly: false - - containerPath: /opt/app/mediafiles - volume: - name: mediavol - readOnly: false - - containerPath: /build - volume: - name: volume-1 - readOnly: false - initContainers: - - args: - - -c - - while [ ! -f /tmp/done ]; do sleep 2; done - command: - - sh - image: alpine - name: upload-volumes - volumeMounts: - - containerPath: /build - volume: - name: volume-1 - readOnly: false - service: - ports: - - containerPort: 8081 - port: 8081 - protocol: TCP - volumes: - - name: staticvol - size: 5Gi - - name: mediavol - size: 5Gi - - emptyDir: {} - name: volume-1 -- name: nginx - # This deployment uses `helm` but you can also define `kubectl` deployments or kustomizations - helm: - # We are deploying the so-called Component Chart: https://devspace.sh/component-chart/docs - componentChart: true - # Under `values` we can define the values for this Helm chart used during `helm install/upgrade` - # You may also use `valuesFiles` to load values from files, e.g. valuesFiles: ["values.yaml"] - values: - replicas: 1 - autoScaling: - horizontal: - maxReplicas: 25 - averageCPU: 500m - averageMemory: 1Gi - rollingUpdate: - enabled: true - maxSurge: "50%" - maxUnavailable: "30%" - partition: 1 - containers: - - image: nginx - name: nginx-container - volumeMounts: - - containerPath: /opt/app/static - volume: - name: staticvol - readOnly: false - - containerPath: /opt/app/mediafiles - volume: - name: mediavol - readOnly: false - ingress: - tls: true - rules: - - host: chain.teamfemr.org - - host: chain-dev.teamfemr.org - - host: chain-training.teamfemr.org - - host: localhost - service: - ports: - - containerPort: 80 - port: 1337 - protocol: TCP - volumes: - - name: staticvol - size: 5Gi - - name: mediavol - size: 5Gi -- name: redis - # This deployment uses `helm` but you can also define `kubectl` deployments or kustomizations - helm: - # We are deploying the so-called Component Chart: https://devspace.sh/component-chart/docs - componentChart: true - # Under `values` we can define the values for this Helm chart used during `helm install/upgrade` - # You may also use `valuesFiles` to load values from files, e.g. valuesFiles: ["values.yaml"] - values: - containers: - - image: redis:6-alpine - name: redis-container - service: - ports: - - containerPort: 6379 - port: 6379 - protocol: TCP -- name: celery - # This deployment uses `helm` but you can also define `kubectl` deployments or kustomizations - helm: - # We are deploying the so-called Component Chart: https://devspace.sh/component-chart/docs - componentChart: true - # Under `values` we can define the values for this Helm chart used during `helm install/upgrade` - # You may also use `valuesFiles` to load values from files, e.g. valuesFiles: ["values.yaml"] - values: - containers: - - env: - - name: CELERY_BACKEND - value: redis://redis:6379/0 - - name: CELERY_BROKER - value: redis://redis:6379/0 - - name: ENVIRONMENT - value: LOCAL - - name: POSTGRES_DB - value: postgres - - name: POSTGRES_NAME - value: database - - name: POSTGRES_PASS - value: postgres - - name: POSTGRES_USER - value: postgres - - name: SECRET_KEY - value: 2HY>fXi!dQ&(9Vf.XghCa;L6G=Ul4r-Bwqh>ae0RG3vIh1ZJ%T - image: celery - name: celery-container - volumeMounts: - - containerPath: /build - volume: - name: volume-1 - readOnly: false - initContainers: - - args: - - -c - - while [ ! -f /tmp/done ]; do sleep 2; done - command: - - sh - image: alpine - name: upload-volumes - volumeMounts: - - containerPath: /build - volume: - name: volume-1 - readOnly: false - volumes: - - emptyDir: {} - name: volume-1 - -# `dev` only applies when you run `devspace dev` -dev: - # `dev.ports` specifies all ports that should be forwarded while `devspace dev` is running - # Port-forwarding lets you access your application via localhost on your local machine - ports: - - labelSelector: - app.kubernetes.io/component: cache - forward: - - port: 11211 - - labelSelector: - app.kubernetes.io/component: database - forward: - - port: 5432 - - labelSelector: - app.kubernetes.io/component: dev - forward: - - port: 8081 - - labelSelector: - app.kubernetes.io/component: nginx - forward: - - port: 1337 - remotePort: 80 - - labelSelector: - app.kubernetes.io/component: redis - forward: - - port: 6379 - # `dev.sync` configures a file sync between our Pods in k8s and your local project files - sync: - - labelSelector: - app.kubernetes.io/component: dev - containerName: dev-container - localSubPath: . - containerPath: /build - - labelSelector: - app.kubernetes.io/component: celery - containerName: celery-container - localSubPath: . - containerPath: /build -hooks: -- events: - - after:deploy:cache - wait: - running: true - terminatedWithCode: 0 - container: - labelSelector: - app.kubernetes.io/component: cache - containerName: cache-container -- events: - - after:deploy:database - wait: - running: true - terminatedWithCode: 0 - container: - labelSelector: - app.kubernetes.io/component: database - containerName: database-container -- events: - - after:deploy:dev - upload: - localPath: . - containerPath: /build - container: - labelSelector: - app.kubernetes.io/component: dev - containerName: upload-volumes -- events: - - after:deploy:dev - command: touch /tmp/done - container: - labelSelector: - app.kubernetes.io/component: dev - containerName: upload-volumes -- events: - - after:deploy:dev - wait: - running: true - terminatedWithCode: 0 - container: - labelSelector: - app.kubernetes.io/component: dev - containerName: dev-container -- events: - - after:deploy:redis - wait: - running: true - terminatedWithCode: 0 - container: - labelSelector: - app.kubernetes.io/component: redis - containerName: redis-container -- events: - - after:deploy:celery - upload: - localPath: . - containerPath: /build - container: - labelSelector: - app.kubernetes.io/component: celery - containerName: upload-volumes -- events: - - after:deploy:celery - command: touch /tmp/done - container: - labelSelector: - app.kubernetes.io/component: celery - containerName: upload-volumes diff --git a/docker-compose.yml b/docker-compose.yml index c343a75f..8ebcd872 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,6 +56,39 @@ services: - "database" - "cache" + celery_beat: + build: + context: . + dockerfile: Dockerfile-celery + volumes: + - .:/build + entrypoint: "./build-celery-beat.sh" + environment: + - DEBUG=True + - QLDB_ENABLED="FALSE" + - qldb_name="fEMR-OnChain-Test" + - ADMIN_NAME="" + - ADMIN_EMAIL="" + - EMAIL_HOST="" + - EMAIL_PORT="" + - EMAIL_USERNAME="" + - EMAIL_PASSWORD="" + - DEFAULT_FROM_EMAIL="" + - SERVER_EMAIL="" + - ENVIRONMENT="LOCAL" + - SECRET_KEY=2HY>fXi!dQ&(9Vf.XghCa;L6G=Ul4r-Bwqh>ae0RG3vIh1ZJ%T + - CELERY_BROKER=redis://redis:6379/0 + - CELERY_BACKEND=redis://redis:6379/0 + - POSTGRES_DB=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASS=postgres + - POSTGRES_NAME=database + - ENVIRONMENT=LOCAL + depends_on: + - "dev" + - "redis" + - "database" + celery: build: context: . diff --git a/docs/_autosummary/appMR.admin.html b/docs/_autosummary/appMR.admin.html index 3c05ca96..09d65577 100644 --- a/docs/_autosummary/appMR.admin.html +++ b/docs/_autosummary/appMR.admin.html @@ -6,7 +6,7 @@ - app_mr.admin — fEMR OnChain v1.5.3 documentation + app_mr.admin — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.apps.html b/docs/_autosummary/appMR.apps.html index d449a8a7..1dae80d6 100644 --- a/docs/_autosummary/appMR.apps.html +++ b/docs/_autosummary/appMR.apps.html @@ -6,7 +6,7 @@ - app_mr.apps — fEMR OnChain v1.5.3 documentation + app_mr.apps — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.background_tasks.html b/docs/_autosummary/appMR.background_tasks.html index 310a0f95..b2ad70e4 100644 --- a/docs/_autosummary/appMR.background_tasks.html +++ b/docs/_autosummary/appMR.background_tasks.html @@ -6,7 +6,7 @@ - app_mr.background_tasks — fEMR OnChain v1.5.3 documentation + app_mr.background_tasks — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.forms.html b/docs/_autosummary/appMR.forms.html index 3d8dd579..7febba6c 100644 --- a/docs/_autosummary/appMR.forms.html +++ b/docs/_autosummary/appMR.forms.html @@ -6,7 +6,7 @@ - app_mr.forms — fEMR OnChain v1.5.3 documentation + app_mr.forms — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.html b/docs/_autosummary/appMR.html index f713624e..5aaf7f96 100644 --- a/docs/_autosummary/appMR.html +++ b/docs/_autosummary/appMR.html @@ -6,7 +6,7 @@ - app_mr — fEMR OnChain v1.5.3 documentation + app_mr — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.migrations.0001_initial.html b/docs/_autosummary/appMR.migrations.0001_initial.html index 2f2a8dd3..e4144223 100644 --- a/docs/_autosummary/appMR.migrations.0001_initial.html +++ b/docs/_autosummary/appMR.migrations.0001_initial.html @@ -6,7 +6,7 @@ - app_mr.migrations.0001_initial — fEMR OnChain v1.5.3 documentation + app_mr.migrations.0001_initial — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.migrations.0002_supportticket_active.html b/docs/_autosummary/appMR.migrations.0002_supportticket_active.html index f51512be..a5c0dc77 100644 --- a/docs/_autosummary/appMR.migrations.0002_supportticket_active.html +++ b/docs/_autosummary/appMR.migrations.0002_supportticket_active.html @@ -6,7 +6,7 @@ - app_mr.migrations.0002_supportticket_active — fEMR OnChain v1.5.3 documentation + app_mr.migrations.0002_supportticket_active — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.migrations.0003_auto_20210306_1728.html b/docs/_autosummary/appMR.migrations.0003_auto_20210306_1728.html index 44bf60e8..b57b7049 100644 --- a/docs/_autosummary/appMR.migrations.0003_auto_20210306_1728.html +++ b/docs/_autosummary/appMR.migrations.0003_auto_20210306_1728.html @@ -6,7 +6,7 @@ - app_mr.migrations.0003_auto_20210306_1728 — fEMR OnChain v1.5.3 documentation + app_mr.migrations.0003_auto_20210306_1728 — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.migrations.0004_auto_20210306_1743.html b/docs/_autosummary/appMR.migrations.0004_auto_20210306_1743.html index 91d0cd5b..63d4ebf7 100644 --- a/docs/_autosummary/appMR.migrations.0004_auto_20210306_1743.html +++ b/docs/_autosummary/appMR.migrations.0004_auto_20210306_1743.html @@ -6,7 +6,7 @@ - app_mr.migrations.0004_auto_20210306_1743 — fEMR OnChain v1.5.3 documentation + app_mr.migrations.0004_auto_20210306_1743 — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.migrations.0005_auto_20210306_1746.html b/docs/_autosummary/appMR.migrations.0005_auto_20210306_1746.html index 76164f79..83e13df6 100644 --- a/docs/_autosummary/appMR.migrations.0005_auto_20210306_1746.html +++ b/docs/_autosummary/appMR.migrations.0005_auto_20210306_1746.html @@ -6,7 +6,7 @@ - app_mr.migrations.0005_auto_20210306_1746 — fEMR OnChain v1.5.3 documentation + app_mr.migrations.0005_auto_20210306_1746 — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.migrations.0006_auto_20210306_1748.html b/docs/_autosummary/appMR.migrations.0006_auto_20210306_1748.html index d1b912df..8d4ea7d7 100644 --- a/docs/_autosummary/appMR.migrations.0006_auto_20210306_1748.html +++ b/docs/_autosummary/appMR.migrations.0006_auto_20210306_1748.html @@ -6,7 +6,7 @@ - app_mr.migrations.0006_auto_20210306_1748 — fEMR OnChain v1.5.3 documentation + app_mr.migrations.0006_auto_20210306_1748 — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.migrations.0007_auto_20210306_1808.html b/docs/_autosummary/appMR.migrations.0007_auto_20210306_1808.html index 77459120..e27061f6 100644 --- a/docs/_autosummary/appMR.migrations.0007_auto_20210306_1808.html +++ b/docs/_autosummary/appMR.migrations.0007_auto_20210306_1808.html @@ -6,7 +6,7 @@ - app_mr.migrations.0007_auto_20210306_1808 — fEMR OnChain v1.5.3 documentation + app_mr.migrations.0007_auto_20210306_1808 — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.migrations.0008_auto_20210306_1839.html b/docs/_autosummary/appMR.migrations.0008_auto_20210306_1839.html index 4f1c3aa3..ee3bec0a 100644 --- a/docs/_autosummary/appMR.migrations.0008_auto_20210306_1839.html +++ b/docs/_autosummary/appMR.migrations.0008_auto_20210306_1839.html @@ -6,7 +6,7 @@ - app_mr.migrations.0008_auto_20210306_1839 — fEMR OnChain v1.5.3 documentation + app_mr.migrations.0008_auto_20210306_1839 — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.migrations.0009_auto_20210803_1440.html b/docs/_autosummary/appMR.migrations.0009_auto_20210803_1440.html index 766b1a28..5b766ec3 100644 --- a/docs/_autosummary/appMR.migrations.0009_auto_20210803_1440.html +++ b/docs/_autosummary/appMR.migrations.0009_auto_20210803_1440.html @@ -6,7 +6,7 @@ - app_mr.migrations.0009_auto_20210803_1440 — fEMR OnChain v1.5.3 documentation + app_mr.migrations.0009_auto_20210803_1440 — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.migrations.0010_auto_20210830_1504.html b/docs/_autosummary/appMR.migrations.0010_auto_20210830_1504.html index 1e62ca76..4749e6bb 100644 --- a/docs/_autosummary/appMR.migrations.0010_auto_20210830_1504.html +++ b/docs/_autosummary/appMR.migrations.0010_auto_20210830_1504.html @@ -6,7 +6,7 @@ - app_mr.migrations.0010_auto_20210830_1504 — fEMR OnChain v1.5.3 documentation + app_mr.migrations.0010_auto_20210830_1504 — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.migrations.html b/docs/_autosummary/appMR.migrations.html index 9a4cfa40..f3043c7f 100644 --- a/docs/_autosummary/appMR.migrations.html +++ b/docs/_autosummary/appMR.migrations.html @@ -6,7 +6,7 @@ - app_mr.migrations — fEMR OnChain v1.5.3 documentation + app_mr.migrations — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.models.html b/docs/_autosummary/appMR.models.html index b43f70af..821d5cbb 100644 --- a/docs/_autosummary/appMR.models.html +++ b/docs/_autosummary/appMR.models.html @@ -6,7 +6,7 @@ - app_mr.models — fEMR OnChain v1.5.3 documentation + app_mr.models — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.tests.html b/docs/_autosummary/appMR.tests.html index 0ea8c9f6..ff18feaa 100644 --- a/docs/_autosummary/appMR.tests.html +++ b/docs/_autosummary/appMR.tests.html @@ -6,7 +6,7 @@ - app_mr.tests — fEMR OnChain v1.5.3 documentation + app_mr.tests — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.tests.test_background_tasks.html b/docs/_autosummary/appMR.tests.test_background_tasks.html index e162477d..7c907999 100644 --- a/docs/_autosummary/appMR.tests.test_background_tasks.html +++ b/docs/_autosummary/appMR.tests.test_background_tasks.html @@ -6,7 +6,7 @@ - app_mr.tests.test_background_tasks — fEMR OnChain v1.5.3 documentation + app_mr.tests.test_background_tasks — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.urls.html b/docs/_autosummary/appMR.urls.html index 6eb83c11..0a0f4234 100644 --- a/docs/_autosummary/appMR.urls.html +++ b/docs/_autosummary/appMR.urls.html @@ -6,7 +6,7 @@ - app_mr.urls — fEMR OnChain v1.5.3 documentation + app_mr.urls — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/appMR.views.html b/docs/_autosummary/appMR.views.html index e2619379..53a9d571 100644 --- a/docs/_autosummary/appMR.views.html +++ b/docs/_autosummary/appMR.views.html @@ -6,7 +6,7 @@ - app_mr.views — fEMR OnChain v1.5.3 documentation + app_mr.views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.admin.html b/docs/_autosummary/clinic_messages.admin.html index 2b971e52..2e3b3180 100644 --- a/docs/_autosummary/clinic_messages.admin.html +++ b/docs/_autosummary/clinic_messages.admin.html @@ -6,7 +6,7 @@ - clinic_messages.admin — fEMR OnChain v1.5.3 documentation + clinic_messages.admin — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.apps.html b/docs/_autosummary/clinic_messages.apps.html index ed7dd6d7..a9efecdc 100644 --- a/docs/_autosummary/clinic_messages.apps.html +++ b/docs/_autosummary/clinic_messages.apps.html @@ -6,7 +6,7 @@ - clinic_messages.apps — fEMR OnChain v1.5.3 documentation + clinic_messages.apps — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.form.html b/docs/_autosummary/clinic_messages.form.html index c49ed3b9..0aea5ebe 100644 --- a/docs/_autosummary/clinic_messages.form.html +++ b/docs/_autosummary/clinic_messages.form.html @@ -6,7 +6,7 @@ - clinic_messages.form — fEMR OnChain v1.5.3 documentation + clinic_messages.form — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.html b/docs/_autosummary/clinic_messages.html index 2e6c6c2e..473e3de9 100644 --- a/docs/_autosummary/clinic_messages.html +++ b/docs/_autosummary/clinic_messages.html @@ -6,7 +6,7 @@ - clinic_messages — fEMR OnChain v1.5.3 documentation + clinic_messages — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.migrations.0001_initial.html b/docs/_autosummary/clinic_messages.migrations.0001_initial.html index 6781e719..36f9f8cb 100644 --- a/docs/_autosummary/clinic_messages.migrations.0001_initial.html +++ b/docs/_autosummary/clinic_messages.migrations.0001_initial.html @@ -6,7 +6,7 @@ - clinic_messages.migrations.0001_initial — fEMR OnChain v1.5.3 documentation + clinic_messages.migrations.0001_initial — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.migrations.0002_message_read.html b/docs/_autosummary/clinic_messages.migrations.0002_message_read.html index 7f8bf8c3..da8bee63 100644 --- a/docs/_autosummary/clinic_messages.migrations.0002_message_read.html +++ b/docs/_autosummary/clinic_messages.migrations.0002_message_read.html @@ -6,7 +6,7 @@ - clinic_messages.migrations.0002_message_read — fEMR OnChain v1.5.3 documentation + clinic_messages.migrations.0002_message_read — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.migrations.0003_alter_message_read.html b/docs/_autosummary/clinic_messages.migrations.0003_alter_message_read.html index 0406cadf..c3bfde5c 100644 --- a/docs/_autosummary/clinic_messages.migrations.0003_alter_message_read.html +++ b/docs/_autosummary/clinic_messages.migrations.0003_alter_message_read.html @@ -6,7 +6,7 @@ - clinic_messages.migrations.0003_alter_message_read — fEMR OnChain v1.5.3 documentation + clinic_messages.migrations.0003_alter_message_read — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.migrations.0004_auto_20210608_2159.html b/docs/_autosummary/clinic_messages.migrations.0004_auto_20210608_2159.html index 7e716082..1e138686 100644 --- a/docs/_autosummary/clinic_messages.migrations.0004_auto_20210608_2159.html +++ b/docs/_autosummary/clinic_messages.migrations.0004_auto_20210608_2159.html @@ -6,7 +6,7 @@ - clinic_messages.migrations.0004_auto_20210608_2159 — fEMR OnChain v1.5.3 documentation + clinic_messages.migrations.0004_auto_20210608_2159 — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.migrations.0005_message_timestamp.html b/docs/_autosummary/clinic_messages.migrations.0005_message_timestamp.html index ff1c2dd7..c63ec01e 100644 --- a/docs/_autosummary/clinic_messages.migrations.0005_message_timestamp.html +++ b/docs/_autosummary/clinic_messages.migrations.0005_message_timestamp.html @@ -6,7 +6,7 @@ - clinic_messages.migrations.0005_message_timestamp — fEMR OnChain v1.5.3 documentation + clinic_messages.migrations.0005_message_timestamp — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.migrations.html b/docs/_autosummary/clinic_messages.migrations.html index f392139b..cdd8e5c4 100644 --- a/docs/_autosummary/clinic_messages.migrations.html +++ b/docs/_autosummary/clinic_messages.migrations.html @@ -6,7 +6,7 @@ - clinic_messages.migrations — fEMR OnChain v1.5.3 documentation + clinic_messages.migrations — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.models.html b/docs/_autosummary/clinic_messages.models.html index 848df42f..0fe391f5 100644 --- a/docs/_autosummary/clinic_messages.models.html +++ b/docs/_autosummary/clinic_messages.models.html @@ -6,7 +6,7 @@ - clinic_messages.models — fEMR OnChain v1.5.3 documentation + clinic_messages.models — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.tests.html b/docs/_autosummary/clinic_messages.tests.html index f3cbfd05..03f7fe2d 100644 --- a/docs/_autosummary/clinic_messages.tests.html +++ b/docs/_autosummary/clinic_messages.tests.html @@ -6,7 +6,7 @@ - clinic_messages.tests — fEMR OnChain v1.5.3 documentation + clinic_messages.tests — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.urls.html b/docs/_autosummary/clinic_messages.urls.html index 0c4126d7..82bb3f4d 100644 --- a/docs/_autosummary/clinic_messages.urls.html +++ b/docs/_autosummary/clinic_messages.urls.html @@ -6,7 +6,7 @@ - clinic_messages.urls — fEMR OnChain v1.5.3 documentation + clinic_messages.urls — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/clinic_messages.views.html b/docs/_autosummary/clinic_messages.views.html index 059d6fa6..0aa56667 100644 --- a/docs/_autosummary/clinic_messages.views.html +++ b/docs/_autosummary/clinic_messages.views.html @@ -6,7 +6,7 @@ - clinic_messages.views — fEMR OnChain v1.5.3 documentation + clinic_messages.views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.admin.html b/docs/_autosummary/main.admin.html index 92cdd021..92730147 100644 --- a/docs/_autosummary/main.admin.html +++ b/docs/_autosummary/main.admin.html @@ -6,7 +6,7 @@ - main.admin — fEMR OnChain v1.5.3 documentation + main.admin — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.admin_views.html b/docs/_autosummary/main.admin_views.html index 1c9badce..1c58b03f 100644 --- a/docs/_autosummary/main.admin_views.html +++ b/docs/_autosummary/main.admin_views.html @@ -6,7 +6,7 @@ - main.admin_views — fEMR OnChain v1.5.3 documentation + main.admin_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.api_permissions.html b/docs/_autosummary/main.api_permissions.html index c1156ebd..6bab26fb 100644 --- a/docs/_autosummary/main.api_permissions.html +++ b/docs/_autosummary/main.api_permissions.html @@ -6,7 +6,7 @@ - main.api_permissions — fEMR OnChain v1.5.3 documentation + main.api_permissions — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.api_views.html b/docs/_autosummary/main.api_views.html index f63dadf6..d2aaab6b 100644 --- a/docs/_autosummary/main.api_views.html +++ b/docs/_autosummary/main.api_views.html @@ -6,7 +6,7 @@ - main.api_views — fEMR OnChain v1.5.3 documentation + main.api_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.apps.html b/docs/_autosummary/main.apps.html index b92a142a..770b392e 100644 --- a/docs/_autosummary/main.apps.html +++ b/docs/_autosummary/main.apps.html @@ -6,7 +6,7 @@ - main.apps — fEMR OnChain v1.5.3 documentation + main.apps — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.auth_views.html b/docs/_autosummary/main.auth_views.html index bb2f045d..934addb2 100644 --- a/docs/_autosummary/main.auth_views.html +++ b/docs/_autosummary/main.auth_views.html @@ -6,7 +6,7 @@ - main.auth_views — fEMR OnChain v1.5.3 documentation + main.auth_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.autocomplete_views.html b/docs/_autosummary/main.autocomplete_views.html index 33b2d388..ca9c9cb7 100644 --- a/docs/_autosummary/main.autocomplete_views.html +++ b/docs/_autosummary/main.autocomplete_views.html @@ -6,7 +6,7 @@ - main.autocomplete_views — fEMR OnChain v1.5.3 documentation + main.autocomplete_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.background_tasks.html b/docs/_autosummary/main.background_tasks.html index 8a0b0b71..32e55b10 100644 --- a/docs/_autosummary/main.background_tasks.html +++ b/docs/_autosummary/main.background_tasks.html @@ -6,7 +6,7 @@ - main.background_tasks — fEMR OnChain v1.5.3 documentation + main.background_tasks — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.csvio.added_inventory.html b/docs/_autosummary/main.csvio.added_inventory.html index b60a4616..986b6946 100644 --- a/docs/_autosummary/main.csvio.added_inventory.html +++ b/docs/_autosummary/main.csvio.added_inventory.html @@ -6,7 +6,7 @@ - main.csvio.added_inventory — fEMR OnChain v1.5.3 documentation + main.csvio.added_inventory — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.csvio.csv_interface.html b/docs/_autosummary/main.csvio.csv_interface.html index 12e5d4d6..71b27a74 100644 --- a/docs/_autosummary/main.csvio.csv_interface.html +++ b/docs/_autosummary/main.csvio.csv_interface.html @@ -6,7 +6,7 @@ - main.csvio.csv_interface — fEMR OnChain v1.5.3 documentation + main.csvio.csv_interface — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.csvio.html b/docs/_autosummary/main.csvio.html index 63be40d8..c6770125 100644 --- a/docs/_autosummary/main.csvio.html +++ b/docs/_autosummary/main.csvio.html @@ -6,7 +6,7 @@ - main.csvio — fEMR OnChain v1.5.3 documentation + main.csvio — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.csvio.initial_inventory.html b/docs/_autosummary/main.csvio.initial_inventory.html index 7abc1599..776d2064 100644 --- a/docs/_autosummary/main.csvio.initial_inventory.html +++ b/docs/_autosummary/main.csvio.initial_inventory.html @@ -6,7 +6,7 @@ - main.csvio.initial_inventory — fEMR OnChain v1.5.3 documentation + main.csvio.initial_inventory — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.delete_views.html b/docs/_autosummary/main.delete_views.html index cec4279c..36798412 100644 --- a/docs/_autosummary/main.delete_views.html +++ b/docs/_autosummary/main.delete_views.html @@ -6,7 +6,7 @@ - main.delete_views — fEMR OnChain v1.5.3 documentation + main.delete_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.edit_views.html b/docs/_autosummary/main.edit_views.html index 587dd326..b0978ffc 100644 --- a/docs/_autosummary/main.edit_views.html +++ b/docs/_autosummary/main.edit_views.html @@ -6,7 +6,7 @@ - main.edit_views — fEMR OnChain v1.5.3 documentation + main.edit_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.femr_admin_views.html b/docs/_autosummary/main.femr_admin_views.html index 5953159f..274c019c 100644 --- a/docs/_autosummary/main.femr_admin_views.html +++ b/docs/_autosummary/main.femr_admin_views.html @@ -6,7 +6,7 @@ - main.femr_admin_views — fEMR OnChain v1.5.3 documentation + main.femr_admin_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.form_views.html b/docs/_autosummary/main.form_views.html index 56278662..70e65a61 100644 --- a/docs/_autosummary/main.form_views.html +++ b/docs/_autosummary/main.form_views.html @@ -6,7 +6,7 @@ - main.form_views — fEMR OnChain v1.5.3 documentation + main.form_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.forms.html b/docs/_autosummary/main.forms.html index 098a84cb..fc31f093 100644 --- a/docs/_autosummary/main.forms.html +++ b/docs/_autosummary/main.forms.html @@ -6,7 +6,7 @@ - main.forms — fEMR OnChain v1.5.3 documentation + main.forms — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.formulary_management.html b/docs/_autosummary/main.formulary_management.html index 26aca986..64d0b250 100644 --- a/docs/_autosummary/main.formulary_management.html +++ b/docs/_autosummary/main.formulary_management.html @@ -6,7 +6,7 @@ - main.formulary_management — fEMR OnChain v1.5.3 documentation + main.formulary_management — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.html b/docs/_autosummary/main.html index dc0210ce..e10dfe35 100644 --- a/docs/_autosummary/main.html +++ b/docs/_autosummary/main.html @@ -6,7 +6,7 @@ - main — fEMR OnChain v1.5.3 documentation + main — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.list_views.html b/docs/_autosummary/main.list_views.html index 8446bb6e..0c6694f7 100644 --- a/docs/_autosummary/main.list_views.html +++ b/docs/_autosummary/main.list_views.html @@ -6,7 +6,7 @@ - main.list_views — fEMR OnChain v1.5.3 documentation + main.list_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.management.commands.adminoptions.html b/docs/_autosummary/main.management.commands.adminoptions.html index d50c6eff..0871d7c7 100644 --- a/docs/_autosummary/main.management.commands.adminoptions.html +++ b/docs/_autosummary/main.management.commands.adminoptions.html @@ -6,7 +6,7 @@ - main.management.commands.adminoptions — fEMR OnChain v1.5.3 documentation + main.management.commands.adminoptions — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.management.commands.createadmin.html b/docs/_autosummary/main.management.commands.createadmin.html index a231fede..d69fd913 100644 --- a/docs/_autosummary/main.management.commands.createadmin.html +++ b/docs/_autosummary/main.management.commands.createadmin.html @@ -6,7 +6,7 @@ - main.management.commands.createadmin — fEMR OnChain v1.5.3 documentation + main.management.commands.createadmin — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.management.commands.creategroups.html b/docs/_autosummary/main.management.commands.creategroups.html index 9295d219..c00f41ad 100644 --- a/docs/_autosummary/main.management.commands.creategroups.html +++ b/docs/_autosummary/main.management.commands.creategroups.html @@ -6,7 +6,7 @@ - main.management.commands.creategroups — fEMR OnChain v1.5.3 documentation + main.management.commands.creategroups — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.management.commands.createinventoryforms.html b/docs/_autosummary/main.management.commands.createinventoryforms.html index fbef1944..92b5d1e2 100644 --- a/docs/_autosummary/main.management.commands.createinventoryforms.html +++ b/docs/_autosummary/main.management.commands.createinventoryforms.html @@ -6,7 +6,7 @@ - main.management.commands.createinventoryforms — fEMR OnChain v1.5.3 documentation + main.management.commands.createinventoryforms — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.management.commands.createraceandethnicity.html b/docs/_autosummary/main.management.commands.createraceandethnicity.html index 5d6fab0f..f00a5d29 100644 --- a/docs/_autosummary/main.management.commands.createraceandethnicity.html +++ b/docs/_autosummary/main.management.commands.createraceandethnicity.html @@ -6,7 +6,7 @@ - main.management.commands.createraceandethnicity — fEMR OnChain v1.5.3 documentation + main.management.commands.createraceandethnicity — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.management.commands.html b/docs/_autosummary/main.management.commands.html index 17cdb3a7..efb74110 100644 --- a/docs/_autosummary/main.management.commands.html +++ b/docs/_autosummary/main.management.commands.html @@ -6,7 +6,7 @@ - main.management.commands — fEMR OnChain v1.5.3 documentation + main.management.commands — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.management.html b/docs/_autosummary/main.management.html index cd545eeb..ad70bca9 100644 --- a/docs/_autosummary/main.management.html +++ b/docs/_autosummary/main.management.html @@ -6,7 +6,7 @@ - main.management — fEMR OnChain v1.5.3 documentation + main.management — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.middleware.html b/docs/_autosummary/main.middleware.html index 7e29de00..8617a5d2 100644 --- a/docs/_autosummary/main.middleware.html +++ b/docs/_autosummary/main.middleware.html @@ -6,7 +6,7 @@ - main.middleware — fEMR OnChain v1.5.3 documentation + main.middleware — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.migrations.0001_initial.html b/docs/_autosummary/main.migrations.0001_initial.html index f19d5ada..414824bc 100644 --- a/docs/_autosummary/main.migrations.0001_initial.html +++ b/docs/_autosummary/main.migrations.0001_initial.html @@ -6,7 +6,7 @@ - main.migrations.0001_initial — fEMR OnChain v1.5.3 documentation + main.migrations.0001_initial — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.migrations.0002_auto_20211007_1254.html b/docs/_autosummary/main.migrations.0002_auto_20211007_1254.html index 5536fa6b..fd57f898 100644 --- a/docs/_autosummary/main.migrations.0002_auto_20211007_1254.html +++ b/docs/_autosummary/main.migrations.0002_auto_20211007_1254.html @@ -6,7 +6,7 @@ - main.migrations.0002_auto_20211007_1254 — fEMR OnChain v1.5.3 documentation + main.migrations.0002_auto_20211007_1254 — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.migrations.0003_usersession.html b/docs/_autosummary/main.migrations.0003_usersession.html index b9ae0956..1d1c4eae 100644 --- a/docs/_autosummary/main.migrations.0003_usersession.html +++ b/docs/_autosummary/main.migrations.0003_usersession.html @@ -6,7 +6,7 @@ - main.migrations.0003_usersession — fEMR OnChain v1.5.3 documentation + main.migrations.0003_usersession — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.migrations.0004_alter_campaign_encounter_close.html b/docs/_autosummary/main.migrations.0004_alter_campaign_encounter_close.html index 9626c3bf..5c2ef76c 100644 --- a/docs/_autosummary/main.migrations.0004_alter_campaign_encounter_close.html +++ b/docs/_autosummary/main.migrations.0004_alter_campaign_encounter_close.html @@ -6,7 +6,7 @@ - main.migrations.0004_alter_campaign_encounter_close — fEMR OnChain v1.5.3 documentation + main.migrations.0004_alter_campaign_encounter_close — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.migrations.0005_auto_20211012_2105.html b/docs/_autosummary/main.migrations.0005_auto_20211012_2105.html index 2cfa849b..d4a3af41 100644 --- a/docs/_autosummary/main.migrations.0005_auto_20211012_2105.html +++ b/docs/_autosummary/main.migrations.0005_auto_20211012_2105.html @@ -6,7 +6,7 @@ - main.migrations.0005_auto_20211012_2105 — fEMR OnChain v1.5.3 documentation + main.migrations.0005_auto_20211012_2105 — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.migrations.html b/docs/_autosummary/main.migrations.html index 1d73ce7f..f0daf1a6 100644 --- a/docs/_autosummary/main.migrations.html +++ b/docs/_autosummary/main.migrations.html @@ -6,7 +6,7 @@ - main.migrations — fEMR OnChain v1.5.3 documentation + main.migrations — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.models.html b/docs/_autosummary/main.models.html index cc52a5f5..ff6bc624 100644 --- a/docs/_autosummary/main.models.html +++ b/docs/_autosummary/main.models.html @@ -6,7 +6,7 @@ - main.models — fEMR OnChain v1.5.3 documentation + main.models — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.operation_admin_views.html b/docs/_autosummary/main.operation_admin_views.html index 1115222b..8418ab03 100644 --- a/docs/_autosummary/main.operation_admin_views.html +++ b/docs/_autosummary/main.operation_admin_views.html @@ -6,7 +6,7 @@ - main.operation_admin_views — fEMR OnChain v1.5.3 documentation + main.operation_admin_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.organization_admin_views.html b/docs/_autosummary/main.organization_admin_views.html index 81ce8dc9..abe9e6d0 100644 --- a/docs/_autosummary/main.organization_admin_views.html +++ b/docs/_autosummary/main.organization_admin_views.html @@ -6,7 +6,7 @@ - main.organization_admin_views — fEMR OnChain v1.5.3 documentation + main.organization_admin_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.password_validators.html b/docs/_autosummary/main.password_validators.html index 37d7d1f2..d42d0619 100644 --- a/docs/_autosummary/main.password_validators.html +++ b/docs/_autosummary/main.password_validators.html @@ -6,7 +6,7 @@ - main.password_validators — fEMR OnChain v1.5.3 documentation + main.password_validators — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.pharmacy_views.html b/docs/_autosummary/main.pharmacy_views.html index 52856a81..17c450f9 100644 --- a/docs/_autosummary/main.pharmacy_views.html +++ b/docs/_autosummary/main.pharmacy_views.html @@ -6,7 +6,7 @@ - main.pharmacy_views — fEMR OnChain v1.5.3 documentation + main.pharmacy_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.qldb_interface.html b/docs/_autosummary/main.qldb_interface.html index 4346ff16..606b8765 100644 --- a/docs/_autosummary/main.qldb_interface.html +++ b/docs/_autosummary/main.qldb_interface.html @@ -6,7 +6,7 @@ - main.qldb_interface — fEMR OnChain v1.5.3 documentation + main.qldb_interface — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.serializers.html b/docs/_autosummary/main.serializers.html index ad9a7ad5..3fa632a6 100644 --- a/docs/_autosummary/main.serializers.html +++ b/docs/_autosummary/main.serializers.html @@ -6,7 +6,7 @@ - main.serializers — fEMR OnChain v1.5.3 documentation + main.serializers — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.small_forms_views.html b/docs/_autosummary/main.small_forms_views.html index af10a41c..7507304a 100644 --- a/docs/_autosummary/main.small_forms_views.html +++ b/docs/_autosummary/main.small_forms_views.html @@ -6,7 +6,7 @@ - main.small_forms_views — fEMR OnChain v1.5.3 documentation + main.small_forms_views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.survey_models.html b/docs/_autosummary/main.survey_models.html index 1dee1fe2..c095ab1c 100644 --- a/docs/_autosummary/main.survey_models.html +++ b/docs/_autosummary/main.survey_models.html @@ -6,7 +6,7 @@ - main.survey_models — fEMR OnChain v1.5.3 documentation + main.survey_models — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.templatetags.campaign_tags.html b/docs/_autosummary/main.templatetags.campaign_tags.html index 8ae8393c..8333cd32 100644 --- a/docs/_autosummary/main.templatetags.campaign_tags.html +++ b/docs/_autosummary/main.templatetags.campaign_tags.html @@ -6,7 +6,7 @@ - main.templatetags.campaign_tags — fEMR OnChain v1.5.3 documentation + main.templatetags.campaign_tags — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.templatetags.encounter_tags.html b/docs/_autosummary/main.templatetags.encounter_tags.html index 7d588ae2..97ea0eab 100644 --- a/docs/_autosummary/main.templatetags.encounter_tags.html +++ b/docs/_autosummary/main.templatetags.encounter_tags.html @@ -6,7 +6,7 @@ - main.templatetags.encounter_tags — fEMR OnChain v1.5.3 documentation + main.templatetags.encounter_tags — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.templatetags.help_tags.html b/docs/_autosummary/main.templatetags.help_tags.html index 07fca612..b6db1477 100644 --- a/docs/_autosummary/main.templatetags.help_tags.html +++ b/docs/_autosummary/main.templatetags.help_tags.html @@ -6,7 +6,7 @@ - main.templatetags.help_tags — fEMR OnChain v1.5.3 documentation + main.templatetags.help_tags — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.templatetags.html b/docs/_autosummary/main.templatetags.html index 343b1671..47a5de77 100644 --- a/docs/_autosummary/main.templatetags.html +++ b/docs/_autosummary/main.templatetags.html @@ -6,7 +6,7 @@ - main.templatetags — fEMR OnChain v1.5.3 documentation + main.templatetags — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.templatetags.patient_tags.html b/docs/_autosummary/main.templatetags.patient_tags.html index 7e8e4b75..27d90ecf 100644 --- a/docs/_autosummary/main.templatetags.patient_tags.html +++ b/docs/_autosummary/main.templatetags.patient_tags.html @@ -6,7 +6,7 @@ - main.templatetags.patient_tags — fEMR OnChain v1.5.3 documentation + main.templatetags.patient_tags — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.templatetags.user_tags.html b/docs/_autosummary/main.templatetags.user_tags.html index 3fcd07cd..7c44048a 100644 --- a/docs/_autosummary/main.templatetags.user_tags.html +++ b/docs/_autosummary/main.templatetags.user_tags.html @@ -6,7 +6,7 @@ - main.templatetags.user_tags — fEMR OnChain v1.5.3 documentation + main.templatetags.user_tags — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.tests.html b/docs/_autosummary/main.tests.html index e94885d9..76eb9c5a 100644 --- a/docs/_autosummary/main.tests.html +++ b/docs/_autosummary/main.tests.html @@ -6,7 +6,7 @@ - main.tests — fEMR OnChain v1.5.3 documentation + main.tests — fEMR OnChain v1.6.0 documentation diff --git a/docs/_autosummary/main.views.html b/docs/_autosummary/main.views.html index e15b3928..6ac1b371 100644 --- a/docs/_autosummary/main.views.html +++ b/docs/_autosummary/main.views.html @@ -6,7 +6,7 @@ - main.views — fEMR OnChain v1.5.3 documentation + main.views — fEMR OnChain v1.6.0 documentation diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js index 7a867c35..f0b70290 100644 --- a/docs/_static/documentation_options.js +++ b/docs/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: 'v1.5.3', + VERSION: 'v1.6.0', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/appMR.html b/docs/appMR.html index e0f7406f..e01b5888 100644 --- a/docs/appMR.html +++ b/docs/appMR.html @@ -6,7 +6,7 @@ - app_mr package — fEMR OnChain v1.5.3 documentation + app_mr package — fEMR OnChain v1.6.0 documentation diff --git a/docs/appMR.migrations.html b/docs/appMR.migrations.html index 578fb9ea..95aa26ef 100644 --- a/docs/appMR.migrations.html +++ b/docs/appMR.migrations.html @@ -6,7 +6,7 @@ - app_mr.migrations package — fEMR OnChain v1.5.3 documentation + app_mr.migrations package — fEMR OnChain v1.6.0 documentation diff --git a/docs/appMR.tests.html b/docs/appMR.tests.html index 40325ff8..7127d4b8 100644 --- a/docs/appMR.tests.html +++ b/docs/appMR.tests.html @@ -6,7 +6,7 @@ - app_mr.tests package — fEMR OnChain v1.5.3 documentation + app_mr.tests package — fEMR OnChain v1.6.0 documentation diff --git a/docs/clinic_messages.html b/docs/clinic_messages.html index 39839615..0e2ca216 100644 --- a/docs/clinic_messages.html +++ b/docs/clinic_messages.html @@ -6,7 +6,7 @@ - clinic_messages package — fEMR OnChain v1.5.3 documentation + clinic_messages package — fEMR OnChain v1.6.0 documentation diff --git a/docs/clinic_messages.migrations.html b/docs/clinic_messages.migrations.html index 6532c66b..5c0b155f 100644 --- a/docs/clinic_messages.migrations.html +++ b/docs/clinic_messages.migrations.html @@ -6,7 +6,7 @@ - clinic_messages.migrations package — fEMR OnChain v1.5.3 documentation + clinic_messages.migrations package — fEMR OnChain v1.6.0 documentation diff --git a/docs/genindex.html b/docs/genindex.html index e0e5e4be..2ee86a70 100644 --- a/docs/genindex.html +++ b/docs/genindex.html @@ -5,7 +5,7 @@ - Index — fEMR OnChain v1.5.3 documentation + Index — fEMR OnChain v1.6.0 documentation diff --git a/docs/index.html b/docs/index.html index ec5ef05a..96e928f2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,7 +6,7 @@ - Welcome to fEMR OnChain’s documentation! — fEMR OnChain v1.5.3 documentation + Welcome to fEMR OnChain’s documentation! — fEMR OnChain v1.6.0 documentation diff --git a/docs/main.csvio.html b/docs/main.csvio.html index 4524b8a0..ba6ffacb 100644 --- a/docs/main.csvio.html +++ b/docs/main.csvio.html @@ -6,7 +6,7 @@ - main.csvio package — fEMR OnChain v1.5.3 documentation + main.csvio package — fEMR OnChain v1.6.0 documentation diff --git a/docs/main.html b/docs/main.html index 7944db58..1126eda0 100644 --- a/docs/main.html +++ b/docs/main.html @@ -6,7 +6,7 @@ - main package — fEMR OnChain v1.5.3 documentation + main package — fEMR OnChain v1.6.0 documentation diff --git a/docs/main.management.commands.html b/docs/main.management.commands.html index ef73931a..1a2949a2 100644 --- a/docs/main.management.commands.html +++ b/docs/main.management.commands.html @@ -6,7 +6,7 @@ - main.management.commands package — fEMR OnChain v1.5.3 documentation + main.management.commands package — fEMR OnChain v1.6.0 documentation diff --git a/docs/main.management.html b/docs/main.management.html index cf6c2a9b..8e4fbf1b 100644 --- a/docs/main.management.html +++ b/docs/main.management.html @@ -6,7 +6,7 @@ - main.management package — fEMR OnChain v1.5.3 documentation + main.management package — fEMR OnChain v1.6.0 documentation diff --git a/docs/main.migrations.html b/docs/main.migrations.html index e5d386c6..3d22aa54 100644 --- a/docs/main.migrations.html +++ b/docs/main.migrations.html @@ -6,7 +6,7 @@ - main.migrations package — fEMR OnChain v1.5.3 documentation + main.migrations package — fEMR OnChain v1.6.0 documentation diff --git a/docs/main.templatetags.html b/docs/main.templatetags.html index d0b999cc..8184e4ff 100644 --- a/docs/main.templatetags.html +++ b/docs/main.templatetags.html @@ -6,7 +6,7 @@ - main.templatetags package — fEMR OnChain v1.5.3 documentation + main.templatetags package — fEMR OnChain v1.6.0 documentation diff --git a/docs/modules.html b/docs/modules.html index d72cef8b..83cafead 100644 --- a/docs/modules.html +++ b/docs/modules.html @@ -6,7 +6,7 @@ - clinic_messages — fEMR OnChain v1.5.3 documentation + clinic_messages — fEMR OnChain v1.6.0 documentation diff --git a/docs/py-modindex.html b/docs/py-modindex.html index 8b31273a..2d8fdb73 100644 --- a/docs/py-modindex.html +++ b/docs/py-modindex.html @@ -5,7 +5,7 @@ - Python Module Index — fEMR OnChain v1.5.3 documentation + Python Module Index — fEMR OnChain v1.6.0 documentation diff --git a/docs/search.html b/docs/search.html index de1aa812..0cc49745 100644 --- a/docs/search.html +++ b/docs/search.html @@ -5,7 +5,7 @@ - Search — fEMR OnChain v1.5.3 documentation + Search — fEMR OnChain v1.6.0 documentation diff --git a/env_start.sh b/env_start.sh new file mode 100755 index 00000000..ed573c95 --- /dev/null +++ b/env_start.sh @@ -0,0 +1,8 @@ +#!/bin/bash +curl -sL https://deb.nodesource.com/setup_14.x | bash - +apt update +apt upgrade -y +apt install -y nodejs libz-dev libpq-dev python3 \ + python3-dev python3-pip virtualenv \ + python python-dev libmemcached-dev +apt autoremove -y \ No newline at end of file diff --git a/femr_onchain/celery.py b/femr_onchain/celery.py index 33118f45..7980140e 100644 --- a/femr_onchain/celery.py +++ b/femr_onchain/celery.py @@ -1,9 +1,37 @@ from __future__ import absolute_import, unicode_literals import os from celery import Celery +from celery.schedules import crontab os.environ.setdefault("DJANGO_SETTINGS_MODULE", "femr_onchain.settings") app = Celery("femr_onchain") app.config_from_object("django.conf:settings", namespace="CELERY") app.autodiscover_tasks() + +app.conf.beat_schedule = { + "reset-sessions": { + "task": "main.background_tasks.reset_sessions", + "schedule": crontab(), + }, + "run-user-deactivate": { + "task": "main.background_tasks.run_user_deactivate", + "schedule": crontab(minute=0, hour=0), + }, + "run-encounter-close": { + "task": "main.background_tasks.run_encounter_close", + "schedule": crontab(minute=0, hour=0), + }, + "assign-broken-patients": { + "task": "main.background_tasks.assign_broken_patient", + "schedule": crontab(minute=0, hour=0), + }, + "delete-old-export": { + "task": "main.background_tasks.delete_old_export", + "schedule": crontab(minute=0, hour=0), + }, + "assign_new_timestamp": { + "task": "main.background_tasks.assign_new_timestamp", + "schedule": crontab(), + }, +} diff --git a/femr_onchain/settings.py b/femr_onchain/settings.py index a0d1ae1e..d2f4869a 100644 --- a/femr_onchain/settings.py +++ b/femr_onchain/settings.py @@ -48,7 +48,6 @@ "crispy_forms", "axes", "session_security", - "background_task", "drf_yasg", "django_user_agents", "django_nose", @@ -319,7 +318,7 @@ SILKY_MAX_RESPONSE_BODY_SIZE = 1024 SILKY_META = True SILKY_INTERCEPT_PERCENT = 50 -SILKY_MAX_RECORDED_REQUESTS = 10**4 +SILKY_MAX_RECORDED_REQUESTS = 10**3 SILKY_MAX_RECORDED_REQUESTS_CHECK_PERCENT = 10 CELERY_BROKER_URL = "redis://redis:6379" diff --git a/main/admin_views.py b/main/admin_views.py index b847c394..f25278e2 100644 --- a/main/admin_views.py +++ b/main/admin_views.py @@ -16,7 +16,7 @@ from axes.utils import reset from clinic_messages.models import Message -from main.background_tasks import check_admin_permission +from main.decorators import is_admin, is_authenticated from main.femr_admin_views import get_client_ip from main.forms import ( MOTDForm, @@ -35,6 +35,8 @@ ) +@is_admin +@is_authenticated def admin_home(request): """ The landing page for the authenticated administrative user. @@ -42,88 +44,66 @@ def admin_home(request): :param request: Django Request object. :return: An HttpResponse, rendering the home page. """ - if request.user.is_authenticated: - if check_admin_permission(request.user): - return_response = render( - request, "admin/home.html", {"user": request.user, "page_name": "Admin"} - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + return render( + request, "admin/home.html", {"user": request.user, "page_name": "Admin"} + ) +@is_admin +@is_authenticated def list_users_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - try: - active_users = Campaign.objects.get( - name=request.user.current_campaign - ).femruser_set.filter(is_active=True) - inactive_users = Campaign.objects.get( - name=request.user.current_campaign - ).femruser_set.filter(is_active=False) - except ObjectDoesNotExist: - active_users = [] - inactive_users = [] - return_response = render( - request, - "admin/user_list.html", - { - "user": request.user, - "active_users": active_users, - "inactive_users": inactive_users, - "page_name": "Clinic Users", - }, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + try: + active_users = Campaign.objects.get( + name=request.user.current_campaign + ).femruser_set.filter(is_active=True) + inactive_users = Campaign.objects.get( + name=request.user.current_campaign + ).femruser_set.filter(is_active=False) + except ObjectDoesNotExist: + active_users = [] + inactive_users = [] + return render( + request, + "admin/user_list.html", + { + "user": request.user, + "active_users": active_users, + "inactive_users": inactive_users, + "page_name": "Clinic Users", + }, + ) +@is_admin +@is_authenticated def filter_users_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - try: - data = Campaign.objects.get( - name=request.user.current_campaign - ).femruser_set.filter(is_active=True) - except ObjectDoesNotExist: - data = "" - return_response = render( - request, - "admin/user_list.html", - {"user": request.user, "list_view": data, "page_name": "Clinic Users"}, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + try: + data = Campaign.objects.get( + name=request.user.current_campaign + ).femruser_set.filter(is_active=True) + except ObjectDoesNotExist: + data = "" + return render( + request, + "admin/user_list.html", + {"user": request.user, "list_view": data, "page_name": "Clinic Users"}, + ) +@is_admin +@is_authenticated def search_users_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - try: - data = Campaign.objects.get( - name=request.user.current_campaign - ).femruser_set.filter(is_active=True) - except ObjectDoesNotExist: - data = "" - return_response = render( - request, - "admin/user_list.html", - {"user": request.user, "list_view": data, "page_name": "Clinic Users"}, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + try: + data = Campaign.objects.get( + name=request.user.current_campaign + ).femruser_set.filter(is_active=True) + except ObjectDoesNotExist: + data = "" + return render( + request, + "admin/user_list.html", + {"user": request.user, "list_view": data, "page_name": "Clinic Users"}, + ) def __create_user_view_get(request): @@ -155,6 +135,15 @@ def __create_user_view_post(request): Permission.objects.get(name="Can add chief complaint") ) item.user_permissions.add(Permission.objects.get(name="Can add medication")) + + item.user_permissions.add( + Permission.objects.get(name="Can add administration schedule") + ) + item.user_permissions.add( + Permission.objects.get(name="Can add inventory category") + ) + item.user_permissions.add(Permission.objects.get(name="Can add inventory form")) + item.user_permissions.add(Permission.objects.get(name="Can add manufacturer")) item.save() DatabaseChangeLog.objects.create( action="Create", @@ -174,168 +163,145 @@ def __create_user_view_post(request): return return_response +@is_admin +@is_authenticated def create_user_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - if request.method == "GET": - return_response = __create_user_view_get(request) - if request.method == "POST": - return_response = __create_user_view_post(request) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") + if request.method == "GET": + return_response = __create_user_view_get(request) + if request.method == "POST": + return_response = __create_user_view_post(request) return return_response +@is_authenticated def update_user_view(request, user_id=None): - if request.user.is_authenticated: - error = "" - user = get_object_or_404(fEMRUser, pk=user_id) - if request.method == "POST": - form = ( - fEMRAdminUserUpdateForm(request.POST or None, instance=user) - if request.user.groups.filter(name="fEMR Admin").exists() - else UserUpdateForm(request.user, request.POST or None, instance=user) + error = "" + user = get_object_or_404(fEMRUser, pk=user_id) + if request.method == "POST": + form = ( + fEMRAdminUserUpdateForm(request.POST or None, instance=user) + if request.user.groups.filter(name="fEMR Admin").exists() + else UserUpdateForm(request.user, request.POST or None, instance=user) + ) + if form.is_valid(): + item = form.save() + item.save() + DatabaseChangeLog.objects.create( + action="Edit", + model="User", + instance=str(item), + ip=get_client_ip(request), + username=request.user.username, + campaign=Campaign.objects.get(name=request.user.current_campaign), ) - if form.is_valid(): - item = form.save() - item.save() - DatabaseChangeLog.objects.create( - action="Edit", - model="User", - instance=str(item), - ip=get_client_ip(request), - username=request.user.username, - campaign=Campaign.objects.get(name=request.user.current_campaign), - ) - return_response = render(request, "admin/user_edit_confirmed.html") - else: - return_response = render( - request, - "admin/user_edit_form.html", - { - "error": "Form is invalid.", - "form": form, - "user_id": user_id, - "page_name": "Editing User", - }, - ) + return_response = render(request, "admin/user_edit_confirmed.html") else: - form = ( - fEMRAdminUserUpdateForm(instance=user) - if request.user.groups.filter(name="fEMR Admin").exists() - else UserUpdateForm(request.user, instance=user) - ) return_response = render( request, "admin/user_edit_form.html", { - "error": error, + "error": "Form is invalid.", "form": form, "user_id": user_id, "page_name": "Editing User", }, ) else: - return_response = redirect("/not_logged_in") - return return_response - - -def update_user_password_view(request, user_id=None): - if request.user.is_authenticated: - error = "" - user = get_object_or_404(fEMRUser, pk=user_id) - if request.method == "POST": - form = AdminPasswordForm(request.POST or None, instance=user) - if form.is_valid(): - item = form.save() - item.save() - user.change_password = True - user.save() - DatabaseChangeLog.objects.create( - action="Change Password", - model="User", - instance=str(item), - ip=get_client_ip(request), - username=request.user.username, - campaign=Campaign.objects.get(name=request.user.current_campaign), - ) - return_response = render(request, "admin/user_edit_confirmed.html") - else: - error = "Form is invalid." - else: - form = AdminPasswordForm(instance=user) + form = ( + fEMRAdminUserUpdateForm(instance=user) + if request.user.groups.filter(name="fEMR Admin").exists() + else UserUpdateForm(request.user, instance=user) + ) return_response = render( request, - "admin/user_password_edit_form.html", + "admin/user_edit_form.html", { "error": error, "form": form, "user_id": user_id, - "page_name": "Editing User Password", + "page_name": "Editing User", }, ) - else: - return_response = redirect("/not_logged_in") return return_response -def lock_user_view(request, user_id=None): - if request.user.is_authenticated: - if check_admin_permission(request.user): - user = get_object_or_404(fEMRUser, pk=user_id) - user.is_active = False +@is_authenticated +def update_user_password_view(request, user_id=None): + error = "" + user = get_object_or_404(fEMRUser, pk=user_id) + if request.method == "POST": + form = AdminPasswordForm(request.POST or None, instance=user) + if form.is_valid(): + item = form.save() + item.save() + user.change_password = True user.save() - return_response = redirect("main:list_users_view") + DatabaseChangeLog.objects.create( + action="Change Password", + model="User", + instance=str(item), + ip=get_client_ip(request), + username=request.user.username, + campaign=Campaign.objects.get(name=request.user.current_campaign), + ) + return_response = render(request, "admin/user_edit_confirmed.html") else: - return_response = redirect("main:permission_denied") + error = "Form is invalid." else: - return_response = redirect("main:not_logged_in") + form = AdminPasswordForm(instance=user) + return_response = render( + request, + "admin/user_password_edit_form.html", + { + "error": error, + "form": form, + "user_id": user_id, + "page_name": "Editing User Password", + }, + ) return return_response +@is_admin +@is_authenticated +def lock_user_view(request, user_id=None): + user = get_object_or_404(fEMRUser, pk=user_id) + user.is_active = False + user.save() + return redirect("main:list_users_view") + + +@is_admin +@is_authenticated def unlock_user_view(request, user_id=None): - if request.user.is_authenticated: - if check_admin_permission(request.user): - user = get_object_or_404(fEMRUser, pk=user_id) - reset(username=user.username) - user.is_active = True - user.last_login = timezone.now() - user.save() - return_response = redirect("main:list_users_view") - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + user = get_object_or_404(fEMRUser, pk=user_id) + reset(username=user.username) + user.is_active = True + user.last_login = timezone.now() + user.save() + return redirect("main:list_users_view") +@is_admin +@is_authenticated def get_audit_logs_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - try: - data = AuditEntry.objects.filter( - Q(campaign=Campaign.objects.get(name=request.user.current_campaign)) - | Q(action="user_login_failed") - ).order_by("-timestamp") - except ObjectDoesNotExist: - data = "" - return_response = render( - request, - "admin/audit_log_list.html", - { - "user": request.user, - "selected": 6, - "log": data, - "page_name": "Login Log", - }, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + try: + data = AuditEntry.objects.filter( + Q(campaign=Campaign.objects.get(name=request.user.current_campaign)) + | Q(action="user_login_failed") + ).order_by("-timestamp") + except ObjectDoesNotExist: + data = "" + return render( + request, + "admin/audit_log_list.html", + { + "user": request.user, + "selected": 6, + "log": data, + "page_name": "Login Log", + }, + ) def __filter_audit_logs_process(request): @@ -488,97 +454,70 @@ def __filter_audit_logs_process(request): ) +@is_admin +@is_authenticated def filter_audit_logs_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - return_response = __filter_audit_logs_process(request) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + return __filter_audit_logs_process(request) +@is_admin +@is_authenticated def search_audit_logs_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - try: - data = AuditEntry.objects.filter( - Q(campaign=Campaign.objects.get(name=request.user.current_campaign)) - | Q(action="user_login_failed") - ) - except ObjectDoesNotExist: - data = "" - return_response = render( - request, - "admin/audit_log_list.html", - {"user": request.user, "list_view": data, "page_name": "Login Log"}, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + try: + data = AuditEntry.objects.filter( + Q(campaign=Campaign.objects.get(name=request.user.current_campaign)) + | Q(action="user_login_failed") + ) + except ObjectDoesNotExist: + data = "" + return render( + request, + "admin/audit_log_list.html", + {"user": request.user, "list_view": data, "page_name": "Login Log"}, + ) +@is_admin +@is_authenticated def export_audit_logs_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - return_response = render( - request, - "export/audit_logfile.html", - { - "log": AuditEntry.objects.filter( - campaign=Campaign.objects.get( - name=request.user.current_campaign - ) - ) - }, + return render( + request, + "export/audit_logfile.html", + { + "log": AuditEntry.objects.filter( + campaign=Campaign.objects.get(name=request.user.current_campaign) ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + }, + ) +@is_authenticated def get_database_logs_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - try: - excludemodels = ["Campaign", "Instance"] - data = ( - DatabaseChangeLog.objects.exclude(model__in=excludemodels) - .filter( - campaign=Campaign.objects.get( - name=request.user.current_campaign - ) - ) - .order_by("-timestamp") - ) - except ObjectDoesNotExist: - data = [] - return_response = render( - request, - "admin/database_log_list.html", - { - "user": request.user, - "selected": 6, - "list_view": data, - "page_name": "Patient Change Log", - }, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + try: + excludemodels = ["Campaign", "Instance"] + data = ( + DatabaseChangeLog.objects.exclude(model__in=excludemodels) + .filter(campaign=Campaign.objects.get(name=request.user.current_campaign)) + .order_by("-timestamp") + ) + except ObjectDoesNotExist: + data = [] + return render( + request, + "admin/database_log_list.html", + { + "user": request.user, + "selected": 6, + "list_view": data, + "page_name": "Patient Change Log", + }, + ) def __filter_database_logs_check(request): excludemodels = ["Campaign", "Instance"] try: - logs = DatabaseChangeLog.objects.all() + logs = DatabaseChangeLog.objects.all().iterator() if request.GET["filter_list"] == "1": now = timezone.make_aware(datetime.today(), timezone.get_default_timezone()) now = now.astimezone(timezone.get_current_timezone()) @@ -757,130 +696,93 @@ def __filter_database_logs_check(request): ) +@is_admin +@is_authenticated def filter_database_logs_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - return_response = __filter_database_logs_check(request) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + return __filter_database_logs_check(request) +@is_authenticated def search_database_logs_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - try: - excludemodels = ["Campaign", "Instance"] - data = DatabaseChangeLog.objects.exclude( - model__in=excludemodels - ).filter( - campaign=Campaign.objects.get(name=request.user.current_campaign) - ) - except ObjectDoesNotExist: - data = "" - return_response = render( - request, - "admin/database_log_list.html", - { - "user": request.user, - "list_view": data, - "page_name": "Patient Change Log", - }, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + try: + excludemodels = ["Campaign", "Instance"] + data = DatabaseChangeLog.objects.exclude(model__in=excludemodels).filter( + campaign=Campaign.objects.get(name=request.user.current_campaign) + ) + except ObjectDoesNotExist: + data = "" + return render( + request, + "admin/database_log_list.html", + { + "user": request.user, + "list_view": data, + "page_name": "Patient Change Log", + }, + ) +@is_authenticated def export_database_logs_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - excludemodels = ["Campaign", "Instance"] - return_response = render( - request, - "export/data_logfile.html", - { - "log": DatabaseChangeLog.objects.exclude( - model__in=excludemodels - ).filter( - campaign=Campaign.objects.get( - name=request.user.current_campaign - ) - ) - }, + excludemodels = ["Campaign", "Instance"] + return render( + request, + "export/data_logfile.html", + { + "log": DatabaseChangeLog.objects.exclude(model__in=excludemodels).filter( + campaign=Campaign.objects.get(name=request.user.current_campaign) ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + }, + ) +@is_admin +@is_authenticated def add_users_to_campaign(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - return_response = render( - request, - "admin/add_users_to_campaign.html", - {"users": __retrieve_needed_users(request)}, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + return render( + request, + "admin/add_users_to_campaign.html", + {"users": __retrieve_needed_users(request)}, + ) +@is_admin +@is_authenticated def add_user_to_campaign(request, user_id=None): - if request.user.is_authenticated: - if check_admin_permission(request.user): - user = fEMRUser.objects.get(pk=user_id) - user.campaigns.add(Campaign.objects.get(name=request.user.current_campaign)) - user.save() - return_response = render( - request, - "admin/add_users_to_campaign.html", - {"users": __retrieve_needed_users(request)}, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + user = fEMRUser.objects.get(pk=user_id) + user.campaigns.add(Campaign.objects.get(name=request.user.current_campaign)) + user.save() + return render( + request, + "admin/add_users_to_campaign.html", + {"users": __retrieve_needed_users(request)}, + ) +@is_admin +@is_authenticated def cut_user_from_campaign(request, user_id=None): - if request.user.is_authenticated: - if check_admin_permission(request.user): - user = fEMRUser.objects.get(pk=user_id) - user.campaigns.remove( - Campaign.objects.get(name=request.user.current_campaign) - ) - user.save() - return_response = render( - request, - "admin/add_users_to_campaign.html", - {"users": __retrieve_needed_users(request)}, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + user = fEMRUser.objects.get(pk=user_id) + user.campaigns.remove(Campaign.objects.get(name=request.user.current_campaign)) + user.save() + return render( + request, + "admin/add_users_to_campaign.html", + {"users": __retrieve_needed_users(request)}, + ) def __retrieve_needed_users(request): - user_set = fEMRUser.objects.all() - users_created_by_me = user_set.filter(created_by=request.user) - users_in_my_campaigns = user_set.filter( - campaigns__in=request.user.campaigns.all() - ).filter(is_active=True) - users = set(list(itertools.chain(users_created_by_me, users_in_my_campaigns))) - return users + user_set = fEMRUser.objects.filter( + ( + ( + Q(created_by=request.user) + | Q(campaigns__in=request.user.campaigns.all().iterator()) + ) + & Q(is_active=True) + ) + ).exclude(campaigns__name=request.user.current_campaign) + return user_set def __message_of_the_day_form_processor(request, message): @@ -914,23 +816,19 @@ def __message_of_the_day_form_processor(request, message): return return_response +@is_admin +@is_authenticated def message_of_the_day_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - message = MessageOfTheDay.load() - form = MOTDForm() - if request.method == "GET": - form = MOTDForm(instance=message) - form.initial["text"] = message.text - form.initial["start_date"] = message.start_date - form.initial["end_date"] = message.end_date - return_response = render( - request, "admin/motd.html", {"form": form, "page_name": "MotD"} - ) - elif request.method == "POST": - return_response = __message_of_the_day_form_processor(request, message) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") + message = MessageOfTheDay.load() + form = MOTDForm() + if request.method == "GET": + form = MOTDForm(instance=message) + form.initial["text"] = message.text + form.initial["start_date"] = message.start_date + form.initial["end_date"] = message.end_date + return_response = render( + request, "admin/motd.html", {"form": form, "page_name": "MotD"} + ) + elif request.method == "POST": + return_response = __message_of_the_day_form_processor(request, message) return return_response diff --git a/main/auth_views.py b/main/auth_views.py index 43dd4727..56f5d7ab 100644 --- a/main/auth_views.py +++ b/main/auth_views.py @@ -17,11 +17,9 @@ from silk.profiling.profiler import silk_profile from main.background_tasks import ( - check_admin_permission, - reset_sessions, check_browser, - run_user_deactivate, ) +from main.decorators import is_admin, is_authenticated from main.femr_admin_views import get_client_ip from main.forms import RegisterForm, LoginForm from main.models import AuditEntry, UserSession, fEMRUser @@ -30,6 +28,7 @@ @silk_profile("register_post") def __register_post(request): form = RegisterForm(request.POST) + error = "" if form.is_valid(): try: try: @@ -45,19 +44,24 @@ def __register_post(request): user.first_name = form.cleaned_data["first"] user.last_name = form.cleaned_data["last"] login(request, user) - return_response = redirect("/thanks") except IntegrityError: error = "An account already exists using that username." except MultipleObjectsReturned: error = "An account already exists with that email address." except DataError as data_error: error = str(data_error) - return_response = render( - request, "auth/register.html", {"form": RegisterForm(), "error": error} - ) + finally: + if error != "": + return_response = render( + request, + "auth/register.html", + {"form": RegisterForm(), "error": error}, + ) + else: + return_response = redirect("/thanks") else: return_response = render( - request, "auth/register.html", {"form": RegisterForm(), "error": ""} + request, "auth/register.html", {"form": form, "error": error} ) return return_response @@ -154,8 +158,8 @@ def __login_view_post_success(request, user): }, ) elif len(user.campaigns.all()) == 1 and not user.campaigns.all()[0].active: - is_admin = request.user.groups.filter(name="fEMR Admin").exists() - if not is_admin: + user_is_admin = request.user.groups.filter(name="fEMR Admin").exists() + if not user_is_admin: return_response = redirect("main:all_locked") else: request.user.current_campaign = "RECOVERY MODE" @@ -233,8 +237,6 @@ def login_view(request): :param request: Django Request object. :return: HTTPResponse. """ - reset_sessions() - run_user_deactivate() if not check_browser(request): return_response = render(request, "data/stop.html") elif request.user.is_authenticated: @@ -269,6 +271,7 @@ def logout_view(request): return return_response +@is_authenticated @silk_profile("change-password") def change_password(request): """ @@ -277,31 +280,39 @@ def change_password(request): :param request: Django request object. Provided by the URLS config. :return: Renders the change_password page as an HTTPResponse. """ - error = "" - if request.user.is_authenticated: - if request.method == "POST": - form = PasswordChangeForm(request.user, request.POST) - if form.is_valid(): - user = form.save() - update_session_auth_hash(request, user) - user.change_password = False - user.password_reset_last = timezone.now() - user.save() - return_response = redirect("main:index") - else: - error = "Something went wrong." + if request.method == "POST": + form = PasswordChangeForm(request.user, request.POST) + if form.is_valid(): + user = form.save() + update_session_auth_hash(request, user) + user.change_password = False + user.password_reset_last = timezone.now() + user.save() + return_response = redirect("main:index") else: - form = PasswordChangeForm(request.user) + return_response = render( + request, + "auth/change_password.html", + { + "user": request.user, + "form": form, + "error_message": "Something went wrong.", + }, + ) + else: return_response = render( request, "auth/change_password.html", - {"user": request.user, "form": form, "error_message": error}, + { + "user": request.user, + "form": PasswordChangeForm(request.user), + "error_message": "", + }, ) - else: - return_response = redirect("main:not_logged_in") return return_response +@is_authenticated @silk_profile("required-change-password") def required_change_password(request): """ @@ -310,51 +321,44 @@ def required_change_password(request): :param request: Django request object. Provided by the URLS config. :return: Renders the change_password page as an HTTPResponse. """ - if request.user.is_authenticated: - if request.method == "POST": - form = PasswordChangeForm(request.user, request.POST) - if form.is_valid(): - user = form.save() - update_session_auth_hash(request, user) - user.change_password = False - user.password_reset_last = timezone.now() - user.save() - return_response = redirect("main:home") - else: - return_response = render( - request, - "auth/required_change_password.html", - { - "user": request.user, - "form": form, - "error_message": "Something went wrong.", - }, - ) + if request.method == "POST": + form = PasswordChangeForm(request.user, request.POST) + if form.is_valid(): + user = form.save() + update_session_auth_hash(request, user) + user.change_password = False + user.password_reset_last = timezone.now() + user.save() + return_response = redirect("main:home") else: - form = PasswordChangeForm(request.user) return_response = render( request, "auth/required_change_password.html", - {"user": request.user, "form": form, "error_message": ""}, + { + "user": request.user, + "form": form, + "error_message": "Something went wrong.", + }, ) else: - return_response = redirect("main:not_logged_in") + form = PasswordChangeForm(request.user) + return_response = render( + request, + "auth/required_change_password.html", + {"user": request.user, "form": form, "error_message": ""}, + ) return return_response +@is_authenticated +@is_admin @silk_profile("reset-lockouts") def reset_lockouts(request, username=None): - if request.user.is_authenticated: - if check_admin_permission(request.user): - if username is not None: - reset(username=username) - return_response = render( - request, "admin/user_reset_success.html", {"username": username} - ) - else: - return_response = render(request, "admin/user_reset_no_success.html") - else: - return_response = redirect("main:permission_denied") + if username is not None: + reset(username=username) + return_response = render( + request, "admin/user_reset_success.html", {"username": username} + ) else: - return_response = redirect("main:not_logged_in") + return_response = render(request, "admin/user_reset_no_success.html") return return_response diff --git a/main/axes_callable.py b/main/axes_callable.py index 50237e52..cb98ef29 100644 --- a/main/axes_callable.py +++ b/main/axes_callable.py @@ -10,7 +10,7 @@ def user_locked_out_callback(request, credentials): if user.exists() and len(user) == 1: user = user[0] campaigns = user.campaigns.all() - campaign_manager = campaigns[0].main_contact if len(campaigns) != 0 else None + campaign_manager = campaigns[0].main_contact if campaigns.count() != 0 else None if campaign_manager: message = Message.objects.create( subject="User Locked Out", diff --git a/main/background_tasks.py b/main/background_tasks.py index d965b6cb..fca9e2b7 100644 --- a/main/background_tasks.py +++ b/main/background_tasks.py @@ -2,7 +2,8 @@ Non-view functions used to carry out background processes. """ import os -from datetime import timedelta +from datetime import timedelta, datetime +from celery import shared_task from django.db.models.query_utils import Q from django.utils import timezone @@ -11,6 +12,7 @@ from silk.profiling.profiler import silk_profile from main.models import ( + CSVExport, Campaign, Patient, PatientEncounter, @@ -20,26 +22,29 @@ ) +@shared_task @silk_profile("run-encounter-close") -def run_encounter_close(campaign: Campaign): +def run_encounter_close(): """ When triggered, this function will search for expired PatientEncounter objects and set them as inactive. """ now = timezone.now() - close_time = campaign.encounter_close - delta = now - timedelta(days=close_time) + for campaign in Campaign.objects.all().iterator(): + close_time = campaign.encounter_close + delta = now - timedelta(days=close_time) - patients = Patient.objects.filter(campaign=campaign) - encounters = PatientEncounter.objects.filter( - Q(patient__in=patients) & Q(active=True) & Q(timestamp__lt=delta) - ) - for encounter in encounters: - encounter.active = False - encounter.save_no_timestamp() + patients = Patient.objects.filter(campaign=campaign) + encounters = PatientEncounter.objects.filter( + Q(patient__in=patients) & Q(active=True) & Q(timestamp__lt=delta) + ) + for encounter in encounters: + encounter.active = False + encounter.save_no_timestamp() +@shared_task @silk_profile("run-user-deactivate") def run_user_deactivate(now=timezone.now()): """ @@ -65,6 +70,7 @@ def run_user_deactivate(now=timezone.now()): user.save() +@shared_task @silk_profile("reset-sessions") def reset_sessions() -> None: """ @@ -73,9 +79,8 @@ def reset_sessions() -> None: """ now = timezone.now() delta = now - timedelta(minutes=1) - for session in UserSession.objects.all(): - if session.timestamp < delta: - session.delete() + for session in UserSession.objects.filter(timestamp__lt=delta): + session.delete() @silk_profile("check-browser") @@ -104,14 +109,17 @@ def check_admin_permission(user): :param user: A user to check for permissions. :return: Whether the user is in an administrative group. """ - return user.groups.filter( - Q(name="fEMR Admin") - | Q(name="Campaign Manager") - | Q(name="Organization Admin") - | Q(name="Operation Admin") - ).exists() + admin_groups = { + "fEMR Admin", + "Campaign Manager", + "Organization Admin", + "Operation Admin", + } + user_groups = {group.name for group in user.groups.all().iterator()} + return admin_groups.intersection(user_groups) +@shared_task @silk_profile("assign-broken-patients") def assign_broken_patient(): """ @@ -123,3 +131,26 @@ def assign_broken_patient(): for patient in Patient.objects.filter(campaign_key=None): patient.campaign_key = cal_key(patient.id) patient.save() + + +@shared_task +@silk_profile("delete-old-export") +def delete_old_export(): + now = timezone.now() + delta = now - timedelta(weeks=2) + for export in CSVExport.objects.filter(timestamp__lt=delta).iterator(): + export.delete() + + +@shared_task +@silk_profile("assign_new_timestamp") +def assign_new_timestamp(): + now = timezone.make_aware(datetime.today(), timezone.get_default_timezone()) + now = now.astimezone(timezone.get_current_timezone()) + for patient in Patient.objects.filter( + (Q(patientencounter__timestamp__date=now) | Q(timestamp__date=now)) + ).order_by("-timestamp"): + patient.timestamp = ( + patient.patientencounter_set.all().order_by("-timestamp")[0].timestamp + ) + patient.save() diff --git a/main/csvio/patient_csv_export.py b/main/csvio/patient_csv_export.py index 1b0681d3..574827bd 100644 --- a/main/csvio/patient_csv_export.py +++ b/main/csvio/patient_csv_export.py @@ -3,10 +3,10 @@ patient records as CSV files. """ import csv -from datetime import datetime from io import StringIO import math import os +from datetime import datetime, timedelta from celery import shared_task from silk.profiling.profiler import silk_profile @@ -16,6 +16,11 @@ from django.contrib import messages from django.shortcuts import redirect, render from django.core.files.base import ContentFile +from django.core.paginator import Paginator +from django.core.mail import send_mail +from django.db.models import Q +from django.utils import timezone + from clinic_messages.models import Message from main.background_tasks import check_admin_permission @@ -76,14 +81,14 @@ def dict_builder(patient_data, vitals_dict, treatments_dict, hpis_dict): max_treatments = 0 max_hpis = 0 max_vitals = 0 - for _, encounters in patient_data.items(): - for encounter in encounters: - vitals = list(encounter.vitals_set.all()) - vitals_count = len(vitals) - treatments = list(encounter.treatment_set.all()) - treatments_count = len(treatments) - hpis = list(encounter.historyofpresentillness_set.all()) - hpis_count = len(hpis) + for patient in patient_data: + for encounter in patient.patientencounter_set.all(): + vitals = encounter.vitals_set.all() + vitals_count = vitals.count() + treatments = encounter.treatment_set.all() + treatments_count = treatments.count() + hpis = encounter.historyofpresentillness_set.all() + hpis_count = hpis.count() vitals_dict[encounter] = (vitals, vitals_count) treatments_dict[encounter] = (treatments, treatments_count) @@ -219,8 +224,6 @@ def write_result_file(writer, title_row, patient_rows): def patient_processing_loop( patient_data, patient_rows, - campaign_time_zone, - campaign_time_zone_b, campaign, vitals_dict, max_vitals, @@ -229,9 +232,11 @@ def patient_processing_loop( hpis_dict, max_hpis, ): + campaign_time_zone = pytz_timezone(campaign.timezone) + campaign_time_zone_b = datetime.now(tz=campaign_time_zone).strftime("%Z%z") export_id = 1 - for patient, encounters in patient_data.items(): - for encounter in encounters: + for patient in patient_data: + for encounter in patient.patientencounter_set.all(): row = [ export_id, patient.sex_assigned_at_birth, @@ -268,11 +273,50 @@ def patient_processing_loop( extend_hpis_list(row, hpis_dict[encounter], max_hpis) patient_rows.append(row) export_id += 1 + return len(patient_rows) + + +@silk_profile("--filter-patients-by-week") +def __filter_patients_by_week(campaign): + timestamp_from = timezone.now() - timedelta(days=7) + timestamp_to = timezone.now() + return Patient.objects.filter( + Q(campaign=campaign) + & ( + Q( + patientencounter__timestamp__gte=timestamp_from, + patientencounter__timestamp__lt=timestamp_to, + ) + | Q( + timestamp__gte=timestamp_from, + timestamp__lt=timestamp_to, + ) + ) + ).distinct() + + +@silk_profile("--filter-patients-by-month") +def __filter_patients_by_month(campaign): + timestamp_from = timezone.now() - timedelta(days=30) + timestamp_to = timezone.now() + return Patient.objects.filter( + Q(campaign=campaign) + & ( + Q( + patientencounter__timestamp__gte=timestamp_from, + patientencounter__timestamp__lt=timestamp_to, + ) + | Q( + timestamp__gte=timestamp_from, + timestamp__lt=timestamp_to, + ) + ) + ).distinct() @shared_task @silk_profile("csv-export-handler") -def csv_export_handler(user_id, campaign_id): +def csv_export_handler(user_id, campaign_id, timeframe): campaign = Campaign.objects.get(pk=campaign_id) export_file = StringIO() writer = csv.writer(export_file) @@ -298,12 +342,12 @@ def csv_export_handler(user_id, campaign_id): "Current Medications", "Family History", ] - patient_data = { - patient: list(patient.patientencounter_set.all()) - for patient in Patient.objects.filter(campaign=campaign) - } - campaign_time_zone = pytz_timezone(campaign.timezone) - campaign_time_zone_b = datetime.now(tz=campaign_time_zone).strftime("%Z%z") + if timeframe == 2: + patient_data = __filter_patients_by_week(campaign) + elif timeframe == 3: + patient_data = __filter_patients_by_month(campaign) + else: + patient_data = Patient.objects.filter(campaign=campaign) patient_rows = [] vitals_dict = {} treatments_dict = {} @@ -314,8 +358,6 @@ def csv_export_handler(user_id, campaign_id): patient_processing_loop( patient_data, patient_rows, - campaign_time_zone, - campaign_time_zone_b, campaign, vitals_dict, max_vitals, @@ -328,11 +370,14 @@ def csv_export_handler(user_id, campaign_id): write_result_file(writer, title_row, patient_rows) user = fEMRUser.objects.get(pk=user_id) export = CSVExport() - csv_file = ContentFile(export_file.getvalue().encode("utf-8")) - export.file.save(f"patient-export-{campaign.name}-{datetime.now()}.csv", csv_file) + export.file.save( + f"patient-export-{campaign.name}-{datetime.now()}.csv", + ContentFile(export_file.getvalue().encode("utf-8")), + ) export.user = user + export.campaign = campaign export.save() - Message.objects.create( + message = Message.objects.create( subject="CSV Export Finished", content=""" This message is to let you know that the CSV export you began has finished. You can go back to the View Finished Exports page to download it. @@ -340,6 +385,14 @@ def csv_export_handler(user_id, campaign_id): sender=fEMRUser.objects.get(username="admin"), recipient=user, ) + if os.environ.get("DEFAULT_FROM_EMAIL", None) is not None: + send_mail( + f"Message from {message.sender}", + # pylint: disable=C0301 + f"{message.content}\n\n\nTHIS IS AN AUTOMATED MESSAGE. PLEASE DO NOT REPLY TO THIS EMAIL. PLEASE LOG IN TO REPLY.", + os.environ.get("DEFAULT_FROM_EMAIL"), + [message.recipient.email], + ) @silk_profile("csv-export-list") @@ -347,8 +400,11 @@ def csv_export_list(request): if request.user.is_authenticated: if check_admin_permission(request.user): exports = CSVExport.objects.filter(user=request.user).order_by("-id") + paginator = Paginator(exports, 10) + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) return_response = render( - request, "admin/export_list.html", {"exports": exports} + request, "admin/export_list.html", {"exports": page_obj} ) else: return_response = redirect("main:permission_denied") @@ -376,14 +432,14 @@ def fetch_csv_export(request, export_id=None): @silk_profile("run-patient-csv-export") -def run_patient_csv_export(request): +def run_patient_csv_export(request, timeframe=1): if request.user.is_authenticated: if check_admin_permission(request.user): campaign = Campaign.objects.get(name=request.user.current_campaign) - csv_export_handler.delay(request.user.pk, campaign.id) + csv_export_handler.delay(request.user.pk, campaign.id, timeframe) messages.info( request, - "We're building your CSV - you'll receive a message once it's done.", + "We're building your CSV - you'll receive a message once it's done. This may take up to 10 minutes.", ) return_response = render( request, "admin/home.html", {"user": request.user, "page_name": "Admin"} diff --git a/main/dashboard_views.py b/main/dashboard_views.py new file mode 100644 index 00000000..83a182db --- /dev/null +++ b/main/dashboard_views.py @@ -0,0 +1,37 @@ +from django.shortcuts import render +from main.decorators import is_authenticated, is_femr_admin + +from main.models import AuditEntry, Campaign + + +def user_agent_list(): + browsers = {} + logs = AuditEntry.objects.all() + for log in logs: + if log.browser_user_agent is not None: + if log.browser_user_agent in browsers: + browsers[log.browser_user_agent] += 1 + else: + browsers[log.browser_user_agent] = 1 + if log.system_user_agent is not None: + if log.system_user_agent in browsers: + browsers[log.system_user_agent] += 1 + else: + browsers[log.system_user_agent] = 1 + return browsers + + +@is_femr_admin +@is_authenticated +def femr_admin_dashboard_view(request): + campaigns = Campaign.objects.filter(active=True).order_by("id") + browsers = user_agent_list() + return render( + request, + "dashboard/femr_admin.html", + { + "campaigns": campaigns, + "browsers": browsers, + "page_name": "Metrics", + }, + ) diff --git a/main/decorators.py b/main/decorators.py new file mode 100644 index 00000000..83072be1 --- /dev/null +++ b/main/decorators.py @@ -0,0 +1,72 @@ +from django.shortcuts import redirect +from django.db.models.query_utils import Q + +from main.background_tasks import check_admin_permission + + +def is_authenticated(view_func): + def wrap(request, *args, **kwargs): + if request.user.is_authenticated: + return_response = view_func(request, *args, **kwargs) + else: + return_response = redirect("main:not_logged_in") + return return_response + + return wrap + + +def is_femr_admin(view_func): + def wrap(request, *args, **kwargs): + if request.user.groups.filter(name="fEMR Admin").exists(): + return_response = view_func(request, *args, **kwargs) + else: + return_response = redirect("main:permission_denied") + return return_response + + return wrap + + +def is_org_admin(view_func): + def wrap(request, *args, **kwargs): + if request.user.groups.filter(name="Organization Admin").exists(): + return_response = view_func(request, *args, **kwargs) + else: + return_response = redirect("main:permission_denied") + return return_response + + return wrap + + +def is_op_admin(view_func): + def wrap(request, *args, **kwargs): + if request.user.groups.filter( + Q(name="Operation Admin") | Q(name="Organization Admin") + ).exists(): + return_response = view_func(request, *args, **kwargs) + else: + return_response = redirect("main:permission_denied") + return return_response + + return wrap + + +def is_admin(view_func): + def wrap(request, *args, **kwargs): + if check_admin_permission(request.user): + return_response = view_func(request, *args, **kwargs) + else: + return_response = redirect("main:permission_denied") + return return_response + + return wrap + + +def in_recovery_mode(view_func): + def wrap(request, *args, **kwargs): + if request.user.current_campaign == "RECOVERY MODE": + return_response = redirect("main:home") + else: + return_response = view_func(request, *args, **kwargs) + return return_response + + return wrap diff --git a/main/delete_views.py b/main/delete_views.py index f9716682..176efecd 100644 --- a/main/delete_views.py +++ b/main/delete_views.py @@ -14,6 +14,7 @@ from silk.profiling.profiler import silk_profile from clinic_messages.models import Message +from main.decorators import is_authenticated from main.femr_admin_views import get_client_ip from main.forms import PhotoForm, VitalsForm from .models import ( @@ -28,6 +29,7 @@ ) +@is_authenticated def patient_delete_view(request, patient_id=None): """ Delete function. @@ -36,88 +38,81 @@ def patient_delete_view(request, patient_id=None): :param id: The ID of the patient to delete. :return: HTTPResponse. """ - if request.user.is_authenticated: - if request.method == "POST": - try: - target_object = get_object_or_404(Patient, pk=patient_id) - this_campaign = Campaign.objects.get(name=request.user.current_campaign) - contact = this_campaign.instance.main_contact - DatabaseChangeLog.objects.create( - action="Delete", - model="Patient", - instance=str(target_object), - ip=get_client_ip(request), - username=request.user.username, - campaign=this_campaign, - ) - message_content = ( - f"{request.user} has deleted a patient record for the fEMR On-Chain " - f"deployment to {this_campaign} from fEMR On-Chain on {timezone.now()}. " - "To view audit logs, visit the Admin tab in fEMR On-Chain." - ) - Message.objects.create( - sender=request.user, - recipient=contact, - subject="WARNING! PATIENT DELETED", - content=message_content, - ) - send_mail( - "WARNING! PATIENT DELETED", - f"{message_content}\n\n\nTHIS IS AN AUTOMATED MESSAGE FROM fEMR ON-CHAIN. " - "PLEASE DO NOT REPLY TO THIS EMAIL. " - "PLEASE LOG IN TO fEMR ON-CHAIN TO REPLY.", - os.environ.get("DEFAULT_FROM_EMAIL"), - [contact.email], - ) - target_object.delete() - except ObjectDoesNotExist: - pass - return_response = render(request, "data/patient_deleted_success.html") - else: + if request.method == "POST": + try: target_object = get_object_or_404(Patient, pk=patient_id) - return_response = render( - request, "data/delete.html", {"patient": target_object} + this_campaign = Campaign.objects.get(name=request.user.current_campaign) + contact = this_campaign.instance.main_contact + DatabaseChangeLog.objects.create( + action="Delete", + model="Patient", + instance=str(target_object), + ip=get_client_ip(request), + username=request.user.username, + campaign=this_campaign, + ) + message_content = ( + f"{request.user} has deleted a patient record for the fEMR On-Chain " + f"deployment to {this_campaign} from fEMR On-Chain on {timezone.now()}. " + "To view audit logs, visit the Admin tab in fEMR On-Chain." + ) + Message.objects.create( + sender=request.user, + recipient=contact, + subject="WARNING! PATIENT DELETED", + content=message_content, ) + send_mail( + "WARNING! PATIENT DELETED", + f"{message_content}\n\n\nTHIS IS AN AUTOMATED MESSAGE FROM fEMR ON-CHAIN. " + "PLEASE DO NOT REPLY TO THIS EMAIL. " + "PLEASE LOG IN TO fEMR ON-CHAIN TO REPLY.", + os.environ.get("DEFAULT_FROM_EMAIL"), + [contact.email], + ) + target_object.delete() + except ObjectDoesNotExist: + pass + return_response = render(request, "data/patient_deleted_success.html") else: - return_response = redirect("main:not_logged_in") + target_object = get_object_or_404(Patient, pk=patient_id) + return_response = render( + request, "data/delete.html", {"patient": target_object} + ) return return_response +@is_authenticated def delete_chief_complaint( - request, chief_complaint_id=None, patient_id=None, encounter_id=None + _, chief_complaint_id=None, patient_id=None, encounter_id=None ): - if request.user.is_authenticated: - target_object = get_object_or_404(ChiefComplaint, pk=chief_complaint_id) - target_object.active = False - target_object.save() - if encounter_id is not None: - return_response = redirect( - "main:chief_complaint_list_view", patient_id, encounter_id - ) - else: - return_response = redirect("main:chief_complaint_list_view", patient_id) + target_object = get_object_or_404(ChiefComplaint, pk=chief_complaint_id) + target_object.active = False + target_object.save() + if encounter_id is not None: + return_response = redirect( + "main:chief_complaint_list_view", patient_id, encounter_id + ) else: - return_response = redirect("main:not_logged_in") + return_response = redirect("main:chief_complaint_list_view", patient_id) return return_response +@is_authenticated def delete_treatment_view(request, treatment_id=None): - if request.user.is_authenticated: - target_object = get_object_or_404(Treatment, pk=treatment_id) - for item in target_object.medication.all(): - item.amount += target_object.amount - if item.count > 0: - item.quantity = math.ceil(item.amount / item.count) - else: - item.quantity = item.amount - item.save() - target_object.delete() - return_response = redirect(request.META.get("HTTP_REFERER", "/")) - else: - return_response = redirect("main:not_logged_in") - return return_response + target_object = get_object_or_404(Treatment, pk=treatment_id) + for item in target_object.medication.all().iterator(): + item.amount += target_object.amount + if item.count > 0: + item.quantity = math.ceil(item.amount / item.count) + else: + item.quantity = item.amount + item.save() + target_object.delete() + return redirect(request.META.get("HTTP_REFERER", "/")) +@is_authenticated @silk_profile("delete-photo-view") def delete_photo_view(request, patient_id=None, encounter_id=None, photo_id=None): """ @@ -129,33 +124,30 @@ def delete_photo_view(request, patient_id=None, encounter_id=None, photo_id=None :param photo_id: :return: HTTPResponse. """ - if request.user.is_authenticated: - if request.user.current_campaign == "RECOVERY MODE": - return_response = redirect("main:home") - else: - units = Campaign.objects.get(name=request.user.current_campaign).units - encounter = get_object_or_404(PatientEncounter, pk=encounter_id) - patient = get_object_or_404(Patient, pk=patient_id) - photo = Photo.objects.get(pk=photo_id) - photo.delete() - suffix = patient.get_suffix_display() if patient.suffix is not None else "" - return_response = render( - request, - "forms/photos_tab.html", - { - "aux_form": PhotoForm(), - "vitals": Vitals.objects.filter(encounter=encounter), - "treatments": Treatment.objects.filter(encounter=encounter), - "vitals_form": VitalsForm(unit=units), - "page_name": f"Edit Encounter for {patient.first_name}, {patient.last_name}, {suffix}", - "encounter": encounter, - "birth_sex": patient.sex_assigned_at_birth, - "encounter_id": encounter_id, - "patient_name": f"{patient.first_name} {patient.last_name} {suffix}", - "units": units, - "patient": patient, - }, - ) + if request.user.current_campaign == "RECOVERY MODE": + return_response = redirect("main:home") else: - return_response = redirect("/not_logged_in") + units = Campaign.objects.get(name=request.user.current_campaign).units + encounter = get_object_or_404(PatientEncounter, pk=encounter_id) + patient = get_object_or_404(Patient, pk=patient_id) + photo = Photo.objects.get(pk=photo_id) + photo.delete() + suffix = patient.get_suffix_display() if patient.suffix is not None else "" + return_response = render( + request, + "forms/photos_tab.html", + { + "aux_form": PhotoForm(), + "vitals": Vitals.objects.filter(encounter=encounter), + "treatments": Treatment.objects.filter(encounter=encounter), + "vitals_form": VitalsForm(unit=units), + "page_name": f"Edit Encounter for {patient.first_name}, {patient.last_name}, {suffix}", + "encounter": encounter, + "birth_sex": patient.sex_assigned_at_birth, + "encounter_id": encounter_id, + "patient_name": f"{patient.first_name} {patient.last_name} {suffix}", + "units": units, + "patient": patient, + }, + ) return return_response diff --git a/main/edit_views.py b/main/edit_views.py index 203f7f31..670891ec 100644 --- a/main/edit_views.py +++ b/main/edit_views.py @@ -195,7 +195,7 @@ def __encounter_edit_form_post(request, patient_id, encounter_id): encounter = get_object_or_404(PatientEncounter, pk=encounter_id) patient = get_object_or_404(Patient, pk=patient_id) units = Campaign.objects.get(name=request.user.current_campaign).units - photos = list(encounter.photos.all()) + photos = encounter.photos.all().iterator() treatments = Treatment.objects.filter(encounter=encounter) form = PatientEncounterForm(request.POST or None, instance=encounter, unit=units) if form.is_valid(): @@ -468,7 +468,7 @@ def __treatment_view_post(request, encounter): treatment.amount = 1 treatment.save() treatment_form.save_m2m() - for item in treatment.medication.all(): + for item in treatment.medication.all().iterator(): item.amount -= treatment.amount if item.count > 0: item.quantity = math.ceil(item.amount / item.count) @@ -821,7 +821,7 @@ def __hpi_view_post(request, patient_id, encounter_id): vitals = Vitals.objects.filter(encounter=encounter) treatments = Treatment.objects.filter(encounter=encounter) hpis = [] - for query_item in encounter.chief_complaint.all(): + for query_item in encounter.chief_complaint.all().iterator(): hpi_object = HistoryOfPresentIllness.objects.get_or_create( encounter=encounter, chief_complaint=query_item )[0] diff --git a/main/femr_admin_views.py b/main/femr_admin_views.py index 56089952..1e55e20c 100644 --- a/main/femr_admin_views.py +++ b/main/femr_admin_views.py @@ -1,5 +1,6 @@ from django.shortcuts import get_object_or_404, redirect, render from silk.profiling.profiler import silk_profile +from main.decorators import is_authenticated, is_femr_admin from main.forms import ( CampaignForm, @@ -20,38 +21,35 @@ ) +@is_authenticated def femr_admin_home(request): - if request.user.is_authenticated: - if request.method == "POST": - return_response = redirect("main:index") - else: - return_response = render( - request, "femr_admin/home.html", {"page_name": "fEMR Admin"} - ) + if request.method == "POST": + return_response = redirect("main:index") else: - return_response = redirect("main:not_logged_in") + return_response = render( + request, "femr_admin/home.html", {"page_name": "fEMR Admin"} + ) return return_response +@is_authenticated def change_campaign(request): - if request.user.is_authenticated: - if request.method == "POST": - campaign = request.POST.get("campaign", None) - if campaign is not None: - request.user.current_campaign = campaign - request.user.save() - AuditEntry.objects.create( - action="user_changed_campaigns", - ip=get_client_ip(request), - username=request.user.username, - campaign=Campaign.objects.get(name=request.user.current_campaign), - browser_user_agent=request.user_agent.browser.family, - ) - return_response = redirect("main:home") - else: - return_response = redirect("main:index") + if request.method == "POST": + campaign = request.POST.get("campaign", None) + if campaign is not None: + request.user.current_campaign = campaign + request.user.save() + AuditEntry.objects.create( + action="user_changed_campaigns", + ip=get_client_ip(request), + username=request.user.username, + campaign=Campaign.objects.get(name=request.user.current_campaign), + browser_user_agent=request.user_agent.browser.family, + system_user_agent=request.user_agent.os.family, + ) + return_response = redirect("main:home") else: - return_response = redirect("main:not_logged_in") + return_response = redirect("main:index") return return_response @@ -64,580 +62,477 @@ def get_client_ip(request): return ip_address +@is_femr_admin +@is_authenticated def new_campaign_view(request): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - campaign_name = request.session.get("campaign", None) - if campaign_name == "RECOVERY MODE": + campaign_name = request.session.get("campaign", None) + if campaign_name == "RECOVERY MODE": + return_value = render(request, "femr_admin/campaign/op_not_permitted.html") + else: + if request.method == "POST": + form = CampaignForm(request.POST) + if form.is_valid(): + item = form.save() + item.save() + DatabaseChangeLog.objects.create( + action="Create", + model="Campaign", + instance=str(item), + ip=get_client_ip(request), + username=request.user.username, + campaign=Campaign.objects.get(name=request.user.current_campaign), + ) return_value = render( - request, "femr_admin/campaign/op_not_permitted.html" + request, "femr_admin/confirm/campaign_submitted.html" ) - else: - if request.method == "POST": - form = CampaignForm(request.POST) - if form.is_valid(): - item = form.save() - item.save() - DatabaseChangeLog.objects.create( - action="Create", - model="Campaign", - instance=str(item), - ip=get_client_ip(request), - username=request.user.username, - campaign=Campaign.objects.get( - name=request.user.current_campaign - ), - ) - return_value = render( - request, "femr_admin/confirm/campaign_submitted.html" - ) - else: - return_value = render( - request, - "femr_admin/campaign/new_campaign.html", - {"form": form, "page_name": "New Campaign"}, - ) - else: - form = CampaignForm() - return_value = render( - request, - "femr_admin/campaign/new_campaign.html", - {"form": form, "page_name": "New Campaign"}, - ) - else: - return_value = redirect("main:permission_denied") - else: - return_value = redirect("main:not_logged_in") - return return_value - - -def new_instance_view(request): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - if request.method == "POST": - form = InstanceForm(request.POST) - contact_form = fEMRAdminUserForm() - if form.is_valid(): - item = form.save() - item.save() - DatabaseChangeLog.objects.create( - action="Create", - model="Instance", - instance=str(item), - ip=get_client_ip(request), - username=request.user.username, - campaign=Campaign.objects.get( - name=request.user.current_campaign - ), - ) - return_value = render( - request, "femr_admin/confirm/instance_submitted.html" - ) - else: - return_value = render( - request, - "femr_admin/instance/new_instance.html", - { - "form": form, - "contact_form": contact_form, - "page_name": "New Operation", - "show": False, - }, - ) else: return_value = render( request, - "femr_admin/instance/new_instance.html", - { - "form": InstanceForm(), - "contact_form": fEMRAdminUserForm(), - "page_name": "New Operation", - "show": False, - }, + "femr_admin/campaign/new_campaign.html", + {"form": form, "page_name": "New Campaign"}, ) else: - return_value = redirect("main:permission_denied") - else: - return_value = redirect("main:not_logged_in") + form = CampaignForm() + return_value = render( + request, + "femr_admin/campaign/new_campaign.html", + {"form": form, "page_name": "New Campaign"}, + ) return return_value -def new_contact_view(request): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - form = InstanceForm() - contact_form = fEMRAdminUserForm() - if request.method == "POST": - contact_form = fEMRAdminUserForm(request.POST) - if contact_form.is_valid(): - item = contact_form.save() - item.campaigns.add(Campaign.objects.get(name="Test")) - item.save() - DatabaseChangeLog.objects.create( - action="Create", - model="Contact", - instance=str(item), - ip=get_client_ip(request), - username=request.user.username, - campaign=Campaign.objects.get( - name=request.user.current_campaign - ), - ) - contact_form = fEMRAdminUserForm() - return_response = render( +@is_femr_admin +@is_authenticated +def new_instance_view(request): + if request.method == "POST": + form = InstanceForm(request.POST) + contact_form = fEMRAdminUserForm() + if form.is_valid(): + item = form.save() + item.save() + DatabaseChangeLog.objects.create( + action="Create", + model="Instance", + instance=str(item), + ip=get_client_ip(request), + username=request.user.username, + campaign=Campaign.objects.get(name=request.user.current_campaign), + ) + return_value = render(request, "femr_admin/confirm/instance_submitted.html") + else: + return_value = render( request, "femr_admin/instance/new_instance.html", { "form": form, "contact_form": contact_form, "page_name": "New Operation", - "show": True, + "show": False, }, ) - else: - return_response = redirect("main:permission_denied") else: - return_response = redirect("main:not_logged_in") - return return_response - - -def edit_campaign_view(request, campaign_id=None): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - campaign_name = request.session.get("campaign", None) - if campaign_name == "RECOVERY MODE": - return_response = render( - request, "femr_admin/campaign/op_not_permitted.html" - ) - else: - instance = Campaign.objects.get(pk=campaign_id) - if request.method == "POST": - form = CampaignForm(request.POST or None, instance=instance) - if form.is_valid(): - item = form.save() - item.save() - DatabaseChangeLog.objects.create( - action="Edit", - model="Campaign", - instance=str(item), - ip=get_client_ip(request), - username=request.user.username, - campaign=instance, - ) - return_response = render( - request, "femr_admin/confirm/campaign_submitted.html" - ) - else: - return_response = render( - request, - "femr_admin/campaign/edit_campaign.html", - { - "form": form, - "page_name": "Edit Campaign", - "campaign_id": campaign_id, - }, - ) - else: - form = CampaignForm(instance=instance) - return_response = render( - request, - "femr_admin/campaign/edit_campaign.html", - { - "form": form, - "page_name": "Edit Campaign", - "campaign_id": campaign_id, - }, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response - - -def edit_contact_view(request, user_id=None): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - instance = fEMRUser.objects.get(pk=user_id) - if request.method == "POST": - form = fEMRAdminUserUpdateForm(request.POST or None, instance=instance) - if form.is_valid(): - item = form.save() - item.save() - DatabaseChangeLog.objects.create( - action="Edit", - model="Contact", - instance=str(item), - ip=get_client_ip(request), - username=request.user.username, - campaign=Campaign.objects.get( - name=request.user.current_campaign - ), - ) - return_value = render( - request, "femr_admin/confirm/contact_submitted.html" - ) - else: - return_value = render( - request, - "femr_admin/contact/edit_contact.html", - { - "form": form, - "page_name": "Edit Contact", - "contact_id": user_id, - }, - ) - else: - return_value = render( - request, - "femr_admin/contact/edit_contact.html", - { - "form": fEMRAdminUserUpdateForm(instance=instance), - "page_name": "Edit Contact", - "contact_id": user_id, - }, - ) - else: - return_value = redirect("main:permission_denied") - else: - return_value = redirect("main:not_logged_in") + return_value = render( + request, + "femr_admin/instance/new_instance.html", + { + "form": InstanceForm(), + "contact_form": fEMRAdminUserForm(), + "page_name": "New Operation", + "show": False, + }, + ) return return_value -def view_contact_view(request, user_id=None): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - instance = fEMRUser.objects.get(pk=user_id) - return_response = render( - request, - "femr_admin/contact/contact_info.html", - {"instance": instance, "page_name": "Contact Info"}, +@is_femr_admin +@is_authenticated +def new_contact_view(request): + form = InstanceForm() + contact_form = fEMRAdminUserForm() + if request.method == "POST": + contact_form = fEMRAdminUserForm(request.POST) + if contact_form.is_valid(): + item = contact_form.save() + item.campaigns.add(Campaign.objects.get(name="Test")) + item.save() + DatabaseChangeLog.objects.create( + action="Create", + model="Contact", + instance=str(item), + ip=get_client_ip(request), + username=request.user.username, + campaign=Campaign.objects.get(name=request.user.current_campaign), ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response - - -def edit_instance_view(request, instance_id=None): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - instance = Instance.objects.get(pk=instance_id) - contact = instance.main_contact contact_form = fEMRAdminUserForm() - edit_contact_form = fEMRAdminUserUpdateForm(instance=contact) - if request.method == "POST": - form = InstanceForm(request.POST or None, instance=instance) - if form.is_valid(): - item = form.save() - item.save() - DatabaseChangeLog.objects.create( - action="Edit", - model="Instance", - instance=str(item), - ip=get_client_ip(request), - username=request.user.username, - campaign=Campaign.objects.get( - name=request.user.current_campaign - ), - ) - return_response = render( - request, "femr_admin/confirm/instance_submitted.html" - ) + return render( + request, + "femr_admin/instance/new_instance.html", + { + "form": form, + "contact_form": contact_form, + "page_name": "New Operation", + "show": True, + }, + ) + + +@is_femr_admin +@is_authenticated +def edit_campaign_view(request, campaign_id=None): + campaign_name = request.session.get("campaign", None) + if campaign_name == "RECOVERY MODE": + return_response = render(request, "femr_admin/campaign/op_not_permitted.html") + else: + instance = Campaign.objects.get(pk=campaign_id) + if request.method == "POST": + form = CampaignForm(request.POST or None, instance=instance) + if form.is_valid(): + item = form.save() + item.save() + DatabaseChangeLog.objects.create( + action="Edit", + model="Campaign", + instance=str(item), + ip=get_client_ip(request), + username=request.user.username, + campaign=instance, + ) + return_response = render( + request, "femr_admin/confirm/campaign_submitted.html" + ) else: - form = InstanceForm(instance=instance) return_response = render( request, - "femr_admin/instance/edit_instance.html", + "femr_admin/campaign/edit_campaign.html", { "form": form, - "contact_form": contact_form, - "edit_contact_form": edit_contact_form, - "page_name": "Edit Instance", - "contact_id": contact.id, - "instance_id": instance_id, + "page_name": "Edit Campaign", + "campaign_id": campaign_id, }, ) else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response - - -def list_campaign_view(request): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - active_campaigns = Campaign.objects.filter(active=True).order_by( - "instance__name", "name" - ) - inactive_campaigns = Campaign.objects.filter(active=False).order_by( - "instance__name", "name" - ) + form = CampaignForm(instance=instance) return_response = render( request, - "femr_admin/campaign/list_campaign.html", + "femr_admin/campaign/edit_campaign.html", { - "active_campaigns": active_campaigns, - "inactive_campaigns": inactive_campaigns, - "page_name": "Campaigns", + "form": form, + "page_name": "Edit Campaign", + "campaign_id": campaign_id, }, ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") return return_response -def list_instance_view(request): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - active_instances = Instance.objects.filter(active=True).order_by("name") - inactive_instances = Instance.objects.filter(active=False).order_by("name") - return_response = render( +@is_femr_admin +@is_authenticated +def edit_contact_view(request, user_id=None): + instance = fEMRUser.objects.get(pk=user_id) + if request.method == "POST": + form = fEMRAdminUserUpdateForm(request.POST or None, instance=instance) + if form.is_valid(): + item = form.save() + item.save() + DatabaseChangeLog.objects.create( + action="Edit", + model="Contact", + instance=str(item), + ip=get_client_ip(request), + username=request.user.username, + campaign=Campaign.objects.get(name=request.user.current_campaign), + ) + return_value = render(request, "femr_admin/confirm/contact_submitted.html") + else: + return_value = render( request, - "femr_admin/instance/list_instance.html", + "femr_admin/contact/edit_contact.html", { - "active_instances": active_instances, - "inactive_instances": inactive_instances, - "page_name": "Operations", + "form": form, + "page_name": "Edit Contact", + "contact_id": user_id, }, ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response - - -def lock_instance_view(request, instance_id=None): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - instance = get_object_or_404(Instance, pk=instance_id) - instance.active = False - instance.save() - for campaign in instance.campaign_set.all(): - campaign.active = False - campaign.save() - return_response = redirect("main:list_instance") - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response - - -def unlock_instance_view(request, instance_id=None): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - instance = get_object_or_404(Instance, pk=instance_id) - instance.active = True - instance.save() - return_response = redirect("main:list_instance") - else: - return_response = redirect("main:permission_denied") else: - return_response = redirect("main:not_logged_in") - return return_response + return_value = render( + request, + "femr_admin/contact/edit_contact.html", + { + "form": fEMRAdminUserUpdateForm(instance=instance), + "page_name": "Edit Contact", + "contact_id": user_id, + }, + ) + return return_value -def lock_campaign_view(request, campaign_id=None): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - campaign = get_object_or_404(Campaign, pk=campaign_id) - campaign.active = False - campaign.save() - return_response = redirect("main:list_campaign") - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response +@is_femr_admin +@is_authenticated +def view_contact_view(request, user_id=None): + instance = fEMRUser.objects.get(pk=user_id) + return render( + request, + "femr_admin/contact/contact_info.html", + {"instance": instance, "page_name": "Contact Info"}, + ) -def unlock_campaign_view(request, campaign_id=None): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - campaign = get_object_or_404(Campaign, pk=campaign_id) - campaign.active = True - campaign.instance.active = True - campaign.instance.save() - campaign.save() - return_response = redirect("main:list_campaign") - else: - return_response = redirect("main:permission_denied") +@is_femr_admin +@is_authenticated +def edit_instance_view(request, instance_id=None): + instance = Instance.objects.get(pk=instance_id) + contact = instance.main_contact + contact_form = fEMRAdminUserForm() + edit_contact_form = fEMRAdminUserUpdateForm(instance=contact) + if request.method == "POST": + form = InstanceForm(request.POST or None, instance=instance) + if form.is_valid(): + item = form.save() + item.save() + DatabaseChangeLog.objects.create( + action="Edit", + model="Instance", + instance=str(item), + ip=get_client_ip(request), + username=request.user.username, + campaign=Campaign.objects.get(name=request.user.current_campaign), + ) + return_response = render( + request, "femr_admin/confirm/instance_submitted.html" + ) else: - return_response = redirect("main:not_logged_in") + form = InstanceForm(instance=instance) + return_response = render( + request, + "femr_admin/instance/edit_instance.html", + { + "form": form, + "contact_form": contact_form, + "edit_contact_form": edit_contact_form, + "page_name": "Edit Instance", + "contact_id": contact.id, + "instance_id": instance_id, + }, + ) return return_response +@is_femr_admin +@is_authenticated +def list_campaign_view(request): + active_campaigns = Campaign.objects.filter(active=True).order_by( + "instance__name", "name" + ) + inactive_campaigns = Campaign.objects.filter(active=False).order_by( + "instance__name", "name" + ) + return render( + request, + "femr_admin/campaign/list_campaign.html", + { + "active_campaigns": active_campaigns, + "inactive_campaigns": inactive_campaigns, + "page_name": "Campaigns", + }, + ) + + +@is_femr_admin +@is_authenticated +def list_instance_view(request): + active_instances = Instance.objects.filter(active=True).order_by("name") + inactive_instances = Instance.objects.filter(active=False).order_by("name") + return render( + request, + "femr_admin/instance/list_instance.html", + { + "active_instances": active_instances, + "inactive_instances": inactive_instances, + "page_name": "Operations", + }, + ) + + +@is_femr_admin +@is_authenticated +def lock_instance_view(_, instance_id=None): + instance = get_object_or_404(Instance, pk=instance_id) + instance.active = False + instance.save() + for campaign in instance.campaign_set.all().iterator(): + campaign.active = False + campaign.save() + return redirect("main:list_instance") + + +@is_femr_admin +@is_authenticated +def unlock_instance_view(_, instance_id=None): + instance = get_object_or_404(Instance, pk=instance_id) + instance.active = True + instance.save() + return redirect("main:list_instance") + + +@is_femr_admin +@is_authenticated +def lock_campaign_view(_, campaign_id=None): + campaign = get_object_or_404(Campaign, pk=campaign_id) + campaign.active = False + campaign.save() + return redirect("main:list_campaign") + + +@is_femr_admin +@is_authenticated +def unlock_campaign_view(_, campaign_id=None): + campaign = get_object_or_404(Campaign, pk=campaign_id) + campaign.active = True + campaign.instance.active = True + campaign.instance.save() + campaign.save() + return redirect("main:list_campaign") + + +@is_femr_admin +@is_authenticated def new_race_view(request): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - if request.method == "POST": - form = RaceForm(request.POST) - if form.is_valid(): - item = form.save() - item.save() - return_response = redirect(request.META.get("HTTP_REFERER")) - else: - return_response = render( - request, "femr_admin/race/new_race.html", {"form": form} - ) - else: - form = RaceForm() - return_response = render( - request, "femr_admin/race/new_race.html", {"form": form} - ) + if request.method == "POST": + form = RaceForm(request.POST) + if form.is_valid(): + item = form.save() + item.save() + return_response = redirect(request.META.get("HTTP_REFERER")) else: - return_response = redirect("main:permission_denied") + return_response = render( + request, "femr_admin/race/new_race.html", {"form": form} + ) else: - return_response = redirect("main:not_logged_in") + form = RaceForm() + return_response = render( + request, "femr_admin/race/new_race.html", {"form": form} + ) return return_response +@is_femr_admin +@is_authenticated def new_ethnicity_view(request): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - if request.method == "POST": - form = EthnicityForm(request.POST) - if form.is_valid(): - item = form.save() - item.save() - return_response = redirect(request.META.get("HTTP_REFERER")) - else: - return_response = render( - request, "femr_admin/race/new_ethnicity.html", {"form": form} - ) - else: - form = EthnicityForm() - return_response = render( - request, "femr_admin/race/new_ethnicity.html", {"form": form} - ) + if request.method == "POST": + form = EthnicityForm(request.POST) + if form.is_valid(): + item = form.save() + item.save() + return_response = redirect(request.META.get("HTTP_REFERER")) else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response - - -def list_organization_view(request): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - organizations = Organization.objects.all() return_response = render( - request, - "femr_admin/organization/list_organization.html", - { - "user": request.user, - "organizations": organizations, - "page_name": "Organizations", - }, + request, "femr_admin/race/new_ethnicity.html", {"form": form} ) - else: - return_response = redirect("main:permission_denied") else: - return_response = redirect("main:not_logged_in") + form = EthnicityForm() + return_response = render( + request, "femr_admin/race/new_ethnicity.html", {"form": form} + ) return return_response +@is_femr_admin +@is_authenticated +def list_organization_view(request): + organizations = Organization.objects.all().iterator() + return render( + request, + "femr_admin/organization/list_organization.html", + { + "user": request.user, + "organizations": organizations, + "page_name": "Organizations", + }, + ) + + +@is_femr_admin +@is_authenticated @silk_profile("edit-organization-view") def edit_organization_view(request, organization_id=None): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - instance = Organization.objects.get(pk=organization_id) - contact = instance.main_contact - contact_form = fEMRAdminUserForm() - edit_contact_form = fEMRAdminUserUpdateForm(instance=contact) - if request.method == "POST": - form = OrganizationForm(request.POST or None, instance=instance) - if form.is_valid(): - item = form.save() - item.save() - DatabaseChangeLog.objects.create( - action="Edit", - model="Organization", - instance=str(item), - ip=get_client_ip(request), - username=request.user.username, - campaign=Campaign.objects.get( - name=request.user.current_campaign - ), - ) - return_response = render( - request, "femr_admin/confirm/organization_submitted.html" - ) - else: - form = OrganizationForm(instance=instance) - return_response = render( - request, - "femr_admin/organization/edit_organization.html", - { - "form": form, - "contact_form": contact_form, - "edit_contact_form": edit_contact_form, - "page_name": "Edit Organization", - "contact_id": contact.id if contact is not None else None, - "instance_id": organization_id, - }, - ) - else: - return_response = redirect("main:permission_denied") + instance = Organization.objects.get(pk=organization_id) + contact = instance.main_contact + contact_form = fEMRAdminUserForm() + edit_contact_form = fEMRAdminUserUpdateForm(instance=contact) + if request.method == "POST": + form = OrganizationForm(request.POST or None, instance=instance) + if form.is_valid(): + item = form.save() + item.save() + DatabaseChangeLog.objects.create( + action="Edit", + model="Organization", + instance=str(item), + ip=get_client_ip(request), + username=request.user.username, + campaign=Campaign.objects.get(name=request.user.current_campaign), + ) + return_response = render( + request, "femr_admin/confirm/organization_submitted.html" + ) else: - return_response = redirect("main:not_logged_in") + form = OrganizationForm(instance=instance) + return_response = render( + request, + "femr_admin/organization/edit_organization.html", + { + "form": form, + "contact_form": contact_form, + "edit_contact_form": edit_contact_form, + "page_name": "Edit Organization", + "contact_id": contact.id if contact is not None else None, + "instance_id": organization_id, + }, + ) return return_response +@is_femr_admin +@is_authenticated def new_organization_view(request): - if request.user.is_authenticated: - if request.user.groups.filter(name="fEMR Admin").exists(): - if request.method == "POST": - form = OrganizationForm(request.POST) - contact_form = fEMRAdminUserForm() - if form.is_valid(): - item = form.save() - item.save() - DatabaseChangeLog.objects.create( - action="Create", - model="Organization", - instance=str(item), - ip=get_client_ip(request), - username=request.user.username, - campaign=Campaign.objects.get( - name=request.user.current_campaign - ), - ) - return_response = render( - request, "femr_admin/confirm/organization_submitted.html" - ) - else: - return_response = render( - request, - "femr_admin/organization/new_organization.html", - { - "form": form, - "contact_form": contact_form, - "page_name": "New Organization", - "show": False, - }, - ) - else: - form = OrganizationForm() - contact_form = fEMRAdminUserForm() - return_response = render( - request, - "femr_admin/organization/new_organization.html", - { - "form": form, - "contact_form": contact_form, - "page_name": "New Organization", - "show": False, - }, - ) + if request.method == "POST": + form = OrganizationForm(request.POST) + contact_form = fEMRAdminUserForm() + if form.is_valid(): + item = form.save() + item.save() + DatabaseChangeLog.objects.create( + action="Create", + model="Organization", + instance=str(item), + ip=get_client_ip(request), + username=request.user.username, + campaign=Campaign.objects.get(name=request.user.current_campaign), + ) + return_response = render( + request, "femr_admin/confirm/organization_submitted.html" + ) else: - return_response = redirect("main:permission_denied") + return_response = render( + request, + "femr_admin/organization/new_organization.html", + { + "form": form, + "contact_form": contact_form, + "page_name": "New Organization", + "show": False, + }, + ) else: - return_response = redirect("main:not_logged_in") + form = OrganizationForm() + contact_form = fEMRAdminUserForm() + return_response = render( + request, + "femr_admin/organization/new_organization.html", + { + "form": form, + "contact_form": contact_form, + "page_name": "New Organization", + "show": False, + }, + ) return return_response diff --git a/main/form_views.py b/main/form_views.py index 2ca50066..9ee10e57 100644 --- a/main/form_views.py +++ b/main/form_views.py @@ -11,6 +11,7 @@ from datetime import datetime from django.shortcuts import render, redirect +from main.decorators import in_recovery_mode, is_authenticated from main.femr_admin_views import get_client_ip @@ -126,6 +127,7 @@ def __patient_form_view_post(request, campaign): return return_response +@is_authenticated def patient_form_view(request): """ Used to create new Patient objects. @@ -133,17 +135,14 @@ def patient_form_view(request): :param request: Django Request object. :return: HTTPResponse. """ - if request.user.is_authenticated: - if request.user.current_campaign == "RECOVERY MODE": - return_response = redirect("main:home") - else: - campaign = Campaign.objects.get(name=request.user.current_campaign) - if request.method == "POST": - return_response = __patient_form_view_post(request, campaign) - else: - return_response = __patient_form_view_get(request, campaign) + if request.user.current_campaign == "RECOVERY MODE": + return_response = redirect("main:home") else: - return_response = redirect("/not_logged_in") + campaign = Campaign.objects.get(name=request.user.current_campaign) + if request.method == "POST": + return_response = __patient_form_view_post(request, campaign) + else: + return_response = __patient_form_view_get(request, campaign) return return_response @@ -314,6 +313,8 @@ def __patient_encounter_form_post(request, patient): return return_response +@is_authenticated +@in_recovery_mode def patient_encounter_form_view(request, patient_id=None): """ Used to create new PatientEncounter objects. @@ -322,47 +323,38 @@ def patient_encounter_form_view(request, patient_id=None): :param patient_id: The internal ID of the patient to be edited. :return: HTTPResponse. """ - if request.user.is_authenticated: - patient = Patient.objects.get(pk=patient_id) - if request.user.current_campaign == "RECOVERY MODE": - return_response = redirect("main:home") - else: - if request.method == "POST": - return_response = __patient_encounter_form_post(request, patient) - else: - return_response = __patient_encounter_form_get(request, patient) + patient = Patient.objects.get(pk=patient_id) + if request.method == "POST": + return_response = __patient_encounter_form_post(request, patient) else: - return_response = redirect("/not_logged_in") + return_response = __patient_encounter_form_get(request, patient) return return_response +@is_authenticated +@in_recovery_mode def referral_form_view(request, patient_id=None): - if request.user.is_authenticated: - if request.user.current_campaign == "RECOVERY MODE": - return_response = redirect("main:home") - elif request.method == "POST": - patient = Patient.objects.get(pk=patient_id) - patient.campaign.add(Campaign.objects.get(pk=request.POST["campaign"])) - patient.save() - if os.environ.get("QLDB_ENABLED") == "TRUE": - update_patient_encounter( - {"patient": patient.id, "campaign": request.POST["campaign"]} - ) - return_response = redirect("main:patient_list_view") - elif request.method == "GET": - return_response = render( - request, - "forms/referral.html", - { - "patient_id": patient_id, - "page_name": "Campaign Referral", - "campaigns": Campaign.objects.filter( - instance=Campaign.objects.get( - name=request.user.current_campaign - ).instance - ).filter(active=True), - }, + if request.method == "POST": + patient = Patient.objects.get(pk=patient_id) + patient.campaign.add(Campaign.objects.get(pk=request.POST["campaign"])) + patient.save() + if os.environ.get("QLDB_ENABLED") == "TRUE": + update_patient_encounter( + {"patient": patient.id, "campaign": request.POST["campaign"]} ) - else: - return_response = redirect("/not_logged_in") + return_response = redirect("main:patient_list_view") + elif request.method == "GET": + return_response = render( + request, + "forms/referral.html", + { + "patient_id": patient_id, + "page_name": "Campaign Referral", + "campaigns": Campaign.objects.filter( + instance=Campaign.objects.get( + name=request.user.current_campaign + ).instance + ).filter(active=True), + }, + ) return return_response diff --git a/main/forms.py b/main/forms.py index ed3b431c..2c59885d 100644 --- a/main/forms.py +++ b/main/forms.py @@ -611,7 +611,7 @@ class Meta: def filter_campaigns_for_instances(user): - campaigns = user.campaigns.all() + campaigns = user.campaigns.all().iterator() instances = [c.instance for c in campaigns] return Campaign.objects.filter(instance__in=instances) @@ -842,6 +842,7 @@ class Meta: "manufacturer": autocomplete.ModelSelect2( url="main:manufacturer-autocomplete" ), + "expiration_date": DateInputOverride(), } diff --git a/main/formulary_management.py b/main/formulary_management.py index 73a679c6..039b12b3 100644 --- a/main/formulary_management.py +++ b/main/formulary_management.py @@ -1,9 +1,12 @@ from django.http.response import HttpResponse -from django.shortcuts import redirect, render -from main.background_tasks import check_admin_permission +from django.shortcuts import render, redirect, get_object_or_404 +from django.core.paginator import Paginator + +from silk.profiling.profiler import silk_profile from main.csvio.added_inventory import AddedInventoryHandler from main.csvio.initial_inventory import InitialInventoryHandler +from main.decorators import is_admin, is_authenticated from main.forms import ( AddSupplyForm, CSVUploadForm, @@ -13,212 +16,203 @@ from main.models import Campaign, InventoryEntry +@silk_profile("formulary-home-view") +@is_authenticated +@is_admin def formulary_home_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - campaign = Campaign.objects.get(name=request.user.current_campaign) - formulary = campaign.inventory.entries.all().order_by("medication") - return_response = render( - request, - "formulary/home.html", - {"page_name": "Inventory", "list_view": formulary}, - ) - else: - return_response = redirect("main:permission_denied") + campaign = Campaign.objects.get(name=request.user.current_campaign) + formulary = campaign.inventory.entries.all().order_by("medication") + paginator = Paginator(formulary, 10) + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) + return render( + request, + "formulary/home.html", + {"page_name": "Inventory", "list_view": page_obj}, + ) + + +@silk_profile("add-supply-view") +@is_authenticated +def add_supply_view(request): + if request.method == "GET": + form = InventoryEntryForm() + return_response = render(request, "formulary/add_supply.html", {"form": form}) else: - return_response = redirect("main:not_logged_in") + campaign = Campaign.objects.get(name=request.user.current_campaign) + form = InventoryEntryForm(request.POST) + entry = form.save() + entry.amount = entry.count * entry.quantity + entry.save() + campaign.inventory.entries.add(entry) + campaign.save() + return_response = render(request, "formulary/formulary_submitted.html") return return_response -def add_supply_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - if request.method == "GET": - form = InventoryEntryForm() - return_response = render( - request, "formulary/add_supply.html", {"form": form} - ) - else: - campaign = Campaign.objects.get(name=request.user.current_campaign) - form = InventoryEntryForm(request.POST) - entry = form.save() - entry.amount = entry.count * entry.quantity - entry.save() - campaign.inventory.entries.add(entry) - campaign.save() - return_response = render(request, "formulary/formulary_submitted.html") - else: - return_response = redirect("main:permission_denied") +@silk_profile("edit-supply-view") +@is_authenticated +@is_admin +def edit_supply_view(request, entry_id=None): + supply = get_object_or_404(InventoryEntry, pk=entry_id) + if request.method == "GET": + form = InventoryEntryForm(instance=supply) + return_response = render( + request, + "formulary/edit_supply.html", + {"form": form, "supply_id": supply.id}, + ) else: - return_response = redirect("main:not_logged_in") + form = InventoryEntryForm(request.POST or None, instance=supply) + entry = form.save(commit=False) + entry.amount = entry.count * entry.quantity + entry.save() + return_response = render(request, "formulary/formulary_submitted.html") return return_response +@silk_profile("delete-supply-item") +@is_authenticated def delete_supply_item(request, supply_id=None): - if request.user.is_authenticated: - campaign = Campaign.objects.get(name=request.user.current_campaign) - entry = InventoryEntry.objects.get(pk=supply_id) - campaign.inventory.entries.remove(entry) - return_response = redirect("main:formulary_home_view") - else: - return_response = redirect("main:not_logged_in") - return return_response + campaign = Campaign.objects.get(name=request.user.current_campaign) + entry = InventoryEntry.objects.get(pk=supply_id) + campaign.inventory.entries.remove(entry) + return redirect("main:formulary_home_view") +@silk_profile("edit-add-supply-view") +@is_authenticated +@is_admin def edit_add_supply_view(request, entry_id=None): - if request.user.is_authenticated: - if check_admin_permission(request.user): - inventory_entry = InventoryEntry.objects.get(pk=entry_id) - if request.method == "GET": - form = AddSupplyForm() - return_response = render( - request, - "formulary/edit_add_supply.html", - { - "page_name": inventory_entry, - "form": form, - "item_id": inventory_entry.id, - }, - ) - elif request.method == "POST": - form = AddSupplyForm(request.POST) - if form.is_valid(): - inventory_entry.initial_quantity += int(request.POST["quantity"]) - inventory_entry.quantity += int(request.POST["quantity"]) - inventory_entry.save() - return_response = redirect("main:formulary_home_view") - else: - return_response = render( - request, - "formulary/edit_add_supply.html", - { - "page_name": inventory_entry, - "form": form, - "item_id": inventory_entry.id, - }, - ) + inventory_entry = InventoryEntry.objects.get(pk=entry_id) + if request.method == "GET": + form = AddSupplyForm() + return_response = render( + request, + "formulary/edit_add_supply.html", + { + "page_name": inventory_entry, + "form": form, + "item_id": inventory_entry.id, + }, + ) + elif request.method == "POST": + form = AddSupplyForm(request.POST) + if form.is_valid(): + inventory_entry.initial_quantity += int(request.POST["quantity"]) + inventory_entry.quantity += int(request.POST["quantity"]) + inventory_entry.save() + return_response = redirect("main:formulary_home_view") else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") + return_response = render( + request, + "formulary/edit_add_supply.html", + { + "page_name": inventory_entry, + "form": form, + "item_id": inventory_entry.id, + }, + ) return return_response +@silk_profile("edit-sub-supply-view") +@is_authenticated +@is_admin def edit_sub_supply_view(request, entry_id=None): - if request.user.is_authenticated: - if check_admin_permission(request.user): - inventory_entry = InventoryEntry.objects.get(pk=entry_id) - if request.method == "GET": - form = RemoveSupplyForm() - return_response = render( - request, - "formulary/edit_sub_supply.html", - { - "page_name": inventory_entry, - "form": form, - "item_id": inventory_entry.id, - }, - ) - elif request.method == "POST": - form = AddSupplyForm(request.POST) - if form.is_valid(): - if inventory_entry.initial_quantity > int( - request.POST["quantity"] - ) and inventory_entry.quantity > int(request.POST["quantity"]): - inventory_entry.initial_quantity -= int( - request.POST["quantity"] - ) - inventory_entry.quantity -= int(request.POST["quantity"]) - else: - inventory_entry.initial_quantity = 0 - inventory_entry.quantity = 0 - inventory_entry.save() - return_response = redirect("main:formulary_home_view") - else: - return_response = render( - request, - "formulary/edit_add_supply.html", - { - "page_name": inventory_entry, - "form": form, - "item_id": inventory_entry.id, - }, - ) + inventory_entry = InventoryEntry.objects.get(pk=entry_id) + if request.method == "GET": + form = RemoveSupplyForm() + return_response = render( + request, + "formulary/edit_sub_supply.html", + { + "page_name": inventory_entry, + "form": form, + "item_id": inventory_entry.id, + }, + ) + elif request.method == "POST": + form = AddSupplyForm(request.POST) + if form.is_valid(): + if inventory_entry.initial_quantity > int( + request.POST["quantity"] + ) and inventory_entry.quantity > int(request.POST["quantity"]): + inventory_entry.initial_quantity -= int(request.POST["quantity"]) + inventory_entry.quantity -= int(request.POST["quantity"]) + else: + inventory_entry.initial_quantity = 0 + inventory_entry.quantity = 0 + inventory_entry.save() + return_response = redirect("main:formulary_home_view") else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") + return_response = render( + request, + "formulary/edit_add_supply.html", + { + "page_name": inventory_entry, + "form": form, + "item_id": inventory_entry.id, + }, + ) return return_response +@silk_profile("csv-handler-view") +@is_authenticated +@is_admin def csv_handler_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - return_response = render( - request, "formulary/csv_handler.html", {"form": CSVUploadForm()} - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + return render(request, "formulary/csv_handler.html", {"form": CSVUploadForm()}) +@silk_profile("csv-import-view") +@is_authenticated +@is_admin def csv_import_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - campaign = Campaign.objects.get(name=request.user.current_campaign) - form = CSVUploadForm(request.POST, request.FILES) - result = "" - if form.is_valid(): - upload = form.save() - upload.save() - - upload_file = upload.document - if not upload_file: - return_response = render( - request, - "formulary/csv_handler.html", - {"form": form, "result": "File failed to upload."}, - ) - else: - if upload.mode_option == "1": - result = InitialInventoryHandler().read(upload_file, campaign) - elif upload.mode_option == "2": - result = AddedInventoryHandler().read(upload_file, campaign) - upload.document.delete() - upload.delete() - return_response = render( - request, - "formulary/csv_import.html", - {"result": result}, - ) - else: - return_response = render( - request, - "formulary/csv_handler.html", - {"form": form, "result": result}, - ) + campaign = Campaign.objects.get(name=request.user.current_campaign) + form = CSVUploadForm(request.POST, request.FILES) + result = "" + if form.is_valid(): + upload = form.save() + upload.save() + + upload_file = upload.document + if not upload_file: + return_response = render( + request, + "formulary/csv_handler.html", + {"form": form, "result": "File failed to upload."}, + ) else: - return_response = redirect("main:permission_denied") + if upload.mode_option == "1": + result = InitialInventoryHandler().read(upload_file, campaign) + elif upload.mode_option == "2": + result = AddedInventoryHandler().read(upload_file, campaign) + upload.document.delete() + upload.delete() + return_response = render( + request, + "formulary/csv_import.html", + {"result": result}, + ) else: - return_response = redirect("main:not_logged_in") + return_response = render( + request, + "formulary/csv_handler.html", + {"form": form, "result": result}, + ) return return_response +@silk_profile("csv-export-view") +@is_authenticated +@is_admin def csv_export_view(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - campaign = Campaign.objects.get(name=request.user.current_campaign) - formulary = campaign.inventory.entries.all().order_by("medication") - return_response = HttpResponse( - content_type="text/csv", - headers={"Content-Disposition": 'attachment; filename="formulary.csv"'}, - ) - handler = InitialInventoryHandler() - return_response = handler.write(return_response, formulary) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + campaign = Campaign.objects.get(name=request.user.current_campaign) + formulary = campaign.inventory.entries.all().order_by("medication") + return_response = HttpResponse( + content_type="text/csv", + headers={"Content-Disposition": 'attachment; filename="formulary.csv"'}, + ) + handler = InitialInventoryHandler() + return handler.write(return_response, formulary) diff --git a/main/list_views.py b/main/list_views.py index 1d6459f3..5bca85d7 100644 --- a/main/list_views.py +++ b/main/list_views.py @@ -6,17 +6,17 @@ If one is not found, they will direct to the appropriate error page. """ -import itertools from datetime import datetime, timedelta from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q -from django.shortcuts import render, redirect +from django.shortcuts import render from django.core.paginator import Paginator from django.utils import timezone from silk.profiling.profiler import silk_profile from main.csvio.patient_csv_export import run_patient_csv_export +from main.decorators import is_authenticated from .models import ( ChiefComplaint, @@ -27,12 +27,10 @@ @silk_profile("get-latest-timestamp") def get_latest_timestamp(patient): - try: - return patient.patientencounter_set.all().order_by("-timestamp")[0].timestamp - except IndexError: - return patient.timestamp + return patient.timestamp +@is_authenticated @silk_profile("patient_list_view") def patient_list_view(request): """ @@ -41,106 +39,86 @@ def patient_list_view(request): :param request: Django Request object. :return: HTTPResponse. """ - if request.user.is_authenticated: - try: - patients = Patient.objects.filter( - campaign=Campaign.objects.get(name=request.user.current_campaign) - ) - now = timezone.make_aware(datetime.today(), timezone.get_default_timezone()) - now = now.astimezone(timezone.get_current_timezone()) - data = set( - list( - itertools.chain( - patients.filter(patientencounter__timestamp__date=now), - patients.filter(timestamp__date=now), - ) - ) - ) - except ObjectDoesNotExist: - data = [] - data = sorted(data, reverse=True, key=get_latest_timestamp) - paginator = Paginator(data, 25) - page_number = request.GET.get("page") - page_obj = paginator.get_page(page_number) - return_response = render( - request, - "list/patient.html", - { - "user": request.user, - "page_obj": page_obj, - "page_name": "Manager", - # pylint: disable=C0301 - "page_tip": "This provides an overview of all patients in a campaign or location seen that day, week, month, etc. Campaign is listed at the top of the page.", - }, - ) - else: - return_response = redirect("main:not_logged_in") - return return_response - - -def patient_csv_export_view(request): + try: + now = timezone.make_aware(datetime.today(), timezone.get_default_timezone()) + now = now.astimezone(timezone.get_current_timezone()) + data = Patient.objects.filter( + (Q(patientencounter__timestamp__date=now) | Q(timestamp__date=now)) + & Q(campaign=Campaign.objects.get(name=request.user.current_campaign)) + ).order_by("-timestamp") + except ObjectDoesNotExist: + data = [] + paginator = Paginator(data, 10) + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) + return render( + request, + "list/patient.html", + { + "user": request.user, + "page_obj": page_obj, + "page_name": "Manager", + # pylint: disable=C0301 + "page_tip": "This provides an overview of all patients in a campaign or location seen that day, week, month, etc. Campaign is listed at the top of the page.", + }, + ) + + +@is_authenticated +def patient_csv_export_view(request, timeframe=1): """ CSV Export of an Administrative/Clinician list of patients entered into the system. :param request: Django Request object. :return: HTTPResponse. """ - if request.user.is_authenticated: - return_response = run_patient_csv_export(request) - else: - return_response = redirect("main:not_logged_in") - return return_response + return run_patient_csv_export(request, timeframe) @silk_profile("--run-patient-list-filter-one") -def __run_patient_list_filter_one(request, campaign): +def __run_patient_list_filter_one(_, campaign): now = timezone.make_aware(datetime.today(), timezone.get_default_timezone()) now = now.astimezone(timezone.get_current_timezone()) data = Patient.objects.filter( Q(campaign=campaign) & (Q(patientencounter__timestamp__date=now) | Q(timestamp__date=now)) - ).distinct() + ).order_by("-timestamp") return data +@silk_profile("--run_timestamp_filter") +def __run_timestamp_filter(campaign, timestamp_to, timestamp_from): + return ( + Patient.objects.filter( + Q(campaign=campaign) + & ( + Q( + patientencounter__timestamp__gte=timestamp_from, + patientencounter__timestamp__lt=timestamp_to, + ) + | Q( + timestamp__gte=timestamp_from, + timestamp__lt=timestamp_to, + ) + ) + ) + .order_by("-timestamp") + .distinct() + ) + + @silk_profile("--run-patient-list-filter-two") -def __run_patient_list_filter_two(request, campaign): +def __run_patient_list_filter_two(_, campaign): timestamp_from = timezone.now() - timedelta(days=7) timestamp_to = timezone.now() - data = Patient.objects.filter( - Q(campaign=campaign) - & ( - Q( - patientencounter__timestamp__gte=timestamp_from, - patientencounter__timestamp__lt=timestamp_to, - ) - | Q( - timestamp__gte=timestamp_from, - timestamp__lt=timestamp_to, - ) - ) - ).distinct() - return data + return __run_timestamp_filter(campaign, timestamp_to, timestamp_from) @silk_profile("--run-patient-list-filter-three") -def __run_patient_list_filter_three(request, campaign): +def __run_patient_list_filter_three(_, campaign): timestamp_from = timezone.now() - timedelta(days=30) timestamp_to = timezone.now() - data = Patient.objects.filter( - Q(campaign=campaign) - & ( - Q( - patientencounter__timestamp__gte=timestamp_from, - patientencounter__timestamp__lt=timestamp_to, - ) - | Q( - timestamp__gte=timestamp_from, - timestamp__lt=timestamp_to, - ) - ) - ).distinct() - return data + return __run_timestamp_filter(campaign, timestamp_to, timestamp_from) @silk_profile("--run-patient-list-filter-four") @@ -152,19 +130,7 @@ def __run_patient_list_filter_four(request, campaign): timestamp_to = datetime.strptime( request.GET["date_filter_day"], "%Y-%m-%d" ).replace(hour=23, minute=59, second=59, microsecond=0) - data = Patient.objects.filter( - Q(campaign=campaign) - & ( - Q( - patientencounter__timestamp__gte=timestamp_from, - patientencounter__timestamp__lt=timestamp_to, - ) - | Q( - timestamp__gte=timestamp_from, - timestamp__lt=timestamp_to, - ) - ) - ).distinct() + data = __run_timestamp_filter(campaign, timestamp_to, timestamp_from) except ValueError: data = [] return data @@ -177,19 +143,7 @@ def __run_patient_list_filter_five(request, campaign): timestamp_to = datetime.strptime( request.GET["date_filter_end"], "%Y-%m-%d" ) + timedelta(days=1) - data = Patient.objects.filter( - Q(campaign=campaign) - & ( - Q( - patientencounter__timestamp__gte=timestamp_from, - patientencounter__timestamp__lt=timestamp_to, - ) - | Q( - timestamp__gte=timestamp_from, - timestamp__lt=timestamp_to, - ) - ), - ).distinct() + data = __run_timestamp_filter(campaign, timestamp_to, timestamp_from) except ValueError: data = [] return data @@ -211,16 +165,19 @@ def __run_patient_list_filter(request): data = __run_patient_list_filter_five(request, current_campaign) elif request.GET["filter_list"] == "6": try: - data = Patient.objects.filter(campaign=current_campaign) + data = Patient.objects.filter(campaign=current_campaign).order_by( + "-timestamp" + ) except ValueError: data = [] else: data = [] except ObjectDoesNotExist: data = [] - return data + return list(data) +@is_authenticated @silk_profile("filter-patient-list-view") def filter_patient_list_view(request): """ @@ -229,32 +186,28 @@ def filter_patient_list_view(request): :param request: Django Request object. :return: HTTPResponse. """ - if request.user.is_authenticated: - data = __run_patient_list_filter(request) - data = sorted(data, reverse=True, key=get_latest_timestamp) - paginator = Paginator(data, 25) - page_number = request.GET.get("page") - page_obj = paginator.get_page(page_number) - return_response = render( - request, - "list/patient_filter.html", - { - "user": request.user, - "page_name": "Manager", - "page_obj": page_obj, - "selected": int(request.GET["filter_list"]), - "filter_day": request.GET["date_filter_day"], - "filter_start": request.GET["date_filter_start"], - "filter_end": request.GET["date_filter_end"], - # pylint: disable=C0301 - "page_tip": "This provides an overview of all patients in a campaign or location seen that day, week, month, etc. Campaign is listed at the top of the page.", - }, - ) - else: - return_response = redirect("main:not_logged_in") - return return_response - - + data = __run_patient_list_filter(request) + paginator = Paginator(data, 10) + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) + return render( + request, + "list/patient_filter.html", + { + "user": request.user, + "page_name": "Manager", + "page_obj": page_obj, + "selected": int(request.GET["filter_list"]), + "filter_day": request.GET["date_filter_day"], + "filter_start": request.GET["date_filter_start"], + "filter_end": request.GET["date_filter_end"], + # pylint: disable=C0301 + "page_tip": "This provides an overview of all patients in a campaign or location seen that day, week, month, etc. Campaign is listed at the top of the page.", + }, + ) + + +@is_authenticated @silk_profile("search-patient-list-view") def search_patient_list_view(request): """ @@ -263,57 +216,47 @@ def search_patient_list_view(request): :param request: Django Request object. :return: HTTPResponse. """ - if request.user.is_authenticated: - try: - current_campaign = Campaign.objects.get(name=request.user.current_campaign) - patients = Patient.objects.filter(campaign=current_campaign) - data = None - for term in request.GET["name_search"].split(): - data = set( - list( - itertools.chain( - patients.filter( - Q(campaign_key__icontains=request.GET["name_search"]) - | Q(first_name__icontains=request.GET["name_search"]) - | Q(last_name__icontains=request.GET["name_search"]) - | Q(phone_number__icontains=request.GET["name_search"]) - | Q( - phone_number__icontains=__parse_phone_number( - request.GET["name_search"] - ) - ) - | Q(email_address__iexact=request.GET["name_search"]) - ), - patients.filter( - Q(first_name__icontains=term) - | Q(last_name__icontains=term) - ), - ) + try: + current_campaign = Campaign.objects.get(name=request.user.current_campaign) + patients = Patient.objects.filter(campaign=current_campaign) + data = None + break_search = Q() + for term in request.GET["name_search"].split(): + break_search |= Q(first_name__icontains=term) | Q(last_name__icontains=term) + data = patients.filter( + ( + Q(campaign_key__icontains=request.GET["name_search"]) + | Q(first_name__icontains=request.GET["name_search"]) + | Q(last_name__icontains=request.GET["name_search"]) + | Q(phone_number__icontains=request.GET["name_search"]) + | Q( + phone_number__icontains=__parse_phone_number( + request.GET["name_search"] ) ) - data = data if data is not None else [] - except ObjectDoesNotExist: - data = [] - data = sorted(data, reverse=True, key=get_latest_timestamp) - paginator = Paginator(data, 25) - page_number = request.GET.get("page") - page_obj = paginator.get_page(page_number) - return_response = render( - request, - "list/patient_search.html", - { - "user": request.user, - "page_obj": page_obj, - "name_search": request.GET.get("name_search") - if request.GET.get("name_search") is not None - else "", - # pylint: disable=C0301 - "page_tip": "This provides an overview of all patients in a campaign or location seen that day, week, month, etc. Campaign is listed at the top of the page.", - }, - ) - else: - return_response = redirect("main:not_logged_in") - return return_response + | Q(email_address__iexact=request.GET["name_search"]) + | break_search + ) + ).order_by("-timestamp") + data = data if data is not None else [] + except ObjectDoesNotExist: + data = [] + paginator = Paginator(data, 10) + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) + return render( + request, + "list/patient_search.html", + { + "user": request.user, + "page_obj": page_obj, + "name_search": request.GET.get("name_search") + if request.GET.get("name_search") is not None + else "", + # pylint: disable=C0301 + "page_tip": "This provides an overview of all patients in a campaign or location seen that day, week, month, etc. Campaign is listed at the top of the page.", + }, + ) def __parse_phone_number(input_string): @@ -326,18 +269,15 @@ def __parse_phone_number(input_string): return return_response +@is_authenticated def chief_complaint_list_view(request, patient_id=None, encounter_id=None): - if request.user.is_authenticated: - return_response = render( - request, - "list/chief_complaint.html", - { - "list_view": ChiefComplaint.objects.filter(active=True), - "patient_id": patient_id, - "encounter_id": encounter_id, - "new": (encounter_id is None), - }, - ) - else: - return_response = redirect("main:not_logged_in") - return return_response + return render( + request, + "list/chief_complaint.html", + { + "list_view": ChiefComplaint.objects.filter(active=True), + "patient_id": patient_id, + "encounter_id": encounter_id, + "new": (encounter_id is None), + }, + ) diff --git a/main/management/commands/createadmin.py b/main/management/commands/createadmin.py index 33d9c910..0ebf06b2 100644 --- a/main/management/commands/createadmin.py +++ b/main/management/commands/createadmin.py @@ -56,10 +56,11 @@ def handle(self, *args, **options): Group.objects.get(name="Clinician").user_set.add(superuser) Group.objects.get(name="Operation Admin").user_set.add(superuser) Group.objects.get(name="Campaign Manager").user_set.add(superuser) + Group.objects.get(name="Developer").user_set.add(superuser) instance.main_contact = superuser instance.save() else: - for user in fEMRUser.objects.all(): + for user in fEMRUser.objects.all().iterator(): user.user_permissions.add(Permission.objects.get(name="Can add state")) user.user_permissions.add( Permission.objects.get(name="Can add diagnosis") @@ -70,3 +71,15 @@ def handle(self, *args, **options): user.user_permissions.add( Permission.objects.get(name="Can add medication") ) + user.user_permissions.add( + Permission.objects.get(name="Can add administration schedule") + ) + user.user_permissions.add( + Permission.objects.get(name="Can add inventory category") + ) + user.user_permissions.add( + Permission.objects.get(name="Can add inventory form") + ) + user.user_permissions.add( + Permission.objects.get(name="Can add manufacturer") + ) diff --git a/main/management/commands/creategroups.py b/main/management/commands/creategroups.py index bb7abbf1..5f042133 100644 --- a/main/management/commands/creategroups.py +++ b/main/management/commands/creategroups.py @@ -11,6 +11,7 @@ "Clinician", "Operation Admin", "Campaign Manager", + "Developer", ] diff --git a/main/management/commands/scaledata.py b/main/management/commands/scaledata.py index 1d3dfc4b..e63db210 100644 --- a/main/management/commands/scaledata.py +++ b/main/management/commands/scaledata.py @@ -4,7 +4,7 @@ from django.core.management.base import BaseCommand from model_bakery import baker -from main.models import Campaign, Patient +from main.models import Campaign class Command(BaseCommand): @@ -20,13 +20,12 @@ def handle(self, *args, **options): @param options: @return: """ - campaign = Campaign.objects.get_or_create("Test")[0] - if Patient.objects.filter(campaign=campaign).count() == 0: - for _ in range(1000): - patient = baker.make("main.Patient") - patient.campaign.add(campaign) - for _ in range(10): - encounter = baker.make("main.PatientEncounter") - encounter.patient = patient - encounter.campaign = campaign - encounter.save() + campaign = Campaign.objects.get_or_create(name="Test")[0] + for _ in range(1000): + patient = baker.make("main.Patient") + patient.campaign.add(campaign) + for _ in range(10): + encounter = baker.make("main.PatientEncounter") + encounter.patient = patient + encounter.campaign = campaign + encounter.save() diff --git a/main/middleware.py b/main/middleware.py index 8ee8a6e7..5fc10780 100644 --- a/main/middleware.py +++ b/main/middleware.py @@ -71,13 +71,13 @@ def __call__(self, request): return_response = self.get_response(request) return return_response - def __check_valid_campaign(self, user): + @staticmethod + def __check_valid_campaign(user): campaigns = user.campaigns.filter(active=True) if user.current_campaign != "RECOVERY_MODE": try: campaigns.get(name=user.current_campaign) except Campaign.DoesNotExist: - print(f"Campaign {user.current_campaign} _not_ valid.") if len(campaigns) != 0: user.current_campaign = campaigns[0].name user.save() diff --git a/main/migrations/0016_auto_20220419_1853.py b/main/migrations/0016_auto_20220419_1853.py new file mode 100644 index 00000000..99913c4f --- /dev/null +++ b/main/migrations/0016_auto_20220419_1853.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.12 on 2022-04-19 22:53 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0015_femruser_current_campaign"), + ] + + operations = [ + migrations.AddField( + model_name="csvexport", + name="campaign", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="main.campaign", + ), + ), + migrations.AddField( + model_name="csvexport", + name="timestamp", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/main/migrations/0017_auditentry_system_user_agent.py b/main/migrations/0017_auditentry_system_user_agent.py new file mode 100644 index 00000000..9b849588 --- /dev/null +++ b/main/migrations/0017_auditentry_system_user_agent.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.13 on 2022-05-01 02:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("main", "0016_auto_20220419_1853"), + ] + + operations = [ + migrations.AddField( + model_name="auditentry", + name="system_user_agent", + field=models.CharField(max_length=256, null=True), + ), + ] diff --git a/main/models.py b/main/models.py index 73aec514..91bed80b 100644 --- a/main/models.py +++ b/main/models.py @@ -272,9 +272,7 @@ class Patient(models.Model): email_address = models.CharField(max_length=40, null=True, blank=True) shared_email_address = models.BooleanField() - timestamp = models.DateTimeField( - auto_now=True, editable=False, null=False, blank=False - ) + timestamp = models.DateTimeField(auto_now=True, null=False, blank=False) campaign = models.ManyToManyField(Campaign, default=1) @@ -371,7 +369,11 @@ class CSVExport(models.Model): user = models.ForeignKey( "fEMRUser", on_delete=models.CASCADE, blank=True, null=True ) + campaign = models.ForeignKey( + "Campaign", on_delete=models.CASCADE, blank=True, null=True + ) file = models.FileField(upload_to="export/") + timestamp = models.DateTimeField(auto_now=True, editable=False) class PatientEncounter(models.Model): @@ -665,6 +667,7 @@ class AuditEntry(models.Model): Campaign, on_delete=models.CASCADE, blank=True, null=True ) browser_user_agent = models.CharField(max_length=256, null=True) + system_user_agent = models.CharField(max_length=256, null=True) def __str__(self): return f"{self.action} - {self.username} - {self.ip} - {self.timestamp} - {self.campaign}" diff --git a/main/operation_admin_views.py b/main/operation_admin_views.py index ba289e81..e26fb6e4 100644 --- a/main/operation_admin_views.py +++ b/main/operation_admin_views.py @@ -1,31 +1,25 @@ -from django.db.models.query_utils import Q -from django.shortcuts import render, redirect +from django.shortcuts import render + +from main.decorators import is_authenticated, is_op_admin from .models import Instance +@is_op_admin +@is_authenticated def operation_admin_home_view(request): - if request.user.is_authenticated: - if request.user.groups.filter( - Q(name="Operation Admin") | Q(name="Organization Admin") - ).exists(): - try: - active_instances = Instance.objects.filter(active=True).filter( - admins=request.user - ) - except Instance.DoesNotExist: - active_instances = [] - return_response = render( - request, - "operation_admin/home.html", - { - "user": request.user, - "active_instances": active_instances, - "page_name": "Your Operations", - }, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + try: + active_instances = Instance.objects.filter(active=True).filter( + admins=request.user + ) + except Instance.DoesNotExist: + active_instances = [] + return render( + request, + "operation_admin/home.html", + { + "user": request.user, + "active_instances": active_instances, + "page_name": "Your Operations", + }, + ) diff --git a/main/organization_admin_views.py b/main/organization_admin_views.py index e1e6e6d7..e2b77a67 100644 --- a/main/organization_admin_views.py +++ b/main/organization_admin_views.py @@ -1,39 +1,29 @@ -from django.shortcuts import redirect, render +from django.shortcuts import render +from main.decorators import is_authenticated, is_org_admin from main.models import Campaign, Instance +@is_org_admin +@is_authenticated def organization_admin_home_view(request): - if request.user.is_authenticated: - if request.user.groups.filter(name="Organization Admin").exists(): - org = Campaign.objects.get( - name=request.user.current_campaign - ).instance.organization - instances = [] - campaigns = [] - try: - instances = Instance.objects.filter(active=True).filter( - admins=request.user - ) - campaigns = Campaign.objects.filter(active=True).filter( - admins=request.user - ) - except Instance.DoesNotExist: - pass - except Campaign.DoesNotExist: - pass - return_response = render( - request, - "organization_admin/home.html", - { - "user": request.user, - "instances": instances, - "campaigns": campaigns, - "page_name": f"Organization: {org.name}", - }, - ) - else: - return_response = redirect("main:permission_denied") - else: - return_response = redirect("main:not_logged_in") - return return_response + org = Campaign.objects.get(name=request.user.current_campaign).instance.organization + instances = [] + campaigns = [] + try: + instances = Instance.objects.filter(active=True).filter(admins=request.user) + campaigns = Campaign.objects.filter(active=True).filter(admins=request.user) + except Instance.DoesNotExist: + pass + except Campaign.DoesNotExist: + pass + return render( + request, + "organization_admin/home.html", + { + "user": request.user, + "instances": instances, + "campaigns": campaigns, + "page_name": f"Organization: {org.name}", + }, + ) diff --git a/main/patient_export.py b/main/patient_export.py index 6f20e0a4..4039e62e 100644 --- a/main/patient_export.py +++ b/main/patient_export.py @@ -1,5 +1,6 @@ -from django.shortcuts import render, redirect, get_object_or_404 +from django.shortcuts import render, get_object_or_404 from silk.profiling.profiler import silk_profile +from main.decorators import in_recovery_mode, is_authenticated from main.models import ( Campaign, @@ -33,8 +34,8 @@ def __patient_export_view_get(request, patient_id=None): history_of_present_illness_dictionary[encounter] = list( HistoryOfPresentIllness.objects.filter(encounter=encounter) ) - prescriptions[encounter] = list(Treatment.objects.filter(encounter=encounter)) - vitals_dictionary[encounter] = list(Vitals.objects.filter(encounter=encounter)) + prescriptions[encounter] = Treatment.objects.filter(encounter=encounter) + vitals_dictionary[encounter] = Vitals.objects.filter(encounter=encounter) return render( request, "export/patient_export.html", @@ -53,13 +54,8 @@ def __patient_export_view_get(request, patient_id=None): ) +@is_authenticated +@in_recovery_mode @silk_profile("patient-export-view") def patient_export_view(request, patient_id=None): - if request.user.is_authenticated: - if request.user.current_campaign == "RECOVERY MODE": - return_response = redirect("main:home") - else: - return_response = __patient_export_view_get(request, patient_id) - else: - return_response = redirect("/not_logged_in") - return return_response + return __patient_export_view_get(request, patient_id) diff --git a/main/photo_handler.py b/main/photo_handler.py index 12a233ac..618101b7 100644 --- a/main/photo_handler.py +++ b/main/photo_handler.py @@ -1,7 +1,8 @@ import os -from django.shortcuts import render, redirect, get_object_or_404 +from django.shortcuts import render, get_object_or_404 from silk.profiling.profiler import silk_profile +from main.decorators import in_recovery_mode, is_authenticated from main.serializers import PatientEncounterSerializer from main.femr_admin_views import get_client_ip @@ -21,42 +22,37 @@ ) +@is_authenticated +@in_recovery_mode @silk_profile("upload-photo-view") def upload_photo_view(request, patient_id=None, encounter_id=None): - if request.user.is_authenticated: - if request.user.current_campaign == "RECOVERY MODE": - return_response = redirect("main:home") - else: - units = Campaign.objects.get(name=request.user.current_campaign).units - encounter = get_object_or_404(PatientEncounter, pk=encounter_id) - patient = get_object_or_404(Patient, pk=patient_id) - vitals = Vitals.objects.filter(encounter=encounter) - treatments = Treatment.objects.filter(encounter=encounter) - aux_form = PhotoForm() - if request.method == "POST": - aux_form = __upload_photo_view_post(request, encounter) - vitals_form = VitalsForm(unit=units) - suffix = patient.get_suffix_display() if patient.suffix is not None else "" - return_response = render( - request, - "forms/photos_tab.html", - { - "aux_form": aux_form, - "vitals": vitals, - "treatments": treatments, - "vitals_form": vitals_form, - "page_name": f"Edit Encounter for {patient.first_name} {patient.last_name} {suffix}", - "encounter": encounter, - "birth_sex": patient.sex_assigned_at_birth, - "encounter_id": encounter_id, - "patient_name": f"{patient.first_name} {patient.last_name} {suffix}", - "units": units, - "patient": patient, - }, - ) - else: - return_response = redirect("/not_logged_in") - return return_response + units = Campaign.objects.get(name=request.user.current_campaign).units + encounter = get_object_or_404(PatientEncounter, pk=encounter_id) + patient = get_object_or_404(Patient, pk=patient_id) + vitals = Vitals.objects.filter(encounter=encounter) + treatments = Treatment.objects.filter(encounter=encounter) + aux_form = PhotoForm() + if request.method == "POST": + aux_form = __upload_photo_view_post(request, encounter) + vitals_form = VitalsForm(unit=units) + suffix = patient.get_suffix_display() if patient.suffix is not None else "" + return render( + request, + "forms/photos_tab.html", + { + "aux_form": aux_form, + "vitals": vitals, + "treatments": treatments, + "vitals_form": vitals_form, + "page_name": f"Edit Encounter for {patient.first_name} {patient.last_name} {suffix}", + "encounter": encounter, + "birth_sex": patient.sex_assigned_at_birth, + "encounter_id": encounter_id, + "patient_name": f"{patient.first_name} {patient.last_name} {suffix}", + "units": units, + "patient": patient, + }, + ) def __upload_photo_view_post(request, encounter): @@ -126,29 +122,26 @@ def __edit_photo_view_post(request, patient_id, encounter_id, photo_id): ) +@is_authenticated +@in_recovery_mode @silk_profile("edit-profile-view") def edit_photo_view(request, patient_id=None, encounter_id=None, photo_id=None): - if request.user.is_authenticated: - if request.user.current_campaign == "RECOVERY MODE": - return_response = redirect("main:home") - if request.method == "POST": - return_response = __edit_photo_view_post( - request, patient_id, encounter_id, photo_id - ) - else: - photo = Photo.objects.get(pk=photo_id) - aux_form = PhotoForm(instance=photo) - return_response = render( - request, - "forms/edit_photo.html", - { - "page_name": "Edit Photo", - "aux_form": aux_form, - "encounter_id": encounter_id, - "patient_id": patient_id, - "photo_id": photo_id, - }, - ) + if request.method == "POST": + return_response = __edit_photo_view_post( + request, patient_id, encounter_id, photo_id + ) else: - return_response = redirect("/not_logged_in") + photo = Photo.objects.get(pk=photo_id) + aux_form = PhotoForm(instance=photo) + return_response = render( + request, + "forms/edit_photo.html", + { + "page_name": "Edit Photo", + "aux_form": aux_form, + "encounter_id": encounter_id, + "patient_id": patient_id, + "photo_id": photo_id, + }, + ) return return_response diff --git a/main/signals.py b/main/signals.py index baf59900..0f348723 100644 --- a/main/signals.py +++ b/main/signals.py @@ -26,6 +26,7 @@ def user_logged_in_callback(sender, request, user, **kwargs): username=user.username, campaign=campaign, browser_user_agent=request.user_agent.browser.family, + system_user_agent=request.user_agent.os.family, ) @@ -44,6 +45,7 @@ def user_logged_out_callback(sender, request, user, **kwargs): username=user.username, campaign=campaign, browser_user_agent=request.user_agent.browser.family, + system_user_agent=request.user_agent.os.family, ) except AttributeError: pass @@ -71,7 +73,7 @@ def create_auth_token(sender, instance=None, created=False, **kwargs): @receiver(ticket_activity) def handle_ticket_activity(sender, ticket, **kwargs): ticket = SupportTicket.objects.get(pk=ticket) - for user in Group.objects.get(name="Developer").user_set.all(): + for user in Group.objects.get(name="Developer").user_set.all().iterator(): Message.objects.create( subject="Ticket Update", content=f"This message is to let you know that an update was posted to ticket {ticket.id}. Use the Let Us Know link to view the new information.", diff --git a/main/small_forms_views.py b/main/small_forms_views.py index 6f05e985..21963a70 100644 --- a/main/small_forms_views.py +++ b/main/small_forms_views.py @@ -1,83 +1,73 @@ from django.shortcuts import render, redirect +from main.decorators import in_recovery_mode, is_authenticated + from .forms import TreatmentForm, MedicationForm, DiagnosisForm, ChiefComplaintForm +@in_recovery_mode +@is_authenticated def chief_complaint_form_view(request): - if request.user.is_authenticated: - if request.user.current_campaign == "RECOVERY MODE": - return_response = redirect("main:home") - elif request.method == "POST": - form = ChiefComplaintForm(request.POST) - if form.is_valid(): - form.save() - return_response = redirect("main:patient_list_view") - else: - return_response = render(request, "forms/generic.html", {"form": form}) + if request.method == "POST": + form = ChiefComplaintForm(request.POST) + if form.is_valid(): + form.save() + return_response = redirect("main:patient_list_view") else: - return_response = render( - request, "forms/generic.html", {"form": ChiefComplaintForm()} - ) + return_response = render(request, "forms/generic.html", {"form": form}) else: - return_response = redirect("/not_logged_in") + return_response = render( + request, "forms/generic.html", {"form": ChiefComplaintForm()} + ) return return_response +@in_recovery_mode +@is_authenticated def treatment_form_view(request): - if request.user.is_authenticated: - if request.user.current_campaign == "RECOVERY MODE": - return_response = redirect("main:home") - elif request.method == "POST": - form = TreatmentForm(request.POST) - if form.is_valid(): - form.save() - return_response = redirect("main:patient_list_view") - else: - return_response = render(request, "forms/generic.html", {"form": form}) + if request.method == "POST": + form = TreatmentForm(request.POST) + if form.is_valid(): + form.save() + return_response = redirect("main:patient_list_view") else: - return_response = render( - request, "forms/generic.html", {"form": TreatmentForm()} - ) + return_response = render(request, "forms/generic.html", {"form": form}) else: - return_response = redirect("/not_logged_in") + return_response = render( + request, "forms/generic.html", {"form": TreatmentForm()} + ) return return_response +@in_recovery_mode +@is_authenticated def diagnosis_form_view(request): - if request.user.is_authenticated: - if request.user.current_campaign == "RECOVERY MODE": - return_response = redirect("main:home") - elif request.method == "POST": - form = DiagnosisForm(request.POST) - if form.is_valid(): - form.save() - return_response = redirect("main:patient_list_view") - else: - return_response = render(request, "forms/generic.html", {"form": form}) + if request.method == "POST": + form = DiagnosisForm(request.POST) + if form.is_valid(): + form.save() + return_response = redirect("main:patient_list_view") else: - return_response = render( - request, "forms/generic.html", {"form": DiagnosisForm()} - ) + return_response = render(request, "forms/generic.html", {"form": form}) else: - return_response = redirect("/not_logged_in") + return_response = render( + request, "forms/generic.html", {"form": DiagnosisForm()} + ) return return_response +@in_recovery_mode +@is_authenticated def medication_form_view(request): - if request.user.is_authenticated: - if request.user.current_campaign == "RECOVERY MODE": - return_response = redirect("main:home") - elif request.method == "POST": - form = MedicationForm(request.POST) - if form.is_valid(): - form.save() - return_response = redirect("main:patient_list_view") - else: - return_response = render(request, "forms/generic.html", {"form": form}) + if request.method == "POST": + form = MedicationForm(request.POST) + if form.is_valid(): + form.save() + return_response = redirect("main:patient_list_view") else: - return_response = render( - request, "forms/generic.html", {"form": MedicationForm()} - ) + return_response = render(request, "forms/generic.html", {"form": form}) else: - return_response = redirect("/not_logged_in") + return_response = render( + request, "forms/generic.html", {"form": MedicationForm()} + ) return return_response diff --git a/main/templates/admin/export_list.html b/main/templates/admin/export_list.html index 8cf2852e..983af081 100644 --- a/main/templates/admin/export_list.html +++ b/main/templates/admin/export_list.html @@ -19,6 +19,23 @@

Deidentified Patient Export

{% endfor %} + diff --git a/main/templates/admin/home.html b/main/templates/admin/home.html index 340c8520..9a29fd0c 100644 --- a/main/templates/admin/home.html +++ b/main/templates/admin/home.html @@ -29,7 +29,10 @@

Campaign Management

class="fa fa-question-circle">
- Export Deidentified Patient Encounter Data + Export Deidentified Patient Encounter Data: + All | + This Week | + This Month diff --git a/main/templates/auth/required_change_password.html b/main/templates/auth/required_change_password.html index caea9098..d3cc949a 100644 --- a/main/templates/auth/required_change_password.html +++ b/main/templates/auth/required_change_password.html @@ -75,7 +75,7 @@

{{ page_name }}

diff --git a/main/templates/dashboard/femr_admin.html b/main/templates/dashboard/femr_admin.html new file mode 100644 index 00000000..fbb891d4 --- /dev/null +++ b/main/templates/dashboard/femr_admin.html @@ -0,0 +1,53 @@ +{% extends "data/base.html" %} +{% block content %} +{% load dashboard_tags %} +{% load static %} +
+
+

Active Campaigns

+ + + + + + + + + + + + + {% for o in campaigns %} + + + + + + + + + {% endfor %} + +
Campaign NamePatients TodayTotal PatientsEncounters TodayTotal Encounters
{{ o.id }}{{ o.name }}{{ o|patients_today }}{{ o|total_patients }}{{ o|encounters_today }}{{ o|total_encounters }}
+
+
+

Client Details

+ + + + + + + + + {% for key, value in browsers.items %} + + + + + {% endfor %} + +
User AgentUser Login Count
{{ key }}{{ value }}
+
+
+{% endblock %} \ No newline at end of file diff --git a/main/templates/data/base.html b/main/templates/data/base.html index e7ea3d09..b44d48f8 100644 --- a/main/templates/data/base.html +++ b/main/templates/data/base.html @@ -100,6 +100,8 @@ {% if request.user|has_group:"fEMR Admin" %} fEMR Admin + fEMR Metrics Dashboard + {% endif %} {% if request.user|has_group:"Organization Admin" %} Organization @@ -114,6 +116,12 @@ Campaign Manager {% endif %} + {% if request.user|has_group:"Developer" %} + Developer Admin + + Performance Monitoring + + {% endif %} {% endif %} @@ -158,7 +166,7 @@

{{ page_name }}

Find a bug or problem? Let us know.
- v1.5.3 + v1.6.0
fEMR On-Chain Wiki | diff --git a/main/templates/data/base_login.html b/main/templates/data/base_login.html index f8028ac6..0da69a55 100644 --- a/main/templates/data/base_login.html +++ b/main/templates/data/base_login.html @@ -47,7 +47,7 @@
- v1.5.3 + v1.6.0
fEMR On-Chain Wiki | diff --git a/main/templates/data/home.html b/main/templates/data/home.html index ae5a49ae..5bc68ad4 100644 --- a/main/templates/data/home.html +++ b/main/templates/data/home.html @@ -17,7 +17,7 @@
{% endif %}
- Welcome to fEMR On-Chain v1.5.3, {{ user.first_name }} {{ user.last_name }}! + Welcome to fEMR On-Chain v1.6.0, {{ user.first_name }} {{ user.last_name }}!
Please select a tab at the top, search below, or select a campaign below to get started! diff --git a/main/templates/formulary/edit_supply.html b/main/templates/formulary/edit_supply.html new file mode 100644 index 00000000..d9c91212 --- /dev/null +++ b/main/templates/formulary/edit_supply.html @@ -0,0 +1,8 @@ +{% extends "data/base.html" %} +{% load crispy_forms_tags %} +{% block content %} +
+ {% csrf_token %} + {% crispy form %} +
+{% endblock %} \ No newline at end of file diff --git a/main/templates/formulary/formulary_submitted.html b/main/templates/formulary/formulary_submitted.html index a8fdeed6..64aca163 100644 --- a/main/templates/formulary/formulary_submitted.html +++ b/main/templates/formulary/formulary_submitted.html @@ -5,7 +5,7 @@

Medication update submitted.

-
v +
View Pharmacy
diff --git a/main/templates/formulary/home.html b/main/templates/formulary/home.html index 91326350..1943b09c 100644 --- a/main/templates/formulary/home.html +++ b/main/templates/formulary/home.html @@ -6,47 +6,76 @@ Add New Import CSV Export CSV + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - {% for o in list_view %} - - - - - - - - - - - - - - - - - - - {% endfor %} + {% for o in list_view %} + + + + + + + + + + + + + + + + + + + + {% endfor %} +
CategoryNameFormStrengthCountQuantityAmountItem #Box #Expiration DateManufacturerLast Edited
CategoryNameFormStrengthCountQuantityAmountItem #Box #Expiration DateManufacturerLast Edited
{{ o.id }}{{ o.category }}{{ o.medication }}{{ o.form }}{{ o.strength }} {{ o.strength_unit }}{{ o.count }} {{ o.count_unit }}{{ o.quantity }} {{ o.quantity_unit }}{{ o.amount }} {{ o.count_unit }}{{ o.item_number }}{{ o.box_number }}{{ o.expiration_date }}{{ o.manufacturer }}{{ o.timestamp }}AddRemoveDelete
{{ o.id }}{{ o.category }}{{ o.medication }}{{ o.form }}{{ o.strength }} {{ o.strength_unit }}{{ o.count }} {{ o.count_unit }}{{ o.quantity }} {{ o.quantity_unit }}{{ o.amount }} {{ o.count_unit }}{{ o.item_number }}{{ o.box_number }}{{ o.expiration_date }}{{ o.manufacturer }}{{ o.timestamp }}AddRemoveEditDelete
{% endblock %} \ No newline at end of file diff --git a/main/templatetags/dashboard_tags.py b/main/templatetags/dashboard_tags.py new file mode 100644 index 00000000..0cea799e --- /dev/null +++ b/main/templatetags/dashboard_tags.py @@ -0,0 +1,34 @@ +from datetime import datetime +from django import template +from django.utils import timezone +from main.models import Patient, PatientEncounter + +register = template.Library() + + +@register.filter("patients_today") +def patients_today(campaign): + now = timezone.make_aware(datetime.today(), timezone.get_default_timezone()) + now = now.astimezone(timezone.get_current_timezone()) + return Patient.objects.filter(campaign=campaign).filter(timestamp__date=now).count() + + +@register.filter("total_patients") +def total_patients(campaign): + return Patient.objects.filter(campaign=campaign).count() + + +@register.filter("encounters_today") +def encounters_today(campaign): + now = timezone.make_aware(datetime.today(), timezone.get_default_timezone()) + now = now.astimezone(timezone.get_current_timezone()) + return ( + PatientEncounter.objects.filter(campaign=campaign) + .filter(timestamp__date=now) + .count() + ) + + +@register.filter("total_encounters") +def total_encounters(campaign): + return PatientEncounter.objects.filter(campaign=campaign).count() diff --git a/main/templatetags/patient_tags.py b/main/templatetags/patient_tags.py index e4fa6b2b..7c414aa1 100644 --- a/main/templatetags/patient_tags.py +++ b/main/templatetags/patient_tags.py @@ -25,8 +25,9 @@ def has_suffix(patient): def open_encounters(patient): return ( "Yes" - if len(patient.patientencounter_set.filter(patient=patient).filter(active=True)) - > 0 + if patient.patientencounter_set.filter(patient=patient) + .filter(active=True) + .exists() else "" ) @@ -38,10 +39,7 @@ def has_middle_name(patient): @register.filter("last_timestamp") def last_timestamp(patient): - try: - return patient.patientencounter_set.order_by("-timestamp")[0].timestamp - except IndexError: - return patient.timestamp + return patient.timestamp @register.filter("mask_social") @@ -57,7 +55,7 @@ def mask_social(social): @register.filter("get_chief_complaint") def get_chief_complaint(encounter): - return ",".join([str(e) for e in encounter.chief_complaint.all()]) + return ",".join([str(e) for e in encounter.chief_complaint.all().iterator()]) @register.filter("imperial_primary_height") @@ -91,7 +89,7 @@ def imperial_temperature(item): @register.filter("complaint_as_string") def complaint_as_string(item): result = "" - for element in list(item.all()): + for element in item.all().iterator(): result += str(element) + ", " return result @@ -99,7 +97,7 @@ def complaint_as_string(item): @register.filter("get_campaign_info") def get_campaign_info(item): result = "" - for element in list(item.campaign.all()): + for element in item.campaign.all().iterator(): result += str(element) + ", " return result @@ -107,7 +105,7 @@ def get_campaign_info(item): @register.filter("get_medications") def get_medications(treatment): result = "" - for element in list(treatment.medication.all()): + for element in treatment.medication.all().iterator(): result += str(element) return result diff --git a/main/templatetags/user_tags.py b/main/templatetags/user_tags.py index f87ddcad..41f60918 100644 --- a/main/templatetags/user_tags.py +++ b/main/templatetags/user_tags.py @@ -22,15 +22,12 @@ @register.filter("has_group") def has_group(user: fEMRUser, group_name: str) -> bool: - groups = user.groups.all().values_list("name", flat=True) - return group_name in groups + return user.groups.filter(name=group_name).exists() @register.filter("has_campaign") def has_campaign(user: fEMRUser, campaign_name: str) -> bool: - campaign = Campaign.objects.get(name=campaign_name) - campaign_list = user.campaigns.all() - return campaign in campaign_list + return user.campaigns.filter(name=campaign_name).exists() @register.filter("campaign_active") @@ -40,7 +37,7 @@ def campaign_active(campaign_name: str) -> bool: @register.filter("has_any_group") def has_any_group(user: fEMRUser) -> bool: - return user.groups.all() + return user.groups.all().exists() @register.filter("has_admin_group") diff --git a/main/test/test_admin_views.py b/main/test/test_admin_views.py index 840e7f1b..f33d80e5 100644 --- a/main/test/test_admin_views.py +++ b/main/test/test_admin_views.py @@ -44,7 +44,6 @@ def test_permission_denied_account_reset(): email="logintestinguseremail2@email.com", ) u.change_password = False - Group.objects.get_or_create(name="fEMR Admin")[0].user_set.add(u) c = baker.make("main.Campaign") c.active = True c.save() @@ -237,7 +236,65 @@ def test_create_user(): assert "Changes successfully submitted." in str(return_response.content) -def test_permission_denied_account_reset(): +def test_filter_audit_logs_view(): + u = fEMRUser.objects.create_user( + username="testfilterauditlogsview", + password="testingpassword", + email="hometestinguseremail@email.com", + ) + u.change_password = False + Group.objects.get_or_create(name="Clinician")[0] + Group.objects.get_or_create(name="Campaign Manager")[0].user_set.add(u) + Group.objects.get_or_create(name="fEMR Admin")[0].user_set.add(u) + c = baker.make("main.Campaign") + c.active = True + c.save() + u.campaigns.add(c) + u.save() + client = Client() + client.post( + "/login_view/", + {"username": "testfilterauditlogsview", "password": "testingpassword"}, + ) + return_response = client.get( + path="/filter_audit_logs_view/", + data={ + "filter_list": "2", + "date_filter_day": "", + "date_filter_start": "", + "date_filter_end": "", + }, + ) + u.delete() + assert return_response.status_code == 200 + + +def test_add_users_to_campaign_view(): + u = fEMRUser.objects.create_user( + username="testadduserstocampainview", + password="testingpassword", + email="testadduserstocampainview@email.com", + ) + u.change_password = False + Group.objects.get_or_create(name="Clinician")[0] + Group.objects.get_or_create(name="Campaign Manager")[0].user_set.add(u) + Group.objects.get_or_create(name="fEMR Admin")[0].user_set.add(u) + c = baker.make("main.Campaign") + c.active = True + c.save() + u.campaigns.add(c) + u.save() + client = Client() + client.post( + "/login_view/", + {"username": "testadduserstocampainview", "password": "testingpassword"}, + ) + return_response = client.get(path="/add_users_to_campaign") + u.delete() + assert return_response.status_code == 200 + + +def test_permission_denied_account_reset_lockouts(): client = Client() return_response = client.get( reverse("main:reset_lockouts", kwargs={"username": "test2"}) diff --git a/main/test/test_edit_views.py b/main/test/test_edit_views.py index e69de29b..36137098 100644 --- a/main/test/test_edit_views.py +++ b/main/test/test_edit_views.py @@ -0,0 +1,67 @@ +from django.test.client import Client + +from model_bakery import baker + +from main.models import fEMRUser + + +def test_new_diagnosis_view(): + u = fEMRUser.objects.create_user( + username="test", + password="testingpassword", + email="logintestinguseremail@email.com", + ) + u.change_password = False + c = baker.make("main.Campaign") + u.campaigns.add(c) + u.save() + p = baker.make("main.Patient") + d = baker.make("main.Diagnosis") + e = baker.make("main.PatientEncounter", patient=p, campaign=c) + client = Client() + client.post("/login_view/", {"username": "test", "password": "testingpassword"}) + return_response = client.post( + f"/new_diagnosis_view/{p.id}/{e.id}", {"diagnosis": d.id} + ) + u.delete() + p.delete() + d.delete() + e.delete() + c.delete() + assert return_response.status_code == 200 + + +def test_new_diagnosis_view(): + u = fEMRUser.objects.create_user( + username="test", + password="testingpassword", + email="logintestinguseremail@email.com", + ) + u.change_password = False + c = baker.make("main.Campaign") + u.campaigns.add(c) + u.save() + p = baker.make("main.Patient") + d = baker.make("main.Diagnosis") + e = baker.make("main.PatientEncounter", patient=p, campaign=c) + a = baker.make("main.AdministrationSchedule") + m = baker.make("main.Medication") + client = Client() + client.post("/login_view/", {"username": "test", "password": "testingpassword"}) + return_response = client.post( + f"/new_treatment_view/{p.id}/{e.id}", + { + "administration_schedule": a.id, + "days": 30, + "diagnosis": d.id, + "medication": m.id, + }, + ) + u.delete() + p.delete() + d.delete() + e.delete() + c.delete() + a.delete() + m.delete() + assert return_response.status_code == 200 diff --git a/main/test/test_formulary_management.py b/main/test/test_formulary_management.py index e69de29b..f3a5addc 100644 --- a/main/test/test_formulary_management.py +++ b/main/test/test_formulary_management.py @@ -0,0 +1,102 @@ +from django.contrib.auth.models import Group +from django.test.client import Client +from model_bakery import baker + +from main.models import fEMRUser + + +def test_formulary_home_view(): + fEMRUser.objects.all().delete() + u = fEMRUser.objects.create_user( + username="test", + password="testingpassword", + email="logintestinguseremail@email.com", + ) + u.change_password = False + Group.objects.get_or_create(name="fEMR Admin")[0].user_set.add(u) + c = baker.make("main.Campaign") + c.active = True + c.save() + u.campaigns.add(c) + u.save() + client = Client() + return_response = client.post( + "/login_view/", {"username": "test", "password": "testingpassword"} + ) + return_response = client.get("/formulary_home_view/") + assert return_response.status_code == 200 + u.delete() + c.delete() + + +def test_edit_supply_view(): + fEMRUser.objects.all().delete() + u = fEMRUser.objects.create_user( + username="test", + password="testingpassword", + email="logintestinguseremail@email.com", + ) + u.change_password = False + Group.objects.get_or_create(name="fEMR Admin")[0].user_set.add(u) + c = baker.make("main.Campaign") + c.active = True + c.save() + u.campaigns.add(c) + u.save() + client = Client() + return_response = client.post( + "/login_view/", {"username": "test", "password": "testingpassword"} + ) + item = baker.make("main.InventoryEntry") + return_response = client.get("/edit_supply/{0}".format(item.id)) + assert return_response.status_code == 200 + u.delete() + c.delete() + + +def test_post_edit_supply_view(): + fEMRUser.objects.all().delete() + u = fEMRUser.objects.create_user( + username="test", + password="testingpassword", + email="logintestinguseremail@email.com", + ) + u.change_password = False + Group.objects.get_or_create(name="fEMR Admin")[0].user_set.add(u) + c = baker.make("main.Campaign") + c.active = True + c.save() + u.campaigns.add(c) + u.save() + client = Client() + return_response = client.post( + "/login_view/", {"username": "test", "password": "testingpassword"} + ) + item = baker.make("main.InventoryEntry") + category = baker.make("main.InventoryCategory") + medication = baker.make("main.Medication") + form = baker.make("main.InventoryForm") + manufacturer = baker.make("main.Manufacturer") + return_response = client.post( + "/edit_supply/{0}".format(item.id), + { + "amount": "1", + "box_number": "1233", + "category": category.id, + "count": "1", + "count_unit": "cm", + "expiration_date": "2022-05-31", + "form": form.id, + "initial_quantity": "4", + "item_number": "32412", + "manufacturer": manufacturer.id, + "medication": medication.id, + "quantity": "1", + "quantity_unit": "duck", + "strength": "1", + "strength_unit": "lulz", + }, + ) + assert return_response.status_code == 200 + u.delete() + c.delete() diff --git a/main/test/test_patient_csv_export.py b/main/test/test_patient_csv_export.py new file mode 100644 index 00000000..165f39f7 --- /dev/null +++ b/main/test/test_patient_csv_export.py @@ -0,0 +1,64 @@ +from django.contrib.auth.models import Group +from django.test.client import Client + +from clinic_messages.models import Message +from model_bakery import baker + +from main.csvio.patient_csv_export import ( + csv_export_handler, + dict_builder, + patient_processing_loop, +) +from main.models import Patient, fEMRUser + + +def test_patient_processing_loop(): + campaign = baker.make("main.Campaign") + patient = baker.make("main.Patient", campaign=[campaign]) + baker.make("main.PatientEncounter", patient=patient, campaign=campaign) + patient_data = Patient.objects.filter(campaign=campaign) + patient_rows = [] + vitals_dict = {} + treatments_dict = {} + hpis_dict = {} + max_treatments, max_hpis, max_vitals = dict_builder( + patient_data, vitals_dict, treatments_dict, hpis_dict + ) + result = patient_processing_loop( + patient_data, + patient_rows, + campaign, + vitals_dict, + max_vitals, + treatments_dict, + max_treatments, + hpis_dict, + max_hpis, + ) + print(result) + assert result == 1 + + +def test_csv_export_handler(): + admin_user = fEMRUser.objects.create_user( + username="admin", + password="testingpassword", + email="admin@email.com", + ) + user = fEMRUser.objects.create_user( + username="testcsvexporthandler", + password="testingpassword", + email="testcsvexporthandler@email.com", + ) + user.change_password = False + campaign = baker.make("main.Campaign") + campaign.active = True + campaign.save() + user.campaigns.add(campaign) + user.save() + patient = baker.make("main.Patient", campaign=[campaign]) + baker.make("main.PatientEncounter", patient=patient, campaign=campaign) + csv_export_handler(user.id, campaign.id, 1) + assert Message.objects.filter(recipient=user).count() == 1 + user.delete() + admin_user.delete() diff --git a/main/test/test_views.py b/main/test/test_views.py index 8e94461f..16fa63d9 100644 --- a/main/test/test_views.py +++ b/main/test/test_views.py @@ -108,5 +108,4 @@ def test_switch_campaign(): campaign = u.current_campaign u.delete() assert return_response.status_code == 200 - print(campaign) assert campaign == c.name diff --git a/main/unit_converters.py b/main/unit_converters.py index 804bbd7d..ce031d78 100644 --- a/main/unit_converters.py +++ b/main/unit_converters.py @@ -11,7 +11,7 @@ def history_view_imperial(form, encounter): "history_of_hypertension": encounter.history_of_hypertension, "history_of_high_cholesterol": encounter.history_of_high_cholesterol, "alcohol": encounter.alcohol, - "chief_complaint": [c.pk for c in encounter.chief_complaint.all()], + "chief_complaint": [c.pk for c in encounter.chief_complaint.all().iterator()], "patient_history": encounter.patient_history, "community_health_worker_notes": encounter.community_health_worker_notes, "body_height_primary": math.floor( @@ -46,7 +46,7 @@ def new_vitals_imperial(form, encounter): "history_of_high_cholesterol": encounter.history_of_high_cholesterol, "alcohol": encounter.alcohol, "chief_complaint": [ - complaint.pk for complaint in encounter.chief_complaint.all() + complaint.pk for complaint in encounter.chief_complaint.all().iterator() ], "patient_history": encounter.patient_history, "community_health_worker_notes": encounter.community_health_worker_notes, @@ -78,7 +78,7 @@ def new_diagnosis_imperial(form, encounter): "history_of_hypertension": encounter.history_of_hypertension, "history_of_high_cholesterol": encounter.history_of_high_cholesterol, "alcohol": encounter.alcohol, - "chief_complaint": [c.pk for c in encounter.chief_complaint.all()], + "chief_complaint": [c.pk for c in encounter.chief_complaint.all().iterator()], "patient_history": encounter.patient_history, "community_health_worker_notes": encounter.community_health_worker_notes, "body_height_primary": math.floor( @@ -112,7 +112,7 @@ def encounter_update_form_initial_imperial(form, encounter): "history_of_hypertension": encounter.history_of_hypertension, "history_of_high_cholesterol": encounter.history_of_high_cholesterol, "alcohol": encounter.alcohol, - "chief_complaint": [c.pk for c in encounter.chief_complaint.all()], + "chief_complaint": [c.pk for c in encounter.chief_complaint.all().iterator()], "patient_history": encounter.patient_history, "community_health_worker_notes": encounter.community_health_worker_notes, "body_height_primary": math.floor( @@ -171,7 +171,7 @@ def new_treatment_imperial(form, encounter): "history_of_hypertension": encounter.history_of_hypertension, "history_of_high_cholesterol": encounter.history_of_high_cholesterol, "alcohol": encounter.alcohol, - "chief_complaint": [c.pk for c in encounter.chief_complaint.all()], + "chief_complaint": [c.pk for c in encounter.chief_complaint.all().iterator()], "patient_history": encounter.patient_history, "community_health_worker_notes": encounter.community_health_worker_notes, "body_height_primary": math.floor( @@ -204,7 +204,7 @@ def aux_form_imperial(form, encounter): "history_of_hypertension": encounter.history_of_hypertension, "history_of_high_cholesterol": encounter.history_of_high_cholesterol, "alcohol": encounter.alcohol, - "chief_complaint": [c.pk for c in encounter.chief_complaint.all()], + "chief_complaint": [c.pk for c in encounter.chief_complaint.all().iterator()], "patient_history": encounter.patient_history, "community_health_worker_notes": encounter.community_health_worker_notes, "body_height_primary": math.floor( diff --git a/main/urls.py b/main/urls.py index ac8a26fd..af5828be 100644 --- a/main/urls.py +++ b/main/urls.py @@ -33,6 +33,7 @@ update_user_password_view, ) from main.csvio.patient_csv_export import csv_export_list, fetch_csv_export +from main.dashboard_views import femr_admin_dashboard_view from main.delete_views import ( delete_chief_complaint, delete_treatment_view, @@ -48,6 +49,7 @@ delete_supply_item, edit_add_supply_view, edit_sub_supply_view, + edit_supply_view, formulary_home_view, ) from main.operation_admin_views import operation_admin_home_view @@ -164,7 +166,7 @@ schema_view = get_schema_view( openapi.Info( title="fEMR OnChain API", - default_version="v1.5.3", + default_version="v1.6.0", description="API endpoints providing an interface with the fEMR OnChain application.", terms_of_service="https://www.google.com/policies/terms/", contact=openapi.Contact(email="info@teamfemr.org"), @@ -316,8 +318,8 @@ chief_complaint_list_view, name="chief_complaint_list_view", ), - url( - r"^patient_csv_export_view/$", + path( + r"patient_csv_export_view/", patient_csv_export_view, name="patient_csv_export_view", ), @@ -414,6 +416,7 @@ # Formulary Management url(r"^formulary_home_view/$", formulary_home_view, name="formulary_home_view"), url(r"^add_supply_view/$", add_supply_view, name="add_supply_view"), + path(r"edit_supply/", edit_supply_view, name="edit_supply_view"), path( r"edit_add_supply_view/", edit_add_supply_view, @@ -508,6 +511,11 @@ ), url(r"^forgot_username", forgot_username, name="forgot_username"), url(r"^help_messages_off", help_messages_off, name="help_messages_off"), + url( + r"^femr_admin_dashboard_view", + femr_admin_dashboard_view, + name="femr_admin_dashboard_view", + ), url( r"^diagnosis-autocomplete/$", DiagnosisAutocomplete.as_view(create_field="text"), diff --git a/main/views.py b/main/views.py index 2fa533e2..c2af7776 100644 --- a/main/views.py +++ b/main/views.py @@ -12,12 +12,7 @@ from django.utils import timezone from django.core.mail import send_mail from silk.profiling.profiler import silk_profile - -from main.background_tasks import ( - assign_broken_patient, - check_admin_permission, - run_encounter_close, -) +from main.decorators import is_admin, is_authenticated from main.forms import ForgotUsernameForm from main.models import Campaign, MessageOfTheDay, fEMRUser @@ -33,6 +28,7 @@ def index(request): return redirect("main:login_view") +@is_authenticated @silk_profile("home") def home(request): """ @@ -41,36 +37,28 @@ def home(request): :param request: Django Request object. :return: An HttpResponse, rendering the home page. """ - if request.user.is_authenticated: - assign_broken_patient() - campaign_list = request.user.campaigns.filter(active=True) - if len(campaign_list) != 0 and request.user.current_campaign != "RECOVERY MODE": - campaign = campaign_list.get(name=request.user.current_campaign) - run_encounter_close(campaign) - motd = MessageOfTheDay.load() - if motd.start_date is not None or motd.end_date is not None: - if motd.start_date < timezone.now().date() < motd.end_date: - motd_final = motd.text - else: - motd_final = "" + motd = MessageOfTheDay.load() + if motd.start_date is not None or motd.end_date is not None: + if motd.start_date < timezone.now().date() < motd.end_date: + motd_final = motd.text else: motd_final = "" - return_response = render( - request, - "data/home.html", - { - "user": request.user, - "page_name": "Home", - "campaigns": campaign_list, - "motd": motd_final, - "selected_campaign": request.user.current_campaign, - }, - ) else: - return_response = redirect("main:not_logged_in") - return return_response - - + motd_final = "" + return render( + request, + "data/home.html", + { + "user": request.user, + "page_name": "Home", + "campaigns": request.user.campaigns.filter(active=True), + "motd": motd_final, + "selected_campaign": request.user.current_campaign, + }, + ) + + +@is_authenticated def library(request): """ The root of the main library page, where clinical users of the application can view @@ -79,11 +67,7 @@ def library(request): :param request: Django Request object. :return: An HttpResponse, rendering the library page. """ - if request.user.is_authenticated: - return_response = render(request, "data/library.html", {"user": request.user}) - else: - return_response = redirect("main:not_logged_in") - return return_response + return render(request, "data/library.html", {"user": request.user}) # noinspection PyUnusedLocal @@ -94,29 +78,25 @@ def healthcheck(request): return HttpResponse("Working.") +@is_admin +@is_authenticated def set_timezone(request): - if request.user.is_authenticated: - if check_admin_permission(request.user): - campaign = Campaign.objects.get(name=request.user.current_campaign) - if request.method == "POST": - request.session["django_timezone"] = request.POST["timezone"] - campaign.timezone = request.POST["timezone"] - campaign.save() - return_response = redirect("main:index") - else: - selected_time_zone = campaign.timezone - return_response = render( - request, - "data/timezone.html", - { - "selected_time_zone": selected_time_zone, - "timezones": pytz.common_timezones, - }, - ) - else: - return_response = redirect("main:permission_denied") + campaign = Campaign.objects.get(name=request.user.current_campaign) + if request.method == "POST": + request.session["django_timezone"] = request.POST["timezone"] + campaign.timezone = request.POST["timezone"] + campaign.save() + return_response = redirect("main:index") else: - return_response = redirect("main:not_logged_in") + selected_time_zone = campaign.timezone + return_response = render( + request, + "data/timezone.html", + { + "selected_time_zone": selected_time_zone, + "timezones": pytz.common_timezones, + }, + ) return return_response @@ -146,15 +126,12 @@ def forgot_username(request): return return_response +@is_authenticated def help_messages_off(request): - if request.user.is_authenticated: - request.session["tags_off"] = ( - None if request.session.get("tags_off", None) else True - ) - return_response = redirect(request.META.get("HTTP_REFERER", "/")) - else: - return_response = redirect("main:not_logged_in") - return return_response + request.session["tags_off"] = ( + None if request.session.get("tags_off", None) else True + ) + return redirect(request.META.get("HTTP_REFERER", "/")) # open .json file and convert it into a dictionary object, to display in the FAQs page: diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 24dcf099..5f59f54a 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -5,6 +5,7 @@ upstream femr_onchain { server { listen 80; + client_max_body_size 50M; location / { proxy_pass http://femr_onchain; diff --git a/requirements.txt b/requirements.txt index 2c902e03..3c2403a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,13 @@ amazon.ion==0.9.1 amqp==5.1.1 -asgiref==3.5.0 +asgiref==3.4.1 astroid==2.11.3 +async-timeout==4.0.2 autopep8==1.6.0 billiard==3.6.4.0 black==22.3.0 -boto3==1.21.43 -botocore==1.24.45 +boto3==1.22.5 +botocore==1.25.5 celery==5.2.6 certifi==2021.10.8 chardet==4.0.0 @@ -20,25 +21,30 @@ coreapi==2.3.3 coreschema==0.0.4 coverage==6.3.2 Deprecated==1.2.13 +dill==0.3.4 Django==3.2.13 django-autocomplete-light==3.9.4 django-axes==5.32.0 django-background-tasks==1.2.5 django-compat==1.0.15 django-crispy-forms==1.14.0 +django-elasticache==1.0.3 +django-elastipymemcache==2.0.4 django-ipware==4.0.2 django-localflavor==3.1 django-nose==1.4.7 +django-pymemcache==1.0.0 +django-redis-sessions==0.6.2 django-rest-swagger==2.2.0 django-searchable-select==1.5.0 django-selectable==1.3.0 django-session-security==2.6.6 -django-silk==4.3.0 +django-silk==4.2.0 django-storages==1.12.3 -django-timezone-utils==0.15.0 +django-timezone-utils==0.13 django-user-agents==0.4.0 djangorestframework==3.13.1 -docutils==0.18.1 +docutils==0.18 dparse==0.5.1 drf-yasg==1.20.0 flake8==4.0.1 @@ -55,25 +61,27 @@ kombu==5.2.4 lazy-object-proxy==1.7.1 MarkupSafe==2.1.1 mccabe==0.6.1 -model-bakery==1.5.0 +model-bakery==1.4.0 mypy-extensions==0.4.3 nose==1.3.7 openapi-codec==1.3.2 packaging==21.3 pathspec==0.9.0 -platformdirs==2.5.2 -prompt-toolkit==3.0.29 -psycopg2-binary==2.9.3 +platformdirs==2.4.0 +prompt-toolkit==3.0.22 +psycopg2-binary==2.9.2 pycodestyle==2.8.0 pyflakes==2.4.0 Pygments==2.12.0 +pylibmc==1.6.1 pylint==2.13.7 +pymemcache==3.5.1 pyparsing==3.0.8 pyqldb==3.2.2 python-dateutil==2.8.2 python-memcached==1.59 python-stdnum==1.17 -pytz==2022.1 +pytz==2021.3 PyYAML==6.0 redis==4.2.2 regex==2022.4.24 @@ -87,13 +95,13 @@ six==1.16.0 sqlparse==0.4.2 toml==0.10.2 tomli==2.0.1 -typed-ast==1.5.3 -typing-extensions==4.2.0 +typed-ast==1.5.1 +typing-extensions==4.0.1 ua-parser==0.10.0 uritemplate==4.1.1 -urllib3==1.26.9 +urllib3==1.26.7 user-agents==2.2.0 vine==5.0.0 wcwidth==0.2.5 -whitenoise==6.0.0 -wrapt==1.14.0 +whitenoise==5.3.0 +wrapt==1.13.3 diff --git a/runner.sh b/runner.sh index cd820738..c5c693a9 100755 --- a/runner.sh +++ b/runner.sh @@ -20,7 +20,6 @@ function all() { python3 -m safety check -r requirements.txt python3 manage.py check static - run_tests pushd ./main/static/main/js || exit npm install popd || exit @@ -106,7 +105,7 @@ function check() { # that PyLint can't see. clear && \ black . && \ - ./build.sh test && \ + run_tests && \ pylint main app_mr clinic_messages --disable=E1101,W0613,R0903,C0301,C0114,C0115,C0116,R0801 } @@ -157,7 +156,6 @@ setup) ;; init-all-run) - check all setup gunicorn_run diff --git a/source/conf.py b/source/conf.py index f6e62288..b5f0aab8 100644 --- a/source/conf.py +++ b/source/conf.py @@ -27,7 +27,7 @@ author = "Team fEMR" # The full version, including alpha/beta/rc tags -release = "v1.5.3" +release = "v1.6.0" # -- General configuration ---------------------------------------------------