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
+
+
+
+ |
+ Campaign Name |
+ Patients Today |
+ Total Patients |
+ Encounters Today |
+ Total Encounters |
+
+
+
+ {% for o in campaigns %}
+
+ {{ o.id }} |
+ {{ o.name }} |
+ {{ o|patients_today }} |
+ {{ o|total_patients }} |
+ {{ o|encounters_today }} |
+ {{ o|total_encounters }} |
+
+ {% endfor %}
+
+
+
+
+
Client Details
+
+
+
+ User Agent |
+ User Login Count |
+
+
+
+ {% for key, value in browsers.items %}
+
+ {{ key }} |
+ {{ value }} |
+
+ {% endfor %}
+
+
+
+
+{% 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 %}
+
+{% 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
+
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
+
-
- |
- Category |
- Name |
- Form |
- Strength |
- Count |
- Quantity |
- Amount |
- Item # |
- Box # |
- Expiration Date |
- Manufacturer |
- Last Edited |
- |
- |
-
+
+ |
+ Category |
+ Name |
+ Form |
+ Strength |
+ Count |
+ Quantity |
+ Amount |
+ Item # |
+ Box # |
+ Expiration Date |
+ Manufacturer |
+ Last Edited |
+ |
+ |
+ |
+
- {% for o in list_view %}
-
- {{ 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 }} |
- Add |
- Remove |
- Delete |
-
- {% endfor %}
+ {% for o in list_view %}
+
+ {{ 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 }} |
+ Add |
+ Remove |
+ Edit |
+ Delete |
+
+ {% endfor %}
+
{% 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 ---------------------------------------------------