view MoinMoin/web/contexts.py @ 4184:b4acdce23cfa

Group some functionality
author Florian Krupicka <florian.krupicka@googlemail.com>
date Sat, 21 Jun 2008 20:01:54 +0200
parents ca0cf44dab89
children 6246e8f813b7
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
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 import log
logging = log.getLogger(__name__)
default = object()

class renamed_property(property):
    def __init__(self, name):
        property.__init__(self, lambda obj: getattr(obj.request, name))

class EnvironProxy(property):
    def __init__(self, name, factory=default):
        if not isinstance(name, basestring):
            factory = name
            name = factory.__name__
        self.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'", self.name, obj)
        if self.full_name in obj.environ:
            res = obj.environ[self.full_name]
        else:
            factory = self.factory
            if factory is default:
                raise AttributeError(self.name)
            elif hasattr(factory, '__call__'):
                res = obj.environ.setdefault(self.full_name, factory(obj))
            else:
                res = obj.environ.setdefault(self.full_name, factory)
        return res
    
    def set(self, obj, value):
        logging.debug("SET: '%s' on '%r' to '%r'", self.name, obj, value)
        obj.environ[self.full_name] = value

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

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

class Context(object):
    __slots__ = ['request', 'environ']

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

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

    def become(self, cls):
        if self.__class__ is cls:
            return False
        else:
            self.personalities.append(cls)
            self.__class__ = cls
            return True

class UserMixin(object):
    user = EnvironProxy('user')

class LanguageMixin(object):
    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:
            i18n.i18n_init(self)
        lang = None

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

            if lang is None and self.cfg.language_default in i18n.languages:
                lang = self.cfg.language_default
            else:
                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):
    forbidden = EnvironProxy('old.forbidden', 0)
    
    _auth_redirected = EnvironProxy('old._auth_redirected', 0)
    _cache_disabled = EnvironProxy('old._cache_disabled', 0)
        
    def write(self, *data):
        if len(data) > 1:
            logging.warning("Some code still uses write with multiple arguments, "
                            "consider changing this soon")
        self.request.stream.writelines(data)
    
    # implementation of methods expected by RequestBase
    def send_file(self, fileobj, bufsize=8192, do_flush=None):
        pass

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

    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:
            return
        
        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):
    def action(self):
        return self.request.values.get('action','show')
    action = EnvironProxy(action)

class RevisionMixin(object):
    def rev(self):
        try:
            return int(self.values['rev'])
        except:
            return None
    rev = EnvironProxy(rev)

class ConfigMixin(object):
    def cfg(self):
        try:
            self.clock.start('load_multi_cfg')
            cfg = multiconfig.getConfig(self.request.url)
            self.clock.stop('load_multi_cfg')
            return cfg
        except error.NoConfigMatchedError:
            raise NotFound('<p>No wiki configuration matching the URL found!</p>')
    cfg = EnvironProxy(cfg)

class RenamedMixin(object):
    cookie = renamed_property('cookies')
    script_name = renamed_property('script_root')
    path_info = renamed_property('path')
    is_ssl = renamed_property('is_secure')
    request_method = renamed_property('method')

class FormatterMixin(object):
    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):
    page = EnvironProxy('page', None)
    def rootpage(self):
        from MoinMoin.Page import RootPage
        return RootPage(self)
    rootpage = EnvironProxy(rootpage)

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

class AuxilaryMixin(object):
    _fmt_hd_counters = EnvironProxy('_fmt_hd_counters')

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

    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

class ThemeMixin(object):
    theme = EnvironProxy('theme')

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

class PragmaMixin(object):
    pragma = EnvironProxy('pragma', lambda o: dict())

    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 RedirectMixin(object):
    writestack = EnvironProxy('old.writestack', lambda o: list())

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

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

class ClockMixin(object):
    clock = EnvironProxy('clock', lambda o: Clock())

class _AuxilaryContext(Context, ConfigMixin, UserMixin,
                      ClockMixin, LanguageMixin):
    pass

class HTTPContext(_AuxilaryContext, HTTPMixin):
    def __getattribute__(self, name):
         try:
             return super(HTTPContext, self).__getattribute__(name)
         except AttributeError:
             try:
                 return getattr(self.request, name)
             except AttributeError:
                 msg = "'%s' object has no attribute '%s'"
                 msg = msg % (self.__class__.__name__,
                              name)
                 raise AttributeError(msg)

class RenderContext(_AuxilaryContext, RedirectMixin, PragmaMixin, ThemeMixin,
                    AuxilaryMixin, DictsMixin, ActionMixin, PageMixin,
                    RevisionMixin, FormatterMixin):
    def write(self, *data):
        if len(data) > 1:
            logging.warning("Some code still uses write with multiple arguments, "
                            "consider changing this soon")
        self.request.stream.writelines(data)

class XMLRPCContext(HTTPContext):
    pass

class AllContext(HTTPContext, RenderContext):
    pass