From b2c5a4d6ef91604ab49c432e1120696c2dce3ad5 Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Tue, 14 Oct 2025 14:54:41 -0400 Subject: [PATCH 1/2] Move everything over from legal-tech-class Co-authored-by: Quinten Steenhuis --- .../combining-interviews.md | 42 + .../installing-production-app.md | 135 +++ .../rebuild-lightsail-instance.md | 264 ++++++ .../run-docassemble-docker-vscode.md | 230 +++++ docs/admin-guide-docassemble/setup-server.md | 423 +++++++++ .../updates-and-maintenance.md | 317 +++++++ .../basic-troubleshooting.md | 212 +++++ .../controlling-interview-order.md | 504 +++++++++++ docs/docassemble_intro/hello-world.md | 128 +++ .../docassemble_intro/img/error_mandatory.png | Bin 0 -> 25369 bytes .../img/error_missing_question.png | Bin 0 -> 9744 bytes .../img/error_missing_template.png | Bin 0 -> 9590 bytes .../img/error_reading_yaml_file.png | Bin 0 -> 17395 bytes .../img/show_paragraph_marks.png | Bin 0 -> 10619 bytes .../introduction-to-docassemble.md | 83 ++ docs/docassemble_intro/jinja2.md | 82 ++ docs/docassemble_intro/logic.md | 267 ++++++ docs/docassemble_intro/mako.md | 61 ++ docs/docassemble_intro/markdown.md | 39 + .../object-oriented-programming.md | 370 ++++++++ .../practical-guide-docassemble.md | 56 ++ docs/docassemble_intro/python.md | 239 +++++ .../docassemble_intro/repeated-information.md | 821 ++++++++++++++++++ docs/docassemble_intro/theming-docassemble.md | 217 +++++ .../translating-interviews.md | 186 ++++ docs/docassemble_intro/working-with-docx.md | 157 ++++ docs/docassemble_intro/yaml.md | 142 +++ sidebars.js | 52 ++ 28 files changed, 5027 insertions(+) create mode 100644 docs/admin-guide-docassemble/combining-interviews.md create mode 100644 docs/admin-guide-docassemble/installing-production-app.md create mode 100644 docs/admin-guide-docassemble/rebuild-lightsail-instance.md create mode 100644 docs/admin-guide-docassemble/run-docassemble-docker-vscode.md create mode 100644 docs/admin-guide-docassemble/setup-server.md create mode 100644 docs/admin-guide-docassemble/updates-and-maintenance.md create mode 100644 docs/docassemble_intro/basic-troubleshooting.md create mode 100644 docs/docassemble_intro/controlling-interview-order.md create mode 100644 docs/docassemble_intro/hello-world.md create mode 100644 docs/docassemble_intro/img/error_mandatory.png create mode 100644 docs/docassemble_intro/img/error_missing_question.png create mode 100644 docs/docassemble_intro/img/error_missing_template.png create mode 100644 docs/docassemble_intro/img/error_reading_yaml_file.png create mode 100644 docs/docassemble_intro/img/show_paragraph_marks.png create mode 100644 docs/docassemble_intro/introduction-to-docassemble.md create mode 100644 docs/docassemble_intro/jinja2.md create mode 100644 docs/docassemble_intro/logic.md create mode 100644 docs/docassemble_intro/mako.md create mode 100644 docs/docassemble_intro/markdown.md create mode 100644 docs/docassemble_intro/object-oriented-programming.md create mode 100644 docs/docassemble_intro/practical-guide-docassemble.md create mode 100644 docs/docassemble_intro/python.md create mode 100644 docs/docassemble_intro/repeated-information.md create mode 100644 docs/docassemble_intro/theming-docassemble.md create mode 100644 docs/docassemble_intro/translating-interviews.md create mode 100644 docs/docassemble_intro/working-with-docx.md create mode 100644 docs/docassemble_intro/yaml.md diff --git a/docs/admin-guide-docassemble/combining-interviews.md b/docs/admin-guide-docassemble/combining-interviews.md new file mode 100644 index 000000000..180e03aea --- /dev/null +++ b/docs/admin-guide-docassemble/combining-interviews.md @@ -0,0 +1,42 @@ +--- +slug: combining-interviews +title: Combining multiple interviews +sidebar_label: Combining interviews +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +## Overview + +1. linking +1. using an umbrella structure +1. designing a generic interview + +## Linking + +1. using `interview_url()` + +Example: + +* [Dashboard Menu](https://github.com/SuffolkLITLab/docassemble-ALDashboard/blob/main/docassemble/ALDashboard/data/questions/menu.yml) + +## Using an umbrella interview + + + +### Preparing the included interview + +1. remove all mandatory blocks in the child interview +1. add a "named block" to the interview order block in the child interview +1. give each attachment a `variable name` if it does not already have one + +### Reference the included interview in the umbrella interview + +1. add a reference to the `named block` in the umbrella interview +1. add the new `variable name` to the download screen using `attachment code` or + `ALDocumentBundle` + +## Read more + +1. https://docassemble.org/docs/logic.html#multiple%20interviews +1. [Naming your modular interview files](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/coding_style_guide/yaml#use-clear-filenames-for-modular-interview-files) \ No newline at end of file diff --git a/docs/admin-guide-docassemble/installing-production-app.md b/docs/admin-guide-docassemble/installing-production-app.md new file mode 100644 index 000000000..15f9461bb --- /dev/null +++ b/docs/admin-guide-docassemble/installing-production-app.md @@ -0,0 +1,135 @@ +--- +slug: installing-production-app +title: Installing a docassemble app on your production server +sidebar_label: Installing a docassemble app on your production server +--- + +Installing your app involves three basic steps: + +1. [Putting the app into a + package.](https://docassemble.org/docs/packages.html#playground). It's best + to store the package on Github. +2. Installing the app system-wide. +3. Creating a shortcut `alias` to the app so that you can view it on the + "Available interviews" /list menu and run it via /start/`alias`. + +## Quick start + +Before installing the package, you will need: + +1. The URL to the package on GitHub. +1. The name of the interview YAML file. +1. The desired "alias" or shortcut name for the package. + +**The interview author should be able to provide all three pieces of information +to the server administrator before asking for the app to be installed.** + +## Installing the app system-wide + +To install a package, visit the "Package Management" page on your docassemble + server (you can reach it via Menu | Package Management, or the /updatepackage + page on your server. +) + +Copy the Github link to your package into the "Github URL" input. It should look +something like this: `https://github.com/gbls/docassemble-MAEvictionDefense` +(without any trailing `/`). + +Now, scroll down and click on the large "Update" button. Although this is marked +"Update," it also serves to install a package for the first time. + +## Running the installed package + +Now that you've installed the package, how do you run it? You will need to +identify two things: + +1. The internal/Python name of the package that corresponds to your package from + GitHub. +1. The name of the YAML file(s) that contains the runnable logic of the package. + +The internal name of the package is very similar to the name of the GitHub +repository, except that you replace the `-` with a `.`. For example, the package +https://github.com/gbls/docassemble-MAEvictionDefense becomes +`docassemble.MAEvictionDefense`. Package names are case sensitive. If you used +the docassemble playground to create your package, it will always start with +"docassemble" as the beginning of the package name. + +The YAML file name is simply the name that the file has in your playground. +If this is someone else's package, you will need to do a small amount of dective +work. + +Visit the GitHub page for the package. Each docassemble package has the same +folder structure. Navigate by clicking through the folders as follows, replacing +"PackageName" with the name of your package. + +``` +docassemble + +---[PackageName] + +----data + +---questions +``` + +For example, for the MAEvictionDefense package, the folder contents +look like this: +https://github.com/GBLS/docassemble-MAEvictionDefense/tree/master/docassemble/MAEvictionDefense/data/questions + +For some packages, there is only one YAML file. If there is more than one, you +can either ask the package author which one should be run, or click through a +few and pick the likeliest candidate. For the MAEvictionDefense package, the +main interview file is named "eviction.yml". + +For a package named `docassemble.MAEvictionDefense` and a YAML file named +`eviction.yml`, the link to run the interview is +`https://[docassemble.example.com]/run/MAEvictionDefense/eviction`. + +## Creating a permanent shortcut link to the package + +If you want, you can add a [customizable, short +alias](https://docassemble.org/docs/config.html#dispatch) for the interview. + +This adds the interview to the /list URL on the server, the "Available +interviews" menu option, and allows you to advertise a nicer URL, like +`https://docassemble.example.com/start/alias`. + +This gives you a lot of flexibility to repackage your app later, which +makes it a good idea if you ever want to share the link on a different +website. + +You will use the same information needed above: the **package name** +and the **YAML filename**. + +Visit the docassemble configuration page (/config) on your server. + +Scroll down and look for an existing `dispatch` section in your +configuration file. If you don't see one, create a new one that looks +like this: + +``` +dispatch: + alias: docassemble.MyPackageName:data/questions/interview_name.yml +``` + +Replace "MyPackageName" and "interview_name.yml" with the package name +and the YAML filename for your package, respectively. Now, when someone +visits "https://docassemble.example.com/start/alias" they will be redirected to the +"interview_name.yml" interview. + +For example, for the MAEvictionDefense package, the configuration looks like +this: + +``` +dispatch: + eviction: docassemble.MAEvictionDefense:data/questions/eviction.yml +``` + +And the interview can be run via `https://interviews.gbls.org/start/eviction`. + +## Updating an app that is already installed + +If the app has already been installed on the server, you do not need to get the +GitHub URL again. Just scroll down the /updatepackage page until you see the +name of the package (remember, it will be docassemble.PackageName). Then click the +"update" button next to the package. + +If you do not see the "update" button but the package is listed, then use the +GitHub URL as described above. \ No newline at end of file diff --git a/docs/admin-guide-docassemble/rebuild-lightsail-instance.md b/docs/admin-guide-docassemble/rebuild-lightsail-instance.md new file mode 100644 index 000000000..5698370de --- /dev/null +++ b/docs/admin-guide-docassemble/rebuild-lightsail-instance.md @@ -0,0 +1,264 @@ +--- +slug: rebuild-lightsail-instance +title: Rebuilding your AWS Lightsail instance +sidebar_label: Rebuilding your AWS Lightsail instance +--- + +## Rebuilding your Docassemble server + +Periodically, a new version of Ubuntu that runs in your Lightsail container +will be available. A new [Long-term Support (LTS)](https://wiki.ubuntu.com/LTS) +version is released every two years. You don't need to use the latest version, but +for compatibility, security, and performance reasons, it is recommended to stay +within a few major versions. + +Upgrading the Lightsail container also gives you the opportunity to increase +the specifications. For example, you can upgrade to a container with more RAM, +CPUs, or storage space. + +The good news is that you can update the Ubuntu software in your Lightsail +container and connect it to your existing Docassemble database. This saves time and +minimizes changes. + +## Before you begin + +This process involves backing up files, creating a new AWS Lightsail instance, and +connecting the new instance to your database. It will create a new Docker container. +The new Docker container will be running the latest version of Docassemble and other +installed packages. The process usually takes around 1 hour, so do it when you can +afford your Docassemble server to be unavailable for a while. + +This guide assumes you already have an existing, functional Docassemble server with +the following setup: + - AWS Lightsail instance + - AWS S3 data storage + - Docker container + - Docassemble web app + +It also assumes you can access all those items through AWS, including SSH access to +the Lightsail instance. See +[Installing a docassemble server](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/setup-server) +for instructions on setting up a server. + +It is also important that you make note of your existing admin and developer accounts +on the server you are going to rebuild. + +## Stop the docassemble server and copy the env.list file +Sign into AWS, go to the Lightsail instance that you want to rebuild. Connect using +SSH. You can also use another SSH client. + +### Stop the docker container +Once you are connected, stop the docker container with the following command. + +```bash +docker stop -t 600 +``` + +Instead of `` hit the TAB key. This should autofill the name of the currently running +Docker container. Make sure this finishes before you continue. + +### Copy the env.list file +Then retrieve the contents of your env.list file. This has sensitive information such +as your organization's private API keys. The env.list file rarely changes after +initial setup, but it is a good idea to copy the latest version. + +Enter the following command to see the contents of the env.list file: + +```bash +cat env.list +``` + +Copy the contents of that file and paste into a text file on your local computer. Save this +text file for now. You will be copy/pasting it onto the same file on the rebuilt server. + +### Shut down the Lightsail instance +Finally, shut down the Lightsail instance. First, to properly shut down Ubuntu, enter +this command: + +```bash +sudo shutdown now +``` + +When complete, in AWS, go to the Lightsail instance and click **Stop**. + +## Back up S3 files + +You can now make a copy of the Docassemble files that are stored in your S3 bucket. Most +of the time you won't need to use these backups. It's reassuring to have a backup copy. + +Sign into AWS, and go to your Amazon S3 Buckets. Click on the server you want to upgrade. +One at a time, select and download the following: + + - config.yml + - redis.rdb + - docassemble (found inside the /postgres/ folder) + + +## Make a new Lightsail instance + +In AWS Lightsail, you can make a new instance by following these steps. + + - Click **Create instance** to make a new instance. + - Be sure to match the geographical zone where old server is located. + - Select the platform: Unix/Linux. + - Select a blueprint: Under the 'Operating System (OS) only' tab, select Ubuntu 22.04 LTS (or the latest version). + - Choose your instance plan: Most new Docassemble instances use the 4GB Memory option. + - Give your server a name under Identify your instance. This can't be the same as your existing instance. +You might want to include the Ubuntu version or year. It is helpful to include something +like "Dev" or "Prod" to keep these types of severs distinct. For example, you could name the +instance: Dev-easyforms-ilao-ubuntu-22.04. + - Click **Create instance** (at the bottom of the screen). + +## Set up networking on the new instance +To access the new server using the same IP and domain name as the old server, you will +need to change Networking on the new instance to match old instance. + +On the old instance: + + - Click **Networking** + - Under IPv4 click **IP** + - Detach the Public IP address + +You should not have to write down the IP address. AWS will remember it for use with your +account's Lightsail containers. + +On the new instance: + + - Click **IPv4** + - In PUBLIC IP, attach the Public IP address (the one you just detached) + - In Firewall, Add the below rule for https then click **Create** + - Application: HTTPS + - Protocol: TCP + - Port or range: 443 + - Restrict to IP address box Unchecked + - Keep Duplicate rule for IPv6 selected + - Keep IPv6 turned on + +## Follow instructions on Installing a docassemble server + +Most of the remaining steps are the same ones used in the [Finish installation on your Lightsail instance over +SSH](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/setup-server#finish-installation-on-your-lightsail-instance-over-ssh) +section of **Installing a docassemble server**. + +Note: When entering the Linux commands in those instructions, it works best to enter them one +at a time when copy/pasting. For example, this command: + +```bash +sudo apt-get install \ + ca-certificates \ + curl \ + gnupg \ + lsb-release +``` + +Can be converted to: + +```bash +sudo apt-get install ca-certificates curl gnupg lsb-release +``` + +It is important to wait for each command to complete. If there are prompts during the +process, it is okay to accept the defaults. Here are the steps listed individually. + +### Update the server (follow existing instructions) + +Commands from this section: + +```bash +sudo apt update + +sudo apt upgrade -u -y +``` + +### Install docker (follow existing instructions) + +Commands from this section: + +```bash +sudo apt-get install ca-certificates curl gnupg lsb-release + +sudo mkdir -p /etc/apt/keyrings + +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +sudo apt-get update + +sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin +``` + +### Add your user account to the Docker group (follow existing instructions) + +Commands from this section: + +```bash +sudo usermod -aG docker $USER + +newgrp docker +``` + +### Set up a Linux swap file (follow existing instructions) + +Commands from this section: + +```bash +sudo fallocate -l 10G /swapfile + +sudo chmod 600 /swapfile + +sudo mkswap /swapfile + +sudo swapon /swapfile + +echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab +``` + +### Make an env.list file + +This step is similar to the existing 'Create an env.list file' instructions. Using the +contents of your old env.list file, copy/paste the text and save it as a new env.list file. + +Copy the text from your env.list file, then follow these commands: + +```bash +nano env.list + +[shift-insert or right mouse click to paste] +[CTRL-O, enter, CTRL-X to save and exit the Nano editor] +``` + +### Start Docker (follow existing instructions) + +Use this command to start the new Docker container: + +```bash +docker run -d -p 443:443 -p 80:80 --restart always --env-file env.list --stop-timeout 600 jhpyle/docassemble +``` + +Sit back and wait. This cannot be overstated. It can take 10 to 45 minutes or more. + +If you are getting curious/anxious, you can monitor the Docker install progress with +this command: + +```bash +docker exec -ti $(docker ps --format '{{.Names}}') sh -c "tail -f /var/log/supervisor/initialize-stderr*" +``` + +Tip: Once Docker has started, you can hit the TAB key instead of typing `$(docker ps +--format '{{.Names}}')` in the above command. That will enter the container name. Be +sure to finish the command with `sh -c "tail -f /var/log/supervisor/initialize-stderr*"`. + +To exit the log, hit CTRL-C. + + +### Final steps + +Once the server is running, it is recommended to increase timeout period of nginx. This +needs to be done inside docker container. Follow [these instructions](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/installation#increase-nginx-timeouts-to-5-minutes). + +If you want to receive email alerts when the new AWS Lightsail instance is working hard, +set up AWS Lightsail alerts - CPU utilization. A threshold of over 60% CPU utilization 2 +times in 10 minutes might be useful. Adjust accordingly. + +If everything looks good, after a few days, you can delete the old Lightsail instance. diff --git a/docs/admin-guide-docassemble/run-docassemble-docker-vscode.md b/docs/admin-guide-docassemble/run-docassemble-docker-vscode.md new file mode 100644 index 000000000..ac2eb9c9c --- /dev/null +++ b/docs/admin-guide-docassemble/run-docassemble-docker-vscode.md @@ -0,0 +1,230 @@ +--- +slug: run-docassemble-docker-vscode +title: Setup & Run Docassemble with Docker and VS Code +sidebar_label: Docassemble with Docker and VS Code +--- + +Author: [Dele Omotosho](https://github.com/deletosh) + +In this section, we will setup our system to write and run docassemble without using the playground or a server. + +This allows us to use a regular text editor (we will use Visual Studio Code), save our work in a way that allows us to collaborate on interviews with colleagues. + +A big bonus too is, get an easier way to track MS docx templates next to our interview files docassemble. + +At the end of this guide, you will design an interview this way: + +![Designing docassemble interview in VS Code](https://i.imgur.com/aaoZL9h.gif) + +This guide has 6 sections: +- **Install Windows Subsystem for Linux (WSL 2)** (skip if you're on Mac or Linux) +- **Install Docker** +- **Install GitHub and Git for file control and collaboration** +- **Install and configure Visual Studio Code (VS Code)** +- **Install Python and docassemble command-line tool** +- **Write and collaborate on your docassemble interview** + +Let's jump in. + +## Install Windows Subsystem for Linux (WSL 2) + +- Open Command Prompt as an Administrator ![](https://i.imgur.com/1g0fyiS.gif) +- In your command prompt, type `wsl --install -d Ubuntu-16.04` and complete + this installation +![](https://i.imgur.com/hUsbNKM.gif) + +- At the end of this process, it asks you to create your credentials + ![](https://i.imgur.com/HWtxfbN.gif) + +- Now you have a Linux (Ubuntu 16.04) operating system installed inside + windows. + +- To run this later, from the start menu, type **Ubuntu 16.04** and open it + (it will look like the windows command line) + ![](https://i.imgur.com/kAmYdR7.gif) + +## Install Docker +Before continuing with this, confirm your systems [meets the minimum requirement to run docker](https://docs.docker.com/desktop/install/windows-install/#system-requirements). + +### Docker Installation +- Go to [https://docs.docker.com/desktop/install/windows-install](https://docs.docker.com/desktop/install/windows-install) + +- Click **Download Desktop for Window** + +- Run the downloaded installation (leave all the default settings) + ![](https://i.imgur.com/HRB11zm.gif) + +- If you are on MacOS, install the appropriate version of Docker instead of the Windows version. + +- Follow through the installation. When it's complete, restart your computer if you are using Windows. + +### Confirm docker works with WSL 2 + +- Open docker and and confirm you the **Use the WSL 2 based engine** is + checked. This step can be skipped on MacOS. ![](https://i.imgur.com/BxkFGO4.gif) + +- Open your terminal (Ubuntu 16.04) or your MacOS Terminal and type `docker -v`, see the proper + docker version in the terminal (Docker needs to be running for you to try + this). ![](https://i.imgur.com/wv0mNt7.png) + +## Install GitHub and Git for file control and collaboration + +- Go to https://desktop.github.com to download GitHub Desktop. + +- Run the downloaded file, when the installation is complete you should see this screen: ![](https://i.imgur.com/scIdBqU.png) + +You have 2 options: + +If you don't have a GitHub account, click **Create your free account** or [do it here](https://github.com/signup), then continue to the next step. + +With your new (or existing) GitHub account, click the **Sign in to GitHub.com**. You will be redirected to a new page to login + +When that completed, it brings us back to the Git configuration screen: + +![](https://i.imgur.com/p3gCEGD.png) + +## Install and Configure Visual Studio Code (VS Code) + +- Go to [https://code.visualstudio.com](https://code.visualstudio.com) and hit the "Download for X" button, + the "X" will be the name of your operating system. + +- Open the downloaded file and follow through there instructions. Here's a + step-by-step for [in-depth guide on configuring VS Code for legal + automation](https://deletosh.com/connect-vscode-github) + +- In MacOS, there is an additional step to get `code` working from the command line. Per [Visual Studio's Documentation](https://code.visualstudio.com/docs/setup/mac#_launching-from-the-command-line), Open the **Command Palette** (`Cmd+Shift+P`) and type `shell command` to find the `Shell Commmand: Install 'code' command in PATH`. Run this command. + +### Configure VS Code to use WSL 2 +Skip this step on MacOS +- Open VS Code's preferences ![](https://i.imgur.com/oHuFEuT.gif) +- Open the JSON settings and add the following to the last line of the existing content on that file: `"terminal.integrated.shell.windows": "C:\\Windows\\System32\\wsl.exe"` and **File** \> **Save** + ![](https://i.imgur.com/LRiO87R.gif) + +(Notice: I added a comma before adding this line) + +### Test docker works inside VS Code +- With VS Code still open, click **Terminal** \> **New Terminal** +- In the terminal, type `docker -v` we should see the same result as we saw in WSL (Ubuntu 16.04) or your MacOS Terminal earlier. +![](https://i.imgur.com/LRiO87R.gif) + +## Install Python and docassemble command-line app +Docassemble is written in the Python programming language. To make docassemble work offline, we need to install Python. + +### Install Python on WSL +Skip this step on MacOS +- Open your WSL ![](https://i.imgur.com/kAmYdR7.gif) +- Type `sudo apt update && update` +![](https://i.imgur.com/O6eThhR.gif) +- When that completes type: `sudo apt install -y python3 python3-pip ipython3` + ![](https://i.imgur.com/x18BSTP.gif) +- In your WSL, type: `echo "alias python='/usr/bin/python3'" >> ~/. bashrc` (you get no response) +- Next, type: `echo "alias pip='/usr/bin/pip3'" >> ~/.bashrc` (you get no + response) +- Close and re-open your WSL + +### Install Python on MacOS +Skip if using Windows. +- Browse to [https://www.python.org/downloads/](https://www.python.org/downloads/) to find the appropriate version of Python. If you are using Apple Silicon, be sure to download the appropriate version. +- Install the downloaded version. +- In a terminal window, type `curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py`. This will download an install file for `python3-pip`. +- Then run `python3 get-pip.py`. Pip is now installed. + +### Install docassemble command line +- With your WSL still open, type: `pip install docassemblecli` + ![](https://i.imgur.com/hUjb4s9.gif) + +We've now installed all the tools that allows us to write and collaborate on +docassemble files offline. + +Let's write a sample interview. + +## Write and collaborate on your docassemble interview +This process assumes you will collaborate on your interview using version control like Git and GitHub. [The official documentation describes this flow](https://docassemble.org/docs/development.html#versioncontrol) + +We will use a sample GitHub interview package I've setup as a starter for your project. + +Here’s how: + +- Open GitHub desktop +- Click **Clone a repository from the Internet...** select **URL**, in the text box enter: **deletosh/docassemble-premier** then click **Clone**. + ![](https://i.imgur.com/tZEU7NF.gif) +- To edit the files look like, hit **Repository** > **Open in Visual Studio** + ![](https://i.imgur.com/OUTJCFu.gif) +- With our VS Code open, confirm we can run an interview on our computer. Click through **docassemble** > **template** > **questions** > **data** then open **sample.yml** + +![](https://i.imgur.com/AlMV6Ce.gif) + +- Open the terminal. Click **Terminal** \> **‌New Terminal** + ![](https://i.imgur.com/qmHkIBT.gif) +- Type `docker-compose up -d` This starts the docassemble server locally + (Note: this command needs a fast connection — it will take some time the first time you run it) + ![](https://i.imgur.com/GF8SmxY.gif) +- Open your browser and go to `http://localhost`. You might see this + ![](https://i.imgur.com/VaFLb62.png) +- After a few minutes, you should see Docassemble running. +- Sign in with the default password: + - username: admin@admin.com + - password: password +![](https://i.imgur.com/NMMUBUU.gif) + +(the first time you login, it will have you change your password) + +### Run your first offline docassemble + +Our main setup is now complete. Let's test our first interview. +First, go to Docassemble running on your system. In your browser, type **http://localhost**, in the navigation section, click **My Profile**. + +![](https://i.imgur.com/Wq4KE1X.png) + +On the next page, go to the **Other settings** \> **API keys** +![](https://i.imgur.com/JpBul6N.png) + +Click **‌Add a New API Key** and give it any name. Then click **Create**. + +Finally, copy the resulting key +![](https://i.imgur.com/FLTtjRn.gif) + +Now, go back into VS Code with the project we opened. + +Type `code ~/.docassemblecli`, this opens a new tab in VS Code. + +Paste: +``` +apikey: XXXXXXX +apiurl: http://localhost +name: localhost +``` + +Replace **XXXXXXX** with the API you created locally above +![]]() + +Paste the API code you created in the previous section in the **apikey**. Save the file or `Ctrl + S` + +Next, let's rename the **docassemble** \> **template** file to +**docassemble-premier** + +![](https://i.imgur.com/GSOfFNJ.gif) + +With the **sample.yml** file open, type: + +`dainstall --norestart --playground ../docassemble-premier` + +You should see **Installed**. + +You can now go back to the browser at `http://localhost`, then go to the **‌Playground** + +Let's run the code. + +![](https://i.imgur.com/aaoZL9h.gif) + +When we make changes to our files on VS Code, we need to re-run the `dainstall --norestart --playground ../docassemble-premier` in the terminal to see the changes reflected. + +## Points to have on mind while writing interviews locally + +here are some things you need to keep in mind with developing Docassemble +locally: + +- Every docassemble project is self-contained and you need to re-start Docker in each project. + +- When you start a new project, you need to add and re-create your API keys. **You cannot use the API key you've added before**. + diff --git a/docs/admin-guide-docassemble/setup-server.md b/docs/admin-guide-docassemble/setup-server.md new file mode 100644 index 000000000..31b41e7ce --- /dev/null +++ b/docs/admin-guide-docassemble/setup-server.md @@ -0,0 +1,423 @@ +--- +slug: setup-server +title: Installing a docassemble server +sidebar_label: Installing a docassemble server +--- + +# What this guide is + +There are a lot of [ways you can install +Docassemble](https://docassemble.org/docs/installation.html). This is one quick +path that will help you get started quickly without having to make your own +choices. + +1. Provision a full virtual machine in [AWS + Lightsail](https://aws.amazon.com/lightsail/). +2. Store a copy of backups and files on [AWS S3](https://aws.amazon.com/s3/) for safer and easier recovery +3. Use an env.list file that contains all of the startup configuration that I + keep a local copy of. + +Larger deployments might consider using a [multi-server configuration](https://docassemble.org/docs/scalability.html). +But for most deployments, it is cheaper and more reliable to scale vertically. An 8 GB instance on Lightsail, +which costs $40/month to run at this time, handles 10s of thousands of monthly sessions for the state of Massachusetts. + +It typically takes about **1 hour** to follow these steps from start to finish. + +## A video walkthrough of the full process + + + +## Create your AWS account + +[Follow Amazon's +instructions](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/) + +## Choose a DNS name + +Choose a short, readable name, like `apps.example.com`, where `example.com` +is a domain that you own. + +1. Use a subdomain of the domain you own like `apps.example.com`, not a base (second level) domain like `example.com` +1. Stick with the common subdomain names: `app`, `apps`, or `interviews` unless you have a good + reason to choose something different. +1. Avoid `www` as this is usually used for a landing page. + +### If you don't own a domain name yet + +If you don't own a domain name yet, it may be +simplest to register and manage it through [AWS +Lightsail](https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-domain-registration) + +## Reserve a static IPv4 address in Lightsail + +Follow the [AWS +Lightsail](https://lightsail.aws.amazon.com/ls/docs/en_us/articles/lightsail-create-static-ip) +instructions to create a static IP. Use the DNS name you chose earlier to label +the IP, with suffix to indicate that it is the IP. Like: `apps.example.com-IP`. + +## Create an `A` record that points to the reserved IP address in your DNS provider's website + +You now need a new `A` record that points your reserved IP address (like `127.0.0.1`) +to your chosen domain name (like `apps.example.com`) + +The steps for this depend on your DNS provider. + +Here are some basic instructions about adding an `A` record for common platforms: + +* [AWS Lightsail](https://lightsail.aws.amazon.com/ls/docs/en_us/articles/lightsail-how-to-create-dns-entry) +* [AWS Route 53](https://pantheon.io/docs/route53) +* [GoDaddy](https://www.godaddy.com/help/add-an-a-record-19238) + +If you need to, you can stop now and come back to the rest of the steps later. The rest will take about 30 minutes and should +be done all together. + +## Create your Lightsail server + +Create a [Lightsail +instance](https://lightsail.aws.amazon.com/ls/docs/en_us/articles/how-to-create-amazon-lightsail-instance-virtual-private-server-vps) + +Select these options on the "Create an instance" page: + +1. Select the closest "Instance location" or AWS region. (The default is probably fine if it is on your continent!) +1. Select an "OS only" blueprint of "Linux". +1. Select the latest long term support edition of **Ubuntu Server** (24.04 at this writing; avoid Amazon Linux) +1. Select at least the 4 GB of memory plan (at this writing, it costs **$20/month**) + +Label the new Lighsail instance with the DNS name you chose earlier. For +example: `apps.example.com`. + +## Enable HTTPS traffic on your Lightsail server + +1. Click on your Lightsail instance, and then the Networking tab. (Note: make sure +you click on the instance name first). +2. Scroll down to IPv4 Firewall. Click + Add rule to add a new rule. +3. Select "HTTPS" as the application, and then the green "create" button. + +This should also create the matching IPv6 rule automatically for you. + +## Create an S3 Bucket to match your new DNS name + +Follow the instructions to [create an AWS S3 +bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/create-bucket-overview.html) using the AWS console. + +You will make a new S3 bucket with these configuration options: + +1. Named the same as your DNS name. E.g., `apps.example.com` +2. Matches the AWS region of your Lightsail instance (e.g., us-east-1a for Virginia). +3. Use the default security policy, including `Block Public Access`. + +### Turn on S3 bucket versioning + +To improve your ability to recover from disasters, we recommend that you turn on bucket versioning. +This will keep a "shadow copy" of any deleted or modified files. + +[View AWS instructions](https://docs.aws.amazon.com/AmazonS3/latest/userguide/manage-versioning-examples.html) +(For simplicity, jump ahead to the instructions for "S3 console"--you can ignore the preamble). + +## Create an IAM user that has appropriate access to your S3 bucket + +Create a new user with the same name as your DNS name. E.g., +`apps.example.com`. Give it access to S3. + +[View Amazon's up to date instructions](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html), +or keep reading for our more specific instructions. Note: these may change over time. + +1. Visit the Identity Access and Management (IAM) console in AWS +1. Under "Access Management" in the left menu, click "Users" +1. Click "Create user" +1. Name the new user to match your server's name (e.g., apps.example.com) +1. Under permission options, choose "Attach policies directly". +1. Under "Permissions policies", type S3. Check the box next to AmazonS3FullAccess. The permissions boundary is not necessary. +1. Click next and don't modify anything on the Tags page. Review and then Create user. +1. Navigate back to "Users" under "Access Management" and select the one you just created. In the Summary, select the option to "Create Access Key". + +For the use case, select "Command Line Interface (CLI)". Click next and add a description if desired. Create access key. (You may have to +click through or ignore a warning). + +**Copy the Access key ID and Secret access key** to a safe place, such as an open +text editor window. This is your only chance to view the secret access key. But you +can always make a new one later. + +### A note about S3 permissions + +Your new user account has access to all of S3, not just the one bucket. If your AWS account is used for multiple +servers, it is best to limit its access. + +[View AWS instructions to limit access to one bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-policies-s3.html#iam-policy-ex0) + +## Finish installation on your Lightsail instance over SSH + +### Connect with SSH + +Next, use SSH to connect to your newly created server. + +#### Recommended: use Putty or another SSH client + +In Lightsail, download the default keypair for your region. Use this SSH key +to connect to your new Lightsail container: + +Using the Windows command line (with SSH installed), WSL or Apple Terminal: + +```bash +ssh ubuntu@apps.example.com -i /path/to/ssh_key +``` + +Or follow instructions for using +[Putty](https://lightsail.aws.amazon.com/ls/docs/en_us/articles/lightsail-how-to-set-up-putty-to-connect-using-ssh) + +### In a pinch: Use the Lightsail built-in SSH container + +You can also use the browser-based SSH client that is integrated into AWS +Lightsail. **Warning**: it is _really_ bad at copying and pasting. You should copy +and paste each line one at a time if you use the browser SSH container. + +Follow the AWS instructions to [connect to your new server with +SSH](https://lightsail.aws.amazon.com/ls/docs/en_us/articles/understanding-ssh-in-amazon-lightsail). + +### Update the server + +From the SSH console, type the following commands, hitting enter after one: + +```bash +sudo apt update +sudo apt upgrade -u -y +``` + +### Install docker + +Type the command below to install the latest docker from the official Docker Engine repository + +(Or follow the [latest instructions from Docker Engine](https://docs.docker.com/engine/install/ubuntu/)) + +```bash +sudo apt-get install ca-certificates curl +sudo install -m 0755 -d /etc/apt/keyrings +sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc +sudo chmod a+r /etc/apt/keyrings/docker.asc +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +sudo apt-get update +sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +``` + +### Add your user account to the Docker group + +Type the following commands in the SSH console: + +``` +sudo usermod -aG docker $USER +newgrp docker +``` + +### Setup a Linux swap file + +While the 4 GB RAM allocation is almost always enough, Docassemble can have odd +moments where the RAM usage spikes up quickly, which can crash the server. This +is somewhat more likely to be a problem on a development server. + +Quick fix: run the command below to create a 4 GB swap file. This provides +some free, virtual breathing room. + +```bash +sudo fallocate -l 4G /swapfile +sudo chmod 600 /swapfile +sudo mkswap /swapfile +sudo swapon /swapfile +echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab +``` + +Or, follow the [more complete instructions here](https://www.digitalocean.com/community/tutorials/how-to-add-swap-space-on-ubuntu-20-04). + + +### Create an env.list file + +In an editor of your choice, copy the following text: + +``` +DAHOSTNAME=apps.example.com +S3ENABLE=true +S3BUCKET=apps.example.com +S3ACCESSKEY= +S3SECRETACCESSKEY= +S3REGION=us-east-2 +TIMEZONE=America/New_York +USEHTTPS=true +USELETSENCRYPT=true +LETSENCRYPTEMAIL=apps@example.com +DAEXTRAFONTS=false +DAGOOGLEFONTS=false +``` + +Update the values as follows: + +* Under DAHOSTNAME, set it to your DNS name. +* Under S3 bucket, set it to your DNS name. +* Set the S3ACCESSKEY to your Access key ID. +* Set the S3SECRETACCESSKEY to your secret access key that you wrote down earlier. +* Set the S3 region to the appropriate region you selected. +* Set the Timezone to the matching ["tz database time + zone"](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) name + that you want to be the default on your server. This will be used in log files + and to set the time that daily scheduled cron scripts run. +* Set LETSENCRYPTEMAIL to an email of your choice. You will receive messages at this + address if your certificate is about to expire, but otherwise will not. +* In Docassemble version 1.8.14 and later, if DAEXTRAFONTS and DAGOOGLEFONTS are true they + will install those fonts on startup. These might be necessary for your interviews that do + DOCX to PDF conversion. + +Notice that in the Docker environment file, we use a lowercase `true`, not `True`. + +Create a new file on the server named env.list and copy the settings from your text editor into the file on the server. Here is one way you can do that: + +``` +nano env.list +[shift-insert or right mouse click to paste] +[CTRL+O, CTRL+X to save and exit the Nano editor] +``` + +Here is where using a 3rd party SSH console and not the Amazon SSH console is helpful. +Amazon's web-based SSH console is very bad at handling copy and paste. + +Save this env.list file locally somewhere secure. It is the one important file that will not be backed up to S3 if something goes wrong on your server. + +## Start Docker + +Start up your new docassemble server like this: + +```bash +docker run -d -p 443:443 -p 80:80 --restart unless-stopped --env-file env.list --stop-timeout 600 jhpyle/docassemble +``` + +### If you are running Docker on a server you own instead of AWS + +Make sure that you set up [persistent volumes](https://docassemble.org/docs/docker.html#persistent). +Both your startup command and your env.list file will look different. + +### Docker Compose + +For ease of future upgrades, you can save this command in a [Docker +Compose](https://docs.docker.com/compose/) file. Docker Compose allows you to reduce +the `docker run` command to a saved file for repeated use. `docker stop` and `docker start` +commands are not affected by using a Docker Compose file. + +Create a new file named docker-compose.yml on the server in the same directory as the +env.list file. To match the `docker run` command above, it should have this content +(lines beginning with a `#` are comments to help explain the commands): + +```yaml +services: + docassemble_container: + # The name of the service. You can reference this name for inter-service communication. + + image: jhpyle/docassemble + # Specifies the Docker image to use for this service. + # Update this line to pull a different version/tag of the image, e.g., 'jhpyle/docassemble:1.2.3'. + + restart: always + # This ensures the container is always restarted if it stops. + # Options include 'no', 'always', 'on-failure', or 'unless-stopped'. + + stop_grace_period: 600s + # The duration to wait before forcefully killing the container upon stopping. + # You can adjust this value based on your application's shutdown requirements. + + ports: + - '80:80' + # Maps port 80 on the host to port 80 on the container. + # To change the host port, modify the first number, e.g., '8080:80'. + + env_file: + - env.list + # Specifies a file containing environment variables. + # Update the filename if your environment variables are in a different file. + + volumes: + - dacerts:/usr/share/docassemble/certs + - dabackup:/usr/share/docassemble/backup + # Maps the `dacerts` and `dabackup` volumes to specific directories in the container. + # These volumes are defined in the `volumes` section and will be created automatically if they don't exist. + +volumes: + dacerts: + # Defines the `dacerts` volume. Docker Compose will automatically create it if it doesn't exist. + + dabackup: + # Defines the `dabackup` volume. Docker Compose will automatically create it if it doesn't exist. +``` + +If you are not using docker persistant volumes, everything including and after the +first reference to `volumes` can be removed. + +Different operating systems have different command lines for docker compose. To +check which you have, run `docker-compose --version` or `docker compose version`. +Only one of those will be successful. Depending on whether you need the hyphen to +run the service, you'll either run: + +``` +docker-compose up -d +``` + +or + +``` +docker compose up -d +``` + +The `-d` flag runs it in a detached mode meaning that everything is happening +in the background. If you want to run it with some of the supervisorctl logs +visible to you, you can run the command without the -d flag. You'll then know +when your server is up when you see the line that `nginx entered RUNNING +state`. That's the start of the expected webserver. A few seconds later you should +be able to access the web interface. You can hit CTRL+c to close the output. + +When you need to upgrade your server in the future, you can use the same Docker +Compose file to ensure you are setting the same flags in the new container. + +## Sit back and wait + +In a few minutes, your Docassemble server will be up and running. Visit the website at the DNS +name you chose, `https://apps.example.com`. Log in with the default username and password, +admin@admin.com/password. Change it to something more secure. + +### Monitor progress + +While the server starts up, you can run this command to monitor the container's progress, if +Docassemble is the only docker container in your server: + +```bash +docker exec -ti $(docker ps --format '{{.Names}}') sh -c "tail -f /var/log/supervisor/initialize-stderr*" +``` + +If you have more than one container, you can modify the command as follows: + +Note the name of the Docassemble container, which should be listed in the output of `docker ps` +In the command below, replace [YOUR CONTAINER NAME] with the name of your container. + +```bash +docker exec -ti [YOUR CONTAINER NAME] sh -c "tail -f /var/log/supervisor/initialize-stderr*" +``` + +Keep watching the output of this command until the install is finished, which will read +`initialize: Finished initializing`. You can hit CTRL+c to close the output. + +Watch out for the word "error" which may indicate that something went wrong. As long as it keeps +updating, you should just stay patient. + +# How many servers do I need? + +I recommend at least 2 servers: a production server and a development server. You may also want +a test server, for a total of 3. + +Production should have the most stable version of Docassemble. Development can test bleeding edge code. +Test should mirror Production as much as possible. + +If you want to configure load balancing, that is something I have not experimented with in Docassemble. Cost +will be much higher but it may be worthwhile. You still will want at least a test/dev and production environment. + +## Further reading + +For a lot more detail, review [Installation](https://docassemble.org/docs/installation.html) and also +the [configuration](https://docassemble.org/docs/config.html) pages. + diff --git a/docs/admin-guide-docassemble/updates-and-maintenance.md b/docs/admin-guide-docassemble/updates-and-maintenance.md new file mode 100644 index 000000000..ac1a8ea8e --- /dev/null +++ b/docs/admin-guide-docassemble/updates-and-maintenance.md @@ -0,0 +1,317 @@ +--- +slug: maintaining-docassemble +title: Securing and maintaining your server +sidebar_label: Securing and maintaining your server +--- + +## Securing Docassemble + +Docassemble has not had any major security incidents in its history. But the +higher profile your deployment, the more likely you are to have an individual +attack. Such attacks are hard to entirely prevent. The best way to keep +Docassemble secure is by upgrading on a regular basis. + +In addition: + +- Consider using [2-factor + authentication](https://docassemble.org/docs/config.html#mfa) on your + production server +- [Turn off the + Playground](https://docassemble.org/docs/config.html#enable%20playground) on + your production server +- Limit the number of developers and administrators on your production server +- Use [input sanitization](https://securecode.wiki/docs/lang/python/) when + calling a remote API or making a database query that uses user input to + generate a query or insert a new row +- When you have a file upload input, limit the size and type of uploads to + prevent unknown exploits with the + [`accept`](https://docassemble.org/docs/fields.html#file) modifier and the + [`maximum content + length`](https://docassemble.org/docs/config.html#maximum%20content%20length) + configuration option. + +You can review [Docassemble's security policy and +documentation](https://docassemble.org/docs/security.html). + +Instructions on applying regular updates are below. + +## Securing the underlying operating system + +By default, a new Lightsail container with Ubuntu will check for security +updates on a nightly basis and install them automatically. Occasionally, +these updates may cause a problem with your container starting. + +SSHing into the container and doing a manual docker start generally resolves +these problems. + +The alternative, manual installation of OS updates, is usually worse for most +people who run a single small Docassemble server. Just set up automated alerts +that tell you when the website is not available. + +[UpTimeRobot](https://uptimerobot.com/) is one free option that works well for +automated monitoring and alerts. + +If you do not have automatic updates enabled, you can install the +[unattended-upgrades](https://wiki.debian.org/UnattendedUpgrades) package manually +using your host operating system's package manager. + +## Upgrading to a new version of Docassemble + +Docassemble has two parts: + +1. a [Python](/docs/python.md) web application, built around the [Flask +framework](https://flask.palletsprojects.com/en/2.2.x/) +1. An Ubuntu docker image and a series of Linux applications that the web + frontend communicates with, including + - A specific version of [Python](/docs/python.md) (3.10 as of this writing) + - [LibreOffice](https://www.libreoffice.org/discover/writer) for converting + Word documents to PDF + - [LaTeX](https://www.latex-project.org/) for assembling + [Markdown](/docs/markdown.md) files + - [PDFtk](https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/) for + manipulating PDF files and templates + - [LetsEncrypt](https://letsencrypt.org/) for SSL certificate management + - [Nginx](https://www.nginx.com/) as a web server + + +Docassemble is updated regularly. As of this writing, releases come out almost +every few weeks. Most updates affect only the frontend, not the Ubuntu operating +system or its installed applications. + +### When to update + +It's a good idea to stay up to date with the latest version of Docassemble. But +sometimes the very latest release can have bugs. It's best to test out the +release on a development or staging server before you install it on your +production server. + +If you get too far behind from the latest version, there's always a chance that +you will run into a bug that only occurs because your version was too far out of +date. A good cadence is upgrading about once a month. + +Jonathan Pyle, the Docassemble author, is generally very responsive to bug +fixes, as well as helping authors install a fixed version. The best way to get +help with an upgrade gone wrong is to use the Docassemble +[Slack](https://docassemble.org/docs/support.html#tocAnchor-1-1) channel. + +Upgrade the Docassemble frontend every few weeks. Upgrade the container every +six months or so, unless you need a feature right away. But wait a week or two +after the new container is released to make sure there are no lingering bugs. + +### Updates to the Docassemble frontend + +You can update the Python packages and Flask frontend by using the +["Upgrade"](https://docassemble.org/docs/admin.html#upgrade) button in the +package management menu. + +This is safe to do on a monthly basis, but keep in mind that you should test +the upgraded version of Docassemble on a development or staging server before +installing on your production server. + +### Updates to the Docassemble container + +Updates to the base Docassemble OS image (and Python, LibreOffice, etc) are +generally optional. They come with new features that aren't possible to +provide by updating just the frontend. But if you do not want or need +those new features, you can wait to upgrade. + +A good cadence for updating the container is about every 6 months, unless +you see a feature that you want that requires an earlier update. You may +want to wait for a few minor releases (which might fix critical bugs) +before you upgrade your container. The newest container upgrade is the most +likely to have a few bugs to work out. + +The Docassemble [Changelog](https://docassemble.org/docs/changelog.html) +announces when there is a new feature that requires a container upgrade. +Just searching on the page for the word "container" should tell you. + +Whenever you upgrade the Docassemble container, you have a small chance of +losing data, because the upgrade process involves starting an entirely new +Docker container and copying the information into it, either from a shared +Docker volume or from S3. If the Docker container did not properly shut down, +the data that you want to migrate might not be backed up and the new container +may not have the information. + +Here is the upgrade process: + +1. SSH into the virtual machine or server that hosts your Docassemble docker container + +2. In the virtual machine's command line, copy and run the following commands: + +```bash +docker stop -t 600 $(docker ps -a -q) +docker pull jhpyle/docassemble-os +docker pull jhpyle/docassemble +docker run -d -p 443:443 -p 80:80 --restart always --env-file env.list jhpyle/docassemble +``` + +3. After the container has safely started up, login to the web application to make +sure all data is still present. +4. Now, you can clean up all **stopped** containers +(i.e., the old container you are no longer using) by running the following +Docker command: + +```bash +docker system prune +``` + +## Updating individual packages + +You can upgrade individual Python packages and Docassemble interviews on your +server by [visiting the Package Management +page](https://docassemble.org/docs/packages.html#updating). and clicking the +"update" button next to the package you want to update. + +Generally, the "upgrade" button at the top of the Package Management page +will upgrade all of the key packages that you need, except for the ones that +represent individual interviews you authored. + +The exception is if you installed a package or series of packages that is +maintained by someone other than the Docassemble author, such as the +Assembly Line packages. + +### Updating the AssemblyLine packages + +The [Assembly +Line](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/) project's +framework must be updated manually, whenever there is a new feature that you +want. + +The two packages that generally require such updates are: + +- `docassemble.AssemblyLine` +- `docassemble.ALToolbox` + +It's a good idea to stay on the "latest" version of the Assembly Line framework. +New features that can cause disruption are usually put behind a new configuration +option. + +You can review the latest features of the Assembly Line on its +[Changelog](https://github.com/SuffolkLITLab/docassemble-AssemblyLine/blob/main/CHANGELOG.md), +as well as the changelog for the +[ALToolbox](https://github.com/SuffolkLITLab/docassemble-ALToolbox/releases). + +#### PyPi vs GitHub + +If you installed the AssemblyLine framework from GitHub, when you click the +"upgrade" button, you will get individual minor and major updates that have not +yet been "tagged" with a new version number. While any code on the GitHub server +will have undergone a code review process and tests, the "tagged" releases are +the ones that are intended for wider use and installation on production servers. + +Therefore, we recommend that most authors install AssemblyLine from PyPi instead +of directly using the GitHub URL. + +## Docassemble server maintenance checklist +On August 24, 2023, ILAO gave a presentation on Maintaining your Docassemble +server as part of a [Technology Initiative Grant funded by the Legal Services +Corporation](https://www.lsc.gov/grants/technology-initiative-grant-program/tig-program-description). + +Watch the video or +[download the slides](https://docs.google.com/presentation/d/1eJf6x1SwAx4BcvC2OrVUNmg99G5zoAP1TX4ZGnvfkwY/edit?usp=sharing) + + +Much of what was covered in the presentation is found elsewhere in this and other documentation. +What follows is a condensed format to serve as a checklist. + +### AWS Lightsail instance + +This server instance runs Linux, typically Ubuntu. It is sometimes called the host. Docassemble +software runs on it. + + - How to monitor - Log into AWS console and see if it is running. Set up CPU utilization alerts + to be emailed if there's a performance issue. A server reboot through AWS may be required if + the instance freezes. + - How to update + - Important: Before you update or restart, we recommend that you 1) Stop the Docker container using + `docker stop -t 600 $(docker ps -a -q)`, and 2) Have a recent or make a backup of the docassemble + database and redis.rdb files. See below on how to access and backup these files on AWS S3. + - The typical setup is configured to automatically download and install security updates. It still + requires a manual download of other updates and manual reboots. When you connect to the Lighsail + instance via SSH, you may see a message that a number of "***updates can be applied immediately.***" + You can install Ubuntu updates with these commands: + +```bash +sudo apt-get update +sudo apt-get upgrade +``` + +We recommend that you restart Ubuntu after applying updates. The `sudo shutdown -r now` command will +restart Ubuntu. The Docker container should automatically restart if it was run using the `--restart always` +parameter. + +When you connect to the Lighsail instance via SSH, you may see a "***System restart required***" message. +You can restart the instance with this command: + +```bash +sudo shutdown -r now +``` + + - When to update - You can update Ubuntu every month or so. Restart it as needed or more frequently when + the "***System restart required***" message comes up. This could happen if there is an update required + for a security vulnerability. + - Beyond periodic updates, the version of Ubuntu software may need an upgrade. This is like going from + Windows 10 to 11, or macOS Ventura to Sonoma. Every 2 years, there's a new Ubuntu Long Term Support + (LTS) version. See [Rebuilding your AWS Lightsail instance](https://projects.suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/rebuild-lightsail-instance). + - What to back up - Keep a copy of the latest env.list file in case a rebuild is required. + - Wnen to back up - After a backup copy of env.list is made, a new backup is needed only if the contents + of that file changes. + +### AWS S3 bucket + +This is where the Docassemble database lives. Separating the Docassemble data from the software makes it +easier to maintain and rebuild if needed. + - How to monitor - Log into the AWS console. Go to S3. You should see files within each bucket. + - What to back up - The most important files to back up are: + - configuration.yml + - redis.rdb + - docassemble (inside postgres folder) + - When to back up - Every few months or as often as you want to have a backup you could rebuild from. By + default, Docassemble makes a daily backup of these and other files. Each daily backup is stored in the /backups + folder. + +It is important to make backups of the docassemble database and redis.rdb files at about the same time. If these +files are out of sync files when restoring, user accounts and data sync errors may result. See the Docassemble +documentation to learn more about [Recovery from backup files](https://docassemble.org/docs/docker.html#recovery). + +### Docker container + +This virtual machine is installed on the Lightsail instance and runs its own version of Linux. The Docassemble +software runs within. Using a virtual machine adds to resiliency, though it also requires its own maintenance. + + - How to monitor - Use `docker ps` command to make sure it is running. + - How to update - See + [Updates to the Docassemble container](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/maintaining-docassemble#updates-to-the-docassemble-container). + You will use these commands: `docker stop, pull, run,` and `prune`. + - If you [updated the nginx timeout to 5 minutes](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/installation/#increase-nginx-timeouts-to-5-minutes) + earlier, you will need to redo it. + - This will pull the latest version of each package unless specific version of a package was pinned via PyPI. + - When to update - If the [Docassemble Change Log](https://docassemble.org/docs/changelog.html) has an update + that says "**System upgrade required**," rebuild the Docker container after updating the Docassemble web + app. Otherwise, about every 6 months. + +### Docassemble web app + +This is the Docassemble software users and developers most often interact with through interviews and the +Playground. + - How to monitor - If you can get to the Playground, My Interviews, or an individual program, then it's working. + [UptimeRobot](https://uptimerobot.com/) can be used to receive server up/down notifications by email. + - How to update - Log in as an administrator. Go to Package Management. Click the "Upgrade" button. See + [Updates to the Docassemble frontend](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/maintaining-docassemble#updates-to-the-docassemble-frontend) + - When to update - Every few weeks or as needed if there is a critical bug fix or a desired new feature. + +### Packages + +These are the program code, frameworks, and utilities that run on the Docassemble platform. The Assembly Line package +is an example used by many programs. + - How to monitor - Monitors like [httpstatus.io](https://httpstatus.io/) or homegrown programs can check if individual + programs are running. Note: These tools just check whether individual interview pages are reachable. Learn about using + [ALKiln](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/) to do automated + start-to-finish testing and monitoring. + - How to update - You can + [update Assembly Line packages individually](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/), + or you can use the [ALDashboard](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/). + You can also update individual packages on the Package Management screen. + - When to update - Every few weeks or as needed if there is a critical bug fix or a desired new feature. diff --git a/docs/docassemble_intro/basic-troubleshooting.md b/docs/docassemble_intro/basic-troubleshooting.md new file mode 100644 index 000000000..d23d38ef9 --- /dev/null +++ b/docs/docassemble_intro/basic-troubleshooting.md @@ -0,0 +1,212 @@ +--- +slug: basic-troubleshooting +title: Basic Troubleshooting +sidebar_label: Basic Troubleshooting +--- + +## Basic approach to troubleshooting + +In computer programming, errors are inevitable. They can also be extremely helpful. +Error messages can be a guide to writing correct code. + +:::info Errors can't hurt you +In engineering, errors even in the construction +process can lead to catastrophes. In software development, you can restart your +computer, load an old copy of the code, or just try something new. +Don't let errors scare you! +::: + +Take this basic approach to solving problems while developing in Docassemble: + +1. Read the error message +1. Identify whether the problem is with: + - The interview YAML file format + - Docassemble logic in your interview + - Python code + - A template file +1. Isolate the problem to a small section of your project +1. Try making a change to see if the error goes away + +## Read the error message + +Slow down and read the error message. Look for key words that might tell you what has gone wrong. + +If you don't see a key word in the error that you can understand, read the error screen to see if you can identify which block in the YAML file Docassemble was trying to run. Then search for that block in the YAML file (copy and paste a snippet of the exact text you want to match) and look to see if you can catch a typo. + +Some errors will be easier to read than others. Take a look and see if you can spot the error +on the screens below. + +![Docassemble has finished executing all code blocks marked as initial or mandatory, and finished asking all questions marked as mandatory (if any). It is a best practice to end your interview with a question that says goodbye and offers an Exit button.](img/error_mandatory.png) + +In the error message above, Docassemble tells us that there is no "initial or mandatory" block or question. +You may have seen this screen when you wrote your first interview from scratch. + +![Missing docx template file fake_file.docx](img/error_missing_template.png) + +In the error message above, we can see that Docassemble is not able to locate the template file named +`fake_file.docx`. + +## Docassemble usage errors + +![This block is missing a 'question' directive. In file docassemble.playground10ErrorExamples:error.yml from package docassemble.playground10Error Examples: fields: - Test: abc_123](img/error_missing_question.png) + +In the error above, the Docassemble author has forgotten to include a `question` "directive" in a block that Docassemble thinks is intended to be a question block. (The word "directive" has a lot of synonyms in Docassemble--sometimes it is called a "specifier" or a "key"). + +Even if we didn't know what a "directive" is, below the error message, the entire block that Docassemble tried to run is reproduced. We can see that this block has a "fields" statement but nothing else. The more you practice working in Docassemble, the more errors like this will stick out. + +## YAML syntax errors + +Sometimes, you don't get an error message from Docassemble at all. Instead, you get an error message that indicates that Docassemble wasn't able to read the YAML file. A lot of subtle syntax errors can make the YAML file invalid. + + +
+ + Screen-reader friendly error + +

+ +``` +Error reading YAML file docassemble.playground10ErrorExamples:error.yml + +Document source code was: + +--- + +question: | + Here is a test question + fields: + - Test: abc_123--- + +Error was: + +while parsing a block mapping + in "", line 2, column 1: + question: | + ^ (line: 2) +expected , but found '' + in "", line 4, column 2: + fields: + ^ (line: 4) +``` + +

+
+ +![Error reading YAML file docassemble.playground10ErrorExamples:error.yml line: 4](img/error_reading_yaml_file.png) + +The error message above indicates a problem with the YAML file itself. We know +because the first error message says "Error reading YAML file." + +Helpfully, this error message also tells us exactly what line is a problem. +**Note: the error line numbers always refer to the line inside a block, not +relative to the whole file.** Remember that a block starts or ends with `---` at +the beginning of the line. + +The error in this file is on both lines 2 and 4. The up caret `^` symbol tells +us exactly where on the line the YAML file appears to be invalid. In this case, +there's an extra space before the keyword `fields`. Removing the space will make +this valid YAML. + +Error messages with YAML files can include: + +- Using the wrong indentation +- Having a duplicate "key" (what Docassemble sometimes calls a specifier or + directive -- the word before a `:`) +- Using an ambiguous symbol, like a `:` in a place that the YAML parser doesn't + expect + +When you run into an error message that you haven't seen before, first figure +out where Docassemble tells you the error is located. Look to see if anything +seems out of place on that line. If that doesn't help you spot an error, try +looking a line or two above or below. + +### Try a YAML validator + +The error messages you get from Docassemble can be very generic. You might find +it helpful to find a website that lets you validate a YAML file. Just copy and +paste the full YAML document into the online validator. Make sure that the YAML +validator you try understands multi-part YAML documents (ones with `---` in +them). Many don't. + +[JSON Formatter.org's YAML Validator](https://jsonformatter.org/yaml-validator) +is one that works well. + +## Use comments to reduce the problem scope + +One of the most widely applicable troubleshooting techniques you can take is +to reduce the size of the problem. If the full text of the error message doesn't +tell you where the problem is, try temporarily commenting out or deleting text +to make it easier to spot the code that is causing a problem. + + +### Partition the problem by halves for quick locating + +Start by deleting or commenting out half of the document at once. Once you figure +out which half has the problem, divide the document in half again until you zero +in on a particular line. + +### Comments in Mako + +In your YAML file, you can put a comment in-line with the `#` symbol, like +this: + +```yaml +# question: | +# This question is commented out +--- +question: | + This question can still be referenced +``` + +But YAML comments can sometimes cause problems in the middle of a block. +Be ready to temporarily delete lines instead for fast troubleshooting. Make +sure you keep a copy of the lines that you delete! + +### Comments in DOCX files + +In a DOCX template file, you can try adding Jinja2 comments. Jinja2 comments +start with `{#` and end with `#}`. They can span across multiple lines. Like this: + +```jinja2 +This document is for {{ users[0] }}. This section will still display. + +{# +But this paragraph inside the comments won't get evaluated. Even if it has Jinja2 statements, like +{%p if True %} +Conditional paragraph +{%p endif %} +#} + +While this un-commented section will still be evaluated. +``` + +Use the "divide in half" approach to zero in on the section of the document that +has a mistake. + +Sometimes, Jinja2 comments can run into parsing problems, and instead of commenting +out a section you should consider temporarily deleting it. But comments will often +work. + +## Troubleshooting DOCX templates + +DOCX templates can cause a lot of challenges for troubleshooting. Here are a few tips: + +### Problems with syntax + +1. Try adding a highlight to the Jinja2 keywords and variables. +1. Count your Jinja2 directives to make sure opening and closing directives match: + - Search for `{% if` and make sure the number of results matches `{% endif` + - Search for `{%p if` and make sure it matches `{%p endif` + - Search for matching `{{` and `}}` +1. Turn on "show paragraph marks" ![](img/show_paragraph_marks.png) and make sure that + you don't have a `{%p` swallowing up what looks like a new paragraph but is really + just a [soft return](https://word.tips.net/T000170_Understanding_Hard_and_Soft_Returns.html) +1. Make sure there is a space between the `{{` and the variable name. Especially, if your variable starts with `r` or `p`: `{{r` or `{{p` mean something special to Jinja2 + +### Problems with corrupt DOCX templates or layout problems + +Open the file in LibreOffice. However it renders in LibreOffice is how it will +renders when Docassemble converts it to a PDF. If you can fix it in Libreoffice, +it should still look right in Word. + +Sometimes, corrupt DOCX files can be fixed by saving to RTF and then back to DOCX. \ No newline at end of file diff --git a/docs/docassemble_intro/controlling-interview-order.md b/docs/docassemble_intro/controlling-interview-order.md new file mode 100644 index 000000000..632bac8ea --- /dev/null +++ b/docs/docassemble_intro/controlling-interview-order.md @@ -0,0 +1,504 @@ +--- +slug: controlling-interview-order +title: Controlling Interview Order +sidebar_label: Controlling Interview Order +--- + +## The interview as a question library + +You can think of a docassemble interview as a library of questions. +The only questions that docassemble will ask are those that are triggered, +either by a `mandatory` block or in some other way. + +Docassemble will scan the interview file until it finds a question +that contains the answer to every field that is required by any +mandatory block. If a field isn't used in the mandatory block, or a +block that the mandatory block itself triggers, it will never be shown +to the user. + +Copy and paste the interview below into your playground. + +Before you run it, make a prediction. + +1. Which questions will be asked? +1. Which question will be asked first? What will be the order of the remaining questions? + +```yaml +--- +question: | + What is the average airspeed velocity of an unladen swallow? +fields: + - no label: air_speed +--- +question: | + What is your quest? +fields: + - no label: quest +--- +question: | + What is your name? +fields: + - First name: first_name + - Last name: last_name +--- +mandatory: True +question: | + Hello, ${ first_name } +subquestion: | + You said the swallow's speed is ${ air_speed } + + Your first name is ${ first_name } +``` + +### What happened? + +Docassemble only asks the `air_speed` and `first_name` questions. The `quest` +question wasn't used by any mandatory blocks in this interview, so it +was never asked. + +The first question that is asked is the `first_name` question. This is because +the one `mandatory` block mentions the `first_name` field before it mentions any +other fields. + +As you can imagine, the incidental order of fields on your final screen doesn't always +dictate the most logical or pleasant order for questions in a lengthy interview. +There are other ways to control the order of questions in a more fine-grained way. + +## Who's the boss? Why you should use only one mandatory block + +There are a lot of ways to [control the +order](https://docassemble.org/docs/logic.html) of questions in a docassemble +interview. For example: you can use a `mandatory` modifier on many questions, +and the `if` modifier to handle optional questions. Alternatively, you can +use the `need` modifier on multiple individual blocks. + +It's a good habit to use only **one** mandatory block in your interview. + +Using one mandatory block can allow you to: + +1. Visualize and trace your interview logic in one place +1. Better understand which code will end up executing and which will not + +Copy and paste the interview below into your playground. Before you run it, +make a prediction. + +1. What will be the last screen that is shown to the user? +1. What action will happen when the interview is complete? + +```yaml +--- +mandatory: True +question: | + Please answer our intake questionnaire +continue button field: introduction +--- +mandatory: True +question: | + Tell us your name +fields: + - First name: user_first_name + - Last name: user_last_name +--- +mandatory: True +question: | + How old are you? +fields: + - birthdate: birthday + datatype: date +--- +mandatory: True +question: | + Results +subquestion: | + Thank you, ${user_first_name}. + + We will send your results to Dewey, Cheatem and Howe. +--- +template: email_contents +content: | + Here is the intake + + First name: ${ user_first_name } + Last name: ${ user_last_name } + + Birthdate: ${birthday} +--- +mandatory: True +code: | + send_email(to="dewey@example.com", template=email_contents) +``` + +### What happened? + +Mandatory blocks are read from "top to bottom" of the interview file. + +In the interview above, all of the blocks are mandatory. But the code block +that sends an email will never run. Why? It can only run after the last screen +is shown. But docassemble pauses on the "results" screen indefinitely. + +## The interview order block + +I often call the code block that I use to control question order the "interview order" block. +There is no official name for it; in HotDocs and its predecessors, this would be called the +INTERVIEW computation. Here's an example interview order block: + +```yaml +--- +id: interview order +mandatory: True +code: | + intro_screen + user.name.first + if user_type == 'attorney': + attorney_instructions + else: + prose_instructions + user.address.address + download +``` + +### How the block is run + +Docassemble runs this code block from top to bottom, seeking the definition of +each variable listed in the code block in order. Each **undefined** variable +triggers an exception (`NameError`, `AttributeError` or `KeyError`) which +Docassemble intercepts, running code or asking a question that can define that +variable. **Docassemble will then run the interview order block again** from top +to bottom until it reaches the next undefined variable. + +Understanding that the code block might run multiple times is important! Use +this as a place to list variables as a reference and do simple branching logic. +Don't use it to set any variables or call an API that might be triggered +multiple times. + +### You cannot trigger a block with `id` + +Another common pattern new Docassemble developers try is to trigger a specific +block in the interview order block by referencing the block's `id` or by +adding an `event` modifier to the block. + +The `id` of a block is information for you, the developer, and gets used by +analytics tools as well. It is not used to trigger a block. +### `event` does not do what you think + +A new developer might try using an +[`event`](https://docassemble.org/docs/questions.html#event) modifier to trigger +a block. An `event` **generally** does not save or persist any variables that are +set during it. You should not attach an `event` modifier to a block of code that +you want to trigger in an `interview order` block. Reserve it for ending +questions, not to label code you want to run. + +The other place that `event` is used is with the Docassemble `actions` system. + +`Event`s linked to actions that **do** permanently alter an interview's state +can be triggered by an [external +occurrence](https://docassemble.org/docs/api.html#session_action), [clicking a +button](https://docassemble.org/docs/functions.html#url_action), or be used by +[background code](https://docassemble.org/docs/background.html#background). +Don't try to use an `event` to trigger code in the main flow of an interview. + +### How the interview order block differs from HotDocs' INTERVIEW computation + +Remember, Docassemble is goal seeking. It doesn't care what screens the user has +seen: it tries to define all of the variables that you mark as `mandatory` that +it can reach. + +Unlike in HotDocs, listing a variable that is already defined will not trigger +anything being displayed. Docassemble only displays something or tries running +code if the variable triggers a Python exception. + +A common mistake when a developer is getting used to Docassemble's built-in +`Individual` and `Address` classes is to list an object in the interview order +block. The developer may not realize that the `name` and `address` attributes of +an Individual are themselves objects and that they get pre-initialized. + +For example: + +```yaml +objects: + - user: Individual +--- +id: interview order +mandatory: True +code: | + user.name + user.address +``` + +The short interview above will trigger the objects block in lines 1-2, and +nothing else visible will happen. This is because the `name` and the `address` +attributes of the `user` object are created instantly when the `objects` block +is run (by the `__init__` class constructor of `Individual`). If you want to +trigger a question, you need to trigger an attribute that is not defined yet, +either by a question, code, or a class constructor. + +Here is a version of the above interview that probably matches the developer's +intent: + +```yaml +objects: + - user: Individual +--- +id: interview order +mandatory: True +code: | + user.name.first + user.address.address +``` + +The `name.first` and `address.address` attributes are not defined yet. +Mentioning them will cause an `AttributeError` exception and lead +Docassemble to seeking a question or code block to define them. + +Another way that the `interview order` block differs from HotDocs +is that you might find that other variables are triggered that you did not +explicitly list. Remember, Docassemble is seeking to satisfy all of the +variables you list in order. If your question or code block in turn +depends on another variable, that will be triggered along with the +variable you explicitly list. + +```yaml +objects: + - user: Individual +--- +id: interview order +mandatory: True +code: | + user.address.address + user.name.first +--- +id: user's address +question: | + What is the address of ${ user }? +fields: + - Street address: user.address.address + - City: user.address.city +--- +id: user's name +question: | + What is your name? +fields: + - First: user.name.first + - Last: user.name.last +``` + +In the example above, triggering `user.address.address` will run the +`id: user's name` block before asking for the user's address. +That is because the user's name is displayed on the `id: user's address` +block. + +### Triggering a screen for a variable that is already defined + +#### Forcing docassemble to re-ask a defined variable + +One pattern you might encounter is that a variable is pre-defined (maybe by an API) +but you still want the user to have a chance to review and edit the value. + +You can do that a few different ways: + +1. [`invalidate()`](https://docassemble.org/docs/functions.html#invalidate) +2. [`force_ask()`](https://docassemble.org/docs/functions.html#force_ask) +3. Creating a [review screen](https://docassemble.org/docs/fields.html#review) +1. Using [`url_action()`](https://docassemble.org/docs/functions.html#url_action) + +`invalidate()` will tell Docassemble the variable is not defined without erasing the +value it has. This has the effect of allowing you to revisit a question. + +`force_ask()` has a similar effect in most circumstances, but offers much more +complex options to trigger a series of follow-up questions. + +Think carefully about how you use this pattern. You can avoid it +if the API is used to provide defaults. Using `invalidate()` or +`force_ask()` in the interview order block is also risky, as it may +run more than one time. Try the `named block` pattern below to +contain any `force_ask()` or `invalidate()` code and ensure it +only runs one time. + +#### The **default** pattern + +```yaml +--- +id: interview order +mandatory: True +code: | + address_default + user.address.address +--- +code: | + address_default = run_some_api() +--- +question: | + What is your address? +subquestion: | + We set the default value based on an API result. +fields: + - Address: user.address.address + default: address_default +``` + +This very simple pattern just displays the API-generated results as a +placeholder. This pattern is nice because the user gets to see the value and +change it. The potentially "wrong" value is never stored in the `Address` +object. + +#### The "existing or new" pattern + +Another pattern you could try is allowing the user to choose from existing +values or to define a new one. `object_radio` combined with `disable others` is +a good way to do this. This pattern works well with API results. + +```yaml +--- +objects: + user: Individual +--- +id: interview order +mandatory: True +code: | + address_default_object + user.address.address + display_results +--- +code: | + # You could use a function or API call that returns an Address object instead of directly initializing the object + address_default_object = Address(address="123 Main St", city="Boston") +--- +question: | + What is your address? +fields: + - An existing address: user.address + datatype: object_radio + none of the above: True + choices: + - address_default_object + disable others: True + - Address: user.address.address + - City: user.address.city + - State: user.address.state + code: states_list() +--- +event: display_results +question: | + ${ user.address.block() } +``` + +In the interview snippet above, the Address/City/State fields can only be +interacted with if the "Existing address" field has been left set to `None of +the above`. + +#### Displaying a link to allow the user to edit a value + +One safe option is to display a link to edit a variable on an arbitrary screen +with `url_action()`. Here is an example: + +```yaml +question: | + Verify your court +subquestion: | + Based on the information you gave us, it looks like + you belong in ${ trial_court }. + + [Edit court](${ url_action('trial_court') }) +continue button field: inform_about_court +``` + +If you want to review multiple fields at once, use the +[review screen](https://docassemble.org/docs/fields.html#review) pattern. +Review screens will automatically update to display only variables that +have a value, which is handy. They can be displayed in-line or when the +user clicks a link. + +### Avoid setting variables in the interview order block + +You might be tempted to treat the interview order block like a script in an +imperative programming language. This would be incorrect. Docassemble is +declarative. The interview order block should list a set of goals. Setting variables +in the interview order block can: + +1. Lead to infinite loops (you can avoid this by using the modifier `scan for + variables: False`) +1. Lead to variables definition being changed unexpectedly and in a hard to + trace way. + +Yet sometimes you do want to trigger code. Do that by using a "named block". + +### Triggering a question and then continuing: using `continue button field` + +Docassemble does have a way for you to invoke a screen more explicitly. When you +add a [`continue button +field`](https://docassemble.org/docs/fields.html#continue%20button%20field) to +a question block, you give the block a variable name. You can mention that +variable name in your interview order block. + +```yaml +id: interview order +mandatory: True +code: | + intro_screen + user.name.first +--- +id: introduction +continue button field: intro_screen +question: | + Welcome to our interview +subquestion: | + Before you start, follow these steps: + + 1. Step 1 + 2. Step 2 + 3. Step 3 +``` + +Usually you should **only** give a `continue button field` to a screen +that doesn't ask any questions. Avoid using it to simulate HotDocs's +dialog-based interview order. You can run into harder to trace logic. + +### Triggering code and then continuing: using "named" blocks +Named block is a term that I use that is not in the Docassemble documentation, +but is a very handy concept. + +Here is a short example: + +```yaml +objects: + - user: Individual +--- +id: interview order +mandatory: True +code: | + user.name.first + get_api_results + user.address.address +--- +code: | + user.address.address = run_some_api() + get_api_results = True +``` + +Notice that in our interview order block, we referenced a variable named +`get_api_results`. This is the "named block." This variable gets defined at the +**end** of our code block. Because it is not defined until the end of the code +block, Docassemble needs to run the whole code block to define it. + +This is roughly equivalent to a computation in HotDocs, but note that this code +block will only run once. When the interview order block is run again, +`get_api_results` will already be defined. There is no need for Docassemble +to run it again. + +What if you **do** want it to run multiple times? You can use the [`depends +on`](https://docassemble.org/docs/logic.html#depends%20on) modifier to specify +conditions that will cause Docassemble to recalculate the `get_api_results` +definition. More bluntly, you can use +[`reconsider`](https://docassemble.org/docs/logic.html#reconsider) to run the +code block each time the screen is refreshed. It is usually best to avoid +`reconsider` if there is a different tool that works because overusing it can +greatly slow down Docassemble's operation and sometimes lead to unintended +behavior. `depends on` also serves a dual purpose of allowing you to explain +your code's purpose to the next developer to come along and read your interview. + + +# Learn more + +* Docassemble Documentation: [Logic](https://docassemble.org/docs/logic.html) + +Quinten Steenhuis, February 2021 diff --git a/docs/docassemble_intro/hello-world.md b/docs/docassemble_intro/hello-world.md new file mode 100644 index 000000000..cdd0fa58e --- /dev/null +++ b/docs/docassemble_intro/hello-world.md @@ -0,0 +1,128 @@ +--- +slug: hello-world +title: Hello, World +sidebar_label: Hello, World +--- +import useBaseUrl from '@docusaurus/useBaseUrl'; + + +Let's begin our Docassemble journey by trying a simple coding exercise: the classic +Hello, World exercise that is the traditional first exercise in a new programming +language. + +## What is Docassemble? + +Docassemble is a system for running interactive questionnaires. It uses several +open source technologies, including [Python](/docs/python.md), [Markdown](/docs/markdown.md), [YAML](/docs/yaml.md) and docx-template to +let you concentrate on writing your interviews and writing very little code. +Yet, it's flexible enough to allow you to use advanced coding techniques when +you need to do so. + +### Video version of this training + +This training was conducted live in June, 2020. You can follow +along with the video if you want, or if you prefer just follow the +written instructions. + + + +## Introduction to the Docassemble Playground + +We will use the Docassemble playground for all exercises. Log in to your +Docassemble server. When you are loggged +in, click on your email address, and then select the Playground option to reach the +Playground. + +Overview of the Docassemble Playground + +The Playground is made up of several elements. + +1. The main text editing window +1. A list of example blocks that can be directly inserted into the text editing window +1. A list of variables and functions available in the current interview file +1. The save and run button, used to run the interview and view results. +1. The share button which also gives you a link that can be shared + +## Hello, World + +In computer programming, the [traditional first +exercise](https://en.wikipedia.org/wiki/%22Hello,_World!%22_program) in a new +programming language is to display the text "Hello, World." + +### Baby steps + +Let's erase the text in the playground and replace it with the text below: + +```yaml +question: Hello, World +``` + +Docassemble interviews contain a series of `specifiers` followed by text, such +as the `question` specifier. The `question` specifier tells docassemble that +what follows will be the title of a screen displayed to the user. + +When you save and then run this sample, you will notice an error. + +``` +Error +Docassemble has finished executing all code blocks marked as initial or mandatory, and finished asking all questions marked as mandatory (if any). It is a best practice to end your interview with a question that says goodbye and offers an Exit button. +``` + +### The mandatory specifier +Take a minute to read the error message. In Docassemble, your interview can +contain many questions and branches that are never shown to the user. This error +is telling us that _none_ of the questions are marked as `mandatory`, or +required, so Docassemble doesn't know what to display. Let's fix that. + +```yaml +question: Hello, World +mandatory: True +``` + +The `mandatory` specifier marks this block as required. Now when we run the +interview, we'll see the text "Hello, World" on the screen without any errors. + +> **Note**: you should normally have only ONE `mandatory` block in a single +> interview. We'll discuss good practices around this later. + +### Introducing variables + +The purpose of Docassemble is to gather information from the users. Let's ask +the user's name so we can greet them personally. + +```yaml +question: Hello, ${user_name} +mandatory: True +--- +question: What is your name? +fields: + - Name: user_name +``` + +We introduced several new ideas in this interview. Notice that each screen in +the interview is separated by three dashes, like this: `---`. + +We introduced a new `specifier`, `fields`. Instead of single line of text, this +`specifier` takes a list. Each field in the list is indented and begins with a +single dash, like this: `-`. Each item has a label, followed by a colon, +followed by the name of the variable that the user's input will be assigned to. + +To learn more about the format of a Docassemble interview, read the section on +the [YAML format](yaml.md). + +We also introduced `variables`. A variable is simply a placeholder. `user_name` +is a variable that is a placeholder for the user's name. Inside the Docassemble +interview, we can display a variable by surrounding it with `Mako` tags, like +this: `${variable_name}`. + +> **Note**: We don't need to tell Docassemble to mark the second question as +> mandatory. Because the mandatory question requires the variable `user_name`, +> Docassemble automatically asks the first question that provides a definition. + +## Your assignment + +1. Modify the Hello, World exercise so that it ask for the user's first and last + names separately. + +Quinten Steenhuis, June 2020 +Sam Harden, June 2020 diff --git a/docs/docassemble_intro/img/error_mandatory.png b/docs/docassemble_intro/img/error_mandatory.png new file mode 100644 index 0000000000000000000000000000000000000000..355a8bb56dd72348a71f152d1244e573afd6147b GIT binary patch literal 25369 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 J<7FQP{0kM|qrm_G literal 0 HcmV?d00001 diff --git a/docs/docassemble_intro/img/error_missing_question.png b/docs/docassemble_intro/img/error_missing_question.png new file mode 100644 index 0000000000000000000000000000000000000000..fb7c3f7d0338af42fff59101fb913e7673e560c7 GIT binary patch literal 9744 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 literal 0 HcmV?d00001 diff --git a/docs/docassemble_intro/img/error_missing_template.png b/docs/docassemble_intro/img/error_missing_template.png new file mode 100644 index 0000000000000000000000000000000000000000..55b98cf192aecb7b70ac2080cc49199f35df158c GIT binary patch literal 9590 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 literal 0 HcmV?d00001 diff --git a/docs/docassemble_intro/img/error_reading_yaml_file.png b/docs/docassemble_intro/img/error_reading_yaml_file.png new file mode 100644 index 0000000000000000000000000000000000000000..685674a670cad711d6c119e615870e1c788595d3 GIT binary patch literal 17395 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 literal 0 HcmV?d00001 diff --git a/docs/docassemble_intro/img/show_paragraph_marks.png b/docs/docassemble_intro/img/show_paragraph_marks.png new file mode 100644 index 0000000000000000000000000000000000000000..49898de5b1479d42a17e2db5b983e47ed40b6559 GIT binary patch literal 10619 zcma)iWn5Olw=PJxbc4XA8|jpM*>snrbTJV>bm$1}gM_wS;uE&t?_k zB25VOgJb7v;^zp$IZBls*Hs9OnhH7xEOl^YT_c~pQ5KnCj*NUlj0=cvlE@Jy(qN7> zAyZqsc)0MPyNt)su)pC@xO?pUE>DR5?{>ip>5C5gn^v*sh<9hZnU!xZFQugmy0!>- zZoK|_GW@)r@ct5Xlw@(Ue(~3G&F@b4&il$%QiWH@$`W%VW56HI(s}#0X}j@6xvawH zk&HUW)DV9asrmD%vYZ4L3I`~OHH(Z1BTHR%FihEm^ds-5G;&pm-B~N+rQsBU>v<84 z-qWwUOQXh?LnwXzliQlYpKsS;7FYT2gCtsHe*Yjie!DyN<%5~P&sSqFFWzCKbnjWW zGhVU2FOZeQi=^+d6{bx1n*Bpcd32qCzMMyLE>WDj914SUS+nKmzJ!Lc*hU}|gwqFo zR}gNSW&$m3oYN_KaCfq=yta2AHc#mvC?AZ#Wdcu`bE_Q3mtnl%V4NzeNPrf1BbxYM ztJ&Y&zKTTx6dLie<*%}%(~HqHwo}0qHexHOA!CNGM6U>uM-q*Q#CAht4CFY;Wek*0 z0p!&<*cBG;^kBtvm(GJ%V@1-;@5YJ)u;?5O2+TPA6_B5p?msC98}K-#1167_G&-PSk%$Ub)NnzU2pIF)P~kI#RH_D*8Avwlb>U1L6>$Rl|q zu3s3AzpU;o?>;3k%;SA%zU&`~N8tORX1v1O1j&VRQQGEvSpzWeq)_YKW+d5qT124d zXI)%;DkHp)wggWnLuxQ8hEC5FB1{;_(vev|K8cpz45LRkD1Xs3$O6sm!^Y)F!{8{Bi@(p4fYyxo<3%fD!}KH+^gICt`~~WoV`2%qP>+($6U_X>Mk!J} z1>Kl~2p;>LQYCa9cY4stINzq0NB+A&<#ALJY*OAVfdO2NmbLGr`MGU6_JNh6IZB7& zWbQ(bKrL+dATGK^ZaSI06+_)br_GM0*`=8sm#&AFN-htrQkf$_0UFP_@Z9V$V=MJK@>dwv<(aVc(Y}0mYeBkHL%sXf}V>ZX_NaUN}(3gQk z8a{fMxZ*8X0hK7MJT9tqMiaAK}+3>dt zhX=AFbv>$tkKn|xcny{)$A92m$w)4iJcBF}Q2ahVrCR}U<&WEb*1p{}T`FRsP7$ZZ zgQJ+8M#r|*Rx2x{?iA5W@_I!?+M1j?vxYn3nFMNmYOVQ1FUmDP5oYJ>x+{<;Y;KnC z$5>~YNz>e&-+L0d!xb~0Sk6$rn&JjF58+FE^e_2(;53*#GXzW|SJM~w-FWsYb$VFs z9<#Q5_Iv?^w*61%1L$mn${t%4J}Pt1FKBPh4?p+a4PMd*o}&4o<#}PVfX-p?Qs3Mdr!KIW}o58jV5Eb_l#UhCarNtTe*!fT`2&j9ubs|h^!3ZD%w=Fzw`!G0nHh8(xPu8zh2@JL{}kLM5odP% zdvS!=j0Mj!JQ!0D>o84+a99cLgri!o{I*qft|F5@c&mtYBKqWDvB!pZ7Qc%z`YQSU9-Wc{5eLj@Sj)gFL>kr+a-SK2Kqjq zsR7vArYcFVF%(1kXD{sjEmy3FTl-|vrw*)*w>O_`yLOg;$Ngv&K{~Rm4buQ0T&{V) z#}ip=^!;X#4#)i*9b=|CR%_@Uu(>WmD>L!#v*-xo@z2|B{bR;JRia7ry^iy(vo(Hc zH{F}Ff*}6}^U#ka8lVbUu@nmX3K#dlzujU4eJq8Qm_e~E4ZTfXBR3c?eG)%~#VJF4 z{-Aj8*%v(OOAl-GpTd&g^RQb!FJaP%UmZ)}oA=oMc-_!a#;f6Z&c^H7IUNY<@E;zX zwSQjF{Btmw1!L?wSC`GJORh97U_P!%WlUYY0c@s zl_*aEq^=fOp9kM+yRjiQTLc7`q816Id$Xsa$G~KwYDm!Ozvp>jXOk;IHuULdW9{hR zQLRXQm6q2r`BfE1sw!kC^7_F#l#zW7_us*9Z=AuQc}0Tb*alGk0;lX8k^l8p1|%s| z0<-~N?BCiBMG>`20ZM?o7nGfqjDZr&mF<5%+Bb@l^_$h6~XV^6sbVRyuV_r9 zsAdoRYzMT9p`!|~M!VlpnOvK_fu$ku&vqe}E^Lo?rk>&r(1m$TeMC^SD5~No{nD}? z=Ha(p!cY&NtA`5$%2k2DonN+bf<5_k;Pr zFmYi}l~=--l@@+My_n<)*J`7U)WAA}`r0YJES9+y%#+H8RPOYfgFy}J2?WRZ?^ z5o2u7>ohGk4`&=ranVT;^>H9qHL#&qhF#^%CPHZO;YuEhpUl){l9MoT^v&X;6976r zJ2V7&X#$P!5~%TyFIp#R9f%o1T(l(p$JiRDnURSw%xh;oC!rNz)YH+mEx>et$*E_idQf4T zXYbd_yoG?lObD*(-DMCdP)X>fxX<>9K&3WhLb3+LaTDk-nQST)-9i}|l`-F7 zJ^I z>WWaTZDpNZZu_(%HTKbTAQSF+vzU|*B;m21o@WET>0dl-<$OzPrgB{>(Y}gT?T$s! z(e6lm!4wQ;O7>}MnVX*Aqn*BwUzG`^t00`l1~;wL%#9rrG!@1DEa0{1;^abJ=Ds0X zduWjm*8-1bYRt##dgrxOD)+VX!4e5Ar<;N*<;%z&lwb`J4q(h%j(?ZSg;cOG$&wv7 zXb<`O+|a!viP~C~D7*5Z>PN%6?JtEAA~kc1kso@2OJ#dVkdld+qGxAPpt77wyki9h zk$GDb3hH-}=qo}&lzHJV8i4)k<`v5? zL(pwyL4PO3?D!$2%5~k;3fE#ek-9xoP9pG*sHQ$G6zI9^$&TglWYl~KnrPj}4mZZ4 z1WpVQ)n%>pA~}gpKJYtS^MTuHt^UN4jd%k+L{`abSATRZU;{dcQIl&zqnm|&XD^ly znX`lTF=VD?5-g!sbM3zOk0Bh&PS+ERg)!f92_6gl)?RrLVtDU9{kPxFS!W9 zac+BSUU0S9b}E6l_iC6}(==c=lU1|G<_8cuZADm^LLtmiA#i*LT$YZizO&^kwxFQdUyZ~*We^BqCR&jz~S zb1Zn2=h-`pQi_59jB!~NYI9o;9KsXXl*1zD@-Hai_~4sbak#bb*|7r=#L*#B^xf58 zJ~~Tdf^Gb&mtm}&g+a-*5*PdP{$7>UK1d{mzMR-GG0JUl+Rwl~%0p1P29GGi^l3{5>p4(2Og zp5FRbJF!W8`t zFK)JlV{mh(ec3DnILl;8FPp;lr>3@^5vAt5`epASZ^NGk0yFQ8CA#U2G(-ZujUY>)9v{ zZlCGEq-rv3Ir8>(xTPeNls)hJ7wJhG9GbGuCHI^9J&a&$PFtO>hnv&Q+BpR7pD;rU z(!9g74ac*jWWJ;&#U^IeLKK}<@UT_3c9w<`ItW?j>9&p?83CPD#L5qxRv#=xlslxu zk3O~2i+Y5j=$a#TU2Q|hSEf5J&U#4NYyC#@Ns~}i51Nnq!te19Xa!RJpS5#}0DZgy zM!g}2L-T+|tL^V$vb-|Pte-An=NW%YXE~T?-e)iu^YUs^-X~lMq z>GW&9vM7vh>uTu}{Wdat9r0_G0m`n;eJaT@qd?RO4$%K*--M&_|hQ zwgy#daCnyKiUGV=9%BT>rHv;giavuMbS+mQR221-{*nhQeWJ|0>!Yg4d-UoQKW8aR zhE(ohyIjqQ9QE4E{8{z61TnwOY%`(7I_8Z3jYJmYjACWn^efD6CmJTn*IHy?$c)bT ztABdDU^f-_YK1CWpvAmD@e)ll0Q=3Xk8rpf_&}@teekh220l&ysB%jGH}~tH_nOIR zQo_U?DkeDdSwN@&F=C?;pC3M0CfaZ;W3k$f3T4>Kip>1)DL^%0C=ozvOe-$X;n-C| z8bR`RAQK%4B_5FWK;<)VM~%h6LNv$Cs4YD$Lz0~9pPTjbRYt#dNm9)=D6e4iDp}l>H|DS9$nNdmy?s-Uv+rI3-^k!aG%LkyE5xicb zh~}qNgQ%)ipnsBe$l}csKRC%e!IvoZ47w*XltEwu0mz}?$X0RSckfhCfPs53^NOD0i zIuYUQoCgDY*uKZp1N=^9&!3o2pUb{der=gLHtY~)Lnq=&?h(DL*BOeOx_cNxXKEgIF-HEtE`#b>xRkY#P-D;z}hrJZ=;yqligyL=HX9 zfFg0|l}Xp%ARo+W;9dWro26j+W(uJiFcEYiM0*6fR*Bw z2k*rI1|K%%ZeppDqjwsHaDg$ZMzM78;X*Yrs|Jl!;CT$9k|->hCSWUyTx)^NmFKjd z5hS_@Ewq$zr;cuFEQ$2=^trw|iOL4PBTL+ivkT~w;s3GzrFWn?zf3t1d$R$S%Eg9zYFyH^OPKRgxXrF zH+%VYrPYzR3q1mnsnk*D0dYW;SEkR3ZT8W6S2p!fKf?Y;Jbh5y?>oggW(p}ds81yR zmfyWmMKTXzw(}JSXmQ>@e$U#4B5nZjk(#g_4Y=Cp6<}j4BWdsFj`I~r^$DqP?RpP{ zY|I2o_87{Bsbxj##&C(Z#&T7?u0fKefu?OJoDs+^;rIPXQ-lKK;ThQ_>?SBL@L|Tr zS}6!}P_vt5*lI1}41Us89&Krh}?CG9nnWwzWAK2f% zUJFqcPOwUZ=|lY$>iJqfGTYlR#>XKNr_9Y$rfm1r1E<8vCBuACYs+y);Yt-1LG92` zf{ZVQEjui>aX54RpV&YW&c`-Y#&TX*oTG1VgAPgF=nNS#sY42NXLqk66Vk9#sT|o2 zRmk!9XM0?2(eUthj#D^=hBwdNw?S=cDir54#n z{X53=s<$i;=RCKwH0fp^e|=0UZ~Agg15XAnHOvG>1)m~_te)x29S;$whmIPnOdY_$aYo24C@b40< z8sovpSRGEN$gA1z8Y%Z3-^Wn_l{u1=T`3hQ&A63`NGyoo_&yl^bGV3!K-qtwYxNI- z5g~^_Rf>NQ`wwsbpR>sSS^e1ofW0lP07OyBOU;|cQql)K(lXbTC_$t%|neZ3Y( zL|GbPQNd(@#GY=o|M;$5L{so~CHI@=?#;FOMw4prW&7G@)HhDw3ns4Ly=#HO zBAf#QJkxhu97{Zb+uv^qCbjY|80y<|+7C$}b9fr<<F-T+ghkJ-QG^g><-$23yz>Cfcoj0di`#3p*ujB1Eb!iLh4rodB=#>d{O zM<%g72k6Yxdx;0k_L%*(U%8f8ty#teROhx9@u-DeLiV{tXzl4&aW@XF4t-=;@zvAb zG2$qS(<&i@gH4ikl(a-_jX>SHYl&d#+%&(6)&RgIy`@Rq>QQvhp71 z69eN}1Lmtu$&g6#ZrY-b?xgKQ0>8O1s7|w>=ozhWYU9Vb(CQS5csk`f9OC(naW(Fy zJWyJD)KS3Dc@}vvoQ>-Lj0mGMxGW3y>3~f$EkLd$QhQvK^d7Sj9D$xpCG6hw>thrS z`Ao6Q%j2~ULT1NbjW4T2^|mC;W_P;-d>6&8`PhB&vzAi@v0`(DWqgcW;(Q-T6BdY~ z?rRq`bdHx&W^b+TFX$>MrNXZ&dDd4B-@iL_#A7O$I&AgRFLPTlFxWSLvW?!Xi>_jj ztE*}9W0$?K;qu{eR=}^+VV1nW2=3m)=7`s3sDs3seUhKqo^ua>rlVH#s)+Mh24u0y zQnhE;oHB%8pr}3qua#X5H`cP8o{zl7US1M!*C*%1Dt|Z`nnUi8ugyf!R}`)N%x-8F zmwSaS5mbR>V3g7WwUq+dQLw^+rYaqYF9KK3HEAUycD$qn6*NsQMs4lf-9-qhXKG^{ z5Hm>H6};j|p(dKcCo}&zMTEU^`{-zSglR1jPi(tNzy-StSg2{ zPNEUBp+F~-ekdLJ%)sW-ykz{eOPXv5=3*4`xJC-8QX1Ux@xkNI`g#Ui`W5<)N}6iW zY3X|ayiRmtHLEV>fjc3uW}iRKkv`E@8>+Iji)*fUAf?IyDlhRCgL4J^AR~%LrX94C zIJ-N$Ti+?byj7c}@WXNh3U=z_&N^h-4GE74E6P~ z(m!(Zjjp9HM!hL&?VcTJlu6D+TyUkr-=4t2@ zQu;BwVpR<--Z?zAC(_cumeVkP#gYte}aXVHoWl{xxH;H5KY?xp6nbP5gp z(C&EnWXcY&eoyT?z?$N|5h8ssXbB1O&m0oSk`)iszkFDQ43%NpVahSq9sO~yzK@(RZv<}CUgdoKFhUzsPbl`(Eot9ZWX z>PXdm+?3uFhX?$84!B<$6xJ6!sIRNZjanccGl=n7wdy9qJQoWJ<9r&ecc#p?I`Zk$ zD^*lfQH!~n-Nd%X*Do#{v$Br1S>?CHk4pGKl{k)BHkE~mq>M+>w|AX*c&ri`6CnN+ zws_%u!o#j-4Np<=u%^OZIzTppn>LK`O2k^R$0g;%06VioXcl9ta%y+a1-NmK58-ECsLgLEvbntb0e03{BIgG}c|Vy*%vJF*PJZT*WU>%aIk9CQ zt23HS0gju)5^TX1`VoxksZ{(R6{SJhpB`#Gw%a)5O0N7xnYLH7R6O{yCND~l^gaL+ zMjC36QiX8}OUAU-O5pWy$R3_^rWtYbM6{~r~IfuKLDucU+5i+@_V6|&u2UHTwWc!Y^L)G(+{ zU0N9`ee`Z${!=&z@jt&`xJQYex#_AzJCU@n#sXqZ<`D_E8l+Q7BN$KQ>%}CC_qC~% zzxRkl`5-}uI*SWCB@Pjtq(CPbSRU5BA6kcXZxSJJZK%#MXGsam z;NzFEY+$>a%4N8+&h=mNuvPk^iUwcT8=a+@(1Q=cZ?2_M9B-`%0*R>Smnx1an(6pJ zDz1nucqmW4qrs25v>r8gT@5pnzKX7bPVezRgoudE0nluC=RfJ$e-*Hlk{U|7PbjdI z=R$6EX$LrwPu%_CeR~oplSOrAh3$C_!m}|$&qNJ6$30M(6R2SOZObF)aVS=aDfM=< z8mV|6uxwr3V()M=JezQ05Q}5n0z`Rvvgj*f_P-CzJ;(o9(Q>NXI@DdSGQpu#-5VNj zHLOchk=%ZL9XE`jJCz1~Rm0Frm{wE( zyl6?t^wmcgGh-TVX#mjxy_GgGTjY>wpSb9Sn?h=Nth?( zqjIt34WF322#;T&l=Bg*fmm@9%^I6+$95K239);ST>lQYj*CXk3aWeuGGPI8k^4WB zyvDyEa!rK_Zm@*yKpNRm;gLd3s8Q7}+mfLTK2{Y|bgU|Fp1WnTB#_J#!$pVKoHp@LWHIiNcFE&V6`s%;$vNAfKMs_#nW%Tim7=Se1v!#^ z;MQmQK?fuwoYoG-1th2fk;b8q`S7xmws(?P=Ma}*UnF6 z4mvj3xxFXd7*apIi4e?&NVVY^T6M`rgIH@%@4O(S%#Pa`;g6`ps0^arho$4l=L=q* zx|5g=Dm*7nmVD`^bpC@X%ZN~)rcZ|#X#FwmUvL;qd8lBPqk#TaR;HUOKq!?TnOA%+lSMHpB#J0aL&883VxID}Ol5lIaiyJ)TdoT$8l$!pOqOSKg z@cj9Zbo#AUS{mI1vpF8F9W;eP5IxUi4J|}Ct)_j=yGd_RMzmCmpbL`|QWn7^MEy}J ziaX2nj8CzQ))o%)iZeuc^Imb3#-l_wB@rQQY8+vC&KH_9j8;%%zJ$zv!xHnVuf$RbmPr-xs@(YiuVv0uVE-@4gX z)r5ZEbayn1{GKR3rl*ro=w~$W{fOix$rXsRd30GWRweQ1WCnL7fs%=%2#fA|+K^PY zI@cGy>5Z;~?-%==8Tk+qysop*a>M~eY~saENh*bfKUpM4S*!B*XRj zFcI_f?_3xxzi7TwkW~fRm_m_kx+BOo%&wU?2J1vZK3p?Jy2+n<(d<@7YNG zVI{1T0%U@LQzigAw$82C_Y(whAxK3>gYE2AoUnh~Mr$_3^dV0SB zkwniUo8NDKmdnft(hYji3_ofqz6@i+oSi4-UVK z$a9n`R5m0~za5y8JZE-|+dysrp(YIkHQW=l;oA`$3N{5TwqBD`q#sOSp7jo$$dY*@ zuMa20X&g!wQ=AcJp7#vPG+zRXB&KC?!1o~fVpdzL@)C(1jK#I`bU9A5Tw-_-8U0^Y zO)wCX`0YN&d#0IuPo;^mQevZP2(o3o7v7cX(C3i9cgPZ-z What is the "universal" version of the name "Scotty"? + +You likely came up with one of three answers: + +* Scotty +* scotty +* SCOTTY + +Suppose we change the `user_name` variable's capitalization to one of these three forms, regardless of the +original capitalization of the variable. Then we can compare it to the exact string we know it should +match, "Scotty", "scotty" or "SCOTTY". + +How do we change the capitalization of the `user_name` variable? + +Python has handy built-in `methods` that let us transform text. Here are two likely +candidates: + +* [.lower()](https://www.w3schools.com/python/ref_string_lower.asp), which makes text all lowercase. +* [.upper()](https://www.w3schools.com/python/ref_string_upper.asp), which makes text all uppercase. + +We can eliminate the idea of comparing it to the version with an initial capital letter +("Scotty"). While we could achieve it, notice that it would just add an extra step. We'd need +to make everything lowercase first before changing the capitalization of the first letter. + +Here's how we use the `.lower()` and `.upper()` methods: + +`user_name.lower()` applies the `.lower()` method to the text and `returns` a lowercase +version of the text. It doesn't change the value that is stored in `user_name` permanently. + +We can change our comparison to use this `method`, like this: + +```yaml +code: | + if user_name.lower() == "scotty": + secret_message = "Beam Me Up, Scotty" +``` + +We would follow the same pattern to use the `.upper()` method: + +```yaml +code: | + if user_name.upper() == "SCOTTY": + secret_message = "Beam Me Up, Scotty" +``` + +Most programmers will choose the lowercase method first. But it's just habit. Either +method would work fine. + +Suppose we really wanted to try comparing to "Scotty", despite the extra steps it requires. +We can do that by chaining the `.lower()` method with the `.capitalize()` method: + +```yaml +code: | + if user_name.lower().capitalize() == "Scotty": + secret_message = "Beam Me Up, Scotty" +``` + +#### What to take away from this advanced example + +* Logic in Docassemble can take advantage of the full Python programming language +* Python is full of many helpful building blocks. You can look outside of the + Docassemble documentation to find them. + +We've introduced a lot of new concepts here. But you will be able to do a lot +of the Python code in your Docassemble interview by copying and pasting and making +small changes to other working examples. Don't worry about understanding the full +power of the Python language or memorizing the whole library of available functions +and methods. + +## Your assignment + +1. Modify the Logic exercise so that a new secret message is displayed when a + name of your choice is displayed. +2. Make all of the secret messages work regardless of how the user + capitalizes their name. Apply the example that we used to check for upper and lowercase + versions of `scotty` to the other conditions. + +Quinten Steenhuis, June 2020 \ No newline at end of file diff --git a/docs/docassemble_intro/mako.md b/docs/docassemble_intro/mako.md new file mode 100644 index 000000000..e9d5f9aee --- /dev/null +++ b/docs/docassemble_intro/mako.md @@ -0,0 +1,61 @@ +--- +slug: mako +title: The Mako templating language +sidebar_label: Mako +--- + +Docassemble uses Mako for formatting throughout the interview file. Mako is also used inside [Markdown](markdown.md) templates. + +Inside docx template files, you will instead use the [Jinja](jinja2.md) templating language. + +You can read a full reference of Mako at the [project website](https://docs.makotemplates.org/en/latest/syntax.html). + +Mako statements allow you to: + +1. Insert variables +1. Use conditional text +1. Use control structures to display repeated variables + +## Insert variables + +To insert a variable into your document, start with a `$` and surround it with curly braces, like this: `${ variable_name }`. + +It's also possible to include Python code in the place of the variable name. For example, if you had +assigned the value of `my_variable` as below: + +```python +my_variable = "lowercase name" +``` + +Then you could use the `.capitalize()` [method of a string](https://docs.python.org/2.5/lib/string-methods.html) +inside Mako tags to make sure that it started with a capital letter, regardless of how the user entered it inside +your application: + +```mako +Dear ${ my_variable.capitalize() }: + + I am writing to tell you... +``` + +## Use conditional text + +You start conditional text with a % symbol at the beginning of the line. If you want to include a literal % symbol instead, use %%. Just like in Python, you need to include a : at the end of the conditional. The text underneath doesn't need to +be indented. + +```mako +% if person.age_in_years() > 18: +You are an adult +% else: +You are a child +% endif +``` + +## Use control structures + +Control structures start the same way as conditional statements, beginning with a % symbol at the start of the line. + +```mako +% for fruit in fruits: +${fruit} +% endfor +``` diff --git a/docs/docassemble_intro/markdown.md b/docs/docassemble_intro/markdown.md new file mode 100644 index 000000000..a76b85465 --- /dev/null +++ b/docs/docassemble_intro/markdown.md @@ -0,0 +1,39 @@ +--- +slug: markdown +title: The Markdown formatting language +sidebar_label: Markdown +--- + +## The Markdown formatting language +Like YAML, [Markdown](https://daringfireball.net/projects/markdown/) is meant to be a concise, human readable way to represent information: in this case, it represents elements such as font size, headers, bold, italic, formatted lists, and even links to resources on the WWW. Many forums, including the popular Reddit, allow you to format your comments using Markdown. However, it is not quite as expressive as an OOXML, Word, or RTF file would be. + +```markdown +#### Heading level 4 +**Bold text** +_italic text__ + +1. Numbered item 1 +1. Numbered item 2 +1. Numbered item 3 +``` +Turns into: + +#### Heading level 4 + +**Bold text** + +_italic text_ + +1. Numbered item 1 +2. Numbered item 2 +3. Numbered item 3 + +## Including variable text + +In order to add variable text, Docassemble uses the [Mako templating language](mako.md) which +can be inserted directly almost anywhere Docassemble expects text, such as under a +`question`, `subquestion`, `content` or `template` block. + +The [Docassemble documentation](https://docassemble.org/docs/markup.html) covers Markdown fairly well, with examples. + +This section was originally posted as a blog on [Nonprofittechy.com](https://www.nonprofittechy.com/2020/01/17/understanding-docassembles-yaml-interview-format/). \ No newline at end of file diff --git a/docs/docassemble_intro/object-oriented-programming.md b/docs/docassemble_intro/object-oriented-programming.md new file mode 100644 index 000000000..489225298 --- /dev/null +++ b/docs/docassemble_intro/object-oriented-programming.md @@ -0,0 +1,370 @@ +--- +slug: object-oriented-programming +title: Object Oriented Programming in Docassemble +sidebar_label: Object Oriented Programming +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; // Add to the top of the file below the front matter. + + +## Overview of Classes + +Objects are a special type of variable. Instead of holding one piece of information, they can hold several at once. For example, the variable `x` can easily represent the number `10`. But what if you wanted a variable to refer to, for example, an apple? You might want to store several pieces of information about the apple at once. Perhaps you want to know its weight, color, and variety. + +You could store the information in several variables, like this: + +* `apple_weight` +* `apple_color` +* `apple_variety` + +Suppose you need to keep track of 10 different apples. Now you have: + +* `apple2_weight` +* `apple2_color` +* `apple2_variety` +... and so on + +This can quickly become hard to manage. What's more, each time you make a new variable to represent an apple, you introduce the risk of making a mistake. + +In object-oriented programming, you can more easily keep track of your variables by writing a kind of blueprint, or `class definition` that lets you group the related variables together. Once it's part of an object like, this, we call the individual variables `attributes`. An `object` is an `instance` of a `Class` with one or more of those attributes filled-in with specific values. + +You can think of a classes' definition as the column labels in a spreadsheet: + +object | weight | color | variety | calories +---|---|---|---|--- +apple1 | 300 grams | red | Red delicious | 75 +apple2 | 270 grams | green | Granny Smith | 72 + +Or perhaps a blank form: + +Overview of the Docassemble Playground + +In both cases, what the class definition does it give the individual variables a structure. It provides a meaning to a collection of variables, so that every time you create an object, each object will have the same basic outline and you can use it the same way. + +One way to keep track of the different attributes and methods of a class is with a special kind of diagram, called a [UML class diagram](https://tallyfy.com/uml-diagram/#class-diagram). +Here is a UML diagram that represents the `Apple` class: + +[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBBcHBsZSB7XG4gIHdlaWdodDogZmxvYXRcbiAgY29sb3I6IHN0clxuICB2YXJpZXR5OiBzdHJcbn0iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBBcHBsZSB7XG4gIHdlaWdodDogZmxvYXRcbiAgY29sb3I6IHN0clxuICB2YXJpZXR5OiBzdHJcbn0iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ) + +The basic structure of a UML diagram is three boxes: + +1. the class name at the top +1. An optional list of attributes in the middle +1. An optional list of methods at the end + +UML diagrams are just one way to represent a class. It's a standard that can make it easy to quickly communicate the list of different kinds of information that the class contains. + +We also often list the _type_ of each variable after its name in this diagram. In the example diagram, weight is a _floating point_ number, or float (has a decimal). Color and variety are text, or _str_ types. + +Sometimes a class's attributes themselves are classes. You'll see this is common in Docassemble's built-in objects. + +### Class methods + +So far our metaphor has concentrated on information, but something special about `objects` is that you can also group `methods` with the object definition. A `method` is a function that is part of a class definition. When you run an object's method, you are taking an action that uses the information in the class and gives you back something new, such as a true/false value, a number, or text that you can display on the screen. + +For example, an apple might have a method `is_ripe()` that uses the `planted_date` and today's date to tell you if the apple is ready to be picked. It would return `True` if the apple is ripe, and `False` if it isn't ready. + +Here's the updated diagram that shows the `is_ripe()` method: + +[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG4gIGNsYXNzIEFwcGxlIHtcbiAgICB3ZWlnaHRcbiAgICBjb2xvclxuICAgIHZhcmlldHlcbiAgICBpc19yaXBlKClcbiAgfSIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG4gIGNsYXNzIEFwcGxlIHtcbiAgICB3ZWlnaHRcbiAgICBjb2xvclxuICAgIHZhcmlldHlcbiAgICBpc19yaXBlKClcbiAgfSIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9) + +If we had an Apple object named `jons_apple`, we could display its ripeness in our interview like this: `${jons_apple.is_ripe()}`. + +### Inheritance + +A useful aspect of classes is that one class can rely on, or inherit, the definition of a different class. We call the more generic class the `parent` and the more specific class the `child` or `sub-class`. Below, the `Apple` class inherits from the more generic `fruit` class. It has its own `is_ripe()` method, because different kinds of fruit ripen at different times. But it can use all of the same attributes as the original Fruit class. + +[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBBcHBsZSB7XG4gIGlzX3JpcGUoKTogYm9vbFxufVxuXG5BcHBsZSA8fC0tIEZydWl0XG5cbmNsYXNzIEZydWl0IHtcbiAgd2VpZ2h0OiBmbG9hdFxuICBjb2xvcjogc3RyXG4gIHZhcmlldHk6IHN0clxuICBpc19yaXBlKCk6IGJvb2wgIFxufSIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBBcHBsZSB7XG4gIGlzX3JpcGUoKTogYm9vbFxufVxuXG5BcHBsZSA8fC0tIEZydWl0XG5cbmNsYXNzIEZydWl0IHtcbiAgd2VpZ2h0OiBmbG9hdFxuICBjb2xvcjogc3RyXG4gIHZhcmlldHk6IHN0clxuICBpc19yaXBlKCk6IGJvb2wgIFxufSIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9) + +One benefit of inheritance is reducing the code you need to write. But inheritance also allows you to write your own very specific class, such as `Macoun` or `Cortland`, without having to update any functions in your program that might expect to work on every kind of `Apple` or even `Fruit`. + +Any function that expects to work with a Fruit object will also be able to work with an Apple object if the Apple object _inherits_ from the Fruit class. Inheritance lets you layer on new features without breaking compatibility with the old one. + +### Object conventions and syntax + +#### Naming conventions +One convention that is true for almost every programming language that has objects is that we use capital letters at the beginning of words, and without spaces, for class names. For example, if we have a class the represents rectangles, we would normally name it `Rectangle` or maybe `FourSidedFigure`. This capitalization style is called [Pascal Case](https://en.wikipedia.org/wiki/Camel_case) after the first programming language to use this variable naming standard, [Pascal](https://en.wikipedia.org/wiki/Pascal_(programming_language)). (Another name for this style is sometimes camel case, although "camel case" is often reserved for variable names where only the second word is capitalized, looking a little like a camel's hump.) + +When you see a name written in PascalCase style, it's a good clue that you are looking at a class name. + +When you use an object, you still use lower case names with underscores between each word for variable names. For example, `my_apple` could be an instance of the `Apple` class. + +#### Using variables and methods + +You access an object's attributes by using `.` `dot notation`. This simply means the name of the attribute comes after the object's name, separated by a `.`. For example: `my_apple.weight = 100.7`. + +Methods also use dot notation. Like functions, you call a method with two parentheses after the method's name: `apple.is_ripe()`, optionally with one or more parameters. `apple.nutritional_value("human")`. + +### Special class constructor methods `__init__()` and `init()` + +Python Classes all have a special method, named `__init__`. This is called the class constructor. + +> Note: if you write your own class, Docassemble objects should never directly replace the `__init__()` method. Instead, you will use the method `init()`. + +Any time that Docassemble (or Python) creates a new object of this class, the `__init__` method will run. + +What is the purpose of a class constructor? The class constructor does any setup work that your object needs. For example: it could take the parameters and assign them to attributes to use later; it could run +some calculations in advance; or download information from the Internet that it stores for faster access. Just like regular functions and methods, sometimes a class constructor has parameters. For example, a class that represents a Rectangle might have two parameters: side1_length, and side2_length. + +### Special method `__str__()` + +Objects have another special built-in method that they expect to see, named `__str__()`. The `__str__()` method will return a string (text) representation of the object. This is very useful for use inside Docassemble, as we often display information on the screen. For example, the standard string representation of a person is: person.name.first + person.name.middle + person.name. Using the `__str__()` method allows you to just mention the person object without having to write out first name, last name, etc. + +```markdown +${user} +``` + +Is the equivalent of writing: + +```markdown +${user.__str__()} +``` + +Think of `__str__()` as a convenient shortcut. It runs anytime you write the object's name that Docassemble expects to see text, like in an interview or in a template. + +Writing `${user}` would print out `Quinten Steenhuis` if `user.name.first` is Quinten and `user.name.last` is Steenhuis. + +## Classes in Docassemble + +OK. So far, we learned that an object is a type of variable that can store different, related information in one place. You may not create your own classes for some time. However, this background is helpful for making use of Docassemble's built-in helper classes, methods, and functions that expect to work on a built-in class. + +### How Docassemble classes differ + +In order to work with Docassemble, every object should inherit from the `DAObject` class. + +You may have seen that in most class definitions, you typically list all of the attributes when you create the class. In Docassemble, missing variable definitions are needed to trigger a question being asked. So, instead, you will normally leave those attributes undefined at the class definition time. You can include a comment that lists all of the attributes, and also make use of those attributes inside methods. + +Docassemble objects also expect to know their own name, stored as a special attribute `instanceName`. If you use an existing class, you don't need to worry about this special feature. Docassemble handles this for you when you use the `objects` block. If you write your own classes, this is something that is important to know. + +### Using an object inside Docassemble + +#### The `modules` block + +If you write your own `Class`, you need to tell Docassemble that you plan to use it with a modules block: + +```yaml +--- +modules: + - module_from_other_package + - .module_in_this_package +``` + +Module files are just Python code. In the background, the modules block tells Docassemble to `import` a Python file with the classes and functions you need for your interview. + +You do not need to use a modules block for any of the built-in classes. They live in a Python file too, but Docassemble automatically includes it for you. + +#### The `objects` block + +Unlike regular Python/Docassemble variables, objects need to be named and defined before they are used. +You create an `instance` of an object and assign its name with the `objects` block: + +```yaml +--- +objects: + - user: Individual +``` + +Underneath the `objects` keyword, you can write the names of as many object variables that you need for your +interview. Just like the fields statement, this is a list. On the left of the `:`, write the variable name +that you want Docassemble to use. On the right, write the name of the class that the object is a member of. + +In the background, this tells Docassemble to create a new Python object with the name `user`. It will also run the `__init__()` method of the class and do whatever setup is needed for your object. + +If your class expects any parameters when you make a new object, you can pass those to the `__init__()` method with a special Docassemble +method named `.using()`: + +```yaml +objects: + - user: Individual.using(parameter1=value1, parameter2=value2) +``` + +You don't need to fully understand this for now. Just remember that sometimes you want to customize your object when you create it. The .using() method is a way to do that inside Docassemble. We'll explain this in more detail when we discuss working with repeated information (Groups). + +#### Working with the object as a variable + +Treat object `attributes` just like ordinary variables. For example, you can use an ordinary Docassemble `field` to assign the value of an attribute: + +```yaml +--- +question: | + Your birthday +fields: + - Enter date: user.birthdate + datatype: date +``` + +## Docassemble's built-in objects + +Docassemble has a large number of [built-in Classes](https://docassemble.org/docs/objects.html#stdclasses), as well as optional Classes designed to [simplify legal matters](https://docassemble.org/docs/legal.html#tocAnchor-1-2). Many of these are utility classes that help write an interview in a more abstract way, but don't represent real-world objects. + +You will most likely use these few classes representing things in the physical world again and again: + +* [`Person`](https://docassemble.org/docs/objects.html#Person), representing a legal Person that does not need to be an individual (e.g., it could be a corporation) +* [`Individual`](https://docassemble.org/docs/objects.html#Individual), representing an individual person +* [`Name`](https://docassemble.org/docs/objects.html#Name) and [`IndividualName`](https://docassemble.org/docs/,objects.html#IndividualName), representing a name +* [`Address`](https://docassemble.org/docs/objects.html#Address), representing an address in the real-world, together with its different components (street, longitude/latitude, etc). + +They are used throughout Docassemble. Several built-in functions also expect these objects as parameters. + +The third party Income class is also useful for working with financial information: + +* [`Income`](https://github.com/GBLS/docassemble-income) + +It has several advantages over the Docassemble built-in Classes to represent income information. + +You may define your own objects as [`DAObject`](https://docassemble.org/docs/objects.html#DAObject)s and benefit from the neater method of organizing related attributes in one variable, without needing to write your own class definition. + +You will also often work with the [`DAList`](https://docassemble.org/docs/groups.html#gather%20list) and [`DADict`](https://docassemble.org/docs/groups.html#gather%20dictionary) objects, which allow you to gather repeated information. + +### The Individual Class + +The Individual class represents a real-world individual. E.g., the person who is using your interview. + +Here's a diagram of for the key fields in the [Individual](https://docassemble.org/docs/objects.html#Individual) class: + +[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBJbmRpdmlkdWFsIHtcbiAgbmFtZTogSW5kaXZpZHVhbE5hbWVcbiAgYWRkcmVzczogQWRkcmVzc1xuICBiaXJ0aGRhdGU6IERBRGF0ZVRpbWVcbiAgZ2VuZGVyOiBzdHJcbiAgZW1haWw6IHN0clxuICBwaG9uZV9udW1iZXI6IHN0clxuICBtb2JpbGVfbnVtYmVyOiBzdHJcbiAgYWdlX2luX3llYXJzKClcbiAgYWRkcmVzc19ibG9jaygpXG4gIHNhbHV0YXRpb24oKVxuICBwcm9ub3VuKClcbiAgcG9zc2Vzc2l2ZSgpXG59IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBJbmRpdmlkdWFsIHtcbiAgbmFtZTogSW5kaXZpZHVhbE5hbWVcbiAgYWRkcmVzczogQWRkcmVzc1xuICBiaXJ0aGRhdGU6IERBRGF0ZVRpbWVcbiAgZ2VuZGVyOiBzdHJcbiAgZW1haWw6IHN0clxuICBwaG9uZV9udW1iZXI6IHN0clxuICBtb2JpbGVfbnVtYmVyOiBzdHJcbiAgYWdlX2luX3llYXJzKClcbiAgYWRkcmVzc19ibG9jaygpXG4gIHNhbHV0YXRpb24oKVxuICBwcm9ub3VuKClcbiAgcG9zc2Vzc2l2ZSgpXG59IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0) + +Often class diagrams will show the type of each attribute, which the diagram above includes. In the diagram, on the left is the name of the attribute. On the right is the attribute's _type_. `str` is the name for text (or string) type variables. + +Notice that some of the attributes are also objects, and you need to assign values to attributes of those object, instead of directly at the top level. For example: `name` and `address` are objects of class `IndividualName` and `Address`. `name` has attributes `first`, `middle`, and `last`. An `address` has attributes `address` (street address), `city`, `state`, `zip`, and `unit`. See below for more details. + +There are some handy shortcuts that you can see in the diagram. `age_in_years()` does some date math for us. `address_block()` displays the address in one format (each item on its own line). As well, we can use the hidden `__str__()` methods when we refer to an Individual (displays the user's full name) or their address (displays the full address on one line). + +Here is a sample interview that assigns a value to all of an Individual's built-in attributes: + +```yaml +objects: + - client: Individual +--- +mandatory: True +comment: | + We are using this block to control the order of questions +code: | + client.name.first + + # We only need to reference one variable on a screen + # to tell Docassemble to show that screen + client.birthdate + + client.address.address + show_results +--- +question: | + What is your name? +fields: + - First name: client.name.first + - Middle name: client.name.middle + required: False # Not everyone has a middle name, so this is marked optional + - Last name: client.name.last +--- +comment: | + In a real question, you should probably make some of these fields optional, such as phone/email. +question: | + Tell us about yourself +fields: + - When were you born?: client.birthdate + datatype: date + - What is your gender?: client.gender + - What is your phone number: client.phone_number + - What is your cell phone number?: client.mobile_number + - What is your email address?: client.email + datatype: email # This uses the email formatting rules to help the user avoid mistakes in typing their address +--- +question: | + Where do you live? +fields: + - Street address: client.address.address + - Apartment or Unit: client.address.unit + required: False # Another optional field + - City: client.address.city + - State: client.address.state + - Zip: client.address.zip +--- +comment: | + This is an ending screen. It doesn't have any fields, and + we use the event specifier to give it a variable name we can refer to. +event: show_results +question: | + Results +subquestion: | + Hello, ${client}. Your gender is ${client.gender} + + Your age is ${client.age_in_years()} + + You live at ${client.address} + + You can be reached at ${client.email}, ${client.phone_number}, or ${client.mobile_number}. +``` + +### IndividualName + +IndividualName is a pretty simple class. It helps us store the different part of a user's name. + +Here is a class diagram for the key fields in the IndividualName class: + +[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBJbmRpdmlkdWFsTmFtZSB7XG4gIGZpcnN0XG4gIG1pZGRsZVxuICBsYXN0XG4gIGZ1bGwoKVxuICBmaXJzdGxhc3QoKVxuICBsYXN0Zmlyc3QoKVxufSIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBJbmRpdmlkdWFsTmFtZSB7XG4gIGZpcnN0XG4gIG1pZGRsZVxuICBsYXN0XG4gIGZ1bGwoKVxuICBmaXJzdGxhc3QoKVxuICBsYXN0Zmlyc3QoKVxufSIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9) + +When we refer to just an IndividualName, Docassemble runs the `.full()` method and returns the full name with a middle initial. The `.firstlast()` method leaves out the middle name. The `lastfirst()` method shows the name like this: `Last, First`. + +### Address + +The Address object is very powerful. It doesn't just store the different parts of an address: it can store a latitude and longitude, and it can be `geolocated` using Google as the engine to fill in information the user doesn't know (such as county). + +Here's a class diagram for the key fields in the [Address](https://docassemble.org/docs/objects.html#Address) class: + +[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBBZGRyZXNzIHtcbiAgYWRkcmVzc1xuICB1bml0XG4gIGNpdHlcbiAgc3RhdGVcbiAgemlwXG4gIGNvdW50eVxuICBjb3VudHJ5XG4gIGxvY2F0aW9uOiBMYXRpdHVkZUxvbmdpdHVkZVxuICBnZW9sb2NhdGUoKVxuICBub3JtYWxpemUoKVxuICBvbl9vbmVfbGluZSgpXG59IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5jbGFzcyBBZGRyZXNzIHtcbiAgYWRkcmVzc1xuICB1bml0XG4gIGNpdHlcbiAgc3RhdGVcbiAgemlwXG4gIGNvdW50eVxuICBjb3VudHJ5XG4gIGxvY2F0aW9uOiBMYXRpdHVkZUxvbmdpdHVkZVxuICBnZW9sb2NhdGUoKVxuICBub3JtYWxpemUoKVxuICBvbl9vbmVfbGluZSgpXG59IiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0) + +If you want to verify the user's address was entered correctly, you have at least two options: + +1. Use the `address autocomplete` feature. This tells Docassemble to use Google to fill-in the address as you type. +1. Use the `.normalize()` method to correct any mistakes in the address after you collect it from the user. This will overwrite the user's inputs; see the [full documentation](https://docassemble.org/docs/objects.html#Address) to learn about alternatives. + +Here's a sample interview that uses `address autocomplete` to fill-in the user's address as they type: + +```yaml +objects: + - the_address: Address +--- +mandatory: True +code: | + results_screen +--- +question: | + Enter an address +fields: + - Address: the_address.address + address autocomplete: True # This turns on auto completion + - Unit: the_address.unit + required: False + - City: the_address.city + - State: the_address.state + - Zip: the_address.zip + required: False +--- +event: results_screen +question: | + Results +subquestion: | + ${the_address} + + ${map_of(the_address)} +``` + +In the example above, we use the built-in [`map_of`](https://docassemble.org/docs/functions.html#map_of) function to display a Google Map of the address. + +Suppose you collected the user's address in the interview above. You could `normalize` it (fix it to match what Google thinks is a valid address) in an interview snippet like this: + +```yaml +mandatory: True +code: | + the_address.normalize() +``` + +One reason to use this would be to add the user's `county` and `country` without making them type those fields in. This could be helpful if you wanted to show the user a court that serves their county, while you know that most users don't know the name of their county. + +## Further reading + +* https://www.nonprofittechy.com/2018/09/12/object-oriented-programming-for-document-assembly-developers/ +* https://docassemble.org/docs/objects.html +* https://www.programiz.com/python-programming/object-oriented-programming +* https://en.wikipedia.org/wiki/Object-oriented_programming \ No newline at end of file diff --git a/docs/docassemble_intro/practical-guide-docassemble.md b/docs/docassemble_intro/practical-guide-docassemble.md new file mode 100644 index 000000000..906082e34 --- /dev/null +++ b/docs/docassemble_intro/practical-guide-docassemble.md @@ -0,0 +1,56 @@ +--- +slug: practical-guide-docassemble +title: What should you use Docassemble for? +sidebar_label: What should you use Docassemble for? +--- + +## What should you use Docassemble for? + +If we look over our [legal technology taxonomy](legal-tech-overview/legal-tech-overview.md), +which of these tasks is Docassemble best suited for? + +Docassemble's main metaphor is a linear series of step-by-step questions. Many of the things Docassemble does best are controlled by that choice: + +* Expert systems +* Filling forms and templates (document assembly) +* Referral and triage +* Intake tools +* Tickler/reminder systems +* Gathering digital signatures, documents, or more from participants in litigation (i.e., collecting your client's documents that must be provided in discovery, getting a signature on a retainer or release, etc) +* Database frontends (i.e., traditional create/read/update/delete systems) + +## Examples + +Most expert systems built-on Docassemble also include filling forms and templates, but they don't need to. + +The [Court Forms Online](https://courtformsonline.org) site contains many examples +of Docassemble interviews for use in Massachusetts. + +## What are Docassemble's unique strengths? + +### Customizability +The greatest strength of Docassemble is the ability for Docassemble to talk to other existing systems. Because Docassemble is code based, you can easily add in new integrations without waiting for the platform author to add them. + +For example, document assembly has existed for decades. Docassemble makes it possible to have document assembly _with information pulled from a case management system_. Of course some platforms integrate with some case management systems. But Docassemble allows you to use any CMS that has a public API or whose database you can access. This may take time, but it's doable. This makes it safe to invest in Docassemble, without fear that the next system that comes along won't be supported. + +### Working at scale +Docassemble excels at the development of large, complex apps. While the drag-and-drop metaphor is great for infrequently updated and small, contained systems, larger apps have always been built with written code. Docassemble allows you to use traditional programming tools such as linters and code auto-completion while focusing on your core goal. + +Plain-text code can use `diff` to compare and track changes; easily be versioned; easily be searched and take advantage of code-folding and other techniques to provide an overview without requiring drilling down through hundreds of dialogs or menus. Using object-oriented techniques minimizes duplicate code; CSS and other web-development standbys allow you to manage and tweak the appearance of your system without making changes in hundreds of places for each tweak. + +### Internationalization and accessibility + +Docassemble is a modern tool, built with multiple languages and accessibility for different levels of ability in mind from the beginning. + +A single Docassemble interview can easily be written in multiple languages. The default themes are designed to comply with the Americans with Disability Act / Web Content Accessibility Guidelines ([WCAG](https://en.wikipedia.org/wiki/Web_Content_Accessibility_Guidelines)). Most of the important elements use `aria` tags to aid screen readers. And it even includes a built-in screen reading tool and readability analysis scoring system. + +### Built-in features + +Docassemble's built-in features could easily be dedicated tools in their own right. For example: + +* Collecting uploaded files from users +* Optical character recognition +* Digital signatures +* Scheduled tasks that can run without your user being present, for things such as court date reminders +* Integration with SMS and email + diff --git a/docs/docassemble_intro/python.md b/docs/docassemble_intro/python.md new file mode 100644 index 000000000..6b2de7b62 --- /dev/null +++ b/docs/docassemble_intro/python.md @@ -0,0 +1,239 @@ +--- +slug: python +title: Basics of the Python programming language +sidebar_label: Python basics +--- + +## About Python +[Python](https://python.org) is one of the worlds [most popular programming languages](https://www.infoworld.com/article/3401536/python-popularity-reaches-an-all-time-high.html) for a good reason. It is easy to use, while familiar enough that people who have tried other languages can jump right in. + +Two unique features of Python for people who come from other programming languages are that indentation marks the end and beginning of Python statements (rather than curly braces {}); and that you do not need to use a semi-colon at the end of lines. + +Python has a very large collection of [pre-built "modules"](https://docs.python.org/3/library/) and an even larger library of [modules contributed by third-party developers](https://pypi.org). This can make it fast and easy to build very capable applications. + +## The basics of programming + +Docassemble developers may use only a few features of Python. Still, let's take some time to put the different aspects of any programming language in context, and then talk about how they are used in Python and in Docassemble. + +Programming involves two basic concepts: data, and instructions. Data is stored in variables which usually are provided when the program is run. Instructions tell the computer what to do with the data it receives. + +### Variables +Variables could be considered "buckets" that hold information. Just like `x` in an algebra equation, we don't know the value until our program is run. Docassemble variables (or fields) are also Python variables. + +### Datatypes, Datastructures, and Objects + +Variables each have an associated "type". For example, a number is treated differently by the computer than a piece of written text. `"Roger" + "Helicopter"` should really do something different than `1 + 2`. Unlike many languages, in Python variables can change type dynamically. The same variable can be a number at one time, and text later in the same program. + +Python uses the following _basic_ or _primitive_ datatypes: + +* `int` (integer, or whole numbers) +* `float` (floating point, or fractional numbers) +* `str` (string, or text) +* `bool` (Boolean, or `True`/`False`) +* `None` (null value, or undefined) + +Python includes the following built-in data structures: + +* `list` (an array, or list of one or more variables) +* `dict` (a dictionary, or associative array) +* `tuple` (like a list, but cannot be changed (immutable)) +* `set` (like a list, but has unique values) + +Docassemble has its own implementation of these datastructures, named `DAList`, `DADict`, and `DASet` respectively. + +Python also supports object oriented programming principles that allow you to create your own complex types. A `class` is a blueprint for an `object`. When you use objects, you gather a list of `attributes` together with `methods` that can act on the attributes to provide standardized behavior and model your program against the real world. + +When you want to create a new class, you can `inherit` the properties of an existing class. For example, if you had a class `apple` it may inherit properties from a `fruit` class. + +In Docassemble, you are encouraged to have every object inherit from the base object named `DAObject`. You will make frequent use of the [`Individual`](https://docassemble.org/docs/objects.html#Individual) object in Docassemble, which collects attributes of a person together: such as a first and last name, birthdate, address, and more. + +### Tests and logical operators + +You will use `Boolean` values again and again in Docassemble. Sometimes you will get a True/False value directly from the user. Other times, you will use a logical test. Python's comparison operators should look familiar to you. + +* `==` (tests for equality. **Note**: `=` is reserved as the assignment operator) +* `!=` (tests for inequality) +* `>` (greater than) +* `<` (less than) +* `>=` (greater than or equal) +* `<=` (less than or equal) + +Logical operators include: + +* `and` (combines two tests and will be True if both are True) +* `or` (combines two tests and will be True if either is True) +* `not` (returns True if the test is False) + +Operators can be grouped with round brackets `()`. + +See [W3Schools](https://www.w3schools.com/python/python_operators.asp) for a more complete list. + +### Assignment operators + +In Python, you `assign` a value to a variable using an assignment operator. You can get by with just one operator: `=`. For example: + +```python +z = 1 # Assigns the value 1 to z +z = "Hello, World" # Assigns the text Hello, World to z. +``` + +You may also find yourself using the shorthands `+=`, `-=` to add a value and subtract a value in one statement. + +```python +z = 1 +z += 1 # the value of z is now 2 +z -= 3 # the value of z is now -1. +``` + +Python also supports basic arithmetic operators. + +* `+` (addition) +* `-` (subtraction) +* `*` (multiplication) +* `/` (division) +* `%` (modulus, or remainder) + +There are many more assignment operators. See [W3Schools](https://www.w3schools.com/python/python_operators.asp) for a more complete list. + +### Control structures + +`Control structures` let you dictate the sequence of operations that your program will take in different circumstances. There are three basic types of control structures: `sequence`, `selection` and `repetition`. + +#### Sequence (ordering instructions) +`Sequence` controls the order that your instructions to the computer are executed. In Python, the order is implied, top to bottom, in the order that you write each instruction in the file. + +```python +operation1 +operation2 +operation3 +``` + +#### Selection (if then statements) +`Selection` controls which instructions are executed, and which ones remain idle. In Python, the basic `selection` statement is the `if` statement. + +An `if` statement is composed of the keyword `if`, followed by a test, and then a colon. The code that follows will execute if the test resolves to a Boolean True. + +```python +if test1: + operation1 +``` + +You can add-in additional tests as part of the main `if` statement with the keywords `elif` and `else`. + +```python +if test1: + operation1 +elif test2: + operation2 +else: + operation3 +``` + +If you are familiar with other programming languages, you may be surprised that there is no `switch` statement in Python. If not, just ignore it :). `Switch` statements are just a series of `if/elseif` statements. Python does include the `ternary` operator, or one-line `if` statement: + +```python +x = 1 if test1 else 2 # Sets x to 1 if test1 is true. Otherwise, it sets x to 2 +``` + +The `ternary` operator can be hard to read so it's usually best to avoid it, but sometimes it's convenient to include in Docassemble in a place where you can only fit one line of code, or inside a Mako statement (`${}`). + +#### Repetition (loops) + +Docassemble can easily give you the power to gather repeated information. You usually gather that information into a `list` or `dictionary`. You will use Python's repetition control structures to work with those repeated values. + +##### `for` Loops + +Suppose you have a list with following values: + +`[1,2,3,4]` + +Python makes it simple to `iterate`, or `loop` over each value in the list without needing to keep track of the total number of elements. If you have used `C`, `Java` or `JavaScript`, this is a convenient built-in feature. + +```python +for number in [1,2,3,4]: + print number +``` + +Will print + +``` +1 +2 +3 +4 +``` + +#### `while` Loops + +`while` loops combine a test with repeated action. In the example below + +```python +i = 1 +while i <= 4: + print(i) + i += 1 +``` + +Will print + +``` +1 +2 +3 +4 +``` + +> **Here be dragons** Be careful with `while` loops! +> +> Notice that it's up to you to make sure your `while` loop actually ends. It's possible (and easy while you're learning) to create a `while` loop that is stuck in an infinite series of repetitions, crashing the server. + +#### A note on the Mako and Jinja variations + +[Mako](mako.md) statements let you include Python control structures right inside your interview file, as well as inside Markdown templates. When you want to use the Mako version of a control structure, just include a % symbol at the start of the line. The text underneath does _not_ need to be indented. But you must include the ending keyword. + +```markdown +% if statement: +Conditionally displayed text +% endif +``` + +[Jinja](jinja2.md) statements allow you to include something almost, but not quite entirely unlike Python inside a Docx template. There are many variations to be aware of, but usually it's best to learn about them one at a time. Jinja statements do not need to be on their own line. There is no colon `:` at the end of the opening keyword. They do require an ending keyword. + +``` +{% if mytest %} Some conditionally displayed text {% endif %} +``` + +### Functions and methods + +Python `functions` and `methods` are an abstraction that lets you "save" a series of instructions and operations that you will want to use again and again. In some languages, these are called `procedures`. You should write some of your own functions. But there is a large [library of functions](https://docassemble.org/docs/functions.html) built-in to Docassemble that you should also peruse when you are about to do something that you think other developers have run into in the past. + +You create a function in Python with the `def` keyword. Functions have `arguments` and a `return value`. Consider the following useless example: + +```python +def times(x,y): + return x * y +``` + +Notice that the function's contents are indented below the `def` keyword. + +`methods` are not logically distinct from `functions`. A `method` is a function that is part of an `object`'s blueprint, or `class`. The `method` typically operates on the object itself, or transforms and returns one or more attributes of the object. + +For example: + +```python +person.age_in_years() # Uses the person's birthdate attribute to calculate their current age and return it +``` + +Docassemble has many [built-in objects](https://docassemble.org/docs/objects.html). When you are looking in the function library, you might miss a useful shortcut because you do not know that it is considered a method of a class rather than a standalone function. + +## Learning more about Python + +### Books and tutorials + +* [Python Beginner's Guide](https://wiki.python.org/moin/BeginnersGuide) +* [Python Tutorial](https://docs.python.org/3/tutorial/) +* [Automate the Boring Stuff](https://automatetheboringstuff.com/) + +### Online Python interpreter (run Python without installing it to your computer) + +* [Online runtime environment](https://www.onlinegdb.com/online_python_interpreter) diff --git a/docs/docassemble_intro/repeated-information.md b/docs/docassemble_intro/repeated-information.md new file mode 100644 index 000000000..c094d8646 --- /dev/null +++ b/docs/docassemble_intro/repeated-information.md @@ -0,0 +1,821 @@ +--- +slug: repeated-information +title: Working With Repeated Information +sidebar_label: Repeated Information (Groups) +--- + +import useBaseUrl from '@docusaurus/useBaseUrl'; + +## Repeated information, groups, and data structures (are all the same thing) + +Many variables are 1 to 1. Each person has one birthdate, one full name, etc. But right away we run into information where one person has more than one of the same kind of thing. For example: each person might have more than one phone number; child; and more. + +One way to handle that could be to make a bunch of variables: child1, child2, etc. That gets messy fast! In computer programming, we can replace a bunch of variables with one variable that stores that _repeated_ information, giving it a single variable name like `children`. Those special variables are called data structures in computer science. Docassemble calls them "groups". The most common data structures you will run into in Docassemble are lists, dictionaries, and sets. + +## Introduction to lists + +The first kind of data structure you should learn about is called a _list_. Lists can store any kind of repeated information: numbers, text, objects, or even other lists. Lists are similar to arrays in other computer programming languages. + +Here's a portion of a real paper intake form from Greater Boston Legal Services: + +Intake showing list of household members + +Right away, the first thing you might notice is that our form can only handle up to 9 people. That probably is plenty for most households, but not every household (I come from a family of 11!). + +If we wanted to model this intake form in Docassemble, we might start out by using a _list_ named `household_members`. + +Below is a short Python program that demonstrates two ways to handle a list of children: as separate variables, and as one list. + +Click the "run" button to run the code sample. After you have run it once, change the value of `use_variables` to False and run it again. + + + +For easy reference, here is the full code: + +```python +use_variables = True +child1 = "James" +child2 = "Alice" +child3 = "Richard" +children = ["Alexandra","Robert","Lisa"] +if use_variables: + print(child1) + print(child2) + print(child3) +elif not use_variables: + for child in children: + print(child) +``` + +We can see that using several variables and using a list can get us to the same outcome. But using the list is more flexible: we can keep track of many items without having to create a variable name for each item in advance. + +### Accessing items in a list + +Once we have created a list, we can access the items in it like this: + +```python +children = ["Jenny","Shakira"] +children[0] +``` + +Try copying and running the code above (one line at a time) in the interactive Python console below: + + + +The number in the square brackets is called an **index**. In Python, the first item in a list has an **index** of 0, not 1. + +Inside the computer, our list is stored like a spreadsheet: + +Index | Value +------|------ +0 | Jenny +1 | Shakira +2 | Beyonce + +We read down to the row (index) we want to find the value stored in that row. + +After running the code above, try typing `children[1]`. What value does it have? What happens if we try to access `children[2]`? + +### Changing the value of a list + +Changing the value of an item in a list is the same as changing the value of a variable. We use the item's index to say which item we want to change: + +`children[1] = "Sean"` + +You can add items to a list by using `.append()`, like this: + + + +Try adding a new name to the list. + +Here's the code for reference: + +```python +children = ["Alexandra","Robert","Lisa"] +print(children) +children.append("Miles") +print(children) +``` + +### Deleting an item in a list + +You can delete an item in a list two different ways: by value, or by index. + +By value, using `.remove()`: + +```python +shopping_list = ["Eggs","Milk","Cereal"] +shopping_list.remove("Milk") +print(shopping_list) +``` + +By index, using `.pop()`: + +```python +shopping_list = ["Eggs","Milk","Cereal"] +shopping_list.pop(1) +print(shopping_list) +``` + +### `for` loops + +In the first example, we used a `for` loop to print the name of each child. Let's take a closer look at that now. + +```python +children = ["Alexandra","Robert","Lisa"] +for child in children: + print(child) +``` + +When you use a `for` loop like the one above, Python will run the same series of actions for each item in the list. The variable `child` is a temporary variable. Each time the loop runs, the value changes. + +1. On the first loop, `child` is equal to "Alexandra" +1. On the second loop, `child` is equal to "Robert" +1. On the third loop, `child` is equal to "Lisa" + + +### The DAList class in Docassemble + +When you use a list in Docassemble and want it to handle collecting items for you, you will create an object of class `DAList` instead of creating it using `my_list = []`. Once you've done that, you can access/modify, and otherwise work with the items the same way you do in Python. + +As a reminder, we use the `objects` block to create a Docassemble object. Here's an interview that creates a DAList: + +```yaml +objects: + - my_list: DAList +``` + +You can read [Docassemble's documentation about lists](https://docassemble.org/docs/groups.html#list). + +### More explorations of lists + +* https://www.w3schools.com/python/python_lists.asp +* https://teachcomputerscience.com/gcse-python/lists/ + +## Dictionaries + +Like a list, a dictionary is a data structure that can store repeated information. The main difference is that in a list, the index is _numeric_. In a dictionary, the index is a keyword. Dictionaries are similar to associative arrays or hashtables in other computer programming languages. + +If a list is a good way to store a unknown number of items, a dictionary is a good way to store an unknown number of items that match exactly one category. + +Sticking with our intake analogy, let's think about an intake that asks someone to report all of their expenses. We know everyone has some expenses, and we want to be able to work with each expense separately. For example: + +* Rent +* Food +* Utilities +* Credit card payments +* Student loan payments +* Medical bills + +Here's a small example of a Python dictionary: + + + +Run the code sample above. Notice that when we use a `for` loop on this dictionary, on each loop the variable gets the keyword, or index of the dictionary. + +A python dictionary is created with curly braces, like this: `{}`. Each entry is a keyword, followed by a : and then a value. Commas separate multiple pairs of key/value. + +### Accessing items in a dictionary + +Once we have created a dictionary, we can access items in it like this: + +```python +expenses = { + "rent": 1000, + "utilities": 300, + "food": 400, + "credit cards": 120, + "student loans": 1000, + "medical bills": 200 +} +print(expenses['rent']) +``` + +This is very similar to how we access an item in a list. A dictionary is a lot like a spreadsheet where each row has a unique name instead of a number. The difference is that the **index** can be a descriptive word (or even a sentence) instead of a number. A dictionary item's index is called a **key**. + +Key | Value +----|------ +rent | 1000 +utilities | 300 +food | 400 + +For a quick example of why using a dictionary is powerful, let's introduce the `sum()` function. Try running the code sample below. + + + +Try changing some of the numbers and see how the value that is printed out changes at the same time. + +In the example above, we use the `.values()` method of a dictionary to get all of the values as one list. Then we used `sum()` to add all of the numbers together. Storing items in a dictionary lets us label them, while still working on them as a group. It gives us a little more context than a list, where we only would know that one expense was the first, second, third, and so on. + +### Adding a new item to a dictionary + +You can add a new item to a dictionary simply by referencing a key that you haven't used yet. Referencing an existing key will change the value stored at that key. + +Run the code sample below. Try adding an expense for automobile insurance. + + + +### Docassemble's DADict class + +When you use a dictionary in Docassemble and want it to handle collecting items for you, you will create an object of class `DADict` instead of creating it using `my_dict = {}`. Once you've done that, you can access/modify, and otherwise work with the items the same way you do in Python: you can refer to `my_dadict["keyword"]` and use the method `.update()` to combine two DA dictionaries. + +As a reminder, we use the `objects` block to create a Docassemble object. Here's an interview that creates a DAList: + +```yaml +objects: + - my_dict: DADict +``` + +You can read [Docassemble's documentation about dictionaries](https://docassemble.org/docs/groups.html#dictionary). + +### More explorations of dictionaries + +* https://www.w3schools.com/python/python_dictionaries.asp +* https://realpython.com/python-dicts/ + +## Introduction to sets + +Sets come from the world of mathematics: think [Venn diagrams](https://en.wikipedia.org/wiki/Venn_diagram). In a set, each item can only appear exactly once. No duplicates allowed. Items in a set don't have an index _or_ a key, unlike items stored in dictionaries and lists. + +Suppose you go on a bird watch. You want to know how many different species of birds you see. If you store the names of each bird in a set, each bird species appears only once, so you don't have to worry about duplicates. + +Run the code sample below. + + + +Notice that when we stored the bird names in a list, we had duplicates. When we use a set instead, each bird species only appears one time. Try adding a new bird species to both the set and to the list and see what happens when you run it again. + +An alternative Python way to create a set is with curly braces, like the example below: + +```python +birds = {"Blue jay","Pileated woodpecker","Ivory billed woodpecker"} +``` + +### Accessing items in a set + +An item is in a set, or not. It doesn't have an index or a key. The items don't have any order to them. You can use standard mathematical operations on a set, such as Union, Intersection, Difference, etc. You can use a `for` loop to work on the whole list at once. + +```python +birds = {"Blue jay","Pileated woodpecker","Ivory billed woodpecker"} +for bird in birds: + print(bird) +``` + +See the explorations below for more about operations on sets, including use of the union and intersection operators. + +### Turning lists into sets + +Sometimes, you may want to collect everything in a list, and then turn it into a set later, so you can keep track just of the unique values. + +You can do that with `set()`. Try running the code sample below: + + + +### Docassemble's DASet class + +When you use a dictionary in Docassemble and want it to handle collecting items for you, you will create an object of class `DASet`. Once you've done that, you can work with the items the same way you do in Python. Use the method `.update()` to combine two DASets. + +As a reminder, we use the `objects` block to create a Docassemble object. Here's an interview that creates a DASet: + +```yaml +objects: + - my_set: DASet +``` + +You can read [Docassemble's documentation about sets](https://docassemble.org/docs/groups.html#set). + +### More explorations of sets + +* https://www.w3schools.com/python/python_sets.asp +* https://realpython.com/python-sets/ + +## Groups and objects work well together + +Groups can store all kinds of information. One of the most common things to store in a group is an object. + +If we go back to our spreadsheet metaphor, lists and dictionaries are just two columns in a spreadsheet. If we store an object in a list, now we suddenly have a whole table. + +Thinking back to our intake sheet, for each household member we collected: + +1. First name +1. Last name +1. Gender +1. Birthdate +1. Income + +On paper, we could represent that as a table: + +First name | Last name | Gender | Birthdate | Income +-----------|-----------|--------|-----------|-------- +Jane | Smith | F | 10/1/2000 | SSI +Robert | Smith | M | 1/25/1997 | Employment + +Or in a Python list of Individual objects, with each column representing an attribute of our object: + +Index | name.first | name.last | gender | birthdate | income_type +------|------------|-----------|-----------|--------|----- +0 | Jane | Smith | F | 10/1/2000 | SSI +1 | Robert | Smith | M | 1/25/1997 | Employment + +Docassemble lets you work with lists, dictionaries, and sets of objects. They just work better together. On one screen, you can collect all 5 fields for each household member, and store them as one object in the list, dictionary, or set. + +You don't need to create your own objects to store them in lists. Docassemble lets you store the built-in `DAObject` in a list, which can have attributes added in your interview without requiring you to do any special setup. + +## Using Docassemble Groups to Collect Information + +### Special variable `i` + +You can always treat a group like an ordinary variable, and simply use the square bracket syntax, like this: `- User name: names[1]`. However, that has no advantage over using ordinary variables, like `name_1`, `name_2`, etc. + +Instead, Docassemble offers a convenient placeholder: the [special variable `i`](https://docassemble.org/docs/groups.html#i). In a Docassemble interview, the variable `i` always refers to an item inside a list. A question that refers to `children[i]` will work for any item in the list `children`. + +Here's an example: + +```yaml +--- +question: | + Tell us about this child +fields: + - First name: children[i].name.first + - Last name: children[i].name.last + - Birthdate: children[i].birthdate + datatype: date +``` + +The one question we wrote above will work for the first, second, third, etc. child, without us having to write a different question for each child. + +You might want to let someone know what child number they are answering a question about. You can use the variable `i` to help with that, too. The built-in function [`ordinal()`](https://docassemble.org/docs/functions.html#ordinal) will take a list number, and return "first" for the first item (which has index 0, remember, because lists start at zero), "second" for the second item, and so on. Here's a short interview that uses this function: + +```yaml +--- +question: | + Tell us about your ${ordinal(i)} child +fields: + - First name: children[i].name.first + - Last name: children[i].name.last +``` + +This would display on the screen as `Tell us about your first child`. + +> Note: if you are working with a list of lists, or even a list of lists of lists, you can use the variables `i`, `j`, `k`, `l`, `m`, and `n` all on the same screen. E.g., `list1[i].children[j].income_sources[k]`. Because of Docassemble's object structure, you rarely need to do this. It's more common to use the `generic object` feature for those lower levels. + +### The .using() method of an object + +One DAList object can work 4 different ways. To configure those options, Docassemble expects us to set an attribute on the object. One way to do that is to use the method `.using()`. Briefly, `.using()` lets you set the attribute of an object at the same time that you create it. We do that with `keyword` parameters. + +Here's a regular object block: + +```yaml +--- +objects: + - my_object: DAObject +``` + +Let's say we want to set the `favorite_color` attribute of our object to "Blue". One way is with the `.using()` method: + +```yaml +--- +objects: + - my_object: DAObject.using(favorite_color="Blue") +``` + +Another way would be to set it in a mandatory code block: + +```yaml +--- +objects: + - my_object: DAObject +--- +mandatory: True +code: | + my_object.favorite_color = "Blue" +``` + +Or of course, in a question: + +```yaml +--- +objects: + - my_object: DAObject +--- +question: | + What is the object's favorite color? +fields: + - no label: my_object.favorite_color +``` + +### The .gather() method and other ways to trigger gathering + +There are two ways to trigger gathering a group in Docassemble: + +1. Using the .gather() method of the list in a mandatory code block, or +1. Referencing the full list: in a code block, a question's text, or a template. + +Here's an example code block: + +```yaml +--- +mandatory: True +code: | + children.gather() +``` + +And here's a question: + +```yaml +--- +question: | + List of children +subquestion: | + ${children} +``` + +If you want to control when the items of a list will be collected, using the `.gather()` method is a neat and explicit way to do so. But referencing the list is just fine. + +### Methods of collection + +There are four recommended ways to use groups in Docassemble. These apply to lists, dictionaries, and sets: + +1. Ask for the number of items in the group up front. Docassemble will handle showing a separate screen to collect each item. +1. After collecting each item, ask if the user has more items to add. Docassemble will keep going until your user answers "no". +1. Gather the whole group on one screen, letting the user click a button to add a new item. +1. Pre-fill the group with prompts or using computer code, and allow your user to edit and add more later. + +Which method you use depends on your intuititions (and testing) about the best user experience. + +Some kinds of information we naturally number, and some we don't. For example: if we are prompted to list our children, we know how many children we have. If we are prompted to list our assets, we might never have counted them up individually. Asking how many bank accounts we have gives us extra work instead of making it simpler. Think about the information you're gathering and which method works best. + +Similarly, some information is a good fit for gathering on a single screen, and some is too complex. In our household example, 5 fields might be too many to try to fit multiple rows on a small screen. Usually the on-one-screen method works best when you're collecting 3 or fewer fields for each item. + +### Asking for the number up front +One way to use a Docassemble list is to ask the user for how many items they are going to gather up front. Let's walk through this process with gathering a list of children. + +Asking for the number up front requires us to use two special attributes of a DAList: `.ask_number` and `target_number`. We are collecting a list of objects, so we also need to tell Docassemble what kind of object our list will hold with the `object_type` attribute. + +First, let's create our `DAList` object: + +```yaml +--- +objects: + - children: DAList +``` + +We want to use the list style that asks the user how many of that item they have. We do this by setting the `ask_number` attribute to True. Let's do that with the `.using()` method. Modify the objects block so it looks like this: + +```yaml +--- +objects: + - children: DAList.using(ask_number=True) +``` + +We'll use the class Individual to represent each child in our list. To tell Docassemble what class of object we are storing in the list, modify it one more time: + +```yaml +--- +objects: + - children: DAList.using(ask_number=True, object_type=Individual) +``` + +By setting the `object_type`, Docassemble will handle creating new objects for us as needed, any time we add a new item to the list. + +Next, we want to set the `target_number` attribute of our DAList so we know how many children our user has. Let's do that with a question. Add the block below to your interview: + +```yaml +question: | + How many children do you have? +fields: + - no label: children.target_number + datatype: integer +``` + +[Datatype `integer`](https://docassemble.org/docs/fields.html#numbers) represents a whole, round number (instead of a number with a decimal point). We need a whole number for `target_number`. We can't collect 1/2 of a kid! + +Now, we just need a question to get information about each child: + +```yaml +--- +question: | + Information about your ${ordinal(i)} child +fields: + - First name: children[i].name.first + - Last name: children[i].name.last +``` + +### Asking after each item is gathered + +The default way to gather items in a list is to ask, in order: + +1. are there any items in the list? with the attribute `.there_are_any` +1. Tell us about the first item (with whatever attributes of the item are required) +1. Are there any more items? with the attribute `.there_is_another` + +Because this is the default, if you use this method, you don't need to set any options with `.using()`, other than the `object_type`. + +Each of these questions appear on a different screen. In some cases, this can be quite tedious, but sometimes it offers the best user experience. + +Let's show a short example: + +```yaml +--- +mandatory: True +code: | + children.gather() + ending_screen +--- +objects: + - children: DAList.using(object_type=Individual) +--- +question: | + Do you have any children? +yesno: children.there_are_any +--- +question: | + What is your ${ordinal(i)} child's name? +fields: + - First name: children[i].name.first + - Last name: children[i].name.last +--- +question: | + Do you have any more children? +yesno: children.there_is_another +--- +event: ending_screen +question: | + Here are your children +subquestion: | + ${children} +``` + +### Asking for multiple items on one screen + +If you only need a few pieces of information for each item in your list, collecting them all on one screen can be a good user experience. + +We still need to ask one preliminary question: are there any items on the list? with the attribute `.there_are_any`. But we no longer have to answer if there are any more items. The way to trigger this is to add the `specifier` `list collect: True` to the bottom of the question that asks the users for the details of each list item. + +This method only works for a list, and not for a dictionary or set. + +Here is a short interview that uses this technique: + +```yaml +--- +mandatory: True +code: | + children.gather() + ending_screen +--- +objects: + - children: DAList.using(object_type=Individual) +--- +question: | + Do you have any children? +yesno: children.there_are_any +--- +question: | + What is your ${ordinal(i)} child's name? +fields: + - First name: children[i].name.first + - Last name: children[i].name.last +list collect: True +--- +event: ending_screen +question: | + Here are your children +subquestion: | + ${children} +``` + +In some cases, you might know that there is at least one option. For example, collecting members of a corporate board. Then, you could set `.there_are_any` to True with a `.using()` statement, like this: + +```yaml +--- +objects: + - board_members: DAList.using(object_type=Individual, there_are_any=True) +``` + +By default, the list collect screen has buttons to delete each item, as well as a number next to each item. [Read more](https://docassemble.org/docs/groups.html#list%20collect) about how to customize the appearance. + +### Using code to pre-fill items, with or without prompts + +Sometimes, we want to use code to gather items in a list, or otherwise gather the items without Docassemble triggering the questions manually. To do so, we need to set the `auto_gather` attribute to `False`. Then, once we have finished gathering the items into our list, we need to set the `.gathered` attribute to `True`. + +Here is a short example: + +```yaml +--- +mandatory: True +code: | + # For simplicity, "Bob" and "Jane" are just text, and not Individual objects as in the other examples + children.append("Bob") + children.append("Jane") + children.gathered = True + ending_screen +--- +objects: + - children: DAList.using(auto_gather=False) +--- +event: ending_screen +question: | + Here are your children +subquestion: | + ${children} +``` + +## Display information from a Docassemble Group on screen or in a template + +### Using the built-in `comma_and_list()` method + +One easy way to display a list is to simply reference the list's name. The list's `__str__()` method displays each item, in order, separated by a comma and with the word `and` before the final item. It does this with a built-in function named [`comma_and_list()`](https://docassemble.org/docs/functions.html#comma_and_list). + +For example, if you have a DAList `people` with items `["bob","jane","roger"]` and you reference it as `${people}`, the output will be `bob, jane, and roger`. + +### Using a `for` or `while` loop + +If you want more control over displaying the items in your list--for example, you want to display just the first names, and not the last names--you will need to use a loop. + +You can read more about the `for` and `while` loop in the section about [Python](python.md#repetition-loops). + +A `for` loop in Python: + +1. Takes some action +1. **For** each item **in** the list, set, or dictionary + +The basic structure is the keyword `for`, followed by a temporary variable name, the keyword `in`, and finally followed by the name of the list, dictionary, or set. + +In a Python code block, you will write a `for` loop just like an `if` statement, with the start/end shown by indentation. + +```python +for item in my_list: + do_something(item) +``` + +Just like an `if` statement, when you use a `for` loop inside a question block, you need to start the line with a `%` symbol, and you need to explicitly end it, with the `endfor` keyword. For more, you could review the section on [Mako](mako.md). Here's what it looks like in a short example: + +```yaml +--- +mandatory: True +code: | + items = ['A','B','C'] + ending_screen +--- +event: ending_screen +question: | + + % for item in items: + * ${item} + % endfor +``` + +Remember, the `for` loop takes each item in the list, dictionary, or set, and assigns that item to a temporary variable. That makes it possible to work with each item one at a time. + + + +## Further reading + +* https://www.nonprofittechy.com/2018/11/26/gathering-repeated-information-working-with-docassemble-groups/ +* https://docassemble.org/docs/groups.html \ No newline at end of file diff --git a/docs/docassemble_intro/theming-docassemble.md b/docs/docassemble_intro/theming-docassemble.md new file mode 100644 index 000000000..c5205a48c --- /dev/null +++ b/docs/docassemble_intro/theming-docassemble.md @@ -0,0 +1,217 @@ +--- +slug: theming-docassemble +title: Applying custom themes +sidebar_label: Applying custom themes +--- + +## Customizing Docassemble's visual appearance + +[Themes](https://docassemble.org/docs/initial.html#bootstrap%20theme) can be used to control +Docassemble interviews': + +- colors +- fonts +- button appearance +- size and shape of inputs + +You can use an off the shelf theme, but note that as of this writing Docassemble is built +around Bootstrap 5.2. Most themes you find online may be Bootstrap 4 or earlier. Using a +theme from an older version of Bootstrap may result in visual and other interface glitches. + +### Creating a custom CSS theme with Bootstrap.build + +If you want to build a custom theme, encompassing colors, fonts, button styles and other +options that are configurable with css, you can: + +1. build one from Bootstrap 5 source +1. start with a theme generator tool like [bootstrap.build](https://bootstrap.build/) + and in some cases, add some custom CSS to make it work with Docassemble. + +Using bootstrap.build is the simplest option for most authors. + +1. Visit the [bootstrap.build](https://bootstrap.build/) website. +1. Click the button to open the Builder +1. Customize the options that you want to customize. Typically those will include: + * Under color system, the $gray/$blue/etc. colors, if you use a matching color in your theme + * Under color system, almost always the variables `primary`, `secondary`, `success`, `info`, `warning`, `danger`, + `light` and `dark` + * Under Typography, customize any fonts that you wish to use + * Under forms, you may want to customize button size and rounded edges +1. Click the "export theme" button (it may be hidden behind a banner at the top of the page) and choose the + "bootstrap.min.css" option. Rename this theme to be more specific. Optionally, download the `_variables.scss` file + so that you can easily load and adjust your settings in future. + +Add this theme to your Docassemble playground using the Folders | Static menu. + +Next, you will need to make a small adjustment to the theme generated by bootstrap.build. +Copy and paste the code below into the `bootstrap.min.css` file (or your new file name). + +```css +.visually-hidden { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} + +.bg-dark { + background-color: #1a73e8!important; /* replace with your desired nav bar color */ +} +``` + +We have identified the small patches above that are required to use Bootstrap.build to make +a Docassemble theme, but you may run into more. The safest way to create a theme for +Docassemble is by building it from source, following the instructions below. + +### Creating a custom theme from source instead of with a theme generator + +The Bootstrap documentation [covers the details of theming](https://getbootstrap.com/docs/5.1/getting-started/download/). + +While the above instructions to use bootstrap.build can work well in most +circumstances, you may run into small interface bugs introduced by the theme +generator. If you prefer more control over building the theme, first, +[download](https://getbootstrap.com/docs/5.1/getting-started/download/) the +Bootstrap source. As of this writing, the latest version you can use is 5.2. + +Use a computer with a current version of Node. The instructions on this page +assume you are using an Ubuntu Linux computer with Node installed, but they +should be the same on any workstation. They were tested on a machine running +Windows 11 with Ubuntu running under Windows Subsystem for Linux (WSL). + +It also assumes that you have VS Code installed, but you can use any text +editor of your choice. + +```bash +wget https://github.com/twbs/bootstrap/releases/download/v5.1.3/bootstrap-5.1.3-dist.zip +unzip bootstrap-5.1.3-dist.zip +cd bootstrap-5.1.3 +npm install +``` + +Now, create a new `custom.scss` file inside the `scss` subfolder in the +`bootstrap-5.1.3` folder. Detailed instructions on what this file can contain +are in the [Bootstrap +documentation](https://getbootstrap.com/docs/5.1/customize/sass/#importing). + +You can use a [color theme generator](https://huemint.com/bootstrap-basic/) like +[Huemint.com](https://huemint.com/bootstrap-basic/) to make sure that you have a +consistent set of all 9 Bootstrap variables. When you use the color theme +generator, at the bottom of the page, you will see a small snippet of code that +you can copy into the customs.scss file. + +For example, your new custom.scss might look like this: + +```scss +// Custom.scss +// Option A: Include all of Bootstrap + +// Include any default variable overrides here (though functions won't be available) + +$white: #ffffff; + +$theme-colors: ( + "light": #d8e2a5, + "dark": #1b1b1b, + "primary": #25dec6, + "secondary": #375b5a, + "info": #d74d72, + "success": #0cb545, + "warning": #f4ca0b, + "danger": #fa043c, +); + +// Note: we placed our custom.scss file in the bootstrap path for simplicity, +// so we use a different path than in the bootstrap documentation +// @import "../node_modules/bootstrap/scss/bootstrap"; +@import "bootstrap"; + +// Then add additional custom code here +``` + +Now, use `npm` to compile the theme file. + +```bash +cd ~/bootstrap-5.1.3 +npm run css-compile +``` + +Your new `custom.css` file is in the +~/bootstrap-5.1.3/dist/css` directory. Copy this file to your Docassemble +`static` folder and reference it as a `bootstrap theme`. + +### Using custom fonts in the frontend + +You can use custom webfonts with Docassemble, just like you can with any other web +product. + +This [Mozilla guide about web +fonts](https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Web_fonts) +is a good place to start. + +If you do not already have a .woff or .woff2 file but you do have a TrueType +(TTF) or OpenType (OTF) file that you are licensed to distribute, you can create +one with a [free online web font +tool](https://www.fontsquirrel.com/tools/webfont-generator). + +1. Once you have a .woff file, upload it to the /static folder of a Docassemble playground. +1. Add an `@font-face` directive to your bootstrap.css file (or a separate CSS file that you +reference in the `features` block of your interview) + +You cannot include Mako tags in your CSS file, so in order to use the new font face, you should +add it to a Docassemble package and then install the package on your server. + +Example: + +```css +@font-face { + font-family: 'my_font'; + src: url('/packagestatic/docassemble.MyTheme/my_font.woff2') format('woff2'), + url('/packagestatic/docassemble.MyTheme/my_font.woff') format('woff'); + font-weight: normal; + font-style: normal; +} +``` + +In the example above, MyTheme is a Docassemble package that is installed +server-wide. my_font is both the name of a web font and the name of the WOFF file. + +### Using custom fonts when creating PDF files from DOCX templates + +If you would like to use a font **other than** the Microsoft fonts popular in +the late 1990s-2000 era (Arial, Times New Roman, Courier) then you will need to +install the fonts on your Docassemble server. + +Make sure that you have a license for each font you want to install. + +1. Locate the .otf or .ttf file representing the font that you want to use + inside your Word template (note that these are often in c:\windows\fonts\) +1. Copy the font to your docassemble server +1. Copy the font inside the docker container +1. reset the font cache +1. restart the docassemble supervisor processes + +```bash +scp ~/myfont.ttf apps.example.com: +ssh apps.example.com +docker cp myfont.ttf mycontainer:/usr/share/fonts +docker exec mycontainer /bin/bash +fc-cache -f -v +supervisorctl restart uwsgi +supervisorctl start reset +supervisorctl -s http://localhost:9001 restart unoconv +``` + +Instead of copying the fonts to /usr/share/fonts, you could likely copy +them to `/var/www/.fonts`. This has the advantage of being writable by the +web process from a Python module. + +If the font still does not appear to be installed (try generating a PDF with the +custom font),you may need to do a `docker stop -t 600 mycontainer` followed by a +`docker start mycontainer`. + +## See also + +* [Customizing Assembly Line interviews](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/customization/overview) \ No newline at end of file diff --git a/docs/docassemble_intro/translating-interviews.md b/docs/docassemble_intro/translating-interviews.md new file mode 100644 index 000000000..f863ed94d --- /dev/null +++ b/docs/docassemble_intro/translating-interviews.md @@ -0,0 +1,186 @@ +--- +slug: translating-interviews +title: Translating your interview into other languages +sidebar_label: Translating your interview +--- + +# Creating interviews in multiple languages + +Docassemble interviews can be translated into multiple languages. +It includes handy features that load the translation at runtime, which +means you do not need to maintain two separate versions of the interview +to get the benefit of multiple languages. + +The translation system is friendly for working with professional +translators and amateur translators alike. It takes advantage of simple +human-readable spreadsheets or text files. + +## The basics + +### Naming your language + +In Docassemble, you reference the language you are using with a short name of +your choice. + +Most authors use 2-letter [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) +language codes to name their languages. If there is no appropriate +2-letter code, you should consider using [ISO 639-3](https://en.wikipedia.org/wiki/ISO_639-3). + +Some system phrases are already translated and keyed to the two-letter ISO 639-1 +code. So it's a good idea to stick with this convention. But if you need to +translate into a dialect and you can't find an official name, feel free to use +one of your own invention. The official code is just a convention. + +### Switching languages + +You can control the language of an interview by using the [`set_language()`](https://docassemble.org/docs/functions.html#set_language) function. The +name for the language you want to switch to is the only required parameter. + +Docassemble checks the value of `get_language()` to decide what language to +display system phrases and question text in. There is no built-in menu item +that allows you to switch languages. You need to add a question or custom +menu item that invokes `set_language()` at the appropriate time. + +### Incomplete translations + +When a phrase has not been translated yet, the user will not get an error. Instead, Docassemble will show the default language. + +Translation works off of an exact match. When you change the original language +of a question, the translation will no longer be valid. Even changes to +punctuation and white space will cause Docassemble to ignore the translation and +show the page in its original language. + +Despite this need for an exact match, the translation system is granular. +Each field, option, question text, and subquestion text will be considered +separate items to get translated. If some but not all phrases are translated, +a partial translation will be shown. + +### What can be translated + +All of the text in the Docassemble interface and interview can +be translated. + +Docassemble has separate systems for translating different kinds +of interview text: + +Kind of text | Docassemble documentation page +-------------|------------------------ +Question text | [Spreadsheet or XLIFF file](https://docassemble.org/docs/language.html) +System text (buttons, built-in menu items) | [words.yml](https://docassemble.org/docs/config.html#words) +Code and metadata blocks | Add multiple blocks, with a [language modifier](https://docassemble.org/docs/modifiers.html#language) +Documents | [Language modifier](https://docassemble.org/docs/documents.html#language), also see [Creating documents in languages other than English](https://docassemble.org/docs/language.html#documents) +Function output | [Language-specific functions](https://docassemble.org/docs/functions.html#linguistic) + +## Demo interview + +Below is a small interview that demonstrates several +translation features. + +```yaml +--- +# This should always be the first line of the YAML. It needs +# to come before any text that will get translated +translations: + - phrases_es.xlsx # this spreadsheet has most of the interview phrases +--- +features: + navigation: True +--- +language: en +sections: + - introduction: Introduction + - ending: Ending +--- +# The `sections` block is one that doesn't appear in the +# translations spreadsheet. You need to use a language modifier +# instead +language: es +sections: + - introduction: Introducción + - ending: Conclusión +--- +mandatory: True +code: | + set_language(language_choice) # This actually sets the language to the two-letter code below + intro + ending +--- +question: | + What is your language? +fields: + - Language/lengua: language_choice + datatype: radio + choices: + - English: en + - Español: es +--- +section: introduction +continue button field: intro +question: | + This is the first screen +subquestion: | + This is the subquestion area of the first screen. +fields: + - The first field: field1 + - Select one or more: field2 + datatype: checkboxes + choices: + - Label 1: value1 + - Label 2: value2 + - Label 3: value3 + - Label 4: value4 +--- +section: ending +event: ending +question: | + All done +subquestion: | + When you use the comma_and_list() function, the word "and" + is translated so long as a words.yml is defined in the global configuration + file. + +``` + +The XLSX file phrases_es.xlsx has these contents: + +interview | question_id | index_num | hash | orig_lang | tr_lang | orig_text | tr_text +----------|-------------|-----------|------|-----------|---------|-----------|---------- +docassemble.playground10language:language.yml | Question_1 | 0 | dbc292ca7541f124f4924e3f0dd9608d | en | es | "What is your language? +" | ¿Cuál es su idioma? +docassemble.playground10language:language.yml | Question_1 | 1 | 12cad744449f8d4cbdeb9b6fb2738a48 | en | es | Language/lengua | +docassemble.playground10language:language.yml | Question_1 | 2 | 78463a384a5aa4fad5fa73e2f506ecfc | en | es | English | +docassemble.playground10language:language.yml | Question_1 | 3 | de92bbe05815c4eb756acb5897baa99c | en | es | Español | +docassemble.playground10language:language.yml | Question_2 | 0 | 8800e1c9b3e22c44ba59a34db3fe4841 | en | es | introduction | +docassemble.playground10language:language.yml | Question_2 | 1 | 2a11e19cd150a312759299a692dc394a | en | es | "This is the first screen +" | Esta es la primera pantalla +docassemble.playground10language:language.yml | Question_2 | 2 | 1d8d0906aad3c23c4be70b376b8d02d8 | en | es | "This is the subquestion area of the first screen. +" | Esta es el área de subpreguntas de la primera pantalla. +docassemble.playground10language:language.yml | Question_2 | 3 | 75f398248f25e05bc99e157d7145c363 | en | es | The first field | el primer campo +docassemble.playground10language:language.yml | Question_2 | 4 | e055c0d5b54f36c2665d271fb86079fc | en | es | Select one or more | Seleccione uno o más +docassemble.playground10language:language.yml | Question_2 | 5 | eff63417104e8ebe57b3531054153264 | en | es | Label 1 | Etiqueta 1 +docassemble.playground10language:language.yml | Question_2 | 6 | f95d4806340464e46da38ed8a1ce45c6 | en | es | Label 2 | Etiqueta 2 +docassemble.playground10language:language.yml | Question_2 | 7 | a890dd1894c379ab876f8d2616ba5907 | en | es | Label 3 | Etiqueta 3 +docassemble.playground10language:language.yml | Question_2 | 8 | 650857596fe374d17d7712d706f3864c | en | es | Label 4 | Etiqueta 4 +docassemble.playground10language:language.yml | Question_3 | 0 | deeb6f5001d774a40a29140b74cf5011 | en | es | ending | +docassemble.playground10language:language.yml | Question_3 | 1 | c46c51f9085b1460607798292bcf5232 | en | es | "All done +" | Todo listo +docassemble.playground10language:language.yml | Question_3 | 2 | 0c5b0b5e1c20a9af00353e64f6d2a062 | en | es | "When you use the comma_and_list() function, the word "and" is translated so long as a words.yml is defined in the global configuration file. " | Cuando usas la función `coma_and_list()`, la palabra ""y"" se traduce siempre que se defina un archivo words.yml en la configuración global archivo. + +The XLSX file was created by visiting https://[myserver]/utilities and typing +"docassemble.playground10language:language.yml" in the "Download an interview phrase translation file" box. + +In the translation file, notice we left several lines that we do not want to translate blank. + +The important columns are are the `hash` and `tr_text` columns. The translator can directly edit the `tr_text` column +to add the translations that are needed. Any mako tags or other special syntax will be color-coded. Just let the translator +know to ignore any text that is not black. + +## Read more + +* [Docassemble's official documentation on language functions](https://docassemble.org/docs/language.html) + +### More complex multi-lingual interviews you can inspect + +* [MADE](https://gbls.org/MADE), ([GitHub source code](https://github.com/GBLS/docassemble-maevictiondefense)) +* [UpToCode](https://getuptocode.org), ([GitHub source code][https://github.com/LemmaLegalConsulting/docassemble-HousingCodeChecklist]) +* [Massachusetts 209A Abuse Prevention Petition](https://courtformsonline.org/dv/#209A), ([GitHub source code](https://github.com/suffolklitlab/docassemble-MA209AProtectiveOrder)) \ No newline at end of file diff --git a/docs/docassemble_intro/working-with-docx.md b/docs/docassemble_intro/working-with-docx.md new file mode 100644 index 000000000..8a796db39 --- /dev/null +++ b/docs/docassemble_intro/working-with-docx.md @@ -0,0 +1,157 @@ +--- +slug: working-with-docx +title: Working with Docx files +sidebar_label: Working with Docx +--- +import useBaseUrl from '@docusaurus/useBaseUrl'; + +## The big picture + +In our [Hello, World](hello-world.md) exercise, we did everything in the +playground. + +When you work with a Docx file, we're adding something new: an editor that can +open and save files in Microsoft Word's native format, docx. You don't need +Microsoft Office. You can use [Libre Office](https://www.libreoffice.org/) or +the [free version of Office +Online](https://www.microsoft.com/en-us/microsoft-365/free-office-online-for-the-web). + +We still have our YAML file. The YAML file will contain the questions that the +user is asked. The Docx file will contain our formatted text. It can also +include variables that will be filled in by the interview. We can include +display logic and use Python functions to format our text as well. + +In the Hello, World exercise, we used [Markdown](markdown.md) and +[Mako](mako.md) to display variables and format our text. In a Docx file, we use +a very similar language called [Jinja2](jinja2.md). + +## Hello, Docx + +Let's take a look at perhaps the simplest interview that automates a Docx template. + +First, create a Word document that looks like this: + +``` +Hello, {{ user_name }}! +``` + +You can copy and paste the text above right into the Word file. Name the file +`hello_world.docx`. Upload it to your playground using the Folders | Templates +area of the playground. + +Next, make a new interview in your Docassemble playground that looks like this: + +```yaml +--- +question: | + What is your name? +fields: + - no label: user_name +--- +mandatory: True +question: | + Your document is ready +attachment: + docx template file: hello_world.docx +``` + +Save and run the interview, and see what you get. + +### What happened? + +Our interview has a question for one variable: `user_name`. Inside the docx +file, we put the variable name inside curly brackets, like this: `{{ user_name }}`. +Docassemble replaced `{{ user_name }}` with the text we entered when we ran the interview. + +Notice that the `{{ }}` is pretty similar to what we did in the Hello, World exercise. +This syntax is called [Jinja2](jinja2.md). The big difference is in a Docx we use two curly +braces; in our interview YAML, we use a dollar sign and single curly braces, +like this: `${ user_name }`. + +This is the first time that we introduced the +[attachment](https://docassemble.org/docs/documents.html#attachment) `specifier`. +This is the simplest way to add an attachment to a question. Sometimes, you may +want to assign the completed document itself to a variable and do more things with it. +Or, you may want to display different templates depending on the selections the user +makes. + +#### Questions + +1. How did the interview know to ask for the definition of `user_name`? + +## Mail Merge can't do **that** + +What if we wanted to include some optional text in our template? It's pretty common +to want different versions of a document for different scenarios. Docassemble of +course allows you to use conditional text inside your Docx template. + +Create a Word document with the text below. You can copy and paste the +code exactly as is. + +``` +Reynham Industries Company Picnic Ticket + +Welcome, {{ user_name }}. Enjoy the fun in the sun! + +{%p if user_name == "Moss" %} +Don't forget to take some water along in case you get a hot ear! +{%p elif user_name == "Roy" %} +Did you try turning it off and on again? +{%p endif %} +``` + +Save the file with the name `ticket.docx` and upload it to the Folders | +Templates section of the playground. + +Now, create an interview file with the text below: + +```yaml +--- +mandatory: True +code: | + user_name + download +--- +question: | + What is your name? +fields: + - no label: user_name +--- +event: download +question: | + Your document is ready +attachment: + name: Company Picnic + docx template file: ticket.docx +``` + +### Questions + +1. What happens when you use the name "Jen"? +1. What about when you use "Moss"? What about "Roy"? +1. What happens when you type "moss"? + +### What happened? + +To mark conditional text, we surround it with some new tags. +`{%p if ... %}` marks the beginning of an `if` statement in a Docx file. +We can optionally use the `{%p else %}`. We always need to include the special +`{%p endif %}` tag in a Docx file. Notice we did not need something like `endif` +inside a Python code block. + +### Further exploration + +1. What happens when we change the format of the Word file by turning it into +headings, bold, and so on? What about changing the format of the `{{ user_name }}` text? +1. Try removing one of the `p`s in the `{%p if ... %}` lines. Try removing all of them. + +## Further reading + +1. The [attachment](https://docassemble.org/docs/documents.html#attachment) block +1. [Docx Templates](https://docassemble.org/docs/documents.html#docx%20template%20file) +1. [Docassemble tutorial](https://docassemble.org/docs/helloworld.html) +1. [Jinja reference](https://jinja.palletsprojects.com/en/2.11.x/) (this is + similar but not the exact syntax used in Docasssemble) +1. [Docx-template Jinja reference](https://docxtpl.readthedocs.io/en/latest/#jinja2-like-syntax) + +Quinten Steenhuis, June 2020 \ No newline at end of file diff --git a/docs/docassemble_intro/yaml.md b/docs/docassemble_intro/yaml.md new file mode 100644 index 000000000..d0a2754be --- /dev/null +++ b/docs/docassemble_intro/yaml.md @@ -0,0 +1,142 @@ +--- +slug: yaml +title: Question files (YAML) +sidebar_label: Question files (YAML) +--- + +When Jonathan Pyle created Docassemble, he chose to use the [YAML](https://en.wikipedia.org/wiki/YAML) file format as the main way to represent the contents of an interview. When writing a Docassemble interview, it can be helpful to understand what YAML is, which conventions are Docassemble standards that overlay YAML, and how the different components of a Docassemble interview are represented in YAML. + +YAML, which stands for Yet Another Markup Language (or YAML Ain't a Markup Language), is a standardized way for a computer program to save information to a file. Most commonly, this is configuration data: a way to store a list of preferences and settings that relate to a program or web application. One of the goals of YAML is to be human readable. That means a minimum of clutter. YAML stands on the shoulders of previous file formats, such as [INI](https://en.wikipedia.org/wiki/INI_file), [XML](https://en.wikipedia.org/wiki/XML), and [JSON](https://en.wikipedia.org/wiki/JSON), all of which make different trade-offs of readability and conciseness. The structures of the different formats also each tend to represent different kinds of data better. + +One thing you'll notice when you look at a YAML file is that there is very little “punctuation”. There are no square or curly brackets such as those used in JSON, no closing/opening tags such as used in XML, and no section headings such as present in .INI files. Instead, the different kinds of data that a YAML file stores are implied by context, indentation, and two common delimiters: the colon (:) and the dash (-). + +## The components of a YAML file +There are four standard kinds of elements of a YAML file to keep in mind: + +- Documents (more usefully called blocks) +- Dictionaries +- Lists +- Data (which is usually text, but could be numeric) + +### Documents +Documents represent different sections or blocks of a YAML file. If your interview file has only one section, you don't need any special marker. If you have more than one section (as is almost certain), each one needs to be separated by three dashes at the start of a line, like this: --- + +```yaml +--- +Block 1 +--- +Block 2 +``` +The name document is confusing when applied to a Docassemble interview's file, since each interview will contain multiple `documents`, but it's part of the YAML standard. Just mentally replace the word `document` with block if you need to refer to the YAML standard, and you'll be fine. + +### Dictionaries +A [dictionary](https://en.wikipedia.org/wiki/Associative_array) is a kind of data structure in computer programming. Just like the standard definition, the point of a dictionary is that you have multiple entries (the definitions), and you can look up each entry with a keyword. One thing special about the computer kind of dictionary is that the order of entries doesn't matter. The only thing guaranteed is that the keyword (or key) retrieves the linked definition. In YAML, a dictionary entry is a key followed by a :, and then a value or definition. In the example below, the keyword is “question” and the value is “What is your name?” + +```yaml +question: What is your name? +answer: Jane Smith +``` + +Throughout the documentation, Docassemble uses the term `specifier` for a dictionary key that has a special meaning inside the interview format. There are many such specifiers, with question, subquestion, fields, and code being specifiers you will use right away. + +### Lists + +A [list](https://en.wikipedia.org/wiki/List_(abstract_data_type)), which is similar to an array, is another data structure that represents one or more entries. Lists are simpler than dictionaries. Entries in a list don't have a label. Instead, each one will get a numeric index assigned automatically, starting at zero. The order does matter. YAML will preserve the order that you write the entries in a list. In YAML, you indicate a list by starting each entry with a dash (-) as in the example below. + +```yaml +- Item 1 +- Item 2 +- Item 3 +``` + +### Data + +YAML files can also contain data: the values that are stored in a list or dictionary. The most common kind of data is text data. Optionally, text can be surrounded by quotes. This might be needed if your text contains some of the special characters, such as a colon, but more often you leave quotes off. There is one special way to represent data that you should use often inside a Docassemble-flavored YAML file, which is the line continuation indicator or vertical pipe separator (|). Inside a dictionary's value, the vertical pipe separator tells YAML that everything that follows on the next indented line is going to be text, and that the text may flow on multiple lines and can contain special characters. + +```yaml +--- +question: | + This question can contain special characters (such as accents + and colons) and can extend across multiple lines. Each line + needs to be indented to the same level underneath the dictionary key. +``` + +Text is text, and as the saying goes, what you see is what you get. However, the text inside a Docassemble YAML file can almost always be formatted using Markdown, which is another standard. + +I want to discuss [Markdown](markdown.md) here only to point out that an interview file can contain Markdown, but as far as YAML goes, its all plain text. Docassemble looks for Markdown tags in the text and applies the formatting where it's appropriate. + +In addition to text blocks that may or not be formatted with Markdown, Docassemble's YAML files typically contain blocks of instructions called code blocks (which might be called scripts in a different platform). + +### Code blocks + +Code blocks are just text data as far as the YAML file format is concerned. But Docassemble will interpret the text as Python code. Python is one of the most popular programming languages in the world due to its simple and expressive syntax and ease of learning, as well as its power and extensive library of “packages” that make it easy to use functionality created by other people. + +In Docassemble's YAML files, you can mix Python code with your interview file with the code: specifier, like this: + +```yaml +--- +code: | + # Here is a Python comment. Notice the code block must be indented + # And that code blocks always use the line continuation / vertical + # pipe + my_variable = 1 + 3 +``` + +You might notice that a Python code block in Docassemble is really a special kind of dictionary, one that starts with the keyword “code”. This lets Docassemble know that the text that follows will interpreted as instructions rather than another purpose. Code blocks are great for instructions and conditional logic that are related to the flow of the interview. More complex code is better contained inside a regular Python module, or .py file. Python code blocks can contain comments, but YAML itself can contain comments, too. + +### YAML Comments +The final type of YAML contents that you should be aware of are comments. Comments begin with a # symbol and are ignored by Docassemble. Like the other contents of a YAML file, YAML comments need to follow the same indentation rules. Here's an example: + +```yaml +--- +question: | + # This is a heading because Docassemble thinks it is Markdown +# This is a comment because it is not indented as part of the question: key's data field. +``` + +YAML, like Python, does not have multi-line comments. Each line needs to start with a #. Docassemble does have its own special [comment](https://docassemble.org/docs/modifiers.html#comment) `specifier` that can extend over multiple lines. + +When writing an interview file, use comments liberally. They can make your logic easier to read, either for another developer, or more often, you in a few months or a year. + +### Nesting data structures + +Unlike the INI format, but like JSON and XML, a YAML datastructure can itself contain additional datastructures. For example, each item in a list could be a dictionary, or vice versa. Nesting is done by indentation, with no true limit to the number of indentation levels. The most common indentation for Docassemble's YAML file is two spaces. + +```yaml +--- +dictionary_key1: + - Item A + - Item B + - Item C: + - Item 1 + - Item 2 +``` + +This nesting is actually quite common in Docassemble's interview files. For example, fields: is a dictionary key that contains a list of dictionaries. Sometimes Docassemble allows you to use either a dictionary or a list as the contents of a specifier. Whenever this is true, make sure you understand the major difference: a list has an order that is guaranteed, while a dictionary does not. + +## Digging deeper: the fields specifier + +Because Docassemble's fields specifier behaves differently to the regular YAML dictionary keys that they resemble, I wanted to take a minute to look at this specifier a bit closer. + +Here's a typical Docassemble question block with a fields specifier: + +```yaml +--- +question: | + Information about you +fields: + - First name: client.name.first + - Last name: client.name.last +``` + +Let's break this down and label the different components. + + +The specifier 1) `question` itself is a dictionary key, followed by text (which could have had Markdown formatting if we desired). 2) `fields` is also a dictionary key. The dashes (3) and (4) represent items in a list that is indented underneath the fields specifier. (5) `First name` and (6) `Last name` represent dictionary keys again, while (7) `client.name.first` and (8) `client.name.last` are values. + +Wait a second. Doesn't it make more sense to think of the label (First name, Last name) as a value, while the variable names (client.name.first and client.name.last) are more akin to dictionary keys? Yes, it does. That's exactly how they are used inside Docassemble itself. However, in this special case, Docassemble does the magic to allow you to write your field labels in a more natural and human-readable order. This is one example where adhering too closely to the YAML format could be a bit confusing. + +## Keep reading + +* If you use the Assembly Line Weaver, you may want to read about the structure of a YAML file generated + by the [ALWeaver tool](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/generated_yaml). \ No newline at end of file diff --git a/sidebars.js b/sidebars.js index 6382dab7b..eaacf1045 100644 --- a/sidebars.js +++ b/sidebars.js @@ -38,6 +38,58 @@ module.exports = { ], docs: [ 'overview', + { + label: 'Introduction to Docassemble', + type: 'category', + items: [ + 'docassemble_intro/introduction-to-docassemble', + 'docassemble_intro/practical-guide-docassemble', + 'docassemble_intro/hello-world', + 'docassemble_intro/logic', + 'docassemble_intro/working-with-docx', + 'docassemble_intro/controlling-interview-order', + 'docassemble_intro/basic-troubleshooting', + { + type: "category", + label: "Docassemble syntax", + items: [ + 'docassemble_intro/yaml', + 'docassemble_intro/markdown', + 'docassemble_intro/mako', + 'docassemble_intro/python', + 'docassemble_intro/jinja2', + ] + }, + { + type: "category", + label: "Working with lists and objects", + items: [ + 'docassemble_intro/object-oriented-programming', + 'docassemble_intro/repeated-information', + ] + }, + { + type: "category", + label: "Customizing Docassemble", + items: [ + 'docassemble_intro/theming-docassemble', + 'docassemble_intro/translating-interviews', + ] + }, + { + type: 'category', + label: 'Administering Docassemble', + items: [ + 'admin-guide-docassemble/setup-server', + 'admin-guide-docassemble/rebuild-lightsail-instance', + 'admin-guide-docassemble/run-docassemble-docker-vscode', + //'admin-guide-docassemble/combining-interviews', + 'admin-guide-docassemble/installing-production-app', + 'admin-guide-docassemble/updates-and-maintenance', + ], + }, + ] + }, { label: 'Authoring interviews', type: 'category', From 17b62dcc07b1a3d8b40faa9c784954e38a49d611 Mon Sep 17 00:00:00 2001 From: Bryce Willey Date: Tue, 14 Oct 2025 15:34:42 -0400 Subject: [PATCH 2/2] Change links, remove duplicate content, clean up intro * Change links to point to the page on this site, instead of the old page * Theming and translating interviews already had pages on this site, merged with them instead * Changed the overview to not point folks to the other site. --- .../combining-interviews.md | 0 .../installing-production-app.md | 0 .../rebuild-lightsail-instance.md | 4 +- .../run-docassemble-docker-vscode.md | 0 .../setup-server.md | 0 .../updates-and-maintenance.md | 19 +- docs/archive/bootcamp.md | 6 +- docs/authoring/customizing_interview.md | 6 +- docs/authoring/docx_templates.md | 2 +- docs/coding_style/yaml.md | 4 +- docs/coding_style/yaml_dynamic.md | 2 +- docs/coding_style/yaml_structure.md | 6 +- .../ALToolbox/al_income_expenses.md | 6 +- .../ALToolbox/al_income_itemizedjobs.md | 6 +- docs/components/ALToolbox/al_income_jobs.md | 6 +- docs/components/AssemblyLine/translation.md | 33 +++- .../GithubFeedbackForm/github_feedback.md | 4 +- docs/docassemble_intro/hello-world.md | 2 +- docs/docassemble_intro/logic.md | 2 +- .../practical-guide-docassemble.md | 3 - .../translating-interviews.md | 186 ------------------ docs/get_started/beginners_guide.md | 6 +- docs/get_started/installation.md | 2 +- docs/overview.md | 18 +- docusaurus.config.js | 5 - sidebars.js | 20 +- 26 files changed, 79 insertions(+), 269 deletions(-) rename docs/{admin-guide-docassemble => admin_guide_docassemble}/combining-interviews.md (100%) rename docs/{admin-guide-docassemble => admin_guide_docassemble}/installing-production-app.md (100%) rename docs/{admin-guide-docassemble => admin_guide_docassemble}/rebuild-lightsail-instance.md (97%) rename docs/{admin-guide-docassemble => admin_guide_docassemble}/run-docassemble-docker-vscode.md (100%) rename docs/{admin-guide-docassemble => admin_guide_docassemble}/setup-server.md (100%) rename docs/{admin-guide-docassemble => admin_guide_docassemble}/updates-and-maintenance.md (91%) delete mode 100644 docs/docassemble_intro/translating-interviews.md diff --git a/docs/admin-guide-docassemble/combining-interviews.md b/docs/admin_guide_docassemble/combining-interviews.md similarity index 100% rename from docs/admin-guide-docassemble/combining-interviews.md rename to docs/admin_guide_docassemble/combining-interviews.md diff --git a/docs/admin-guide-docassemble/installing-production-app.md b/docs/admin_guide_docassemble/installing-production-app.md similarity index 100% rename from docs/admin-guide-docassemble/installing-production-app.md rename to docs/admin_guide_docassemble/installing-production-app.md diff --git a/docs/admin-guide-docassemble/rebuild-lightsail-instance.md b/docs/admin_guide_docassemble/rebuild-lightsail-instance.md similarity index 97% rename from docs/admin-guide-docassemble/rebuild-lightsail-instance.md rename to docs/admin_guide_docassemble/rebuild-lightsail-instance.md index 5698370de..d0274e8ae 100644 --- a/docs/admin-guide-docassemble/rebuild-lightsail-instance.md +++ b/docs/admin_guide_docassemble/rebuild-lightsail-instance.md @@ -37,7 +37,7 @@ the following setup: It also assumes you can access all those items through AWS, including SSH access to the Lightsail instance. See -[Installing a docassemble server](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/setup-server) +[Installing a docassemble server](setup-server) for instructions on setting up a server. It is also important that you make note of your existing admin and developer accounts @@ -137,7 +137,7 @@ On the new instance: ## Follow instructions on Installing a docassemble server Most of the remaining steps are the same ones used in the [Finish installation on your Lightsail instance over -SSH](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/setup-server#finish-installation-on-your-lightsail-instance-over-ssh) +SSH](setup-server#finish-installation-on-your-lightsail-instance-over-ssh) section of **Installing a docassemble server**. Note: When entering the Linux commands in those instructions, it works best to enter them one diff --git a/docs/admin-guide-docassemble/run-docassemble-docker-vscode.md b/docs/admin_guide_docassemble/run-docassemble-docker-vscode.md similarity index 100% rename from docs/admin-guide-docassemble/run-docassemble-docker-vscode.md rename to docs/admin_guide_docassemble/run-docassemble-docker-vscode.md diff --git a/docs/admin-guide-docassemble/setup-server.md b/docs/admin_guide_docassemble/setup-server.md similarity index 100% rename from docs/admin-guide-docassemble/setup-server.md rename to docs/admin_guide_docassemble/setup-server.md diff --git a/docs/admin-guide-docassemble/updates-and-maintenance.md b/docs/admin_guide_docassemble/updates-and-maintenance.md similarity index 91% rename from docs/admin-guide-docassemble/updates-and-maintenance.md rename to docs/admin_guide_docassemble/updates-and-maintenance.md index ac1a8ea8e..47da4f7a5 100644 --- a/docs/admin-guide-docassemble/updates-and-maintenance.md +++ b/docs/admin_guide_docassemble/updates-and-maintenance.md @@ -59,15 +59,15 @@ using your host operating system's package manager. Docassemble has two parts: -1. a [Python](/docs/python.md) web application, built around the [Flask +1. a [Python](../docassemble_intro/python.md) web application, built around the [Flask framework](https://flask.palletsprojects.com/en/2.2.x/) 1. An Ubuntu docker image and a series of Linux applications that the web frontend communicates with, including - - A specific version of [Python](/docs/python.md) (3.10 as of this writing) + - A specific version of [Python](../docassemble_intro/python.md) (3.10 as of this writing) - [LibreOffice](https://www.libreoffice.org/discover/writer) for converting Word documents to PDF - [LaTeX](https://www.latex-project.org/) for assembling - [Markdown](/docs/markdown.md) files + [Markdown](../docassemble_intro/markdown.md) files - [PDFtk](https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/) for manipulating PDF files and templates - [LetsEncrypt](https://letsencrypt.org/) for SSL certificate management @@ -254,7 +254,7 @@ sudo shutdown -r now for a security vulnerability. - Beyond periodic updates, the version of Ubuntu software may need an upgrade. This is like going from Windows 10 to 11, or macOS Ventura to Sonoma. Every 2 years, there's a new Ubuntu Long Term Support - (LTS) version. See [Rebuilding your AWS Lightsail instance](https://projects.suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/rebuild-lightsail-instance). + (LTS) version. See [Rebuilding your AWS Lightsail instance](rebuild-lightsail-instance). - What to back up - Keep a copy of the latest env.list file in case a rebuild is required. - Wnen to back up - After a backup copy of env.list is made, a new backup is needed only if the contents of that file changes. @@ -283,7 +283,7 @@ software runs within. Using a virtual machine adds to resiliency, though it also - How to monitor - Use `docker ps` command to make sure it is running. - How to update - See - [Updates to the Docassemble container](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/maintaining-docassemble#updates-to-the-docassemble-container). + [Updates to the Docassemble container](maintaining-docassemble#updates-to-the-docassemble-container). You will use these commands: `docker stop, pull, run,` and `prune`. - If you [updated the nginx timeout to 5 minutes](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/installation/#increase-nginx-timeouts-to-5-minutes) earlier, you will need to redo it. @@ -299,7 +299,7 @@ Playground. - How to monitor - If you can get to the Playground, My Interviews, or an individual program, then it's working. [UptimeRobot](https://uptimerobot.com/) can be used to receive server up/down notifications by email. - How to update - Log in as an administrator. Go to Package Management. Click the "Upgrade" button. See - [Updates to the Docassemble frontend](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/maintaining-docassemble#updates-to-the-docassemble-frontend) + [Updates to the Docassemble frontend](maintaining-docassemble#updates-to-the-docassemble-frontend) - When to update - Every few weeks or as needed if there is a critical bug fix or a desired new feature. ### Packages @@ -308,10 +308,9 @@ These are the program code, frameworks, and utilities that run on the Docassembl is an example used by many programs. - How to monitor - Monitors like [httpstatus.io](https://httpstatus.io/) or homegrown programs can check if individual programs are running. Note: These tools just check whether individual interview pages are reachable. Learn about using - [ALKiln](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/) to do automated + [ALKiln](../components/ALKiln/alkiln) to do automated start-to-finish testing and monitoring. - - How to update - You can - [update Assembly Line packages individually](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/), - or you can use the [ALDashboard](https://suffolklitlab.org/docassemble-AssemblyLine-documentation/docs/alkiln/). + - How to update - You can update Assembly Line packages individually, + or you can use the [ALDashboard](../components/ALDashboard/overview) You can also update individual packages on the Package Management screen. - When to update - Every few weeks or as needed if there is a critical bug fix or a desired new feature. diff --git a/docs/archive/bootcamp.md b/docs/archive/bootcamp.md index b82a3553a..6dda6de5e 100644 --- a/docs/archive/bootcamp.md +++ b/docs/archive/bootcamp.md @@ -29,8 +29,8 @@ If you are finding this page after February 18th, 2021, be sure to watch the vid Read: 1. If you are unfamiliar with the Assembly Line project, please give the [intro page](../get_started) a look -1. Read over this short [introduction to Docassemble](https://suffolklitlab.org/legal-tech-class/docs/introduction-to-docassemble) -1. And if you want more context, consider reading [what should you use Docassemble for?](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/practical-guide-docassemble) +1. Read over this short [introduction to Docassemble](../docassemble_intro/introduction-to-docassemble) +1. And if you want more context, consider reading [what should you use Docassemble for?](../docassemble_intro/practical-guide-docassemble) Do: @@ -38,7 +38,7 @@ After signing up, you should have received invites to our docassemble server (ht 1. Confirm that you can access the docassemble server and your developer account at https://apps-dev.suffolklitlab.org 1. Confirm that you can access the [Suffolk LIT Lab Teams group](https://teams.microsoft.com/l/channel/19%3a143ac652e7f1494aacda1f6793b21ccc%40thread.tacv2/bootcamp?groupId=eaa9bd9d-cf39-4686-8f30-e55aa9d98c75&tenantId=78733fa9-540e-4eb8-bf29-73c4eeb63412) -1. **Optional:** Work through this [Hello, World](https://suffolklitlab.org/legal-tech-class/docs/classes/docacon-2020/hello-world) exercise making use of your developer account. NOTE: this may be challenging for those of you without prior programing experience. You will **NOT** be behind if you can't do it all on your own. Use the [#coding-help](https://teams.microsoft.com/l/channel/19%3ae16e9e9701a5445ea4035b5cb776a4cc%40thread.tacv2/coding-help?groupId=eaa9bd9d-cf39-4686-8f30-e55aa9d98c75&tenantId=78733fa9-540e-4eb8-bf29-73c4eeb63412) channel on Teams if you are stuck and want help. +1. **Optional:** Work through this [Hello, World](../docassemble_intro/hello-world) exercise making use of your developer account. NOTE: this may be challenging for those of you without prior programing experience. You will **NOT** be behind if you can't do it all on your own. Use the [#coding-help](https://teams.microsoft.com/l/channel/19%3ae16e9e9701a5445ea4035b5cb776a4cc%40thread.tacv2/coding-help?groupId=eaa9bd9d-cf39-4686-8f30-e55aa9d98c75&tenantId=78733fa9-540e-4eb8-bf29-73c4eeb63412) channel on Teams if you are stuck and want help. ### Week 1: Introduction and getting started using the Assembly Line interview Weaver, 2/18/2021 diff --git a/docs/authoring/customizing_interview.md b/docs/authoring/customizing_interview.md index 994365ca2..819776479 100644 --- a/docs/authoring/customizing_interview.md +++ b/docs/authoring/customizing_interview.md @@ -9,7 +9,7 @@ slug: customizing_interview While the Weaver is a menu-driven, step-by-step process, you'll make further edits in the Playground. In the playground, you can directly edit the -[YAML](https://suffolklitlab.org/legal-tech-class/docs/yaml) text to: +[YAML](../docassemble_intro/yaml) text to: 1. Change the wording of questions 1. Change the datatype of questions and add show/hide logic @@ -22,7 +22,7 @@ many changes can be understood by reading the text and then experimenting. This pages offers information about making some common, simple edits. You may also want to take this time to read through the materials in -the [Legal Tech Class](https://suffolklitlab.org/legal-tech-class/docs/introduction-to-docassemble) +[Introduction to Docassemble](../docassemble_intro/introduction-to-docassemble) about the underlying Docassemble platform and how it works. ## Double-check that you got things right in the Weaver stage @@ -135,7 +135,7 @@ you get the name of the user, you would simply switch the order of those 2 lines :::note Learn more about interview order This section mentions a bit about interview order, but if you want to learn more, -you can read it on the [Legal Tech Class](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/controlling-interview-order) site. +you can read it on the [Legal Tech Class](../docassemble_intro/controlling-interview-order) site. ::: ### Order is usually determined by the first variable on the screen diff --git a/docs/authoring/docx_templates.md b/docs/authoring/docx_templates.md index fa98e0ca7..815a3e98f 100644 --- a/docs/authoring/docx_templates.md +++ b/docs/authoring/docx_templates.md @@ -46,7 +46,7 @@ Microsoft Word document, surrounded by two sets of curly braces, like: `{{ varia The syntax to type in fields in a DOCX template is named `jinja2`. -[Learn more about Jinja2](https://suffolklitlab.org/legal-tech-class/docs/jinja2) +[Learn more about Jinja2](../docassemble_intro/jinja2) and about [DOCX templates in Docassemble](https://docassemble.org/docs/documents.html#docx%20template%20file). diff --git a/docs/coding_style/yaml.md b/docs/coding_style/yaml.md index d76439d09..0fd4cf5c2 100644 --- a/docs/coding_style/yaml.md +++ b/docs/coding_style/yaml.md @@ -6,7 +6,7 @@ slug: yaml --- Docassemble interviews are written in -[YAML](https://suffolklitlab.org/legal-tech-class/docs/yaml). +[YAML](../docassemble_intro/yaml). They may also contain: @@ -121,7 +121,7 @@ you should never put a `mandatory` block in the files for each individual docume To group logic that you might want to run with the template it relates to, without making it mandatory: -1. use the [`named block` pattern](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/controlling-interview-order#triggering-code-and-then-continuing-using-named-blocks) for the `interview order` block +1. use the [`named block` pattern](../docassemble_intro/controlling-interview-order#triggering-code-and-then-continuing-using-named-blocks) for the `interview order` block 1. reference your `named block` in a mandatory block inside the `main` file that users will run. While in the development phase, you might want to temporarily test a single document at a time. diff --git a/docs/coding_style/yaml_dynamic.md b/docs/coding_style/yaml_dynamic.md index 19330cbd9..b7d4d7600 100644 --- a/docs/coding_style/yaml_dynamic.md +++ b/docs/coding_style/yaml_dynamic.md @@ -85,7 +85,7 @@ copying and pasting code. ### Use the "named block" pattern sparingly The [`named block` -pattern](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/controlling-interview-order#triggering-code-and-then-continuing-using-named-blocks) +pattern](../docassemble_intro/controlling-interview-order#triggering-code-and-then-continuing-using-named-blocks) should not be overused. Most often, it is appropriate to just use a standard code block that defines a variable without giving it a "name". diff --git a/docs/coding_style/yaml_structure.md b/docs/coding_style/yaml_structure.md index 6f734246d..49faee3c8 100644 --- a/docs/coding_style/yaml_structure.md +++ b/docs/coding_style/yaml_structure.md @@ -14,7 +14,7 @@ good practice for organizing your blocks into a YAML file: `include`s are in the wrong order. Keep them up top. 1. Metadata, such as file description and title, navigation [`sections`](https://docassemble.org/docs/initial.html#sections). 1. Your single, mandatory, [interview - order](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/controlling-interview-order#the-interview-order-block) + order](../docassemble_intro/controlling-interview-order#the-interview-order-block) block. Ideally this should start "above the fold" in your playground. I.e., in the first 20 or so lines. Relocate other blocks as needed to get this "above the fold" (except the `include` block). 1. `object` blocks representing variables used in your interview. @@ -80,13 +80,13 @@ Adding an id to each question: 1. can prevent some (rare) docassemble bugs, or make it easier to find them 1. make it easier to read and understand an interview phrase translation file -### Use an [interview order](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/controlling-interview-order#the-interview-order-block) block +### Use an [interview order](../docassemble_intro/controlling-interview-order#the-interview-order-block) block It is important to be able view the logic of your interview at a glance. The `interview order` block concept is the most powerful way that we have found to accomplish that goal in Docassemble. -[Read more about interview order blocks](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/controlling-interview-order#the-interview-order-block). +[Read more about interview order blocks](../docassemble_intro/controlling-interview-order#the-interview-order-block). ### Avoid setting values in your interview order block diff --git a/docs/components/ALToolbox/al_income_expenses.md b/docs/components/ALToolbox/al_income_expenses.md index a6ad1e30c..5af1c2bc9 100644 --- a/docs/components/ALToolbox/al_income_expenses.md +++ b/docs/components/ALToolbox/al_income_expenses.md @@ -50,8 +50,8 @@ Before you start, we'll assume that you: * have access to a [developer account on a docassemble server](https://suffolklitlab.org/legal-tech-class/docs/classes/assembly-line/2020-assembly-line-assignment-1#before-you-get-started) * have the [`AssemblyLine` package installed on your server](../../get_started/installation.md#run-the-installation-script), or have installed the [`ALToolbox` package](https://github.com/SuffolkLITLab/docassemble-ALToolbox) separately. -* know [what the playground is](https://suffolklitlab.org/legal-tech-class/docs/classes/docacon-2020/hello-world#introduction-to-the-docassemble-playground) and [how to use it to develop a docassemble interview](https://suffolklitlab.org/legal-tech-class/docs/classes/docacon-2020/hello-world#hello-world) -* know [what "blocks" are](https://suffolklitlab.org/legal-tech-class/docs/yaml#documents) in docassemble +* know [what the playground is](/docs/docassemble_intro/hello-world#introduction-to-the-docassemble-playground) and [how to use it to develop a docassemble interview](/docs/docassemble_intro/hello-world#hello-world) +* know [what "blocks" are](/docs/docassemble_intro/yaml#documents) in docassemble ### Writing the interview @@ -80,7 +80,7 @@ objects: ::: -Then to trigger gathering expenses, add the following [interview order code block](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/controlling-interview-order#the-interview-order-block) into your tutorial interview. +Then to trigger gathering expenses, add the following [interview order code block](/docs/docassemble_intro/controlling-interview-order#the-interview-order-block) into your tutorial interview. ```yml mandatory: True diff --git a/docs/components/ALToolbox/al_income_itemizedjobs.md b/docs/components/ALToolbox/al_income_itemizedjobs.md index d1d06462d..1288478d8 100644 --- a/docs/components/ALToolbox/al_income_itemizedjobs.md +++ b/docs/components/ALToolbox/al_income_itemizedjobs.md @@ -31,8 +31,8 @@ Before you start, we'll assume that you * have access to a [developer account on a docassemble server](https://suffolklitlab.org/legal-tech-class/docs/classes/assembly-line/2020-assembly-line-assignment-1#before-you-get-started) * have the [`ALToolbox` package installed on your server](../../get_started/installation.md#run-the-installation-script) -* know [what the playground is](https://suffolklitlab.org/legal-tech-class/docs/classes/docacon-2020/hello-world#introduction-to-the-docassemble-playground) and [how to use it to develop a docassemble interview](https://suffolklitlab.org/legal-tech-class/docs/classes/docacon-2020/hello-world#hello-world) -* know [what "blocks" are](https://suffolklitlab.org/legal-tech-class/docs/yaml#documents) in docassemble +* know [what the playground is](/docs/docassemble_intro/hello-world#introduction-to-the-docassemble-playground) and [how to use it to develop a docassemble interview](/docs/docassemble_intro/hello-world#hello-world) +* know [what "blocks" are](/docs/docassemble_intro/yaml#documents) in docassemble * have looked at the [the `ALJob` tutorial](al_income_jobs.md) and decided you need to handle more complicated information ### Writing the interview @@ -51,7 +51,7 @@ objects: - jobs: ALItemizedJobList.using(complete_attribute='complete', ask_number=True) ``` -Next, add a [interview order code block](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/controlling-interview-order#the-interview-order-block): +Next, add a [interview order code block](/docs/docassemble_intro/controlling-interview-order#the-interview-order-block): ```yml mandatory: True diff --git a/docs/components/ALToolbox/al_income_jobs.md b/docs/components/ALToolbox/al_income_jobs.md index 6299e49d3..c21ba8f4e 100644 --- a/docs/components/ALToolbox/al_income_jobs.md +++ b/docs/components/ALToolbox/al_income_jobs.md @@ -22,8 +22,8 @@ Before you start, we'll assume that you: * have access to a [developer account on a docassemble server](https://suffolklitlab.org/legal-tech-class/docs/classes/assembly-line/2020-assembly-line-assignment-1#before-you-get-started) * have the [`ALToolbox` package installed on your server](../../get_started/installation.md#run-the-installation-script) -* know [what the playground is](https://suffolklitlab.org/legal-tech-class/docs/classes/docacon-2020/hello-world#introduction-to-the-docassemble-playground) and [how to use it to develop a docassemble interview](https://suffolklitlab.org/legal-tech-class/docs/classes/docacon-2020/hello-world#hello-world) -* know [what "blocks" are](https://suffolklitlab.org/legal-tech-class/docs/yaml#documents) in docassemble +* know [what the playground is](/docs/docassemble_intro/hello-world#introduction-to-the-docassemble-playground) and [how to use it to develop a docassemble interview](/docs/docassemble_intro/hello-world#hello-world) +* know [what "blocks" are](/docs/docassemble_intro/yaml#documents) in docassemble ### Writing the interview @@ -57,7 +57,7 @@ objects: ::: -Next, add a [interview order code block](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/controlling-interview-order#the-interview-order-block): +Next, add a [interview order code block](/docs/docassemble_intro/controlling-interview-order#the-interview-order-block): ```yml mandatory: True diff --git a/docs/components/AssemblyLine/translation.md b/docs/components/AssemblyLine/translation.md index 2ed8e5995..e410cb21f 100644 --- a/docs/components/AssemblyLine/translation.md +++ b/docs/components/AssemblyLine/translation.md @@ -20,6 +20,20 @@ to help you offer your interview in multiple languages: ## Core concepts +### Language Codes + +In Docassemble, you reference the language you are using with a short name of +your choice. + +Most authors use 2-letter [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) +language codes to name their languages. If there is no appropriate +2-letter code, you should consider using [ISO 639-3](https://en.wikipedia.org/wiki/ISO_639-3). + +Some system phrases are already translated and keyed to the two-letter ISO 639-1 +code. So it's a good idea to stick with this convention. But if you need to +translate into a dialect and you can't find an official name, feel free to use +one of your own invention, as long as you use the same one everywhere for that dialect. The official code is just a convention. + ### XLSX File format Docassemble interviews can be translated by adding a special Excel spreadsheet (XLSX format) @@ -27,11 +41,11 @@ in the `sources` folder of your package. Docassemble's translation system works without requiring you to maintain multiple YAML files. The translated phrases are loaded "live" when you run your interview. +### What the translator sees + In the XLSX file, the translator sees two columns: one with English (or your source language) phrases, and an empty column where they can write the translated phrases. -### What the translator sees - Any mako tags that you used, for example, `${ variable }`, will be visible in the English version of the cell, but will be highlighted in a distinct color. Similarly, HTML will also be highlighted. @@ -44,6 +58,13 @@ Some parts of the interview require special handling: * Blocks that do not use Mako, like the `sections` block, need to be translated in-place with the `language` modifier. +When a phrase has not been translated yet, the user will not get an error. Instead, Docassemble will show the default language. + +Translation works off of an exact match. When you change the original language +of a question, the translation will no longer be valid. Even changes to +punctuation and white space will cause Docassemble to ignore the translation and +show the page in its original language. + ## Making a translation file Use the ALDashboard's "Prepare Translation Files" menu item to make a new translation file, @@ -287,4 +308,10 @@ If a language code is not listed in `languages.yml`, the Assembly Line functions You can read more about the stock language features in the official Docassemble [language features documentation](https://docassemble.org/docs/language.html). -Also, see the documentation for the [AL language module](language.md) for complete API documentation of all language-related functions. \ No newline at end of file +Also, see the documentation for the [AL language module](language.md) for complete API documentation of all language-related functions. + +### More complex multi-lingual interviews you can inspect + +* [MADE](https://gbls.org/MADE), ([GitHub source code](https://github.com/GBLS/docassemble-maevictiondefense)) +* [UpToCode](https://getuptocode.org), ([GitHub source code][https://github.com/LemmaLegalConsulting/docassemble-HousingCodeChecklist]) +* [Massachusetts 209A Abuse Prevention Petition](https://courtformsonline.org/dv/#209A), ([GitHub source code](https://github.com/suffolklitlab/docassemble-MA209AProtectiveOrder)) \ No newline at end of file diff --git a/docs/components/GithubFeedbackForm/github_feedback.md b/docs/components/GithubFeedbackForm/github_feedback.md index f2ef3f4a5..e41dd91eb 100644 --- a/docs/components/GithubFeedbackForm/github_feedback.md +++ b/docs/components/GithubFeedbackForm/github_feedback.md @@ -53,8 +53,8 @@ Before you start, we'll assume that you: * have access to an [admin account on a docassemble server](https://suffolklitlab.org/legal-tech-class/docs/classes/assembly-line/2020-assembly-line-assignment-1#before-you-get-started) * have the [`GithubFeedbackForm` package installed on your server](../../get_started/installation.md#run-the-installation-script) * know how to [edit the docassemble configuration (config) file](https://docassemble.org/docs/config.html#edit) -* know [what the playground is](https://suffolklitlab.org/legal-tech-class/docs/classes/docacon-2020/hello-world#introduction-to-the-docassemble-playground) and [how to use it to develop a docassemble interview](https://suffolklitlab.org/legal-tech-class/docs/classes/docacon-2020/hello-world#hello-world) -* know [what "blocks" are](https://suffolklitlab.org/legal-tech-class/docs/yaml#documents) in docassemble +* know [what the playground is](/docs/docassemble_intro/hello-world#introduction-to-the-docassemble-playground) and [how to use it to develop a docassemble interview](/docs/docassemble_intro/hello-world#hello-world) +* know [what "blocks" are](/docs/docassemble_intro/yaml#documents) in docassemble ### Configuration diff --git a/docs/docassemble_intro/hello-world.md b/docs/docassemble_intro/hello-world.md index cdd0fa58e..ffd0b5e70 100644 --- a/docs/docassemble_intro/hello-world.md +++ b/docs/docassemble_intro/hello-world.md @@ -13,7 +13,7 @@ language. ## What is Docassemble? Docassemble is a system for running interactive questionnaires. It uses several -open source technologies, including [Python](/docs/python.md), [Markdown](/docs/markdown.md), [YAML](/docs/yaml.md) and docx-template to +open source technologies, including [Python](python.md), [Markdown](markdown.md), [YAML](yaml.md) and docx-template to let you concentrate on writing your interviews and writing very little code. Yet, it's flexible enough to allow you to use advanced coding techniques when you need to do so. diff --git a/docs/docassemble_intro/logic.md b/docs/docassemble_intro/logic.md index 2f9f8f718..1968d23b7 100644 --- a/docs/docassemble_intro/logic.md +++ b/docs/docassemble_intro/logic.md @@ -72,7 +72,7 @@ with `%`. We also need to use an `endif` statement, instead of using indentation to show the beginning and end of the `if` statement. :::info About the `%` symbol in Mako -The ``%` symbol has a special meaning in [Mako](../../mako.md). It +The ``%` symbol has a special meaning in [Mako](mako.md). It lets you use Python syntax at the start and end of a block, usually to control conditional text. It is very handy when you have a large block of text that you want to show or hide, especially if the block has formatting diff --git a/docs/docassemble_intro/practical-guide-docassemble.md b/docs/docassemble_intro/practical-guide-docassemble.md index 906082e34..b0bd8f95f 100644 --- a/docs/docassemble_intro/practical-guide-docassemble.md +++ b/docs/docassemble_intro/practical-guide-docassemble.md @@ -6,9 +6,6 @@ sidebar_label: What should you use Docassemble for? ## What should you use Docassemble for? -If we look over our [legal technology taxonomy](legal-tech-overview/legal-tech-overview.md), -which of these tasks is Docassemble best suited for? - Docassemble's main metaphor is a linear series of step-by-step questions. Many of the things Docassemble does best are controlled by that choice: * Expert systems diff --git a/docs/docassemble_intro/translating-interviews.md b/docs/docassemble_intro/translating-interviews.md deleted file mode 100644 index f863ed94d..000000000 --- a/docs/docassemble_intro/translating-interviews.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -slug: translating-interviews -title: Translating your interview into other languages -sidebar_label: Translating your interview ---- - -# Creating interviews in multiple languages - -Docassemble interviews can be translated into multiple languages. -It includes handy features that load the translation at runtime, which -means you do not need to maintain two separate versions of the interview -to get the benefit of multiple languages. - -The translation system is friendly for working with professional -translators and amateur translators alike. It takes advantage of simple -human-readable spreadsheets or text files. - -## The basics - -### Naming your language - -In Docassemble, you reference the language you are using with a short name of -your choice. - -Most authors use 2-letter [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) -language codes to name their languages. If there is no appropriate -2-letter code, you should consider using [ISO 639-3](https://en.wikipedia.org/wiki/ISO_639-3). - -Some system phrases are already translated and keyed to the two-letter ISO 639-1 -code. So it's a good idea to stick with this convention. But if you need to -translate into a dialect and you can't find an official name, feel free to use -one of your own invention. The official code is just a convention. - -### Switching languages - -You can control the language of an interview by using the [`set_language()`](https://docassemble.org/docs/functions.html#set_language) function. The -name for the language you want to switch to is the only required parameter. - -Docassemble checks the value of `get_language()` to decide what language to -display system phrases and question text in. There is no built-in menu item -that allows you to switch languages. You need to add a question or custom -menu item that invokes `set_language()` at the appropriate time. - -### Incomplete translations - -When a phrase has not been translated yet, the user will not get an error. Instead, Docassemble will show the default language. - -Translation works off of an exact match. When you change the original language -of a question, the translation will no longer be valid. Even changes to -punctuation and white space will cause Docassemble to ignore the translation and -show the page in its original language. - -Despite this need for an exact match, the translation system is granular. -Each field, option, question text, and subquestion text will be considered -separate items to get translated. If some but not all phrases are translated, -a partial translation will be shown. - -### What can be translated - -All of the text in the Docassemble interface and interview can -be translated. - -Docassemble has separate systems for translating different kinds -of interview text: - -Kind of text | Docassemble documentation page --------------|------------------------ -Question text | [Spreadsheet or XLIFF file](https://docassemble.org/docs/language.html) -System text (buttons, built-in menu items) | [words.yml](https://docassemble.org/docs/config.html#words) -Code and metadata blocks | Add multiple blocks, with a [language modifier](https://docassemble.org/docs/modifiers.html#language) -Documents | [Language modifier](https://docassemble.org/docs/documents.html#language), also see [Creating documents in languages other than English](https://docassemble.org/docs/language.html#documents) -Function output | [Language-specific functions](https://docassemble.org/docs/functions.html#linguistic) - -## Demo interview - -Below is a small interview that demonstrates several -translation features. - -```yaml ---- -# This should always be the first line of the YAML. It needs -# to come before any text that will get translated -translations: - - phrases_es.xlsx # this spreadsheet has most of the interview phrases ---- -features: - navigation: True ---- -language: en -sections: - - introduction: Introduction - - ending: Ending ---- -# The `sections` block is one that doesn't appear in the -# translations spreadsheet. You need to use a language modifier -# instead -language: es -sections: - - introduction: Introducción - - ending: Conclusión ---- -mandatory: True -code: | - set_language(language_choice) # This actually sets the language to the two-letter code below - intro - ending ---- -question: | - What is your language? -fields: - - Language/lengua: language_choice - datatype: radio - choices: - - English: en - - Español: es ---- -section: introduction -continue button field: intro -question: | - This is the first screen -subquestion: | - This is the subquestion area of the first screen. -fields: - - The first field: field1 - - Select one or more: field2 - datatype: checkboxes - choices: - - Label 1: value1 - - Label 2: value2 - - Label 3: value3 - - Label 4: value4 ---- -section: ending -event: ending -question: | - All done -subquestion: | - When you use the comma_and_list() function, the word "and" - is translated so long as a words.yml is defined in the global configuration - file. - -``` - -The XLSX file phrases_es.xlsx has these contents: - -interview | question_id | index_num | hash | orig_lang | tr_lang | orig_text | tr_text -----------|-------------|-----------|------|-----------|---------|-----------|---------- -docassemble.playground10language:language.yml | Question_1 | 0 | dbc292ca7541f124f4924e3f0dd9608d | en | es | "What is your language? -" | ¿Cuál es su idioma? -docassemble.playground10language:language.yml | Question_1 | 1 | 12cad744449f8d4cbdeb9b6fb2738a48 | en | es | Language/lengua | -docassemble.playground10language:language.yml | Question_1 | 2 | 78463a384a5aa4fad5fa73e2f506ecfc | en | es | English | -docassemble.playground10language:language.yml | Question_1 | 3 | de92bbe05815c4eb756acb5897baa99c | en | es | Español | -docassemble.playground10language:language.yml | Question_2 | 0 | 8800e1c9b3e22c44ba59a34db3fe4841 | en | es | introduction | -docassemble.playground10language:language.yml | Question_2 | 1 | 2a11e19cd150a312759299a692dc394a | en | es | "This is the first screen -" | Esta es la primera pantalla -docassemble.playground10language:language.yml | Question_2 | 2 | 1d8d0906aad3c23c4be70b376b8d02d8 | en | es | "This is the subquestion area of the first screen. -" | Esta es el área de subpreguntas de la primera pantalla. -docassemble.playground10language:language.yml | Question_2 | 3 | 75f398248f25e05bc99e157d7145c363 | en | es | The first field | el primer campo -docassemble.playground10language:language.yml | Question_2 | 4 | e055c0d5b54f36c2665d271fb86079fc | en | es | Select one or more | Seleccione uno o más -docassemble.playground10language:language.yml | Question_2 | 5 | eff63417104e8ebe57b3531054153264 | en | es | Label 1 | Etiqueta 1 -docassemble.playground10language:language.yml | Question_2 | 6 | f95d4806340464e46da38ed8a1ce45c6 | en | es | Label 2 | Etiqueta 2 -docassemble.playground10language:language.yml | Question_2 | 7 | a890dd1894c379ab876f8d2616ba5907 | en | es | Label 3 | Etiqueta 3 -docassemble.playground10language:language.yml | Question_2 | 8 | 650857596fe374d17d7712d706f3864c | en | es | Label 4 | Etiqueta 4 -docassemble.playground10language:language.yml | Question_3 | 0 | deeb6f5001d774a40a29140b74cf5011 | en | es | ending | -docassemble.playground10language:language.yml | Question_3 | 1 | c46c51f9085b1460607798292bcf5232 | en | es | "All done -" | Todo listo -docassemble.playground10language:language.yml | Question_3 | 2 | 0c5b0b5e1c20a9af00353e64f6d2a062 | en | es | "When you use the comma_and_list() function, the word "and" is translated so long as a words.yml is defined in the global configuration file. " | Cuando usas la función `coma_and_list()`, la palabra ""y"" se traduce siempre que se defina un archivo words.yml en la configuración global archivo. - -The XLSX file was created by visiting https://[myserver]/utilities and typing -"docassemble.playground10language:language.yml" in the "Download an interview phrase translation file" box. - -In the translation file, notice we left several lines that we do not want to translate blank. - -The important columns are are the `hash` and `tr_text` columns. The translator can directly edit the `tr_text` column -to add the translations that are needed. Any mako tags or other special syntax will be color-coded. Just let the translator -know to ignore any text that is not black. - -## Read more - -* [Docassemble's official documentation on language functions](https://docassemble.org/docs/language.html) - -### More complex multi-lingual interviews you can inspect - -* [MADE](https://gbls.org/MADE), ([GitHub source code](https://github.com/GBLS/docassemble-maevictiondefense)) -* [UpToCode](https://getuptocode.org), ([GitHub source code][https://github.com/LemmaLegalConsulting/docassemble-HousingCodeChecklist]) -* [Massachusetts 209A Abuse Prevention Petition](https://courtformsonline.org/dv/#209A), ([GitHub source code](https://github.com/suffolklitlab/docassemble-MA209AProtectiveOrder)) \ No newline at end of file diff --git a/docs/get_started/beginners_guide.md b/docs/get_started/beginners_guide.md index e62f4cd7b..6b0152e98 100644 --- a/docs/get_started/beginners_guide.md +++ b/docs/get_started/beginners_guide.md @@ -23,11 +23,11 @@ In order to build interviews you will need access to a [Docassemble playground]( ## Do the Hello, World exercise -In his Legal Tech Class, Quinten Steenhuis introduces Docassemble with a [short "Hello, World" exercise](https://suffolklitlab.org/legal-tech-class/docs/classes/docacon-2020/hello-world). This introductory exercise is a great way to demystify Docassemble and see what it's like to build a simple interview—by actually doing it. +We usually introduces Docassemble with a [short "Hello, World" exercise](../docassemble_intro/hello-world). This introductory exercise is a great way to demystify Docassemble and see what it's like to build a simple interview—by actually doing it. -**➡️ [Go to the Hello, World exercise.](https://suffolklitlab.org/legal-tech-class/docs/classes/docacon-2020/hello-world)** +**➡️ [Go to the Hello, World exercise.](../docassemble_intro/hello-world)** -Once you have successfully completed the Hello, World exercise you might want to continue with the next few lessons in the Legal Tech Class to learn more about building interviews in Docassemble. +Once you have successfully completed the Hello, World exercise you might want to continue with the next few pages in the Introduction to Docassemble to learn more about building interviews in Docassemble. When you have finished the Hello, World exercise, move on to the next step: learning about the Weaver. diff --git a/docs/get_started/installation.md b/docs/get_started/installation.md index e194849b9..2bc94c0fe 100644 --- a/docs/get_started/installation.md +++ b/docs/get_started/installation.md @@ -6,7 +6,7 @@ slug: installation --- Before you get started, make sure that you have [installed -Docassemble](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/setup-server) +Docassemble](../admin_guide_docassemble/setup-server) on a server that you maintain. Installing the Assembly Line covers the following basic steps: diff --git a/docs/overview.md b/docs/overview.md index 241304a4b..e0e6babca 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -7,20 +7,6 @@ slug: overview ## This documentation covers the custom stuff -While some of the documentation in this guide is general purpose, -we don't cover the basics of Docassemble development. - -To learn about Docassemble syntax, such as: - -- [The format of a Docassemble question file (YAML)](https://suffolklitlab.org/legal-tech-class/docs/yaml) -- [Using Python in Docassemble](https://suffolklitlab.org/legal-tech-class/docs/python) -- The syntax for inserting [Mako](https://suffolklitlab.org/legal-tech-class/docs/mako) or [Jinja2](https://suffolklitlab.org/legal-tech-class/docs/jinja2) variables -- Advanced logic, like [controlling question order](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/controlling-interview-order) -- Working with Docassemble [objects](https://suffolklitlab.org/legal-tech-class/docs/practical-guide-docassemble/object-oriented-programming) and object-oriented programming -- Using [repeated items](https://suffolklitlab.org/legal-tech-class/docs/repeated-information) and lists in Docassemble - -Please review: - -* [Introduction to Docassemble](https://suffolklitlab.org/legal-tech-class/docs/introduction-to-docassemble/) (and the pages that follow) for a readable, step-by-step guide to the core features and syntax of Docassemble -* [The official Docassemble documentation](https://docassemble.org/docs.html) for everything else. Be aware that the official documentation has a **lot** of content. Feel free to ask for help finding the right link! +Please review [Introduction to Docassemble](docassemble_intro/introduction-to-docassemble.md) (and the pages that follow) for a readable, step-by-step guide to the core features and syntax of Docassemble, such as: +See [The official Docassemble documentation](https://docassemble.org/docs.html) for everything else. Be aware that the official documentation has a **lot** of content. Feel free to ask for help finding the right link! diff --git a/docusaurus.config.js b/docusaurus.config.js index 88558b59a..e5e5bf894 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -36,11 +36,6 @@ module.exports = { sidebarId: 'docs', label: 'Documentation', }, - { - href: 'https://suffolklitlab.org/legal-tech-class/docs/introduction-to-docassemble', - label: 'Legal tech class', - position: 'right', - }, { href: 'https://docassemble.org/docs.html', label: 'Docassemble docs', diff --git a/sidebars.js b/sidebars.js index eaacf1045..557afb0b6 100644 --- a/sidebars.js +++ b/sidebars.js @@ -68,24 +68,16 @@ module.exports = { 'docassemble_intro/repeated-information', ] }, - { - type: "category", - label: "Customizing Docassemble", - items: [ - 'docassemble_intro/theming-docassemble', - 'docassemble_intro/translating-interviews', - ] - }, { type: 'category', label: 'Administering Docassemble', items: [ - 'admin-guide-docassemble/setup-server', - 'admin-guide-docassemble/rebuild-lightsail-instance', - 'admin-guide-docassemble/run-docassemble-docker-vscode', - //'admin-guide-docassemble/combining-interviews', - 'admin-guide-docassemble/installing-production-app', - 'admin-guide-docassemble/updates-and-maintenance', + 'admin_guide_docassemble/setup-server', + 'admin_guide_docassemble/rebuild-lightsail-instance', + 'admin_guide_docassemble/run-docassemble-docker-vscode', + //'admin_guide_docassemble/combining-interviews', + 'admin_guide_docassemble/installing-production-app', + 'admin_guide_docassemble/updates-and-maintenance', ], }, ]