preferences.py
changeset 129 67a30d680f60
parent 79 43ac75054d5c
child 130 4212fd69d671
--- 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,