diff -r 2a8a190f8aee -r 67a30d680f60 preferences.py --- 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,