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 _get_form_data(self): return
[docs] @staticmethod def _view(url, *args, **kwargs): webbrowser.open(url)
[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