Skip to content

PDF builder that's used by the SimplerNOFOs project, part of the Simpler Grants initiative at HHS.

Notifications You must be signed in to change notification settings

HHS/simpler-grants-pdf-builder

Repository files navigation

NOFO Builder

📄✍️🦆

The no-code NOFO web flow

About

The NOFO Builder is a Word-2-PDF pipeline that ingests Word files and generates a tagged PDF file using a USWDS-based design that is both accessible and attractive for applicants. It is a tool to build publishable PDFs from reviewed and finalized NOFO documents.

The NOFO Builder is a Django app that can be run as a Python process or as a Docker container.

What is a NOFO?

A "Notice of Financial Opportunity" (NOFO) is a big document accouncing government funding for certain projects or activities (like an RFP). Suppliers can bid on the NOFO for a chance to win funding for delivering the specified outcome.

An example of a NOFO might be an announcement of funding to provide preschool services in Florida.

NOFOs are typically very long, very plain documents without much in the way of formatting.

The SimplerNOFOs project relies on NOFOs that have been written using content guides: essentially, templated starter documents that ensure NOFOs are structured in similar ways.

Once the NOFO documents have been finalized, the NOFO Builder imports these documents as .docx files to generate publishable PDFs that are better structured and easier to read.

Workflow

NOFOs are written by HHS’ Operating Divisions (OpDivs), and peer-edited by Bloom editing coaches, before proceeding through internal reviews. The writing and editing happens using ‘content guides:’ template-like Word documents that provide a starting point for new NOFOs. Content guides use tagged headings, lists, and tables, and structure the flow of content for a NOFO.

Once a NOFO is reviewed and approved, our workflow is:

  1. NOFO is approved to be published
  2. A NOFO designer receives the finalized .docx file
  3. The NOFO designer logs into the NOFO builder
  4. The NOFO designer uploads the .docx file to create an HTML representation of the NOFO
  5. The NOFO designer can view and make edits to the uploaded NOFO
  6. We use a PDF renderer to output the NOFO as a PDF, based on the HTML layout.
  7. Done!

Getting started

python is a high-level, general-purpose programming language, popular for programming web applications.

This project uses Python >=3.11.

You can find the exact version of python that is being used by referencing the Dockerfile. If you want to change your Python version, I would recommend pyenv.

poetry is a tool for dependency management and packaging in Python. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you.

A docker container allows a developer to package up an application and all of its parts. This means we can build an app in any language, in any stack, and then run it anywhere — whether locally or on a server.

Create a .env file

You will need a .env file to run this application.

# create .env file from example file
cp ./bloom_nofos/bloom_nofos/.env.example ./bloom_nofos/bloom_nofos/.env

If you are running locally, the example file will work just fine.

Build and run locally with poetry

Just install the dependencies and boot it up. Pretty slick. 😎

Important: make sure to run poetry commands from the ./bloom_nofos directory.

# install dependencies
poetry install

# make sure you are in the "./bloom_nofos" directory
cd ./bloom_nofos

# run migrations (needed when first booting up the app)
poetry run migrate

# run application in 'dev' mode
# (ie, the server restarts when you save a file)
poetry run start

The app should be running at http://localhost:8000/.

On a Mac, press Control + C to quit the running application.

Adding users

Currently, the NOFO Builder is an internal tool whose entire purpose is managing and printing NOFO documents, so the user features are pretty barebones. What this means is that we rely on the Django admin for user adminstration.

During first-time setup, create a superuser account.

# create superuser account
poetry run python manage.py createsuperuser

Superusers are the only accounts able to access the admin backend at http://localhost:8000/admin. Once you are logged in, you can use the admin backend to create and manage accounts for new users.

Running default django commands with poetry

Django's default commands can be run by calling python manage.py {command}. In this repo, we are using poetry to run them.

Important: make sure to run poetry commands from the ./bloom_nofos directory.

# running default django commands
poetry run python manage.py {runserver, makemigrations, migrate, etc}

A note on the CSS/HTML

This app uses a static version of the US Web Design System (USWDS) styles, generated on November 8th, 2023.

I've made a couple of tweaks so that they work in this app.

Adjustments to styles.css

  • Update font paths from "../fonts" to "/static/fonts"
  • Update icon paths from "../img/usa-icons" to "/static/img/usa-icons"
  • Update img paths from "../img" to "/static/img"

Environment variables

No additional environment variables are needed to run the application in dev mode, but to run in production, several are needed.

To manually deploy to production, create a new file ./bloom_nofos/bloom_nofos/.env.production.

  • DEBUG=false: Never run in production with debug mode turned on.

    • default True
  • SECRET_KEY: used by Django to encrypt sessions. This can be any random string of sufficient complexity.

    • default secret-key-123
  • DATABASE_URL: This app can be configured to use an external database or a local SQLite database. In production, it uses an external Postgres database.

    • default "": this means Django will default to using a local SQLite database.
  • DJANGO_ALLOWED_HOSTS: Django will not run correctly on the server unless the domain is specified ahead of time. This env var can contain 1 domain or a comma-separated list of domains

    • default "": no effect unless Django is running in production.
  • DOCRAPTOR_API_KEY: Our API key for printing documents using DocRaptor.

    • default "YOUR_API_KEY_HERE": this key works for printing test documents (with a watermark)
  • DOCRAPTOR_IPS: IP addresses that we expect DocRaptor requests to come from. Note that these can be overridden.

    • default "": this means zero IPs are safelisted
  • API_TOKEN: Bearer token to allow API access.

    • default "": this will block any and all API access.
  • LOGIN_GOV_CLIENT_ID: Should match the "Issuer" string of our Login.gov app.

    • default "": No issuer, will not connect to Login.gov
  • LOGIN_GOV_OIDC_URL=This is the root URL for Login.gov, where we send our auth requests.

    • default "": No url, will not connect to Login.gov
  • LOGIN_GOV_REDIRECT_URI: The URL that Login.gov will redirect to after authentication.

    • default "": No url, will not connect to Login.gov
  • GOOGLE_CLOUD_PROJECT: the GCP project ID containing our Login.gov .pem file.

Login.gov Key Configuration

This application uses Login.gov for authentication and requires both private and public keys. These keys can be sourced from either Google Cloud Secret Manager or local files.

If you do not have these cert files, you won’t be able to log in with Login.gov, but you will still be able to login with Django Auth.

Development Environment

For local development, the application will:

  1. Attempt to fetch keys from Google Cloud Secret Manager if GOOGLE_CLOUD_PROJECT env is set.
  2. If GOOGLE_CLOUD_PROJECT is missing or Secret Manager access fails, fall back to local certificate files.

Option 1: Using Local Certificate Files

  1. Place your Login.gov certificate files in bloom_nofos/bloom_nofos/certs/:

    • login-gov-private.pem
    • login-gov-public.crt
  2. No additional configuration needed - the application will automatically use these files if Secret Manager access fails

Option 2: Using Google Cloud Secret Manager

  1. Ensure you have access to the bloom-nofos-1 project in Google Cloud
  2. You need the "Secret Manager Secret Accessor" role (roles/secretmanager.secretAccessor)
    • This can be granted by a project admin
    • Even if you have the Editor role, you still need this specific role for secret access
  3. Configure your environment:
    # Set up Google Cloud authentication
    gcloud auth application-default login
    gcloud config set project bloom-nofos-1
    gcloud auth application-default set-quota-project bloom-nofos-1
  4. This will fetch the private key for you to use, the public keys should be commited to the bloom_nofos/bloom_nofos/certs directory

Build and run as a Docker container

# build an image locally
docker build -t pcraig3/bloom-nofos:{TAG} .

# run the container
docker run -it -p 8000:8000 pcraig3/bloom-nofos:{TAG}

The container should be running at http://localhost:8000/.

On a Mac, press Control + C to quit the running docker container.

Push to Cloud Run from an M1 Mac

Building a container on an M1 Mac to deploy on a cloud environment means targeting amd64 architecture.

# build the container
- docker buildx build --platform linux/amd64 --build-arg IS_PROD_ARG=1 -t gcr.io/{SERVICE}/{PROJECT}:{TAG} .

# push the container
- docker push gcr.io/{SERVICE}/{PROJECT}:{TAG}

# deploy the container
- gcloud run deploy {SERVICE} \
   --project {PROJECT} \
   --platform managed \
   --region {REGION} \
   --image gcr.io/{SERVICE}/{PROJECT}:{TAG} \
   --add-cloudsql-instances {SERVICE}:{REGION}:{PROJECT} \
   --allow-unauthenticated

About

PDF builder that's used by the SimplerNOFOs project, part of the Simpler Grants initiative at HHS.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •