Skip to content
Snippets Groups Projects

mst-flasklib

Installation

Production Installation

Command Line:

pip install mst-flasklib --index-url https://git.mst.edu/api/v4/groups/it%2Fshared%2Fmst-python-libraries/-/packages/pypi/simple

requirements.txt:

--extra-index-url https://git.mst.edu/api/v4/groups/it%2Fshared%2Fmst-python-libraries/-/packages/pypi/simple
mst-flasklib

Development Installation

While this module is in development you may install it by cloning the repository and then running pip install -e <path to repo>. This will install an editable copy of the module allowing you to make changes and see the results live.

To get this to work with containerized development it is a little more tricky.

  1. Modify your ./launch-dev.sh file to add this line after the last -v line: -v $HOME/repos/mst-flasklib/:/local/<project>/tmp/mst-flasklib/ \
  2. Modify your Dockerfile to run the fixperms command both before and after the setup-python.sh script.
  3. Copy the mst-flasklib repo folder to ./tmp/mst-flasklib (this is only temporary)
  4. Add -e ./tmp/mst-flasklib/ to your requirements.txt
  5. Build the container docker build -t <tag> .
  6. Delete the tmp folder
  7. Run ./launch-dev.sh

At this point, you should have the ./tmp/mst-flasklib/ folder overlayed onto the container from whever you cloned the repo.

Usage

To use this module in your flask project add the following to your create_app() function:

from flask import Flask
from mst.flasklib import MSTFlask
from config import Config # See config section below
from app import main # Import your blueprints

def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)

    # Register all of your blueprints
    app.register_blueprint(main.bp)


    mst_flask_lib = MSTFlask()
    mst_flask_lib.init_app(app)

    return app

Functionality

mst-flasklib contains several components, some of which are automatically included when you run init_app(app).

Templating

mst-flasklib has built in templates for error handling and base page formatting. These templates can be overridden in your application by creating a file with the same name in your templates folder. These templates may also reference templates that may optionally exist in your application.

Directory Structure:

├── appmenu.html (optionally provided by your app, does not exist by default)
├── base.html
├── errors
│   ├── 400.html
│   ├── 401.html
│   ├── 403.html
│   ├── 404.html
│   └── 500.html
└── views
    └── datatable.html

base.html

This file contains the outermost container for your application.

The following variables are available to be used in this template:

  • APP_HEAD_PRE - used before the <title> tag
  • PAGE_TITLE - used inside the <title> tag
  • APP_TITLE - displayed in the header as a link
  • APP_URL - the target of the APP_TITLE link
  • error_content - displays immediatly after the content block inside a <p> tag
  • CONTACT_LABEL - displays at the bottom of the content as a link, prefixed with "Application Contact: "
  • CONTACT_URL - the target of the CONTACT_LABEL link
  • ELAPSED_TIME - displays below the application contact, prefixed with "Elapsed Time: ", can only be seen when highlighted

The following blocks are also available:

  • head_extra - extra content to be placed at the end of the <head> tag
  • app_extra_upper_right - extra content on the upper right, right before the login/logout link
  • content - the main content of the page
  • app_extra_lower_left - extra content to the left of the application contact
  • app_extra_lower_right - extra content to the right of the application contact

It also attempts to include the following templates, create these files to use this functionality:

  • appmenu.html - included below the login/logout button, intended to display a drop down list of links Example appmenu.html:

    <ul id="udm" class="udm">
    <li class='dropdown left_side'><a href="{{url_for(config.INDEX_VIEW)}}"><span class='drop_caret'></span>Home</a>
        <ul>
        <li>
            <a href="{{url_for(config.INDEX_VIEW)}}">Main Application</a>
        </li>
        <li>
            <a href="{{url_for('main.other_page')}}">Some other page</a>
        </li>
        </ul>
    </li>
    </ul>

errors

The library also includes error pages for several error types pre-registered with flask. They are as follows:

  • 400.html - bad request
  • 401.html - Unauthorized
  • 403.html - Forbidden -- This is returned when the user is missing a required privilege in privsys. This template displays a page stating Insufficient Privileges and displays the {{priv_code}} that is required. Be sure to include that variable if manually throwing this error. Example: abort(403, {"priv_code": priv_code})
  • 404.html - Not Found
  • 500.html - Internal Server Error

Views

In addition to having individual html template files, mst-flasklib also provides views that may be used to provide entire pages with a simple configuration.

DataTable

The DataTable view is used to render a table of data using the Javascript DataTables library.

from mst.flasklib.views import DataTable
@bp.route("/datatable")
def datatable():
    column_config = [
        {"data": "first_name", "title": "First Name", "width": "20px"},
        {"data": "last_name", "title": "Last Name"}
    ]

    dt = DataTable(url_for('main.datatable_json'), column_config=column_config)
    return dt.render_template()

@bp.route("/datatable_json")
def datatable_json():
    # Fetch and return JSON data of the form:
    dt_json = {"data": [
        {"first_name": "Joe", "last_name": "Miner"},
        ...
    ]}

    return dt_json

The DataTable constructor can be passed a dictionary containing additional configuration for the DataTable plugin through the datatable_config parameter. These values will override the defaults and be passed to the Javascript plugin. There is also an optional column_defs parameter which should contain a string to be output as raw Javascript as the value for the DataTables columnDefs parameter.

The DataTable.render_template() function has parameters for the template to render, defaulting to views/datatable.html, as well as any other keyword arguments accepted by Flask.render_template()

The html template views/datatable.html is associated with this view.

  • Variables:
    • PAGE_HEADER - Displayed in an h1 tag above the table
  • Blocks:
    • before_table - Displayed before the table
    • after_table - Displayed after the table
    • column_defs - Displayed after the after_table block, intended to contain a script tag that defines the column_defs variable. This may also contain a dt_config variable that will get merged with the configs on the python side.

If you choose to extend the datatable.html template, there are a few things to keep in mind. If you need to add additional content to the head_extra block, you must call {{ super() }} in the block otherwise you will overwrite the contents of the head_extra block set in the template.

Example:

<!-- custom_datatable.html -->
{% extends "views/datatable.html" %}

{% block head_extra %}
{{ super() }}
<!-- Some additional header contents -->
{% endblock %}

{% block before_table %}
<p>This text will be shown above the table.</p>
{% endblock %}

{% block after_table %}
<p>This text will be shown after the table.</p>
{% block %}

{% block column_defs %}
<script>
    // Sets column index 3 to use a renderer that appends the value of the 'empt' column in parentheses to the value of the target column
    var column_defs = [{ targets: 3, render: (data, type, row) => data + ' (' + row['empt'] + ')' }];

    // Create a custom button. Note: Since this overrides the default buttons value, you'll need to include the other button types
    var dt_config = {
        buttons: [
            {
                text: 'Custom Button',
                action: function (e, dt, node, config) {
                    console.log("I'm a button")
                }
            }, "reload", "csv", "excel", "colvis"
        ]
    }
</script>
{% endblock %}
from mst.flasklib.views import DataTable
@bp.route("/datatable")
def datatable():
    column_config = [
        {"data": "first_name", "title": "First Name", "width": "20px"},
        {"data": "last_name", "title": "Last Name"}
    ]

    dt = DataTable(url_for('main.datatable_json'), column_config=column_config)
    return dt.render_template("custom_datatable.html")

Routes

mst-flasklib includes routes for the aforementioned error pages as well as the authentication flow. The default homepage of the application is assumed to be a route defined for main.index. You can override this by setting the config value INDEX_VIEW to a different route.

Decorators

priv_required

The priv_required decorator can be used to require a certain privilege code to access a route. You can set the regex parameter to True to do a regex match on the provided priv_code.

@bp.route('/somepage')
@priv_required(priv_code='itapps:webapps:itdept:somepage')
def somepage():
    return render_template('somepage.html')
@bp.route('/somepage')
@priv_required(priv_code='^itapps:webapps:itdept:some.*', regex=True)
def somepage():
    return render_template('somepage.html')

Configs

Default configurations are provided in mst.flasklib.config.DefaultConfig. To use these defaults in your application, create a config.py file like this example:

from mst.flasklib.config import DefaultConfig

class Config(DefaultConfig):
    """Contains app specific configuration
    """

    APP_TITLE = "My Flask Application"

    if DefaultConfig.LOCAL_ENV != "prod":
        _env = DefaultConfig.LOCAL_ENV.upper()
        APP_TITLE = f"{_env} - {APP_TITLE} - {_env}"

Then, in your create_app function:

from flask import Flask
from config import Config


def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)

    ...

    return app

Context Processor

mst-flasklib contains a basic context processor that defines a few variables and functions for you.

To override these values simply override them in your config file. The provided context processor uses the values in your config file as defaults.

You may also want to create a new context processor that sets other desired variables to different values. Note that overriding them via context processor does NOT update them in the config.

@app.context_processor
def custom_contact():
    return {
        'CONTACT_LABEL': 'IT Applications Infrastructure',
        'CONTACT_URL': 'mailto:it-ai@mst.edu'
    }

You can also pass individual variable overrides in when rendering an individual template.

# Passed individually
return render_template(
    'my_template.html',
    CONTACT_LABEL='IT Applications Infrastructure',
    CONTACT_URL='mailto:it-ai@mst.edu'
)

# Passed as dictionary
my_vars = {
    'CONTACT_LABEL': 'IT Applications Infrastructure',
    'CONTACT_URL': 'mailto:it-ai@mst.edu'
}
return render_template('my_template.html', **my_vars)

has_priv function

In the default context processor there is a has_priv function that allows you to check priv codes inside of a template.

Example:

<ul id="udm" class="udm">
    <li class='dropdown left_side'>
        <a href="{{url_for(config.INDEX_VIEW)}}"><span class='drop_caret'></span>Home</a>
        <ul>
            <li>
                <a href="{{url_for(config.INDEX_VIEW)}}">Main Application</a>
            </li>
            {% if has_priv("itapps:myapp:other_page") %}
            <li>
                <a href="{{url_for('main.other_page')}}">Some other page</a>
            </li>
            {% endif %}
        </ul>
    </li>
</ul>