--- a/preferences.py Sat Feb 14 20:13:26 2009 +0200
+++ b/preferences.py Sun Feb 15 23:50:24 2009 +0200
@@ -6,6 +6,7 @@
import Cookie
from qmsk.web import urltree
+import utils
class Preference (urltree.URLType) :
"""
@@ -15,13 +16,22 @@
# the name to use
name = None
- # the default value
+ # 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 values of other preferences or the request via
- the given RequestPreferences. Note that the other items will not be post-processed here.
+ 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.
"""
@@ -35,7 +45,11 @@
def __init__ (self, preferences, request, value_map=None) :
"""
- Initialize with the given Preferences object, http Request, and list of mapping of raw preference values
+ 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
@@ -46,6 +60,9 @@
self.values = {}
self.set_cookies = {}
+ # initial value map
+ pre_values = {}
+
# load preferences
for pref in preferences.pref_list :
# got a value for it?
@@ -59,40 +76,117 @@
else :
# use default value
value = pref.default
-
+
# add
- self.values[pref.name] = value
+ pre_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()
- )
+ # 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
"""
- # Preference -> name
- if isinstance(pref, Preference) :
- pref = pref.name
-
# look up
- return self.values[pref]
+ 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 set (self, name, value_obj) :
+ def build (self, pref) :
"""
- Set a new value for the given preference
+ 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
-
- # encode using the Preference object
- value_str = self.preferences.pref_map[name].build(value_obj)
+
+ # 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
@@ -107,7 +201,10 @@
def __init__ (self, pref_list) :
"""
- Use the given list of Preference objects
+ 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
@@ -174,12 +271,18 @@
# 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() :
- cookies[key] = value
+ if value is None :
+ assert False, "Not implemented yet..."
+
+ else :
+ # set
+ cookies[key] = value
# add headers
for morsel in cookies.itervalues() :
@@ -219,6 +322,24 @@
# 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
@@ -227,22 +348,68 @@
# set name
name = 'timezone'
- # default value is UTC...
- default = config.PREF_TIMEZONE_DEFAULT
+ # 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
- return pytz.timezone(name)
+ else :
+ return pytz.timezone(name)
+
+ def is_default (self, tz) :
+ """
+ True if it's a FixedOffsetTimezone
+ """
+
+ return (isinstance(tz, utils.FixedOffsetTimezone))
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
- return tz.zone
+ 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_DEFAULT
+ 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 :
+ print
+ print "Timezone.process: %r, %r -> default: %r" % (prefs, tz, config.PREF_TIMEZONE_DEFAULT)
+ return config.PREF_TIMEZONE_DEFAULT
class ImageFont (Preference) :
"""
@@ -283,7 +450,7 @@
name = 'image_font_size'
default = config.PREF_IMAGE_FONT_SIZE_DEFAULT
- # XXX: params
+ # XXX: constraints for valid values
class Formatter (Preference) :
"""
@@ -320,10 +487,15 @@
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(prefs[timezone], prefs[time_format], font_path, font_size)
+ return fmt_cls(tz, time_fmt, font_path, font_size)
class Count (urltree.URLIntegerType, Preference) :
"""
@@ -342,6 +514,7 @@
# 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()
@@ -351,6 +524,7 @@
preferences = Preferences([
time_format,
date_format,
+ timezone_offset,
timezone,
image_font,
image_font_size,