preferences.py
author Tero Marttila <terom@fixme.fi>
Wed, 11 Feb 2009 03:46:59 +0200
changeset 99 8719ac564b22
parent 79 43ac75054d5c
child 129 67a30d680f60
permissions -rw-r--r--
implement non-blocking locking for the estdb, and our own locking for the autoload statetmpfile... it should work well now
"""
    Handling user preferences
"""

import functools
import Cookie

from qmsk.web import urltree

class Preference (urltree.URLType) :
    """
        A specific preference
    """

    # the name to use
    name = None

    # the default value
    default = None
    
    def process (self, preferences, value) :
        """
            Post-process this preference value. This can access the values of other preferences or the request via
            the given RequestPreferences. Note that the other items will not be post-processed here.

            Defaults to just return value.
        """

        return value

class RequestPreferences (object) :
    """
        Represents the specific preferences for some request
    """

    def __init__ (self, preferences, request, value_map=None) :
        """
            Initialize with the given Preferences object, http Request, and list of mapping of raw preference values
        """
        
        # store
        self.preferences = preferences
        self.request = request

        # initialize
        self.values = {}
        self.set_cookies = {}

        # load preferences
        for pref in preferences.pref_list :
            # got a value for it?
            if value_map and pref.name in value_map :
                # get value
                value = value_map[pref.name]

                # parse it
                value = pref.parse(value)

            else :
                # use default value
                value = pref.default
            
            # add
            self.values[pref.name] = value
        
        # then post-process using the Preferences
        self.values = dict(
            (name, self.preferences.pref_map[name].process(self, value)) for name, value in self.values.iteritems()
        )
    
    def get (self, pref) :
        """
            Return the value for the given Preference, or preference name
        """
        
        # Preference -> name
        if isinstance(pref, Preference) :
            pref = pref.name
        
        # look up
        return self.values[pref]

    # support dict-access
    __getitem__ = get

    def set (self, name, value_obj) :
        """
            Set a new value for the given preference
        """

        # sanity-check to make sure we're not setting it twice...
        assert name not in self.set_cookies

        # encode using the Preference object
        value_str = self.preferences.pref_map[name].build(value_obj)

        # update in our dict
        self.values[name] = value_obj

        # add to set_cookies
        self.set_cookies[name] = value_str

class Preferences (object) :
    """
        Handle user preferences using cookies
    """

    def __init__ (self, pref_list) :
        """
            Use the given list of Preference objects
        """

        # store
        self.pref_list = pref_list

        # translate to mapping as well
        self.pref_map = dict((pref.name, pref) for pref in pref_list)

    def load (self, request, ) :
        """
            Load the set of preferences for the given request, and return as a { name -> value } dict
        """

        # the dict of values
        values = {}

        # load the cookies
        cookie_data = request.env.get('HTTP_COOKIE')

        # got any?
        if cookie_data :
            # parse into a SimpleCookie
            cookies = Cookie.SimpleCookie(cookie_data)

            # update the the values
            values.update((morsel.key, morsel.value) for morsel in cookies.itervalues())
        
        else :
            cookies = None

        # apply any query parameters
        for pref in self.pref_list :
            # look for a query param
            value = request.get_arg(pref.name)

            if value :
                # override
                values[pref.name] = value

        # build the RequestPreferences object
        return cookies, RequestPreferences(self, request, values)

    def handler (self, *pref_list) :
        """
            Intended to be used as a decorator for a request handler, this will load the give Preferences and pass
            them to the wrapped handler as keyword arguments, in addition to any others given.
        """

        def _decorator (func) :
            @functools.wraps(func)
            def _handler (request, **args) :
                # load preferences
                cookies, prefs = self.load(request)

                # bind to request.prefs
                # XXX: better way to do this? :/
                request.prefs = prefs

                # update args with new ones
                args.update(((pref.name, prefs.get(pref)) for pref in pref_list))

                # handle to get response
                response = func(request, **args)

                # set cookies?
                if prefs.set_cookies :
                    if not cookies :
                        cookies = Cookie.SimpleCookie('')

                    # update cookies
                    for key, value in prefs.set_cookies.iteritems() :
                        cookies[key] = value

                    # add headers
                    for morsel in cookies.itervalues() :
                        response.add_header('Set-cookie', morsel.OutputString())

                return response
            
            # return wrapped handler
            return _handler
        
        # return decorator...
        return _decorator

# now for our defined preferences....
import pytz
import config

class TimeFormat (urltree.URLStringType, Preference) :
    """
        Time format
    """

    # set name
    name = 'time_format'

    # default value
    default = config.PREF_TIME_FMT_DEFAULT

class DateFormat (urltree.URLStringType, Preference) :
    """
        Date format
    """

    # set name
    name = 'date_format'

    # default value
    default = config.PREF_DATE_FMT_DEFAULT

class Timezone (Preference) :
    """
        Timezone
    """
    
    # set name
    name = 'timezone'

    # default value is UTC...
    default = config.PREF_TIMEZONE_DEFAULT

    def parse (self, name) :
        """
            tz_name -> pytz.timezone
        """

        return pytz.timezone(name)

    def build (self, tz) :
        """
            pytz.timezone -> tz_name
        """

        return tz.zone

class ImageFont (Preference) :
    """
        Font for ImageFormatter
    """

    # set name
    name = 'image_font'
    
    def __init__ (self, font_dict, default_name) :
        """
            Use the given { name: (path, title) } dict and default the given name
        """

        self.font_dict = font_dict
        self.default = self.parse(default_name)
    
    def parse (self, name) :
        """
            name -> (name, path, title)
        """

        path, title = self.font_dict[name]

        return name, path, title
    
    def build (self, font_info) :
        """
            (name, path, title) -> name
        """

        name, path, title = font_info

        return name

class ImageFontSize (urltree.URLIntegerType, Preference) :
    # set name, default
    name = 'image_font_size'
    default = config.PREF_IMAGE_FONT_SIZE_DEFAULT
    
    # XXX: params

class Formatter (Preference) :
    """
        LogFormatter to use
    """

    # set name
    name = 'formatter'

    def __init__ (self, formatters, default) :
        """
            Use the given { name -> class LogFormatter } dict and default (a LogFormatter class)
        """

        self.formatters = formatters
        self.default = default
    
    def parse (self, fmt_name) :
        """
            fmt_name -> class LogFormatter
        """

        return self.formatters[fmt_name]
    
    def build (self, fmt_cls) :
        """
            class LogFormatter -> fmt_name
        """

        return fmt_cls.name
    
    def process (self, prefs, fmt_cls) :
        """
            class LogFormatter -> LogFormatter(tz, time_fmt, image_font.path)
        """

        font_name, font_path, font_title = prefs[image_font]
        font_size = prefs[image_font_size]

        return fmt_cls(prefs[timezone], prefs[time_format], font_path, font_size)

class Count (urltree.URLIntegerType, Preference) :
    """
        Number of lines of log data to display per page
    """

    # set name
    name = "count"
    
    # default
    default = config.PREF_COUNT_DEFAULT
    
    def __init__ (self) :
        super(Count, self).__init__(allow_negative=False, allow_zero=False, max=config.PREF_COUNT_MAX)

# and then build the Preferences object
time_format     = TimeFormat()
date_format     = DateFormat()
timezone        = Timezone()
image_font      = ImageFont(config.FORMATTER_IMAGE_FONTS, config.PREF_IMAGE_FONT_DEFAULT)
image_font_size = ImageFontSize()
formatter       = Formatter(config.LOG_FORMATTERS, config.PREF_FORMATTER_DEFAULT)
count           = Count()

preferences = Preferences([
    time_format,
    date_format,
    timezone,
    image_font,
    image_font_size,
    formatter,
    count,
])