moin-2.0 / MoinMoin /

Full commit
# Copyright: 2000-2006 by Juergen Hermann <>
# Copyright: 2002-2011 MoinMoin:ThomasWaldmann
# Copyright: 2008 MoinMoin:FlorianKrupicka
# Copyright: 2010 MoinMoin:DiogenesAugusto
# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.

MoinMoin - wsgi application setup and related code

Use create_app(config) to create the WSGI application (using Flask).

from __future__ import absolute_import, division

import os

# do this early, but not in MoinMoin/ because we need to be able to
# "import MoinMoin" from even before flask, werkzeug, ... is installed.
from MoinMoin.util import monkeypatch

from flask import Flask, request, session
from flask import current_app as app
from flask import g as flaskg

from flaskext.cache import Cache
from flaskext.themes import setup_themes

from werkzeug.exceptions import HTTPException

from jinja2 import ChoiceLoader, FileSystemLoader

from MoinMoin import log
logging = log.getLogger(__name__)

from MoinMoin.i18n import i18n_init
from MoinMoin.i18n import _, L_, N_

from MoinMoin.themes import setup_jinja_env, themed_error

from MoinMoin.util.clock import Clock

def create_app(config=None):
    simple wrapper around create_app_ext() for flask-script
    return create_app_ext(flask_config_file=config)

def create_app_ext(flask_config_file=None, flask_config_dict=None,
                   moin_config_class=None, warn_default=True, **kwargs
    Factory for moin wsgi apps

    :param flask_config_file: a flask config file name (may have a MOINCFG class),
                              if not given, a config pointed to by MOINCFG env var
                              will be loaded (if possible).
    :param flask_config_dict: a dict used to update flask config (applied after
                              flask_config_file was loaded [if given])
    :param moin_config_class: if you give this, it'll be instantiated as app.cfg,
                              otherwise it'll use MOINCFG from flask config. If that
                              also is not there, it'll use the DefaultConfig built
                              into MoinMoin.
    :param warn_default: emit a warning if moin falls back to its builtin default
                         config (maybe user forgot to specify MOINCFG?)
    :param kwargs: if you give additional keyword args, the keys/values will get patched
                   into the moin configuration class (before its instance is created)
    clock = Clock()
    clock.start('create_app total')
    app = Flask('MoinMoin')
    clock.start('create_app load config')
    if flask_config_file:
        if not app.config.from_envvar('MOINCFG', silent=True):
            # no MOINCFG env variable set, try stuff in cwd:
            from os import path
            flask_config_file = path.abspath('')
            if not path.exists(flask_config_file):
                flask_config_file = path.abspath('')
                if not path.exists(flask_config_file):
                    flask_config_file = None
            if flask_config_file:
    if flask_config_dict:
    Config = moin_config_class
    if not Config:
        Config = app.config.get('MOINCFG')
    if not Config:
        if warn_default:
            logging.warning("using builtin default configuration")
        from MoinMoin.config.default import DefaultConfig as Config
    for key, value in kwargs.iteritems():
        setattr(Config, key, value)
    if Config.secrets is None:
        # reuse the secret configured for flask (which is required for sessions)
        Config.secrets = app.config.get('SECRET_KEY')
    app.cfg = Config()
    clock.stop('create_app load config')
    clock.start('create_app register')
    # register converters
    from werkzeug.routing import PathConverter
    app.url_map.converters['itemname'] = PathConverter
    # register modules, before/after request functions
    from MoinMoin.apps.frontend import frontend
    from MoinMoin.apps.admin import admin
    app.register_blueprint(admin, url_prefix='/+admin')
    from MoinMoin.apps.feed import feed
    app.register_blueprint(feed, url_prefix='/+feed')
    from MoinMoin.apps.misc import misc
    app.register_blueprint(misc, url_prefix='/+misc')
    from MoinMoin.apps.serve import serve
    app.register_blueprint(serve, url_prefix='/+serve')
    clock.stop('create_app register')
    clock.start('create_app flask-cache')
    cache = Cache()
    app.cache = cache
    clock.stop('create_app flask-cache')
    # init storage
    clock.start('create_app init backends')
    app.unprotected_storage, = init_backends(app)
    clock.stop('create_app init backends')
    clock.start('create_app load/save xml')
    clock.stop('create_app load/save xml')
    clock.start('create_app flask-babel')
    clock.stop('create_app flask-babel')
    # configure templates
    clock.start('create_app flask-themes')
    if app.cfg.template_dirs:
        app.jinja_env.loader = ChoiceLoader([
    app.register_error_handler(403, themed_error)
    clock.stop('create_app flask-themes')
    clock.stop('create_app total')
    del clock
    return app

def destroy_app(app):

from import StorageError
from import serialize, unserialize
from import router, acl, memory
from MoinMoin import auth, config, user

def init_backends(app):
    initialize the backends
    # A ns_mapping consists of several lines, where each line is made up like this:
    # mountpoint, unprotected backend, protection to apply as a dict
    ns_mapping = app.cfg.namespace_mapping
    # Just initialize with unprotected backends.
    unprotected_mapping = [(ns, backend) for ns, backend, acls in ns_mapping]
    unprotected_storage = router.RouterBackend(unprotected_mapping, cfg=app.cfg)
    # Protect each backend with the acls provided for it in the mapping at position 2
    amw = acl.AclWrapperBackend
    protected_mapping = [(ns, amw(app.cfg, backend, **acls)) for ns, backend, acls in ns_mapping]
    storage = router.RouterBackend(protected_mapping, cfg=app.cfg)
    return unprotected_storage, storage

def deinit_backends(app):

def import_export_xml(app):
    # If the content was already pumped into the backend, we don't want
    # to do that again. (Works only until the server is restarted.)
    xmlfile = app.cfg.load_xml
    if xmlfile:
        app.cfg.load_xml = None
        tmp_backend = router.RouterBackend([('/', memory.MemoryBackend())], cfg=app.cfg)
        unserialize(tmp_backend, xmlfile)
        # TODO optimize this, maybe unserialize could count items it processed
        item_count = 0
        for item in tmp_backend.iteritems():
            item_count += 1
        logging.debug("loaded xml into tmp_backend: %s, %d items" % (xmlfile, item_count))
            # In case the server was restarted we cannot know whether
            # the xml data already exists in the target backend.
            # Hence we check the existence of the items before we unserialize
            # them to the backend.
            backend = app.unprotected_storage
            for item in tmp_backend.iteritems():
                item = backend.get_item(
        except StorageError:
            # if there is some exception, we assume that backend needs to be filled
            # we need to use it as unserialization target so that update mode of
            # unserialization creates the correct item revisions
            logging.debug("unserialize xml file %s into %r" % (xmlfile, backend))
            unserialize(backend, xmlfile)
        item_count = 0

    # XXX wrong place / name - this is a generic preload functionality, not just for tests
    # To make some tests happy
    app.cfg.test_num_pages = item_count

    xmlfile = app.cfg.save_xml
    if xmlfile:
        app.cfg.save_xml = None
        backend = app.unprotected_storage
        serialize(backend, xmlfile)

def setup_user():
    Try to retrieve a valid user object from the request, be it
    either through the session or through a login.
    # init some stuff for auth processing:
    flaskg._login_multistage = None
    flaskg._login_multistage_name = None
    flaskg._login_messages = []

    # first try setting up from session
    userobj = auth.setup_from_session()

    # then handle login/logout forms
    form = request.values.to_dict()
    if 'login_submit' in form:
        # this is a real form, submitted by POST
        userobj = auth.handle_login(userobj, **form)
    elif 'logout_submit' in form:
        # currently just a GET link
        userobj = auth.handle_logout(userobj)
        userobj = auth.handle_request(userobj)

    # if we still have no user obj, create a dummy:
    if not userobj:
        userobj = user.User(auth_method='invalid')
    # if we have a valid user we store it in the session
    if userobj.valid:
        # TODO: auth_trusted should be set by the auth method (auth class
        # could have a param where the admin could tell whether he wants to
        # trust it)
        userobj.auth_trusted = userobj.auth_method in app.cfg.auth_methods_trusted
        session[''] =
        session['user.auth_method'] = userobj.auth_method
        session['user.auth_attribs'] = userobj.auth_attribs
    return userobj

def before_wiki():
    Setup environment for wiki requests, start timers.
    logging.debug("running before_wiki")
    flaskg.clock = Clock()
        flaskg.unprotected_storage = app.unprotected_storage

        flaskg.user = setup_user()

        flaskg.dicts = app.cfg.dicts()
        flaskg.groups = app.cfg.groups()

        flaskg.content_lang = app.cfg.language_default
        flaskg.current_lang = app.cfg.language_default =


    # if return value is not None, it is the final response

def teardown_wiki(response):
    Teardown environment of wiki requests, stop timers.
    logging.debug("running teardown_wiki")
        del flaskg.clock
    except AttributeError:
        # can happen if teardown_wiki() is called twice, e.g. by unit tests.
    return response