view MoinMoin/web/ @ 4206:7c83580dba77

Added lazy theme property and missing output-method
author Florian Krupicka <>
date Wed, 09 Jul 2008 16:12:20 +0200
parents c21991eed9c9
children dde44d6e24ae
line wrap: on
line source

# -*- coding: iso-8859-1 -*-
    MoinMoin - Context objects which are passed thru instead of the classic
               request objects. Currently contains legacy wrapper code for
               a single request object.

    @copyright: 2008-2008 MoinMoin:FlorianKrupicka
    @license: GNU GPL, see COPYING for details.

import time, inspect, StringIO

from werkzeug.utils import Headers, http_date
from werkzeug.exceptions import Unauthorized, NotFound

from MoinMoin import i18n, error, user
from MoinMoin.config import multiconfig
from MoinMoin.formatter import text_html
from MoinMoin.theme import load_theme_fallback
from MoinMoin.util.clock import Clock
from MoinMoin.web.request import Request
from MoinMoin.web.utils import check_spider, UniqueIDGenerator
from MoinMoin.web.exceptions import Forbidden, SurgeProtection
from MoinMoin.web.api import IContext

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

class EnvironProxy(property):
    """ Proxy attribute lookups to keys in the environ. """
    def __init__(self, name, factory=default):
        An entry will be proxied to the supplied name in the .environ
        object of the property holder. A factory can be supplied, for
        values that need to be preinstantiated. If given as first
        parameter name is taken from the callable too.

        @param name: key (or factory for convenience)
        @param factory: literal object or callable
        if not isinstance(name, basestring):
            factory = name
            name = factory.__name__ = name
        self.full_name = 'moin.%s' % name
        self.factory = factory
        property.__init__(self, self.get, self.set, self.delete)

    def get(self, obj):
        logging.debug("GET: '%s' on '%r'",, obj)
        if self.full_name in obj.environ:
            res = obj.environ[self.full_name]
            factory = self.factory
            if factory is default:
                raise AttributeError(
            elif hasattr(factory, '__call__'):
                res = obj.environ.setdefault(self.full_name, factory(obj))
                res = obj.environ.setdefault(self.full_name, factory)
        return res

    def set(self, obj, value):
        logging.debug("SET: '%s' on '%r' to '%r'",, obj, value)
        obj.environ[self.full_name] = value

    def delete(self, obj):
        logging.debug("DEL: '%s' on '%r'",, obj)
        del obj.environ[self.full_name]

    def __repr__(self):
        return "<%s for '%s'>" % (self.__class__.__name__,

class Context(object):
    """ Standard implementation for the context interface.

    This one wraps up a Moin-Request object and the associated
    environ and also keeps track of it's changes.
    __slots__ = ['request', 'environ']
    __implements__ = (IContext, )

    def __init__(self, request):
        assert isinstance(request, Request)
        self.request = request
        self.environ = request.environ

    personalities = EnvironProxy('context.personalities', lambda o: list())

    def become(self, cls):
        """ Become another context, based on given class.

        @param cls: class to change to, must be a sister class
        @rtype: boolean
        @return: wether a class change took place
        if self.__class__ is cls:
            return False
            self.__class__ = cls
            return True

class UserMixin(object):
    """ Mixin for user attributes and methods. """
    def user(self):
        return user.User(self, auth_method='request:invalid')
    user = EnvironProxy(user)

class LanguageMixin(object):
    """ Mixin for language attributes and methods. """
    def lang(self):
        for key in ('moin.user.lang', 'moin.request.lang'):
            if key in self.environ:
                return self.environ[key]

        if i18n.languages is None:
        lang = None

        user = getattr(self, 'user')
        if user and user.valid and user.language:
            lang = user.language
            self.environ['moin.user.lang'] = lang
            if i18n.languages and not self.cfg.language_ignore_browser:
                for l in self.request.accept_languages:
                    if l in i18n.languages:
                        lang = l

            if lang is None and self.cfg.language_default in i18n.languages:
                lang = self.cfg.language_default
                lang = 'en'
            self.environ['moin.request.lang'] = lang
        return lang
    lang = property(lang)

    def getText(self):
        lang = self.lang
        def _(text, i18n=i18n, request=self, lang=lang, **kw):
            return i18n.getText(text, request, lang, **kw)
        return _
    getText = EnvironProxy(getText)

    def content_lang(self):
        return self.cfg.language_default
    content_lang = EnvironProxy(content_lang)
    current_lang = EnvironProxy('current_lang')

    def setContentLanguage(self, lang):
        """ Set the content language, used for the content div

        Actions that generate content in the user language, like search,
        should set the content direction to the user language before they
        call send_title!
        self.content_lang = lang
        self.current_lang = lang

class HTTPMixin(object):
    """ Mixin for HTTP attributes and methods. """
    forbidden = EnvironProxy('old.forbidden', 0)
    session = EnvironProxy('session')

    _auth_redirected = EnvironProxy('old._auth_redirected', 0)
    _cache_disabled = EnvironProxy('old._cache_disabled', 0)
    cacheable = EnvironProxy('old.cacheable', 0)

    def write(self, *data):
        if len(data) > 1:
            logging.warning("Some code still uses write with multiple arguments, "
                            "consider changing this soon")

    # implementation of methods expected by RequestBase
    def send_file(self, fileobj, bufsize=8192, do_flush=None):

    def read(self, n=None):
        if n is None:
            return self.request.in_data

    def makeForbidden(self, resultcode, msg):
        status = {401: Unauthorized,
                  403: Forbidden,
                  404: NotFound,
                  503: SurgeProtection}
        raise status[resultcode](msg)

    def setHttpHeader(self, header):
        header, value = header.split(':', 1)
        self.headers.add(header, value)

    def disableHttpCaching(self, level=1):
        if level <= self._cache_disabled:

        if level == 1:
            self.headers.add('Cache-Control', 'private, must-revalidate, mag-age=10')
        elif level == 2:
            self.headers.add('Cache-Control', 'no-cache')
            self.headers.set('Pragma', 'no-cache')

        if not self._cache_disabled:
            when = time.time() - (3600 * 24 * 365)
            self.headers.set('Expires', http_date(when))

        self._cache_disabled = level

    def isSpiderAgent(self):
        return check_spider(self.request.user_agent, self.cfg)
    isSpiderAgent = EnvironProxy(isSpiderAgent)

class ActionMixin(object):
    """ Mixin for the action related attributes. """
    def action(self):
        return self.request.values.get('action', 'show')
    action = EnvironProxy(action)

    def rev(self):
            return int(self.values['rev'])
            return None
    rev = EnvironProxy(rev)

class ConfigMixin(object):
    """ Mixin for the everneeded config object. """
    def cfg(self):
            cfg = multiconfig.getConfig(self.request.url)
            return cfg
        except error.NoConfigMatchedError:
            raise NotFound('<p>No wiki configuration matching the URL found!</p>')
    cfg = EnvironProxy(cfg)

class FormatterMixin(object):
    """ Mixin for the standard formatter attributes. """
    def html_formatter(self):
        return text_html.Formatter(self)
    html_formatter = EnvironProxy(html_formatter)

    def formatter(self):
        return self.html_formatter
    formatter = EnvironProxy(formatter)

class PageMixin(object):
    """ Mixin for ondemand rootpage. """
    page = EnvironProxy('page', None)
    def rootpage(self):
        from MoinMoin.Page import RootPage
        return RootPage(self)
    rootpage = EnvironProxy(rootpage)

class AuxilaryMixin(object):
    Mixin for diverse attributes and methods that aren't clearly assignable
    to a particular phase of the request.
    _fmt_hd_counters = EnvironProxy('_fmt_hd_counters')
    parsePageLinks_running = EnvironProxy('parsePageLinks_running', lambda o: {})
    mode_getpagelinks = EnvironProxy('mode_getpagelinks', 0)
    clock = EnvironProxy('clock', lambda o: Clock())
    pragma = EnvironProxy('pragma', lambda o: {})
    _login_messages = EnvironProxy('_login_messages', lambda o: [])
    _login_multistage = EnvironProxy('_login_multistage', None)
    _setuid_real_user = EnvironProxy('_setuid_real_user', None)
    pages = EnvironProxy('pages', lambda o: {})

    def uid_generator(self):
        pagename = None
        if hasattr(self, 'page') and hasattr(, 'page_name'):
            pagename =
        return UniqueIDGenerator(pagename=pagename)
    uid_generator = EnvironProxy(uid_generator)

    def dicts(self):
        """ Lazy initialize the dicts on the first access """
        from MoinMoin import wikidicts
        dicts = wikidicts.GroupDict(self)
        return dicts
    dicts = EnvironProxy(dicts)

    def reset(self):
        self.current_lang = self.cfg.language_default
        if hasattr(self, '_fmt_hd_counters'):
            del self._fmt_hd_counters
        if hasattr(self, 'uid_generator'):
            del self.uid_generator

    def getPragma(self, key, defval=None):
        """ Query a pragma value (#pragma processing instruction)

            Keys are not case-sensitive.
        return self.pragma.get(key.lower(), defval)

    def setPragma(self, key, value):
        """ Set a pragma value (#pragma processing instruction)

            Keys are not case-sensitive.
        self.pragma[key.lower()] = value

class ThemeMixin(object):
    """ Mixin for the theme attributes and methods. """
    def _theme(self):
        return self.theme
    theme = EnvironProxy('theme', _theme)

    def initTheme(self):
        """ Set theme - forced theme, user theme or wiki default """
        if self.cfg.theme_force:
            theme_name = self.cfg.theme_default
            theme_name = self.user.theme_name
        load_theme_fallback(self, theme_name)

class RedirectMixin(object):
    """ Mixin to redirect output into buffers instead to the client. """
    writestack = EnvironProxy('old.writestack', lambda o: list())

    def redirectedOutput(self, function, *args, **kw):
        """ Redirect output during function, return redirected output """
        buf = StringIO.StringIO()
            function(*args, **kw)
        text = buf.getvalue()
        return text

    def redirect(self, file=None):
        """ Redirect output to file, or restore saved output """
        if file:
            self.write = file.write
            self.write = self.writestack.pop()

class HTTPContext(Context, HTTPMixin, ConfigMixin, UserMixin,
                  LanguageMixin, AuxilaryMixin):
    """ Context to act mainly in HTTP handling related phases. """
    def __getattr__(self, name):
            return getattr(self.request, name)
        except AttributeError, e:
            return super(HTTPContext, self).__getattribute__(name)

class RenderContext(Context, RedirectMixin, ConfigMixin, UserMixin,
                    LanguageMixin, ThemeMixin, AuxilaryMixin,
                    ActionMixin, PageMixin, FormatterMixin):
    """ Context to act during the rendering phase. """
    def write(self, *data):
        if len(data) > 1:
            logging.warning("Some code still uses write with multiple arguments, "
                            "consider changing this soon")

    def output(self):
        return self.request()

# TODO: extend xmlrpc context
class XMLRPCContext(HTTPContext):
    """ Context to act during a XMLRPC request. """

class AllContext(HTTPContext, RenderContext):
    """ Catchall context to be able to quickly test old Moin code. """