-
Austin Wall authoredAustin Wall authored
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.
- 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/ \
- Modify your
Dockerfile
to run thefixperms
command both before and after thesetup-python.sh
script. - Copy the mst-flasklib repo folder to
./tmp/mst-flasklib
(this is only temporary) - Add
-e ./tmp/mst-flasklib/
to yourrequirements.txt
- Build the container
docker build -t <tag> .
- Delete the
tmp
folder - 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>