From 57e1341738798eab6e3aa54b2433a3ca1b36b297 Mon Sep 17 00:00:00 2001 From: <> Date: Thu, 26 Dec 2024 11:05:46 +0000 Subject: [PATCH] Deployed 21aab5253 with MkDocs version: 1.5.2 --- .../iaso_concepts/iaso_concepts.html | 137 ++++++++++++- search/search_index.json | 2 +- sitemap.xml | 188 +++++++++--------- sitemap.xml.gz | Bin 1903 -> 1903 bytes 4 files changed, 227 insertions(+), 100 deletions(-) diff --git a/fr/pages/users/reference/iaso_concepts/iaso_concepts.html b/fr/pages/users/reference/iaso_concepts/iaso_concepts.html index 83dd309985..d69d6b5526 100644 --- a/fr/pages/users/reference/iaso_concepts/iaso_concepts.html +++ b/fr/pages/users/reference/iaso_concepts/iaso_concepts.html @@ -321,10 +321,57 @@
La collecte de données implique des questions, et pour organiser ces questions, des formulaires de collecte de données. Ils se composent de questions pour lesquelles on souhaite collecter des réponses, tout en spécifiant des options (obligatoires ou non, saut d'une question en fonction d'une réponse précédente, etc.).\ +IASO utilise les XLS forms pour ses questionnaires, qui sont donc prédéfinis à l'aide d'un fichier Excel.
+Dans IASO, les formulaires de collecte de données sont versionnés, ce qui signifie qu'à chaque fois qu'une nouvelle version est créée, l'ancienne version est conservée et reste disponible dans le système. Il est possible d'y revenir aisément.
+IASO utilise la notion d'Unités Od'organisation (Org unit ou OU) pour gérer les données géographiques.\ +Les types d'unité d'organisation (OUT) représentent les niveaux dans la hiérarchie.
+Les unités d'organisation sont classées dans la pyramide selon un parent et un ou plusieurs enfants (sauf les parents au sommet et les enfants au bas de la hiérarchie).
+La collecte de données dans IASO est structurée selon la hiérarchie définie, et chaque utilisateur doit explicitement sélectionner une unité d'organisation avant d'ouvrir le questionnaire et de répondre aux questions. Cela garantit que les données collectées sont correctement associées à la géographie pertinente.
+Dans IASO, un Projet est lié à une instance d'application mobile, avec son propre ID d'application (ou "App ID"). Au sein d'un même compte IASO, vous pouvez avoir un ou plusieurs Projet(s) avec différentes options de fonctionnalités.\ +Les utilisateurs peuvent être liés à un ou plusieurs Projet(s).
+on ElasticBeanstalk + RDS
"},{"location":"AWS-Deployment.html#main-parts","title":"Main parts","text":"This documentation concerned the main Iaso deployment, that are done on AWS.
The main pillar is AWS Elastic beanstalk
Which is kind of a magic solution from Amazon that tie several of their service together, handle deployment logic, etc...
In the past we configured it by hands but now we are moving toward having it all handled via Terrraform so it is in code (we can have an history, avoid misclick, do complex ops etc...).
The technical term for this is \"Provisioning\" if you want to look it up.
"},{"location":"AWS-Deployment.html#setup-of-the-elastic-beainstalk","title":"Setup of the Elastic Beainstalk","text":"See https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-django.html
We have custom commands and configuration in .ebextensions/ and in and in .platform/ to extend the nginx config.
"},{"location":"AWS-Deployment.html#running-django-3-on-elastic-beanstalk-custom-ami","title":"Running Django 3 on Elastic Beanstalk / Custom AMI","text":"Django 3 requires version 2+ of the gdal library. Sadly, Beanstalk is based on Amazon Linux that contain an outdated version of GDAL.
To fix this you have to create a custom AMI to use in your Elastic Beanstalk environments and compile and your own version of gdal and it's dependencies.
See the AWS documentation on creating adn using a custom AMI and the Django documentation on compiling the GIS libraries
Custom build of the following libraries were done:
You can check scripts/create_ami.sh
for reference
Read AWS documentation on creating adn using a custom AMI but in summary:
#cloud-config\n repo_releasever: repository version number\n repo_upgrade: none\n
Infrastructure and configuration in the Github repository BLSQ/terraform-iaso-eb There is good documentation from Mbayang in the docs/
folder. Visibility is restricted.
In the BLSQ/iaso
Github repository: * .github
For the workflow info * scripts/
* .ebextension
For the Elastic beanstalk specific command * .platform
Configuration override
Two type of env in elastic beanstalk: * web * worker
We tie them via an \"env\" tag in AWS, so we can deploy them at the same time
One Elastic Beanstalk Env can contain multipe \"EC2 Instance\", that is virtual machine server. Their number auto scale according to rule in elastic beanstalk. Usually we have 1 instance for Worker and 2 for Web.
"},{"location":"AWS-Deployment.html#s3-bucket","title":"S3 bucket","text":"Static are readable by all.
Media are only accessible via Signed url that expire after a laps of time (15 minutes I think) and that are generated on the fly by iaso when needed.
"},{"location":"AWS-Deployment.html#cicd","title":"CI/CD","text":"Deployment of new version is done via Github action.
Each change on main are automically deployed on the staging environment
Deployment to other env have to be manually triggered
"},{"location":"AWS-Deployment.html#deployement-process","title":"Deployement process","text":"How a new version of Iaso is deployed
This is a simplified view, some details are omitted for clarity
iaso
environment. e.g. : staging
-> iaso-staging2
and iaso-staging2-worker
eb deploy
(from awseb cli):.ebextensions
Action markqe with \u00a5 are part of Elastic beanstalk logic/var/app/staging
\u00a5/var/app/staging
is moved in /var/app/current
\u00a5Deployed separately, handled via Elastic Beanstalk also, linked to Iaso via environment variable. Mbayang manage this
"},{"location":"AWS-Deployment.html#aws-sqs","title":"AWS SQS","text":"Queue system used for Worker, see worker section in README
"},{"location":"AWS-Deployment.html#s3-bucket_1","title":"S3 bucket","text":"S3 see above
"},{"location":"AWS-Deployment.html#architecture-inside-the-vm","title":"Architecture inside the VM","text":"Code is in the /var/app/current
Systemctl launch the web server as the web
unit. This is done via Gunicorn under the web user, gunicorn launch multiple Django server.
There is a NGINX in front of gunicorn. The above is handled automatically via Iaso
The logs can be listed inside the VM via journalctl -u web
We have 2 crons (for now). They can be seen by using systemctl list-timers
IASO is an innovative, open-source, bilingual (EN/FR) data collection platform with advanced geospatial features to plan, monitor and evaluate health, environmental or social programmes in low- and middle-income settings (LMICs). IASO is recognized as a Digital Public Good by the Digital Public Good Alliance and listed as a Digital Square Software Global Good, a testament to its proven impact. For more information and detailed use cases, please visit IASO website.
IASO comprises:
In terms of features, IASO can be summarized around four main components which are interconnected and expand the powers of one another:
Geospatial data management (Georegistry)
Geo-structured data collection
Geo-enabled Microplanning
Entities - these can be individuals (e.g. health programme beneficiaries) or physical objects (e.g. vaccines parcel, mosquito net, etc.). Workflows allows the tracking of entities by opening specific forms according to previous answers given to previous forms.
The platform has been implemented in Benin, Burkina Faso, Burundi, Cameroon, Central African Republic, the Democratic Republic of Congo, Haiti, Ivory Coast, Mali, Niger, Nigeria and Uganda. It is the official georegistry in Burkina Faso since 2023. IASO has also been implemented at regional level (AFRO region) in support to the Global Polio Eradication Initiative for its geospatial and health facility registries capabilities.
"},{"location":"index.html#technical-stack","title":"Technical stack","text":"IASO is made of a white labeled Android application using Java/Kotlin, reusing large parts of the ODK projects, and a web platform programmed using Python/GeoDjango on top of PostGIS. Frontend is mainly React/Leaflet. The API is implemented via Django rest framework, all data is stored in Postgresql or the media/ directory. One of the aims is the ease of integration with other platforms. We already have csv and geopackage imports and exports and target easy integration with OSM.
The companion mobile app for Android allows form submission and and org unit creation. Forms can also be filled in a web interface via the Enketo companion service.
"},{"location":"pages/dev/how_to/add_new_permission/add_new_permission.html","title":"How to add a new permission in Iaso","text":""},{"location":"pages/dev/how_to/add_new_permission/add_new_permission.html#1-add-permission-in-the-model","title":"1. Add permission in the model","text":"_PREFIX
: MY_PERMISSION = _PREFIX + _MY_PERMISSION
permissions
property of CustomPermissionSupport
's Meta
class: (_MY_PERMISSION, _(\"Access some stuff\"))
/hat/menupermissions/constants.py
MODULE_PERMISSIONS
MODULES
(in the same file)/hat/menupermissions/constants.py
PERMISSIONS_PRESENTATION
/hat/menupermissions/constants.py
READ_EDIT_PERMISSIONS
dictionnaryread
and edit
or other keys like no-admin
and admin
item_key\": {\"read\": \"added_permission\", \"edit\": \"coupled_permission\"}
item_key, read and edit
) and the tooltip message of the principal key(item_key
)docker compose run --rm iaso manage makemigrations && docker compose run --rm iaso manage migrate
/hat/assets/js/apps/Iaso/utils/permissions.ts
. Add and export a constant with the permission key, in a similar way as what was done for the backend in step 1.permissionMessages.ts
. The tooltip key should have the format: <permission name>_tooltip
to enable the component to recognize and translate it.en.json
and fr.json
/hat/assets/js/apps/Iaso/domains/modules/messages.ts
To set up automatic formatting with Black in Visual Studio Code:
Click \"Install\" for the extension by Microsoft
Create a .vscode/settings.json
file in your project root if it doesn't exist.
Add the following to settings.json
:
```json { \"editor.formatOnSave\": true, \"[python]\": { \"editor.defaultFormatter\": \"ms-python.black-formatter\", \"editor.formatOnSave\": true }, \"black-formatter.args\": [\"--config\", \"${workspaceFolder}/pyproject.toml\"], \"black-formatter.path\": [\"${workspaceFolder}/.venv/bin/black\"] }
```
python3.9 -m venv .venv source .venv/bin/activate
pip install -r requirements-dev.txt
This ensures you're using the version of Black specified in the project's requirements.
.vscode/.prettierignore
file:.git .venv venv node_modules
Now, VS Code will automatically format your Python files using Black when you save them, using the settings specified in your settings.json
file and the version of Black specified in your project's requirements.
Note: This configuration is local to your VS Code environment and won't affect other developers or CI processes that might use the pyproject.toml
file.
We have adopted Black as our code formatting tool. Line length is 120.
The easiest way to use is is to install the pre-commit hook: 1. Install pre-commit: pip install pre-commit 2. Execute pre-commit install to install git hooks in your .git/ directory.
Another good way to have it working is to set it up in your code editor. Pycharm, for example, has good support for this.
The pre-commit is not mandatory but Continuous Integration will check if the formatting is respected!
"},{"location":"pages/dev/how_to/contribute/contribute.html#tests-and-linting","title":"Tests and linting","text":"For the Python backend, we use the Django builtin test framework. Tests can be executed with
docker compose exec iaso ./manage.py test\n
"},{"location":"pages/dev/how_to/contribute/contribute.html#translations","title":"Translations","text":"The few translation for the Django side (login and reset password email etc..) are separated from the test. We only translate the template for now not the python code (string on model or admin).
When modifying or adding new strings to translate, use the following command to regenerate the translations:
manage.py makemessages --locale=fr --extension txt --extension html
This will update hat/locale/fr/LC_MESSAGES/django.po
with the new strings to translate.
After updating it with the translation you need to following command to have them reflected in the interface:
manage.py compilemessages
In development the servers will reload when they detect a file change, either in Python or Javascript. If you need reloading for the bluesquare-components code, see the \"Live Bluesquare Components\" section.
"},{"location":"pages/dev/how_to/contribute/contribute.html#troubleshooting","title":"Troubleshooting","text":"If you need to restart everything
docker compose stop && docker compose start\n
If you encounter problems, you can try to rebuild everything from scratch.
# kill containers\ndocker compose kill\n# remove `iaso` container\ndocker compose rm -f iaso\n# build containers\ndocker compose build\n# start-up containers\ndocker compose up\n
"},{"location":"pages/dev/how_to/contribute/contribute.html#jupyter-notebook","title":"Jupyter Notebook","text":"To run a Jupyter Notebook, just copy the env variable from runaisasdev.sh, activate the virtualenv and run
python manage.py shell_plus --notebook\n
"},{"location":"pages/dev/how_to/contribute/contribute.html#testing-prod-js-assets-in-development","title":"Testing prod js assets in development","text":"During local development, by default, the Javascript and CSS will be loaded from a webpack server with live reloading of the code. To locally test the compiled version as it is in production ( minified and with the same compilation option). You can launch docker compose with the TEST_PROD=true
environment variable set.
e.g TEST_PROD=true docker compose up
This can be useful to reproduce production only bugs. Please also test with this configuration whenever you modify webpack.prod.js to validate your changes.
Alternatively this can be done outside of docker by running:
npm run webpack-prod
to do the buildTEST_PROD
e.g. TEST_PROD=true python manage.py runserver
.Use case: develop or test entity related features in the web interface
"},{"location":"pages/dev/how_to/create_entities_in_web_ui/create_entities_in_web_ui.html#1-get-some-xls-forms","title":"1. Get some XLS Forms","text":"At least one form will be needed as reference form. More would be optimal as it would allow to test the follow-up feature.
"},{"location":"pages/dev/how_to/create_entities_in_web_ui/create_entities_in_web_ui.html#2-create-forms-and-form-versions","title":"2. Create Forms and form versions","text":"For each XLS form:
EntityType
s","text":"Repeat as needed
"},{"location":"pages/dev/how_to/create_entities_in_web_ui/create_entities_in_web_ui.html#4-create-submissions-for-the-reference-forms","title":"4. Create submissions for the reference form(s)","text":"Note: To be able to create submissions, Enketo needs to be running. This can be done with the following command: docker compose -f docker-compose.yml -f docker/docker-compose-enketo.yml up
Entities
with the Django admin","text":"Entities
menu entryEntity
. It should be a submission for the form defined as reference form for the EntityType
of the Entity
being created.from django.db import connection\n\nwith connection.cursor() as cursor:\n cursor.execute('CREATE EXTENSION fuzzystrmatch;')\n
Note: To open a python shell in docker: docker compose exec iaso ./manage.py shell
- In a terminal, launch a task worker: docker compose run iaso manage tasks_worker
- Go to /api/entityduplicates_analyzes
to launch an algorithm analysis (e.g: inverse) - In Iaso web, go to Benefiaries > Duplicates to see if the algorithm matched the duplicates created in previous steps
Use case: develop or test registry related features in the web interface
"},{"location":"pages/dev/how_to/create_registry_in_web_ui/create_registry_in_web_ui.html#1-get-some-submission","title":"1. Get some Submission","text":"Select a submission you are going to use has reference_instance
in an org unit.
We will use one organization unit and link a reference submission to it. Make sure you choose a org unit having children and that they are all validated. Make also sure that the type of this org unit has sub org unit types, and that those sub types are those used by the children org units. This org unit should have a shape and be visible by the account you are using. Children should also be visible, location (point or shape) is not mandatory.
"},{"location":"pages/dev/how_to/create_registry_in_web_ui/create_registry_in_web_ui.html#3-add-reference-instance-to-org-unit","title":"3. Add reference instance to org unit","text":"Org units
menu entryReference instance
fieldNote: To be able to create submissions, Enketo needs to be running. This can be done with the following command: docker compose -f docker-compose.yml -f docker/docker-compose-enketo.yml up
View registry
registry-demo
You can activate the Django Debug Toolbar.
To do so, create a hat/local_settings.py
files and configure the toolbar.
E.g.:
from .settings import * # noqa\n\nfrom debug_toolbar.middleware import show_toolbar\n\n\nINSTALLED_APPS += [\"debug_toolbar\"]\n\nMIDDLEWARE += [\"debug_toolbar.middleware.DebugToolbarMiddleware\"]\n\n\ndef custom_show_toolbar(request):\n included_urls = [\"/__debug__\", \"/admin\", \"/api\"]\n included = any(request.path.startswith(url) for url in included_urls)\n return show_toolbar(request) and included\n\n\nDEBUG_TOOLBAR_CONFIG = {\n \"DISABLE_PANELS\": [\n \"debug_toolbar.panels.redirects.RedirectsPanel\",\n # ProfilingPanel makes the django admin extremely slow.\n \"debug_toolbar.panels.profiling.ProfilingPanel\",\n ],\n \"SHOW_TEMPLATE_CONTEXT\": True,\n \"SHOW_TOOLBAR_CALLBACK\": custom_show_toolbar,\n}\n
"},{"location":"pages/dev/how_to/deploy/deploy.html","title":"Deploy","text":""},{"location":"pages/dev/how_to/deploy/deploy.html#0-make-sure-everything-is-there","title":"0. Make sure everything is there","text":"For IASO deployment make sure you ran eb init with following info
eb init\nSelect a default region\n5) eu-central-1 : EU (Frankfurt)\nSelect an application to use\n4) Iaso\nDo you wish to continue with CodeCommit? (y/N) (default is n): n\n
"},{"location":"pages/dev/how_to/deploy/deploy.html#1-prepare-assets","title":"1. Prepare assets","text":"To avoid long/failing deployment, we commit the production assets in the repository
git checkout development\ngit pull\nrm hat/assets/webpack/*\nnpm run webpack-prod\ngit add hat/assets/webpack/\ngit commit -m 'Committing assets'\ngit push\n
Troubleshooting :
Make sure you have eb installed and run eb init
Then you deploy the development
branch to staging
eb use Iaso-staging\neb deploy\n
eb deploy will take of (via container commands see ./.ebextensions/50_container_commands.config)
Troubleshooting :
Technically : we should merge development in master, and deploy master in production but for the momenent use \"just deploy\" to developement to prod
eb use Iaso-env\neb deploy\n
Check the production
Troubleshooting :
For the Playground, deploy as for staging and prod for the web server, but you also need to update the jupyter server (using the pem file you can find on 1password)
Note jupyter is currently using the development branch too.
ssh -i ~/.ssh/lightsail.pem ubuntu@18.196.197.98\ncd iaso\ngit pull\nsource bin/activate\npip install -r requirements.txt\nkillall python\nnohup ./run.sh &\n
Obviously, a more stable playground setup would be welcome.
Troubleshooting :
chmod 600 ~/.ssh/lightsail.pem
/hat/menupermissions/constants.py
FEATUREFLAGES_TO_EXCLUDE
a new itemDATA_COLLECTION_FORMS
in capitale letter[\"FEATUREFLAG_1\",\"FEATUREFLAG_2\"]
FEATUREFLAGES_TO_EXCLUDE
should be like FEATUREFLAGES_TO_EXCLUDE = { \"MODULE_1\": [\"FEATUREFLAG_1\"], \"MODULE_2\": [\"FEATUREFLAG_2\", \"FEATUREFLAG_3\"],}
/hat/menupermissions/constants.py
Ensure you have a usable project, form (with form version etc..) and an orgunit in iaso. And Enketo is working. You should be able to make a submission from the web interface. Do it to verify everything
Edit the Project in Django admin. And copy the external token It's automatically generated, so you should always have one
token='xxxxxxxxx-xxxx-xxxxx-xxxxx-xxxxxxxxx'
Still in the admin, take the form_id on the Form (caution: this is an external id, a string, different from the form.id) You should have one if you uploaded a correct FormVersion
form_id= 'FORM_ID'
On the OrgUnit take the external token: (it's in the dashboard)
external_org_unit_id= \"AAAA0000000000\"
Make an url with it:
f\"/api/enketo/public_create_url/?period={period}&form_id={form_id}&token={token}&external_org_unit_id={external_org_unit_id}\"\n=> '/api/enketo/public_create_url/?period=202301&form_id=FORM_ID&token=xxxxxxxxx-xxxx-xxxxx-xxxxx-xxxxxxxxx&external_org_unit_id=AAAA0000000000'\n
Iaso will return a json with a URL, open it. Fill the form.
To test the export, add the to_export=true argument. You will need FormMapping and a DHIS2 configuration to test the full export
"},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html","title":"Rebuilding the Frontend","text":""},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#docker","title":"Docker","text":""},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#removing-existing-docker-images","title":"Removing Existing Docker Images:","text":"docker rmi -f iaso-webpack:latest\n
"},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#build-the-new-docker-image","title":"Build the new Docker image:","text":"docker compose build --no-cache webpack\n
"},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#local","title":"Local","text":""},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#removing-node_modules","title":"Removing node_modules:","text":"rm -rf node_modules\n
"},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#clean-npm-cache","title":"Clean npm cache:","text":"npm cache clean --force\n
"},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#reinstall-npm-packages","title":"Reinstall npm packages:","text":"npm ci\n
"},{"location":"pages/dev/how_to/run_docs_locally/run_docs_locally.html","title":"How to run this site locally","text":"/docs/
pip install -r requirements.txt
mkdocs serve
A running local instance for development can be spun up via docker compose which will install and configure all deps in separate container. As such your computer should only need:
If docker compose give you trouble, make sure it can connect to the docker daemon.
If you use an Apple Silicon Mac, ensure export DOCKER_DEFAULT_PLATFORM=linux/amd64
is set.
A pgdata-iaso
folder, containing the database data, will be created in the parent directory of the git repository.
The docker-compose.yml file contains sensible defaults for the Django application.
Other environment variables can be provided by a .env file.
As a starting point, you can copy the sample .env.dist file and edit it to your needs.
cp .env.dist .env\n
note: All the commands here need to be run in the project directory in which the repository was cloned
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#2-build-the-containers","title":"2. Build the containers","text":"This will build and download the containers.
docker compose build\n
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#3-start-the-database","title":"3. Start the database","text":"docker compose up db\n
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#4-run-migrations","title":"4. Run migrations","text":"docker compose run --rm iaso manage migrate\n
If you get a message saying that the database iaso does not exist, you can connect to your postgres instance using
psql -h localhost -p 5433 -U postgres\n
then type
create database iaso; \n
to create the missing database.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#5-start-the-server","title":"5. Start the server","text":"To start all the containers (backend, frontend, db)
docker compose up\n
The web server will be reachable at http://localhost:8081
.
The docker-compose.yml
file describes the setup of the containers.
To login to the app or the Django admin, a superuser needs to be created with:
docker compose exec iaso ./manage.py createsuperuser\n
You can now login in the admin at http://localhost:8081/admin
.
Then additional users with custom groups and permissions can be added through the Django admin or loaded via fixtures.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#7-create-and-import-data","title":"7. Create and import data","text":"To create the initial account, project and profile, do the following:
docker compose exec iaso ./manage.py create_and_import_data\n
You can now login on http://localhost:8081
but still need to import your own data.
An alternative to this and the following steps is to import data from DHIS2.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#8-create-a-form","title":"8. Create a form","text":"Run the following command to create a form:
docker compose exec iaso ./manage.py create_form\n
At this point, if you want to edit forms directly on your machine using Enketo, go to the Enketo setup section of this README (down below).
Once you are done, you can click on the eye for your newly added form, click on \"+ Create\", tap a letter, then enter, select the org unit, then click \"Create submission\".
If Enketo is running and well setup, you can fill the form now.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#9-start-adding-features","title":"9. Start adding features","text":"You can now start to develop additional features on Iaso!
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#10-import-orgunit-forms-and-submission-from-dhis2","title":"10. Import OrgUnit, Forms and Submission from DHIS2","text":"Alternatively or in addition to steps 7-8, you can import data from the DHIS2 demo server (play.dhis2.org):
docker compose run --rm iaso manage seed_test_data --mode=seed --dhis2version=2.35.3\n
The hierarchy of OrgUnit, group of OrgUnit, Forms, and their Submissions will be imported. OrgUnit types are not handled at the moment
Log in to http://127.0.0.1:8081/dashboard with :
To submit and edit existing form submission from the browser, an Enketo service is needed.
To enable the Enketo editor in your local environment, include the additional docker compose configuration file for Enketo. Do so by invoking docker compose with both files.
docker compose -f docker-compose.yml -f docker/docker-compose-enketo.yml up\n
No additional configuration is needed. The first time the docker image is launched, it will download dependencies and do a build witch may take a few minutes. Subsequents launches are faster.
You can check that the server is correctly launched by going to http://localhost:8005
To seed your DB with typical example forms editable by Enketo, see import data from DHIS2
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#12-database-dump","title":"12. Database dump","text":"To create a copy of your iaso database in a file (dump) you can use:
docker compose exec db pg_dump -U postgres iaso -Fc > iaso.dump\n
The dumpfile will be created on your host. The -Fc
meant it will use an optimised Postgres format (which take less place). If you want the plain sql command use -Fp
docker compose down
docker compose up db
iaso5
You can list existing databases using docker compose exec db psql -U postgres -l
docker compose exec db psql -U postgres -c \"create database iaso5\"
cat iaso.dump | docker compose exec -T db pg_restore -U postgres -d iaso5 -Fc --no-owner /dev/stdin\n
.env
file to use to this database in the RDS_DB_NAME
settings.On the /health/ url you can find listed the Iaso version number, environment, deployment time, etc... that might help you understand how this server instance is deployed for debugging. e.g. https://iaso.bluesquare.org/health/
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#15-set-up-a-local-dhis2-server","title":"15. Set up a local DHIS2 server","text":"Experimental. For development if you need a local dhis2 server, you can spin up one in your docker compose by using the docker/docker-compose-dhis2.yml
configuration file.
Replace your invocations of docker compose
by docker compose -f docker-compose.yml -f docker/docker-compose-dhis2.yml
you need to specify both config files. e.g to launch the cluster:
docker compose -f docker-compose.yml -f docker/docker-compose-dhis2.yml up\n
The DHIS2 will be available on your computer on http://localhost:8080 and is reachable from Iaso as http://dhis2:8080. The login and password are admin / district. If you use it as an import source do not set a trailing /
Database file are stored in ../pgdata-dhis2
and dhis2 log and uploaded files in docker/DHIS2_home
.
You will probably require some sample data in your instance. It is possible to populate your DHIS2 server with sample data from a database dump like it's done for the official play servers. The DHIS2 database take around 3 GB.
The steps as are follow: Download the file, stop all the docker, remove the postgres database directory, start only the database docker, load the database dump and then restart everything.
wget https://databases.dhis2.org/sierra-leone/2.36.4/dhis2-db-sierra-leone.sql.gz\ndocker compose down\nsudo rm ../pgdata-dhis2 -r\ndocker compose up db_dhis2\nzcat dhis2-db-sierra-leone.sql.gz| docker compose exec -T db_dhis2 psql -U dhis dhis2 -f /dev/stdin\ndocker compose up\ncd Projects/blsq/iaso\ndocker compose up dhis2 db_dhis2\n
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#17-set-up-single-sign-on-sso-with-a-local-dhis2","title":"17. Set up Single Sign On (SSO) with a local DHIS2","text":"If you want to test the feature with your local dhis2 you can use the following step. This assume you are running everything in Docker containers
docker compose -f docker-compose.yml -f docker/docker-compose-dhis2.yml up
With the default docker compose setup, iaso is on port 8081 and dhis2 on port 8081 on your machineRedirect URI : http://localhost:8081/api/dhis2/{same as client id}/login/
Setup external credential in iaso
5 Create a new user in Iaso, grant it some rights
In DHIS2 retrieve the id for the user
Add the dhis2 id to the Iaso user : Open the target user in the iaso Admin http://localhost:8081/admin/iaso/profile/ and add it to the dhis2 id field, save.
Unlog from iaso or in a separate session/container
Download and setup Ngrok on https://ngrok.com/. Once Ngrok installed and running you must add your ngrok server url in settings.py
by adding the following line :
FILE_SERVER_URL = os.environ.get(\"FILE_SERVER_URL\", \"YOUR_NGROK_SERVER_URL\")\n
After this step you have to import settings.py
and add FILE_SERVER_URL
to forms.py
in iaso/models/forms as shown on the following lines :
\"file\": settings.FILE_SERVER_URL + self.file.url,\n\"xls_file\": settings.FILE_SERVER_URL + self.xls_file.url if self.xls_file else None\n
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#2-setup-the-mobile-app","title":"2 - Setup the mobile app","text":"Once Ngrok installed and running you have to run the app in developer mode (tap 10 times on the Iaso icon at start ) and connect the mobile app to your server by selecting the 3 dots in the top right corner and select \"change server url\". When connected to your server, refresh all data and your app will be ready and connected to your development server.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#19-sso-with-dhis2","title":"19. SSO with DHIS2","text":"You can use DHIS2 as identity provider to login on Iaso. It requires a little configuration on DHIS2 and Iaso in order to achieve that.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#1-setup-oauth2-clients-in-dhis2","title":"1 - Setup OAuth2 clients in DHIS2","text":"In DHIS2 settings you must setup your Iaso instance as Oauth2 Clients. Client ID and Grant types must be : * Client ID : What you want (Must be the same as your external credential name in Iaso) * Grant Types : Authorization code
Redirect URIs is your iaso server followed by : /api/dhis2/{your_dhis2_client_id}/login/
For example : https://myiaso.com/api/dhis2/dhis2_client_id/login/
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#2-configure-the-oauth2-connection-in-iaso","title":"2 - Configure the OAuth2 connection in Iaso","text":"In iaso you must setup your dhis2 server credentials. To do so, go to /admin
and setup as follow :
Don't forget the /
at the end of the urls.
sequenceDiagram\n autonumber\n Note right of Browser: user open url to login\n Browser->>DHIS2: GET /uaa/oauth/authorize<br>?client_id={your_dhis2_client_id}\n loop if not logged\n DHIS2->>Browser: Login screen\n Browser->>DHIS2: Enter credentials\n DHIS2->>Browser: Login ok\n end\n DHIS2 -->> Browser: 200 Authorize Iaso? Authorize/Deny\n Browser ->> DHIS2: POST /authorize\n DHIS2 -->> Browser: 303 redirect\n Browser ->> IASO: GET /api/dhis2/<dhis2_slug>/login/?code=\n IASO ->> DHIS2: POST /uaa/oauth/token/\n DHIS2 -->> IASO: access token\n IASO ->> DHIS2 : GET /api/me\n DHIS2 -->> IASO: credential info\n Note right of IASO: find matching IASO user\n Note right of IASO: Log in session\n IASO -->> Browser: 303 Redirect & set cookies\n Browser ->> IASO: Use iaso normally as logged user.\n
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#20live-bluesquare-components","title":"20.Live Bluesquare components","text":"It is possible to configure the project to load a version of Bluesquare components from a local git repository instead of the one installed from a package. This enabled to develop feature necessitating modification in the components code.
To do so: * place the repository in the parent repository of Iaso ../bluesquare-components/
* install the dependency for bluesquare-component by running npm install in its directory * set the environment variable LIVE_COMPONENTS=true
* start your docker compose
cd ..\ngit clone git@github.com:BLSQ/bluesquare-components.git\ncd bluesquare-components\nnpm install\ncd ../iaso\nLIVE_COMPONENTS=true docker compose up\n
This way the page will reload automatically if you make a change to the bluesquare-components code.
This functionality also works if you launch webpack outside of docker.
If you encounter any problem, first check that your repo is on the correct branch and the deps are up-to-date
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#21-task-worker","title":"21. Task worker","text":"In local development, you can run a worker for background tasks by using the command:
docker compose run iaso manage tasks_worker\n
Alternatively, you can call the url tasks/run_all
which will run all the pending tasks in queue.
You can override default application title, logo and colors using the .env
file and specify those variables:
THEME_PRIMARY_COLOR=\"<hexa_color>\"\nTHEME_PRIMARY_BACKGROUND_COLOR=\"<hexa_color>\"\nTHEME_SECONDARY_COLOR=\"<hexa_color>\"\nAPP_TITLE=\"<app_title>\"\nFAVICON_PATH=\"<path_in_static_folder>\"\nLOGO_PATH=\"<path_in_static_folder>\"\nSHOW_NAME_WITH_LOGO=\"<'yes' or 'no'>\"\n
Those settings are optional and are using a default value if nothing is provided
"},{"location":"pages/dev/how_to/setup_dev_env/setuper.html","title":"Setuper","text":""},{"location":"pages/dev/how_to/setup_dev_env/setuper.html#introduction","title":"Introduction","text":"The setuper.py script:
It will:
Once the script has run, you can log in to your server using the account name as login and password.
"},{"location":"pages/dev/how_to/setup_dev_env/setuper.html#how-to-use","title":"How To Use","text":"Backup your DB
docker compose exec db pg_dump -U postgres iaso -Fc > ~/Desktop/iaso.dump\n
Use an empty DB
# Find your Iaso DB.\ndocker compose exec db psql -U postgres -l\n\n# Delete your Iaso DB.\ndocker compose exec db psql -U postgres -c \"drop database if exists iaso\"\n\n# Create your Iaso DB.\ndocker compose exec db psql -U postgres -c \"create database iaso\"\n
Run the Django server in a first terminal (this will run DB migrations)
docker compose up iaso\n
Run a worker in a second terminal
docker compose run iaso manage tasks_worker\n
Create a superuser
docker compose exec iaso ./manage.py createsuperuser\n
Prepare the setuper (it requires a local Python 3)
cd setuper\n
Create a virtual env for your local Python:
python3 -m venv venv source venv/bin/activate
Install requirements:
pip install -r requirements.txt\n
Update credentials.py
because we need a user with API access (use your superuser credentials)
cp data/sample-credentials.py credentials.py
Run the setuper
python3 setuper.py\n
It's a relatively standard json based API built using the Django Rest Framework.
Here is a sample Python script showing how to fetch your list of submissions:
import os\nimport requests\n\n# API setup\nserver = \"https://iaso.bluesquare.org\"\ncreds = {\n \"username\": USERNAME,\n \"password\": PASSWORD\n}\n\ninstances_endpoint = server + \"/api/instances/\"\n\n# get API token\nr = requests.post(server + \"/api/token/\", json=creds)\n\ntoken = r.json().get('access')\nheaders = {\"Authorization\": \"Bearer %s\" % token}\n\n# request submissions data\nr = requests.get(instances_endpoint,\n headers=headers)\n\nj = r.json()\n
Most endpoints of Iaso provide exports to csv through the mechanisms provided by the Django Rest Framework (by adding format=csv
to the url) and some of them provide exports to xlsx (xlsx=true
) and geopackage (gpkg=true
) formats.
In the context of CODA2, Iaso will have to write some forms\u2019 information on an NFC card. The NFC card\u2019s size may vary in the future, but the current size of the card is 8k. Yet, the card is currently split into two partitions; one is used by CODA and the other by SCOPE. SCOPE has 6kB out of the 8, leaving 2kB to CODA.
Famoco provided the implementation for the CODA 1.5 application.
We know that the patient\u2019s profile can take up to 500B, and the subsequent visits can take up to 144B with a total of 8 visits maximum.
This gives us a total of 1652B if all slots are filled.
I assume that the remaining 396B (2048 - 1652) is the encryption overhead.
Assuming Famoco is using an AES/CBC symmetric encryption with a salt of 256B and an IV (initialization vector) of 16B over the whole data, the assumption would hold.
"},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#famocos-split","title":"Famoco\u2019s split","text":"In the documentation provided by Famoco, called DESFireService_v1.0.1, they mention two types of writing:
File \u201c01\u201d, which we\u2019ll call the profile, is up to 500B
File \u201c02\u201d, which we\u2019ll call the visits, is of variable length based > on the type of card used.
The profile is marked as a binary file overridden on each writing.
The visits are marked as a cyclic record. Every time a visit is recorded, it is added to the previous ones. If the max length is reached, the oldest record is deleted to make room for the new one (FIFO).
\u2753 It is yet to be defined if a model outside of Famoco should follow the same split and enforce the writing of one record at a time."},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#ndef-messages-and-ndef-records","title":"NDEF Messages and NDEF Records","text":"NFC Data Exchange Format (or NDEF) is a lightweight binary format, used to encapsulate typed data. It is specified by the NFC Forum, for transmission and storage with NFC. However, it is transport agnostic.
The format defines Messages and Records:
An NDEF Record contains typed data, such as MIME-type media, a URI, > or a custom application payload.
An NDEF Message is a container for one or more NDEF Records.
As described in the Android documentation, It is mandatory for all Android devices with NFC to correctly enumerate Ndef on NFC Forum Tag Types 1-4, and implement all NDEF operations as defined in said documentation.
Therefore, it guarantees that a perfectly written tag will be readable by all Android devices with NFC.
Finally, the overhead of NDEF is existent but can be considered negligible compared to the advantages it brings. Based on this StackOverflow answer, the overhead is roughly 12 bytes:
NDEF Header byte: 1 byte
NDEF type length field: 1 byte
NDEF payload length field: 1-4 bytes
NDEF type name \"iaso:p\" (external type): 6 bytes
Famoco\u2019s devices are relying on a Secure Access Module (or SAM) chip which encrypts and guarantees a secured shared secret across the authorized devices.
Unfortunately, regular Android devices can't rely on such a chip. Therefore, we must find a solution to encrypt the card's data in a way only decipherable by another authorized device without an internet connection.
Since all the authorized devices will need to be able to encrypt and decrypt the data, there is no need to go with asymmetric encryption.
The current standard for symmetric encryption is AES.
"},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#the-overhead","title":"The overhead","text":"As explained in this StackOverflow answer, AES doesn\u2019t expand data, but the padding will.
The maximum amount of padding is up to the padding algorithm.
In the case of PKCS7Padding, it should be up to 16 bytes as defined in the RFC3602.
On top of that, it\u2019s good practice to salt the password to make the deciphering even more complex, avoid rainbow tables attacks, and avoid being able to compare two identical records (which is more likely to happen with small forms).
AES also comes with the concept of Initialization Vector (or IV) randomly generated on each encryption.
The salt and the IV will be communicated in clear text alongside the encrypted data to be able to decrypt it. Therefore, they will add up to the number of bytes written on the card.
\ud83d\udd10 As advised by the National Institute of Standards and Technology (NIST), the length of the randomly-generated portion of the salt shall be at least 128 bits (16 bytes). In the current PoC implementation, it\u2019s 256 bytes. \ud83d\udd10 AES requires an IV size of 16 bytesThe total overhead is therefore of the size of the salt (256B), the size of the IV (16B), and the padding (up to 16B), which sums up to 288B but could be lowered to 48B if we reduce the salt to 16B for lesser security.
"},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#how-the-data-is-split","title":"How the data is split","text":"The overhead discussed previously is computed per encryption; if we encrypt all the data at once, the overhead is of that amount. If we encrypt the profile and the visits separately, the overhead is multiplied by two. The overhead is much higher if we encrypt the profile and each visit individually.
\ud83d\udd10 I don\u2019t have sufficient knowledge to tell if there is any security benefit in one or the other approach. Yet, based on the size of the available space on the card, I assume we should encrypt all the data at once or encrypt the profile and the visits separately but not all the visits apiece."},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#sharing-the-nfc-password-securely","title":"Sharing the NFC password securely","text":"Every security measure is as strong as its weakest link. The whole encryption mechanism is rendered useless if the password is too weak, easily guessable, or easily accessible.
Therefore, we need to find a good way to share the password while considering the complexity for the end user to obtain it.
\ud83d\ude08 Keep in mind that nothing is 100% secure. All the solutions we could come up with have flaws, even the SAM chip used by Famoco. Yet, we can only make it harder and harder for someone to retrieve the key to the point that they either can\u2019t retrieve it (missing skills or hardware) or don\u2019t find that it is worth their while."},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#embedded-inside-the-apk","title":"Embedded inside the APK","text":"The easiest way to share a password across authorized devices is to embed it directly into the APK (an APK is a file containing the code and resources of an application that an Android device can interpret to install it).
Yet, this solution is only viable if, and only if, the APK is distributed via a Mobile Device Management (or MDM) and that the MDM can securely prevent people from retrieving the APK from the device.
If the APK is distributed via the Play Store (or any other means that is publicly available or that allow retrieving the APK), the password must be considered compromised and rendered useless.
This solution is similar to what Famoco is doing since, in their case, the password is embedded and secured inside the SAM chip.
"},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#over-the-network","title":"Over the network","text":"This solution is easy for the end user, but more vulnerable as anyone can make a network request.
The client should make an authorized (given a valid token previously retrieved with valid credentials) request to a specific endpoint.
The endpoint would receive a public key (from an asymmetric encryption scheme like RSA) of a hardware-backed key pair as described in Android\u2019s documentation.
The backend would be able to verify the validity of the key and its certificate chain, pointing to a Google valid certificate, and containing the correct application ID and signature.
The password would then be encrypted by the backend with the public key and communicated back to the phone. The application should then be able to decrypt the password and store it securely.
This solution involves that only the application can decrypt the given password.
\ud83d\udd11 This is the investigated solution at the moment of this writing but we are unsure how secure this would be on a rooted Android device.This solution involves security measures that can be hard for attackers to obtain:
Valid credentials
A compromised phone with the application on it.
A way to compromise the device in a way it is not detected by > Android.
If the password is stored inside a physical device (like a USB key, a specific phone application, etc.) that is provided in limited quantities to trusted members of the organization, it can be shared through physical contact between the device to authorize and the trusted member with the physical device.
The password is then securely stored on the authorized device for later use.
This solution is secure but could be cumbersome. For example, if there are many devices to authorize, it can take a long time to transfer the password on each device. There could also be an issue if the devices are spread across the country, and the trusted members must go to each location to authorize the devices.
"},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#a-mix-of-the-above-solutions","title":"A mix of the above solutions","text":"The solutions provided above are not exhaustive and can also be mixed. For example, we could consider storing the password on a physical device to be encrypted with a key that needs to be retrieved via an authorized network request.
The more barrier we put in recovering the key, the more secure it is. Yet, it also makes the logistics for genuine authorized devices more complex.
"},{"location":"pages/dev/reference/API/entity.html","title":"API reference: Entity","text":"Entity and related models
The entity concept might feel a bit abstract, so it might be useful to reason about them using a concrete example (beneficiaries):
Entity
","text":" Bases: SoftDeletableModel
An entity represents a physical object or person with a known Entity Type
Contrary to forms, they are not linked to a specific OrgUnit. The core attributes that define this entity are not stored as fields in the Entity model, but in an Instance / submission
Source code iniaso/models/entity.py
class Entity(SoftDeletableModel):\n\"\"\"An entity represents a physical object or person with a known Entity Type\n\n Contrary to forms, they are not linked to a specific OrgUnit.\n The core attributes that define this entity are not stored as fields in the Entity model, but in an Instance /\n submission\n \"\"\"\n\n name = models.CharField(max_length=255, blank=True) # this field is not used, name value is taken from attributes\n uuid = models.UUIDField(default=uuid.uuid4, editable=False)\n created_at = models.DateTimeField(auto_now_add=True)\n updated_at = models.DateTimeField(auto_now=True)\n entity_type = models.ForeignKey(EntityType, blank=True, on_delete=models.PROTECT)\n attributes = models.OneToOneField(\n Instance, on_delete=models.PROTECT, help_text=\"instance\", related_name=\"attributes\", blank=True, null=True\n )\n account = models.ForeignKey(Account, on_delete=models.PROTECT)\n merged_to = models.ForeignKey(\"self\", null=True, blank=True, on_delete=models.PROTECT)\n\n objects = DefaultSoftDeletableManager.from_queryset(EntityQuerySet)()\n\n objects_only_deleted = OnlyDeletedSoftDeletableManager.from_queryset(EntityQuerySet)()\n\n objects_include_deleted = IncludeDeletedSoftDeletableManager.from_queryset(EntityQuerySet)()\n\n class Meta:\n verbose_name_plural = \"Entities\"\n\n def __str__(self):\n return \"%s %s %s %d\" % (self.entity_type.name, self.uuid, self.name, self.id)\n\n def get_nfc_cards(self):\n from iaso.models.storage import StorageDevice\n\n nfc_count = StorageDevice.objects.filter(entity=self, type=StorageDevice.NFC).count()\n return nfc_count\n\n def as_small_dict(self):\n return {\n \"id\": self.pk,\n \"uuid\": self.uuid,\n \"name\": self.name,\n \"created_at\": self.created_at,\n \"updated_at\": self.updated_at,\n \"entity_type\": self.entity_type_id,\n \"entity_type_name\": self.entity_type and self.entity_type.name,\n \"attributes\": self.attributes and self.attributes.as_dict(),\n }\n\n def as_small_dict_with_nfc_cards(self, instance):\n entity_dict = self.as_small_dict()\n entity_dict[\"nfc_cards\"] = self.get_nfc_cards()\n return entity_dict\n\n def as_dict(self):\n instances = dict()\n\n for i in self.instances.all():\n instances[\"uuid\"] = i.uuid\n instances[\"file_name\"]: i.file_name\n instances[str(i.name)] = i.name\n\n return {\n \"id\": self.pk,\n \"uuid\": self.uuid,\n \"created_at\": self.created_at,\n \"updated_at\": self.updated_at,\n \"entity_type\": self.entity_type.as_dict(),\n \"attributes\": self.attributes.as_dict(),\n \"instances\": instances,\n \"account\": self.account.as_dict(),\n }\n\n def soft_delete_with_instances_and_pending_duplicates(self, audit_source, user):\n\"\"\"\n This method does a proper soft-deletion of the entity:\n - soft delete the entity\n - soft delete its attached form instances\n - delete relevant pending EntityDuplicate pairs\n \"\"\"\n from iaso.models.deduplication import ValidationStatus\n\n original = copy(self)\n self.delete() # soft delete\n log_modification(original, self, audit_source, user=user)\n\n for instance in set([self.attributes] + list(self.instances.all())):\n original = copy(instance)\n instance.soft_delete()\n log_modification(original, instance, audit_source, user=user)\n\n self.duplicates1.filter(validation_status=ValidationStatus.PENDING).delete()\n self.duplicates2.filter(validation_status=ValidationStatus.PENDING).delete()\n\n return self\n
"},{"location":"pages/dev/reference/API/entity.html#iaso.models.entity.Entity.soft_delete_with_instances_and_pending_duplicates","title":"soft_delete_with_instances_and_pending_duplicates(audit_source, user)
","text":"This method does a proper soft-deletion of the entity: - soft delete the entity - soft delete its attached form instances - delete relevant pending EntityDuplicate pairs
Source code iniaso/models/entity.py
def soft_delete_with_instances_and_pending_duplicates(self, audit_source, user):\n\"\"\"\n This method does a proper soft-deletion of the entity:\n - soft delete the entity\n - soft delete its attached form instances\n - delete relevant pending EntityDuplicate pairs\n \"\"\"\n from iaso.models.deduplication import ValidationStatus\n\n original = copy(self)\n self.delete() # soft delete\n log_modification(original, self, audit_source, user=user)\n\n for instance in set([self.attributes] + list(self.instances.all())):\n original = copy(instance)\n instance.soft_delete()\n log_modification(original, instance, audit_source, user=user)\n\n self.duplicates1.filter(validation_status=ValidationStatus.PENDING).delete()\n self.duplicates2.filter(validation_status=ValidationStatus.PENDING).delete()\n\n return self\n
"},{"location":"pages/dev/reference/API/entity.html#iaso.models.entity.EntityType","title":"EntityType
","text":" Bases: models.Model
Its reference_form
describes the core attributes/metadata about the entity type (in case it refers to a person: name, age, ...)
iaso/models/entity.py
class EntityType(models.Model):\n\"\"\"Its `reference_form` describes the core attributes/metadata about the entity type (in case it refers to a person: name, age, ...)\"\"\"\n\n name = models.CharField(max_length=255) # Example: \"Child under 5\"\n code = models.CharField(\n max_length=255, null=True, blank=True\n ) # As the name could change over the time, this field will never change once the entity type created and ETL script will rely on that\n created_at = models.DateTimeField(auto_now_add=True)\n updated_at = models.DateTimeField(auto_now=True)\n # Link to the reference form that contains the core attribute/metadata specific to this entity type\n reference_form = models.ForeignKey(Form, blank=True, null=True, on_delete=models.PROTECT)\n account = models.ForeignKey(Account, on_delete=models.PROTECT, blank=True, null=True)\n is_active = models.BooleanField(default=False)\n # Fields (subset of the fields from the reference form) that will be shown in the UI - entity list view\n fields_list_view = ArrayField(\n models.CharField(max_length=255, blank=True, db_collation=\"case_insensitive\"), size=100, null=True, blank=True\n )\n # Fields (subset of the fields from the reference form) that will be shown in the UI - entity detail view\n fields_detail_info_view = ArrayField(\n models.CharField(max_length=255, blank=True, db_collation=\"case_insensitive\"), size=100, null=True, blank=True\n )\n # Fields (subset of the fields from the reference form) that will be used to search for duplicate entities\n fields_duplicate_search = ArrayField(\n models.CharField(max_length=255, blank=True, db_collation=\"case_insensitive\"), size=100, null=True, blank=True\n )\n prevent_add_if_duplicate_found = models.BooleanField(\n default=False,\n )\n\n class Meta:\n unique_together = [\"name\", \"account\"]\n\n def __str__(self):\n return f\"{self.name}\"\n\n def as_dict(self):\n return {\n \"name\": self.name,\n \"created_at\": self.created_at,\n \"updated_at\": self.updated_at,\n \"reference_form\": self.reference_form.as_dict(),\n \"account\": self.account.as_dict(),\n }\n
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html","title":"OrgUnitChangeRequestConfiguration
API","text":"This document is a first draft of the OrgUnitChangeRequestConfiguration
API that was designed by Benjamin in August 2024 and updated by Thibault. It still needs to be updated and completed, once the developments are over.
API used to create or modify a Change Request configuration.
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#permissions","title":"Permissions","text":"{\n \"uuid\": \"UUID? - OrgUnitChangeRequestConfiguration UUID\",\n \"project_id\": \"Int - Project ID\",\n \"org_unit_type_id\": \"Int - OrgUnit Type ID\",\n \"org_units_editable\": \"Boolean? - Whether or not OrgUnits of this OrgUnit Type are editable\",\n \"editable_fields\": [\"Array<String> - List of possible fields\"],\n \"possible_type_ids\": [\"Array<Int> - List of possible OrgUnit Type IDs that are allowed as new type for this OrgUnit Type\"],\n \"possible_parent_type_ids\": [\"Array<Int> - List of possible OrgUnit Type IDs that are allowed as new parent for this OrgUnit Type\"],\n \"group_set_ids\": [\"Array<Int> - List of GroupSet IDs for this OrgUnit Type\"],\n \"editable_reference_form_ids\": [\"Array<Int> - List of reference Form ID that can be modified\"],\n \"other_group_ids\": [\"Array<Int> - List of possible Group IDs for this OrgUnit Type\"]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#possible-responses","title":"Possible responses","text":""},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#201-created","title":"201 - Created","text":"# Do we return ID/UUID when something is successfully created? Let's try to return it, or even the full OUCRC
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#400-bad-request","title":"400 - Bad request","text":"org_unit_type_id
is not assigned to the given project_id
editable_fields
value is unknownpossible_parent_type_ids
is not a suitable for as a parent for the given OrgUnit Typeeditable_reference_forms
is not a reference form for the given OrgUnit Typeproject_id
is not null and doesn't existorg_unit_type_id
is not null and doesn't existpossible_type_ids
don't existpossible_parent_type_ids
don't existgroup_set_ids
don't existeditable_reference_form_ids
don't existother_group_ids
don't existAPI used to list all Change Request configurations.
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#permissions_1","title":"Permissions","text":"OrgUnitType
to filter onProject
to filter on{\n \"count\": \"Long\",\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\",\n \"results\": [\n {\n \"id\": \"Int - ID in the database\",\n \"uuid\": \"UUID - UUID in the database\",\n \"project\": {\n \"id\": \"Int - Project ID\",\n \"name\": \"String - Project name\"\n },\n \"org_unit_type\": {\n \"id\": \"Int - OrgUnit Type ID\",\n \"name\": \"String - OrgUnit Type name\"\n },\n \"org_units_editable\": \"Boolean - Whether or not OrgUnits of this OrgUnit Type are editable\",\n \"editable_fields\": \"Array<String> - List of possible fields\",\n \"created_at\": \"Timestamp\",\n \"created_by\": {\n \"id\": \"Int - User ID\",\n \"username\": \"String\",\n \"first_name\": \"String\",\n \"last_name\": \"String\"\n },\n \"updated_at\": \"Timestamp\",\n \"updated_by\": {\n \"id\": \"Int - User ID\",\n \"username\": \"String\",\n \"first_name\": \"String\",\n \"last_name\": \"String\"\n }\n }\n ]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#400-bad-request_1","title":"400 - Bad request","text":"project_id
)API used to fully retrieve a Change Request configuration.
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#permissions_2","title":"Permissions","text":"{\n \"id\": \"Int - ID in the database\",\n \"uuid\": \"UUID - UUID in the database\",\n \"project\": {\n \"id\": \"Int - Project ID\",\n \"name\": \"String - Project name\"\n },\n \"org_unit_type\": {\n \"id\": \"Int - OrgUnit Type ID\",\n \"name\": \"String - OrgUnit Type name\"\n },\n \"org_units_editable\": \"Boolean - Whether or not OrgUnits of this OrgUnit Type are editable\",\n \"editable_fields\": \"Array<String> - List of possible fields\",\n \"possible_types\": [\n {\n \"id\": \"Int - OrgUnit Type ID\",\n \"name\": \"String - OrgUnit Type name\"\n }\n ],\n \"possible_parent_types\": [\n {\n \"id\": \"Int - OrgUnit Type ID\",\n \"name\": \"String - OrgUnit Type name\"\n }\n ],\n \"group_sets\": [\n {\n \"id\": \"Int - GroupSet ID\",\n \"name\": \"String - GroupSet name\"\n }\n ],\n \"editable_reference_forms\": [\n {\n \"id\": \"Int - Form ID\",\n \"name\": \"String - Form name\"\n }\n ],\n \"other_groups\": [\n {\n \"id\": \"Int - Group ID\",\n \"name\": \"String - Group name\"\n }\n ],\n \"created_at\": \"Timestamp\",\n \"created_by\": {\n \"id\": \"Int - User ID\",\n \"username\": \"String\",\n \"first_name\": \"String\",\n \"last_name\": \"String\"\n },\n \"updated_at\": \"Timestamp\",\n \"updated_by\": {\n \"id\": \"Int - User ID\",\n \"username\": \"String\",\n \"first_name\": \"String\",\n \"last_name\": \"String\"\n }\n}\n
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#401-unauthorized_2","title":"401 - Unauthorized","text":"API used to list all Change Request configurations for the mobile app.
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#permissions_3","title":"Permissions","text":"{\n \"count\": \"Long\",\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\",\n \"results\": [\n {\n \"org_unit_type_id\": \"Int - OrgUnit Type ID\",\n \"org_units_editable\": \"Boolean - Whether or not OrgUnits of this OrgUnit Type are editable\",\n \"editable_fields\": [\"Array<String> - List of possible fields\"],\n \"possible_type_ids\": [\"Array<Int> - List of possible OrgUnit Type IDs that are allowed as new type for this OrgUnit Type\"],\n \"possible_parent_types\": [\"Array<Int> - List of possible OrgUnit Type IDs that are allowed as new parent for this OrgUnit Type\"],\n \"group_sets\": [\"Array<Int> - List of GroupSet IDs for this OrgUnit Type\"],\n \"editable_reference_forms\": [\"Array<Int> - List of reference Form ID that can be modified\"],\n \"other_groups\": [\"Array<Int> - List of other Group IDs for this OrgUnit Type\"],\n \"created_at\": \"Timestamp\",\n \"updated_at\": \"Timestamp\"\n }\n ]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#400-bad-request_2","title":"400 - Bad request","text":"project_id
)OrgUnitChangeRequest
API","text":"\"Change Requests\" can be submitted for an OrgUnit
and then reviewed (approved or rejected).
This API allows the DHIS2 Health pyramid to be updated using Iaso.
The Django model that stores \"Change Requests\" is OrgUnitChangeRequest
.
OrgUnitChangeRequest
object - Web + Mobile (IA-2421)","text":"POST /api/orgunits/changes/?app_id=\u2026
app_id
: String - Optional, project for which this is created.{\n \"uuid\": \"UUID - Client generated UUID\",\n \"org_unit_id\": \"String - id or UUID of the OrgUnit to change\",\n \"new_parent_id\": \"String? - id or UUID of the parent OrgUnit, null to erase, omitted to ignore.\",\n \"new_name\": \"String? - Name of the OrgUnit, \\\"\\\" (empty string) to erase, omitted to ignore.\",\n \"new_org_unit_type_id\": \"Int? - id of the OrgUnitType, null to erase, omitted to ignore.\",\n \"new_groups\": \"Array of Group ids? - empty array to erase, omitted to ignore.\",\n \"new_location\": {\n \"\": \"New geopoint for the OrgUnit, null to erase, omitted to ignore.\",\n \"latitude\": \"Double - New latitude of the OrgUnit\",\n \"longitude\": \"Double - New longitude of the OrgUnit\",\n \"altitude\": \"Double - New altitude of the OrgUnit\"\n },\n \"new_location_accuracy\": \"Double - New accuracy of the OrgUnit, null to erase, omitted to ignore.\",\n \"new_opening_date\": \"Timestamp, null to erase, omitted to ignore.\",\n \"new_closed_date\": \"Timestamp, null to erase, omitted to ignore.\",\n \"new_reference_instances\": \"Array of instance ids or UUIDs? - empty array to erase, omitted to ignore.\"\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#possible-responses","title":"Possible responses","text":""},{"location":"pages/dev/reference/API/org_unit_registry.html#201-created","title":"201 - Created","text":""},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request","title":"400 - Bad request","text":"String
field has an empty valuenew_parent_id
is not a valid OrgUnitnew_org_unit_type_id
is not a valid OrgUnitTypenew_groups
id is not a valid Groupnew_reference_instances
only one reference instance can exist by org_unit/form pairnew_org_unit_type_id
is not part of the user accountnew_closed_date
must be later than new_opening_date
new_parent_id
and org_unit_id
must have the same versionnew_parent_id
is already a child of org_unit_id
new_reference_instances
ids is not foundnew_parent_id
is not foundnew_org_unit_type_id
is not foundOrgUnitChangeRequest
objects - Web only (IA-2422)","text":"GET /api/orgunits/changes/
page
: Int (optional) - Current page (default: 1)limit
: Int (optional) - Number of entities returned by page (default: 20)org_unit_id
: Int (optional) - Id of the OrgUnit to which the changes apply (default: null)org_unit_type_id
: Int (optional) - Id of the OrgUnitType to filter on, either the old OrgUnitType before the change or the new one after the change (default: null)status
: Array> (optional) - One of new
, validated
, rejected
to filter the requests (default: null)&status=rejected&status=new
parent_id
: Int (optional) - Id of the old parent OrgUnit to filter on, before the change (default: null)project
: Int (optional) - Id of the project to filter on.groups
: List of int, comma separated (optional) - Ids of the old groups to filter on, before the change (default: null)&groups=1847,1846
forms
: List of int, comma separated (optional) - Ids of the old forms to filter on, before the change (default: null)&forms=12,34
users
: List of int, comma separated (optional) - Ids of the users who either created or last updated the change request (default: null)&users=56,78
user_roles
: List of int, comma separated (optional) - Ids of the old user roles to filter on, specifically the roles associated with the user who created the change request (default: null)&user_roles=90,123
with_location
: String (optional) - Filters the change requests based on the presence (\"true\"
) or absence (\"false\"
) of an old location, before the change (default: null)&with_location=true
{\n \"count\": \"Long\",\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\",\n \"results\": [\n {\n \"id\": \"Int - id in the database\",\n \"uuid\": \"UUID - uuid in the database\",\n \"org_unit_id\": \"Int - id of the OrgUnit\",\n \"org_unit_uuid\": \"UUID - uuid of the OrgUnit\",\n \"org_unit_name\": \"String - name of the OrgUnit\",\n \"org_unit_type_id\": \"Int - id of the current OrgUnitType\",\n \"org_unit_type_name\": \"String - name of the current OrgUnitType\",\n \"status\": \"Enum<Status> - one of `new`, `validated`, `rejected`\",\n \"groups\": [\n {\n \"id\": \"Int - id of the Group\",\n \"name\": \"String - name of the Group\"\n }\n ],\n \"requested_fields\": \"Array<String> - name of the properties that were requested to change\",\n \"approved_fields\": \"Array<String>? - name of the properties that were approved to change\",\n \"rejection_comment\": \"String? - Comment about why the changes were rejected\",\n \"created_by\": {\n \"id\": \"Int - id of the User who created that request\",\n \"username\": \"String?\",\n \"first_name\": \"String?\",\n \"last_name\": \"String?\"\n },\n \"created_at\": \"Timestamp\",\n \"updated_by\": {\n \"\": \"May be null\",\n \"id\": \"Int - id of the User\",\n \"username\": \"String?\",\n \"first_name\": \"String?\",\n \"last_name\": \"String?\"\n },\n \"updated_at\": \"Timestamp?\"\n }\n ]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request_1","title":"400 - Bad request","text":"page
or limit
cannot be parsed to a correct integer valueOrgUnitChangeRequest
objects - Mobile only (IA-2425)","text":"GET /api/mobile/orgunits/changes/?app_id=\u2026
app_id
: String - Must be provided, project for which this is queried.last_sync
: DateString - May be null or omitted. Limits the results to everything that was modified after this DateStringlast_sync
filter is built with django.utils.dateparse.parse_datetime
and allows:&last_sync=2023-09-26T17:21:22.921692Z
&last_sync=2021-09-26T17:21:22Z
&last_sync=2021-09-26T17:21:22
&last_sync=2021-09-26T17:21
page
: Int (optional) - Current page (default: 1)limit
: Int (optional) - Number of entities returned by page (default: 20){\n \"count\": \"Long\",\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\",\n \"results\": [\n {\n \"id\": \"Int - id in the database\",\n \"uuid\": \"UUID - uuid in the database\",\n \"org_unit_id\": \"Int - id of the OrgUnit\",\n \"org_unit_uuid\": \"UUID - uuid of the OrgUnit\",\n \"status\": \"Enum<Status> - one of `new`, `validated`, `rejected`\",\n \"approved_fields\": \"Array<String>? - name of the properties that were approved to change\",\n \"rejection_comment\": \"String? - Comment about why the changes were rejected\",\n \"created_at\": \"Timestamp in double\",\n \"updated_at\": \"Timestamp in double\",\n \"new_parent_id\": \"String? - id or UUID of the parent OrgUnit, may be null or omitted.\",\n \"new_name\": \"String? - Name of the OrgUnit, may be null or omitted.\",\n \"new_org_unit_type_id\": \"Int? - id of the OrgUnitType, may be null or omitted\",\n \"new_groups\": \"Array of Group ids? - can be empty, null or omitted. Empty means we want to remove all values\",\n \"new_location\": {\n \"\": \"New geopoint for the OrgUnit, may be null or omitted\",\n \"latitude\": \"Double - New latitude of the OrgUnit\",\n \"longitude\": \"Double - New longitude of the OrgUnit\",\n \"altitude\": \"Double - New altitude of the OrgUnit\"\n },\n \"new_location_accuracy\": \"Double - New accuracy of the OrgUnit\",\n \"new_opening_date\": \"Timestamp in double\",\n \"new_closed_date\": \"Timestamp in double\",\n \"new_reference_instances\": [\n {\n \"id\": \"Int\",\n \"uuid\": \"UUID - provided by the client\",\n \"form_id\": \"Int\",\n \"form_version_id\": \"Int\",\n \"created_at\": \"Timestamp in double\",\n \"updated_at\": \"Timestamp in double\",\n \"json\": \"JSONObject - contains the key/value of the instance\"\n }\n ]\n }\n ]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request_2","title":"400 - Bad request","text":"app_id
was not providedpage
or limit
cannot be parsed to a correct integer valuelast_sync
cannot be parsed to a correct date time.OrgUnitChangeRequest
object - Web only (IA-2423)","text":"GET /api/orgunits/changes/{id}/
{\n \"id\": \"Int - id in the database\",\n \"uuid\": \"UUID - uuid in the database\",\n \"status\": \"Enum<Status> - one of `new`, `validated`, `rejected`\",\n \"created_by\": {\n \"id\": \"Int - id of the User who created that request\",\n \"username\": \"String?\",\n \"first_name\": \"String?\",\n \"last_name\": \"String?\"\n },\n \"created_at\": \"Timestamp\",\n \"updated_by\": {\n \"\": \"May be null\",\n \"id\": \"Int - id of the User who updated that request\",\n \"username\": \"String?\",\n \"first_name\": \"String?\",\n \"last_name\": \"String?\"\n },\n \"updated_at\": \"Timestamp?\",\n \"requested_fields\": \"Array<String> - name of the properties that were requested to change\",\n \"approved_fields\": \"Array<String>? - name of the properties that were approved to change\",\n \"rejection_comment\": \"String? - Comment about why the changes were rejected\",\n \"org_unit\": {\n \"id\": \"Int - id in the database\",\n \"parent\": {\n \"id\": \"Int - id of the parent OrgUnit\",\n \"name\": \"String - name of the parent OrgUnit\"\n },\n \"name\": \"String - Name of the OrgUnit.\",\n \"org_unit_type\": {\n \"id\": \"Int - id of the OrgUnitType\",\n \"name\": \"String - name of the OrgUnitType\",\n \"short_name\": \"String - short name of the OrgUnitType\"\n },\n \"groups\": [\n {\n \"id\": \"Int - id of the Group\",\n \"name\": \"String - name of the Group\"\n }\n ],\n \"location\": {\n \"\": \"Geopoint for the OrgUnit\",\n \"latitude\": \"Double - New latitude of the OrgUnit\",\n \"longitude\": \"Double - New longitude of the OrgUnit\",\n \"altitude\": \"Double - New altitude of the OrgUnit\"\n },\n \"opening_date\": \"Timestamp?\",\n \"closed_date\": \"Timestamp?\",\n \"reference_instances\": [\n \"Array of form objects - can be empty\",\n {\n \"id\": \"Int - id in the database\",\n \"form_id\": \"id of the form\",\n \"form_name\": \"Name of the form\",\n \"values\": [\n {\n \"key\": \"String\",\n \"label\": \"String or translated object\",\n \"value\": \"String\"\n }\n ]\n }\n ]\n },\n \"new_parent\": {\n \"\": \"May be null\",\n \"id\": \"Int - id of the new parent OrgUnit in the database\",\n \"name\": \"String? - name of the new parent OrgUnit\"\n },\n \"new_name\": \"String? - New name of the OrgUnit, may be null or omitted\",\n \"new_org_unit_type\": {\n \"\": \"May be null\",\n \"id\": \"Int? - id of the new OrgUnitType\",\n \"name\": \"String? - name of the new OrgUnitType\",\n \"short_name\": \"String? - short name of the new OrgUnitType\"\n },\n \"new_groups\": [\n {\n \"id\": \"Int - id of the Group\",\n \"name\": \"String - name of the Group\"\n }\n ],\n \"new_location\": {\n \"\": \"New GeoPoint? for the OrgUnit, may be null or omitted\",\n \"latitude\": \"Double - New latitude of the OrgUnit\",\n \"longitude\": \"Double - New longitude of the OrgUnit\",\n \"altitude\": \"Double - New altitude of the OrgUnit\"\n },\n \"new_location_accuracy\": \"Double? - New accuracy of the OrgUnit\",\n \"new_opening_date\": \"Timestamp?\",\n \"new_closed_date\": \"Timestamp?\",\n \"new_reference_instances\": [\n \"Array of form objects? - may be null or omitted, cannot be empty\",\n {\n \"id\": \"Int - id in the database\",\n \"form_id\": \"id of the form\",\n \"form_name\": \"Name of the form\",\n \"values\": [\n {\n \"key\": \"String\",\n \"label\": \"String or translated object\",\n \"value\": \"String\"\n }\n ]\n }\n ],\n \"old_name\": \"String? - Old name of the OrgUnit, may be an empty\",\n \"old_org_unit_type\": {\n \"\": \"May be null\",\n \"id\": \"Int? - id of the old OrgUnitType\",\n \"name\": \"String? - name of the old OrgUnitType\",\n \"short_name\": \"String? - short name of the old OrgUnitType\"\n },\n \"old_groups\": [\n \"Array of old groups objects? - may be empty\",\n {\n \"id\": \"Int - id of the Group\",\n \"name\": \"String - name of the Group\"\n }\n ],\n \"old_location\": {\n \"\": \"Old GeoPoint? for the OrgUnit, may be null\",\n \"latitude\": \"Double - New latitude of the OrgUnit\",\n \"longitude\": \"Double - New longitude of the OrgUnit\",\n \"altitude\": \"Double - New altitude of the OrgUnit\"\n },\n \"old_opening_date\": \"Timestamp? - may be null\",\n \"old_closed_date\": \"Timestamp? - may be null\",\n \"old_reference_instances\": [\n \"Array of old form instance objects? - may be empty\",\n {\n \"id\": \"Int - id in the database\",\n \"form_id\": \"id of the form\",\n \"form_name\": \"Name of the form\",\n \"values\": [\n {\n \"key\": \"String\",\n \"label\": \"String or translated object\",\n \"value\": \"String\"\n }\n ]\n }\n ]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request_3","title":"400 - Bad request","text":"OrgUnitChangeRequest
- Web only (IA-2424)","text":"PATCH /api/orgunits/changes/{id}/
API to change the status of on change request.
"},{"location":"pages/dev/reference/API/org_unit_registry.html#permissions_4","title":"Permissions","text":"ORG_UNITS_CHANGE_REQUEST_REVIEW
permission{\n \"status\": \"Enum<Status> - One of `validated` or `rejected`\",\n \"approved_fields\": \"Array<Enum<Field>>? - name of the properties that were approved to change\",\n \"rejection_comment\": \"String? - Comment about why the changes were rejected\"\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#possible-responses_4","title":"Possible responses","text":""},{"location":"pages/dev/reference/API/org_unit_registry.html#204-no-content","title":"204 - No content","text":"Change were applied successfully
"},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request_4","title":"400 - Bad request","text":"status
of the change to be patched is not new
status
must be approved
or rejected
status
was validated
but approved_fields
was null, omitted or empty: at least one approved_fields
must be providedapproved_fields
contains one or more unknown fieldsstatus
was rejected
but rejection_comment
was null, omitted or empty: a rejection_comment
must be providedreference_instances
objects for a given OrgUnit
- Mobile only (IA-2420)","text":"GET /api/mobile/orgunits/{id or UUID}/reference_instances?app_id=\u2026
Returns Instance
objects marked as reference_instances
for an OrgUnit
from newest to oldest.
page
: Int (optional) - Current page (default: 1)limit
: Int (optional) - Number of entities returned by page (default: 20)app_id
: String - project for which this is queried.last_sync
: DateString - May be null or omitted. Limits the results to everything that was modified after this DateStringlast_sync
filter is built with django.utils.dateparse.parse_datetime
and allows:&last_sync=2023-09-26T17:21:22.921692Z
&last_sync=2021-09-26T17:21:22Z
&last_sync=2021-09-26T17:21:22
&last_sync=2021-09-26T17:21
{\n \"count\": \"Long\",\n \"instances\": [\n {\n \"id\": \"Int\",\n \"uuid\": \"UUID - provided by the client\",\n \"form_id\": \"Int\",\n \"form_version_id\": \"Int\",\n \"created_at\": \"Timestamp in double\",\n \"updated_at\": \"Timestamp in double\",\n \"json\": \"JSONObject - contains the key/value of the instance\"\n }\n ],\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\"\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request_5","title":"400 - Bad request","text":"app_id
was not providedpage
, limit
or version_count
cannot be parsed to a correct integer valuelast_sync
cannot be parsed to a correct date time.PaymentStatus
API","text":"Mock-ups
This API allows the status of payments linked to multiple OrgUnitChangeRequest
by the same user to be updated and queried.
The Django model that stores \"Payment Status\" is PaymentStatus
.
The PaymentStatus
model has a status
field which can have one of the following values:
PENDING
: This is the default status. It indicates that the payment is yet to be processed.SENT
: This status indicates that the payment has been processed and sent.REJECTED
: This status indicates that the payment was not successful and has been rejected.These statuses are stored as a list of tuples in the STATUS_CHOICES
field.
PaymentStatus
list","text":""},{"location":"pages/dev/reference/API/payments/payments.html#permissions","title":"Permissions","text":"iaso_payments
permissionpage
: Int (optional) - Specifies the current page number. If not provided, the default value is 1.order
: String (optional) - Specifies the order in which the results should be returned. If not provided, the default ordering value is id
.limit
: Int (optional) - Defines the number of entities to be returned per page. The default value is 20 if not specified.user_ids
: String (optional) - A comma-separated list of User IDs associated with the payments&user_ids=10,9
user_role_ids
: String (optional) - A comma-separated list of User Role IDs associated with the payments&user_role_ids=10,9
org_unit_id
: Int (optional) - The ID of the parent organization unit linked to the change requests. This should also include child units.from_date
: Date - 'YYYY-MM-DD' (optional) - The start date for when the change request has been validated. to_date
: Date - 'YYYY-MM-DD' (optional) - The end date for when the change request has been validated.{\n \"count\": \"Long\",\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\",\n \"results\": [\n \"id\": \"Int - unique id\",\n \"status\": \"String - PENDING or SENT or REJECTED\",\n \"change_requests\": [\n {\n \"id\": \"Int - change request unique id\",\n \"org_unit_id\": \"String - id or UUID of the OrgUnit to change\",\n }\n ],\n \"created_by\": {\n \"id\": \"Int - id of the User who created that payment\",\n \"username\": \"String - username of user\",\n \"first_name\": \"String - first name of user\",\n \"last_name\": \"String - last name of user\",\n },\n \"created_at\": \"Timestamp\",\n \"updated_by\": {\n \"id\": \"Int - id of the User who updated that payment\",\n \"username\": \"String - username of user\",\n \"first_name\": \"String - first name of user\",\n \"last_name\": \"String - last name of user\",\n },\n \"updated_at\": \"Timestamp\",\n \"user\": {\n \"id\": \"Int - user unique id\",\n \"username\": \"String - username of user\",\n \"first_name\": \"String - first name of user\",\n \"last_name\": \"String - last name of user\",\n \"user_role_id\": \"Int - user role id if one\",\n \"user_role_name\": \"String - user role name if one\",\n \"telephone\": \"String? - This type of field should be specified\",\n },\n ]\n}\n
"},{"location":"pages/dev/reference/API/payments/payments.html#400-bad-request","title":"400 - Bad request","text":"page
or limit
cannot be parsed to a correct integer valueuser_id
, user_role_id
, org_unit_id
not foundclass PaymentStatus(models.Model):\n \"\"\"\n Model to store the status of payments linked to multiple OrgUnitChangeRequest by the same user.\n \"\"\"\n\n class Statuses(models.TextChoices):\n PENDING = \"pending\", _(\"Pending\")\n SENT = \"sent\", _(\"Sent\")\n REJECTED = \"rejected\", _(\"Rejected\")\n\n status = models.CharField(choices=Statuses.choices, default=Statuses.PENDING, max_length=40)\n change_requests = models.ManyToManyField(OrgUnitChangeRequest, related_name=\"payment_statuses\")\n user = models.ForeignKey(User, on_delete=models.CASCADE, related_name=\"payment_statuses\")\n created_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name=\"payment_created_set\")\n updated_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name=\"payment_updated_set\")\n created_at = models.DateTimeField(auto_now_add=True)\n updated_at = models.DateTimeField(auto_now=True)\n
"},{"location":"pages/dev/reference/API/payments/payments.html#remarks","title":"Remarks","text":"Who creates the payments? Typically, a payment is created when a user initiates a transaction. This could be done automatically when a change request is made or manually by an admin user. The creation of a payment could be triggered in the backend code where the change request is processed.
Marking change requests as paid: We could add a payment_status field to the OrgUnitChangeRequest model. This field would reference the PaymentStatus of the associated payment. When a payment is processed, We can update this field accordingly.
Allow keeping a history of modification done on a Model instance.
It is not automatic, model that wish to implement this have to call log_modification manually when changed. Diff are stored in audit.Modification model.
"},{"location":"pages/dev/reference/background_tasks/background_tasks.html","title":"Background tasks & worker","text":"Iaso queue certains functions (task) for later execution, so they can run outside an HTTP request. This is used for functions that take a long time to execute so they don't canceled in the middle by a timeout of a connection closed. e.g: bulk import, modifications or export of OrgUnits. Theses are the functions marked by the decorator @task_decorator, when called they get added to a Queue and get executed by a worker.
The logic is based on a fork of the library django-beanstalk-worker from tolomea, please consult it's doc for reference.
If you want to develop a new background task, the endpoint /api/copy_version/
is a good example of how to create a task and to plug it to the api.
To call a function with the @task decorator, you need to pass it a User objects, in addition to the other function's arguments, this arg represent which user is launching the task. At execution time the task will receive a iaso.models.Task instance in argument that should be used to report progress. It's mandatory for the function, at the end of a successful execution to call task.report_success() to mark its proper completion.
"},{"location":"pages/dev/reference/campaigns/subactivities.html","title":"SubActivity and SubActivityScope Models and APIs","text":""},{"location":"pages/dev/reference/campaigns/subactivities.html#models","title":"Models","text":""},{"location":"pages/dev/reference/campaigns/subactivities.html#subactivity","title":"SubActivity","text":"The SubActivity
model represents a sub-activity within a round of a campaign. It has the following fields:
round
: A foreign key to the Round
model, representing the round to which the sub-activity belongs.name
: A string field representing the name of the sub-activity.age_unit
: A choice field representing the unit of age targeted by the sub-activity. The choices are \"Months\" and \"Years\".age_min
: An integer field representing the minimum age targeted by the sub-activity.age_max
: An integer field representing the maximum age targeted by the sub-activity.start_date
: A date field representing the start date of the sub-activity.end_date
: A date field representing the end date of the sub-activity.The SubActivityScope
model represents the scope of a sub-activity, including the selection of an organizational unit and the vaccines used. It has the following fields:
group
: A one-to-one field to the Group
model, representing the group of organizational units for the sub-activity.subactivity
: A foreign key to the SubActivity
model, representing the sub-activity to which the scope belongs.vaccine
: A choice field representing the vaccine used in the sub-activity. The choices are \"mOPV2\", \"nOPV2\", and \"bOPV\".The SubActivity
API allows for the creation, retrieval, update, and deletion of sub-activities. The API endpoint is /api/polio/campaigns_subactivities/
.
To create a new sub-activity, send a POST request to the endpoint with the following data:
{\n \"round_number\": <round_number>,\n \"campaign\": <campaign_obr_name>,\n \"name\": <subactivity_name>,\n \"start_date\": <start_date>,\n \"end_date\": <end_date>,\n \"scopes\": [\n {\n \"group\": {\n \"name\": <group_name>,\n \"org_units\": [<org_unit_id>]\n },\n \"vaccine\": <vaccine_choice>\n }\n ]\n}\n
"},{"location":"pages/dev/reference/campaigns/subactivities.html#retrieve","title":"Retrieve","text":"To retrieve all sub-activities, send a GET request to the endpoint. To retrieve a specific sub-activity, send a GET request to /api/polio/campaigns_subactivities/<subactivity_id>/
.
To update a sub-activity, send a PUT request to /api/polio/campaigns_subactivities/<subactivity_id>/
with the new data.
To delete a sub-activity, send a DELETE request to /api/polio/campaigns_subactivities/<subactivity_id>/
.
Only authenticated users can interact with the SubActivity
API. The user must belong to the same account as the campaign associated with the round of the sub-activity.
Some terminology in Iaso come from DHIS2, some from ODK which mean that it can be a bit confusing. We will highlight some equivalences that might help you.
This is not (yet) the complete Data Model, but here are the main concepts/model in Iaso:
Account
. It represents roughly one client org or country. It also represents the natural limit of right for a user.Profile
that link it to an Account
and store extra parameters for the user.Project
. Projects are linked to one android version App via the app_id
. We use the link to control what a user can see from that app.DHIS2
is a standard server application and web UI in the industry to handle Health Data. Iaso can import and export data (forms and org unit) to it.OrgUnit
(Organizational Unit) is a Node of the GeoRegistry tree. e.g a particular Country, City or Hospital. each belonging to each other via a parent
relationship.OrgUnitType
e.g. Country, City, HospitalGroup
, e.g. Urban Region or Campaign 2017Group
but not Type
so when importing from a DHIS2 Instance all the type will be Unknown and OrgUnit will belong to group like Clinic
GroupSet
are Group of group. Used when we export Group to DHIS2geom
field is then used, or just a Point, the location
field is then used.DataSource
links OrgUnit and Group imported from the same source, e.g a DHIS2 instance, a CSV or a GeoPackage.source_ref
on the imported instance is used to keep the reference from the original source, so we can match it again in the future (when updating the import or exporting it back)SourceVersion
is used to keep each version separated. e.g each time we import from DHIS2 we create a new version.Task
are asynchronous function that will be run by a background worker in production. eg: Importing Data from DHIS2. see Worker section below for more info.Form
is the definition of a Form (list of question and their presentation).XSLForm
as an attached file.Instance
or Form instance is the Submission
of a form. A form that has actually been filed by a user.APIImport
are used to log some request from the mobile app so we can replay them in case of error. See vector_control wikiaudit.Modification
are used to keep a history of modification on some models (mainly orgunit). See audit wikiLink
are used to match two OrgUnit (in different sources or not) that should be the same in the real world. Links have a confidence score indicating how much we trust that the two OrgUnit are actually the same.They are usually generated via AlgorithmRun
, or the matching is done in a Notebook and uploaded via the API.
Iaso's online documentation is built using mkDocs and deployed on Github pages. A CNAME record allows it to be available on docs.openiaso.com
"},{"location":"pages/dev/reference/doc_setup/doc_setup.html#mkdocs-setup","title":"mkDocs setup","text":"The mkDocs build is configured in mkdocs.yml, at the root of the project The docs (markdown files) themselves as well as all other files related to mkdocs are located in the /docs
folder
The dependencies are in /docs/requirements.txt
. The file has been generated using pip-compile
and /docs/requirements.in
, except for the plugin mkdocs-static-i18n
which had its version upgarded manually.
The markdown files follow the format <name>.<locale>.md
to enable the localization plugin to display the right files
The menu is defined in mkdocs.yml: - default menu is under nav`` - french menu is under
plugins>i18n>languages>fr> nav`
We had to duplicate the whole menu because we have a nested menu. On the flip side this can allow us to have an entirely different menu structure for each language if we wish to do so.
"},{"location":"pages/dev/reference/doc_setup/doc_setup.html#github-pages-setup","title":"Github pages setup","text":"There is a deploy_doc
Github action that will build and deploy the documentation when pushing on main
. The action itself will deploy the branch gh-pages
. This in turn will trigger Github's own action to deploy the Github page.
IMPORTANT: - There is a CNAME
file in the /docs
folder. Removing or altering it will break the redirection of docs.openisao.com
to the Github page - The gh-pages
branch should be left alone. It only contains the built documentation and none of the Iaso code
Each docker container uses the entrypoint.
The entrypoint.sh
script offers a range of commands to start services or run commands. The full list of commands can be seen in the script. The pattern to run a command is
docker compose run <container-name> <entrypoint-command> <...args>\n
The following are some examples:
docker compose exec iaso ./manage.py test
docker compose run iaso bash
docker compose run iaso eval curl http://google.com
docker compose exec iaso ./manage.py help
docker compose exec iaso ./manage.py shell
docker compose exec iaso ./manage.py dbshell
docker compose exec iaso ./manage.py makemigrations
docker compose exec iaso ./manage.py migrate
docker compose exec iaso ./manage.py showmigrations
docker compose run iaso manage tasks_worker
All the container definitions for development can be found in docker-compose.yml
.
docker compose run
launches a new docker container, docker compose exec
launches a command in the existing container.
So run
will ensure the dependencies like the database are up before executing. exec
main advantage is that it is faster but the containers must already be running (launched manually)
run
will launch the entrypoint.sh script but exec will take a bash command to run which is why if you want to run the django manage.py you will need to use run iaso manage
but exec iaso ./manage.py
Also take care that run
unless evoked with the --rm
will leave you with a lot of left over containers that take up disk space and need to be cleaned occasionally with docker compose rm
to reclaim disk space.
Table of contents 2
What are entities? 3
How to create an entity? 3
Enable the feature 3
Create and upload the profile form 3
Create the entity type 4
Create an entity 4
How to configure how we display an entity? 6
In the web interface 6
In the list 6
In the details screen 7
In the mobile application 7
In the list 7
In the details screen 8
Searching for an entity 8
On the web 8
In the application 9
What are workflows? 9
Create a workflow 9
Follow-ups and changes 10
Follow-ups 10
Changes 10
Using values from the profile in subsequent forms 11
Publishing workflows 11
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#what-are-entities","title":"What are entities?","text":"We call an \u201cEntity\u201d anything that can move or be moved and that we want to track through time and Org Units. For example, a beneficiary, a car, a vaccination card, etc.
To differentiate between different kinds of entities, Iaso has a concept of \u201cEntity Type\u201d.
Iaso heavily relies on XLSForms, and entities are no exceptions. Therefore, an entity is represented by a submission to a form. This submission is referred to as the profile. The entity type defines which form has to be filled in.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#how-to-create-an-entity","title":"How to create an entity?","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#enable-the-feature","title":"Enable the feature","text":"In order to create an entity, your project must first enable the entity feature flag. You can set this flag either during its creation or by updating it later.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#create-and-upload-the-profile-form","title":"Create and upload the profile form","text":"Using the sheet application of your choosing, create an XLSForm which will contain all the questions related to your entity that are either fixed (I.e., first name and last name) or can evolve through time (I.e., a program to which an entity can be affiliated to).
Upload it on the server using the web application.
Note: The questions that can evolve through time should not be editable."},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#create-the-entity-type","title":"Create the entity type","text":"In the entity types screen, click on the \u201cCREATE\u201d button. Give the entity type a name and select the newly uploaded form as a reference form:
Note: We\u2019ll see later what \u201cList fields\u201d and \u201cDetail info fields\u201d are."},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#create-an-entity","title":"Create an entity","text":"In the mobile application, make sure that the data has been refreshed and are up to date with the backend server. You will now be able to see the entity screen.
At the moment, it is not possible to create an Entity from a web interface.
Click the \u201cAdd\u201d button in the application.
Select the entity type you want to create.
You will be prompted to confirm your selection.
You can then fill out the form to finalize your first entity.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#how-to-configure-how-we-display-an-entity","title":"How to configure how we display an entity?","text":"Within the entity type\u2019s configuration, it is possible for administrators to define which questions are displayed within lists and within the details screen.
This impacts how the web and mobile applications display entities, as shown below.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-web-interface","title":"In the web interface","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-list","title":"In the list","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-details-screen","title":"In the details screen","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-mobile-application","title":"In the mobile application","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-list_1","title":"In the list","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-details-screen_1","title":"In the details screen","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#searching-for-an-entity","title":"Searching for an entity","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#on-the-web","title":"On the web","text":"In the beneficiary list, you can filter by type and/or enter a query to filter based on the identifier or any of the list fields values.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-application","title":"In the application","text":"Clicking on the magnifying glass icon on the entity screen will lead you to the list of all entities and allow you to filter them quickly based on the identifier or any of the list fields values.
If you need a more fine-grained selection, you can click on the funnel icon, select a type and fill out the search form (second picture)
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#what-are-workflows","title":"What are workflows?","text":"As stated before, an entity is tracked through time and Org Units. In order to achieve this, Iaso links the subsequent submissions for an entity together and allows subsequent submissions to change the profile. In order for you to choose which forms should be presented next and what values override the properties of the profile, you can define a workflow.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#high-level-schema-of-the-workflows-models","title":"High Level Schema of the workflow's models","text":"classDiagram\nclass WorkflowVersion {\n Workflow workflow\n String name\n Form reference_form\n Enum status\n}\nclass Workflow {\n EntityType entity_type\n}\nclass EntityType {\n String name\n Form reference_form\n Account account\n Bool is_active\n}\n\nclass Form {\n String form_id\n String namer\n String device_field\n String location_field\n String correlation_field\n Bool correlatable\n JSON possible_fields\n String period_ty pe\n Bool single_per_period\n Int periods_before_allowed\n Int periods_after_allowed\n Bool derived\n UUID uuid\n}\n\nclass FormVersion {\n Form form\n File file\n File xls_file\n JSON form_descriptor\n String version_id\n}\n\nWorkflowVersion --> Form : Foreign Key (reference_form)\nEntityType --> Form : Foreign Key (reference_form)\nEntityType -- Workflow : One To One (entity_type)\nWorkflowVersion --> Workflow : Foreign Key (form) \nFormVersion --> Form : Foreign Key (form)\n
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#create-a-workflow","title":"Create a workflow","text":"In the entity types\u2019 list, click on the workflow icon
In the list of the workflow versions, create the \u201cCREATE\u201d button and give the version a name:
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#follow-ups-and-changes","title":"Follow-ups and changes","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#follow-ups","title":"Follow-ups","text":"They represent the next possible forms based on the state of the profile. They are based on a condition. In the following example, the mobile application will offer \u201cU5 Registration WFP\u201d as the next possible form if the first name is \u201cBill\u201d.
Reminder: \u201cFirst Name\u201d is one of the questions in the Entity Type\u2019s form."},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#changes","title":"Changes","text":"They represent the mapping of what value from a form will change the values in the profile.
In the example below, the \u201cTarget form\u201d is the Entity Type\u2019s form, and the \u201cSource form\u201d is the subsequent submission.
When a \u201cU5 Registration WFP\u201d form is filled out, the value entered in \u201cChild\u2019s Age in months\u201d will be copied into the profile\u2019s \u201cAge (Months)\u201d question. And the value entered in \u201cChild\u2019s Name\u201d will be copied into the profile\u2019s \u201cFirst Name\u201d question.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#using-values-from-the-profile-in-subsequent-forms","title":"Using values from the profile in subsequent forms","text":"Sometimes, you want a subsequent form to use values from the profile. In order to do so, just add a question with the same identifier and type as the value from the profile.
I.e., Let\u2019s assume the profile has 2 questions of type \u201ctext\u201d: first_name and last_name. By adding a read-only similar question in your subsequent forms, the value will be available to you.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#publishing-workflows","title":"Publishing workflows","text":"Once a workflow version has been published, it is marked as finalized, and it cannot be edited anymore. Only workflows in \u201cdraft\u201d can be edited.
If you want to edit a finalized workflow, you first need to duplicate it using the \u201cCopy version\u201d button. A new draft version is then created with the same content.
"},{"location":"pages/dev/reference/env_variables/env_variables.html","title":"Environnement variables","text":""},{"location":"pages/dev/reference/env_variables/env_variables.html#db-connection-related","title":"DB connection related","text":"the url is build based on the following env variables
RDS_USERNAME\nRDS_PASSWORD\nRDS_HOSTNAME\nRDS_DB_NAME\nRDS_PORT\n
the SQL dashboard use a dedicated user/password with readonly access to the data
DB_READONLY_USERNAME \nDB_READONLY_PASSWORD\n
"},{"location":"pages/dev/reference/env_variables/env_variables.html#aws-related","title":"AWS related","text":"Storing the various files like
AWS_ACCESS_KEY_ID:\nAWS_SECRET_ACCESS_KEY:\nAWS_S3_REGION_NAME\nAWS_STORAGE_BUCKET_NAME:\nAWS_S3_ENDPOINT_URL: (used to for ex to point to minio)\n
for async task
BACKGROUND_TASK_SERVICE : default to SQS : possible values are SQS POSTGRES\nBEANSTALK_SQS_REGION\nBEANSTALK_SQS_URL\n\n
"},{"location":"pages/dev/reference/env_variables/env_variables.html#security-settings","title":"Security Settings","text":""},{"location":"pages/dev/reference/env_variables/env_variables.html#django-settings","title":"Django settings","text":"Iaso allows to set some of Django security settings as environment variable. To activate these features set the environment variable to \"true\"
. Default is \"false\"
CSRF_COOKIE_HTTPONLY \nCSRF_COOKIE_SECURE \nSESSION_COOKIE_SECURE\n
"},{"location":"pages/dev/reference/env_variables/env_variables.html#cors","title":"CORS","text":"It is possible to setup a IASO server with CORS authorizing access from any server with the following environment variable \"ENABLE_CORS\"
. Default is \"true\"
Set the environment variable DISABLE_PASSWORD_LOGINS
to the value\"true\"
in case you wish to deactivate passwords using login:
If you don't provide a SENTRY_URL, sentry won't be configured
name optional default value description --- SENTRY_URL true - url specific to your sentry account SENTRY_ENVIRONMENT true development environnement (dev, staging, prod,...) SENTRY_TRACES_SAMPLE_RATE true 0.1 float between 0 and 1 : send 10% SENTRY_ERRORS_SAMPLE_RATE true 1.0 float between 0 and 1 : send everything SENTRY_ERRORS_HTTPERROR_SAMPLE_RATE true 0.8 float between 0 and 1 : send 80% of the errors"},{"location":"pages/dev/reference/env_variables/env_variables.html#maintenance-mode","title":"Maintenance mode","text":"MAINTENANCE_MODE
(default is \"false\"
)
If you need to set up IASO in maintenance mode, meaning that it will display at / a page indicating that the server is under maintenance, and give a 404 answer to all requests except for /health or /_health (wich we encourage to use for status monitoring), you can set the environment variable MAINTENANCE_MODE
to the value \"true\"
Frontend assets include JS, CSS, translations and images. They are all handled by webpack. Most of the structure is taken from this blog post: http://owaislone.org/blog/webpack-plus-reactjs-and-django/
Frontend assets are mounted on the pages via the django-webpack-loader <https://github.com/owais/django-webpack-loader>
__
There are two webpack configuration files: webpack.dev.js
and webpack.prod.js
.
A JS production build is created inside the docker-file, via npm run build
the start_dev
entry point starts a webpack development server, that watches assets, rebuilds and does hot reloading of JS Components.
The CSS build is separate, and can contain both .sass
and .css
files. They spit out a webpack build called styles.css
.
Each page has their own JS entry point (needs to be defined in both webpack files). On top of that, they load a common chunk, containing react
, react-intl
and other stuff that the webpack common chunk
plugin finds is shared between the apps.
Including a JS bundle via django-webpack-loader ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If we have created a new JS app MyCustomApp
in hat/assets/js/apps/
.
The entrypoint should be /dashboard/my-custom-app
Folders and files affected::
+ hat/\n + assets/\n + js/\n + apps/\n + MyCustomApp\n . index.js\n . MyCustomApp.js\n . MyCustomAppContainer.js\n\n + dashboard/\n . urls.py\n . views.py\n\n + templates/\n + dashboard/\n . my_custom_app.html\n\n . webpack.dev.js\n . webpack.prod.js\n
These are the steps to visualize it within the dashboard:
hat/webpack.dev.js
include a new entry
. 'my_custom_app': [\n 'webpack-dev-server/client?' + WEBPACK_URL,\n 'webpack/hot/only-dev-server',\n './assets/js/apps/MyCustomApp/index'\n ],\n
hat/webpack.prod.js
include a new entry
. 'my_custom_app': './assets/js/apps/MyCustomApp/index',\n
hat/dashboard/views.py
include a new view. @login_required() # needs login?\n @permission_required('cases.view') # the needed permissions\n @require_http_methods(['GET']) # http methods allowed\n def my_custom_app(request: HttpRequest) -> HttpResponse:\n return render(request, 'dashboard/my_custom_app.html')\n
hat/dashboard/urls.py
include a new url pattern. url(r'^my-custom-app/.*$', views.my_custom_app, name='my_custom_app'),\n
url(r'^my-custom-app/.*$', views.my_custom_app, name='my_custom_app'),\n
hat/templates/dashboard
create a new template file my_custom_app.html
. {% extends 'app.html' %}\n {% load i18n %}\n {% load render_bundle from webpack_loader %}\n\n {% block header %}\n <h1 class=\"header__title\">{% trans 'My Custom App' %}</h1>\n {% endblock %}\n\n {% block content %}\n\n <div class=\"content\">\n <div id=\"app-container\"></div>\n </div>\n\n {% render_bundle 'common' %}\n {% render_bundle 'my_custom_app' %}\n <script>\n HAT.MyCustomApp.default(\n document.getElementById('app-container'),\n '/dashboard/my-custom-app/'\n )\n </script>\n {% endblock %}\n
"},{"location":"pages/dev/reference/front-end_reference/front-end_reference.html#testing-the-production-build","title":"Testing the production build","text":""},{"location":"pages/dev/reference/front-end_reference/front-end_reference.html#stop-any-containers-that-might-be-currently-running","title":". Stop any containers that might be currently running.","text":""},{"location":"pages/dev/reference/front-end_reference/front-end_reference.html#start-the-containers-with","title":". Start the containers with:","text":".. code:: shell
TEST_PROD=true docker compose up\n
When the setup is run with TEST_PROD=true
, it will exit the unneeded containers webpack
and jupyter
. It will also run the webpack build during startup, so that there is no need to rebuild the image for that.
docker compose run hat test_js\n
"},{"location":"pages/dev/reference/front-end_reference/front-end_reference.html#adding-new-assets-in-packagejson","title":"Adding new assets in package.json","text":"Unfortunately, for now you need to rebuild the container after adding or upgrading packages in package.json
.
.. code:: shell
docker compose build\n
or
.. code:: shell
docker compose up --build\n
"},{"location":"pages/dev/reference/front-end_reference/front-end_reference.html#translations","title":"Translations","text":"Translations are extracted on the first webpack build. Just like the django translation strings; translations are downloaded for every Travis CI <https://travis-ci.com>
__ build, and uploaded on the development
branch.
See also: this pull request <https://github.com/BLSQ/iaso/pull/120>
__
The script show-lint-problems
can be turned into a VSCode task that will show all linter errors in VSCode's PROBLEMS Tab.
Steps to follow:
Go to Terminal>Configure task
Select npm: show-lint-problems
Add $eslint-stylish
to the problemMatcher
array
Run the task: Terminal>Run Task...> npm: show-lint-problems. IMPORTANT: you need to run the task this way. Running the script directly from the terminal using npm will not enable VS Code to display the problems in the PROBLEMS tab
You should be able to see and track the problems through the dedicated tab. CAUTION: if you navigate to a file through the tab, then close the file, it will be removed from the problems list, even if it wasn't changed. This seems to be a problem with using npm through VSCode's tasks
See the library's README <https://github.com/BLSQ/bluesquare-components/blob/main/README.md>
__ for the general setup.
When depending on a local version of the library:
Your local folder should be on the same level as the iaso folder, so that the path to the tgz file in your package.json is : ../bluesquare-components/bluesquare-components-0.1.0.tgz
Run docker compose build --build-arg LIBRARY=<name-of-the-library-image>
Please do a rough design before any implementation and discuss it with the rest of the team. It doesn't need to be too detailed but you should have at least a list of Model, the important fields on it, the security model and what endpoint you will add.
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#security","title":"Security","text":"When Adding New feature that will add Model and API please think about the security model. You can start by asking yourself these questions:
Iaso is multi tenant. Tenant are called and represented by the model Account
. It represents roughly one client org or country. It also represents the natural limit of right for a user.
So all new model and API per default should support tenancy. It's kind of annoying to add it later.
We have two kind of tenancy, one per Project, one per Account. A Project represent a mobile app, there might be several linked to an account. You will have to consider which want you one to use. In some case it might be both.
In some case if your new model is linked to existing one it might derive the tenancy from there (for example FormVersion derive their tenancy from Form).
In practice this will consist on: 1. adding a ForeignKey to Account or Project on your model. 2. At creation 2. in your ViewSet filtering on the Object in the account (see filtering for user)
In most case if this an API for the Mobile the tenancy will be per project. But if that's not the case and you are really not sure, there is nobody to ask and you need to advance, add it on the Account it will be simpler.
Don't hesitate to ask if you don't understand what tenancy means or how it works.
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#in-the-broad-lines-adding-a-complete-new-feature-will-consist-of","title":"In the broad lines adding a complete new feature will consist of","text":"/api/yourmodel
Always add the field created_at
, updated_at
. This allow us the minimum of traceability and is useful in debug.
created_at = models.DateTimeField(auto_now_add=True)\n updated_at = models.DateTimeField(auto_now=True)\n
You might need Soft delete (see Soft Delete section)
Don't forget the tenancy.
Examples: There is a very simple Model/API example in the directory plugins/tests
that you can use as a template.
Please use serializers !
This will allow the API to be autodocumented in the swagger and the browsable API interface. You can check the swagger at /swagger/
For the ViewSet, always^ inherit ModelViewSet from iaso.api.common, not the one from DRF ! This will handle the Pagination that is particular to Django correctly out of the box.
^ Except in some case obviously but it should be the default
For the default case you ModelViewSet should be very simple, and you should not have to reimplement def list()
and def create()
etc... if you properly did the Serializer and inherited from iaso.api.common.ModelViewset
Per default you will want to filter what is viewable by the correctly connected user (at minimum for the tenancy, see relevant section)
To do so add a get_queryset()
method on your ViewSet which will filter the queryset on the user. It's not a bad idea to move the logic of that code directly on your Model queryset by adding a Queryset.for_user(user: User)
on your model so we can reuse it on other models. See for example TeamQuerySet.
We use the DRF system with permission_class
see DRF Doc. See also plugins/test/api.py
for an example.
By default all the API require to be logged but any user can post GET / POST / PATCH / DELETE so beware of that.
Further restriction can be added using http_method_names = [\"put\"]
if you want to be extra sure, but that shouldn't be the only check method.
Do not hesitate to put test to check that the method are effectively restricted to both authorized and unauthenticated user so they are not re-enabled by default.
If you want to check if an user has a permission there is a HasPermission class in iaso.api.common.py
TODO : expand section
Set the filter_backends
config key in your ViewSet, with per default at least, the backends filters.OrderingFilter
and DjangoFilterBackend
filter_backends = [\n filters.OrderingFilter,\n DjangoFilterBackend,\n\n ]\n
This will allow ordering and filtering on the key present on the model automatically.
The front dev will push you to include special filter with Javascript case name like FormId
or stuff which will require special case in list() because that's what they are used to but in 99% of the case this is not needed and there is already a filter in python case form_id
. The normal django operator can also be used __in
for list, __gte
for >=
, iexact
to ignore case, etc..
See the field lookup for the complete reference https://docs.djangoproject.com/en/4.1/ref/models/querysets/#field-lookups
All these filters are conveniently listed in the swagger for each endpoint and in the browsable API
If your model use SoftDelete include the DeletionFilterBackend
filter. See the SoftDelete section
DeletionFilterBackend,\n
If you need more control on how fields can be used for ordering and filtering you can respectively use ordering_fields
and filterset_fields
. See the django-filter and drf doc.
Frontend expect \"big\" api to accept a search
filter which usually does a icontains
on any of the model StringField (e.g. name or description) or related model (e.g the org unit name).
The filters.OrderingFilter
use the ?order_by
query parameters, multiple field can be specified, separated by ,
, -
in front of the field can be used to specify to reverse the order
In very special case if you need strange manipulation for ordering or filtering, adding an annotation on the queryset might help a lot. Since it does all the calculation on the database side, this allows use to add filter on fields that would otherwise not be possible without retrieving a lot of data on the backend.
You might need to add them in ordering_fields
and filterset_fields
. (TODO Olivier : check)
If this API is accessible via mobile, add a separate endpoint for the mobile to use, in /api/mobile
, even if it is the same as the \"regular\" web
endpoint and that you connect it to the same ViewSet. This allows us flexibility if we have to break compatibility in the future.
For date in new API endpoint we use the RFC format (2022-03-02 23:34...) that is the default in DRF so there is nothing to be done. Old endpoint might still use the old format in Timestamp.
In the endpoint for mobile we always use Timestamp. You can use TimestampField and DateTimestampField in your Serializer for this.
Take special care when modifying the API used by the mobile, we don't have a complete list, but we still have old version of the APP using very old endpoints that newer version don't use anymore.
For API endpoint that the mobile APP will POST to, you should check with Martin if you may need the decorator @safe_api_import
to ensure no data is lost. See /iaso/hat/vector_control/README.md
For data that will be presented as a table, we will want to provide CSV and XLS export to the user in nearly all cases.
We don't use the CSV export provided by DRF for that and use a bit of code that we copy past and should really be refactored.
TODO: Expand section
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#softdelete","title":"SoftDelete","text":"Soft Delete is a way to mark an Object as deleted without actually deleting it from the database. That way we can still show them to user in the interface it they choose to and the user can restore it easily.
We have a standard way to implement it: - Have your model inherit from the SoftDelete. - This will add the deleted_at field. When the field is null it's not deleted if it contains a data it is deleted.
In the ViewSet add the filter DeletionFilterBackend.
Test that you can restore by doing a patch on your row on the deleted_at
fields
if you add a new permission, don't forget to add it in the Frontend, or it will not be displayed properly. See the instruction on the top of menupermissions/models.py
If you add New model, add them in the Admin if you don't know which fields or filter to add just add at least the minimum, we can expand it latter.
Minimum Admin for a Model (BlogPost in this Example), in admin.py
class BlogPostAdmin(admin.ModelAdmin):\n pass\n\nadmin.site.register(BlogPost, BlogPostAdmin)\n
If you want a base to be a mot more fancy:
search_fields = (\"title\", \"content\")\n list_display = (\"title\", \"author\", \"created_at\", \"updated_at\")\n date_hierarchy = \"created_at\"\n list_filter = (\"author\", \"updated_at\")\n readonly_fields = (\"created_at\", \"updated_at\")\n\n
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#configuration-settings","title":"Configuration settings","text":"We configure the Iaso deployement (the server) via environment flags. So if you add something configurable on the whole server level do it that way. See https://12factor.net/config if your are not familiar with the philosophy.
Note :There are of course some exceptions and thus some settings are configured in the Database. Notably for the Polio plugins. But please keep that exceptional.
You can change your own local configuration in the file .env
. Do note that if you make any modification in your .env
or in your docker-compose.yml
file. You will need to restart the whole docker compose for it to take effect (Ctrl-c your current docker compose and bring it back up with docker compose up
If you add a new Environement variable to allow some configuration: 1. Do not access the Enviroement variable directly from your python code ! 1. Instead add it as a variable in the settings.py
. 1. Add a comment explaing what this variable does. 1. Always provide a default value 1. To allow developer to change the variable locally add it in docker-compose.yml
Also KISS, the less configuration the better !
There is no comprehensive documentation of all the configuration settings except what is in settings.py so it's important that you comment it well !
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#example","title":"Example","text":"settings.py
# Application customizations\nAPP_TITLE = os.environ.get(\"APP_TITLE\", \"Iaso\")\n
diff --git a/docker-compose.yml b/docker-compose.yml\nindex 057b81e39..49b29ac30 100644\n--- a/docker-compose.yml\n+++ b/docker-compose.yml\n@@ -44,6 +44,7 @@ services:\n THEME_PRIMARY_BACKGROUND_COLOR:\n FAVICON_PATH:\n LOGO_PATH:\n+ APP_TITLE:\n SHOW_NAME_WITH_LOGO:\n RDS_USERNAME: postgres\n RDS_PASSWORD: postgres\n
from django.conf import settings\nsettings.APP_TITLE\n
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#feature-flags","title":"Feature Flags","text":"Feature flags allow to enable special feature or behaviour for our clients. The use case are: certain workflow that are particular to a client use case or feature that are still in development and that we are co-developing with the client.
Example of feature flag: Enable editing an org unit geography directly via the web map or requiring user to log in into the mobile app to submit.
We have two kind of flags in iaso: Mobile and Web
FeatureFlag
that are linked to Project
(there might have multiple Mobile application per account)AccountFeatureFlag
for the whole account. It is mainly used to control the behaviour of the web frontend.The list of flags are stored in database tables, to add a new Flag migration are used.
Usually client can control which Project/Mobile flag they have but not the one on the Account level.
To toggle a Mobile Feature flag for a client, add it in the dashboard -> Admin -> Projects -> Edit a project -> Options
.
To toggle an Account Feature flag, it is in the django Admin
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#adding-a-new-feature-flag-via-a-migration","title":"Adding a new Feature Flag via a Migration","text":"For example a Project Feature Flag
python manage.py makemigrations --empty yourappname
def create_feature_flags(apps, schema_editor):\n FeatureFlag = apps.get_model(\"iaso\", \"FeatureFlag\")\n FeatureFlag.objects.create(\n code=\"CHECK_POSITION_FOR_FORMS\",\n name=\"Mobile: Enforce users are within reach of the Org Unit before starting a form.\",\n )\n\n\ndef destroy_feature_flags(apps, schema_editor):\n FeatureFlag = apps.get_model(\"iaso\", \"FeatureFlag\")\n FeatureFlag.objects.filter(code=\"CHECK_POSITION_FOR_FORMS\").delete()\n\n\nclass Migration(migrations.Migration):\n dependencies = [\n (\"iaso\", \"0150_profile_home_page\"),\n ]\n\n operations = [\n migrations.RunPython(create_feature_flags, destroy_feature_flags),\n ]\n
See https://docs.djangoproject.com/en/4.0/topics/migrations/#data-migrations
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#typing-and-annotations","title":"Typing and annotations","text":"If you use an annotate and mypy complains when using the field. You can add the field on the model using Annotated. for example:
from typing_extensions import Annotated, TypedDict\n\nclass LastBudgetAnnotation(TypedDict):\n budget_last_updated_at: datetime\n\n\nclass MonSerializeur()\n def get_budget_last_updated_at(self, campaign: Annotated[Campaign, LastBudgetAnnotation]):\n if campaign.budget_last_updated_at:\n return campaign.budget_last_updated_at.strftime(\"%Y-%m-%d\")\n\n\n
Do not use the WithAnnotations
from django-stubs. it doesn't work with our setup. I think it's a problem of python 3.8
settings.py
","text":"The new environment variable need to be listed in the docker-compose.yml
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#logging-is-broken","title":"Logging is broken","text":"Symptom : The request are not displayed in the the sever console, or in productoin /var/app/log
Someone probably imported a function from the tests
directory, which disable logging. Please don't do that. Move the imported code elsewhere. See https://github.com/BLSQ/iaso/commit/b22b1bcc31a5b05650b675a3c168285103f9bcf8
journalctl -u web
First of all make sure your eslint, prettier, typescript environnement is set properly.
On VSC code you can format code following IAOS rules while saving in the settings:
\"[javascript]\": {\n \"editor.formatOnSave\": true,\n},\n\"[typescript]\": {\n \"editor.formatOnSave\": true,\n},\n
Make sure you installed eslint extension too. On each file you can have only one component. Use constants, and config files to store static data. Don't be afraid to split your code into smaller parts, using understandable naming convention. It will help to understand what you are doing in your code.
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#legacy","title":"Legacy","text":"Class component, proptypes are still old way to create features in IASO. Please use hooks
, typescript
and arrow component
. We already have a lot of typing done in each domain of the application (forms, submissions, org units, ... )
Lots of components used in IASO has been moved to a separate repo. We tried to be the most generic has possible in this repo, people from outside IASO should be able to use it in their own project. To use it locally, checkout the repo on the same level has ISO and run: LIVE_COMPONENTS=true pm run dev
in IASO folder. This will use directly the code from your local repo. To make it available too everybody you have to build new files with npm run clean && npm run build
in bluesquare-component folder.
Main index file is located here: hat/assets/js/apps/Iaso/index
This is the entrypoint of the app, setting up providers, theme, react-query query client, custom plugins,...
components
Used to store generic components that can be used everywhere, like inputComponent
, buttons
, ...
domains
For every big feature entity in IASO (forms, org units, plannings, ...) we have a domain folder allowing to display related pages. - index
is generally used to display a list of items - details
the details of an item - config
used to store constants for the domain (columns, default order, ...) - hooks
: dedicated hooks to make requests or compute specitic data for the domain - components
: mostly intermediate components using smaller ones to construct domain page - messages
: translations messages used for this specific domain config: used to store constants like defaultOrder, columns, 'baseUrls' - types
: All types related to the domain
In our effort to maintain readability and conciseness in our code, we are transitioning away from the makeStyles
hook and adopting a new approach for styling our components. We will now use a separate object, styles
, to define our styles outside of the component function. This object will then be referenced within the sx
prop in our JSX.
Here's how to apply this approach:
styles
object using the SxStyles
type from hat/assets/js/apps/Iaso/types/general.ts
. This helps with readability and keeps the component code clean.sx
prop by referencing the styles
object properties.Example:
const styles: SxStyles = {\n root: {\n cursor: 'pointer',\n },\n tooltip: {\n color: 'text.primary',\n bgcolor: 'background.paper',\n boxShadow: (theme: Theme) => theme.shadows[1],\n '& .MuiTooltip-arrow': {\n color: 'background.paper',\n },\n },\n noResult: {\n textDecoration: 'underline dotted',\n },\n};\n\n...\n <Box sx={styles.root}>\n ...\n
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#maps","title":"Maps","text":"We are using leaflet latest version and react-leaflet (LTS version 3). To use latest version of react-leaflet we need to upgrade to react 18.
Styles are located in bluesquare-components
, you have to import it on each map:
const styles = (theme) => ({\n mapContainer: {\n ...commonStyles(theme).mapContainer,\n },\n});\n
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#from-react-leaflet","title":"From react-leaflet","text":"MapContainer
The main container of the Map. Props we use: - bounds
: not required, bounds of markers and shapes displayed, used by fit to bound, doc here. - boundsOptions
: not required, options related to bounds, doc here. - zoomControl
: required and set to false
, in order to use the CustomZoomControl
. - whenCreated
: not required,to use a ref of the map in the same component as the MapContainer, you get it by doing whenCreated={mapInstance => { map.current = mapInstance; }}
default props:
doubleClickZoom={false}\nscrollWheelZoom={false}\nmaxZoom={currentTile.maxZoom}\nstyle={{ height: '100%' }}\ncenter={[0, 0]}\nzoomControl={false}\nkeyboard={false}\nbounds={bounds}\nboundsOptions={boundsOptions}\n
Scalecontrol
Used to display a scale on the bottom left of the map.
Props we use: - imperial
: always set to false
We use other component from react-leaflet not listed here as they are optionnal and used like describe in their docs.
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#custom-components","title":"Custom components","text":"CustomZoomControl
This will display an extended zoom control on the top left of the map. You can zoom in and out, select an area to zoom in and fit the map to the bounds of the map.
Props: - bounds
: not required, computed bounds displayed on the map, doc here. - boundsOptions
: not required, options related to bounds, doc here. - bound
: not required, a boolean to fit to bounds on load, not working if bounds stays undefined.
CustomTileLayer + TilesSwitchDialog
Control to display a dialog allowing to change the tile layer of the map, on the top right of the map Those twot components are going together, maybe we should refactor it to a single component.
CustomTileLayer Props: - currentTile
: required, active tile of the map, usually setted in the map itself with a useState
.
TilesSwitchDialog Props: - currentTile
: required, active tile of the map, usually setted in the map itself with a useState
. - setCurrentTile
: required, method to update current tile on the map.
MapToggleTooltips
A switch to show or hide tooltips on the map.
Props:
showTooltip
: required, usually setted in the map itself with a useState
.setShowTooltip
: required, method to showTooltip or not.MapToggleFullscreen
A switch to set the map fullscreen or not.
Props:
isMapFullScreen
: required, usually setted in the map itself with a useState
.setIsMapFullScreen
: required, method to set into fullscreen or not.MapToggleCluster
A switch to allow clustering or not of marker on the map. You should use MarkerClusterGroup
from react-leaflet-markercluster
Props:
isClusterActive
: required, usually setted in the map itself with a useState
.setIsClusterActive
: required, method to enable custering of markers or not.MarkerComponent / CircleMarkerComponent Components used to display a marker on the map. Props: - see js file for not required props, mainly the same props as Marker
from react-leaflet. - PopupComponent
: not required, Popup used while clicking on the marker - TooltipComponent
: not required, Tooltip used while hovering the marker - item
: required, an object with latitude
an longitude
arguments, those are numbers
MarkersListComponent Used to display a list of markers
Props: - markerProps
: not required, props spreaded to the marker - items
: required, array of items use by previous component - PopupComponent
: not required, Popup used while clicking on the marker - TooltipComponent
: not required, Tooltip used while hovering the marker - onMarkerClick
: not required, method applied while clicking on the marker on the map - isCircle
: not required, display marker as a circle or not - onContextmenu
: not required, method applied while right clicking on the marker on the map
Most tables we use need to support filters and deep linking. We have a TableWithDeepLinking
component for that purpose, which is a wrapper on the Table
from Bluesuare-components.
The typical props to pass are: - data
: the table data. Usually originate s from a react-query
hook - page
: the current page. Usually returned by the API - pageSize
: the amount of rows to display on each page. Also comes from the API - count
: the total amount of items in the page. From the API - pages: total number of pages. From the API -
baseUrl: the baseUrl the table will redirect to -
params: the params of the current location.
TableWithDeepLinkwill combine them with
baseUrlto redirect to the correct location -
extraProps: an object. The
loadingkey will be used to manage the table's loading state. Other values will force a table re-render when they change (similar to
useEffectdeps array), which can be useful in some situations -
columns: an array of objects of type
Column` (imported from bluesquare-components). It's usually defined in a custom hook in order to easily handle translations.
Note: useDeleteTableRow
When performing DELETE
operations from the table that will reduce the amount of table rows, we can run into a pagination bug. To avoid it, use useDeleteTableRow
in the useDelete<whatever>
hook that will return the delete function:
export const useDeleteWhatever = (\n params: Record<string, any>,\n count: number,\n): UseMutationResult => {\n const onSuccess = useDeleteTableRow({\n params,\n pageKey: 'whateverPage', // optional, will default to \"page\"\n pageSizeKey: 'whateverPageSize', //optional, will default to \"pageSize\"\n count,\n invalidateQueries: ['whatever'], // optional\n baseUrl: baseUrls.whatever,\n });\n return useSnackMutation({\n mutationFn: deleteWhatever,\n options: { onSuccess },\n });\n};\n
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#filters","title":"Filters","text":"Most tables we display come with one or several filters, most commonly a text search and date filters.
It's a global feature in Iaso that all filter searches performed on pages are deep-linked, i.e.: the parameters of the filters are saved in the url, so users can share the results of their search/filtering. This has an impact on the architecture of Iaso:
All search fields need to be declared in routes.js
, under the params
key. Important: params
is an ordered list. Passing them in the wrong order in the URL will result in a 404.
Applying a filter doesn't change the component state per se but results in a redirection. Depending on the use cases, we may or may not want to save consecutive filters/searches in the routers history
We have a few ready-made components for filters:
InputComponent
: handles most types of inputs: text, select, checkbox, radioOrgUnitTreeviewModal
: handles searches on org unitsDatePicker
and DateRange
: handle datesInputComponent
takes a keyValue
prop, which is a string that corresponds to the url parameter that stores the filter value, and an onChange
prop which is a function with the signature (keyValue,value) => void. DateRange
takes a keyDateFrom
and a keyDateTo
that play the same role for the start and end date respectively.
We also have a useFilterState
hook that handles the state and update methods for filters of a given page:
const { filters, handleSearch, handleChange, filtersUpdated } =\n useFilterState({\n baseUrl,\n params,\n withPagination: false,\n saveSearchInHistory: false,\n });\n
baseUrl
: is the url of the pageparams
: the parameters passed to the url. useFilterState
will generate the state for the filters based on those. false
the hook will remove parameters related to table pagination (page
, pageSize
and so on)saveSearchInHistory
: if true
, the redirection will use redirect
i.o redirectToReplace
and the searchg will be saved in the router's history, meaning that using the back arrow will bring the user to the previous search and not the previous page. type?:string
to type: string | undefined
const
to let
:```javascript // BAD let myVar = \"placeholder\" if(otherVAlue) { myVar = otherValue }
// GOOD const myVar = otherValue ?? \"placeholder
```
// BAD\nconst username = user => user.firstname + user.lastname\n\n// GOOD\nconst makeUsername = user => user.firstname + user.lastname\n
// BAD, we're just returning a value\nconst useMyValue = (value :string) => {\n return parseInt(value,10)\n}\n\n// GOOD, because of useSafeIntl, the return value will change with user locale\nconst useMyValue = (value :IntlMessage) => {\n const { formatMessage } = useSafeIntl()\n return formatMessage(value)\n}\n\n//GOOD, the returned object is memoized\nconst useMyValue = (value: string) => {\n return useMemo(() => {\n const result = {\n isNumber:false, \n value\n }\n if (parseFloat(value)){\n result.isNumber = true\n }\n return result\n },[value])\n}\n\n// GOOD, as side effect will be triggered after the first render\nexport const useSkipEffectOnMount = (func:Function, deps:Array<unknown>) => {\n const didMount = useRef(false);\n\n useEffect(() => {\n if (didMount.current) {\n func();\n } else {\n didMount.current = true;\n }\n }, deps);\n};\n\n
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#remarks","title":"Remarks","text":"params
listed are ordered, meaning you can get a 404 when they are not in the right order. Related to this, the paginationPathParams
that we spread in most routes should come first, right after the accountId
to avoid getting 404 because of automatic redirections@cypress
in a comment on your PRrelease
label to itsuggestion
block. More information can be found here on point 6.To configure a public registry, follow these steps:
"},{"location":"pages/dev/reference/public_registry/public_registry.html#step-1-activate-the-plugin","title":"Step 1: Activate the Plugin","text":"First, you need to activate the plugin by adding \"registry\"
to the PLUGINS
variable in your settings.
PLUGINS = registry,...\n
"},{"location":"pages/dev/reference/public_registry/public_registry.html#step-2-make-a-data-source-public","title":"Step 2: Make a Data Source Public","text":"default_registry
(this value is hardcoded in the front-end at the moment).Here is an example configuration:
https://www.example.com
default_registry
{\"fields\": [\"Name\"]}
polioTest
polio
Polio 1
com.poliooutbreaks.app
After filling in all the required fields, save the configuration. Your public registry should now be configured and ready to use.
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html","title":"SQL Dashboard feature","text":"For super user across account there is a way to run raw read only SQL queries at the /explore/
page : https://iaso.bluesquare.org/explore/ e.g SELECT name FROM iaso_orgunittype
This is useful to check the database state and query data accross different client account. You can also save query and share them with others.
This feature is implemented via the excellent Django SQL Dashboard, their documentation has more complete information: https://django-sql-dashboard.datasette.io/en/stable/index.html
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#tips","title":"Tips","text":""},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#bar-charts","title":"Bar charts","text":"You can generate bar chart by having two column named bar_label
and bar_quantity
Examples
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#example-number-of-org-unit-per-type-in-a-project","title":"Example: Number of Org Unit per type in a project","text":"select iaso_orgunittype.name as bar_label, count(org_unit.id) as bar_quantity \nfrom iaso_orgunittype \n join iaso_orgunittype_projects on iaso_orgunittype.id = iaso_orgunittype_projects.orgunittype_id \n left join iaso_orgunit org_unit on iaso_orgunittype.id = org_unit.org_unit_type_id \n where iaso_orgunittype_projects.project_id = 1 \ngroup by iaso_orgunittype.id \norder by bar_quantity\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#orgunit-hierarchy-linked-to-an-org-unit","title":"OrgUnit hierarchy linked to an org unit","text":"SELECT * FROM iaso_orgunit WHERE path ~ '*.104133.*'\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#use-of-parameters","title":"Use of parameters","text":"You can use parameter, this will automatically create an input.
If you save them as a dashboard it will allow passing the paramter in the url
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#example-number-of-submission-per-form-and-per-org_unit-in-a-particular-sourceversion-version_id","title":"Example number of submission per form and per org_unit in a particular SourceVersion (version_id)","text":"SELECT \"iaso_orgunit\".\"path\", \n \"iaso_orgunit\".\"name\", \n \"iaso_instance\".\"form_id\", \n count(\"iaso_instance\".\"id\") filter \n (WHERE (not (\"iaso_instance\".\"file\" = '' and \"iaso_instance\".\"file\" is not null) and \n not (\"iaso_instance\".\"deleted\" and \"iaso_instance\".\"deleted\" is not null))) as \"instances_count\" \n\nFROM \"iaso_orgunit\" \n JOIN \"iaso_instance\" \n ON (\"iaso_orgunit\".\"id\" = \"iaso_instance\".\"org_unit_id\") \n and version_id = %(version_id)s\nGROUP BY \"iaso_orgunit\".path, \"iaso_orgunit\".\"id\", \"iaso_instance\".\"form_id\" \norder by \"iaso_orgunit\".path \nlimit 100;\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#use-multiple-ids","title":"Use multiple ids","text":"This tips is useful to allow passing multiple ids, separated per ,
select * from iaso_form where\niaso_form.id = ANY (string_to_array(%(form_ids)s::text, ',')::int[])\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#multi-line-chart","title":"Multi Line chart","text":"You can generate multi line chart by naming columns line_label
, line_quantity
and line_category
(you need all three)
select line_label,\n line_category,\n sum(line_quantity) over (PARTITION BY line_category order by line_label) as line_quantity\nfrom (\nselect TO_CHAR(date_trunc('month', COALESCE(iaso_instance.source_created_at, iaso_instance.created_at)), 'YYYY/MM') as line_label, count(*) as line_quantity, iaso_project.name as line_category from iaso_instance inner join iaso_project on iaso_instance.project_id = iaso_project.id\n group by line_label, iaso_project.name order by line_label, line_quantity desc limit 200\n) as data\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#cumulative-sum","title":"Cumulative sum","text":"To generate a cumulative sum (particularly useful for progression over time). Wrap your query with
select line_label,\n line_category,\n sum(line_quantity) over (PARTITION BY line_category order by line_label) as line_quantity\nfrom (\n YOUR QUERY\n) as data\n
See previous example.
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#random-data-generation-example","title":"random data generation example","text":"select line_label, \n line_category, \n sum(line_quantity) over (PARTITION BY line_category order by line_label) as line_quantity \nfrom (select TO_CHAR(gen_date.generate_series, 'YYYY/MM') as line_label, \n (random() - 0.2) * 1000::int as line_quantity, \n name as line_category \n from (select * \n from generate_series('2008-03-01 08:00'::timestamp, \n '2009-03-04 12:00'::timestamp, '1 month')) gen_date \n cross join (VALUES ('foo'), ('bar'), ('baz')) as categories (name)) as data\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#searching-in-org-units-org-unit-types","title":"Searching in Org Units, Org Unit Types","text":"Here are some examples of queries to find Org Units, their types, reference forms and everything linked to the hierarchy of a specific Org Unit.
As we are using Postgre's ltree extension and django-ltree to model this hierarchy, specific SQL operators are available to search in a performant way and queries can be cumbersome.
Let's say you have a OrgUnit with ID : XXXXXX
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#find-the-hierarchy-linked-to-this-org-unit","title":"Find the hierarchy linked to this Org Unit.","text":"SELECT * FROM iaso_orgunit WHERE path ~ '*.XXXXXX.*'\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#find-the-related-org-unit-types","title":"Find the related Org Unit Types :","text":"SELECT * FROM iaso_orgunittype WHERE id IN \n (SELECT org_unit_type_id FROM iaso_orgunit WHERE path ~ '*.XXXXXX.*')\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#reference-forms-of-these-org-unit-types","title":"Reference forms of these Org Unit Types","text":"SELECT * FROM iaso_form WHERE id IN \n (SELECT reference_form_id FROM iaso_orgunittype WHERE id IN \n (SELECT org_unit_type_id FROM iaso_orgunit WHERE path ~ '*.XXXXXX.*'))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#find-the-form-versions-of-these-reference-forms","title":"Find the Form Versions of these Reference Forms.","text":"SELECT * FROM iaso_formversion WHERE id IN \n (SELECT id FROM iaso_form WHERE id IN \n (SELECT reference_form_id FROM iaso_orgunittype WHERE id IN \n (SELECT org_unit_type_id FROM iaso_orgunit WHERE path ~ '*.XXXXXX.*')))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#the-instances-linked-to-that-hierarchy","title":"The Instances linked to that hierarchy","text":"SELECT * FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*')\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#finding-the-projects-linked-to-that-hierarchy","title":"Finding the projects linked to that hierarchy","text":"SELECT * FROM iaso_project WHERE id in \n (SELECT project_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*'))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#devices-linked-to-that-hierarchy","title":"Devices linked to that hierarchy","text":"SELECT * FROM iaso_device WHERE id in \n (SELECT device_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*'))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#accounts-linked-to-these-projects","title":"Accounts linked to these projects","text":"SELECT * FROM iaso_account WHERE id IN \n (SELECT account_id FROM iaso_project WHERE id in \n (SELECT project_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*')))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#source-versions-linked-to-these-projects","title":"Source versions linked to these projects","text":"SELECT * FROM iaso_sourceversion WHERE id IN\n (SELECT default_version_id FROM iaso_account WHERE id IN \n (SELECT account_id FROM iaso_project WHERE id in \n (SELECT project_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*'))))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#datasources-linked-to-these-versions","title":"Datasources linked to these versions","text":"SELECT * FROM iaso_datasource WHERE id IN (SELECT data_source_id FROM iaso_sourceversion WHERE id IN\n(SELECT default_version_id FROM iaso_account WHERE id IN \n (SELECT account_id FROM iaso_project WHERE id in \n (SELECT project_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*')))))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#credentials-linked-these-datasources","title":"Credentials linked these datasources","text":"SELECT * FROM iaso_externalcredentials WHERE id IN (SELECT credentials_id FROM iaso_datasource WHERE id IN (SELECT data_source_id FROM iaso_sourceversion WHERE id IN\n(SELECT default_version_id FROM iaso_account WHERE id IN \n (SELECT account_id FROM iaso_project WHERE id in \n (SELECT project_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*'))))))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#restrictions","title":"Restrictions","text":"This functionality is severly restricted to prevent the risk of data leak and security issues:
To garantee read only access this feature use a separate user that should only be given restricted right.
The functionnality is automatically enabled if this user is set via the DB_READONLY_USERNAME
environment variable.
To configure it: Create a Postgresql user with a password and no acess and give him the role readonlyrole
. You can do so using the sql command
GRANT readonlyrole to YOUR_USER\n
Set the environment variable DB_READONLY_USERNAME
and DB_READONLY_PASSWORD
.
Some migration will give read acess to the certain tables to the readonlyrole
, should you give access to more table use the command
GRANT SELECT ON TABLE \n iaso_new_table_1,\n iaso_new_table_2,\nTO \"readonlyrole\";\n
to only give access to certain column on a table
GRANT SELECT( \n id, username, is_active, date_joined \n) ON auth_user TO \"readonlyrole\";\n
See also https://django-sql-dashboard.datasette.io/en/stable/security.html
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#in-local-dev","title":"In local dev","text":"this feature is automatically enabled.
"},{"location":"pages/dev/reference/vector_control/vector_control.html","title":"Vector control","text":"This App is used to log API calls in the DB.
The idea is that the mobile app user may not have great internet connection where they are so in case of import problem we can fix it server side and not ask them to upload again.
API endpoint to be logged as such are decorated with the @safe_api_import decorator.\nThe request themselves are stored in the vector_control.APIImport model.\nTo replay the failings requests use the django command reimport_failed_imports.py\n
This app used to be a lot of others things too before, hence the not matching name.
"},{"location":"pages/users/FAQ/faq.html","title":"Faq","text":"How do I configure Iaso in a way that mobile users cannot create new OUs ? And/or is it possible to limit OU creation to certain OU types. Ex: mobile users can create the types at the bottom of the hierarchy (FOSA, village), but not at the top (Region -> Aire sanitaire) ?
It\u2019s in the org unit type configuration: you specify what is allowed under a given org unit type in the \"sub org unit types\" selector.
"},{"location":"pages/users/how_to/convert_docx_to_md/convert_docx_to_md.html","title":"How to convert a .docx file to .md","text":"docx
file to iaso/docs/originals
iaso/docs/pages/users/how_to/my_new_page
. Make sure to use snake_case when naming your folder.iaso/docs/pages/users/how_to/my_new_page
folder, create a media
folder. The full path to the media folder should be: iaso/docs/pages/users/how_to/my_new_page/media/
iaso/docs/pages/users/how_to/my_new_page/
, run the following pandoc command: pandoc -s -f docx -t markdown_mmd --extract-media=. -o ./my_new_page.md ../../../../originals/MyPage.docx\n
This will copy all the attached media of you docx
file (like screenshots) in iaso/docs/pages/users/how_to/my_new_page/media/
, and create my_new_page.md
in iaso/docs/pages/users/how_to/my_new_page/
- Rename the media
folder to attachments
- Open my_new_pages.md
. search (ctrl+F/cmd+F) for the word \"media\" and replace it with \"attachment\" whenever it is a path to a file, eg:
<!-->Initial value<-->\n<img src=\"./media/image1.png\"style=\"width:3.02211in;height:0.79688in\" />\n<!-->Correct value<-->\n<img src=\"./attachments/image1.png\"style=\"width:3.02211in;height:0.79688in\" />\n
This is because media
folders are not pushed on github, so if we don't rename it, the screenshots in your docs will be lost
my_new_pages.md
and fix any layout issues that may have been caused by the conversion.iaso/mkDocs.yml
and locate the nav
entry. nav:\n - Home: index.md\n - Users: \n - References:\n - User guide:\n - ./pages/users/reference/user_guide/user_guide.md\n - How to:\n - ./pages/users/how_to/my_new_page/my_new_page.md #<-- Your page would go here\n - FAQ: ./pages/users/FAQ/faq.md\n
"},{"location":"pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html","title":"How to add a new page to Iaso's documentation","text":""},{"location":"pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html#1-determine-where-the-page-belongs","title":"1. Determine where the page belongs","text":"To determine where a new documentation page fits, there are 2 questions to answer: - Who is it intended for? - What kind of document is it?
We currently have 2 categories of documentation users: developers and users. This will determine the style of the writing and the assumptions you can make in terms of, eg prior knowledge of the product.
We determine the kind of document using the diataxis framework. Basically, the idea is to ask what the goal of the document is. A simple rule of thumb is: - Give instructions about how to perform a task, eg: how do I login? -> how to - Explanation of concepts, eg: what is an org unit type? -> reference - Explanation on choices made for some implementation, eg: why do we use react-query for API calls? -> explanation
Once we know who the document is intended for and what kind of document it is, we just need to follow the folder structure. For example, this document is intended for users and aims to explain how to create a new page in the documentation, so it will go in:
> pages\n > users\n > how_to\n
There is an exception for the FAQ whixh we keep separate and visible for convenience
"},{"location":"pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html#2-create-a-branch-on-git-for-the-new-document","title":"2. Create a branch on git for the new document","text":"Ideally, there will be a Jira ticket for the changes about to be made. It should be used to name the branch, as it will enable Jira to directly link the ticket to the branch. For example, the development branch for this document is called IA-2630_how_to_write_iaso_doc
To create the branch: - Open a terminal - Make sure you are on main. If not run git checkout main
- Run git checkout -b <Branch name>
. This will create the branch and switch to it
By convention, we create a folder with the same name as the markdown file. For example, this document is in:
> pages\n > users\n > how_to\n > create_new_documentation_page\n - create_new_documentation_page.en.md --> this page\n - create_new_documentation_page.fr.md --> its french counterpart\n
It's important to use the format <name>.<language>.md
, as it will be used by mkDocs to display the right page for the right language
Format the text using markdown syntax
"},{"location":"pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html#41-add-images","title":"4.1. Add images","text":"/attachments/
in the document's folder and move the images there> pages\n > users\n > how_to\n > create_new_documentation_page\n > attachments\n - create_new_documentation_page.en.md\n - create_new_documentation_page.fr.md\n
To add the image in the markdown file, either: - use markdown syntax to add the link ![my_image](./attachments/my_image.png)
- use an html img
tag:
<img src=\"./attachments/image49.png\" />\n
The markdown syntax is less cumbersome if the image is already at the right size. The img
tag allows for manually setting the image width and heigh via the style
attribute:
<img src=\"./attachments/image49.png\" style=\"width:50px;height:50px\" />\n
Note: Add the /attachments/
folder even if the document doesn't contain any images yet. It will make it easier for others to add them later.
Once the document is ready, the changes need to be saved on the git branch, then pushed on Github, so they can be reviewed and merged. There are tools to make this part faster and easier to manage (Github desktop, or even just the git ineterface of VS Code), but if it needs to be done in the terminal (from the document's branch): - git add .
- git commit -m <commit message>
- git push
Pull requests are the process through which we review code. Since the documentation is hosted as part of the code, it's going through the same review process, though not necessarily by the same persons.
To open a pull request: - Go to iaso's pull requests - Click New pull request - Select a branch - Describe the changes. The description template can be ignored for documentation changes, but please leave a description, as it helps tracking and understanding changes.
"},{"location":"pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html#7-review-a-pull-request","title":"7. Review a pull request","text":"Pull requests are peer-reviews that insure that all changes are cross-checked, so one should not merge their own pull requests.
To review a pull request: - Go to iaso's pull requests - Click in the pull request - Click \"Add your review\" - Review the changes, comment where necessary - To finish the review, click \"Review changes\" - If the changes can be deployed as they are, choose \"Approve\" - If not, explain what needs to be corrected and chooses \"Request changes\" - If the PR has been approved, go the \"Conversation\" tab of the PR, scroll down and click \"Merge pull request\"
The changes will be visible in production once the pull request has been merged
"},{"location":"pages/users/how_to/edit_documentation/edit_documentation.html","title":"How to edit an existing page in iaso's documentation","text":""},{"location":"pages/users/how_to/edit_documentation/edit_documentation.html#1-to-add-only-text","title":"1. To add only text","text":"For the text, see point 1 above. For the images, add the images to the /attachments/
folder of that document, eg, for user_guide:
> user_guide\n > attachments // <-- there\n - user_guide.md\n
To add the image in the markdown file, either: - use markdown syntax to add the link ![my_image](./attachments/my_image.png)
- use an html img
tag:
<img src=\"./attachments/image49.png\" />\n
The markdown syntax is less cumbersome if the image is already at the right size. The img
tag allows for manually setting the image width and heigh via the style
attribute: ```html
If the /attachments/
folder doesn't exist, the change can't be made using Github's interface and should be made using git and an IDE/text editor
Find someone with appropriate access right (django admin required)
https://iaso.bluesquare.org/api/setupaccount/
Use and store the user/password in a password manager
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#2-create-a-dedicated-dhis2-for-iaso","title":"2. Create a dedicated DHIS2 for iaso","text":"We want to keep track of which app is changing which data/metadata of dhis2 so please don\u2019t use the main/default \u201cadmin\u201d user but a dedicated one.
Go in the dhis2 \u201cUsers / Utilisateurs\u201d module
\u201cDuplicate the admin\u201d
Verify the account is empty or in the left menu
Avoid doing the next steps with the django admin, as it can lead to industrial accident: the user may be linked to a totally different account/project you might end up with the pyramid of a project filled with orgunits of another country.
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#4-add-a-new-project","title":"4. Add a new project","text":"Use the naming used by clients if applicable.
Promote \u201cgood behavior\u201d by enabling authentication by default
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#5-create-a-new-datasource","title":"5. Create a new datasource","text":"Use the user created at step 2
Make the source the default one
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#6-create-a-new-first-version-of-the-data-source","title":"6. Create a new (first) Version of the data source","text":"You can import this first version
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#7-updating-the-pyramid","title":"7. Updating the pyramid","text":"IMPORTANT note that if you \u201cadd new orgunits or add/change groups\u201d that\u2019s not previous step screen that you should use
but the \u201cupdate\u201d button on the default version !
If you create a new version \u201cswap it to the default version\u201d this detection will be broken since incoming submission will be attached to different orgUnit iaso id.
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#8-whats-next","title":"8. What's next ?","text":"Use the Mobile app in the store and provide the appId/user/password
Start configuring the iaso forms
try to be consistent and future proof in the naming !
this is good :
``` PMA - Qualit\u00e9 01 - Indicateurs g\u00e9n\u00e9raux PMA - Qualit\u00e9 02 - Plan financier PMA - Qualit\u00e9 03 - Consultation Postnatale ... PMA - Qualit\u00e9 10 - Vaccination PMA - Qualit\u00e9 11 - Accouchements PMA - Quantit\u00e9 PCA - Qualit\u00e9 ...
\nthis is **BAD** : \n\n
PMA - Qualit\u00e9 1 - Indicateurs g\u00e9n\u00e9raux PMA - qualit\u00e9 10 - Vaccination PMA - Qualit\u00e9 11 - accouchements PMA - qualit\u00e9 2 - plan financier PMA - Qualit\u00e9 3 - consultation Postnatale... ... PMA - Quantit\u00e9 Qualit\u00e9 - PCA - ... ``` - computers are really bad at sorting in natural order so prefer 01 02 03 - be consistent in your upper/lower case usage - be consistent by prefixing the entity type (no need to put the country in it, we have limited space in the mobile app)
"},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html","title":"Setup login with dhis2 for iaso","text":""},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#in-dhis2","title":"In DHIS2","text":""},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#go-in-the-oauth-settings","title":"Go in the oauth settings","text":"in the menu :
System settings > Oauth 2 clients\nParametres Systeme > Oauth 2 clients\n
"},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#create-oauth-client","title":"Create oauth client","text":"Name : iaso
Select : authorization code Url : you need to pick a unique code : https://iaso.bluesquare.org/api/dhis2/<<unique-code>>/login/
In django admin : https://iaso.bluesquare.org/admin/iaso/externalcredentials/
to be able to easily go back to dhis2 add the feature flag \"SHOW_DHIS2_LINK\" on the Project
then the entry should appear
"},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#in-iaso","title":"In iaso","text":""},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#link-the-iaso-user-with-a-dhis2-user","title":"Link the iaso user with a dhis2 user","text":"in iaso general ui
"},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#test","title":"Test","text":"login in dhis2 with the linked in previous step
<<dhis2>>/uaa/oauth/authorize?client_id=<<unique-code>>&response_type=code
then \"Authorize\": you should end up in iaso
It's not working ?
What comes with data collection are questions, and to organize these questions, data collection forms. These are basically lists of the questions one would like to collect answers for, while specifying options (mandatory or not, skip a question depending on previous answer, etc.). IASO builds on XLS forms for its questionnaires, which are therefore pre-defined using an Excel file.
In IASO, data collection forms are versioned meaning that every time a new version is created, the former version is kept and available in the system.
"},{"location":"pages/users/reference/iaso_concepts/iaso_concepts.html#organization-units","title":"Organization Units","text":"IASO uses the notion of Organization Units (Org unit or OU) to manage geographic data. The organisation unit types (OUT) represent levels in the hierarchy
Example:
Country
Region
The organization units are classified in the pyramid according to a parent and one or several children (except the top parent(s) and the lowest child/children). Example below:
Democratic Republic of Congo (Org unit type \"Country\") is the parent org unit of
Kinshasa (Org unit type \"City\"), which is the parent org unit of
Data collection in IASO is structured according to the defined hierarchy, and any user needs to explicitly select an organization unit before proceeding to opening the questionnaire and answer questions. This way, one makes sure that the data collected is correctly associated with the relevant geography.
"},{"location":"pages/users/reference/iaso_concepts/iaso_concepts.html#projects","title":"Projects","text":"In IASO, a Project is a mobile application instance, with its own App ID. Within one account, you can have one or several Project(s) with different feature option(s). Users can be linked to one or several Project(s).
Good to know:
One Project is linked one data source
One Project can be linked to one or several users
IASO mobile application is available on Google Play Store (Android phones only).
It can work completely offline - once the end user has encoded the data needed, he/she can upload the data collected offline all at once when network is available.
Updates made from the web (forms versions, health pyramid) will be reflected in the App only after the App data has been refreshed and this requires network connectivity.
Key tip before testing / using the App - Make sure you have refreshed data beforehand
"},{"location":"pages/users/reference/iaso_mobile/iaso_mobile.html#run-the-mobile-application-for-the-first-time","title":"Run the mobile application for the first time","text":"IASO Mobile application has to be configured on the web part before using (see the part \u201cProject\u201d).
Then you can:
Download IASO App on Google Play
Insert the server url : https://iaso.bluesquare.org
See below an overview of the main buttons that you can find on the main screen in data collection mode.
In the More Options part, you can take the below actions: - Refresh data: you need to have internet connectivity to do so. It will synchronize the mobile application with IASO web data. In order to avoid that it takes too long in low-connectivity settings, you can choose to refresh only sub-parts such as Forms, Organization Units, or other. - Change the App ID: you can switch Project by entering another App ID. In order to make sure that there is no data from the former App ID left on the IASO mobile application, please access your parameters and erase storage and cache data from IASO beforehand. - Change the URL of the server: this can be handy if you need to switch from Production to Staging server - Logout: your user can logout. This does not prevent data consultation of local data (data available on IASO on the user's device) - About: gives the version of the IASO mobile application. It can be good to have to debug.
"},{"location":"pages/users/reference/iaso_mobile/iaso_mobile.html#collect-data","title":"Collect data","text":"Once you are connected to the IASO mobile application, you can then proceed with your data collection. Here below are the different screens that you would see for a simple data collection.
You will then have data collection form chosen opening. You can proceed with answering the different questions and press \"Next\" until the end of the Form.
If you wish to interrupt data collection during input, you can press the back button on the tablet or smartphone.
Once you click on the button, you have 2 options: - Save Changes: to save all data already filled and the form with unfinalized status. With this option you can, come back and continue enter data - Ignore Changes: to delete data filled and the form
Upload collected data
If you collect data with your mobile device, they are stored in your device. You need to upload data to the server to make them visible at central level. Keep in mind that you need internet connection in order to be able to upload data.
Click on the \"Send Finalized Forms\" icon on the mobile application home page on the top right corner.
Then, a specific page will open to let you know if the data has been correctly uploaded. Finalize the operation by clicking on \"Send to server\".
"},{"location":"pages/users/reference/iaso_modules/iaso_modules.html","title":"Modules","text":"IASO is organized according to Modules, which are groups of functionalities which can be added up depending on the use case to cover. Here are the Modules available in IASO:
"},{"location":"pages/users/reference/iaso_modules/iaso_modules.html#data-collection-functionalities","title":"Data collection functionalities","text":"IASO web platform is intended to administrators for them to define the details of the data collection they would like to proceed with. Some key assets of IASO are:
the versioning of all data - every change is tracked and former versions can be retrieved as needed
geo-structured data collection - forms are linked to clear Geographical levels or \"Organization Units\"
the traceability of changes - allowing decentralization of the activities and responsibilities Administrators can therefore use the web platform to plan, monitor and then evaluate the data collection efforts.
To log into the web interface, go to https://iaso.bluesquare.org/login/ and sign in with your username and password.
You can also reset your password by clicking on the link \"Forgot password\". This will send an automatic email and allow you to create a new password.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#navigating-in-iaso","title":"Navigating in IASO","text":""},{"location":"pages/users/reference/iaso_web/user_guide.html#manage-data-collection-forms","title":"Manage data collection forms","text":""},{"location":"pages/users/reference/iaso_web/user_guide.html#forms-list","title":"Forms list","text":"From the forms list, you can search through the available forms of the IASO account you are connected to using the filters:
The below buttons allow you to manage the data collections forms.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#createupload-a-data-collection-form","title":"Create/Upload a data collection form","text":"Access the Forms entry in the menu, then click on Form list. Click on the button \"Create\". Once on the Form creation page, follow the below steps:
Tips:
Once a form has been completed and sent to the server, it creates a \"form submission\". Every form submission is recorded into the platform and data submitted can be consulted from there. You can use the filters to consult the form submissions as needed:
This view allows you to search forms through free text entry and several filters that can be combined.
Once you have applied at least one form filter, you can download submissions using the \"CSV\" or \"XLSX\" buttons.
You can also create a new submission by clicking on \"Create\". This will open Enketo and ask you which Organization Unit it relates to.
You can also check the submissions on the map view, on which the filters apply. To make sure to have this map view enabled, make sure you have added the feature flag \"GPS for each form\" to the related Project.
The tab \"File\" allows you to visualize the files that have been submitted together with the forms, such as pictures. When clicking on a given file, you can then be redirected to the relevant form submission.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#manage-submissions","title":"Manage submissions","text":"On the Submissions page, you can see the list of submissions that have been done for the account. You can manage them using the below options.
Visualise a submission
You can view a specific submission/submitted form by clicking on the \"View\" button (see above).
This allows you to see the data submitted and edit it on Enketo (open-source web application.
The \u201cInformation\u201d section provides a technical overview of the form.
The \u201cLocation\u201d section shows the health pyramid's indication of where the data was collected.
The \u201cExport Requests\u201d section shows when the data was exported to DHIS2, by whom, and any errors that occurred during export.
The \u201cFiles\u201d section can contain images, videos, documents.
The \u201cForm\u201d section shows all form questions and answers entered during data collection.
Download a submission
The \"XML\" icon allows you to download a submission in XML format.
The gear icon on the bottom corner at the right hand side shows you a series of icons upon hover. These allow you to:
See below the dedicated sections for more information on each of these actions.
Delete a submission
Allows you to delete the form. If it has already been exported to DHIS2, this will not delete the data in DHIS2. A warning message will appear:
Edit attached Org Unit or Period
When you click on \u201cEdit Period and/or Organisational Unit\u201d, a window opens allowing you to reassign the instance. You can change the time period or organization unit that has been assigned to the submitted form.
Export a submission
The export function allows you to export the form to DHIS2. Beforehand, it needs to have been mapped using the DHIS2 mapping functionality.
Edit the submission via Enketo
To edit a form, click on the Enketo icon (see above).
Edit the form by changing the answers to the questions that need to be changed. Then click on submit at the bottom of the form.
Push GPS coordinates from the submission to the Org Unit
This will use the GPS coordinate collected via the form to push to the Organization Unit GPS coordinates.
Lock submission
This functionalty allows you to protect the form submissions from further editing by users who have less permissions than you.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#form-statistics","title":"Form statistics","text":"This view allows you to see statistics about the forms. When clicking on \u201dForm Statistics\" you will open a page with two graphs.
The first one shows the total number of submissions over time and the second one shows the new submissions per month per form.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#dhis2-mappings","title":"DHIS2 mappings","text":"A great advantage of IASO is that you can export data to DHIS2. When doing so, prior mapping is necessary. After the form is uploaded, map the form to match the data item in DHIS2.
Click on DHIS mappings to see the forms :
In the Form view you can see details of:
Actions
Name of forms available for mapping
Versions
Type of the form :
Aggregate : fix
Event : series of singular events
Event Tracker : continuous
Number of questionnaire to be mapped
Total number of questionnaires
Mapping coverage
Date of last modification
Click \"Create\" and a window will open allowing you to map each questionnaire of the xls forms to the correspondent DHIS2 data element
The mapping process consists of selecting a question on the left and deciding whether it should be mapped to DHIS2 or not.
Some questions may not need to be mapped like notes, metadata etc. in such a case click on never map.
If the question is to be mapped, search for the correspondence DE in the box by using the name, code or ID and then confirm.
Once confirmed, the question will turn to green and be counted.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#completeness","title":"Completeness","text":"This functionality is intended to use cases where Periods have been set to the data collection forms. In the view \u201ccompleteness\u201d you will see details of :
Buttons to select forms \u201cready\u201d to be exported, form with \u201cerrors\u201d > and forms that have been \u201cexported\u201d
Periodicity filter : the periodicity filter allows you to organise > the data into months, quarters, semesters or years. The list will > display the forms available for the selected period, and will > indicate how many forms have been submitted for each
Synchronise button to synchronise two forms
Click on each of these buttons to have forms ready to be exported, errors and exported. A periodicity filter is there to organise data in months, quarters, semester or yearly.
If you click on the number of submissions, you will be taken to the submissions view, where you can click on the view icon and see the submissions for that form.
Click on the button to synchronise two forms
Eg: to get aggregate data from community verification survey, all the client forms should be synchronised to a single form.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#completeness-statistics","title":"Completeness statistics","text":"This table view shows you the completeness of the forms submissions in number (number of completed forms) and in percentages (Data completeness). A distinction is made between \u201cdirect forms\u201d (which relate to the select Organization unit level) and \u201clower level forms\u201d (which relates to forms down the hierarchy).
Use the filters (Form name, Parent Organization Unit, Organization Unit type, User, Planning, Teams, Period) to only see statistics in a more specific way.
The \"See children\" action button allows you to drilldown the geographical hierarchy to identify the level of completeness and spot where issues may have happened.
The first two columns \"itself\" indicate the number of forms completed at the level of the Organization Unit highlighted. The next column \"descendants\" give information on the number of forms completed at the level in question, but also at all lower levels.
You can also view data completeness with a map view by clicking on the \"Map\" tab. Be aware that you need to select a Form in the filters beforehand to enable this view. You can adjust the thresholds to apply to the legend on completeness in the relevant form's advanced settings.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#georegistry-organization-units-management","title":"Georegistry - Organization Units Management","text":"See the Organization Unit definition for more insight on what Organization Units are. In a nutshell, you can manage your geographical data associated to your account using the Organization Unit part of IASO.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#organization-units-list","title":"Organization Units List","text":"Click Organization Units in the menu and then on Organization Unit List to navigate the organization unit pyramid.
You can view in list or map.
You can select an Organization Unit Navigation and:
The search results can be exported in CSV, XLSX or GPKG.
Results can be seen in a list or on a map
The status for when a village has just been added and needs to be reviewed for example.
The external reference is used to export data to DHIS2.
The map helps you to know where the structure is located.
You can see the history of modifications by clicking on the little clock icon or the details of the filled forms by clicking on the eye icon.
Several searches can be made by adding tabs to the page with the + button.
You can choose the colour of the results on the map for each search.
Creation of an Organization Unit
On the Organization Unit list page, click on \"Create\". You can then create an Organization Unit as needed.
You will need to enter the below information before saving:
Optional fields:
Edit an Organization Unit or consult details
To access the detailed view of an Organization Unit, proceed as described below:
In this view, you have a set of tabs that allow you to edit the Organization Unit as needed:
Bulk edition of Organization Units
You can also edit Organization Units in bulk. In order to do this, from the Organization Unit list, tick the boxes of the Organization Units you would like to bulk edit, then hover on the action button. Click on the gear action button, and select the action you would like to perform.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#organization-unit-groups","title":"Organization Unit Groups","text":"Organisation units can be grouped in organisation unit groups, and these groups can be further organised into group sets. Together they can mimic an alternative organisational hierarchy which can be used when creating reports and other data output. In addition to representing alternative geographical locations not part of the main hierarchy, these groups are useful for assigning classification schemes to Organization Units.
Manage Organization Unit Groups
In order to manage the Organization Unit Groups, access the menu entry Organization Units > Groups.
This view allows you to search the Organisation Unit Groups through free text entry.
You can create a new group by clicking on the create button.
Groups can be edited by clicking on the gear icon or deleted by clicking on the delete button.
In the table, the column \"Org Units\" shows the number of Organization Units that are assigned to this group. When you click on the number, you will see the list of that Org Unit group.
Assign Organization Units to Groups
To assign Organization Units to Groups, go to the Organization Units List view from the menu and make a bulk edit of the selected organization Units. See above in section \"Bulk edition of Organization Units\" for more details on bulk edition of Organization Units.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#organization-unit-types-management","title":"Organization Unit types management","text":"Organization Unit types are specific to IASO (i.e. this is not handled in DHIS2). See the part about Organization Units for more details on what Organization Unit types are.
From the Organization Unit menu entry, click on \"Organization Unit types\". This view lists the Organization Unit types existing in your IASO account.
Create an Organization Unit type
Click on \"Create\" and enter the below mandatory fields:
These other fields are not mandatory:
IASO allows to import and handle one or several geographic data source(s).
"},{"location":"pages/users/reference/iaso_web/user_guide.html#data-sources-list","title":"Data Source(s) List","text":"Find here the data sources with their names, versions and descriptions. It is possible to edit the data source, check up on the files\u2019 version history or compare data sources and export them to DHIS2.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#matching","title":"Matching","text":"This is rather a \"geospatial\" functionality : to have several geographical pyramid sources and try to make links (Example: where in a csv \u201cprovince x\u201d is called \"PROVINCE X\" and in another source it is called \"DPS X\").
The algorithms run part is intended for data science work.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#registry","title":"Registry","text":"The Registry entry in Organization Unit is a visualization tool allowing users to drilldown in the geographical hierarchy and consult the geographical data as well as data collection associated to the different level(s).
"},{"location":"pages/users/reference/iaso_web/user_guide.html#review-change-proposals","title":"Review change proposals","text":"With IASO, supervisors can compare and validate data submissions as they are sent to the server. Note that this feature will only work provided that you have activated the \"Change requests\" feature flag on the IASO Project you would like to validate data collected for. See the Projects part for more information on mobile feature flags in IASO.
On the Review change proposals page, users can use the filters to select the proposals they would like to focus on. See on the picture below the detailed filters.
Supervisors can then click on the gear icon at the end of the relevant line to be able to see the details of the change proposal submitted and compare with the former version on the left.
Supervisors can then select the changes they would like to approve by ticking the boxes of the changes selected on the right column, and then hit \"Approve selected changes\". If the changes proposed are not satisfactory, supervisors can reject all changes and provide a comment.
For each change proposal sent, IASO mobile application users will be able to see if they have been approved or rejected, and if rejected, consult the comment.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#planning","title":"Planning","text":"The Planning feature in IASO allows you to plan field work per team and user in defined zones/organization units and according to a specific timeline. Once data collection activities would have been assigned via the interface, field agents using the mobile application would only be able to see the activities assigned to them, and navigate towards the relevant GPS point(s).
In order to be able to create a Planning, you will need to have created beforehand Organization units, Users, a Project, Teams of Users/Teams of Teams and data collection Forms for which you would like to use the Planning feature.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#planning-list","title":"Planning List","text":"Click on Planning in the menu panel. Under Planning List you will see the list of schedules/plannings that have been created in IASO. You can search through the different Plannings using the different filters and take the below actions:
Create a Planning
Click on \"Create\" and you will see the below window opening:
The below fields are mandatory:
You can add a description as an option.
The \u201cPublishing status\u201d (in the lower left corner) feature makes it possible to ensure, once completed (and all assignments made), the newly created planning will be available in the IASO mobile app for the relevant project.
Once you have completed the fields, click \"Save\" to finish.
Click on the eye icon button from the Planning list to start editing your new Planning via the Map interface.
You can do the assignment either through the \u201cMap\u201d or the \u201cList\u201d tab. If processing through the map, first select the Team you would like to assign a geography to in the dropdown, as well as the relevant \u201cBase Org Unit type\u201d in the dropdown. You can then start assigning geographic areas or points directly to the selected Team members directly on the map.
Selected areas will be highlighted with the team\u2019s colour, that you can change as needed.
In order to assign all children Org Unit of a given parent Org unit to the same Team/User, you can select the \"Parent picking mode\" before proceeding to your selection.
If you prefer using the List tab, the process is pretty similar. The main difference being that you work here with a list of names, according to the selected level. Org units are assigned by clicking in front of the item name, in the \u201cAssignment\u201d column.
You can sort Org units and Parents by clicking on the column name.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#admin","title":"Admin","text":"The Admin part of IASO comprises several parts, which will appear or not depending on the user's permissions:
This is the IASO batch updates log. An operation log contains information about when and where an operation ran, the operation status, the number of source and target records processed, and any log messages.
Examples of tasks include:
The statuses are:
The Task list can be refreshed by pressing the button \"Refresh\" on the right top hand side.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#monitoring","title":"Monitoring","text":"This part allows supervisors to monitor devices that are linked with the IASO account. From this page, you can consult:
On the right hand side, you can see the number of devices that are connected under the IASO account you are connected to.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#projects","title":"Projects","text":"A Project in IASO relates to an instance of mobile application. Each Project is identified by a Name and an App ID. See here for a more detailed definition of Projects in IASO.
Create a Project
From the menu, Admin > Projects > Click on \"Create\"
Then, add a Project name, and an App ID. Be aware that the App ID will have to be entered by IASO mobile application users the first time they connect to the IASO app, so it should not be overly complicated to avoid typing errors.
You can then select the Feature flags you would like to apply to your Project in the next tab and press \"Save\".
Feature flags definition
See the table below for more details on the Project Feature flags:
Feature flag Description Authentication Users have to enter their login and password on the mobile application before proceeding to the data collection. Please note that this is possible in IASO to proceed to data collection without authentication for simplified processes (also called \u201canonymous mode\u201d) Mobile: show data collection screen Enable the feature to collect data from the IASO mobile application (data collection that is not linked to a planning or a change request workflow) GPS for each form Every time a data collection form is submitted, a GPS point is automatically taken and associated to the form submission Enforce users are within reach of the org unit before starting the form IASO mobile application users have to be close (50m) to the organization unit GPS point they are collecting data for in order for the form to open Mobile. Show planning screen When a planning has been done in IASO via the web interface, the assigned data collection points and tasks are reflected via this tab Mobile: limit download of org unit to what the user has access to When loading data into the mobile application, only the geographical zone that is assigned to the user is downloaded, so as to enable offline use. This allows a lighter (and then quicker and less data-consuming) download of data at the start of the IASO mobile application Mobile. Show Map of org unit Adds a tab in the mobile application to show the geographic information available for the selected Org Unit in the mobile application. For instance, if a GPS coordinate is available for a health facility, it would show on the map via this tab Request changes to org units Enable the feature to propose changes to org units and their related reference form(s) Mobile: Change requests Adds the tab allowing to propose changes to org units and their reference form(s) GPS for trajectory Enable the user to activate a function that track their position every 15 minutes over a period of time Mobile. Warn the user when forms have been updated When new form versions have been uploaded on the web, the IASO mobile application user is notified. Then the user can choose to apply them or not Mobile. Warn the user when forms have been updated and force them to update When new form versions have been uploaded on the web, the IASO mobile application user is notified and the update happens automatically Mobile. Warn the user when the org units have been updated When changes to the Org units (health pyramid) have been done on the web, the IASO mobile application user is notified. Then the user can choose to apply them or not Mobile. Warn the user when the org units have been updated and force them to update When changes to the Org units (health pyramid) have been done on the web, the IASO mobile application user is notified and the update happens automatically Auto upload of finalized forms The synchronization of forms that have been filled takes place automatically as soon as the user has connectivity Mobile. Finalized forms are read only IASO mobile application users cannot edit the forms once finalized in the mobile application"},{"location":"pages/users/reference/iaso_web/user_guide.html#users","title":"Users","text":"Users can access IASO web and mobile application with login credentials. Each user is assigned permissions and can be limited by location.
Permissions are relatively granular: - By screen/tab - Different read/write permissions for important domains - Restriction of access using the health pyramid - Batch creation/modification of users - Customizable user roles (Administrator, Data manager, etc.)
Please note that the permissions assigned from the User management apply to IASO web only. IASO does not have a system of permissions for its mobile application, but rather a set of Feature Flags.
Create a new IASO user
From the menu Admin > Users, click on \"Create\".
Note that you can also indicate the following information:
Language: you can specify in which default language this user will use IASO web. IASO mobile application is based on the default language of the users's device.
Assign user permissions
On the next tab \u201cPermissions\u201d, you can enable/disable permissions for that user as needed. Note that in the \u201c?\u201d are tooltips to explain what the permissions do.
On the last tab \"Location\", you can restrict the access of the user you are editing to a sub-part of the Organization Unit hierarchy (hence the user will only be able to see data relating to his/her Geography). If no Location is specified here, by default the user will see all data available across the entire hierarchy.
Create users in bulk
You can create several users at once using a CSV file that you import to IASO.
Use the button \u201cCreate from file\u201d and you can then import your list of users (or download the relevant template to do so beforehand).
Manage IASO users
This view allows you to manage users and their permissions. You can search for a user using the different filters.
You can edit IASO users in bulk using the bulk update feature. First, tick each user you would like to update using the check boxes on the right side of each user line.
Then select the action(s) you would like to perform for these users. They can be:
Click on \"Validate\" when done.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#user-roles","title":"User roles","text":"User roles allow to group users that are granted a set of permissions under the same role. In the User role, you are able to create User roles with their matching permissions, to which Users can be assigned to.
Create a User role
From the Admin > User roles page, click on \"Create\".
You can then assign this user role to any user through the Permission tab in the User edit popup. Be aware that the User role permissions will apply to the user, but if the User has more permissions that had been previsouly assigned to him/her, he/she will not lose them but they will add up.
To assign multiple Users to this newly created user role in bulk, go back to the Users list and proceed to a bulk update (see Manage Users above).
"},{"location":"pages/users/reference/iaso_web/user_guide.html#teams","title":"Teams","text":"The notion of Teams in IASO is used mainly for the Planning feature. It allows to organize Users in Team hierarchies and assign data collection activities to the relevant geographies as needed for the Planning purposes.
There are two types of Teams:
Create a Team
From the menu, access Admin > Teams. Click on \u201cCreate\u201d
Fill out the below fields:
You can then use the gear or bin icon on the main page to edit or delete Team(s) as needed.
"},{"location":"pages/users/reference/interop/interop.html","title":"Interoperability roadmap 2023-2024","text":""},{"location":"pages/users/reference/interop/interop.html#introduction","title":"Introduction","text":"Iaso, whose name was taken from the name of a Greek goddess for health, has initially been developed to support national health programmes in their data collection and organization of geographical information in remote and low connectivity areas. Since then, it has also been used in other fields, such as education and environmental projects.
Iaso builds on three essential concepts users, forms (in XLSForm format) and org units (e.g. districts and facilities) with a focus on structuring data collection along geographic lines to allow for splitting responsibility geographically, as is commonly done in health programs. This allows to decentralize monitoring, validation and team management. It also allows to have out of the box completeness reporting for data collection.
Iaso has been recognized as a Global Good for Health by Digital Square. As such, Bluesquare acknowledges the importance of making Iaso as interoperable as possible in order to facilitate data exchange within the global digital health ecosystem such as the Open Health Information Exchange (OpenHIE) community.
"},{"location":"pages/users/reference/interop/interop.html#open-standards-already-implemented","title":"Open standards already implemented","text":"The below below standard technologies are already being used by Iaso today:
Data collection: XLSForm, CSV
Geographical data: geopackage
Iaso data collection can be done through forms in the common XLSForm format used for example by ODK, and allows to import and export data to DHIS2 (thanks to a user-friendly mapping interface), which is not per se using open formats in general, but is a de facto standard in some health topics, DHIS2 being probably the most installed open source health information management system.
Iaso allows imports and exports of geographical data through the geopackage format (http://www.geopackage.org/) which is the relatively new golden standard for Geographical Information Systems.
"},{"location":"pages/users/reference/interop/interop.html#interoperability-roadmap","title":"Interoperability roadmap","text":"1. Short-term (by end of 2023)
Recently, we implemented case management features in Iaso, which is mainly the possibility to collect and store data about individuals. One goal will be to further develop the integration between Iaso and DHIS2 Tracker, to allow the import and export of data linked to individuals.
In the same context as above, Bluesquare would ensure that Iaso is compatible with the FHIR standard for health care data exchange. That said, Iaso is a generic data collection tool, and consequently we can\u2019t enforce that collected data always uses a predefined set of fields. Consequently, support of FHIR for case management would be made on a project basis, and where Bluesquare could help is by providing documentation of how to implement some parts of the FHIR standard.
On the other hand, Iaso is a very complete facility list management system and here, there is a very good opportunity to adopt OHIE facility registry standards. This will be studied by the end of the year and implemented if we can identify a project needing the feature.
2. Long-term (end of 2023 and beyond)
Iaso\u2019s code and general information is published on the dedicated Github repository that you can find here: https://github.com/BLSQ/iaso/wiki
Bluesquare has started to organize processes to ensure more easily accessible documentation about Iaso, that will benefit the open source health softwares community. An evolving user guide will be made available on https://readthedocs.org/, together with more technical documentation on new features. A high-level roadmap on next features will also be published and maintained.
To facilitate interoperability, we are in the process of publishing the api specification in the OpenAPI standard (the format used by the Swagger tool).
Iaso is growing more and more to be a planning system, e.g. for vaccination campaigns. We need to investigate if there are existing standards (outside of calendar standards like caldav) , especially in the OHIE specification that could be reused to expose our plannings to external systems.
There is a growing demand for Iaso to be able to handle logistics, in order to monitor stocks of certain health-related or other supplies, such as vaccines, mosquito nets in certain locations. If Iaso would further develop features in this field, Bluesquare will make sure to follow the openHIE \u201cLogistics Management Information System (LMIS)\u201d and \u201cProduct Catalogue\u201d components principles.
"},{"location":"fr/AWS-Deployment.html","title":"AWS Deployment","text":""},{"location":"fr/AWS-Deployment.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/index.html","title":"Bienvenue dans la documentation d'IASO","text":""},{"location":"fr/index.html#introduction-a-iaso","title":"Introduction \u00e0 IASO","text":"IASO est une plateforme innovante, open-source, bilingue (EN/FR) de collecte de donn\u00e9es \u00e0 fonctionnalit\u00e9s g\u00e9ospatiales avanc\u00e9es pour planifier, surveiller et \u00e9valuer les programmes de sant\u00e9, environnementaux ou sociaux dans les pays \u00e0 revenu faible et interm\u00e9diaire (PRFI). IASO est reconnu comme un Bien Public Num\u00e9rique par l'Alliance des Biens Publics Num\u00e9riques et figure parmi les logiciels Global Goods de Digital Square, t\u00e9moignant de son utilit\u00e9 publique.
IASO comprend :
En termes de fonctionnalit\u00e9s, IASO peut \u00eatre r\u00e9sum\u00e9 autour de quatre composantes principales qui sont interconnect\u00e9es et renforcent leurs capacit\u00e9s mutuelles :
Gestion des donn\u00e9es g\u00e9ospatiales (G\u00e9oregistre)
Collecte de donn\u00e9es g\u00e9o-structur\u00e9es
Microplanification
Entit\u00e9s - celles-ci peut consister en des individus (par exemple, les b\u00e9n\u00e9ficiaires de programmes de sant\u00e9) ou des objets physiques (par exemple, des lots de vaccins, des moustiquaires, etc.). Les workflows permettent de suivre les entit\u00e9s en ouvrant des formulaires sp\u00e9cifiques en fonction des r\u00e9ponses donn\u00e9es \u00e0 des formulaires pr\u00e9c\u00e9dents.
La plateforme a \u00e9t\u00e9 mise en \u0153uvre au B\u00e9nin, au Burkina Faso, au Burundi, au Cameroun, en R\u00e9publique Centrafricaine, en R\u00e9publique D\u00e9mocratique du Congo, en Ha\u00efti, en C\u00f4te d'Ivoire, au Mali, au Niger, au Nig\u00e9ria et en Ouganda. Elle est le registre g\u00e9ographique officiel au Burkina Faso depuis 2023. IASO a \u00e9galement \u00e9t\u00e9 mise en \u0153uvre au niveau r\u00e9gional (r\u00e9gion AFRO) pour soutenir l'Initiative Mondiale pour l'Eradication de la Polio gr\u00e2ce \u00e0 ses capacit\u00e9s de registres g\u00e9ospatiaux et d'\u00e9tablissements de sant\u00e9.
"},{"location":"fr/index.html#technologies-utilisees","title":"Technologies utilis\u00e9es","text":"IASO est compos\u00e9e d'une application Android white labeled utilisant Java/Kotlin, r\u00e9utilisant une grande partie des projets ODK, et d'une plateforme web programm\u00e9e en Python/GeoDjango sur PostGIS. Le frontend est principalement en React/Leaflet. L'API est impl\u00e9ment\u00e9e via Django Rest Framework, et toutes les donn\u00e9es sont stock\u00e9es dans PostgreSQL ou le r\u00e9pertoire media/. L'un des objectifs est la facilit\u00e9 d'int\u00e9gration avec d'autres plateformes. Nous avons d\u00e9j\u00e0 des imports et exports en formats CSV et GeoPackage, et nous visons une int\u00e9gration facile avec OSM.
L'application mobile pour Android permet la soumission de formulaires et la cr\u00e9ation d'unit\u00e9s d'organisation. Les formulaires peuvent \u00e9galement \u00eatre remplis dans une interface web via le service compagnon Enketo.
"},{"location":"fr/pages/dev/how_to/add_new_permission/add_new_permission.html","title":"Add new permission","text":""},{"location":"fr/pages/dev/how_to/add_new_permission/add_new_permission.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/contribute/contribute.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/create_entities_in_web_ui/create_entities_in_web_ui.html","title":"Create entities in web ui","text":""},{"location":"fr/pages/dev/how_to/create_entities_in_web_ui/create_entities_in_web_ui.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/create_registry_in_web_ui/create_registry_in_web_ui.html","title":"Create registry in web ui","text":""},{"location":"fr/pages/dev/how_to/create_registry_in_web_ui/create_registry_in_web_ui.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/debug_backend/debug_backend.html","title":"Debug backend","text":""},{"location":"fr/pages/dev/how_to/debug_backend/debug_backend.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/deploy/deploy.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/exclude_featureflag_related_module/exclude_featureflag_related_module.html","title":"Exclude featureflag related module","text":""},{"location":"fr/pages/dev/how_to/exclude_featureflag_related_module/exclude_featureflag_related_module.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/manually_test_enketo/manually_test_enketo.html","title":"Manually test enketo","text":""},{"location":"fr/pages/dev/how_to/manually_test_enketo/manually_test_enketo.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/rebuild_front/rebuild_front.html","title":"Rebuild front","text":""},{"location":"fr/pages/dev/how_to/rebuild_front/rebuild_front.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/run_docs_locally/run_docs_locally.html","title":"Run docs locally","text":""},{"location":"fr/pages/dev/how_to/run_docs_locally/run_docs_locally.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/setup_dev_env/setup_dev_env.html","title":"Setup dev env","text":""},{"location":"fr/pages/dev/how_to/setup_dev_env/setup_dev_env.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/setup_dev_env/setuper.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/use_iaso_apis/use_iaso_apis.html","title":"Use iaso apis","text":""},{"location":"fr/pages/dev/how_to/use_iaso_apis/use_iaso_apis.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html","title":"Write visit on nfc","text":""},{"location":"fr/pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/API/org_unit_change_request_configuration.html","title":"Org unit change request configuration","text":""},{"location":"fr/pages/dev/reference/API/org_unit_change_request_configuration.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/API/org_unit_registry.html","title":"Org unit registry","text":""},{"location":"fr/pages/dev/reference/API/org_unit_registry.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/API/payments/payments.html","title":"Payments","text":""},{"location":"fr/pages/dev/reference/API/payments/payments.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/campaigns/subactivities.html","title":"Subactivities","text":""},{"location":"fr/pages/dev/reference/campaigns/subactivities.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/data_model_glossary/data_model_glossary.html","title":"Data model glossary","text":""},{"location":"fr/pages/dev/reference/data_model_glossary/data_model_glossary.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/doc_setup/doc_setup.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/docker/docker.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/entities_in_iaso/entities_in_iaso.html","title":"Entities in iaso","text":""},{"location":"fr/pages/dev/reference/entities_in_iaso/entities_in_iaso.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/env_variables/env_variables.html","title":"Env variables","text":""},{"location":"fr/pages/dev/reference/env_variables/env_variables.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/front-end_reference/front-end_reference.html","title":"Front end reference","text":""},{"location":"fr/pages/dev/reference/front-end_reference/front-end_reference.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/guidelines/back-end/back-end.html","title":"Back end","text":""},{"location":"fr/pages/dev/reference/guidelines/back-end/back-end.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/guidelines/front-end/front-end.html","title":"Front end","text":""},{"location":"fr/pages/dev/reference/guidelines/front-end/front-end.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/guidelines/git/git.html","title":"Git","text":""},{"location":"fr/pages/dev/reference/guidelines/git/git.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/public_registry/public_registry.html","title":"Public registry","text":""},{"location":"fr/pages/dev/reference/public_registry/public_registry.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html","title":"SQL Dashboard feature","text":""},{"location":"fr/pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/vector_control/vector_control.html","title":"Vector control","text":""},{"location":"fr/pages/dev/reference/vector_control/vector_control.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/FAQ/faq.html","title":"Faq","text":""},{"location":"fr/pages/users/FAQ/faq.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/how_to/convert_docx_to_md/convert_docx_to_md.html","title":"Convert docx to md","text":""},{"location":"fr/pages/users/how_to/convert_docx_to_md/convert_docx_to_md.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html","title":"Create new documentation page","text":""},{"location":"fr/pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/how_to/edit_documentation/edit_documentation.html","title":"Edit documentation","text":""},{"location":"fr/pages/users/how_to/edit_documentation/edit_documentation.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html","title":"Setup an empty iaso account","text":""},{"location":"fr/pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html","title":"Setup dhis2 login in iaso","text":""},{"location":"fr/pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/reference/iaso_concepts/iaso_concepts.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/reference/iaso_mobile/iaso_mobile.html","title":"Application mobile","text":""},{"location":"fr/pages/users/reference/iaso_mobile/iaso_mobile.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/reference/iaso_modules/iaso_modules.html","title":"Modules","text":""},{"location":"fr/pages/users/reference/iaso_modules/iaso_modules.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/reference/iaso_web/user_guide.html","title":"Plateforme web","text":""},{"location":"fr/pages/users/reference/iaso_web/user_guide.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/reference/interop/interop.html","title":"Interop\u00e9rabilit\u00e9: roadmap 2023-2024","text":""},{"location":"fr/pages/users/reference/interop/interop.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""}]} \ No newline at end of file +{"config":{"lang":["en","fr"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"AWS-Deployment.html","title":"How Iaso is deployed on AWS","text":"on ElasticBeanstalk + RDS
"},{"location":"AWS-Deployment.html#main-parts","title":"Main parts","text":"This documentation concerned the main Iaso deployment, that are done on AWS.
The main pillar is AWS Elastic beanstalk
Which is kind of a magic solution from Amazon that tie several of their service together, handle deployment logic, etc...
In the past we configured it by hands but now we are moving toward having it all handled via Terrraform so it is in code (we can have an history, avoid misclick, do complex ops etc...).
The technical term for this is \"Provisioning\" if you want to look it up.
"},{"location":"AWS-Deployment.html#setup-of-the-elastic-beainstalk","title":"Setup of the Elastic Beainstalk","text":"See https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-django.html
We have custom commands and configuration in .ebextensions/ and in and in .platform/ to extend the nginx config.
"},{"location":"AWS-Deployment.html#running-django-3-on-elastic-beanstalk-custom-ami","title":"Running Django 3 on Elastic Beanstalk / Custom AMI","text":"Django 3 requires version 2+ of the gdal library. Sadly, Beanstalk is based on Amazon Linux that contain an outdated version of GDAL.
To fix this you have to create a custom AMI to use in your Elastic Beanstalk environments and compile and your own version of gdal and it's dependencies.
See the AWS documentation on creating adn using a custom AMI and the Django documentation on compiling the GIS libraries
Custom build of the following libraries were done:
You can check scripts/create_ami.sh
for reference
Read AWS documentation on creating adn using a custom AMI but in summary:
#cloud-config\n repo_releasever: repository version number\n repo_upgrade: none\n
Infrastructure and configuration in the Github repository BLSQ/terraform-iaso-eb There is good documentation from Mbayang in the docs/
folder. Visibility is restricted.
In the BLSQ/iaso
Github repository: * .github
For the workflow info * scripts/
* .ebextension
For the Elastic beanstalk specific command * .platform
Configuration override
Two type of env in elastic beanstalk: * web * worker
We tie them via an \"env\" tag in AWS, so we can deploy them at the same time
One Elastic Beanstalk Env can contain multipe \"EC2 Instance\", that is virtual machine server. Their number auto scale according to rule in elastic beanstalk. Usually we have 1 instance for Worker and 2 for Web.
"},{"location":"AWS-Deployment.html#s3-bucket","title":"S3 bucket","text":"Static are readable by all.
Media are only accessible via Signed url that expire after a laps of time (15 minutes I think) and that are generated on the fly by iaso when needed.
"},{"location":"AWS-Deployment.html#cicd","title":"CI/CD","text":"Deployment of new version is done via Github action.
Each change on main are automically deployed on the staging environment
Deployment to other env have to be manually triggered
"},{"location":"AWS-Deployment.html#deployement-process","title":"Deployement process","text":"How a new version of Iaso is deployed
This is a simplified view, some details are omitted for clarity
iaso
environment. e.g. : staging
-> iaso-staging2
and iaso-staging2-worker
eb deploy
(from awseb cli):.ebextensions
Action markqe with \u00a5 are part of Elastic beanstalk logic/var/app/staging
\u00a5/var/app/staging
is moved in /var/app/current
\u00a5Deployed separately, handled via Elastic Beanstalk also, linked to Iaso via environment variable. Mbayang manage this
"},{"location":"AWS-Deployment.html#aws-sqs","title":"AWS SQS","text":"Queue system used for Worker, see worker section in README
"},{"location":"AWS-Deployment.html#s3-bucket_1","title":"S3 bucket","text":"S3 see above
"},{"location":"AWS-Deployment.html#architecture-inside-the-vm","title":"Architecture inside the VM","text":"Code is in the /var/app/current
Systemctl launch the web server as the web
unit. This is done via Gunicorn under the web user, gunicorn launch multiple Django server.
There is a NGINX in front of gunicorn. The above is handled automatically via Iaso
The logs can be listed inside the VM via journalctl -u web
We have 2 crons (for now). They can be seen by using systemctl list-timers
IASO is an innovative, open-source, bilingual (EN/FR) data collection platform with advanced geospatial features to plan, monitor and evaluate health, environmental or social programmes in low- and middle-income settings (LMICs). IASO is recognized as a Digital Public Good by the Digital Public Good Alliance and listed as a Digital Square Software Global Good, a testament to its proven impact. For more information and detailed use cases, please visit IASO website.
IASO comprises:
In terms of features, IASO can be summarized around four main components which are interconnected and expand the powers of one another:
Geospatial data management (Georegistry)
Geo-structured data collection
Geo-enabled Microplanning
Entities - these can be individuals (e.g. health programme beneficiaries) or physical objects (e.g. vaccines parcel, mosquito net, etc.). Workflows allows the tracking of entities by opening specific forms according to previous answers given to previous forms.
The platform has been implemented in Benin, Burkina Faso, Burundi, Cameroon, Central African Republic, the Democratic Republic of Congo, Haiti, Ivory Coast, Mali, Niger, Nigeria and Uganda. It is the official georegistry in Burkina Faso since 2023. IASO has also been implemented at regional level (AFRO region) in support to the Global Polio Eradication Initiative for its geospatial and health facility registries capabilities.
"},{"location":"index.html#technical-stack","title":"Technical stack","text":"IASO is made of a white labeled Android application using Java/Kotlin, reusing large parts of the ODK projects, and a web platform programmed using Python/GeoDjango on top of PostGIS. Frontend is mainly React/Leaflet. The API is implemented via Django rest framework, all data is stored in Postgresql or the media/ directory. One of the aims is the ease of integration with other platforms. We already have csv and geopackage imports and exports and target easy integration with OSM.
The companion mobile app for Android allows form submission and and org unit creation. Forms can also be filled in a web interface via the Enketo companion service.
"},{"location":"pages/dev/how_to/add_new_permission/add_new_permission.html","title":"How to add a new permission in Iaso","text":""},{"location":"pages/dev/how_to/add_new_permission/add_new_permission.html#1-add-permission-in-the-model","title":"1. Add permission in the model","text":"_PREFIX
: MY_PERMISSION = _PREFIX + _MY_PERMISSION
permissions
property of CustomPermissionSupport
's Meta
class: (_MY_PERMISSION, _(\"Access some stuff\"))
/hat/menupermissions/constants.py
MODULE_PERMISSIONS
MODULES
(in the same file)/hat/menupermissions/constants.py
PERMISSIONS_PRESENTATION
/hat/menupermissions/constants.py
READ_EDIT_PERMISSIONS
dictionnaryread
and edit
or other keys like no-admin
and admin
item_key\": {\"read\": \"added_permission\", \"edit\": \"coupled_permission\"}
item_key, read and edit
) and the tooltip message of the principal key(item_key
)docker compose run --rm iaso manage makemigrations && docker compose run --rm iaso manage migrate
/hat/assets/js/apps/Iaso/utils/permissions.ts
. Add and export a constant with the permission key, in a similar way as what was done for the backend in step 1.permissionMessages.ts
. The tooltip key should have the format: <permission name>_tooltip
to enable the component to recognize and translate it.en.json
and fr.json
/hat/assets/js/apps/Iaso/domains/modules/messages.ts
To set up automatic formatting with Black in Visual Studio Code:
Click \"Install\" for the extension by Microsoft
Create a .vscode/settings.json
file in your project root if it doesn't exist.
Add the following to settings.json
:
```json { \"editor.formatOnSave\": true, \"[python]\": { \"editor.defaultFormatter\": \"ms-python.black-formatter\", \"editor.formatOnSave\": true }, \"black-formatter.args\": [\"--config\", \"${workspaceFolder}/pyproject.toml\"], \"black-formatter.path\": [\"${workspaceFolder}/.venv/bin/black\"] }
```
python3.9 -m venv .venv source .venv/bin/activate
pip install -r requirements-dev.txt
This ensures you're using the version of Black specified in the project's requirements.
.vscode/.prettierignore
file:.git .venv venv node_modules
Now, VS Code will automatically format your Python files using Black when you save them, using the settings specified in your settings.json
file and the version of Black specified in your project's requirements.
Note: This configuration is local to your VS Code environment and won't affect other developers or CI processes that might use the pyproject.toml
file.
We have adopted Black as our code formatting tool. Line length is 120.
The easiest way to use is is to install the pre-commit hook: 1. Install pre-commit: pip install pre-commit 2. Execute pre-commit install to install git hooks in your .git/ directory.
Another good way to have it working is to set it up in your code editor. Pycharm, for example, has good support for this.
The pre-commit is not mandatory but Continuous Integration will check if the formatting is respected!
"},{"location":"pages/dev/how_to/contribute/contribute.html#tests-and-linting","title":"Tests and linting","text":"For the Python backend, we use the Django builtin test framework. Tests can be executed with
docker compose exec iaso ./manage.py test\n
"},{"location":"pages/dev/how_to/contribute/contribute.html#translations","title":"Translations","text":"The few translation for the Django side (login and reset password email etc..) are separated from the test. We only translate the template for now not the python code (string on model or admin).
When modifying or adding new strings to translate, use the following command to regenerate the translations:
manage.py makemessages --locale=fr --extension txt --extension html
This will update hat/locale/fr/LC_MESSAGES/django.po
with the new strings to translate.
After updating it with the translation you need to following command to have them reflected in the interface:
manage.py compilemessages
In development the servers will reload when they detect a file change, either in Python or Javascript. If you need reloading for the bluesquare-components code, see the \"Live Bluesquare Components\" section.
"},{"location":"pages/dev/how_to/contribute/contribute.html#troubleshooting","title":"Troubleshooting","text":"If you need to restart everything
docker compose stop && docker compose start\n
If you encounter problems, you can try to rebuild everything from scratch.
# kill containers\ndocker compose kill\n# remove `iaso` container\ndocker compose rm -f iaso\n# build containers\ndocker compose build\n# start-up containers\ndocker compose up\n
"},{"location":"pages/dev/how_to/contribute/contribute.html#jupyter-notebook","title":"Jupyter Notebook","text":"To run a Jupyter Notebook, just copy the env variable from runaisasdev.sh, activate the virtualenv and run
python manage.py shell_plus --notebook\n
"},{"location":"pages/dev/how_to/contribute/contribute.html#testing-prod-js-assets-in-development","title":"Testing prod js assets in development","text":"During local development, by default, the Javascript and CSS will be loaded from a webpack server with live reloading of the code. To locally test the compiled version as it is in production ( minified and with the same compilation option). You can launch docker compose with the TEST_PROD=true
environment variable set.
e.g TEST_PROD=true docker compose up
This can be useful to reproduce production only bugs. Please also test with this configuration whenever you modify webpack.prod.js to validate your changes.
Alternatively this can be done outside of docker by running:
npm run webpack-prod
to do the buildTEST_PROD
e.g. TEST_PROD=true python manage.py runserver
.Use case: develop or test entity related features in the web interface
"},{"location":"pages/dev/how_to/create_entities_in_web_ui/create_entities_in_web_ui.html#1-get-some-xls-forms","title":"1. Get some XLS Forms","text":"At least one form will be needed as reference form. More would be optimal as it would allow to test the follow-up feature.
"},{"location":"pages/dev/how_to/create_entities_in_web_ui/create_entities_in_web_ui.html#2-create-forms-and-form-versions","title":"2. Create Forms and form versions","text":"For each XLS form:
EntityType
s","text":"Repeat as needed
"},{"location":"pages/dev/how_to/create_entities_in_web_ui/create_entities_in_web_ui.html#4-create-submissions-for-the-reference-forms","title":"4. Create submissions for the reference form(s)","text":"Note: To be able to create submissions, Enketo needs to be running. This can be done with the following command: docker compose -f docker-compose.yml -f docker/docker-compose-enketo.yml up
Entities
with the Django admin","text":"Entities
menu entryEntity
. It should be a submission for the form defined as reference form for the EntityType
of the Entity
being created.from django.db import connection\n\nwith connection.cursor() as cursor:\n cursor.execute('CREATE EXTENSION fuzzystrmatch;')\n
Note: To open a python shell in docker: docker compose exec iaso ./manage.py shell
- In a terminal, launch a task worker: docker compose run iaso manage tasks_worker
- Go to /api/entityduplicates_analyzes
to launch an algorithm analysis (e.g: inverse) - In Iaso web, go to Benefiaries > Duplicates to see if the algorithm matched the duplicates created in previous steps
Use case: develop or test registry related features in the web interface
"},{"location":"pages/dev/how_to/create_registry_in_web_ui/create_registry_in_web_ui.html#1-get-some-submission","title":"1. Get some Submission","text":"Select a submission you are going to use has reference_instance
in an org unit.
We will use one organization unit and link a reference submission to it. Make sure you choose a org unit having children and that they are all validated. Make also sure that the type of this org unit has sub org unit types, and that those sub types are those used by the children org units. This org unit should have a shape and be visible by the account you are using. Children should also be visible, location (point or shape) is not mandatory.
"},{"location":"pages/dev/how_to/create_registry_in_web_ui/create_registry_in_web_ui.html#3-add-reference-instance-to-org-unit","title":"3. Add reference instance to org unit","text":"Org units
menu entryReference instance
fieldNote: To be able to create submissions, Enketo needs to be running. This can be done with the following command: docker compose -f docker-compose.yml -f docker/docker-compose-enketo.yml up
View registry
registry-demo
You can activate the Django Debug Toolbar.
To do so, create a hat/local_settings.py
files and configure the toolbar.
E.g.:
from .settings import * # noqa\n\nfrom debug_toolbar.middleware import show_toolbar\n\n\nINSTALLED_APPS += [\"debug_toolbar\"]\n\nMIDDLEWARE += [\"debug_toolbar.middleware.DebugToolbarMiddleware\"]\n\n\ndef custom_show_toolbar(request):\n included_urls = [\"/__debug__\", \"/admin\", \"/api\"]\n included = any(request.path.startswith(url) for url in included_urls)\n return show_toolbar(request) and included\n\n\nDEBUG_TOOLBAR_CONFIG = {\n \"DISABLE_PANELS\": [\n \"debug_toolbar.panels.redirects.RedirectsPanel\",\n # ProfilingPanel makes the django admin extremely slow.\n \"debug_toolbar.panels.profiling.ProfilingPanel\",\n ],\n \"SHOW_TEMPLATE_CONTEXT\": True,\n \"SHOW_TOOLBAR_CALLBACK\": custom_show_toolbar,\n}\n
"},{"location":"pages/dev/how_to/deploy/deploy.html","title":"Deploy","text":""},{"location":"pages/dev/how_to/deploy/deploy.html#0-make-sure-everything-is-there","title":"0. Make sure everything is there","text":"For IASO deployment make sure you ran eb init with following info
eb init\nSelect a default region\n5) eu-central-1 : EU (Frankfurt)\nSelect an application to use\n4) Iaso\nDo you wish to continue with CodeCommit? (y/N) (default is n): n\n
"},{"location":"pages/dev/how_to/deploy/deploy.html#1-prepare-assets","title":"1. Prepare assets","text":"To avoid long/failing deployment, we commit the production assets in the repository
git checkout development\ngit pull\nrm hat/assets/webpack/*\nnpm run webpack-prod\ngit add hat/assets/webpack/\ngit commit -m 'Committing assets'\ngit push\n
Troubleshooting :
Make sure you have eb installed and run eb init
Then you deploy the development
branch to staging
eb use Iaso-staging\neb deploy\n
eb deploy will take of (via container commands see ./.ebextensions/50_container_commands.config)
Troubleshooting :
Technically : we should merge development in master, and deploy master in production but for the momenent use \"just deploy\" to developement to prod
eb use Iaso-env\neb deploy\n
Check the production
Troubleshooting :
For the Playground, deploy as for staging and prod for the web server, but you also need to update the jupyter server (using the pem file you can find on 1password)
Note jupyter is currently using the development branch too.
ssh -i ~/.ssh/lightsail.pem ubuntu@18.196.197.98\ncd iaso\ngit pull\nsource bin/activate\npip install -r requirements.txt\nkillall python\nnohup ./run.sh &\n
Obviously, a more stable playground setup would be welcome.
Troubleshooting :
chmod 600 ~/.ssh/lightsail.pem
/hat/menupermissions/constants.py
FEATUREFLAGES_TO_EXCLUDE
a new itemDATA_COLLECTION_FORMS
in capitale letter[\"FEATUREFLAG_1\",\"FEATUREFLAG_2\"]
FEATUREFLAGES_TO_EXCLUDE
should be like FEATUREFLAGES_TO_EXCLUDE = { \"MODULE_1\": [\"FEATUREFLAG_1\"], \"MODULE_2\": [\"FEATUREFLAG_2\", \"FEATUREFLAG_3\"],}
/hat/menupermissions/constants.py
Ensure you have a usable project, form (with form version etc..) and an orgunit in iaso. And Enketo is working. You should be able to make a submission from the web interface. Do it to verify everything
Edit the Project in Django admin. And copy the external token It's automatically generated, so you should always have one
token='xxxxxxxxx-xxxx-xxxxx-xxxxx-xxxxxxxxx'
Still in the admin, take the form_id on the Form (caution: this is an external id, a string, different from the form.id) You should have one if you uploaded a correct FormVersion
form_id= 'FORM_ID'
On the OrgUnit take the external token: (it's in the dashboard)
external_org_unit_id= \"AAAA0000000000\"
Make an url with it:
f\"/api/enketo/public_create_url/?period={period}&form_id={form_id}&token={token}&external_org_unit_id={external_org_unit_id}\"\n=> '/api/enketo/public_create_url/?period=202301&form_id=FORM_ID&token=xxxxxxxxx-xxxx-xxxxx-xxxxx-xxxxxxxxx&external_org_unit_id=AAAA0000000000'\n
Iaso will return a json with a URL, open it. Fill the form.
To test the export, add the to_export=true argument. You will need FormMapping and a DHIS2 configuration to test the full export
"},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html","title":"Rebuilding the Frontend","text":""},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#docker","title":"Docker","text":""},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#removing-existing-docker-images","title":"Removing Existing Docker Images:","text":"docker rmi -f iaso-webpack:latest\n
"},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#build-the-new-docker-image","title":"Build the new Docker image:","text":"docker compose build --no-cache webpack\n
"},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#local","title":"Local","text":""},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#removing-node_modules","title":"Removing node_modules:","text":"rm -rf node_modules\n
"},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#clean-npm-cache","title":"Clean npm cache:","text":"npm cache clean --force\n
"},{"location":"pages/dev/how_to/rebuild_front/rebuild_front.html#reinstall-npm-packages","title":"Reinstall npm packages:","text":"npm ci\n
"},{"location":"pages/dev/how_to/run_docs_locally/run_docs_locally.html","title":"How to run this site locally","text":"/docs/
pip install -r requirements.txt
mkdocs serve
A running local instance for development can be spun up via docker compose which will install and configure all deps in separate container. As such your computer should only need:
If docker compose give you trouble, make sure it can connect to the docker daemon.
If you use an Apple Silicon Mac, ensure export DOCKER_DEFAULT_PLATFORM=linux/amd64
is set.
A pgdata-iaso
folder, containing the database data, will be created in the parent directory of the git repository.
The docker-compose.yml file contains sensible defaults for the Django application.
Other environment variables can be provided by a .env file.
As a starting point, you can copy the sample .env.dist file and edit it to your needs.
cp .env.dist .env\n
note: All the commands here need to be run in the project directory in which the repository was cloned
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#2-build-the-containers","title":"2. Build the containers","text":"This will build and download the containers.
docker compose build\n
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#3-start-the-database","title":"3. Start the database","text":"docker compose up db\n
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#4-run-migrations","title":"4. Run migrations","text":"docker compose run --rm iaso manage migrate\n
If you get a message saying that the database iaso does not exist, you can connect to your postgres instance using
psql -h localhost -p 5433 -U postgres\n
then type
create database iaso; \n
to create the missing database.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#5-start-the-server","title":"5. Start the server","text":"To start all the containers (backend, frontend, db)
docker compose up\n
The web server will be reachable at http://localhost:8081
.
The docker-compose.yml
file describes the setup of the containers.
To login to the app or the Django admin, a superuser needs to be created with:
docker compose exec iaso ./manage.py createsuperuser\n
You can now login in the admin at http://localhost:8081/admin
.
Then additional users with custom groups and permissions can be added through the Django admin or loaded via fixtures.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#7-create-and-import-data","title":"7. Create and import data","text":"To create the initial account, project and profile, do the following:
docker compose exec iaso ./manage.py create_and_import_data\n
You can now login on http://localhost:8081
but still need to import your own data.
An alternative to this and the following steps is to import data from DHIS2.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#8-create-a-form","title":"8. Create a form","text":"Run the following command to create a form:
docker compose exec iaso ./manage.py create_form\n
At this point, if you want to edit forms directly on your machine using Enketo, go to the Enketo setup section of this README (down below).
Once you are done, you can click on the eye for your newly added form, click on \"+ Create\", tap a letter, then enter, select the org unit, then click \"Create submission\".
If Enketo is running and well setup, you can fill the form now.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#9-start-adding-features","title":"9. Start adding features","text":"You can now start to develop additional features on Iaso!
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#10-import-orgunit-forms-and-submission-from-dhis2","title":"10. Import OrgUnit, Forms and Submission from DHIS2","text":"Alternatively or in addition to steps 7-8, you can import data from the DHIS2 demo server (play.dhis2.org):
docker compose run --rm iaso manage seed_test_data --mode=seed --dhis2version=2.35.3\n
The hierarchy of OrgUnit, group of OrgUnit, Forms, and their Submissions will be imported. OrgUnit types are not handled at the moment
Log in to http://127.0.0.1:8081/dashboard with :
To submit and edit existing form submission from the browser, an Enketo service is needed.
To enable the Enketo editor in your local environment, include the additional docker compose configuration file for Enketo. Do so by invoking docker compose with both files.
docker compose -f docker-compose.yml -f docker/docker-compose-enketo.yml up\n
No additional configuration is needed. The first time the docker image is launched, it will download dependencies and do a build witch may take a few minutes. Subsequents launches are faster.
You can check that the server is correctly launched by going to http://localhost:8005
To seed your DB with typical example forms editable by Enketo, see import data from DHIS2
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#12-database-dump","title":"12. Database dump","text":"To create a copy of your iaso database in a file (dump) you can use:
docker compose exec db pg_dump -U postgres iaso -Fc > iaso.dump\n
The dumpfile will be created on your host. The -Fc
meant it will use an optimised Postgres format (which take less place). If you want the plain sql command use -Fp
docker compose down
docker compose up db
iaso5
You can list existing databases using docker compose exec db psql -U postgres -l
docker compose exec db psql -U postgres -c \"create database iaso5\"
cat iaso.dump | docker compose exec -T db pg_restore -U postgres -d iaso5 -Fc --no-owner /dev/stdin\n
.env
file to use to this database in the RDS_DB_NAME
settings.On the /health/ url you can find listed the Iaso version number, environment, deployment time, etc... that might help you understand how this server instance is deployed for debugging. e.g. https://iaso.bluesquare.org/health/
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#15-set-up-a-local-dhis2-server","title":"15. Set up a local DHIS2 server","text":"Experimental. For development if you need a local dhis2 server, you can spin up one in your docker compose by using the docker/docker-compose-dhis2.yml
configuration file.
Replace your invocations of docker compose
by docker compose -f docker-compose.yml -f docker/docker-compose-dhis2.yml
you need to specify both config files. e.g to launch the cluster:
docker compose -f docker-compose.yml -f docker/docker-compose-dhis2.yml up\n
The DHIS2 will be available on your computer on http://localhost:8080 and is reachable from Iaso as http://dhis2:8080. The login and password are admin / district. If you use it as an import source do not set a trailing /
Database file are stored in ../pgdata-dhis2
and dhis2 log and uploaded files in docker/DHIS2_home
.
You will probably require some sample data in your instance. It is possible to populate your DHIS2 server with sample data from a database dump like it's done for the official play servers. The DHIS2 database take around 3 GB.
The steps as are follow: Download the file, stop all the docker, remove the postgres database directory, start only the database docker, load the database dump and then restart everything.
wget https://databases.dhis2.org/sierra-leone/2.36.4/dhis2-db-sierra-leone.sql.gz\ndocker compose down\nsudo rm ../pgdata-dhis2 -r\ndocker compose up db_dhis2\nzcat dhis2-db-sierra-leone.sql.gz| docker compose exec -T db_dhis2 psql -U dhis dhis2 -f /dev/stdin\ndocker compose up\ncd Projects/blsq/iaso\ndocker compose up dhis2 db_dhis2\n
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#17-set-up-single-sign-on-sso-with-a-local-dhis2","title":"17. Set up Single Sign On (SSO) with a local DHIS2","text":"If you want to test the feature with your local dhis2 you can use the following step. This assume you are running everything in Docker containers
docker compose -f docker-compose.yml -f docker/docker-compose-dhis2.yml up
With the default docker compose setup, iaso is on port 8081 and dhis2 on port 8081 on your machineRedirect URI : http://localhost:8081/api/dhis2/{same as client id}/login/
Setup external credential in iaso
5 Create a new user in Iaso, grant it some rights
In DHIS2 retrieve the id for the user
Add the dhis2 id to the Iaso user : Open the target user in the iaso Admin http://localhost:8081/admin/iaso/profile/ and add it to the dhis2 id field, save.
Unlog from iaso or in a separate session/container
Download and setup Ngrok on https://ngrok.com/. Once Ngrok installed and running you must add your ngrok server url in settings.py
by adding the following line :
FILE_SERVER_URL = os.environ.get(\"FILE_SERVER_URL\", \"YOUR_NGROK_SERVER_URL\")\n
After this step you have to import settings.py
and add FILE_SERVER_URL
to forms.py
in iaso/models/forms as shown on the following lines :
\"file\": settings.FILE_SERVER_URL + self.file.url,\n\"xls_file\": settings.FILE_SERVER_URL + self.xls_file.url if self.xls_file else None\n
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#2-setup-the-mobile-app","title":"2 - Setup the mobile app","text":"Once Ngrok installed and running you have to run the app in developer mode (tap 10 times on the Iaso icon at start ) and connect the mobile app to your server by selecting the 3 dots in the top right corner and select \"change server url\". When connected to your server, refresh all data and your app will be ready and connected to your development server.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#19-sso-with-dhis2","title":"19. SSO with DHIS2","text":"You can use DHIS2 as identity provider to login on Iaso. It requires a little configuration on DHIS2 and Iaso in order to achieve that.
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#1-setup-oauth2-clients-in-dhis2","title":"1 - Setup OAuth2 clients in DHIS2","text":"In DHIS2 settings you must setup your Iaso instance as Oauth2 Clients. Client ID and Grant types must be : * Client ID : What you want (Must be the same as your external credential name in Iaso) * Grant Types : Authorization code
Redirect URIs is your iaso server followed by : /api/dhis2/{your_dhis2_client_id}/login/
For example : https://myiaso.com/api/dhis2/dhis2_client_id/login/
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#2-configure-the-oauth2-connection-in-iaso","title":"2 - Configure the OAuth2 connection in Iaso","text":"In iaso you must setup your dhis2 server credentials. To do so, go to /admin
and setup as follow :
Don't forget the /
at the end of the urls.
sequenceDiagram\n autonumber\n Note right of Browser: user open url to login\n Browser->>DHIS2: GET /uaa/oauth/authorize<br>?client_id={your_dhis2_client_id}\n loop if not logged\n DHIS2->>Browser: Login screen\n Browser->>DHIS2: Enter credentials\n DHIS2->>Browser: Login ok\n end\n DHIS2 -->> Browser: 200 Authorize Iaso? Authorize/Deny\n Browser ->> DHIS2: POST /authorize\n DHIS2 -->> Browser: 303 redirect\n Browser ->> IASO: GET /api/dhis2/<dhis2_slug>/login/?code=\n IASO ->> DHIS2: POST /uaa/oauth/token/\n DHIS2 -->> IASO: access token\n IASO ->> DHIS2 : GET /api/me\n DHIS2 -->> IASO: credential info\n Note right of IASO: find matching IASO user\n Note right of IASO: Log in session\n IASO -->> Browser: 303 Redirect & set cookies\n Browser ->> IASO: Use iaso normally as logged user.\n
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#20live-bluesquare-components","title":"20.Live Bluesquare components","text":"It is possible to configure the project to load a version of Bluesquare components from a local git repository instead of the one installed from a package. This enabled to develop feature necessitating modification in the components code.
To do so: * place the repository in the parent repository of Iaso ../bluesquare-components/
* install the dependency for bluesquare-component by running npm install in its directory * set the environment variable LIVE_COMPONENTS=true
* start your docker compose
cd ..\ngit clone git@github.com:BLSQ/bluesquare-components.git\ncd bluesquare-components\nnpm install\ncd ../iaso\nLIVE_COMPONENTS=true docker compose up\n
This way the page will reload automatically if you make a change to the bluesquare-components code.
This functionality also works if you launch webpack outside of docker.
If you encounter any problem, first check that your repo is on the correct branch and the deps are up-to-date
"},{"location":"pages/dev/how_to/setup_dev_env/setup_dev_env.html#21-task-worker","title":"21. Task worker","text":"In local development, you can run a worker for background tasks by using the command:
docker compose run iaso manage tasks_worker\n
Alternatively, you can call the url tasks/run_all
which will run all the pending tasks in queue.
You can override default application title, logo and colors using the .env
file and specify those variables:
THEME_PRIMARY_COLOR=\"<hexa_color>\"\nTHEME_PRIMARY_BACKGROUND_COLOR=\"<hexa_color>\"\nTHEME_SECONDARY_COLOR=\"<hexa_color>\"\nAPP_TITLE=\"<app_title>\"\nFAVICON_PATH=\"<path_in_static_folder>\"\nLOGO_PATH=\"<path_in_static_folder>\"\nSHOW_NAME_WITH_LOGO=\"<'yes' or 'no'>\"\n
Those settings are optional and are using a default value if nothing is provided
"},{"location":"pages/dev/how_to/setup_dev_env/setuper.html","title":"Setuper","text":""},{"location":"pages/dev/how_to/setup_dev_env/setuper.html#introduction","title":"Introduction","text":"The setuper.py script:
It will:
Once the script has run, you can log in to your server using the account name as login and password.
"},{"location":"pages/dev/how_to/setup_dev_env/setuper.html#how-to-use","title":"How To Use","text":"Backup your DB
docker compose exec db pg_dump -U postgres iaso -Fc > ~/Desktop/iaso.dump\n
Use an empty DB
# Find your Iaso DB.\ndocker compose exec db psql -U postgres -l\n\n# Delete your Iaso DB.\ndocker compose exec db psql -U postgres -c \"drop database if exists iaso\"\n\n# Create your Iaso DB.\ndocker compose exec db psql -U postgres -c \"create database iaso\"\n
Run the Django server in a first terminal (this will run DB migrations)
docker compose up iaso\n
Run a worker in a second terminal
docker compose run iaso manage tasks_worker\n
Create a superuser
docker compose exec iaso ./manage.py createsuperuser\n
Prepare the setuper (it requires a local Python 3)
cd setuper\n
Create a virtual env for your local Python:
python3 -m venv venv source venv/bin/activate
Install requirements:
pip install -r requirements.txt\n
Update credentials.py
because we need a user with API access (use your superuser credentials)
cp data/sample-credentials.py credentials.py
Run the setuper
python3 setuper.py\n
It's a relatively standard json based API built using the Django Rest Framework.
Here is a sample Python script showing how to fetch your list of submissions:
import os\nimport requests\n\n# API setup\nserver = \"https://iaso.bluesquare.org\"\ncreds = {\n \"username\": USERNAME,\n \"password\": PASSWORD\n}\n\ninstances_endpoint = server + \"/api/instances/\"\n\n# get API token\nr = requests.post(server + \"/api/token/\", json=creds)\n\ntoken = r.json().get('access')\nheaders = {\"Authorization\": \"Bearer %s\" % token}\n\n# request submissions data\nr = requests.get(instances_endpoint,\n headers=headers)\n\nj = r.json()\n
Most endpoints of Iaso provide exports to csv through the mechanisms provided by the Django Rest Framework (by adding format=csv
to the url) and some of them provide exports to xlsx (xlsx=true
) and geopackage (gpkg=true
) formats.
In the context of CODA2, Iaso will have to write some forms\u2019 information on an NFC card. The NFC card\u2019s size may vary in the future, but the current size of the card is 8k. Yet, the card is currently split into two partitions; one is used by CODA and the other by SCOPE. SCOPE has 6kB out of the 8, leaving 2kB to CODA.
Famoco provided the implementation for the CODA 1.5 application.
We know that the patient\u2019s profile can take up to 500B, and the subsequent visits can take up to 144B with a total of 8 visits maximum.
This gives us a total of 1652B if all slots are filled.
I assume that the remaining 396B (2048 - 1652) is the encryption overhead.
Assuming Famoco is using an AES/CBC symmetric encryption with a salt of 256B and an IV (initialization vector) of 16B over the whole data, the assumption would hold.
"},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#famocos-split","title":"Famoco\u2019s split","text":"In the documentation provided by Famoco, called DESFireService_v1.0.1, they mention two types of writing:
File \u201c01\u201d, which we\u2019ll call the profile, is up to 500B
File \u201c02\u201d, which we\u2019ll call the visits, is of variable length based > on the type of card used.
The profile is marked as a binary file overridden on each writing.
The visits are marked as a cyclic record. Every time a visit is recorded, it is added to the previous ones. If the max length is reached, the oldest record is deleted to make room for the new one (FIFO).
\u2753 It is yet to be defined if a model outside of Famoco should follow the same split and enforce the writing of one record at a time."},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#ndef-messages-and-ndef-records","title":"NDEF Messages and NDEF Records","text":"NFC Data Exchange Format (or NDEF) is a lightweight binary format, used to encapsulate typed data. It is specified by the NFC Forum, for transmission and storage with NFC. However, it is transport agnostic.
The format defines Messages and Records:
An NDEF Record contains typed data, such as MIME-type media, a URI, > or a custom application payload.
An NDEF Message is a container for one or more NDEF Records.
As described in the Android documentation, It is mandatory for all Android devices with NFC to correctly enumerate Ndef on NFC Forum Tag Types 1-4, and implement all NDEF operations as defined in said documentation.
Therefore, it guarantees that a perfectly written tag will be readable by all Android devices with NFC.
Finally, the overhead of NDEF is existent but can be considered negligible compared to the advantages it brings. Based on this StackOverflow answer, the overhead is roughly 12 bytes:
NDEF Header byte: 1 byte
NDEF type length field: 1 byte
NDEF payload length field: 1-4 bytes
NDEF type name \"iaso:p\" (external type): 6 bytes
Famoco\u2019s devices are relying on a Secure Access Module (or SAM) chip which encrypts and guarantees a secured shared secret across the authorized devices.
Unfortunately, regular Android devices can't rely on such a chip. Therefore, we must find a solution to encrypt the card's data in a way only decipherable by another authorized device without an internet connection.
Since all the authorized devices will need to be able to encrypt and decrypt the data, there is no need to go with asymmetric encryption.
The current standard for symmetric encryption is AES.
"},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#the-overhead","title":"The overhead","text":"As explained in this StackOverflow answer, AES doesn\u2019t expand data, but the padding will.
The maximum amount of padding is up to the padding algorithm.
In the case of PKCS7Padding, it should be up to 16 bytes as defined in the RFC3602.
On top of that, it\u2019s good practice to salt the password to make the deciphering even more complex, avoid rainbow tables attacks, and avoid being able to compare two identical records (which is more likely to happen with small forms).
AES also comes with the concept of Initialization Vector (or IV) randomly generated on each encryption.
The salt and the IV will be communicated in clear text alongside the encrypted data to be able to decrypt it. Therefore, they will add up to the number of bytes written on the card.
\ud83d\udd10 As advised by the National Institute of Standards and Technology (NIST), the length of the randomly-generated portion of the salt shall be at least 128 bits (16 bytes). In the current PoC implementation, it\u2019s 256 bytes. \ud83d\udd10 AES requires an IV size of 16 bytesThe total overhead is therefore of the size of the salt (256B), the size of the IV (16B), and the padding (up to 16B), which sums up to 288B but could be lowered to 48B if we reduce the salt to 16B for lesser security.
"},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#how-the-data-is-split","title":"How the data is split","text":"The overhead discussed previously is computed per encryption; if we encrypt all the data at once, the overhead is of that amount. If we encrypt the profile and the visits separately, the overhead is multiplied by two. The overhead is much higher if we encrypt the profile and each visit individually.
\ud83d\udd10 I don\u2019t have sufficient knowledge to tell if there is any security benefit in one or the other approach. Yet, based on the size of the available space on the card, I assume we should encrypt all the data at once or encrypt the profile and the visits separately but not all the visits apiece."},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#sharing-the-nfc-password-securely","title":"Sharing the NFC password securely","text":"Every security measure is as strong as its weakest link. The whole encryption mechanism is rendered useless if the password is too weak, easily guessable, or easily accessible.
Therefore, we need to find a good way to share the password while considering the complexity for the end user to obtain it.
\ud83d\ude08 Keep in mind that nothing is 100% secure. All the solutions we could come up with have flaws, even the SAM chip used by Famoco. Yet, we can only make it harder and harder for someone to retrieve the key to the point that they either can\u2019t retrieve it (missing skills or hardware) or don\u2019t find that it is worth their while."},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#embedded-inside-the-apk","title":"Embedded inside the APK","text":"The easiest way to share a password across authorized devices is to embed it directly into the APK (an APK is a file containing the code and resources of an application that an Android device can interpret to install it).
Yet, this solution is only viable if, and only if, the APK is distributed via a Mobile Device Management (or MDM) and that the MDM can securely prevent people from retrieving the APK from the device.
If the APK is distributed via the Play Store (or any other means that is publicly available or that allow retrieving the APK), the password must be considered compromised and rendered useless.
This solution is similar to what Famoco is doing since, in their case, the password is embedded and secured inside the SAM chip.
"},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#over-the-network","title":"Over the network","text":"This solution is easy for the end user, but more vulnerable as anyone can make a network request.
The client should make an authorized (given a valid token previously retrieved with valid credentials) request to a specific endpoint.
The endpoint would receive a public key (from an asymmetric encryption scheme like RSA) of a hardware-backed key pair as described in Android\u2019s documentation.
The backend would be able to verify the validity of the key and its certificate chain, pointing to a Google valid certificate, and containing the correct application ID and signature.
The password would then be encrypted by the backend with the public key and communicated back to the phone. The application should then be able to decrypt the password and store it securely.
This solution involves that only the application can decrypt the given password.
\ud83d\udd11 This is the investigated solution at the moment of this writing but we are unsure how secure this would be on a rooted Android device.This solution involves security measures that can be hard for attackers to obtain:
Valid credentials
A compromised phone with the application on it.
A way to compromise the device in a way it is not detected by > Android.
If the password is stored inside a physical device (like a USB key, a specific phone application, etc.) that is provided in limited quantities to trusted members of the organization, it can be shared through physical contact between the device to authorize and the trusted member with the physical device.
The password is then securely stored on the authorized device for later use.
This solution is secure but could be cumbersome. For example, if there are many devices to authorize, it can take a long time to transfer the password on each device. There could also be an issue if the devices are spread across the country, and the trusted members must go to each location to authorize the devices.
"},{"location":"pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#a-mix-of-the-above-solutions","title":"A mix of the above solutions","text":"The solutions provided above are not exhaustive and can also be mixed. For example, we could consider storing the password on a physical device to be encrypted with a key that needs to be retrieved via an authorized network request.
The more barrier we put in recovering the key, the more secure it is. Yet, it also makes the logistics for genuine authorized devices more complex.
"},{"location":"pages/dev/reference/API/entity.html","title":"API reference: Entity","text":"Entity and related models
The entity concept might feel a bit abstract, so it might be useful to reason about them using a concrete example (beneficiaries):
Entity
","text":" Bases: SoftDeletableModel
An entity represents a physical object or person with a known Entity Type
Contrary to forms, they are not linked to a specific OrgUnit. The core attributes that define this entity are not stored as fields in the Entity model, but in an Instance / submission
Source code iniaso/models/entity.py
class Entity(SoftDeletableModel):\n\"\"\"An entity represents a physical object or person with a known Entity Type\n\n Contrary to forms, they are not linked to a specific OrgUnit.\n The core attributes that define this entity are not stored as fields in the Entity model, but in an Instance /\n submission\n \"\"\"\n\n name = models.CharField(max_length=255, blank=True) # this field is not used, name value is taken from attributes\n uuid = models.UUIDField(default=uuid.uuid4, editable=False)\n created_at = models.DateTimeField(auto_now_add=True)\n updated_at = models.DateTimeField(auto_now=True)\n entity_type = models.ForeignKey(EntityType, blank=True, on_delete=models.PROTECT)\n attributes = models.OneToOneField(\n Instance, on_delete=models.PROTECT, help_text=\"instance\", related_name=\"attributes\", blank=True, null=True\n )\n account = models.ForeignKey(Account, on_delete=models.PROTECT)\n merged_to = models.ForeignKey(\"self\", null=True, blank=True, on_delete=models.PROTECT)\n\n objects = DefaultSoftDeletableManager.from_queryset(EntityQuerySet)()\n\n objects_only_deleted = OnlyDeletedSoftDeletableManager.from_queryset(EntityQuerySet)()\n\n objects_include_deleted = IncludeDeletedSoftDeletableManager.from_queryset(EntityQuerySet)()\n\n class Meta:\n verbose_name_plural = \"Entities\"\n\n def __str__(self):\n return \"%s %s %s %d\" % (self.entity_type.name, self.uuid, self.name, self.id)\n\n def get_nfc_cards(self):\n from iaso.models.storage import StorageDevice\n\n nfc_count = StorageDevice.objects.filter(entity=self, type=StorageDevice.NFC).count()\n return nfc_count\n\n def as_small_dict(self):\n return {\n \"id\": self.pk,\n \"uuid\": self.uuid,\n \"name\": self.name,\n \"created_at\": self.created_at,\n \"updated_at\": self.updated_at,\n \"entity_type\": self.entity_type_id,\n \"entity_type_name\": self.entity_type and self.entity_type.name,\n \"attributes\": self.attributes and self.attributes.as_dict(),\n }\n\n def as_small_dict_with_nfc_cards(self, instance):\n entity_dict = self.as_small_dict()\n entity_dict[\"nfc_cards\"] = self.get_nfc_cards()\n return entity_dict\n\n def as_dict(self):\n instances = dict()\n\n for i in self.instances.all():\n instances[\"uuid\"] = i.uuid\n instances[\"file_name\"]: i.file_name\n instances[str(i.name)] = i.name\n\n return {\n \"id\": self.pk,\n \"uuid\": self.uuid,\n \"created_at\": self.created_at,\n \"updated_at\": self.updated_at,\n \"entity_type\": self.entity_type.as_dict(),\n \"attributes\": self.attributes.as_dict(),\n \"instances\": instances,\n \"account\": self.account.as_dict(),\n }\n\n def soft_delete_with_instances_and_pending_duplicates(self, audit_source, user):\n\"\"\"\n This method does a proper soft-deletion of the entity:\n - soft delete the entity\n - soft delete its attached form instances\n - delete relevant pending EntityDuplicate pairs\n \"\"\"\n from iaso.models.deduplication import ValidationStatus\n\n original = copy(self)\n self.delete() # soft delete\n log_modification(original, self, audit_source, user=user)\n\n for instance in set([self.attributes] + list(self.instances.all())):\n original = copy(instance)\n instance.soft_delete()\n log_modification(original, instance, audit_source, user=user)\n\n self.duplicates1.filter(validation_status=ValidationStatus.PENDING).delete()\n self.duplicates2.filter(validation_status=ValidationStatus.PENDING).delete()\n\n return self\n
"},{"location":"pages/dev/reference/API/entity.html#iaso.models.entity.Entity.soft_delete_with_instances_and_pending_duplicates","title":"soft_delete_with_instances_and_pending_duplicates(audit_source, user)
","text":"This method does a proper soft-deletion of the entity: - soft delete the entity - soft delete its attached form instances - delete relevant pending EntityDuplicate pairs
Source code iniaso/models/entity.py
def soft_delete_with_instances_and_pending_duplicates(self, audit_source, user):\n\"\"\"\n This method does a proper soft-deletion of the entity:\n - soft delete the entity\n - soft delete its attached form instances\n - delete relevant pending EntityDuplicate pairs\n \"\"\"\n from iaso.models.deduplication import ValidationStatus\n\n original = copy(self)\n self.delete() # soft delete\n log_modification(original, self, audit_source, user=user)\n\n for instance in set([self.attributes] + list(self.instances.all())):\n original = copy(instance)\n instance.soft_delete()\n log_modification(original, instance, audit_source, user=user)\n\n self.duplicates1.filter(validation_status=ValidationStatus.PENDING).delete()\n self.duplicates2.filter(validation_status=ValidationStatus.PENDING).delete()\n\n return self\n
"},{"location":"pages/dev/reference/API/entity.html#iaso.models.entity.EntityType","title":"EntityType
","text":" Bases: models.Model
Its reference_form
describes the core attributes/metadata about the entity type (in case it refers to a person: name, age, ...)
iaso/models/entity.py
class EntityType(models.Model):\n\"\"\"Its `reference_form` describes the core attributes/metadata about the entity type (in case it refers to a person: name, age, ...)\"\"\"\n\n name = models.CharField(max_length=255) # Example: \"Child under 5\"\n code = models.CharField(\n max_length=255, null=True, blank=True\n ) # As the name could change over the time, this field will never change once the entity type created and ETL script will rely on that\n created_at = models.DateTimeField(auto_now_add=True)\n updated_at = models.DateTimeField(auto_now=True)\n # Link to the reference form that contains the core attribute/metadata specific to this entity type\n reference_form = models.ForeignKey(Form, blank=True, null=True, on_delete=models.PROTECT)\n account = models.ForeignKey(Account, on_delete=models.PROTECT, blank=True, null=True)\n is_active = models.BooleanField(default=False)\n # Fields (subset of the fields from the reference form) that will be shown in the UI - entity list view\n fields_list_view = ArrayField(\n models.CharField(max_length=255, blank=True, db_collation=\"case_insensitive\"), size=100, null=True, blank=True\n )\n # Fields (subset of the fields from the reference form) that will be shown in the UI - entity detail view\n fields_detail_info_view = ArrayField(\n models.CharField(max_length=255, blank=True, db_collation=\"case_insensitive\"), size=100, null=True, blank=True\n )\n # Fields (subset of the fields from the reference form) that will be used to search for duplicate entities\n fields_duplicate_search = ArrayField(\n models.CharField(max_length=255, blank=True, db_collation=\"case_insensitive\"), size=100, null=True, blank=True\n )\n prevent_add_if_duplicate_found = models.BooleanField(\n default=False,\n )\n\n class Meta:\n unique_together = [\"name\", \"account\"]\n\n def __str__(self):\n return f\"{self.name}\"\n\n def as_dict(self):\n return {\n \"name\": self.name,\n \"created_at\": self.created_at,\n \"updated_at\": self.updated_at,\n \"reference_form\": self.reference_form.as_dict(),\n \"account\": self.account.as_dict(),\n }\n
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html","title":"OrgUnitChangeRequestConfiguration
API","text":"This document is a first draft of the OrgUnitChangeRequestConfiguration
API that was designed by Benjamin in August 2024 and updated by Thibault. It still needs to be updated and completed, once the developments are over.
API used to create or modify a Change Request configuration.
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#permissions","title":"Permissions","text":"{\n \"uuid\": \"UUID? - OrgUnitChangeRequestConfiguration UUID\",\n \"project_id\": \"Int - Project ID\",\n \"org_unit_type_id\": \"Int - OrgUnit Type ID\",\n \"org_units_editable\": \"Boolean? - Whether or not OrgUnits of this OrgUnit Type are editable\",\n \"editable_fields\": [\"Array<String> - List of possible fields\"],\n \"possible_type_ids\": [\"Array<Int> - List of possible OrgUnit Type IDs that are allowed as new type for this OrgUnit Type\"],\n \"possible_parent_type_ids\": [\"Array<Int> - List of possible OrgUnit Type IDs that are allowed as new parent for this OrgUnit Type\"],\n \"group_set_ids\": [\"Array<Int> - List of GroupSet IDs for this OrgUnit Type\"],\n \"editable_reference_form_ids\": [\"Array<Int> - List of reference Form ID that can be modified\"],\n \"other_group_ids\": [\"Array<Int> - List of possible Group IDs for this OrgUnit Type\"]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#possible-responses","title":"Possible responses","text":""},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#201-created","title":"201 - Created","text":"# Do we return ID/UUID when something is successfully created? Let's try to return it, or even the full OUCRC
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#400-bad-request","title":"400 - Bad request","text":"org_unit_type_id
is not assigned to the given project_id
editable_fields
value is unknownpossible_parent_type_ids
is not a suitable for as a parent for the given OrgUnit Typeeditable_reference_forms
is not a reference form for the given OrgUnit Typeproject_id
is not null and doesn't existorg_unit_type_id
is not null and doesn't existpossible_type_ids
don't existpossible_parent_type_ids
don't existgroup_set_ids
don't existeditable_reference_form_ids
don't existother_group_ids
don't existAPI used to list all Change Request configurations.
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#permissions_1","title":"Permissions","text":"OrgUnitType
to filter onProject
to filter on{\n \"count\": \"Long\",\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\",\n \"results\": [\n {\n \"id\": \"Int - ID in the database\",\n \"uuid\": \"UUID - UUID in the database\",\n \"project\": {\n \"id\": \"Int - Project ID\",\n \"name\": \"String - Project name\"\n },\n \"org_unit_type\": {\n \"id\": \"Int - OrgUnit Type ID\",\n \"name\": \"String - OrgUnit Type name\"\n },\n \"org_units_editable\": \"Boolean - Whether or not OrgUnits of this OrgUnit Type are editable\",\n \"editable_fields\": \"Array<String> - List of possible fields\",\n \"created_at\": \"Timestamp\",\n \"created_by\": {\n \"id\": \"Int - User ID\",\n \"username\": \"String\",\n \"first_name\": \"String\",\n \"last_name\": \"String\"\n },\n \"updated_at\": \"Timestamp\",\n \"updated_by\": {\n \"id\": \"Int - User ID\",\n \"username\": \"String\",\n \"first_name\": \"String\",\n \"last_name\": \"String\"\n }\n }\n ]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#400-bad-request_1","title":"400 - Bad request","text":"project_id
)API used to fully retrieve a Change Request configuration.
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#permissions_2","title":"Permissions","text":"{\n \"id\": \"Int - ID in the database\",\n \"uuid\": \"UUID - UUID in the database\",\n \"project\": {\n \"id\": \"Int - Project ID\",\n \"name\": \"String - Project name\"\n },\n \"org_unit_type\": {\n \"id\": \"Int - OrgUnit Type ID\",\n \"name\": \"String - OrgUnit Type name\"\n },\n \"org_units_editable\": \"Boolean - Whether or not OrgUnits of this OrgUnit Type are editable\",\n \"editable_fields\": \"Array<String> - List of possible fields\",\n \"possible_types\": [\n {\n \"id\": \"Int - OrgUnit Type ID\",\n \"name\": \"String - OrgUnit Type name\"\n }\n ],\n \"possible_parent_types\": [\n {\n \"id\": \"Int - OrgUnit Type ID\",\n \"name\": \"String - OrgUnit Type name\"\n }\n ],\n \"group_sets\": [\n {\n \"id\": \"Int - GroupSet ID\",\n \"name\": \"String - GroupSet name\"\n }\n ],\n \"editable_reference_forms\": [\n {\n \"id\": \"Int - Form ID\",\n \"name\": \"String - Form name\"\n }\n ],\n \"other_groups\": [\n {\n \"id\": \"Int - Group ID\",\n \"name\": \"String - Group name\"\n }\n ],\n \"created_at\": \"Timestamp\",\n \"created_by\": {\n \"id\": \"Int - User ID\",\n \"username\": \"String\",\n \"first_name\": \"String\",\n \"last_name\": \"String\"\n },\n \"updated_at\": \"Timestamp\",\n \"updated_by\": {\n \"id\": \"Int - User ID\",\n \"username\": \"String\",\n \"first_name\": \"String\",\n \"last_name\": \"String\"\n }\n}\n
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#401-unauthorized_2","title":"401 - Unauthorized","text":"API used to list all Change Request configurations for the mobile app.
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#permissions_3","title":"Permissions","text":"{\n \"count\": \"Long\",\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\",\n \"results\": [\n {\n \"org_unit_type_id\": \"Int - OrgUnit Type ID\",\n \"org_units_editable\": \"Boolean - Whether or not OrgUnits of this OrgUnit Type are editable\",\n \"editable_fields\": [\"Array<String> - List of possible fields\"],\n \"possible_type_ids\": [\"Array<Int> - List of possible OrgUnit Type IDs that are allowed as new type for this OrgUnit Type\"],\n \"possible_parent_types\": [\"Array<Int> - List of possible OrgUnit Type IDs that are allowed as new parent for this OrgUnit Type\"],\n \"group_sets\": [\"Array<Int> - List of GroupSet IDs for this OrgUnit Type\"],\n \"editable_reference_forms\": [\"Array<Int> - List of reference Form ID that can be modified\"],\n \"other_groups\": [\"Array<Int> - List of other Group IDs for this OrgUnit Type\"],\n \"created_at\": \"Timestamp\",\n \"updated_at\": \"Timestamp\"\n }\n ]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_change_request_configuration.html#400-bad-request_2","title":"400 - Bad request","text":"project_id
)OrgUnitChangeRequest
API","text":"\"Change Requests\" can be submitted for an OrgUnit
and then reviewed (approved or rejected).
This API allows the DHIS2 Health pyramid to be updated using Iaso.
The Django model that stores \"Change Requests\" is OrgUnitChangeRequest
.
OrgUnitChangeRequest
object - Web + Mobile (IA-2421)","text":"POST /api/orgunits/changes/?app_id=\u2026
app_id
: String - Optional, project for which this is created.{\n \"uuid\": \"UUID - Client generated UUID\",\n \"org_unit_id\": \"String - id or UUID of the OrgUnit to change\",\n \"new_parent_id\": \"String? - id or UUID of the parent OrgUnit, null to erase, omitted to ignore.\",\n \"new_name\": \"String? - Name of the OrgUnit, \\\"\\\" (empty string) to erase, omitted to ignore.\",\n \"new_org_unit_type_id\": \"Int? - id of the OrgUnitType, null to erase, omitted to ignore.\",\n \"new_groups\": \"Array of Group ids? - empty array to erase, omitted to ignore.\",\n \"new_location\": {\n \"\": \"New geopoint for the OrgUnit, null to erase, omitted to ignore.\",\n \"latitude\": \"Double - New latitude of the OrgUnit\",\n \"longitude\": \"Double - New longitude of the OrgUnit\",\n \"altitude\": \"Double - New altitude of the OrgUnit\"\n },\n \"new_location_accuracy\": \"Double - New accuracy of the OrgUnit, null to erase, omitted to ignore.\",\n \"new_opening_date\": \"Timestamp, null to erase, omitted to ignore.\",\n \"new_closed_date\": \"Timestamp, null to erase, omitted to ignore.\",\n \"new_reference_instances\": \"Array of instance ids or UUIDs? - empty array to erase, omitted to ignore.\"\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#possible-responses","title":"Possible responses","text":""},{"location":"pages/dev/reference/API/org_unit_registry.html#201-created","title":"201 - Created","text":""},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request","title":"400 - Bad request","text":"String
field has an empty valuenew_parent_id
is not a valid OrgUnitnew_org_unit_type_id
is not a valid OrgUnitTypenew_groups
id is not a valid Groupnew_reference_instances
only one reference instance can exist by org_unit/form pairnew_org_unit_type_id
is not part of the user accountnew_closed_date
must be later than new_opening_date
new_parent_id
and org_unit_id
must have the same versionnew_parent_id
is already a child of org_unit_id
new_reference_instances
ids is not foundnew_parent_id
is not foundnew_org_unit_type_id
is not foundOrgUnitChangeRequest
objects - Web only (IA-2422)","text":"GET /api/orgunits/changes/
page
: Int (optional) - Current page (default: 1)limit
: Int (optional) - Number of entities returned by page (default: 20)org_unit_id
: Int (optional) - Id of the OrgUnit to which the changes apply (default: null)org_unit_type_id
: Int (optional) - Id of the OrgUnitType to filter on, either the old OrgUnitType before the change or the new one after the change (default: null)status
: Array> (optional) - One of new
, validated
, rejected
to filter the requests (default: null)&status=rejected&status=new
parent_id
: Int (optional) - Id of the old parent OrgUnit to filter on, before the change (default: null)project
: Int (optional) - Id of the project to filter on.groups
: List of int, comma separated (optional) - Ids of the old groups to filter on, before the change (default: null)&groups=1847,1846
forms
: List of int, comma separated (optional) - Ids of the old forms to filter on, before the change (default: null)&forms=12,34
users
: List of int, comma separated (optional) - Ids of the users who either created or last updated the change request (default: null)&users=56,78
user_roles
: List of int, comma separated (optional) - Ids of the old user roles to filter on, specifically the roles associated with the user who created the change request (default: null)&user_roles=90,123
with_location
: String (optional) - Filters the change requests based on the presence (\"true\"
) or absence (\"false\"
) of an old location, before the change (default: null)&with_location=true
{\n \"count\": \"Long\",\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\",\n \"results\": [\n {\n \"id\": \"Int - id in the database\",\n \"uuid\": \"UUID - uuid in the database\",\n \"org_unit_id\": \"Int - id of the OrgUnit\",\n \"org_unit_uuid\": \"UUID - uuid of the OrgUnit\",\n \"org_unit_name\": \"String - name of the OrgUnit\",\n \"org_unit_type_id\": \"Int - id of the current OrgUnitType\",\n \"org_unit_type_name\": \"String - name of the current OrgUnitType\",\n \"status\": \"Enum<Status> - one of `new`, `validated`, `rejected`\",\n \"groups\": [\n {\n \"id\": \"Int - id of the Group\",\n \"name\": \"String - name of the Group\"\n }\n ],\n \"requested_fields\": \"Array<String> - name of the properties that were requested to change\",\n \"approved_fields\": \"Array<String>? - name of the properties that were approved to change\",\n \"rejection_comment\": \"String? - Comment about why the changes were rejected\",\n \"created_by\": {\n \"id\": \"Int - id of the User who created that request\",\n \"username\": \"String?\",\n \"first_name\": \"String?\",\n \"last_name\": \"String?\"\n },\n \"created_at\": \"Timestamp\",\n \"updated_by\": {\n \"\": \"May be null\",\n \"id\": \"Int - id of the User\",\n \"username\": \"String?\",\n \"first_name\": \"String?\",\n \"last_name\": \"String?\"\n },\n \"updated_at\": \"Timestamp?\"\n }\n ]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request_1","title":"400 - Bad request","text":"page
or limit
cannot be parsed to a correct integer valueOrgUnitChangeRequest
objects - Mobile only (IA-2425)","text":"GET /api/mobile/orgunits/changes/?app_id=\u2026
app_id
: String - Must be provided, project for which this is queried.last_sync
: DateString - May be null or omitted. Limits the results to everything that was modified after this DateStringlast_sync
filter is built with django.utils.dateparse.parse_datetime
and allows:&last_sync=2023-09-26T17:21:22.921692Z
&last_sync=2021-09-26T17:21:22Z
&last_sync=2021-09-26T17:21:22
&last_sync=2021-09-26T17:21
page
: Int (optional) - Current page (default: 1)limit
: Int (optional) - Number of entities returned by page (default: 20){\n \"count\": \"Long\",\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\",\n \"results\": [\n {\n \"id\": \"Int - id in the database\",\n \"uuid\": \"UUID - uuid in the database\",\n \"org_unit_id\": \"Int - id of the OrgUnit\",\n \"org_unit_uuid\": \"UUID - uuid of the OrgUnit\",\n \"status\": \"Enum<Status> - one of `new`, `validated`, `rejected`\",\n \"approved_fields\": \"Array<String>? - name of the properties that were approved to change\",\n \"rejection_comment\": \"String? - Comment about why the changes were rejected\",\n \"created_at\": \"Timestamp in double\",\n \"updated_at\": \"Timestamp in double\",\n \"new_parent_id\": \"String? - id or UUID of the parent OrgUnit, may be null or omitted.\",\n \"new_name\": \"String? - Name of the OrgUnit, may be null or omitted.\",\n \"new_org_unit_type_id\": \"Int? - id of the OrgUnitType, may be null or omitted\",\n \"new_groups\": \"Array of Group ids? - can be empty, null or omitted. Empty means we want to remove all values\",\n \"new_location\": {\n \"\": \"New geopoint for the OrgUnit, may be null or omitted\",\n \"latitude\": \"Double - New latitude of the OrgUnit\",\n \"longitude\": \"Double - New longitude of the OrgUnit\",\n \"altitude\": \"Double - New altitude of the OrgUnit\"\n },\n \"new_location_accuracy\": \"Double - New accuracy of the OrgUnit\",\n \"new_opening_date\": \"Timestamp in double\",\n \"new_closed_date\": \"Timestamp in double\",\n \"new_reference_instances\": [\n {\n \"id\": \"Int\",\n \"uuid\": \"UUID - provided by the client\",\n \"form_id\": \"Int\",\n \"form_version_id\": \"Int\",\n \"created_at\": \"Timestamp in double\",\n \"updated_at\": \"Timestamp in double\",\n \"json\": \"JSONObject - contains the key/value of the instance\"\n }\n ]\n }\n ]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request_2","title":"400 - Bad request","text":"app_id
was not providedpage
or limit
cannot be parsed to a correct integer valuelast_sync
cannot be parsed to a correct date time.OrgUnitChangeRequest
object - Web only (IA-2423)","text":"GET /api/orgunits/changes/{id}/
{\n \"id\": \"Int - id in the database\",\n \"uuid\": \"UUID - uuid in the database\",\n \"status\": \"Enum<Status> - one of `new`, `validated`, `rejected`\",\n \"created_by\": {\n \"id\": \"Int - id of the User who created that request\",\n \"username\": \"String?\",\n \"first_name\": \"String?\",\n \"last_name\": \"String?\"\n },\n \"created_at\": \"Timestamp\",\n \"updated_by\": {\n \"\": \"May be null\",\n \"id\": \"Int - id of the User who updated that request\",\n \"username\": \"String?\",\n \"first_name\": \"String?\",\n \"last_name\": \"String?\"\n },\n \"updated_at\": \"Timestamp?\",\n \"requested_fields\": \"Array<String> - name of the properties that were requested to change\",\n \"approved_fields\": \"Array<String>? - name of the properties that were approved to change\",\n \"rejection_comment\": \"String? - Comment about why the changes were rejected\",\n \"org_unit\": {\n \"id\": \"Int - id in the database\",\n \"parent\": {\n \"id\": \"Int - id of the parent OrgUnit\",\n \"name\": \"String - name of the parent OrgUnit\"\n },\n \"name\": \"String - Name of the OrgUnit.\",\n \"org_unit_type\": {\n \"id\": \"Int - id of the OrgUnitType\",\n \"name\": \"String - name of the OrgUnitType\",\n \"short_name\": \"String - short name of the OrgUnitType\"\n },\n \"groups\": [\n {\n \"id\": \"Int - id of the Group\",\n \"name\": \"String - name of the Group\"\n }\n ],\n \"location\": {\n \"\": \"Geopoint for the OrgUnit\",\n \"latitude\": \"Double - New latitude of the OrgUnit\",\n \"longitude\": \"Double - New longitude of the OrgUnit\",\n \"altitude\": \"Double - New altitude of the OrgUnit\"\n },\n \"opening_date\": \"Timestamp?\",\n \"closed_date\": \"Timestamp?\",\n \"reference_instances\": [\n \"Array of form objects - can be empty\",\n {\n \"id\": \"Int - id in the database\",\n \"form_id\": \"id of the form\",\n \"form_name\": \"Name of the form\",\n \"values\": [\n {\n \"key\": \"String\",\n \"label\": \"String or translated object\",\n \"value\": \"String\"\n }\n ]\n }\n ]\n },\n \"new_parent\": {\n \"\": \"May be null\",\n \"id\": \"Int - id of the new parent OrgUnit in the database\",\n \"name\": \"String? - name of the new parent OrgUnit\"\n },\n \"new_name\": \"String? - New name of the OrgUnit, may be null or omitted\",\n \"new_org_unit_type\": {\n \"\": \"May be null\",\n \"id\": \"Int? - id of the new OrgUnitType\",\n \"name\": \"String? - name of the new OrgUnitType\",\n \"short_name\": \"String? - short name of the new OrgUnitType\"\n },\n \"new_groups\": [\n {\n \"id\": \"Int - id of the Group\",\n \"name\": \"String - name of the Group\"\n }\n ],\n \"new_location\": {\n \"\": \"New GeoPoint? for the OrgUnit, may be null or omitted\",\n \"latitude\": \"Double - New latitude of the OrgUnit\",\n \"longitude\": \"Double - New longitude of the OrgUnit\",\n \"altitude\": \"Double - New altitude of the OrgUnit\"\n },\n \"new_location_accuracy\": \"Double? - New accuracy of the OrgUnit\",\n \"new_opening_date\": \"Timestamp?\",\n \"new_closed_date\": \"Timestamp?\",\n \"new_reference_instances\": [\n \"Array of form objects? - may be null or omitted, cannot be empty\",\n {\n \"id\": \"Int - id in the database\",\n \"form_id\": \"id of the form\",\n \"form_name\": \"Name of the form\",\n \"values\": [\n {\n \"key\": \"String\",\n \"label\": \"String or translated object\",\n \"value\": \"String\"\n }\n ]\n }\n ],\n \"old_name\": \"String? - Old name of the OrgUnit, may be an empty\",\n \"old_org_unit_type\": {\n \"\": \"May be null\",\n \"id\": \"Int? - id of the old OrgUnitType\",\n \"name\": \"String? - name of the old OrgUnitType\",\n \"short_name\": \"String? - short name of the old OrgUnitType\"\n },\n \"old_groups\": [\n \"Array of old groups objects? - may be empty\",\n {\n \"id\": \"Int - id of the Group\",\n \"name\": \"String - name of the Group\"\n }\n ],\n \"old_location\": {\n \"\": \"Old GeoPoint? for the OrgUnit, may be null\",\n \"latitude\": \"Double - New latitude of the OrgUnit\",\n \"longitude\": \"Double - New longitude of the OrgUnit\",\n \"altitude\": \"Double - New altitude of the OrgUnit\"\n },\n \"old_opening_date\": \"Timestamp? - may be null\",\n \"old_closed_date\": \"Timestamp? - may be null\",\n \"old_reference_instances\": [\n \"Array of old form instance objects? - may be empty\",\n {\n \"id\": \"Int - id in the database\",\n \"form_id\": \"id of the form\",\n \"form_name\": \"Name of the form\",\n \"values\": [\n {\n \"key\": \"String\",\n \"label\": \"String or translated object\",\n \"value\": \"String\"\n }\n ]\n }\n ]\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request_3","title":"400 - Bad request","text":"OrgUnitChangeRequest
- Web only (IA-2424)","text":"PATCH /api/orgunits/changes/{id}/
API to change the status of on change request.
"},{"location":"pages/dev/reference/API/org_unit_registry.html#permissions_4","title":"Permissions","text":"ORG_UNITS_CHANGE_REQUEST_REVIEW
permission{\n \"status\": \"Enum<Status> - One of `validated` or `rejected`\",\n \"approved_fields\": \"Array<Enum<Field>>? - name of the properties that were approved to change\",\n \"rejection_comment\": \"String? - Comment about why the changes were rejected\"\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#possible-responses_4","title":"Possible responses","text":""},{"location":"pages/dev/reference/API/org_unit_registry.html#204-no-content","title":"204 - No content","text":"Change were applied successfully
"},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request_4","title":"400 - Bad request","text":"status
of the change to be patched is not new
status
must be approved
or rejected
status
was validated
but approved_fields
was null, omitted or empty: at least one approved_fields
must be providedapproved_fields
contains one or more unknown fieldsstatus
was rejected
but rejection_comment
was null, omitted or empty: a rejection_comment
must be providedreference_instances
objects for a given OrgUnit
- Mobile only (IA-2420)","text":"GET /api/mobile/orgunits/{id or UUID}/reference_instances?app_id=\u2026
Returns Instance
objects marked as reference_instances
for an OrgUnit
from newest to oldest.
page
: Int (optional) - Current page (default: 1)limit
: Int (optional) - Number of entities returned by page (default: 20)app_id
: String - project for which this is queried.last_sync
: DateString - May be null or omitted. Limits the results to everything that was modified after this DateStringlast_sync
filter is built with django.utils.dateparse.parse_datetime
and allows:&last_sync=2023-09-26T17:21:22.921692Z
&last_sync=2021-09-26T17:21:22Z
&last_sync=2021-09-26T17:21:22
&last_sync=2021-09-26T17:21
{\n \"count\": \"Long\",\n \"instances\": [\n {\n \"id\": \"Int\",\n \"uuid\": \"UUID - provided by the client\",\n \"form_id\": \"Int\",\n \"form_version_id\": \"Int\",\n \"created_at\": \"Timestamp in double\",\n \"updated_at\": \"Timestamp in double\",\n \"json\": \"JSONObject - contains the key/value of the instance\"\n }\n ],\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\"\n}\n
"},{"location":"pages/dev/reference/API/org_unit_registry.html#400-bad-request_5","title":"400 - Bad request","text":"app_id
was not providedpage
, limit
or version_count
cannot be parsed to a correct integer valuelast_sync
cannot be parsed to a correct date time.PaymentStatus
API","text":"Mock-ups
This API allows the status of payments linked to multiple OrgUnitChangeRequest
by the same user to be updated and queried.
The Django model that stores \"Payment Status\" is PaymentStatus
.
The PaymentStatus
model has a status
field which can have one of the following values:
PENDING
: This is the default status. It indicates that the payment is yet to be processed.SENT
: This status indicates that the payment has been processed and sent.REJECTED
: This status indicates that the payment was not successful and has been rejected.These statuses are stored as a list of tuples in the STATUS_CHOICES
field.
PaymentStatus
list","text":""},{"location":"pages/dev/reference/API/payments/payments.html#permissions","title":"Permissions","text":"iaso_payments
permissionpage
: Int (optional) - Specifies the current page number. If not provided, the default value is 1.order
: String (optional) - Specifies the order in which the results should be returned. If not provided, the default ordering value is id
.limit
: Int (optional) - Defines the number of entities to be returned per page. The default value is 20 if not specified.user_ids
: String (optional) - A comma-separated list of User IDs associated with the payments&user_ids=10,9
user_role_ids
: String (optional) - A comma-separated list of User Role IDs associated with the payments&user_role_ids=10,9
org_unit_id
: Int (optional) - The ID of the parent organization unit linked to the change requests. This should also include child units.from_date
: Date - 'YYYY-MM-DD' (optional) - The start date for when the change request has been validated. to_date
: Date - 'YYYY-MM-DD' (optional) - The end date for when the change request has been validated.{\n \"count\": \"Long\",\n \"has_next\": \"Boolean\",\n \"has_previous\": \"Boolean\",\n \"page\": \"Long\",\n \"pages\": \"Long\",\n \"limit\": \"Long\",\n \"results\": [\n \"id\": \"Int - unique id\",\n \"status\": \"String - PENDING or SENT or REJECTED\",\n \"change_requests\": [\n {\n \"id\": \"Int - change request unique id\",\n \"org_unit_id\": \"String - id or UUID of the OrgUnit to change\",\n }\n ],\n \"created_by\": {\n \"id\": \"Int - id of the User who created that payment\",\n \"username\": \"String - username of user\",\n \"first_name\": \"String - first name of user\",\n \"last_name\": \"String - last name of user\",\n },\n \"created_at\": \"Timestamp\",\n \"updated_by\": {\n \"id\": \"Int - id of the User who updated that payment\",\n \"username\": \"String - username of user\",\n \"first_name\": \"String - first name of user\",\n \"last_name\": \"String - last name of user\",\n },\n \"updated_at\": \"Timestamp\",\n \"user\": {\n \"id\": \"Int - user unique id\",\n \"username\": \"String - username of user\",\n \"first_name\": \"String - first name of user\",\n \"last_name\": \"String - last name of user\",\n \"user_role_id\": \"Int - user role id if one\",\n \"user_role_name\": \"String - user role name if one\",\n \"telephone\": \"String? - This type of field should be specified\",\n },\n ]\n}\n
"},{"location":"pages/dev/reference/API/payments/payments.html#400-bad-request","title":"400 - Bad request","text":"page
or limit
cannot be parsed to a correct integer valueuser_id
, user_role_id
, org_unit_id
not foundclass PaymentStatus(models.Model):\n \"\"\"\n Model to store the status of payments linked to multiple OrgUnitChangeRequest by the same user.\n \"\"\"\n\n class Statuses(models.TextChoices):\n PENDING = \"pending\", _(\"Pending\")\n SENT = \"sent\", _(\"Sent\")\n REJECTED = \"rejected\", _(\"Rejected\")\n\n status = models.CharField(choices=Statuses.choices, default=Statuses.PENDING, max_length=40)\n change_requests = models.ManyToManyField(OrgUnitChangeRequest, related_name=\"payment_statuses\")\n user = models.ForeignKey(User, on_delete=models.CASCADE, related_name=\"payment_statuses\")\n created_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name=\"payment_created_set\")\n updated_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name=\"payment_updated_set\")\n created_at = models.DateTimeField(auto_now_add=True)\n updated_at = models.DateTimeField(auto_now=True)\n
"},{"location":"pages/dev/reference/API/payments/payments.html#remarks","title":"Remarks","text":"Who creates the payments? Typically, a payment is created when a user initiates a transaction. This could be done automatically when a change request is made or manually by an admin user. The creation of a payment could be triggered in the backend code where the change request is processed.
Marking change requests as paid: We could add a payment_status field to the OrgUnitChangeRequest model. This field would reference the PaymentStatus of the associated payment. When a payment is processed, We can update this field accordingly.
Allow keeping a history of modification done on a Model instance.
It is not automatic, model that wish to implement this have to call log_modification manually when changed. Diff are stored in audit.Modification model.
"},{"location":"pages/dev/reference/background_tasks/background_tasks.html","title":"Background tasks & worker","text":"Iaso queue certains functions (task) for later execution, so they can run outside an HTTP request. This is used for functions that take a long time to execute so they don't canceled in the middle by a timeout of a connection closed. e.g: bulk import, modifications or export of OrgUnits. Theses are the functions marked by the decorator @task_decorator, when called they get added to a Queue and get executed by a worker.
The logic is based on a fork of the library django-beanstalk-worker from tolomea, please consult it's doc for reference.
If you want to develop a new background task, the endpoint /api/copy_version/
is a good example of how to create a task and to plug it to the api.
To call a function with the @task decorator, you need to pass it a User objects, in addition to the other function's arguments, this arg represent which user is launching the task. At execution time the task will receive a iaso.models.Task instance in argument that should be used to report progress. It's mandatory for the function, at the end of a successful execution to call task.report_success() to mark its proper completion.
"},{"location":"pages/dev/reference/campaigns/subactivities.html","title":"SubActivity and SubActivityScope Models and APIs","text":""},{"location":"pages/dev/reference/campaigns/subactivities.html#models","title":"Models","text":""},{"location":"pages/dev/reference/campaigns/subactivities.html#subactivity","title":"SubActivity","text":"The SubActivity
model represents a sub-activity within a round of a campaign. It has the following fields:
round
: A foreign key to the Round
model, representing the round to which the sub-activity belongs.name
: A string field representing the name of the sub-activity.age_unit
: A choice field representing the unit of age targeted by the sub-activity. The choices are \"Months\" and \"Years\".age_min
: An integer field representing the minimum age targeted by the sub-activity.age_max
: An integer field representing the maximum age targeted by the sub-activity.start_date
: A date field representing the start date of the sub-activity.end_date
: A date field representing the end date of the sub-activity.The SubActivityScope
model represents the scope of a sub-activity, including the selection of an organizational unit and the vaccines used. It has the following fields:
group
: A one-to-one field to the Group
model, representing the group of organizational units for the sub-activity.subactivity
: A foreign key to the SubActivity
model, representing the sub-activity to which the scope belongs.vaccine
: A choice field representing the vaccine used in the sub-activity. The choices are \"mOPV2\", \"nOPV2\", and \"bOPV\".The SubActivity
API allows for the creation, retrieval, update, and deletion of sub-activities. The API endpoint is /api/polio/campaigns_subactivities/
.
To create a new sub-activity, send a POST request to the endpoint with the following data:
{\n \"round_number\": <round_number>,\n \"campaign\": <campaign_obr_name>,\n \"name\": <subactivity_name>,\n \"start_date\": <start_date>,\n \"end_date\": <end_date>,\n \"scopes\": [\n {\n \"group\": {\n \"name\": <group_name>,\n \"org_units\": [<org_unit_id>]\n },\n \"vaccine\": <vaccine_choice>\n }\n ]\n}\n
"},{"location":"pages/dev/reference/campaigns/subactivities.html#retrieve","title":"Retrieve","text":"To retrieve all sub-activities, send a GET request to the endpoint. To retrieve a specific sub-activity, send a GET request to /api/polio/campaigns_subactivities/<subactivity_id>/
.
To update a sub-activity, send a PUT request to /api/polio/campaigns_subactivities/<subactivity_id>/
with the new data.
To delete a sub-activity, send a DELETE request to /api/polio/campaigns_subactivities/<subactivity_id>/
.
Only authenticated users can interact with the SubActivity
API. The user must belong to the same account as the campaign associated with the round of the sub-activity.
Some terminology in Iaso come from DHIS2, some from ODK which mean that it can be a bit confusing. We will highlight some equivalences that might help you.
This is not (yet) the complete Data Model, but here are the main concepts/model in Iaso:
Account
. It represents roughly one client org or country. It also represents the natural limit of right for a user.Profile
that link it to an Account
and store extra parameters for the user.Project
. Projects are linked to one android version App via the app_id
. We use the link to control what a user can see from that app.DHIS2
is a standard server application and web UI in the industry to handle Health Data. Iaso can import and export data (forms and org unit) to it.OrgUnit
(Organizational Unit) is a Node of the GeoRegistry tree. e.g a particular Country, City or Hospital. each belonging to each other via a parent
relationship.OrgUnitType
e.g. Country, City, HospitalGroup
, e.g. Urban Region or Campaign 2017Group
but not Type
so when importing from a DHIS2 Instance all the type will be Unknown and OrgUnit will belong to group like Clinic
GroupSet
are Group of group. Used when we export Group to DHIS2geom
field is then used, or just a Point, the location
field is then used.DataSource
links OrgUnit and Group imported from the same source, e.g a DHIS2 instance, a CSV or a GeoPackage.source_ref
on the imported instance is used to keep the reference from the original source, so we can match it again in the future (when updating the import or exporting it back)SourceVersion
is used to keep each version separated. e.g each time we import from DHIS2 we create a new version.Task
are asynchronous function that will be run by a background worker in production. eg: Importing Data from DHIS2. see Worker section below for more info.Form
is the definition of a Form (list of question and their presentation).XSLForm
as an attached file.Instance
or Form instance is the Submission
of a form. A form that has actually been filed by a user.APIImport
are used to log some request from the mobile app so we can replay them in case of error. See vector_control wikiaudit.Modification
are used to keep a history of modification on some models (mainly orgunit). See audit wikiLink
are used to match two OrgUnit (in different sources or not) that should be the same in the real world. Links have a confidence score indicating how much we trust that the two OrgUnit are actually the same.They are usually generated via AlgorithmRun
, or the matching is done in a Notebook and uploaded via the API.
Iaso's online documentation is built using mkDocs and deployed on Github pages. A CNAME record allows it to be available on docs.openiaso.com
"},{"location":"pages/dev/reference/doc_setup/doc_setup.html#mkdocs-setup","title":"mkDocs setup","text":"The mkDocs build is configured in mkdocs.yml, at the root of the project The docs (markdown files) themselves as well as all other files related to mkdocs are located in the /docs
folder
The dependencies are in /docs/requirements.txt
. The file has been generated using pip-compile
and /docs/requirements.in
, except for the plugin mkdocs-static-i18n
which had its version upgarded manually.
The markdown files follow the format <name>.<locale>.md
to enable the localization plugin to display the right files
The menu is defined in mkdocs.yml: - default menu is under nav`` - french menu is under
plugins>i18n>languages>fr> nav`
We had to duplicate the whole menu because we have a nested menu. On the flip side this can allow us to have an entirely different menu structure for each language if we wish to do so.
"},{"location":"pages/dev/reference/doc_setup/doc_setup.html#github-pages-setup","title":"Github pages setup","text":"There is a deploy_doc
Github action that will build and deploy the documentation when pushing on main
. The action itself will deploy the branch gh-pages
. This in turn will trigger Github's own action to deploy the Github page.
IMPORTANT: - There is a CNAME
file in the /docs
folder. Removing or altering it will break the redirection of docs.openisao.com
to the Github page - The gh-pages
branch should be left alone. It only contains the built documentation and none of the Iaso code
Each docker container uses the entrypoint.
The entrypoint.sh
script offers a range of commands to start services or run commands. The full list of commands can be seen in the script. The pattern to run a command is
docker compose run <container-name> <entrypoint-command> <...args>\n
The following are some examples:
docker compose exec iaso ./manage.py test
docker compose run iaso bash
docker compose run iaso eval curl http://google.com
docker compose exec iaso ./manage.py help
docker compose exec iaso ./manage.py shell
docker compose exec iaso ./manage.py dbshell
docker compose exec iaso ./manage.py makemigrations
docker compose exec iaso ./manage.py migrate
docker compose exec iaso ./manage.py showmigrations
docker compose run iaso manage tasks_worker
All the container definitions for development can be found in docker-compose.yml
.
docker compose run
launches a new docker container, docker compose exec
launches a command in the existing container.
So run
will ensure the dependencies like the database are up before executing. exec
main advantage is that it is faster but the containers must already be running (launched manually)
run
will launch the entrypoint.sh script but exec will take a bash command to run which is why if you want to run the django manage.py you will need to use run iaso manage
but exec iaso ./manage.py
Also take care that run
unless evoked with the --rm
will leave you with a lot of left over containers that take up disk space and need to be cleaned occasionally with docker compose rm
to reclaim disk space.
Table of contents 2
What are entities? 3
How to create an entity? 3
Enable the feature 3
Create and upload the profile form 3
Create the entity type 4
Create an entity 4
How to configure how we display an entity? 6
In the web interface 6
In the list 6
In the details screen 7
In the mobile application 7
In the list 7
In the details screen 8
Searching for an entity 8
On the web 8
In the application 9
What are workflows? 9
Create a workflow 9
Follow-ups and changes 10
Follow-ups 10
Changes 10
Using values from the profile in subsequent forms 11
Publishing workflows 11
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#what-are-entities","title":"What are entities?","text":"We call an \u201cEntity\u201d anything that can move or be moved and that we want to track through time and Org Units. For example, a beneficiary, a car, a vaccination card, etc.
To differentiate between different kinds of entities, Iaso has a concept of \u201cEntity Type\u201d.
Iaso heavily relies on XLSForms, and entities are no exceptions. Therefore, an entity is represented by a submission to a form. This submission is referred to as the profile. The entity type defines which form has to be filled in.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#how-to-create-an-entity","title":"How to create an entity?","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#enable-the-feature","title":"Enable the feature","text":"In order to create an entity, your project must first enable the entity feature flag. You can set this flag either during its creation or by updating it later.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#create-and-upload-the-profile-form","title":"Create and upload the profile form","text":"Using the sheet application of your choosing, create an XLSForm which will contain all the questions related to your entity that are either fixed (I.e., first name and last name) or can evolve through time (I.e., a program to which an entity can be affiliated to).
Upload it on the server using the web application.
Note: The questions that can evolve through time should not be editable."},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#create-the-entity-type","title":"Create the entity type","text":"In the entity types screen, click on the \u201cCREATE\u201d button. Give the entity type a name and select the newly uploaded form as a reference form:
Note: We\u2019ll see later what \u201cList fields\u201d and \u201cDetail info fields\u201d are."},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#create-an-entity","title":"Create an entity","text":"In the mobile application, make sure that the data has been refreshed and are up to date with the backend server. You will now be able to see the entity screen.
At the moment, it is not possible to create an Entity from a web interface.
Click the \u201cAdd\u201d button in the application.
Select the entity type you want to create.
You will be prompted to confirm your selection.
You can then fill out the form to finalize your first entity.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#how-to-configure-how-we-display-an-entity","title":"How to configure how we display an entity?","text":"Within the entity type\u2019s configuration, it is possible for administrators to define which questions are displayed within lists and within the details screen.
This impacts how the web and mobile applications display entities, as shown below.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-web-interface","title":"In the web interface","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-list","title":"In the list","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-details-screen","title":"In the details screen","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-mobile-application","title":"In the mobile application","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-list_1","title":"In the list","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-details-screen_1","title":"In the details screen","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#searching-for-an-entity","title":"Searching for an entity","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#on-the-web","title":"On the web","text":"In the beneficiary list, you can filter by type and/or enter a query to filter based on the identifier or any of the list fields values.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#in-the-application","title":"In the application","text":"Clicking on the magnifying glass icon on the entity screen will lead you to the list of all entities and allow you to filter them quickly based on the identifier or any of the list fields values.
If you need a more fine-grained selection, you can click on the funnel icon, select a type and fill out the search form (second picture)
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#what-are-workflows","title":"What are workflows?","text":"As stated before, an entity is tracked through time and Org Units. In order to achieve this, Iaso links the subsequent submissions for an entity together and allows subsequent submissions to change the profile. In order for you to choose which forms should be presented next and what values override the properties of the profile, you can define a workflow.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#high-level-schema-of-the-workflows-models","title":"High Level Schema of the workflow's models","text":"classDiagram\nclass WorkflowVersion {\n Workflow workflow\n String name\n Form reference_form\n Enum status\n}\nclass Workflow {\n EntityType entity_type\n}\nclass EntityType {\n String name\n Form reference_form\n Account account\n Bool is_active\n}\n\nclass Form {\n String form_id\n String namer\n String device_field\n String location_field\n String correlation_field\n Bool correlatable\n JSON possible_fields\n String period_ty pe\n Bool single_per_period\n Int periods_before_allowed\n Int periods_after_allowed\n Bool derived\n UUID uuid\n}\n\nclass FormVersion {\n Form form\n File file\n File xls_file\n JSON form_descriptor\n String version_id\n}\n\nWorkflowVersion --> Form : Foreign Key (reference_form)\nEntityType --> Form : Foreign Key (reference_form)\nEntityType -- Workflow : One To One (entity_type)\nWorkflowVersion --> Workflow : Foreign Key (form) \nFormVersion --> Form : Foreign Key (form)\n
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#create-a-workflow","title":"Create a workflow","text":"In the entity types\u2019 list, click on the workflow icon
In the list of the workflow versions, create the \u201cCREATE\u201d button and give the version a name:
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#follow-ups-and-changes","title":"Follow-ups and changes","text":""},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#follow-ups","title":"Follow-ups","text":"They represent the next possible forms based on the state of the profile. They are based on a condition. In the following example, the mobile application will offer \u201cU5 Registration WFP\u201d as the next possible form if the first name is \u201cBill\u201d.
Reminder: \u201cFirst Name\u201d is one of the questions in the Entity Type\u2019s form."},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#changes","title":"Changes","text":"They represent the mapping of what value from a form will change the values in the profile.
In the example below, the \u201cTarget form\u201d is the Entity Type\u2019s form, and the \u201cSource form\u201d is the subsequent submission.
When a \u201cU5 Registration WFP\u201d form is filled out, the value entered in \u201cChild\u2019s Age in months\u201d will be copied into the profile\u2019s \u201cAge (Months)\u201d question. And the value entered in \u201cChild\u2019s Name\u201d will be copied into the profile\u2019s \u201cFirst Name\u201d question.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#using-values-from-the-profile-in-subsequent-forms","title":"Using values from the profile in subsequent forms","text":"Sometimes, you want a subsequent form to use values from the profile. In order to do so, just add a question with the same identifier and type as the value from the profile.
I.e., Let\u2019s assume the profile has 2 questions of type \u201ctext\u201d: first_name and last_name. By adding a read-only similar question in your subsequent forms, the value will be available to you.
"},{"location":"pages/dev/reference/entities_in_iaso/entities_in_iaso.html#publishing-workflows","title":"Publishing workflows","text":"Once a workflow version has been published, it is marked as finalized, and it cannot be edited anymore. Only workflows in \u201cdraft\u201d can be edited.
If you want to edit a finalized workflow, you first need to duplicate it using the \u201cCopy version\u201d button. A new draft version is then created with the same content.
"},{"location":"pages/dev/reference/env_variables/env_variables.html","title":"Environnement variables","text":""},{"location":"pages/dev/reference/env_variables/env_variables.html#db-connection-related","title":"DB connection related","text":"the url is build based on the following env variables
RDS_USERNAME\nRDS_PASSWORD\nRDS_HOSTNAME\nRDS_DB_NAME\nRDS_PORT\n
the SQL dashboard use a dedicated user/password with readonly access to the data
DB_READONLY_USERNAME \nDB_READONLY_PASSWORD\n
"},{"location":"pages/dev/reference/env_variables/env_variables.html#aws-related","title":"AWS related","text":"Storing the various files like
AWS_ACCESS_KEY_ID:\nAWS_SECRET_ACCESS_KEY:\nAWS_S3_REGION_NAME\nAWS_STORAGE_BUCKET_NAME:\nAWS_S3_ENDPOINT_URL: (used to for ex to point to minio)\n
for async task
BACKGROUND_TASK_SERVICE : default to SQS : possible values are SQS POSTGRES\nBEANSTALK_SQS_REGION\nBEANSTALK_SQS_URL\n\n
"},{"location":"pages/dev/reference/env_variables/env_variables.html#security-settings","title":"Security Settings","text":""},{"location":"pages/dev/reference/env_variables/env_variables.html#django-settings","title":"Django settings","text":"Iaso allows to set some of Django security settings as environment variable. To activate these features set the environment variable to \"true\"
. Default is \"false\"
CSRF_COOKIE_HTTPONLY \nCSRF_COOKIE_SECURE \nSESSION_COOKIE_SECURE\n
"},{"location":"pages/dev/reference/env_variables/env_variables.html#cors","title":"CORS","text":"It is possible to setup a IASO server with CORS authorizing access from any server with the following environment variable \"ENABLE_CORS\"
. Default is \"true\"
Set the environment variable DISABLE_PASSWORD_LOGINS
to the value\"true\"
in case you wish to deactivate passwords using login:
If you don't provide a SENTRY_URL, sentry won't be configured
name optional default value description --- SENTRY_URL true - url specific to your sentry account SENTRY_ENVIRONMENT true development environnement (dev, staging, prod,...) SENTRY_TRACES_SAMPLE_RATE true 0.1 float between 0 and 1 : send 10% SENTRY_ERRORS_SAMPLE_RATE true 1.0 float between 0 and 1 : send everything SENTRY_ERRORS_HTTPERROR_SAMPLE_RATE true 0.8 float between 0 and 1 : send 80% of the errors"},{"location":"pages/dev/reference/env_variables/env_variables.html#maintenance-mode","title":"Maintenance mode","text":"MAINTENANCE_MODE
(default is \"false\"
)
If you need to set up IASO in maintenance mode, meaning that it will display at / a page indicating that the server is under maintenance, and give a 404 answer to all requests except for /health or /_health (wich we encourage to use for status monitoring), you can set the environment variable MAINTENANCE_MODE
to the value \"true\"
Frontend assets include JS, CSS, translations and images. They are all handled by webpack. Most of the structure is taken from this blog post: http://owaislone.org/blog/webpack-plus-reactjs-and-django/
Frontend assets are mounted on the pages via the django-webpack-loader <https://github.com/owais/django-webpack-loader>
__
There are two webpack configuration files: webpack.dev.js
and webpack.prod.js
.
A JS production build is created inside the docker-file, via npm run build
the start_dev
entry point starts a webpack development server, that watches assets, rebuilds and does hot reloading of JS Components.
The CSS build is separate, and can contain both .sass
and .css
files. They spit out a webpack build called styles.css
.
Each page has their own JS entry point (needs to be defined in both webpack files). On top of that, they load a common chunk, containing react
, react-intl
and other stuff that the webpack common chunk
plugin finds is shared between the apps.
Including a JS bundle via django-webpack-loader ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If we have created a new JS app MyCustomApp
in hat/assets/js/apps/
.
The entrypoint should be /dashboard/my-custom-app
Folders and files affected::
+ hat/\n + assets/\n + js/\n + apps/\n + MyCustomApp\n . index.js\n . MyCustomApp.js\n . MyCustomAppContainer.js\n\n + dashboard/\n . urls.py\n . views.py\n\n + templates/\n + dashboard/\n . my_custom_app.html\n\n . webpack.dev.js\n . webpack.prod.js\n
These are the steps to visualize it within the dashboard:
hat/webpack.dev.js
include a new entry
. 'my_custom_app': [\n 'webpack-dev-server/client?' + WEBPACK_URL,\n 'webpack/hot/only-dev-server',\n './assets/js/apps/MyCustomApp/index'\n ],\n
hat/webpack.prod.js
include a new entry
. 'my_custom_app': './assets/js/apps/MyCustomApp/index',\n
hat/dashboard/views.py
include a new view. @login_required() # needs login?\n @permission_required('cases.view') # the needed permissions\n @require_http_methods(['GET']) # http methods allowed\n def my_custom_app(request: HttpRequest) -> HttpResponse:\n return render(request, 'dashboard/my_custom_app.html')\n
hat/dashboard/urls.py
include a new url pattern. url(r'^my-custom-app/.*$', views.my_custom_app, name='my_custom_app'),\n
url(r'^my-custom-app/.*$', views.my_custom_app, name='my_custom_app'),\n
hat/templates/dashboard
create a new template file my_custom_app.html
. {% extends 'app.html' %}\n {% load i18n %}\n {% load render_bundle from webpack_loader %}\n\n {% block header %}\n <h1 class=\"header__title\">{% trans 'My Custom App' %}</h1>\n {% endblock %}\n\n {% block content %}\n\n <div class=\"content\">\n <div id=\"app-container\"></div>\n </div>\n\n {% render_bundle 'common' %}\n {% render_bundle 'my_custom_app' %}\n <script>\n HAT.MyCustomApp.default(\n document.getElementById('app-container'),\n '/dashboard/my-custom-app/'\n )\n </script>\n {% endblock %}\n
"},{"location":"pages/dev/reference/front-end_reference/front-end_reference.html#testing-the-production-build","title":"Testing the production build","text":""},{"location":"pages/dev/reference/front-end_reference/front-end_reference.html#stop-any-containers-that-might-be-currently-running","title":". Stop any containers that might be currently running.","text":""},{"location":"pages/dev/reference/front-end_reference/front-end_reference.html#start-the-containers-with","title":". Start the containers with:","text":".. code:: shell
TEST_PROD=true docker compose up\n
When the setup is run with TEST_PROD=true
, it will exit the unneeded containers webpack
and jupyter
. It will also run the webpack build during startup, so that there is no need to rebuild the image for that.
docker compose run hat test_js\n
"},{"location":"pages/dev/reference/front-end_reference/front-end_reference.html#adding-new-assets-in-packagejson","title":"Adding new assets in package.json","text":"Unfortunately, for now you need to rebuild the container after adding or upgrading packages in package.json
.
.. code:: shell
docker compose build\n
or
.. code:: shell
docker compose up --build\n
"},{"location":"pages/dev/reference/front-end_reference/front-end_reference.html#translations","title":"Translations","text":"Translations are extracted on the first webpack build. Just like the django translation strings; translations are downloaded for every Travis CI <https://travis-ci.com>
__ build, and uploaded on the development
branch.
See also: this pull request <https://github.com/BLSQ/iaso/pull/120>
__
The script show-lint-problems
can be turned into a VSCode task that will show all linter errors in VSCode's PROBLEMS Tab.
Steps to follow:
Go to Terminal>Configure task
Select npm: show-lint-problems
Add $eslint-stylish
to the problemMatcher
array
Run the task: Terminal>Run Task...> npm: show-lint-problems. IMPORTANT: you need to run the task this way. Running the script directly from the terminal using npm will not enable VS Code to display the problems in the PROBLEMS tab
You should be able to see and track the problems through the dedicated tab. CAUTION: if you navigate to a file through the tab, then close the file, it will be removed from the problems list, even if it wasn't changed. This seems to be a problem with using npm through VSCode's tasks
See the library's README <https://github.com/BLSQ/bluesquare-components/blob/main/README.md>
__ for the general setup.
When depending on a local version of the library:
Your local folder should be on the same level as the iaso folder, so that the path to the tgz file in your package.json is : ../bluesquare-components/bluesquare-components-0.1.0.tgz
Run docker compose build --build-arg LIBRARY=<name-of-the-library-image>
Please do a rough design before any implementation and discuss it with the rest of the team. It doesn't need to be too detailed but you should have at least a list of Model, the important fields on it, the security model and what endpoint you will add.
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#security","title":"Security","text":"When Adding New feature that will add Model and API please think about the security model. You can start by asking yourself these questions:
Iaso is multi tenant. Tenant are called and represented by the model Account
. It represents roughly one client org or country. It also represents the natural limit of right for a user.
So all new model and API per default should support tenancy. It's kind of annoying to add it later.
We have two kind of tenancy, one per Project, one per Account. A Project represent a mobile app, there might be several linked to an account. You will have to consider which want you one to use. In some case it might be both.
In some case if your new model is linked to existing one it might derive the tenancy from there (for example FormVersion derive their tenancy from Form).
In practice this will consist on: 1. adding a ForeignKey to Account or Project on your model. 2. At creation 2. in your ViewSet filtering on the Object in the account (see filtering for user)
In most case if this an API for the Mobile the tenancy will be per project. But if that's not the case and you are really not sure, there is nobody to ask and you need to advance, add it on the Account it will be simpler.
Don't hesitate to ask if you don't understand what tenancy means or how it works.
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#in-the-broad-lines-adding-a-complete-new-feature-will-consist-of","title":"In the broad lines adding a complete new feature will consist of","text":"/api/yourmodel
Always add the field created_at
, updated_at
. This allow us the minimum of traceability and is useful in debug.
created_at = models.DateTimeField(auto_now_add=True)\n updated_at = models.DateTimeField(auto_now=True)\n
You might need Soft delete (see Soft Delete section)
Don't forget the tenancy.
Examples: There is a very simple Model/API example in the directory plugins/tests
that you can use as a template.
Please use serializers !
This will allow the API to be autodocumented in the swagger and the browsable API interface. You can check the swagger at /swagger/
For the ViewSet, always^ inherit ModelViewSet from iaso.api.common, not the one from DRF ! This will handle the Pagination that is particular to Django correctly out of the box.
^ Except in some case obviously but it should be the default
For the default case you ModelViewSet should be very simple, and you should not have to reimplement def list()
and def create()
etc... if you properly did the Serializer and inherited from iaso.api.common.ModelViewset
Per default you will want to filter what is viewable by the correctly connected user (at minimum for the tenancy, see relevant section)
To do so add a get_queryset()
method on your ViewSet which will filter the queryset on the user. It's not a bad idea to move the logic of that code directly on your Model queryset by adding a Queryset.for_user(user: User)
on your model so we can reuse it on other models. See for example TeamQuerySet.
We use the DRF system with permission_class
see DRF Doc. See also plugins/test/api.py
for an example.
By default all the API require to be logged but any user can post GET / POST / PATCH / DELETE so beware of that.
Further restriction can be added using http_method_names = [\"put\"]
if you want to be extra sure, but that shouldn't be the only check method.
Do not hesitate to put test to check that the method are effectively restricted to both authorized and unauthenticated user so they are not re-enabled by default.
If you want to check if an user has a permission there is a HasPermission class in iaso.api.common.py
TODO : expand section
Set the filter_backends
config key in your ViewSet, with per default at least, the backends filters.OrderingFilter
and DjangoFilterBackend
filter_backends = [\n filters.OrderingFilter,\n DjangoFilterBackend,\n\n ]\n
This will allow ordering and filtering on the key present on the model automatically.
The front dev will push you to include special filter with Javascript case name like FormId
or stuff which will require special case in list() because that's what they are used to but in 99% of the case this is not needed and there is already a filter in python case form_id
. The normal django operator can also be used __in
for list, __gte
for >=
, iexact
to ignore case, etc..
See the field lookup for the complete reference https://docs.djangoproject.com/en/4.1/ref/models/querysets/#field-lookups
All these filters are conveniently listed in the swagger for each endpoint and in the browsable API
If your model use SoftDelete include the DeletionFilterBackend
filter. See the SoftDelete section
DeletionFilterBackend,\n
If you need more control on how fields can be used for ordering and filtering you can respectively use ordering_fields
and filterset_fields
. See the django-filter and drf doc.
Frontend expect \"big\" api to accept a search
filter which usually does a icontains
on any of the model StringField (e.g. name or description) or related model (e.g the org unit name).
The filters.OrderingFilter
use the ?order_by
query parameters, multiple field can be specified, separated by ,
, -
in front of the field can be used to specify to reverse the order
In very special case if you need strange manipulation for ordering or filtering, adding an annotation on the queryset might help a lot. Since it does all the calculation on the database side, this allows use to add filter on fields that would otherwise not be possible without retrieving a lot of data on the backend.
You might need to add them in ordering_fields
and filterset_fields
. (TODO Olivier : check)
If this API is accessible via mobile, add a separate endpoint for the mobile to use, in /api/mobile
, even if it is the same as the \"regular\" web
endpoint and that you connect it to the same ViewSet. This allows us flexibility if we have to break compatibility in the future.
For date in new API endpoint we use the RFC format (2022-03-02 23:34...) that is the default in DRF so there is nothing to be done. Old endpoint might still use the old format in Timestamp.
In the endpoint for mobile we always use Timestamp. You can use TimestampField and DateTimestampField in your Serializer for this.
Take special care when modifying the API used by the mobile, we don't have a complete list, but we still have old version of the APP using very old endpoints that newer version don't use anymore.
For API endpoint that the mobile APP will POST to, you should check with Martin if you may need the decorator @safe_api_import
to ensure no data is lost. See /iaso/hat/vector_control/README.md
For data that will be presented as a table, we will want to provide CSV and XLS export to the user in nearly all cases.
We don't use the CSV export provided by DRF for that and use a bit of code that we copy past and should really be refactored.
TODO: Expand section
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#softdelete","title":"SoftDelete","text":"Soft Delete is a way to mark an Object as deleted without actually deleting it from the database. That way we can still show them to user in the interface it they choose to and the user can restore it easily.
We have a standard way to implement it: - Have your model inherit from the SoftDelete. - This will add the deleted_at field. When the field is null it's not deleted if it contains a data it is deleted.
In the ViewSet add the filter DeletionFilterBackend.
Test that you can restore by doing a patch on your row on the deleted_at
fields
if you add a new permission, don't forget to add it in the Frontend, or it will not be displayed properly. See the instruction on the top of menupermissions/models.py
If you add New model, add them in the Admin if you don't know which fields or filter to add just add at least the minimum, we can expand it latter.
Minimum Admin for a Model (BlogPost in this Example), in admin.py
class BlogPostAdmin(admin.ModelAdmin):\n pass\n\nadmin.site.register(BlogPost, BlogPostAdmin)\n
If you want a base to be a mot more fancy:
search_fields = (\"title\", \"content\")\n list_display = (\"title\", \"author\", \"created_at\", \"updated_at\")\n date_hierarchy = \"created_at\"\n list_filter = (\"author\", \"updated_at\")\n readonly_fields = (\"created_at\", \"updated_at\")\n\n
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#configuration-settings","title":"Configuration settings","text":"We configure the Iaso deployement (the server) via environment flags. So if you add something configurable on the whole server level do it that way. See https://12factor.net/config if your are not familiar with the philosophy.
Note :There are of course some exceptions and thus some settings are configured in the Database. Notably for the Polio plugins. But please keep that exceptional.
You can change your own local configuration in the file .env
. Do note that if you make any modification in your .env
or in your docker-compose.yml
file. You will need to restart the whole docker compose for it to take effect (Ctrl-c your current docker compose and bring it back up with docker compose up
If you add a new Environement variable to allow some configuration: 1. Do not access the Enviroement variable directly from your python code ! 1. Instead add it as a variable in the settings.py
. 1. Add a comment explaing what this variable does. 1. Always provide a default value 1. To allow developer to change the variable locally add it in docker-compose.yml
Also KISS, the less configuration the better !
There is no comprehensive documentation of all the configuration settings except what is in settings.py so it's important that you comment it well !
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#example","title":"Example","text":"settings.py
# Application customizations\nAPP_TITLE = os.environ.get(\"APP_TITLE\", \"Iaso\")\n
diff --git a/docker-compose.yml b/docker-compose.yml\nindex 057b81e39..49b29ac30 100644\n--- a/docker-compose.yml\n+++ b/docker-compose.yml\n@@ -44,6 +44,7 @@ services:\n THEME_PRIMARY_BACKGROUND_COLOR:\n FAVICON_PATH:\n LOGO_PATH:\n+ APP_TITLE:\n SHOW_NAME_WITH_LOGO:\n RDS_USERNAME: postgres\n RDS_PASSWORD: postgres\n
from django.conf import settings\nsettings.APP_TITLE\n
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#feature-flags","title":"Feature Flags","text":"Feature flags allow to enable special feature or behaviour for our clients. The use case are: certain workflow that are particular to a client use case or feature that are still in development and that we are co-developing with the client.
Example of feature flag: Enable editing an org unit geography directly via the web map or requiring user to log in into the mobile app to submit.
We have two kind of flags in iaso: Mobile and Web
FeatureFlag
that are linked to Project
(there might have multiple Mobile application per account)AccountFeatureFlag
for the whole account. It is mainly used to control the behaviour of the web frontend.The list of flags are stored in database tables, to add a new Flag migration are used.
Usually client can control which Project/Mobile flag they have but not the one on the Account level.
To toggle a Mobile Feature flag for a client, add it in the dashboard -> Admin -> Projects -> Edit a project -> Options
.
To toggle an Account Feature flag, it is in the django Admin
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#adding-a-new-feature-flag-via-a-migration","title":"Adding a new Feature Flag via a Migration","text":"For example a Project Feature Flag
python manage.py makemigrations --empty yourappname
def create_feature_flags(apps, schema_editor):\n FeatureFlag = apps.get_model(\"iaso\", \"FeatureFlag\")\n FeatureFlag.objects.create(\n code=\"CHECK_POSITION_FOR_FORMS\",\n name=\"Mobile: Enforce users are within reach of the Org Unit before starting a form.\",\n )\n\n\ndef destroy_feature_flags(apps, schema_editor):\n FeatureFlag = apps.get_model(\"iaso\", \"FeatureFlag\")\n FeatureFlag.objects.filter(code=\"CHECK_POSITION_FOR_FORMS\").delete()\n\n\nclass Migration(migrations.Migration):\n dependencies = [\n (\"iaso\", \"0150_profile_home_page\"),\n ]\n\n operations = [\n migrations.RunPython(create_feature_flags, destroy_feature_flags),\n ]\n
See https://docs.djangoproject.com/en/4.0/topics/migrations/#data-migrations
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#typing-and-annotations","title":"Typing and annotations","text":"If you use an annotate and mypy complains when using the field. You can add the field on the model using Annotated. for example:
from typing_extensions import Annotated, TypedDict\n\nclass LastBudgetAnnotation(TypedDict):\n budget_last_updated_at: datetime\n\n\nclass MonSerializeur()\n def get_budget_last_updated_at(self, campaign: Annotated[Campaign, LastBudgetAnnotation]):\n if campaign.budget_last_updated_at:\n return campaign.budget_last_updated_at.strftime(\"%Y-%m-%d\")\n\n\n
Do not use the WithAnnotations
from django-stubs. it doesn't work with our setup. I think it's a problem of python 3.8
settings.py
","text":"The new environment variable need to be listed in the docker-compose.yml
"},{"location":"pages/dev/reference/guidelines/back-end/back-end.html#logging-is-broken","title":"Logging is broken","text":"Symptom : The request are not displayed in the the sever console, or in productoin /var/app/log
Someone probably imported a function from the tests
directory, which disable logging. Please don't do that. Move the imported code elsewhere. See https://github.com/BLSQ/iaso/commit/b22b1bcc31a5b05650b675a3c168285103f9bcf8
journalctl -u web
First of all make sure your eslint, prettier, typescript environnement is set properly.
On VSC code you can format code following IAOS rules while saving in the settings:
\"[javascript]\": {\n \"editor.formatOnSave\": true,\n},\n\"[typescript]\": {\n \"editor.formatOnSave\": true,\n},\n
Make sure you installed eslint extension too. On each file you can have only one component. Use constants, and config files to store static data. Don't be afraid to split your code into smaller parts, using understandable naming convention. It will help to understand what you are doing in your code.
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#legacy","title":"Legacy","text":"Class component, proptypes are still old way to create features in IASO. Please use hooks
, typescript
and arrow component
. We already have a lot of typing done in each domain of the application (forms, submissions, org units, ... )
Lots of components used in IASO has been moved to a separate repo. We tried to be the most generic has possible in this repo, people from outside IASO should be able to use it in their own project. To use it locally, checkout the repo on the same level has ISO and run: LIVE_COMPONENTS=true pm run dev
in IASO folder. This will use directly the code from your local repo. To make it available too everybody you have to build new files with npm run clean && npm run build
in bluesquare-component folder.
Main index file is located here: hat/assets/js/apps/Iaso/index
This is the entrypoint of the app, setting up providers, theme, react-query query client, custom plugins,...
components
Used to store generic components that can be used everywhere, like inputComponent
, buttons
, ...
domains
For every big feature entity in IASO (forms, org units, plannings, ...) we have a domain folder allowing to display related pages. - index
is generally used to display a list of items - details
the details of an item - config
used to store constants for the domain (columns, default order, ...) - hooks
: dedicated hooks to make requests or compute specitic data for the domain - components
: mostly intermediate components using smaller ones to construct domain page - messages
: translations messages used for this specific domain config: used to store constants like defaultOrder, columns, 'baseUrls' - types
: All types related to the domain
In our effort to maintain readability and conciseness in our code, we are transitioning away from the makeStyles
hook and adopting a new approach for styling our components. We will now use a separate object, styles
, to define our styles outside of the component function. This object will then be referenced within the sx
prop in our JSX.
Here's how to apply this approach:
styles
object using the SxStyles
type from hat/assets/js/apps/Iaso/types/general.ts
. This helps with readability and keeps the component code clean.sx
prop by referencing the styles
object properties.Example:
const styles: SxStyles = {\n root: {\n cursor: 'pointer',\n },\n tooltip: {\n color: 'text.primary',\n bgcolor: 'background.paper',\n boxShadow: (theme: Theme) => theme.shadows[1],\n '& .MuiTooltip-arrow': {\n color: 'background.paper',\n },\n },\n noResult: {\n textDecoration: 'underline dotted',\n },\n};\n\n...\n <Box sx={styles.root}>\n ...\n
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#maps","title":"Maps","text":"We are using leaflet latest version and react-leaflet (LTS version 3). To use latest version of react-leaflet we need to upgrade to react 18.
Styles are located in bluesquare-components
, you have to import it on each map:
const styles = (theme) => ({\n mapContainer: {\n ...commonStyles(theme).mapContainer,\n },\n});\n
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#from-react-leaflet","title":"From react-leaflet","text":"MapContainer
The main container of the Map. Props we use: - bounds
: not required, bounds of markers and shapes displayed, used by fit to bound, doc here. - boundsOptions
: not required, options related to bounds, doc here. - zoomControl
: required and set to false
, in order to use the CustomZoomControl
. - whenCreated
: not required,to use a ref of the map in the same component as the MapContainer, you get it by doing whenCreated={mapInstance => { map.current = mapInstance; }}
default props:
doubleClickZoom={false}\nscrollWheelZoom={false}\nmaxZoom={currentTile.maxZoom}\nstyle={{ height: '100%' }}\ncenter={[0, 0]}\nzoomControl={false}\nkeyboard={false}\nbounds={bounds}\nboundsOptions={boundsOptions}\n
Scalecontrol
Used to display a scale on the bottom left of the map.
Props we use: - imperial
: always set to false
We use other component from react-leaflet not listed here as they are optionnal and used like describe in their docs.
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#custom-components","title":"Custom components","text":"CustomZoomControl
This will display an extended zoom control on the top left of the map. You can zoom in and out, select an area to zoom in and fit the map to the bounds of the map.
Props: - bounds
: not required, computed bounds displayed on the map, doc here. - boundsOptions
: not required, options related to bounds, doc here. - bound
: not required, a boolean to fit to bounds on load, not working if bounds stays undefined.
CustomTileLayer + TilesSwitchDialog
Control to display a dialog allowing to change the tile layer of the map, on the top right of the map Those twot components are going together, maybe we should refactor it to a single component.
CustomTileLayer Props: - currentTile
: required, active tile of the map, usually setted in the map itself with a useState
.
TilesSwitchDialog Props: - currentTile
: required, active tile of the map, usually setted in the map itself with a useState
. - setCurrentTile
: required, method to update current tile on the map.
MapToggleTooltips
A switch to show or hide tooltips on the map.
Props:
showTooltip
: required, usually setted in the map itself with a useState
.setShowTooltip
: required, method to showTooltip or not.MapToggleFullscreen
A switch to set the map fullscreen or not.
Props:
isMapFullScreen
: required, usually setted in the map itself with a useState
.setIsMapFullScreen
: required, method to set into fullscreen or not.MapToggleCluster
A switch to allow clustering or not of marker on the map. You should use MarkerClusterGroup
from react-leaflet-markercluster
Props:
isClusterActive
: required, usually setted in the map itself with a useState
.setIsClusterActive
: required, method to enable custering of markers or not.MarkerComponent / CircleMarkerComponent Components used to display a marker on the map. Props: - see js file for not required props, mainly the same props as Marker
from react-leaflet. - PopupComponent
: not required, Popup used while clicking on the marker - TooltipComponent
: not required, Tooltip used while hovering the marker - item
: required, an object with latitude
an longitude
arguments, those are numbers
MarkersListComponent Used to display a list of markers
Props: - markerProps
: not required, props spreaded to the marker - items
: required, array of items use by previous component - PopupComponent
: not required, Popup used while clicking on the marker - TooltipComponent
: not required, Tooltip used while hovering the marker - onMarkerClick
: not required, method applied while clicking on the marker on the map - isCircle
: not required, display marker as a circle or not - onContextmenu
: not required, method applied while right clicking on the marker on the map
Most tables we use need to support filters and deep linking. We have a TableWithDeepLinking
component for that purpose, which is a wrapper on the Table
from Bluesuare-components.
The typical props to pass are: - data
: the table data. Usually originate s from a react-query
hook - page
: the current page. Usually returned by the API - pageSize
: the amount of rows to display on each page. Also comes from the API - count
: the total amount of items in the page. From the API - pages: total number of pages. From the API -
baseUrl: the baseUrl the table will redirect to -
params: the params of the current location.
TableWithDeepLinkwill combine them with
baseUrlto redirect to the correct location -
extraProps: an object. The
loadingkey will be used to manage the table's loading state. Other values will force a table re-render when they change (similar to
useEffectdeps array), which can be useful in some situations -
columns: an array of objects of type
Column` (imported from bluesquare-components). It's usually defined in a custom hook in order to easily handle translations.
Note: useDeleteTableRow
When performing DELETE
operations from the table that will reduce the amount of table rows, we can run into a pagination bug. To avoid it, use useDeleteTableRow
in the useDelete<whatever>
hook that will return the delete function:
export const useDeleteWhatever = (\n params: Record<string, any>,\n count: number,\n): UseMutationResult => {\n const onSuccess = useDeleteTableRow({\n params,\n pageKey: 'whateverPage', // optional, will default to \"page\"\n pageSizeKey: 'whateverPageSize', //optional, will default to \"pageSize\"\n count,\n invalidateQueries: ['whatever'], // optional\n baseUrl: baseUrls.whatever,\n });\n return useSnackMutation({\n mutationFn: deleteWhatever,\n options: { onSuccess },\n });\n};\n
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#filters","title":"Filters","text":"Most tables we display come with one or several filters, most commonly a text search and date filters.
It's a global feature in Iaso that all filter searches performed on pages are deep-linked, i.e.: the parameters of the filters are saved in the url, so users can share the results of their search/filtering. This has an impact on the architecture of Iaso:
All search fields need to be declared in routes.js
, under the params
key. Important: params
is an ordered list. Passing them in the wrong order in the URL will result in a 404.
Applying a filter doesn't change the component state per se but results in a redirection. Depending on the use cases, we may or may not want to save consecutive filters/searches in the routers history
We have a few ready-made components for filters:
InputComponent
: handles most types of inputs: text, select, checkbox, radioOrgUnitTreeviewModal
: handles searches on org unitsDatePicker
and DateRange
: handle datesInputComponent
takes a keyValue
prop, which is a string that corresponds to the url parameter that stores the filter value, and an onChange
prop which is a function with the signature (keyValue,value) => void. DateRange
takes a keyDateFrom
and a keyDateTo
that play the same role for the start and end date respectively.
We also have a useFilterState
hook that handles the state and update methods for filters of a given page:
const { filters, handleSearch, handleChange, filtersUpdated } =\n useFilterState({\n baseUrl,\n params,\n withPagination: false,\n saveSearchInHistory: false,\n });\n
baseUrl
: is the url of the pageparams
: the parameters passed to the url. useFilterState
will generate the state for the filters based on those. false
the hook will remove parameters related to table pagination (page
, pageSize
and so on)saveSearchInHistory
: if true
, the redirection will use redirect
i.o redirectToReplace
and the searchg will be saved in the router's history, meaning that using the back arrow will bring the user to the previous search and not the previous page. type?:string
to type: string | undefined
const
to let
:```javascript // BAD let myVar = \"placeholder\" if(otherVAlue) { myVar = otherValue }
// GOOD const myVar = otherValue ?? \"placeholder
```
// BAD\nconst username = user => user.firstname + user.lastname\n\n// GOOD\nconst makeUsername = user => user.firstname + user.lastname\n
// BAD, we're just returning a value\nconst useMyValue = (value :string) => {\n return parseInt(value,10)\n}\n\n// GOOD, because of useSafeIntl, the return value will change with user locale\nconst useMyValue = (value :IntlMessage) => {\n const { formatMessage } = useSafeIntl()\n return formatMessage(value)\n}\n\n//GOOD, the returned object is memoized\nconst useMyValue = (value: string) => {\n return useMemo(() => {\n const result = {\n isNumber:false, \n value\n }\n if (parseFloat(value)){\n result.isNumber = true\n }\n return result\n },[value])\n}\n\n// GOOD, as side effect will be triggered after the first render\nexport const useSkipEffectOnMount = (func:Function, deps:Array<unknown>) => {\n const didMount = useRef(false);\n\n useEffect(() => {\n if (didMount.current) {\n func();\n } else {\n didMount.current = true;\n }\n }, deps);\n};\n\n
"},{"location":"pages/dev/reference/guidelines/front-end/front-end.html#remarks","title":"Remarks","text":"params
listed are ordered, meaning you can get a 404 when they are not in the right order. Related to this, the paginationPathParams
that we spread in most routes should come first, right after the accountId
to avoid getting 404 because of automatic redirections@cypress
in a comment on your PRrelease
label to itsuggestion
block. More information can be found here on point 6.To configure a public registry, follow these steps:
"},{"location":"pages/dev/reference/public_registry/public_registry.html#step-1-activate-the-plugin","title":"Step 1: Activate the Plugin","text":"First, you need to activate the plugin by adding \"registry\"
to the PLUGINS
variable in your settings.
PLUGINS = registry,...\n
"},{"location":"pages/dev/reference/public_registry/public_registry.html#step-2-make-a-data-source-public","title":"Step 2: Make a Data Source Public","text":"default_registry
(this value is hardcoded in the front-end at the moment).Here is an example configuration:
https://www.example.com
default_registry
{\"fields\": [\"Name\"]}
polioTest
polio
Polio 1
com.poliooutbreaks.app
After filling in all the required fields, save the configuration. Your public registry should now be configured and ready to use.
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html","title":"SQL Dashboard feature","text":"For super user across account there is a way to run raw read only SQL queries at the /explore/
page : https://iaso.bluesquare.org/explore/ e.g SELECT name FROM iaso_orgunittype
This is useful to check the database state and query data accross different client account. You can also save query and share them with others.
This feature is implemented via the excellent Django SQL Dashboard, their documentation has more complete information: https://django-sql-dashboard.datasette.io/en/stable/index.html
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#tips","title":"Tips","text":""},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#bar-charts","title":"Bar charts","text":"You can generate bar chart by having two column named bar_label
and bar_quantity
Examples
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#example-number-of-org-unit-per-type-in-a-project","title":"Example: Number of Org Unit per type in a project","text":"select iaso_orgunittype.name as bar_label, count(org_unit.id) as bar_quantity \nfrom iaso_orgunittype \n join iaso_orgunittype_projects on iaso_orgunittype.id = iaso_orgunittype_projects.orgunittype_id \n left join iaso_orgunit org_unit on iaso_orgunittype.id = org_unit.org_unit_type_id \n where iaso_orgunittype_projects.project_id = 1 \ngroup by iaso_orgunittype.id \norder by bar_quantity\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#orgunit-hierarchy-linked-to-an-org-unit","title":"OrgUnit hierarchy linked to an org unit","text":"SELECT * FROM iaso_orgunit WHERE path ~ '*.104133.*'\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#use-of-parameters","title":"Use of parameters","text":"You can use parameter, this will automatically create an input.
If you save them as a dashboard it will allow passing the paramter in the url
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#example-number-of-submission-per-form-and-per-org_unit-in-a-particular-sourceversion-version_id","title":"Example number of submission per form and per org_unit in a particular SourceVersion (version_id)","text":"SELECT \"iaso_orgunit\".\"path\", \n \"iaso_orgunit\".\"name\", \n \"iaso_instance\".\"form_id\", \n count(\"iaso_instance\".\"id\") filter \n (WHERE (not (\"iaso_instance\".\"file\" = '' and \"iaso_instance\".\"file\" is not null) and \n not (\"iaso_instance\".\"deleted\" and \"iaso_instance\".\"deleted\" is not null))) as \"instances_count\" \n\nFROM \"iaso_orgunit\" \n JOIN \"iaso_instance\" \n ON (\"iaso_orgunit\".\"id\" = \"iaso_instance\".\"org_unit_id\") \n and version_id = %(version_id)s\nGROUP BY \"iaso_orgunit\".path, \"iaso_orgunit\".\"id\", \"iaso_instance\".\"form_id\" \norder by \"iaso_orgunit\".path \nlimit 100;\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#use-multiple-ids","title":"Use multiple ids","text":"This tips is useful to allow passing multiple ids, separated per ,
select * from iaso_form where\niaso_form.id = ANY (string_to_array(%(form_ids)s::text, ',')::int[])\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#multi-line-chart","title":"Multi Line chart","text":"You can generate multi line chart by naming columns line_label
, line_quantity
and line_category
(you need all three)
select line_label,\n line_category,\n sum(line_quantity) over (PARTITION BY line_category order by line_label) as line_quantity\nfrom (\nselect TO_CHAR(date_trunc('month', COALESCE(iaso_instance.source_created_at, iaso_instance.created_at)), 'YYYY/MM') as line_label, count(*) as line_quantity, iaso_project.name as line_category from iaso_instance inner join iaso_project on iaso_instance.project_id = iaso_project.id\n group by line_label, iaso_project.name order by line_label, line_quantity desc limit 200\n) as data\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#cumulative-sum","title":"Cumulative sum","text":"To generate a cumulative sum (particularly useful for progression over time). Wrap your query with
select line_label,\n line_category,\n sum(line_quantity) over (PARTITION BY line_category order by line_label) as line_quantity\nfrom (\n YOUR QUERY\n) as data\n
See previous example.
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#random-data-generation-example","title":"random data generation example","text":"select line_label, \n line_category, \n sum(line_quantity) over (PARTITION BY line_category order by line_label) as line_quantity \nfrom (select TO_CHAR(gen_date.generate_series, 'YYYY/MM') as line_label, \n (random() - 0.2) * 1000::int as line_quantity, \n name as line_category \n from (select * \n from generate_series('2008-03-01 08:00'::timestamp, \n '2009-03-04 12:00'::timestamp, '1 month')) gen_date \n cross join (VALUES ('foo'), ('bar'), ('baz')) as categories (name)) as data\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#searching-in-org-units-org-unit-types","title":"Searching in Org Units, Org Unit Types","text":"Here are some examples of queries to find Org Units, their types, reference forms and everything linked to the hierarchy of a specific Org Unit.
As we are using Postgre's ltree extension and django-ltree to model this hierarchy, specific SQL operators are available to search in a performant way and queries can be cumbersome.
Let's say you have a OrgUnit with ID : XXXXXX
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#find-the-hierarchy-linked-to-this-org-unit","title":"Find the hierarchy linked to this Org Unit.","text":"SELECT * FROM iaso_orgunit WHERE path ~ '*.XXXXXX.*'\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#find-the-related-org-unit-types","title":"Find the related Org Unit Types :","text":"SELECT * FROM iaso_orgunittype WHERE id IN \n (SELECT org_unit_type_id FROM iaso_orgunit WHERE path ~ '*.XXXXXX.*')\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#reference-forms-of-these-org-unit-types","title":"Reference forms of these Org Unit Types","text":"SELECT * FROM iaso_form WHERE id IN \n (SELECT reference_form_id FROM iaso_orgunittype WHERE id IN \n (SELECT org_unit_type_id FROM iaso_orgunit WHERE path ~ '*.XXXXXX.*'))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#find-the-form-versions-of-these-reference-forms","title":"Find the Form Versions of these Reference Forms.","text":"SELECT * FROM iaso_formversion WHERE id IN \n (SELECT id FROM iaso_form WHERE id IN \n (SELECT reference_form_id FROM iaso_orgunittype WHERE id IN \n (SELECT org_unit_type_id FROM iaso_orgunit WHERE path ~ '*.XXXXXX.*')))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#the-instances-linked-to-that-hierarchy","title":"The Instances linked to that hierarchy","text":"SELECT * FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*')\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#finding-the-projects-linked-to-that-hierarchy","title":"Finding the projects linked to that hierarchy","text":"SELECT * FROM iaso_project WHERE id in \n (SELECT project_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*'))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#devices-linked-to-that-hierarchy","title":"Devices linked to that hierarchy","text":"SELECT * FROM iaso_device WHERE id in \n (SELECT device_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*'))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#accounts-linked-to-these-projects","title":"Accounts linked to these projects","text":"SELECT * FROM iaso_account WHERE id IN \n (SELECT account_id FROM iaso_project WHERE id in \n (SELECT project_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*')))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#source-versions-linked-to-these-projects","title":"Source versions linked to these projects","text":"SELECT * FROM iaso_sourceversion WHERE id IN\n (SELECT default_version_id FROM iaso_account WHERE id IN \n (SELECT account_id FROM iaso_project WHERE id in \n (SELECT project_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*'))))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#datasources-linked-to-these-versions","title":"Datasources linked to these versions","text":"SELECT * FROM iaso_datasource WHERE id IN (SELECT data_source_id FROM iaso_sourceversion WHERE id IN\n(SELECT default_version_id FROM iaso_account WHERE id IN \n (SELECT account_id FROM iaso_project WHERE id in \n (SELECT project_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*')))))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#credentials-linked-these-datasources","title":"Credentials linked these datasources","text":"SELECT * FROM iaso_externalcredentials WHERE id IN (SELECT credentials_id FROM iaso_datasource WHERE id IN (SELECT data_source_id FROM iaso_sourceversion WHERE id IN\n(SELECT default_version_id FROM iaso_account WHERE id IN \n (SELECT account_id FROM iaso_project WHERE id in \n (SELECT project_id FROM iaso_instance WHERE org_unit_id IN \n (SELECT id FROM iaso_orgunit WHERE path ~ '*.104133.*'))))))\n
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#restrictions","title":"Restrictions","text":"This functionality is severly restricted to prevent the risk of data leak and security issues:
To garantee read only access this feature use a separate user that should only be given restricted right.
The functionnality is automatically enabled if this user is set via the DB_READONLY_USERNAME
environment variable.
To configure it: Create a Postgresql user with a password and no acess and give him the role readonlyrole
. You can do so using the sql command
GRANT readonlyrole to YOUR_USER\n
Set the environment variable DB_READONLY_USERNAME
and DB_READONLY_PASSWORD
.
Some migration will give read acess to the certain tables to the readonlyrole
, should you give access to more table use the command
GRANT SELECT ON TABLE \n iaso_new_table_1,\n iaso_new_table_2,\nTO \"readonlyrole\";\n
to only give access to certain column on a table
GRANT SELECT( \n id, username, is_active, date_joined \n) ON auth_user TO \"readonlyrole\";\n
See also https://django-sql-dashboard.datasette.io/en/stable/security.html
"},{"location":"pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#in-local-dev","title":"In local dev","text":"this feature is automatically enabled.
"},{"location":"pages/dev/reference/vector_control/vector_control.html","title":"Vector control","text":"This App is used to log API calls in the DB.
The idea is that the mobile app user may not have great internet connection where they are so in case of import problem we can fix it server side and not ask them to upload again.
API endpoint to be logged as such are decorated with the @safe_api_import decorator.\nThe request themselves are stored in the vector_control.APIImport model.\nTo replay the failings requests use the django command reimport_failed_imports.py\n
This app used to be a lot of others things too before, hence the not matching name.
"},{"location":"pages/users/FAQ/faq.html","title":"Faq","text":"How do I configure Iaso in a way that mobile users cannot create new OUs ? And/or is it possible to limit OU creation to certain OU types. Ex: mobile users can create the types at the bottom of the hierarchy (FOSA, village), but not at the top (Region -> Aire sanitaire) ?
It\u2019s in the org unit type configuration: you specify what is allowed under a given org unit type in the \"sub org unit types\" selector.
"},{"location":"pages/users/how_to/convert_docx_to_md/convert_docx_to_md.html","title":"How to convert a .docx file to .md","text":"docx
file to iaso/docs/originals
iaso/docs/pages/users/how_to/my_new_page
. Make sure to use snake_case when naming your folder.iaso/docs/pages/users/how_to/my_new_page
folder, create a media
folder. The full path to the media folder should be: iaso/docs/pages/users/how_to/my_new_page/media/
iaso/docs/pages/users/how_to/my_new_page/
, run the following pandoc command: pandoc -s -f docx -t markdown_mmd --extract-media=. -o ./my_new_page.md ../../../../originals/MyPage.docx\n
This will copy all the attached media of you docx
file (like screenshots) in iaso/docs/pages/users/how_to/my_new_page/media/
, and create my_new_page.md
in iaso/docs/pages/users/how_to/my_new_page/
- Rename the media
folder to attachments
- Open my_new_pages.md
. search (ctrl+F/cmd+F) for the word \"media\" and replace it with \"attachment\" whenever it is a path to a file, eg:
<!-->Initial value<-->\n<img src=\"./media/image1.png\"style=\"width:3.02211in;height:0.79688in\" />\n<!-->Correct value<-->\n<img src=\"./attachments/image1.png\"style=\"width:3.02211in;height:0.79688in\" />\n
This is because media
folders are not pushed on github, so if we don't rename it, the screenshots in your docs will be lost
my_new_pages.md
and fix any layout issues that may have been caused by the conversion.iaso/mkDocs.yml
and locate the nav
entry. nav:\n - Home: index.md\n - Users: \n - References:\n - User guide:\n - ./pages/users/reference/user_guide/user_guide.md\n - How to:\n - ./pages/users/how_to/my_new_page/my_new_page.md #<-- Your page would go here\n - FAQ: ./pages/users/FAQ/faq.md\n
"},{"location":"pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html","title":"How to add a new page to Iaso's documentation","text":""},{"location":"pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html#1-determine-where-the-page-belongs","title":"1. Determine where the page belongs","text":"To determine where a new documentation page fits, there are 2 questions to answer: - Who is it intended for? - What kind of document is it?
We currently have 2 categories of documentation users: developers and users. This will determine the style of the writing and the assumptions you can make in terms of, eg prior knowledge of the product.
We determine the kind of document using the diataxis framework. Basically, the idea is to ask what the goal of the document is. A simple rule of thumb is: - Give instructions about how to perform a task, eg: how do I login? -> how to - Explanation of concepts, eg: what is an org unit type? -> reference - Explanation on choices made for some implementation, eg: why do we use react-query for API calls? -> explanation
Once we know who the document is intended for and what kind of document it is, we just need to follow the folder structure. For example, this document is intended for users and aims to explain how to create a new page in the documentation, so it will go in:
> pages\n > users\n > how_to\n
There is an exception for the FAQ whixh we keep separate and visible for convenience
"},{"location":"pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html#2-create-a-branch-on-git-for-the-new-document","title":"2. Create a branch on git for the new document","text":"Ideally, there will be a Jira ticket for the changes about to be made. It should be used to name the branch, as it will enable Jira to directly link the ticket to the branch. For example, the development branch for this document is called IA-2630_how_to_write_iaso_doc
To create the branch: - Open a terminal - Make sure you are on main. If not run git checkout main
- Run git checkout -b <Branch name>
. This will create the branch and switch to it
By convention, we create a folder with the same name as the markdown file. For example, this document is in:
> pages\n > users\n > how_to\n > create_new_documentation_page\n - create_new_documentation_page.en.md --> this page\n - create_new_documentation_page.fr.md --> its french counterpart\n
It's important to use the format <name>.<language>.md
, as it will be used by mkDocs to display the right page for the right language
Format the text using markdown syntax
"},{"location":"pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html#41-add-images","title":"4.1. Add images","text":"/attachments/
in the document's folder and move the images there> pages\n > users\n > how_to\n > create_new_documentation_page\n > attachments\n - create_new_documentation_page.en.md\n - create_new_documentation_page.fr.md\n
To add the image in the markdown file, either: - use markdown syntax to add the link ![my_image](./attachments/my_image.png)
- use an html img
tag:
<img src=\"./attachments/image49.png\" />\n
The markdown syntax is less cumbersome if the image is already at the right size. The img
tag allows for manually setting the image width and heigh via the style
attribute:
<img src=\"./attachments/image49.png\" style=\"width:50px;height:50px\" />\n
Note: Add the /attachments/
folder even if the document doesn't contain any images yet. It will make it easier for others to add them later.
Once the document is ready, the changes need to be saved on the git branch, then pushed on Github, so they can be reviewed and merged. There are tools to make this part faster and easier to manage (Github desktop, or even just the git ineterface of VS Code), but if it needs to be done in the terminal (from the document's branch): - git add .
- git commit -m <commit message>
- git push
Pull requests are the process through which we review code. Since the documentation is hosted as part of the code, it's going through the same review process, though not necessarily by the same persons.
To open a pull request: - Go to iaso's pull requests - Click New pull request - Select a branch - Describe the changes. The description template can be ignored for documentation changes, but please leave a description, as it helps tracking and understanding changes.
"},{"location":"pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html#7-review-a-pull-request","title":"7. Review a pull request","text":"Pull requests are peer-reviews that insure that all changes are cross-checked, so one should not merge their own pull requests.
To review a pull request: - Go to iaso's pull requests - Click in the pull request - Click \"Add your review\" - Review the changes, comment where necessary - To finish the review, click \"Review changes\" - If the changes can be deployed as they are, choose \"Approve\" - If not, explain what needs to be corrected and chooses \"Request changes\" - If the PR has been approved, go the \"Conversation\" tab of the PR, scroll down and click \"Merge pull request\"
The changes will be visible in production once the pull request has been merged
"},{"location":"pages/users/how_to/edit_documentation/edit_documentation.html","title":"How to edit an existing page in iaso's documentation","text":""},{"location":"pages/users/how_to/edit_documentation/edit_documentation.html#1-to-add-only-text","title":"1. To add only text","text":"For the text, see point 1 above. For the images, add the images to the /attachments/
folder of that document, eg, for user_guide:
> user_guide\n > attachments // <-- there\n - user_guide.md\n
To add the image in the markdown file, either: - use markdown syntax to add the link ![my_image](./attachments/my_image.png)
- use an html img
tag:
<img src=\"./attachments/image49.png\" />\n
The markdown syntax is less cumbersome if the image is already at the right size. The img
tag allows for manually setting the image width and heigh via the style
attribute: ```html
If the /attachments/
folder doesn't exist, the change can't be made using Github's interface and should be made using git and an IDE/text editor
Find someone with appropriate access right (django admin required)
https://iaso.bluesquare.org/api/setupaccount/
Use and store the user/password in a password manager
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#2-create-a-dedicated-dhis2-for-iaso","title":"2. Create a dedicated DHIS2 for iaso","text":"We want to keep track of which app is changing which data/metadata of dhis2 so please don\u2019t use the main/default \u201cadmin\u201d user but a dedicated one.
Go in the dhis2 \u201cUsers / Utilisateurs\u201d module
\u201cDuplicate the admin\u201d
Verify the account is empty or in the left menu
Avoid doing the next steps with the django admin, as it can lead to industrial accident: the user may be linked to a totally different account/project you might end up with the pyramid of a project filled with orgunits of another country.
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#4-add-a-new-project","title":"4. Add a new project","text":"Use the naming used by clients if applicable.
Promote \u201cgood behavior\u201d by enabling authentication by default
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#5-create-a-new-datasource","title":"5. Create a new datasource","text":"Use the user created at step 2
Make the source the default one
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#6-create-a-new-first-version-of-the-data-source","title":"6. Create a new (first) Version of the data source","text":"You can import this first version
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#7-updating-the-pyramid","title":"7. Updating the pyramid","text":"IMPORTANT note that if you \u201cadd new orgunits or add/change groups\u201d that\u2019s not previous step screen that you should use
but the \u201cupdate\u201d button on the default version !
If you create a new version \u201cswap it to the default version\u201d this detection will be broken since incoming submission will be attached to different orgUnit iaso id.
"},{"location":"pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#8-whats-next","title":"8. What's next ?","text":"Use the Mobile app in the store and provide the appId/user/password
Start configuring the iaso forms
try to be consistent and future proof in the naming !
this is good :
``` PMA - Qualit\u00e9 01 - Indicateurs g\u00e9n\u00e9raux PMA - Qualit\u00e9 02 - Plan financier PMA - Qualit\u00e9 03 - Consultation Postnatale ... PMA - Qualit\u00e9 10 - Vaccination PMA - Qualit\u00e9 11 - Accouchements PMA - Quantit\u00e9 PCA - Qualit\u00e9 ...
\nthis is **BAD** : \n\n
PMA - Qualit\u00e9 1 - Indicateurs g\u00e9n\u00e9raux PMA - qualit\u00e9 10 - Vaccination PMA - Qualit\u00e9 11 - accouchements PMA - qualit\u00e9 2 - plan financier PMA - Qualit\u00e9 3 - consultation Postnatale... ... PMA - Quantit\u00e9 Qualit\u00e9 - PCA - ... ``` - computers are really bad at sorting in natural order so prefer 01 02 03 - be consistent in your upper/lower case usage - be consistent by prefixing the entity type (no need to put the country in it, we have limited space in the mobile app)
"},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html","title":"Setup login with dhis2 for iaso","text":""},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#in-dhis2","title":"In DHIS2","text":""},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#go-in-the-oauth-settings","title":"Go in the oauth settings","text":"in the menu :
System settings > Oauth 2 clients\nParametres Systeme > Oauth 2 clients\n
"},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#create-oauth-client","title":"Create oauth client","text":"Name : iaso
Select : authorization code Url : you need to pick a unique code : https://iaso.bluesquare.org/api/dhis2/<<unique-code>>/login/
In django admin : https://iaso.bluesquare.org/admin/iaso/externalcredentials/
to be able to easily go back to dhis2 add the feature flag \"SHOW_DHIS2_LINK\" on the Project
then the entry should appear
"},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#in-iaso","title":"In iaso","text":""},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#link-the-iaso-user-with-a-dhis2-user","title":"Link the iaso user with a dhis2 user","text":"in iaso general ui
"},{"location":"pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#test","title":"Test","text":"login in dhis2 with the linked in previous step
<<dhis2>>/uaa/oauth/authorize?client_id=<<unique-code>>&response_type=code
then \"Authorize\": you should end up in iaso
It's not working ?
What comes with data collection are questions, and to organize these questions, data collection forms. These are basically lists of the questions one would like to collect answers for, while specifying options (mandatory or not, skip a question depending on previous answer, etc.). IASO builds on XLS forms for its questionnaires, which are therefore pre-defined using an Excel file.
In IASO, data collection forms are versioned meaning that every time a new version is created, the former version is kept and available in the system.
"},{"location":"pages/users/reference/iaso_concepts/iaso_concepts.html#organization-units","title":"Organization Units","text":"IASO uses the notion of Organization Units (Org unit or OU) to manage geographic data. The organisation unit types (OUT) represent levels in the hierarchy
Example:
Country
Region
The organization units are classified in the pyramid according to a parent and one or several children (except the top parent(s) and the lowest child/children). Example below:
Democratic Republic of Congo (Org unit type \"Country\") is the parent org unit of
Kinshasa (Org unit type \"City\"), which is the parent org unit of
Data collection in IASO is structured according to the defined hierarchy, and any user needs to explicitly select an organization unit before proceeding to opening the questionnaire and answer questions. This way, one makes sure that the data collected is correctly associated with the relevant geography.
"},{"location":"pages/users/reference/iaso_concepts/iaso_concepts.html#projects","title":"Projects","text":"In IASO, a Project is a mobile application instance, with its own App ID. Within one account, you can have one or several Project(s) with different feature option(s). Users can be linked to one or several Project(s).
Good to know:
One Project is linked one data source
One Project can be linked to one or several users
IASO mobile application is available on Google Play Store (Android phones only).
It can work completely offline - once the end user has encoded the data needed, he/she can upload the data collected offline all at once when network is available.
Updates made from the web (forms versions, health pyramid) will be reflected in the App only after the App data has been refreshed and this requires network connectivity.
Key tip before testing / using the App - Make sure you have refreshed data beforehand
"},{"location":"pages/users/reference/iaso_mobile/iaso_mobile.html#run-the-mobile-application-for-the-first-time","title":"Run the mobile application for the first time","text":"IASO Mobile application has to be configured on the web part before using (see the part \u201cProject\u201d).
Then you can:
Download IASO App on Google Play
Insert the server url : https://iaso.bluesquare.org
See below an overview of the main buttons that you can find on the main screen in data collection mode.
In the More Options part, you can take the below actions: - Refresh data: you need to have internet connectivity to do so. It will synchronize the mobile application with IASO web data. In order to avoid that it takes too long in low-connectivity settings, you can choose to refresh only sub-parts such as Forms, Organization Units, or other. - Change the App ID: you can switch Project by entering another App ID. In order to make sure that there is no data from the former App ID left on the IASO mobile application, please access your parameters and erase storage and cache data from IASO beforehand. - Change the URL of the server: this can be handy if you need to switch from Production to Staging server - Logout: your user can logout. This does not prevent data consultation of local data (data available on IASO on the user's device) - About: gives the version of the IASO mobile application. It can be good to have to debug.
"},{"location":"pages/users/reference/iaso_mobile/iaso_mobile.html#collect-data","title":"Collect data","text":"Once you are connected to the IASO mobile application, you can then proceed with your data collection. Here below are the different screens that you would see for a simple data collection.
You will then have data collection form chosen opening. You can proceed with answering the different questions and press \"Next\" until the end of the Form.
If you wish to interrupt data collection during input, you can press the back button on the tablet or smartphone.
Once you click on the button, you have 2 options: - Save Changes: to save all data already filled and the form with unfinalized status. With this option you can, come back and continue enter data - Ignore Changes: to delete data filled and the form
Upload collected data
If you collect data with your mobile device, they are stored in your device. You need to upload data to the server to make them visible at central level. Keep in mind that you need internet connection in order to be able to upload data.
Click on the \"Send Finalized Forms\" icon on the mobile application home page on the top right corner.
Then, a specific page will open to let you know if the data has been correctly uploaded. Finalize the operation by clicking on \"Send to server\".
"},{"location":"pages/users/reference/iaso_modules/iaso_modules.html","title":"Modules","text":"IASO is organized according to Modules, which are groups of functionalities which can be added up depending on the use case to cover. Here are the Modules available in IASO:
"},{"location":"pages/users/reference/iaso_modules/iaso_modules.html#data-collection-functionalities","title":"Data collection functionalities","text":"IASO web platform is intended to administrators for them to define the details of the data collection they would like to proceed with. Some key assets of IASO are:
the versioning of all data - every change is tracked and former versions can be retrieved as needed
geo-structured data collection - forms are linked to clear Geographical levels or \"Organization Units\"
the traceability of changes - allowing decentralization of the activities and responsibilities Administrators can therefore use the web platform to plan, monitor and then evaluate the data collection efforts.
To log into the web interface, go to https://iaso.bluesquare.org/login/ and sign in with your username and password.
You can also reset your password by clicking on the link \"Forgot password\". This will send an automatic email and allow you to create a new password.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#navigating-in-iaso","title":"Navigating in IASO","text":""},{"location":"pages/users/reference/iaso_web/user_guide.html#manage-data-collection-forms","title":"Manage data collection forms","text":""},{"location":"pages/users/reference/iaso_web/user_guide.html#forms-list","title":"Forms list","text":"From the forms list, you can search through the available forms of the IASO account you are connected to using the filters:
The below buttons allow you to manage the data collections forms.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#createupload-a-data-collection-form","title":"Create/Upload a data collection form","text":"Access the Forms entry in the menu, then click on Form list. Click on the button \"Create\". Once on the Form creation page, follow the below steps:
Tips:
Once a form has been completed and sent to the server, it creates a \"form submission\". Every form submission is recorded into the platform and data submitted can be consulted from there. You can use the filters to consult the form submissions as needed:
This view allows you to search forms through free text entry and several filters that can be combined.
Once you have applied at least one form filter, you can download submissions using the \"CSV\" or \"XLSX\" buttons.
You can also create a new submission by clicking on \"Create\". This will open Enketo and ask you which Organization Unit it relates to.
You can also check the submissions on the map view, on which the filters apply. To make sure to have this map view enabled, make sure you have added the feature flag \"GPS for each form\" to the related Project.
The tab \"File\" allows you to visualize the files that have been submitted together with the forms, such as pictures. When clicking on a given file, you can then be redirected to the relevant form submission.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#manage-submissions","title":"Manage submissions","text":"On the Submissions page, you can see the list of submissions that have been done for the account. You can manage them using the below options.
Visualise a submission
You can view a specific submission/submitted form by clicking on the \"View\" button (see above).
This allows you to see the data submitted and edit it on Enketo (open-source web application.
The \u201cInformation\u201d section provides a technical overview of the form.
The \u201cLocation\u201d section shows the health pyramid's indication of where the data was collected.
The \u201cExport Requests\u201d section shows when the data was exported to DHIS2, by whom, and any errors that occurred during export.
The \u201cFiles\u201d section can contain images, videos, documents.
The \u201cForm\u201d section shows all form questions and answers entered during data collection.
Download a submission
The \"XML\" icon allows you to download a submission in XML format.
The gear icon on the bottom corner at the right hand side shows you a series of icons upon hover. These allow you to:
See below the dedicated sections for more information on each of these actions.
Delete a submission
Allows you to delete the form. If it has already been exported to DHIS2, this will not delete the data in DHIS2. A warning message will appear:
Edit attached Org Unit or Period
When you click on \u201cEdit Period and/or Organisational Unit\u201d, a window opens allowing you to reassign the instance. You can change the time period or organization unit that has been assigned to the submitted form.
Export a submission
The export function allows you to export the form to DHIS2. Beforehand, it needs to have been mapped using the DHIS2 mapping functionality.
Edit the submission via Enketo
To edit a form, click on the Enketo icon (see above).
Edit the form by changing the answers to the questions that need to be changed. Then click on submit at the bottom of the form.
Push GPS coordinates from the submission to the Org Unit
This will use the GPS coordinate collected via the form to push to the Organization Unit GPS coordinates.
Lock submission
This functionalty allows you to protect the form submissions from further editing by users who have less permissions than you.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#form-statistics","title":"Form statistics","text":"This view allows you to see statistics about the forms. When clicking on \u201dForm Statistics\" you will open a page with two graphs.
The first one shows the total number of submissions over time and the second one shows the new submissions per month per form.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#dhis2-mappings","title":"DHIS2 mappings","text":"A great advantage of IASO is that you can export data to DHIS2. When doing so, prior mapping is necessary. After the form is uploaded, map the form to match the data item in DHIS2.
Click on DHIS mappings to see the forms :
In the Form view you can see details of:
Actions
Name of forms available for mapping
Versions
Type of the form :
Aggregate : fix
Event : series of singular events
Event Tracker : continuous
Number of questionnaire to be mapped
Total number of questionnaires
Mapping coverage
Date of last modification
Click \"Create\" and a window will open allowing you to map each questionnaire of the xls forms to the correspondent DHIS2 data element
The mapping process consists of selecting a question on the left and deciding whether it should be mapped to DHIS2 or not.
Some questions may not need to be mapped like notes, metadata etc. in such a case click on never map.
If the question is to be mapped, search for the correspondence DE in the box by using the name, code or ID and then confirm.
Once confirmed, the question will turn to green and be counted.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#completeness","title":"Completeness","text":"This functionality is intended to use cases where Periods have been set to the data collection forms. In the view \u201ccompleteness\u201d you will see details of :
Buttons to select forms \u201cready\u201d to be exported, form with \u201cerrors\u201d > and forms that have been \u201cexported\u201d
Periodicity filter : the periodicity filter allows you to organise > the data into months, quarters, semesters or years. The list will > display the forms available for the selected period, and will > indicate how many forms have been submitted for each
Synchronise button to synchronise two forms
Click on each of these buttons to have forms ready to be exported, errors and exported. A periodicity filter is there to organise data in months, quarters, semester or yearly.
If you click on the number of submissions, you will be taken to the submissions view, where you can click on the view icon and see the submissions for that form.
Click on the button to synchronise two forms
Eg: to get aggregate data from community verification survey, all the client forms should be synchronised to a single form.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#completeness-statistics","title":"Completeness statistics","text":"This table view shows you the completeness of the forms submissions in number (number of completed forms) and in percentages (Data completeness). A distinction is made between \u201cdirect forms\u201d (which relate to the select Organization unit level) and \u201clower level forms\u201d (which relates to forms down the hierarchy).
Use the filters (Form name, Parent Organization Unit, Organization Unit type, User, Planning, Teams, Period) to only see statistics in a more specific way.
The \"See children\" action button allows you to drilldown the geographical hierarchy to identify the level of completeness and spot where issues may have happened.
The first two columns \"itself\" indicate the number of forms completed at the level of the Organization Unit highlighted. The next column \"descendants\" give information on the number of forms completed at the level in question, but also at all lower levels.
You can also view data completeness with a map view by clicking on the \"Map\" tab. Be aware that you need to select a Form in the filters beforehand to enable this view. You can adjust the thresholds to apply to the legend on completeness in the relevant form's advanced settings.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#georegistry-organization-units-management","title":"Georegistry - Organization Units Management","text":"See the Organization Unit definition for more insight on what Organization Units are. In a nutshell, you can manage your geographical data associated to your account using the Organization Unit part of IASO.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#organization-units-list","title":"Organization Units List","text":"Click Organization Units in the menu and then on Organization Unit List to navigate the organization unit pyramid.
You can view in list or map.
You can select an Organization Unit Navigation and:
The search results can be exported in CSV, XLSX or GPKG.
Results can be seen in a list or on a map
The status for when a village has just been added and needs to be reviewed for example.
The external reference is used to export data to DHIS2.
The map helps you to know where the structure is located.
You can see the history of modifications by clicking on the little clock icon or the details of the filled forms by clicking on the eye icon.
Several searches can be made by adding tabs to the page with the + button.
You can choose the colour of the results on the map for each search.
Creation of an Organization Unit
On the Organization Unit list page, click on \"Create\". You can then create an Organization Unit as needed.
You will need to enter the below information before saving:
Optional fields:
Edit an Organization Unit or consult details
To access the detailed view of an Organization Unit, proceed as described below:
In this view, you have a set of tabs that allow you to edit the Organization Unit as needed:
Bulk edition of Organization Units
You can also edit Organization Units in bulk. In order to do this, from the Organization Unit list, tick the boxes of the Organization Units you would like to bulk edit, then hover on the action button. Click on the gear action button, and select the action you would like to perform.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#organization-unit-groups","title":"Organization Unit Groups","text":"Organisation units can be grouped in organisation unit groups, and these groups can be further organised into group sets. Together they can mimic an alternative organisational hierarchy which can be used when creating reports and other data output. In addition to representing alternative geographical locations not part of the main hierarchy, these groups are useful for assigning classification schemes to Organization Units.
Manage Organization Unit Groups
In order to manage the Organization Unit Groups, access the menu entry Organization Units > Groups.
This view allows you to search the Organisation Unit Groups through free text entry.
You can create a new group by clicking on the create button.
Groups can be edited by clicking on the gear icon or deleted by clicking on the delete button.
In the table, the column \"Org Units\" shows the number of Organization Units that are assigned to this group. When you click on the number, you will see the list of that Org Unit group.
Assign Organization Units to Groups
To assign Organization Units to Groups, go to the Organization Units List view from the menu and make a bulk edit of the selected organization Units. See above in section \"Bulk edition of Organization Units\" for more details on bulk edition of Organization Units.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#organization-unit-types-management","title":"Organization Unit types management","text":"Organization Unit types are specific to IASO (i.e. this is not handled in DHIS2). See the part about Organization Units for more details on what Organization Unit types are.
From the Organization Unit menu entry, click on \"Organization Unit types\". This view lists the Organization Unit types existing in your IASO account.
Create an Organization Unit type
Click on \"Create\" and enter the below mandatory fields:
These other fields are not mandatory:
IASO allows to import and handle one or several geographic data source(s).
"},{"location":"pages/users/reference/iaso_web/user_guide.html#data-sources-list","title":"Data Source(s) List","text":"Find here the data sources with their names, versions and descriptions. It is possible to edit the data source, check up on the files\u2019 version history or compare data sources and export them to DHIS2.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#matching","title":"Matching","text":"This is rather a \"geospatial\" functionality : to have several geographical pyramid sources and try to make links (Example: where in a csv \u201cprovince x\u201d is called \"PROVINCE X\" and in another source it is called \"DPS X\").
The algorithms run part is intended for data science work.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#registry","title":"Registry","text":"The Registry entry in Organization Unit is a visualization tool allowing users to drilldown in the geographical hierarchy and consult the geographical data as well as data collection associated to the different level(s).
"},{"location":"pages/users/reference/iaso_web/user_guide.html#review-change-proposals","title":"Review change proposals","text":"With IASO, supervisors can compare and validate data submissions as they are sent to the server. Note that this feature will only work provided that you have activated the \"Change requests\" feature flag on the IASO Project you would like to validate data collected for. See the Projects part for more information on mobile feature flags in IASO.
On the Review change proposals page, users can use the filters to select the proposals they would like to focus on. See on the picture below the detailed filters.
Supervisors can then click on the gear icon at the end of the relevant line to be able to see the details of the change proposal submitted and compare with the former version on the left.
Supervisors can then select the changes they would like to approve by ticking the boxes of the changes selected on the right column, and then hit \"Approve selected changes\". If the changes proposed are not satisfactory, supervisors can reject all changes and provide a comment.
For each change proposal sent, IASO mobile application users will be able to see if they have been approved or rejected, and if rejected, consult the comment.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#planning","title":"Planning","text":"The Planning feature in IASO allows you to plan field work per team and user in defined zones/organization units and according to a specific timeline. Once data collection activities would have been assigned via the interface, field agents using the mobile application would only be able to see the activities assigned to them, and navigate towards the relevant GPS point(s).
In order to be able to create a Planning, you will need to have created beforehand Organization units, Users, a Project, Teams of Users/Teams of Teams and data collection Forms for which you would like to use the Planning feature.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#planning-list","title":"Planning List","text":"Click on Planning in the menu panel. Under Planning List you will see the list of schedules/plannings that have been created in IASO. You can search through the different Plannings using the different filters and take the below actions:
Create a Planning
Click on \"Create\" and you will see the below window opening:
The below fields are mandatory:
You can add a description as an option.
The \u201cPublishing status\u201d (in the lower left corner) feature makes it possible to ensure, once completed (and all assignments made), the newly created planning will be available in the IASO mobile app for the relevant project.
Once you have completed the fields, click \"Save\" to finish.
Click on the eye icon button from the Planning list to start editing your new Planning via the Map interface.
You can do the assignment either through the \u201cMap\u201d or the \u201cList\u201d tab. If processing through the map, first select the Team you would like to assign a geography to in the dropdown, as well as the relevant \u201cBase Org Unit type\u201d in the dropdown. You can then start assigning geographic areas or points directly to the selected Team members directly on the map.
Selected areas will be highlighted with the team\u2019s colour, that you can change as needed.
In order to assign all children Org Unit of a given parent Org unit to the same Team/User, you can select the \"Parent picking mode\" before proceeding to your selection.
If you prefer using the List tab, the process is pretty similar. The main difference being that you work here with a list of names, according to the selected level. Org units are assigned by clicking in front of the item name, in the \u201cAssignment\u201d column.
You can sort Org units and Parents by clicking on the column name.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#admin","title":"Admin","text":"The Admin part of IASO comprises several parts, which will appear or not depending on the user's permissions:
This is the IASO batch updates log. An operation log contains information about when and where an operation ran, the operation status, the number of source and target records processed, and any log messages.
Examples of tasks include:
The statuses are:
The Task list can be refreshed by pressing the button \"Refresh\" on the right top hand side.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#monitoring","title":"Monitoring","text":"This part allows supervisors to monitor devices that are linked with the IASO account. From this page, you can consult:
On the right hand side, you can see the number of devices that are connected under the IASO account you are connected to.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#projects","title":"Projects","text":"A Project in IASO relates to an instance of mobile application. Each Project is identified by a Name and an App ID. See here for a more detailed definition of Projects in IASO.
Create a Project
From the menu, Admin > Projects > Click on \"Create\"
Then, add a Project name, and an App ID. Be aware that the App ID will have to be entered by IASO mobile application users the first time they connect to the IASO app, so it should not be overly complicated to avoid typing errors.
You can then select the Feature flags you would like to apply to your Project in the next tab and press \"Save\".
Feature flags definition
See the table below for more details on the Project Feature flags:
Feature flag Description Authentication Users have to enter their login and password on the mobile application before proceeding to the data collection. Please note that this is possible in IASO to proceed to data collection without authentication for simplified processes (also called \u201canonymous mode\u201d) Mobile: show data collection screen Enable the feature to collect data from the IASO mobile application (data collection that is not linked to a planning or a change request workflow) GPS for each form Every time a data collection form is submitted, a GPS point is automatically taken and associated to the form submission Enforce users are within reach of the org unit before starting the form IASO mobile application users have to be close (50m) to the organization unit GPS point they are collecting data for in order for the form to open Mobile. Show planning screen When a planning has been done in IASO via the web interface, the assigned data collection points and tasks are reflected via this tab Mobile: limit download of org unit to what the user has access to When loading data into the mobile application, only the geographical zone that is assigned to the user is downloaded, so as to enable offline use. This allows a lighter (and then quicker and less data-consuming) download of data at the start of the IASO mobile application Mobile. Show Map of org unit Adds a tab in the mobile application to show the geographic information available for the selected Org Unit in the mobile application. For instance, if a GPS coordinate is available for a health facility, it would show on the map via this tab Request changes to org units Enable the feature to propose changes to org units and their related reference form(s) Mobile: Change requests Adds the tab allowing to propose changes to org units and their reference form(s) GPS for trajectory Enable the user to activate a function that track their position every 15 minutes over a period of time Mobile. Warn the user when forms have been updated When new form versions have been uploaded on the web, the IASO mobile application user is notified. Then the user can choose to apply them or not Mobile. Warn the user when forms have been updated and force them to update When new form versions have been uploaded on the web, the IASO mobile application user is notified and the update happens automatically Mobile. Warn the user when the org units have been updated When changes to the Org units (health pyramid) have been done on the web, the IASO mobile application user is notified. Then the user can choose to apply them or not Mobile. Warn the user when the org units have been updated and force them to update When changes to the Org units (health pyramid) have been done on the web, the IASO mobile application user is notified and the update happens automatically Auto upload of finalized forms The synchronization of forms that have been filled takes place automatically as soon as the user has connectivity Mobile. Finalized forms are read only IASO mobile application users cannot edit the forms once finalized in the mobile application"},{"location":"pages/users/reference/iaso_web/user_guide.html#users","title":"Users","text":"Users can access IASO web and mobile application with login credentials. Each user is assigned permissions and can be limited by location.
Permissions are relatively granular: - By screen/tab - Different read/write permissions for important domains - Restriction of access using the health pyramid - Batch creation/modification of users - Customizable user roles (Administrator, Data manager, etc.)
Please note that the permissions assigned from the User management apply to IASO web only. IASO does not have a system of permissions for its mobile application, but rather a set of Feature Flags.
Create a new IASO user
From the menu Admin > Users, click on \"Create\".
Note that you can also indicate the following information:
Language: you can specify in which default language this user will use IASO web. IASO mobile application is based on the default language of the users's device.
Assign user permissions
On the next tab \u201cPermissions\u201d, you can enable/disable permissions for that user as needed. Note that in the \u201c?\u201d are tooltips to explain what the permissions do.
On the last tab \"Location\", you can restrict the access of the user you are editing to a sub-part of the Organization Unit hierarchy (hence the user will only be able to see data relating to his/her Geography). If no Location is specified here, by default the user will see all data available across the entire hierarchy.
Create users in bulk
You can create several users at once using a CSV file that you import to IASO.
Use the button \u201cCreate from file\u201d and you can then import your list of users (or download the relevant template to do so beforehand).
Manage IASO users
This view allows you to manage users and their permissions. You can search for a user using the different filters.
You can edit IASO users in bulk using the bulk update feature. First, tick each user you would like to update using the check boxes on the right side of each user line.
Then select the action(s) you would like to perform for these users. They can be:
Click on \"Validate\" when done.
"},{"location":"pages/users/reference/iaso_web/user_guide.html#user-roles","title":"User roles","text":"User roles allow to group users that are granted a set of permissions under the same role. In the User role, you are able to create User roles with their matching permissions, to which Users can be assigned to.
Create a User role
From the Admin > User roles page, click on \"Create\".
You can then assign this user role to any user through the Permission tab in the User edit popup. Be aware that the User role permissions will apply to the user, but if the User has more permissions that had been previsouly assigned to him/her, he/she will not lose them but they will add up.
To assign multiple Users to this newly created user role in bulk, go back to the Users list and proceed to a bulk update (see Manage Users above).
"},{"location":"pages/users/reference/iaso_web/user_guide.html#teams","title":"Teams","text":"The notion of Teams in IASO is used mainly for the Planning feature. It allows to organize Users in Team hierarchies and assign data collection activities to the relevant geographies as needed for the Planning purposes.
There are two types of Teams:
Create a Team
From the menu, access Admin > Teams. Click on \u201cCreate\u201d
Fill out the below fields:
You can then use the gear or bin icon on the main page to edit or delete Team(s) as needed.
"},{"location":"pages/users/reference/interop/interop.html","title":"Interoperability roadmap 2023-2024","text":""},{"location":"pages/users/reference/interop/interop.html#introduction","title":"Introduction","text":"Iaso, whose name was taken from the name of a Greek goddess for health, has initially been developed to support national health programmes in their data collection and organization of geographical information in remote and low connectivity areas. Since then, it has also been used in other fields, such as education and environmental projects.
Iaso builds on three essential concepts users, forms (in XLSForm format) and org units (e.g. districts and facilities) with a focus on structuring data collection along geographic lines to allow for splitting responsibility geographically, as is commonly done in health programs. This allows to decentralize monitoring, validation and team management. It also allows to have out of the box completeness reporting for data collection.
Iaso has been recognized as a Global Good for Health by Digital Square. As such, Bluesquare acknowledges the importance of making Iaso as interoperable as possible in order to facilitate data exchange within the global digital health ecosystem such as the Open Health Information Exchange (OpenHIE) community.
"},{"location":"pages/users/reference/interop/interop.html#open-standards-already-implemented","title":"Open standards already implemented","text":"The below below standard technologies are already being used by Iaso today:
Data collection: XLSForm, CSV
Geographical data: geopackage
Iaso data collection can be done through forms in the common XLSForm format used for example by ODK, and allows to import and export data to DHIS2 (thanks to a user-friendly mapping interface), which is not per se using open formats in general, but is a de facto standard in some health topics, DHIS2 being probably the most installed open source health information management system.
Iaso allows imports and exports of geographical data through the geopackage format (http://www.geopackage.org/) which is the relatively new golden standard for Geographical Information Systems.
"},{"location":"pages/users/reference/interop/interop.html#interoperability-roadmap","title":"Interoperability roadmap","text":"1. Short-term (by end of 2023)
Recently, we implemented case management features in Iaso, which is mainly the possibility to collect and store data about individuals. One goal will be to further develop the integration between Iaso and DHIS2 Tracker, to allow the import and export of data linked to individuals.
In the same context as above, Bluesquare would ensure that Iaso is compatible with the FHIR standard for health care data exchange. That said, Iaso is a generic data collection tool, and consequently we can\u2019t enforce that collected data always uses a predefined set of fields. Consequently, support of FHIR for case management would be made on a project basis, and where Bluesquare could help is by providing documentation of how to implement some parts of the FHIR standard.
On the other hand, Iaso is a very complete facility list management system and here, there is a very good opportunity to adopt OHIE facility registry standards. This will be studied by the end of the year and implemented if we can identify a project needing the feature.
2. Long-term (end of 2023 and beyond)
Iaso\u2019s code and general information is published on the dedicated Github repository that you can find here: https://github.com/BLSQ/iaso/wiki
Bluesquare has started to organize processes to ensure more easily accessible documentation about Iaso, that will benefit the open source health softwares community. An evolving user guide will be made available on https://readthedocs.org/, together with more technical documentation on new features. A high-level roadmap on next features will also be published and maintained.
To facilitate interoperability, we are in the process of publishing the api specification in the OpenAPI standard (the format used by the Swagger tool).
Iaso is growing more and more to be a planning system, e.g. for vaccination campaigns. We need to investigate if there are existing standards (outside of calendar standards like caldav) , especially in the OHIE specification that could be reused to expose our plannings to external systems.
There is a growing demand for Iaso to be able to handle logistics, in order to monitor stocks of certain health-related or other supplies, such as vaccines, mosquito nets in certain locations. If Iaso would further develop features in this field, Bluesquare will make sure to follow the openHIE \u201cLogistics Management Information System (LMIS)\u201d and \u201cProduct Catalogue\u201d components principles.
"},{"location":"fr/AWS-Deployment.html","title":"AWS Deployment","text":""},{"location":"fr/AWS-Deployment.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/index.html","title":"Bienvenue dans la documentation d'IASO","text":""},{"location":"fr/index.html#introduction-a-iaso","title":"Introduction \u00e0 IASO","text":"IASO est une plateforme innovante, open-source, bilingue (EN/FR) de collecte de donn\u00e9es \u00e0 fonctionnalit\u00e9s g\u00e9ospatiales avanc\u00e9es pour planifier, surveiller et \u00e9valuer les programmes de sant\u00e9, environnementaux ou sociaux dans les pays \u00e0 revenu faible et interm\u00e9diaire (PRFI). IASO est reconnu comme un Bien Public Num\u00e9rique par l'Alliance des Biens Publics Num\u00e9riques et figure parmi les logiciels Global Goods de Digital Square, t\u00e9moignant de son utilit\u00e9 publique.
IASO comprend :
En termes de fonctionnalit\u00e9s, IASO peut \u00eatre r\u00e9sum\u00e9 autour de quatre composantes principales qui sont interconnect\u00e9es et renforcent leurs capacit\u00e9s mutuelles :
Gestion des donn\u00e9es g\u00e9ospatiales (G\u00e9oregistre)
Collecte de donn\u00e9es g\u00e9o-structur\u00e9es
Microplanification
Entit\u00e9s - celles-ci peut consister en des individus (par exemple, les b\u00e9n\u00e9ficiaires de programmes de sant\u00e9) ou des objets physiques (par exemple, des lots de vaccins, des moustiquaires, etc.). Les workflows permettent de suivre les entit\u00e9s en ouvrant des formulaires sp\u00e9cifiques en fonction des r\u00e9ponses donn\u00e9es \u00e0 des formulaires pr\u00e9c\u00e9dents.
La plateforme a \u00e9t\u00e9 mise en \u0153uvre au B\u00e9nin, au Burkina Faso, au Burundi, au Cameroun, en R\u00e9publique Centrafricaine, en R\u00e9publique D\u00e9mocratique du Congo, en Ha\u00efti, en C\u00f4te d'Ivoire, au Mali, au Niger, au Nig\u00e9ria et en Ouganda. Elle est le registre g\u00e9ographique officiel au Burkina Faso depuis 2023. IASO a \u00e9galement \u00e9t\u00e9 mise en \u0153uvre au niveau r\u00e9gional (r\u00e9gion AFRO) pour soutenir l'Initiative Mondiale pour l'Eradication de la Polio gr\u00e2ce \u00e0 ses capacit\u00e9s de registres g\u00e9ospatiaux et d'\u00e9tablissements de sant\u00e9.
"},{"location":"fr/index.html#technologies-utilisees","title":"Technologies utilis\u00e9es","text":"IASO est compos\u00e9e d'une application Android white labeled utilisant Java/Kotlin, r\u00e9utilisant une grande partie des projets ODK, et d'une plateforme web programm\u00e9e en Python/GeoDjango sur PostGIS. Le frontend est principalement en React/Leaflet. L'API est impl\u00e9ment\u00e9e via Django Rest Framework, et toutes les donn\u00e9es sont stock\u00e9es dans PostgreSQL ou le r\u00e9pertoire media/. L'un des objectifs est la facilit\u00e9 d'int\u00e9gration avec d'autres plateformes. Nous avons d\u00e9j\u00e0 des imports et exports en formats CSV et GeoPackage, et nous visons une int\u00e9gration facile avec OSM.
L'application mobile pour Android permet la soumission de formulaires et la cr\u00e9ation d'unit\u00e9s d'organisation. Les formulaires peuvent \u00e9galement \u00eatre remplis dans une interface web via le service compagnon Enketo.
"},{"location":"fr/pages/dev/how_to/add_new_permission/add_new_permission.html","title":"Add new permission","text":""},{"location":"fr/pages/dev/how_to/add_new_permission/add_new_permission.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/contribute/contribute.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/create_entities_in_web_ui/create_entities_in_web_ui.html","title":"Create entities in web ui","text":""},{"location":"fr/pages/dev/how_to/create_entities_in_web_ui/create_entities_in_web_ui.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/create_registry_in_web_ui/create_registry_in_web_ui.html","title":"Create registry in web ui","text":""},{"location":"fr/pages/dev/how_to/create_registry_in_web_ui/create_registry_in_web_ui.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/debug_backend/debug_backend.html","title":"Debug backend","text":""},{"location":"fr/pages/dev/how_to/debug_backend/debug_backend.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/deploy/deploy.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/exclude_featureflag_related_module/exclude_featureflag_related_module.html","title":"Exclude featureflag related module","text":""},{"location":"fr/pages/dev/how_to/exclude_featureflag_related_module/exclude_featureflag_related_module.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/manually_test_enketo/manually_test_enketo.html","title":"Manually test enketo","text":""},{"location":"fr/pages/dev/how_to/manually_test_enketo/manually_test_enketo.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/rebuild_front/rebuild_front.html","title":"Rebuild front","text":""},{"location":"fr/pages/dev/how_to/rebuild_front/rebuild_front.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/run_docs_locally/run_docs_locally.html","title":"Run docs locally","text":""},{"location":"fr/pages/dev/how_to/run_docs_locally/run_docs_locally.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/setup_dev_env/setup_dev_env.html","title":"Setup dev env","text":""},{"location":"fr/pages/dev/how_to/setup_dev_env/setup_dev_env.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/setup_dev_env/setuper.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/use_iaso_apis/use_iaso_apis.html","title":"Use iaso apis","text":""},{"location":"fr/pages/dev/how_to/use_iaso_apis/use_iaso_apis.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html","title":"Write visit on nfc","text":""},{"location":"fr/pages/dev/how_to/write_visit_on_nfc/write_visit_on_nfc.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/API/org_unit_change_request_configuration.html","title":"Org unit change request configuration","text":""},{"location":"fr/pages/dev/reference/API/org_unit_change_request_configuration.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/API/org_unit_registry.html","title":"Org unit registry","text":""},{"location":"fr/pages/dev/reference/API/org_unit_registry.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/API/payments/payments.html","title":"Payments","text":""},{"location":"fr/pages/dev/reference/API/payments/payments.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/campaigns/subactivities.html","title":"Subactivities","text":""},{"location":"fr/pages/dev/reference/campaigns/subactivities.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/data_model_glossary/data_model_glossary.html","title":"Data model glossary","text":""},{"location":"fr/pages/dev/reference/data_model_glossary/data_model_glossary.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/doc_setup/doc_setup.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/docker/docker.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/entities_in_iaso/entities_in_iaso.html","title":"Entities in iaso","text":""},{"location":"fr/pages/dev/reference/entities_in_iaso/entities_in_iaso.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/env_variables/env_variables.html","title":"Env variables","text":""},{"location":"fr/pages/dev/reference/env_variables/env_variables.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/front-end_reference/front-end_reference.html","title":"Front end reference","text":""},{"location":"fr/pages/dev/reference/front-end_reference/front-end_reference.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/guidelines/back-end/back-end.html","title":"Back end","text":""},{"location":"fr/pages/dev/reference/guidelines/back-end/back-end.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/guidelines/front-end/front-end.html","title":"Front end","text":""},{"location":"fr/pages/dev/reference/guidelines/front-end/front-end.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/guidelines/git/git.html","title":"Git","text":""},{"location":"fr/pages/dev/reference/guidelines/git/git.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/public_registry/public_registry.html","title":"Public registry","text":""},{"location":"fr/pages/dev/reference/public_registry/public_registry.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html","title":"SQL Dashboard feature","text":""},{"location":"fr/pages/dev/reference/sql_dashboard/SQL_Dashboard_feature.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/dev/reference/vector_control/vector_control.html","title":"Vector control","text":""},{"location":"fr/pages/dev/reference/vector_control/vector_control.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/FAQ/faq.html","title":"Faq","text":""},{"location":"fr/pages/users/FAQ/faq.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/how_to/convert_docx_to_md/convert_docx_to_md.html","title":"Convert docx to md","text":""},{"location":"fr/pages/users/how_to/convert_docx_to_md/convert_docx_to_md.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html","title":"Create new documentation page","text":""},{"location":"fr/pages/users/how_to/create_new_documentation_page/create_new_documentation_page.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/how_to/edit_documentation/edit_documentation.html","title":"Edit documentation","text":""},{"location":"fr/pages/users/how_to/edit_documentation/edit_documentation.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html","title":"Setup an empty iaso account","text":""},{"location":"fr/pages/users/how_to/setup_an_empty_iaso_account/setup_an_empty_iaso_account.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html","title":"Setup dhis2 login in iaso","text":""},{"location":"fr/pages/users/how_to/setup_dhis2_login_in_iaso/setup_dhis2_login_in_iaso.html#traduction-francaise-bientot-disponible","title":"Traduction fran\u00e7aise bient\u00f4t disponible","text":""},{"location":"fr/pages/users/reference/iaso_concepts/iaso_concepts.html#formulaires-de-collecte-de-donnees-xls","title":"Formulaires de collecte de donn\u00e9es XLS","text":"La collecte de donn\u00e9es implique des questions, et pour organiser ces questions, des formulaires de collecte de donn\u00e9es. Ils se composent de questions pour lesquelles on souhaite collecter des r\u00e9ponses, tout en sp\u00e9cifiant des options (obligatoires ou non, saut d'une question en fonction d'une r\u00e9ponse pr\u00e9c\u00e9dente, etc.).\\ IASO utilise les XLS forms pour ses questionnaires, qui sont donc pr\u00e9d\u00e9finis \u00e0 l'aide d'un fichier Excel.
Dans IASO, les formulaires de collecte de donn\u00e9es sont versionn\u00e9s, ce qui signifie qu'\u00e0 chaque fois qu'une nouvelle version est cr\u00e9\u00e9e, l'ancienne version est conserv\u00e9e et reste disponible dans le syst\u00e8me. Il est possible d'y revenir ais\u00e9ment.
"},{"location":"fr/pages/users/reference/iaso_concepts/iaso_concepts.html#unites-dorganisation","title":"Unit\u00e9s d'organisation","text":"IASO utilise la notion d'Unit\u00e9s Od'organisation (Org unit ou OU) pour g\u00e9rer les donn\u00e9es g\u00e9ographiques.\\ Les types d'unit\u00e9 d'organisation (OUT) repr\u00e9sentent les niveaux dans la hi\u00e9rarchie.
"},{"location":"fr/pages/users/reference/iaso_concepts/iaso_concepts.html#exemple","title":"Exemple :","text":"Les unit\u00e9s d'organisation sont class\u00e9es dans la pyramide selon un parent et un ou plusieurs enfants (sauf les parents au sommet et les enfants au bas de la hi\u00e9rarchie).
"},{"location":"fr/pages/users/reference/iaso_concepts/iaso_concepts.html#exemple-ci-dessous","title":"Exemple ci-dessous :","text":"La collecte de donn\u00e9es dans IASO est structur\u00e9e selon la hi\u00e9rarchie d\u00e9finie, et chaque utilisateur doit explicitement s\u00e9lectionner une unit\u00e9 d'organisation avant d'ouvrir le questionnaire et de r\u00e9pondre aux questions. Cela garantit que les donn\u00e9es collect\u00e9es sont correctement associ\u00e9es \u00e0 la g\u00e9ographie pertinente.
"},{"location":"fr/pages/users/reference/iaso_concepts/iaso_concepts.html#projets","title":"Projets","text":"Dans IASO, un Projet est li\u00e9 \u00e0 une instance d'application mobile, avec son propre ID d'application (ou \"App ID\"). Au sein d'un m\u00eame compte IASO, vous pouvez avoir un ou plusieurs Projet(s) avec diff\u00e9rentes options de fonctionnalit\u00e9s.\\ Les utilisateurs peuvent \u00eatre li\u00e9s \u00e0 un ou plusieurs Projet(s).
"},{"location":"fr/pages/users/reference/iaso_concepts/iaso_concepts.html#a-savoir","title":"\u00c0 savoir :","text":"