Skip to content
Snippets Groups Projects
Select Git revision
  • 4c009fe51edbbab0ea068a697db54a25d76a5db4
  • master default protected
2 results

gen_plotly.py

Blame
  • __init__.py 7.09 KiB
    from flask import Flask, request, g, session
    import flask.logging
    import flask.wrappers
    from flask_bootstrap import Bootstrap
    from flask_sqlalchemy import SQLAlchemy
    import locale
    import logging
    import os
    import tempfile
    from typing import Optional
    import werkzeug.exceptions
    import werkzeug.formparser
    
    import mo
    import mo.config as config
    import mo.db as db
    import mo.ext.assets
    import mo.jobs
    import mo.rights
    import mo.users
    import mo.util
    
    
    # Ohýbáme Flask, aby uploadované soubory ukládal do adresáře podle našeho přání,
    # aby se pak daly zařadit mezi datové soubory prostým hardlinkováním. Za tímto účelem
    # subclassujeme Request, aby použil subclassovaný FormDataParser, který použije naši
    # stream factory místo defaultní.
    
    def mo_stream_factory(total_content_length, filename, content_type, content_length=None):
        return tempfile.NamedTemporaryFile(dir=mo.util.data_dir('tmp'), prefix='upload-')
    
    
    class FormDataParser(werkzeug.formparser.FormDataParser):
    
        def __init__(self,
                     stream_factory=None,
                     charset='utf-8',
                     errors='replace',
                     max_form_memory_size=None,
                     max_content_length=None,
                     cls=None,
                     silent=True):
            super().__init__(mo_stream_factory, charset, errors, max_form_memory_size, max_content_length, cls, silent)
    
    
    class Request(flask.wrappers.Request):
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.form_data_parser_class = FormDataParser
    
        # Můžeme zvýšit maximální velikost souboru pro dávkové uploady
        custom_max_content_length: Optional[int] = None
    
        # Původně atribut ve werkzeug.BaseRequest, předefinován na property ve flask.Request
        @property
        def max_content_length(self):
            return self.custom_max_content_length or mo.config.MAX_CONTENT_LENGTH
    
    
    # Flask interpretuje relativní cesty všelijak, tak mu vyrobíme absolutní
    mo.config.DATA_DIR = os.path.abspath(mo.config.DATA_DIR)
    static_dir = os.path.abspath('static')
    
    # Aplikační objekt
    app = Flask(__name__, static_folder=static_dir)
    app.config.from_object(config)
    app.request_class = Request
    db.flask_db = SQLAlchemy(app, metadata=db.metadata)
    
    Bootstrap(app)  # make bootstrap libs accessible for the app
    
    # Budeme používat české locale
    locale.setlocale(locale.LC_COLLATE, 'cs_CZ.UTF-8')
    
    
    # Incializace logování
    
    def setup_logging():
        # Logování v Pythonu je vymyšlené hezky, ale bohužel spousta modulů má tendenci
        # konfigurovat si u svých loggerů náhodné handlery. Pokud chceme, aby bylo logování
        # aspoň trochu jednotné, musíme jim to trochu předrátovat.
    
        # Nastavíme root logger, aby logoval po našem do flaskového logu
        log_handler = flask.logging.default_handler
        log_formatter = logging.Formatter(fmt='%(asctime)-15s.%(msecs)03d %(levelname)-5.5s [%(process)s] (%(name)s) %(message)s',
                                          datefmt='%Y-%m-%d %H:%M:%S')
        log_handler.setFormatter(log_formatter)
        root_logger = logging.getLogger()
        root_logger.addHandler(log_handler)
    
        # app.logger se jmenuje "mo.web" a propaguje se do root loggeru,
        # takže nechceme, aby měl svůj handler.
        app.logger.removeHandler(log_handler)
        app.logger.setLevel(logging.DEBUG)
    
        # sqlalchemy má logger, ke kterému si při prvním vypsání hlášky přidá handler,
        # není-li tam zatím žádný. Tak přidáme dummy handler. Vše se propaguje do root loggeru.
        sqla_logger = logging.getLogger('sqlalchemy.engine.base.Engine')
        sqla_logger.addHandler(logging.NullHandler())
    
        # werkzeug má vlastní handler s vlastním formátem hlášek, který už obsahuje timestamp,
        # takže vypneme propagování
        wz_logger = logging.getLogger('werkzeug')
        wz_logger.propagate = False
    
    
    setup_logging()
    
    
    # Incializace assetového mechanismu
    mo.ext.assets.Assets(app, url_prefix='/assets', asset_dir=static_dir)
    
    app.assets.add_assets([
        'bootstrap.min.css',
        'mo.css',
        'js/news-reloader.js',
        'js/osmo.js',
    ])
    
    
    # Inicializace požadavků a nucená autorizace
    
    class NeedLoginError(werkzeug.exceptions.Forbidden):
        description = 'Need to log in'
    
    
    def need_login():
        if not g.user:
            raise NeedLoginError()
    
    
    def init_request():
        path = request.path
        # XXX: Když celá aplikace běží v adresáři, request.path je relativní ke kořeni aplikace, ne celého webu
        if path.startswith('/static/') or path.startswith('/assets/'):
            # Pro statické soubory v development nasazení nepotřebujeme nastavovat
            # nic dalšího (v ostrém nasazení je servíruje uwsgi)
            return
    
        if 'uid' in session:
            user = mo.users.user_by_uid(session['uid'])
            if not user:
                # Uživatel mezitím přestal existovat
                app.logger.error('Zrušena session pro neexistujícího uživatele uid=%s', session['uid'])
                return mo.web.acct.logout()
        else:
            user = None
    
        g.user = user
        if have_uwsgi:
            uwsgi.set_logvar('osmo_uid', str(user.user_id) if user else '-')
    
        mo.now = mo.util.get_now()
        g.now = mo.now  # for templates
        mo.util.current_log_user = user
    
        # K některým podstromům je nutné mít speciální oprávnění
        if path.startswith('/org/'):
            if not user:
                raise NeedLoginError()
            if not (user.is_org or user.is_admin):
                raise werkzeug.exceptions.Forbidden()
            g.gatekeeper = mo.rights.Gatekeeper(user)
        elif path.startswith('/user/'):
            if not user:
                raise NeedLoginError()
    
    
    app.before_request(init_request)
    
    
    ### UWSGI glue ###
    
    # Čas od času se probudíme a spustíme garbage collector:
    #    - projdeme joby pro případ, že by se ztratil signál
    #    - expirujeme zastaralé joby
    #    - expirujeme zastaralé registrační tokeny
    @app.cli.command('gc')
    def gc():
        """Run garbage collector."""
    
        mo.now = mo.util.get_now()
        mo.jobs.process_jobs()
        mo.users.expire_reg_requests()
    
    
    try:
        import uwsgi
        from uwsgidecorators import timer, signal
    
        @timer(config.GC_PERIOD, target='mule')
        def mule_timer(signum):
            # app.logger.debug('Mule: Timer tick')
            with app.app_context():
                garbage_collect()
    
        # Obykle při vložení jobu dostaneme signál.
        @signal(42, target='mule')
        def mule_signal(signum):
            app.logger.debug('Mule: Přijat signál')
            with app.app_context():
                mo.now = mo.util.get_now()
                mo.jobs.process_jobs()
    
        def wake_up_mule():
            app.logger.debug('Mule: Posíláme signál')
            uwsgi.signal(42)
    
        have_uwsgi = True
        mo.jobs.send_notify = wake_up_mule
    
    except ImportError:
        app.logger.warn('Nenalezeno UWSGI, takže automatické notifikace nepoběží.')
        have_uwsgi = False
    
    
    # Většina webu je v samostatných modulech
    import mo.web.api
    import mo.web.acct
    import mo.web.doc
    import mo.web.jinja
    import mo.web.menu
    import mo.web.misc
    import mo.web.org
    import mo.web.org_contest
    import mo.web.org_jobs
    import mo.web.org_log
    import mo.web.org_place
    import mo.web.org_round
    import mo.web.org_score
    import mo.web.org_users
    import mo.web.user