Source code for schedula.utils.form
# coding=utf-8
# -*- coding: UTF-8 -*-
#
# Copyright 2015-2025, Vincenzo Arcidiacono;
# Licensed under the EUPL (the 'Licence');
# You may not use this work except in compliance with the Licence.
# You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
"""
It provides functions to build a form flask app from a dispatcher.
Sub-Modules:
.. currentmodule:: schedula.utils.form
.. autosummary::
:nosignatures:
:toctree: form/
cli
config
gapp
json_secrets
server
"""
import functools
import io
import os
import gzip
import glob
import json
import mimetypes
import webbrowser
import os.path as osp
from ..web import WebMap
from . import json_secrets
from jinja2 import TemplateNotFound
from collections import OrderedDict
from werkzeug.exceptions import NotFound
try:
from smart_open import open as _open
from smart_open.compression import NO_COMPRESSION
_open = functools.partial(_open, compression=NO_COMPRESSION)
except ImportError:
_open = open
__author__ = 'Vincenzo Arcidiacono <vinci1it2000@gmail.com>'
static_dir = osp.join(osp.dirname(__file__), 'static')
STATIC_CONTEXT = {}
[docs]
def get_static_context():
if ~all(
f'main_{k}' in STATIC_CONTEXT and
osp.exists(STATIC_CONTEXT[f'main_{k}'])
for k in ('js', 'css')
):
STATIC_CONTEXT.clear()
static_context = {
f'main_{k}': osp.relpath(glob.glob(osp.join(
static_dir, 'schedula', k, f'main.*.{k}.gz'
))[0], osp.join(static_dir, 'schedula')).replace('\\', '/')
for k in ('js', 'css')
}
static_context = {
k: v[:-3] if v.endswith('.gz') else v for k, v in
static_context.items()
}
STATIC_CONTEXT.update(static_context)
return STATIC_CONTEXT
[docs]
def get_form_name(name):
from flask import current_app
for k in name.split('/')[:1]:
for sdir in (current_app.static_folder, static_dir):
sd = osp.join(sdir, 'schedula', 'forms')
for j in ('schema', 'ui'):
if osp.exists(osp.join(sd, f'{k}-{j}.json')):
return k
return 'index'
[docs]
def get_template(form, context, name=None):
from flask import render_template
if name is None:
name = get_form_name(form)
try:
template = f'schedula/{form}.html'
return render_template(template, name=name, form_id=name, **context)
except TemplateNotFound:
form = '/'.join(form.split('/')[:-1])
if form:
return get_template(form, context, name)
# noinspection PyUnresolvedReferences
return get_template('index', context, name=name)
[docs]
def send_static_file(
filename, static_folders, is_form=False):
from flask import current_app, request, send_file
filename = f'{filename}'.split('/')
download_name = filename[-1]
kw = {
'conditional': True,
'download_name': download_name,
'max_age': current_app.get_send_file_max_age(download_name)
}
gzipped = 'gzip' in request.headers.get('Accept-Encoding', '').lower()
if isinstance(static_folders, (list, tuple)):
static_folders = OrderedDict(((k, True) for k in static_folders))
for sdir, immutable in static_folders.items():
sdir = osp.join(sdir, *filename[:-1])
for ext in ('.gz', '')[::gzipped and 1 or -1]:
fn = f'{download_name}{ext}'
fp = osp.join(sdir, fn)
try:
with _open(fp, "rb") as f:
if is_form:
data = json_secrets.dumps(json.load(f)).encode()
else:
data = f.read()
if gzipped != bool(ext):
func = gzipped and gzip.compress or gzip.decompress
f = io.BytesIO(func(data))
fn = gzipped and f'{fn}.gz' or download_name
else:
f = io.BytesIO(data)
try:
kw['last_modified'] = os.stat(fp).st_mtime
except FileNotFoundError: # Remote file.
pass
mimetype, encoding = mimetypes.guess_type(fn)
response = send_file(f, **kw)
response.cache_control.immutable = immutable
response.cache_control.public = immutable
response.cache_control.pop('no_cache', None)
response.cache_control.pop('no-cache', None)
if immutable:
response.cache_control.max_age = 946080000 # 30 years.
else:
response.cache_control.must_revalidate = True
response.cache_control.max_age = 604800 # 1 week.
response.cache_control.stale_while_revalidate = 120 # 2 min.
response.content_type = mimetype
response.content_encoding = encoding
return response
except FileNotFoundError:
continue
raise NotFound
[docs]
class FormMap(WebMap):
[docs]
def get_form_context(self):
from .server import default_get_form_context
context = default_get_form_context().copy()
if hasattr(self, '_get_form_context'):
context.update(self._get_form_context())
return context
[docs]
def __init__(self):
super(FormMap, self).__init__()
self.url_prefix = os.environ.get('SCHEDULA_FORM_URL_PREFIX', '')
[docs]
def __getattr__(self, item):
if item.startswith('get_') and hasattr(self, f'_{item}'):
attr = getattr(self, f'_{item}')
if isinstance(attr, dict):
from flask import request
attr = attr.get(request.path, attr.get(
None, getattr(self.__class__, f'_{item}')
))
if hasattr(attr, '__call__'):
return attr
return lambda: attr
return super(FormMap, self).__getattr__(item)
[docs]
def render_form(self, form='index', ctx=None):
from flask import current_app
from flask_babel import get_locale
context = {
'form': self,
'app': current_app,
'get_locale': get_locale
}
context.update(get_static_context())
ref_dir = osp.join(current_app.static_folder, 'schedula', 'props')
for i in ('js', 'css'):
k = f'props_{i}'
for j in (form, 'index'):
if k in context:
continue
for fp in sorted(glob.glob(osp.join(ref_dir, i, f'{j}.*'))):
fp = osp.relpath(fp, ref_dir).replace("\\", " / ")
if fp.endswith('.gz'):
fp = fp[:-3]
context[k] = f'props/{fp}'
break
if ctx is not None:
context.update(ctx)
return get_template(form, context)
[docs]
@staticmethod
def send_static_file(filename):
from flask import current_app
return send_static_file(f'schedula/{filename}', OrderedDict((
(current_app.static_folder, False), (static_dir, True)
)), is_form=filename.startswith('forms'))
[docs]
def app(self, root_path=None, depth=1, mute=False, blueprint_name=None,
index=False, debug=False, **kwargs):
from flask import Blueprint
app = self.basic_app(
root_path, mute=mute, blueprint_name=blueprint_name, **kwargs
)
bp = Blueprint(
'schedula', __name__, template_folder='templates'
)
bp.add_url_rule('/', 'render_form', self.render_form)
bp.add_url_rule('/<string:form>', 'render_form')
bp.add_url_rule('/<string:form>/', 'render_form')
bp.add_url_rule('/<path:form>', 'render_form')
bp.add_url_rule(
'/static/schedula/<path:filename>', 'static', self.send_static_file
)
bp.add_url_rule('/static/schedula/<string:filename>', 'static')
bp.register_blueprint(self.api(depth, debug))
app.register_blueprint(bp)
return app
[docs]
def basic_app(self, root_path, mute=True, blueprint_name=None, **kwargs):
app = super(FormMap, self).basic_app(
root_path, mute=mute, blueprint_name=blueprint_name, **kwargs
)
if blueprint_name is None:
from .server import basic_app
app = basic_app(self, app)
return app