terom@53: """ terom@53: Handling user preferences terom@53: """ terom@53: terom@53: import functools terom@53: import Cookie terom@53: terom@53: from qmsk.web import urltree terom@53: terom@53: class Preference (urltree.URLType) : terom@53: """ terom@53: A specific preference terom@53: """ terom@53: terom@53: # the name to use terom@53: name = None terom@53: terom@53: # the default value terom@53: default = None terom@53: terom@53: def process (self, preferences, value) : terom@53: """ terom@53: Post-process this preference value. This can access the values of other preferences or the request via terom@53: the given RequestPreferences. Note that the other items will not be post-processed here. terom@53: terom@53: Defaults to just return value. terom@53: """ terom@53: terom@53: return value terom@53: terom@53: class RequestPreferences (object) : terom@53: """ terom@53: Represents the specific preferences for some request terom@53: """ terom@53: terom@53: def __init__ (self, preferences, request, value_map=None) : terom@53: """ terom@53: Initialize with the given Preferences object, http Request, and list of mapping of raw preference values terom@53: """ terom@53: terom@53: # store terom@53: self.preferences = preferences terom@53: self.request = request terom@53: terom@53: # initialize terom@53: self.values = {} terom@53: self.set_cookies = {} terom@53: terom@53: # load preferences terom@53: for pref in preferences.pref_list : terom@53: # got a value for it? terom@53: if value_map and pref.name in value_map : terom@53: # get value terom@53: value = value_map[pref.name] terom@53: terom@53: # parse it terom@53: value = pref.parse(value) terom@53: terom@53: else : terom@53: # use default value terom@53: value = pref.default terom@53: terom@53: # add terom@53: self.values[pref.name] = value terom@53: terom@53: # then post-process using the Preferences terom@53: self.values = dict( terom@53: (name, self.preferences.pref_map[name].process(self, value)) for name, value in self.values.iteritems() terom@53: ) terom@59: terom@53: def get (self, pref) : terom@53: """ terom@62: Return the value for the given Preference, or preference name terom@53: """ terom@53: terom@62: # Preference -> name terom@62: if isinstance(pref, Preference) : terom@62: pref = pref.name terom@62: terom@62: # look up terom@62: return self.values[pref] terom@59: terom@59: # support dict-access terom@59: __getitem__ = get terom@59: terom@53: def set (self, name, value_obj) : terom@53: """ terom@53: Set a new value for the given preference terom@53: """ terom@53: terom@53: # sanity-check to make sure we're not setting it twice... terom@53: assert name not in self.set_cookies terom@53: terom@53: # encode using the Preference object terom@53: value_str = self.preferences.pref_map[name].build(value_obj) terom@53: terom@53: # update in our dict terom@53: self.values[name] = value_obj terom@53: terom@53: # add to set_cookies terom@53: self.set_cookies[name] = value_str terom@53: terom@53: class Preferences (object) : terom@53: """ terom@53: Handle user preferences using cookies terom@53: """ terom@53: terom@53: def __init__ (self, pref_list) : terom@53: """ terom@53: Use the given list of Preference objects terom@53: """ terom@53: terom@53: # store terom@53: self.pref_list = pref_list terom@53: terom@53: # translate to mapping as well terom@53: self.pref_map = dict((pref.name, pref) for pref in pref_list) terom@53: terom@53: def load (self, request, ) : terom@53: """ terom@53: Load the set of preferences for the given request, and return as a { name -> value } dict terom@53: """ terom@53: terom@53: # the dict of values terom@53: values = {} terom@53: terom@53: # load the cookies terom@53: cookie_data = request.env.get('HTTP_COOKIE') terom@53: terom@53: # got any? terom@53: if cookie_data : terom@53: # parse into a SimpleCookie terom@53: cookies = Cookie.SimpleCookie(cookie_data) terom@53: terom@53: # update the the values terom@53: values.update((morsel.key, morsel.value) for morsel in cookies.itervalues()) terom@53: terom@53: else : terom@53: cookies = None terom@53: terom@53: # apply any query parameters terom@53: for pref in self.pref_list : terom@53: # look for a query param terom@53: value = request.get_arg(pref.name) terom@53: terom@53: if value : terom@53: # override terom@53: values[pref.name] = value terom@53: terom@53: # build the RequestPreferences object terom@53: return cookies, RequestPreferences(self, request, values) terom@53: terom@53: def handler (self, *pref_list) : terom@53: """ terom@53: Intended to be used as a decorator for a request handler, this will load the give Preferences and pass terom@53: them to the wrapped handler as keyword arguments, in addition to any others given. terom@53: """ terom@53: terom@53: def _decorator (func) : terom@53: @functools.wraps(func) terom@53: def _handler (request, **args) : terom@53: # load preferences terom@53: cookies, prefs = self.load(request) terom@53: terom@59: # bind to request.prefs terom@59: # XXX: better way to do this? :/ terom@59: request.prefs = prefs terom@59: terom@53: # update args with new ones terom@53: args.update(((pref.name, prefs.get(pref)) for pref in pref_list)) terom@53: terom@53: # handle to get response terom@53: response = func(request, **args) terom@53: terom@53: # set cookies? terom@53: if prefs.set_cookies : terom@59: if not cookies : terom@59: cookies = Cookie.SimpleCookie('') terom@59: terom@53: # update cookies terom@53: for key, value in prefs.set_cookies.iteritems() : terom@53: cookies[key] = value terom@53: terom@53: # add headers terom@53: for morsel in cookies.itervalues() : terom@53: response.add_header('Set-cookie', morsel.OutputString()) terom@53: terom@53: return response terom@53: terom@53: # return wrapped handler terom@53: return _handler terom@53: terom@53: # return decorator... terom@53: return _decorator terom@53: terom@53: # now for our defined preferences.... terom@53: import pytz terom@73: import config terom@53: terom@53: class TimeFormat (urltree.URLStringType, Preference) : terom@53: """ terom@53: Time format terom@53: """ terom@53: terom@53: # set name terom@53: name = 'time_format' terom@53: terom@53: # default value terom@73: default = config.PREF_TIME_FMT_DEFAULT terom@53: terom@53: class DateFormat (urltree.URLStringType, Preference) : terom@53: """ terom@53: Date format terom@53: """ terom@53: terom@53: # set name terom@53: name = 'date_format' terom@53: terom@53: # default value terom@73: default = config.PREF_DATE_FMT_DEFAULT terom@53: terom@53: class Timezone (Preference) : terom@53: """ terom@53: Timezone terom@53: """ terom@53: terom@53: # set name terom@53: name = 'timezone' terom@53: terom@53: # default value is UTC... terom@73: default = config.PREF_TIMEZONE_DEFAULT terom@53: terom@53: def parse (self, name) : terom@53: """ terom@53: tz_name -> pytz.timezone terom@53: """ terom@53: terom@53: return pytz.timezone(name) terom@53: terom@53: def build (self, tz) : terom@53: """ terom@53: pytz.timezone -> tz_name terom@53: """ terom@53: terom@53: return tz.zone terom@53: terom@53: class Formatter (Preference) : terom@53: """ terom@53: LogFormatter to use terom@53: """ terom@53: terom@53: # set name terom@53: name = 'formatter' terom@53: terom@53: def __init__ (self, formatters, default) : terom@53: """ terom@53: Use the given { name -> class LogFormatter } dict and default (a LogFormatter class) terom@53: """ terom@53: terom@53: self.formatters = formatters terom@53: self.default = default terom@53: terom@53: def parse (self, fmt_name) : terom@53: """ terom@53: fmt_name -> class LogFormatter terom@53: """ terom@53: terom@53: return self.formatters[fmt_name] terom@53: terom@53: def build (self, fmt_cls) : terom@53: """ terom@53: class LogFormatter -> fmt_name terom@53: """ terom@53: terom@59: return fmt_cls.name terom@53: terom@53: def process (self, prefs, fmt_cls) : terom@53: """ terom@53: class LogFormatter -> LogFormatter(tz, time_fmt) terom@53: """ terom@53: terom@59: return fmt_cls(prefs[timezone], prefs[time_format]) terom@53: terom@73: class Count (urltree.URLIntegerType, Preference) : terom@73: """ terom@73: Number of lines of log data to display per page terom@73: """ terom@53: terom@73: # set name terom@73: name = "count" terom@73: terom@73: # default terom@73: default = config.PREF_COUNT_DEFAULT terom@73: terom@73: def __init__ (self) : terom@73: super(Count, self).__init__(allow_negative=False, allow_zero=False, max=config.PREF_COUNT_MAX) terom@73: terom@73: # and then build the Preferences object terom@73: time_format = TimeFormat() terom@73: date_format = DateFormat() terom@73: timezone = Timezone() terom@73: formatter = Formatter(config.LOG_FORMATTERS, config.PREF_FORMATTER_DEFAULT) terom@73: count = Count() terom@59: terom@53: preferences = Preferences([ terom@59: time_format, terom@59: date_format, terom@59: timezone, terom@59: formatter, terom@73: count, terom@53: ]) terom@53: