add some user-preferences support (e.g. timezone, time formats)
authorTero Marttila <terom@fixme.fi>
Mon, 09 Feb 2009 03:05:43 +0200
changeset 53 8103d18907a0
parent 52 dcb67a8f24be
child 54 b65a95eb9f6b
add some user-preferences support (e.g. timezone, time formats)
handlers.py
preferences.py
urls.py
utils.py
--- a/handlers.py	Mon Feb 09 01:17:32 2009 +0200
+++ b/handlers.py	Mon Feb 09 03:05:43 2009 +0200
@@ -7,6 +7,8 @@
 from qmsk.web import http, template
 
 import urls, channels, helpers
+import preferences as prefs
+from preferences import preferences
 
 # load templates from here
 templates = template.TemplateLoader("templates",
@@ -31,6 +33,7 @@
    
     return http.Redirect(urls.channel_view.build(request, channel=channel.id))
 
+@preferences.handler(prefs.Formatter)
 def channel_view (request, channel, count, formatter) :
     """
         The main channel view page, display the most important info, and all requisite links
@@ -39,9 +42,6 @@
     # get latest events
     lines = channel.source.get_latest(count)
 
-    # formatter
-    formatter = formatter(pytz.utc, '%H:%M:%S')
-    
     # lines
     lines = formatter.format_html(lines)
 
@@ -73,20 +73,18 @@
 
     pass
 
-def channel_date (request, channel, date, formatter) :
+@preferences.handler(prefs.Formatter, prefs.Timezone)
+def channel_date (request, channel, date, formatter, timezone) :
     """
         Display all log data for the given date
     """
     
-    # XXX: fix date timezone
-    date = date.replace(tzinfo=pytz.utc)
+    # fix date timezone
+    date = date.replace(tzinfo=timezone)
 
     # get latest events
     lines = channel.source.get_date(date)
 
-    # formatter
-    formatter = formatter(pytz.utc, '%H:%M:%S')
-    
     # lines
     lines = formatter.format_html(lines)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/preferences.py	Mon Feb 09 03:05:43 2009 +0200
@@ -0,0 +1,277 @@
+"""
+    Handling user preferences
+"""
+
+import functools
+import Cookie
+
+from qmsk.web import urltree
+
+class Preference (urltree.URLType) :
+    """
+        A specific preference
+    """
+
+    # the name to use
+    name = None
+
+    # the default value
+    default = None
+    
+    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.
+
+            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 list of mapping of raw preference values
+        """
+        
+        # store
+        self.preferences = preferences
+        self.request = request
+
+        # initialize
+        self.values = {}
+        self.set_cookies = {}
+
+        # 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
+            self.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()
+        )
+
+    def get (self, pref) :
+        """
+            Return the value for the given Preference
+        """
+        
+        return self.values[pref.name]
+    
+    def set (self, name, value_obj) :
+        """
+            Set a new value for the given preference
+        """
+
+        # 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)
+
+        # 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
+        """
+
+        # 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)
+
+                # 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 :
+                    # update cookies
+                    for key, value in prefs.set_cookies.iteritems() :
+                        cookies[key] = value
+
+                    # 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
+
+class TimeFormat (urltree.URLStringType, Preference) :
+    """
+        Time format
+    """
+
+    # set name
+    name = 'time_format'
+
+    # default value
+    default = "%H:%M:%S"
+
+class DateFormat (urltree.URLStringType, Preference) :
+    """
+        Date format
+    """
+
+    # set name
+    name = 'date_format'
+
+    # default value
+    default = "%Y-%m%d"
+
+class Timezone (Preference) :
+    """
+        Timezone
+    """
+    
+    # set name
+    name = 'timezone'
+
+    # default value is UTC...
+    default = pytz.utc
+
+    def parse (self, name) :
+        """
+            tz_name -> pytz.timezone
+        """
+
+        return pytz.timezone(name)
+
+    def build (self, tz) :
+        """
+            pytz.timezone -> tz_name
+        """
+
+        return tz.zone
+
+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.name
+    
+    def process (self, prefs, fmt_cls) :
+        """
+            class LogFormatter -> LogFormatter(tz, time_fmt)
+        """
+
+        return fmt_cls(prefs.get(Timezone), prefs.get(TimeFormat))
+
+# and then build the Preferences object
+import log_formatter
+
+preferences = Preferences([
+    TimeFormat(),
+    DateFormat(),
+    Timezone(),
+    Formatter(log_formatter.FORMATTERS, log_formatter.IrssiFormatter),
+])
+
--- a/urls.py	Mon Feb 09 01:17:32 2009 +0200
+++ b/urls.py	Mon Feb 09 03:05:43 2009 +0200
@@ -13,7 +13,7 @@
 import utils
 
 # for configuration
-import channels, log_formatter
+import channels
 
 # our URLConfig
 urls = url = urltree.URLConfig(
@@ -21,9 +21,6 @@
         # LogChannel
         cid     = utils.URLChannelName(channels.channel_list.dict()),
 
-        # LogFormatter
-        fmt     =  utils.URLFormatterName(log_formatter.FORMATTERS),
-
         # datetime
         date    = utils.URLDateType('%Y-%m-%d'),
     )
@@ -32,10 +29,10 @@
 # urls
 index           = url('/',                                                              handlers.index                  )
 channel_select  = url('/channel_select/?channel:cid',                                   handlers.channel_select         )
-channel_view    = url('/channels/{channel:cid}/?count:int=10&formatter:fmt=irssi',      handlers.channel_view           )
+channel_view    = url('/channels/{channel:cid}/?count:int=10',                          handlers.channel_view           )
 channel_last    = url('/channels/{channel:cid}/last/{count:int=100}/{format=html}',     handlers.channel_last           )
 channel_date    = url('/channels/{channel:cid}/calendar',                               handlers.channel_calendar       )
-channel_date    = url('/channels/{channel:cid}/date/{date:date}/?formatter:fmt=irssi',  handlers.channel_date           )
+channel_date    = url('/channels/{channel:cid}/date/{date:date}',                       handlers.channel_date           )
 channel_search  = url('/channels/{channel:cid}/search/?q',                              handlers.channel_search         )
 
 # mapper
--- a/utils.py	Mon Feb 09 01:17:32 2009 +0200
+++ b/utils.py	Mon Feb 09 03:05:43 2009 +0200
@@ -32,33 +32,6 @@
 
         return chan.id
 
-class URLFormatterName (URLType) :
-    """
-        Handle LogFormatter names in URLs. Note that they evaluate into the LogFormatter class itself, not an
-        instance, although build requiers an instance
-    """
-
-    def __init__ (self, formatters) :
-        """
-            Use the given { name -> class LogFormatter } dict
-        """
-
-        self.formatters = formatters
-    
-    def parse (self, fmt_name) :
-        """
-            fmt_name -> class LogFormatter
-        """
-
-        return self.formatters[fmt_name]
-    
-    def build (self, fmt) :
-        """
-            LogFormatter -> fmt_name
-        """
-
-        return fmt.name
-
 class URLDateType (URLType) :
     """
         Handle dates in URLs as naive datetime objects (with indeterminate time info)