-------------------
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:
#. Make changes to models.py and not in the database directly.
#. Generate a migrate script with ``flask db migrate`` so others can apply
the changes with a simple ``flask db upgrade``
#. 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:
.. code-block:: jinja
{% 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.
.. code-block:: jinja
# 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:
.. code-block:: python
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.