--- a/preferences.py Sun Sep 13 00:49:55 2009 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,534 +0,0 @@
-"""
- Handling user preferences
-"""
-
-import functools
-import Cookie
-
-from qmsk.web import urltree
-import utils
-
-class Preference (urltree.URLType) :
- """
- A specific preference
- """
-
- # the name to use
- name = None
-
- # the default value, as from parse()
- default = None
-
- def is_default (self, value) :
- """
- Returns True if the given post-value is the default value for this preference.
-
- Defaults to just compare against self.default
- """
-
- return (value == self.default)
-
- def process (self, preferences, value) :
- """
- Post-process this preference value. This can access the post-processed values of all other preferences that
- were defined before this one in the list given to Preferences.
-
- 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 { key: value } mapping of raw preference values.
-
- This will build a mapping of { name: pre-value } using Preference.parse/Preference.default, and then
- post-process them into the final { name: value } mapping using Preference.process, in strict pref_list
- order. Note that the process() method will only have access to those preferences processed before it was.
- """
-
- # store
- self.preferences = preferences
- self.request = request
-
- # initialize
- self.values = {}
- self.set_cookies = {}
-
- # initial value map
- pre_values = {}
-
- # 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
- pre_values[pref.name] = value
-
- # then post-process using Preferences.process(), in strict pref_list order
- for pref in preferences.pref_list :
- # store into self.values, so that pref.get(...) will be able to access the still-incomplete self.values
- # dict
- self.values[pref.name] = pref.process(self, pre_values[pref.name])
-
- def _get_name (self, pref) :
- """
- Look up a Preference's name, either by class, object or name.
- """
-
- # Preference -> name
- if isinstance(pref, Preference) :
- pref = pref.name
-
- return pref
-
- def pref (self, name) :
- """
- Look up a Preference by object, name
- """
-
- # Preference
- if isinstance(name, Preference) :
- return name
-
- # Preference.name
- elif isinstance(name, basestring) :
- return self.preferences.pref_map[name]
-
- # XXX: class?
- else :
- assert False
-
- def get (self, pref) :
- """
- Return the value for the given Preference, or preference name
- """
-
- # look up
- return self.values[self._get_name(pref)]
-
- # support dict-access
- __getitem__ = get
-
- def is_default (self, pref) :
- """
- Returns True if the given preference is at its default value
- """
-
- # determine using Preference.is_default
- return self.pref(pref).is_default(self.get(pref))
-
- def build (self, pref) :
- """
- Like 'get', but return the raw cookie value
- """
-
- # the Preference
- pref = self.pref(pref)
-
- # build
- return pref.build(self.get(pref))
-
- def parse (self, pref, value=None) :
- """
- Parse+process the raw value for some pref into a value object.
-
- Is the given raw value is None, this uses Preference.default
- """
-
- # lookup pref
- pref = self.pref(pref)
-
- # build value
- if value is not None :
- # parse
- value = pref.parse(value)
-
- else :
- # default
- value = pref.default
-
- # post-process
- value = pref.process(self, value)
-
- # return
- return value
-
- def set (self, name, value_obj=None) :
- """
- Set a new value for the given preference (by str name).
-
- If value_obj is None, then the preference cookie is unset
- """
-
- # sanity-check to make sure we're not setting it twice...
- assert name not in self.set_cookies
-
- # None?
- if value_obj is not None :
- # encode using the Preference object
- value_str = self.preferences.pref_map[name].build(value_obj)
-
- else :
- # unset as None
- value_str = None
-
- # 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.
-
- The ordering of the given pref_list is significant for the process() implementation, as the
- Preferences are process()'d in order.
- """
-
- # 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 :
- # default, empty, cookiejar
- if not cookies :
- cookies = Cookie.SimpleCookie('')
-
- # update cookies
- for key, value in prefs.set_cookies.iteritems() :
- if value is None :
- assert False, "Not implemented yet..."
-
- else :
- # set
- cookies[key] = value
- cookies[key]["path"] = config.PREF_COOKIE_PATH
- cookies[key]["expires"] = config.PREF_COOKIE_EXPIRE_SECONDS
-
- # 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 TimezoneOffset (Preference) :
- """
- If the DST-aware 'timezone' is missing, we can fallback to a fixed-offset timezone as detected by
- Javascript.
-
- This is read-only, and None by default
- """
-
- name = 'timezone_offset'
- default = None
-
- def parse (self, offset) :
- """
- Offset in minutes -> said minutes
- """
-
- return int(offset)
-
-class Timezone (Preference) :
- """
- Timezone
- """
-
- # set name
- name = 'timezone'
-
- # default is handled via process()
- default = 'auto'
-
- # the list of available (value, name) options for use with helpers.select_options
- OPTIONS = [('auto', "Autodetect")] + [(None, tz_name) for tz_name in pytz.common_timezones]
-
- def parse (self, name) :
- """
- default -> default
- tz_name -> pytz.timezone
- """
-
- # special-case for 'auto'
- if name == self.default :
- return self.default
-
- else :
- return pytz.timezone(name)
-
- def is_default (self, tz) :
- """
- True if it's a FixedOffsetTimezone or PREF_TIMEZONE_FALLBACK
- """
-
- return (isinstance(tz, utils.FixedOffsetTimezone) or tz == config.PREF_TIMEZONE_FALLBACK)
-
- def build (self, tz) :
- """
- FixedOffsetTimezone -> None
- pytz.timezone -> tz_name
- """
-
- # special-case for auto/no explicit timezone
- if self.is_default(tz) :
- return self.default
-
- else :
- # pytz.timezone zone name
- return tz.zone
-
- def process (self, prefs, tz) :
- """
- If this timezone is given, simply build that. Otherwise, try and use TimezoneOffset, and if that fails,
- just return the default.
-
- None -> FixedOffsetTimezone/PREF_TIMEZONE_FALLBACK
- pytz.timezone -> pytz.timezone
- """
-
- # specific timezone set?
- if tz != self.default :
- return tz
-
- # fixed offset?
- elif prefs[timezone_offset] is not None :
- return utils.FixedOffsetTimezone(prefs[timezone_offset])
-
- # default
- else :
- return config.PREF_TIMEZONE_FALLBACK
-
-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: constraints for valid values
-
-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)
- """
-
- # time stuff
- tz = prefs[timezone]
- time_fmt = prefs[time_format]
-
- # font stuff
- font_name, font_path, font_title = prefs[image_font]
- font_size = prefs[image_font_size]
-
- return fmt_cls(tz, time_fmt, 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_offset = TimezoneOffset()
-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_offset,
- timezone,
- image_font,
- image_font_size,
- formatter,
- count,
-])
-