Developer Resources

This page contains additional information that may be useful to anyone hoping to work on the dashboard’s code.

The app uses a postgreSQL database and the front end is programmed in python using the Flask framework. If you’re trying to get familiar with Flask we recommend this tutorial. We also use SQLAlchemy to manage the database models.

Our production web server uses uWSGI behind an NGINX server. Authentication is handled using OAuth.

Dashboard Structure

dashboard/models/models.py defines the Object Relational Mapping (ORM) used by SQLAlchemy to map from the relational database to the python objects used in the code.

All of the ‘views.py’ scripts define the entry points (i.e. valid URLs for the app). These have been organized into blueprints (dashboard/blueprints) that group related functionality together.

If a blueprint requires any HTML, it is stored inside a ‘templates’ folder nested within the blueprint. All other HTML templates are in dashboard/templates/. Templates with _snip.html or in a ‘snips’ subfolder are used as reusable pieces embedded in other pages.

Changing the Database Schema

To keep the database migrations accurate you should:

  1. Make changes to models.py and not in the database directly.

  2. Generate a migrate script with flask db migrate so others can apply the changes with a simple flask db upgrade

  3. Verify that the script generated by flask db migrate is accurate. Flask-migrate uses Alembic, which is not capable of reliably detecting all changes to the schema so manual intervention is sometimes needed. See here for detailed info on what to look out for.

Jinja Template Tips

Flask’s HTML templates use Jinja to render pages. The most important thing to be aware of is that while you can break up your pages into smaller, more readable, chunks by saving HTML in another file and then importing it with something like this:

{% include 'my_other_file.html' %}

performance-wise, this is not always a good idea. Each and every time the ‘include’ statement is read while the page is constructed, the included file has to be read from the filesystem. File reads are (relatively) slow and if the include is inside of a loop with a large number of iterations, you can easily add extra seconds of load time for a minimal boost in HTML readability.

Some tips to get the most out of Jinja without adding too much overhead:
  • If you have a loop and want to ‘include’ the body of the loop from another file, it’s better to keep the loop inside the included file (so the file is opened and read once, rather than once per iteration)

  • If you have an ‘if’ statement and the body of it is included from another snippet, it’s better to keep the ‘if’ in your original file, so you don’t need to open the snippet just to discover the ‘if’ statement failed

Also note that if you’re organizing your HTML snippets in a nested folder, you always need to give the full path from the root of the template directory to the file you want to include. If you get an error about a missing template, make sure you quoted the name of the file to be included.

# Good
{% include 'my_snippet.html' %} # for templates/my_snippet.html
{% include 'modals/incidental_findings.html' %} # For templates/modals/incidental_findings.html

# Bad
{% include my_snippet.html %} # Missing quotes on file name
{% include 'incidental_findings.html' %} # This file won't be found without the full path

SQLAlchemy Tips

SQLAlchemy is awesome and very powerful BUT sometimes it makes really naive queries. If you try to work with objects from dashboard.models like they’re normal python objects, you can very easily end up generating thousands of queries without realizing it. For instance, if you tried doing something like this:

from dashboard.models import Site, db

cmh = db.session.get(Site, 'CMH')

for timepoint in cmh.timepoints:
   # Do some stuff with the timepoint record here

This loop would generate one query to the database for each timepoint that has the site ‘CMH’. If code like that were embedded in a function called in a Jinja template, then those extra queries would hit every time a user loaded the page. In these sorts of cases it is often better to craft your own queries using SQLAlchemy’s Query API.

Debugging Tips

  • To help with debugging, consider using the Flask Debug Toolbar. It can assist in identifying bottlenecks in load time caused by excess querying by giving you a breakdown of your SQLAlchemy queries.