preferences.py
author Tero Marttila <terom@fixme.fi>
Sat, 14 Feb 2009 20:13:26 +0200
changeset 128 2a8a190f8aee
parent 79 43ac75054d5c
child 129 67a30d680f60
permissions -rw-r--r--
fix config.VERSION_LINK to use relpath('.')
"""
    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,
])