preferences.py
changeset 129 67a30d680f60
parent 79 43ac75054d5c
child 130 4212fd69d671
equal deleted inserted replaced
128:2a8a190f8aee 129:67a30d680f60
     4 
     4 
     5 import functools
     5 import functools
     6 import Cookie
     6 import Cookie
     7 
     7 
     8 from qmsk.web import urltree
     8 from qmsk.web import urltree
       
     9 import utils
     9 
    10 
    10 class Preference (urltree.URLType) :
    11 class Preference (urltree.URLType) :
    11     """
    12     """
    12         A specific preference
    13         A specific preference
    13     """
    14     """
    14 
    15 
    15     # the name to use
    16     # the name to use
    16     name = None
    17     name = None
    17 
    18 
    18     # the default value
    19     # the default value, as from parse()
    19     default = None
    20     default = None
    20     
    21     
       
    22     def is_default (self, value) :
       
    23         """
       
    24             Returns True if the given post-value is the default value for this preference.
       
    25 
       
    26             Defaults to just compare against self.default
       
    27         """
       
    28 
       
    29         return (value == self.default)
       
    30         
    21     def process (self, preferences, value) :
    31     def process (self, preferences, value) :
    22         """
    32         """
    23             Post-process this preference value. This can access the values of other preferences or the request via
    33             Post-process this preference value. This can access the post-processed values of all other preferences that
    24             the given RequestPreferences. Note that the other items will not be post-processed here.
    34             were defined before this one in the list given to Preferences. 
    25 
    35 
    26             Defaults to just return value.
    36             Defaults to just return value.
    27         """
    37         """
    28 
    38 
    29         return value
    39         return value
    33         Represents the specific preferences for some request
    43         Represents the specific preferences for some request
    34     """
    44     """
    35 
    45 
    36     def __init__ (self, preferences, request, value_map=None) :
    46     def __init__ (self, preferences, request, value_map=None) :
    37         """
    47         """
    38             Initialize with the given Preferences object, http Request, and list of mapping of raw preference values
    48             Initialize with the given Preferences object, http Request, and { key: value } mapping of raw preference values.
       
    49 
       
    50             This will build a mapping of { name: pre-value } using Preference.parse/Preference.default, and then
       
    51             post-process them into the final { name: value } mapping using Preference.process, in strict pref_list
       
    52             order. Note that the process() method will only have access to those preferences processed before it was.
    39         """
    53         """
    40         
    54         
    41         # store
    55         # store
    42         self.preferences = preferences
    56         self.preferences = preferences
    43         self.request = request
    57         self.request = request
    44 
    58 
    45         # initialize
    59         # initialize
    46         self.values = {}
    60         self.values = {}
    47         self.set_cookies = {}
    61         self.set_cookies = {}
       
    62 
       
    63         # initial value map
       
    64         pre_values = {}
    48 
    65 
    49         # load preferences
    66         # load preferences
    50         for pref in preferences.pref_list :
    67         for pref in preferences.pref_list :
    51             # got a value for it?
    68             # got a value for it?
    52             if value_map and pref.name in value_map :
    69             if value_map and pref.name in value_map :
    57                 value = pref.parse(value)
    74                 value = pref.parse(value)
    58 
    75 
    59             else :
    76             else :
    60                 # use default value
    77                 # use default value
    61                 value = pref.default
    78                 value = pref.default
    62             
    79                 
    63             # add
    80             # add
    64             self.values[pref.name] = value
    81             pre_values[pref.name] = value
    65         
    82         
    66         # then post-process using the Preferences
    83         # then post-process using Preferences.process(), in strict pref_list order
    67         self.values = dict(
    84         for pref in preferences.pref_list :
    68             (name, self.preferences.pref_map[name].process(self, value)) for name, value in self.values.iteritems()
    85             # store into self.values, so that pref.get(...) will be able to access the still-incomplete self.values
    69         )
    86             # dict
    70     
    87             self.values[pref.name] = pref.process(self, pre_values[pref.name])
    71     def get (self, pref) :
    88     
    72         """
    89     def _get_name (self, pref) :
    73             Return the value for the given Preference, or preference name
    90         """
    74         """
    91             Look up a Preference's name, either by class, object or name.
    75         
    92         """
       
    93 
    76         # Preference -> name
    94         # Preference -> name
    77         if isinstance(pref, Preference) :
    95         if isinstance(pref, Preference) :
    78             pref = pref.name
    96             pref = pref.name
       
    97 
       
    98         return pref
       
    99     
       
   100     def pref (self, name) :
       
   101         """
       
   102             Look up a Preference by object, name
       
   103         """
       
   104 
       
   105         # Preference
       
   106         if isinstance(name, Preference) :
       
   107             return name
       
   108         
       
   109         # Preference.name
       
   110         elif isinstance(name, basestring) :
       
   111             return self.preferences.pref_map[name]
       
   112         
       
   113         # XXX: class?
       
   114         else :
       
   115             assert False
       
   116 
       
   117     def get (self, pref) :
       
   118         """
       
   119             Return the value for the given Preference, or preference name
       
   120         """
    79         
   121         
    80         # look up
   122         # look up
    81         return self.values[pref]
   123         return self.values[self._get_name(pref)]
    82 
   124 
    83     # support dict-access
   125     # support dict-access
    84     __getitem__ = get
   126     __getitem__ = get
    85 
   127     
    86     def set (self, name, value_obj) :
   128     def is_default (self, pref) :
    87         """
   129         """
    88             Set a new value for the given preference
   130             Returns True if the given preference is at its default value
       
   131         """
       
   132         
       
   133         # determine using Preference.is_default
       
   134         return self.pref(pref).is_default(self.get(pref))
       
   135 
       
   136     def build (self, pref) :
       
   137         """
       
   138             Like 'get', but return the raw cookie value
       
   139         """
       
   140         
       
   141         # the Preference
       
   142         pref = self.pref(pref)
       
   143         
       
   144         # build
       
   145         return pref.build(self.get(pref))
       
   146     
       
   147     def parse (self, pref, value=None) :
       
   148         """
       
   149             Parse+process the raw value for some pref into a value object.
       
   150 
       
   151             Is the given raw value is None, this uses Preference.default
       
   152         """
       
   153 
       
   154         # lookup pref
       
   155         pref = self.pref(pref)
       
   156         
       
   157         # build value
       
   158         if value is not None :
       
   159             # parse
       
   160             value = pref.parse(value)
       
   161         
       
   162         else :
       
   163             # default
       
   164             value = pref.default
       
   165         
       
   166         # post-process
       
   167         value = pref.process(self, value)
       
   168 
       
   169         # return
       
   170         return value
       
   171 
       
   172     def set (self, name, value_obj=None) :
       
   173         """
       
   174             Set a new value for the given preference (by str name).
       
   175 
       
   176             If value_obj is None, then the preference cookie is unset
    89         """
   177         """
    90 
   178 
    91         # sanity-check to make sure we're not setting it twice...
   179         # sanity-check to make sure we're not setting it twice...
    92         assert name not in self.set_cookies
   180         assert name not in self.set_cookies
    93 
   181         
    94         # encode using the Preference object
   182         # None?
    95         value_str = self.preferences.pref_map[name].build(value_obj)
   183         if value_obj is not None :
       
   184             # encode using the Preference object
       
   185             value_str = self.preferences.pref_map[name].build(value_obj)
       
   186         
       
   187         else :
       
   188             # unset as None
       
   189             value_str = None
    96 
   190 
    97         # update in our dict
   191         # update in our dict
    98         self.values[name] = value_obj
   192         self.values[name] = value_obj
    99 
   193 
   100         # add to set_cookies
   194         # add to set_cookies
   105         Handle user preferences using cookies
   199         Handle user preferences using cookies
   106     """
   200     """
   107 
   201 
   108     def __init__ (self, pref_list) :
   202     def __init__ (self, pref_list) :
   109         """
   203         """
   110             Use the given list of Preference objects
   204             Use the given list of Preference objects.
       
   205 
       
   206             The ordering of the given pref_list is significant for the process() implementation, as the
       
   207             Preferences are process()'d in order.
   111         """
   208         """
   112 
   209 
   113         # store
   210         # store
   114         self.pref_list = pref_list
   211         self.pref_list = pref_list
   115 
   212 
   172                 # handle to get response
   269                 # handle to get response
   173                 response = func(request, **args)
   270                 response = func(request, **args)
   174 
   271 
   175                 # set cookies?
   272                 # set cookies?
   176                 if prefs.set_cookies :
   273                 if prefs.set_cookies :
       
   274                     # default, empty, cookiejar
   177                     if not cookies :
   275                     if not cookies :
   178                         cookies = Cookie.SimpleCookie('')
   276                         cookies = Cookie.SimpleCookie('')
   179 
   277 
   180                     # update cookies
   278                     # update cookies
   181                     for key, value in prefs.set_cookies.iteritems() :
   279                     for key, value in prefs.set_cookies.iteritems() :
   182                         cookies[key] = value
   280                         if value is None :
       
   281                             assert False, "Not implemented yet..."
       
   282 
       
   283                         else :
       
   284                             # set
       
   285                             cookies[key] = value
   183 
   286 
   184                     # add headers
   287                     # add headers
   185                     for morsel in cookies.itervalues() :
   288                     for morsel in cookies.itervalues() :
   186                         response.add_header('Set-cookie', morsel.OutputString())
   289                         response.add_header('Set-cookie', morsel.OutputString())
   187 
   290 
   217     name = 'date_format'
   320     name = 'date_format'
   218 
   321 
   219     # default value
   322     # default value
   220     default = config.PREF_DATE_FMT_DEFAULT
   323     default = config.PREF_DATE_FMT_DEFAULT
   221 
   324 
       
   325 class TimezoneOffset (Preference) :
       
   326     """
       
   327         If the DST-aware 'timezone' is missing, we can fallback to a fixed-offset timezone as detected by
       
   328         Javascript.
       
   329 
       
   330         This is read-only, and None by default
       
   331     """
       
   332 
       
   333     name = 'timezone_offset'
       
   334     default = None
       
   335 
       
   336     def parse (self, offset) :
       
   337         """
       
   338             Offset in minutes -> said minutes
       
   339         """
       
   340 
       
   341         return int(offset)
       
   342 
   222 class Timezone (Preference) :
   343 class Timezone (Preference) :
   223     """
   344     """
   224         Timezone
   345         Timezone
   225     """
   346     """
   226     
   347     
   227     # set name
   348     # set name
   228     name = 'timezone'
   349     name = 'timezone'
   229 
   350 
   230     # default value is UTC...
   351     # default is handled via process()
   231     default = config.PREF_TIMEZONE_DEFAULT
   352     default = 'auto'
       
   353 
       
   354     # the list of available (value, name) options for use with helpers.select_options
       
   355     OPTIONS = [('auto', "Autodetect")] + [(None, tz_name) for tz_name in pytz.common_timezones]
   232 
   356 
   233     def parse (self, name) :
   357     def parse (self, name) :
   234         """
   358         """
       
   359             default -> default
   235             tz_name -> pytz.timezone
   360             tz_name -> pytz.timezone
   236         """
   361         """
   237 
   362         
   238         return pytz.timezone(name)
   363         # special-case for 'auto'
       
   364         if name == self.default :
       
   365             return self.default
       
   366 
       
   367         else :
       
   368             return pytz.timezone(name)
       
   369 
       
   370     def is_default (self, tz) :
       
   371         """
       
   372             True if it's a FixedOffsetTimezone
       
   373         """
       
   374 
       
   375         return (isinstance(tz, utils.FixedOffsetTimezone))
   239 
   376 
   240     def build (self, tz) :
   377     def build (self, tz) :
   241         """
   378         """
       
   379             FixedOffsetTimezone -> None
   242             pytz.timezone -> tz_name
   380             pytz.timezone -> tz_name
   243         """
   381         """
   244 
   382         
   245         return tz.zone
   383         # special-case for auto/no explicit timezone
       
   384         if self.is_default(tz) :
       
   385             return self.default
       
   386 
       
   387         else :
       
   388             # pytz.timezone zone name
       
   389             return tz.zone
       
   390     
       
   391     def process (self, prefs, tz) :
       
   392         """
       
   393             If this timezone is given, simply build that. Otherwise, try and use TimezoneOffset, and if that fails,
       
   394             just return the default.
       
   395 
       
   396             None -> FixedOffsetTimezone/PREF_TIMEZONE_DEFAULT
       
   397             pytz.timezone -> pytz.timezone
       
   398         """
       
   399         
       
   400         # specific timezone set?
       
   401         if tz != self.default :
       
   402             return tz
       
   403         
       
   404         # fixed offset?
       
   405         elif prefs[timezone_offset] is not None :
       
   406             return utils.FixedOffsetTimezone(prefs[timezone_offset])
       
   407         
       
   408         # default
       
   409         else :
       
   410             print
       
   411             print "Timezone.process: %r, %r -> default: %r" % (prefs, tz, config.PREF_TIMEZONE_DEFAULT)
       
   412             return config.PREF_TIMEZONE_DEFAULT
   246 
   413 
   247 class ImageFont (Preference) :
   414 class ImageFont (Preference) :
   248     """
   415     """
   249         Font for ImageFormatter
   416         Font for ImageFormatter
   250     """
   417     """
   281 class ImageFontSize (urltree.URLIntegerType, Preference) :
   448 class ImageFontSize (urltree.URLIntegerType, Preference) :
   282     # set name, default
   449     # set name, default
   283     name = 'image_font_size'
   450     name = 'image_font_size'
   284     default = config.PREF_IMAGE_FONT_SIZE_DEFAULT
   451     default = config.PREF_IMAGE_FONT_SIZE_DEFAULT
   285     
   452     
   286     # XXX: params
   453     # XXX: constraints for valid values
   287 
   454 
   288 class Formatter (Preference) :
   455 class Formatter (Preference) :
   289     """
   456     """
   290         LogFormatter to use
   457         LogFormatter to use
   291     """
   458     """
   318     def process (self, prefs, fmt_cls) :
   485     def process (self, prefs, fmt_cls) :
   319         """
   486         """
   320             class LogFormatter -> LogFormatter(tz, time_fmt, image_font.path)
   487             class LogFormatter -> LogFormatter(tz, time_fmt, image_font.path)
   321         """
   488         """
   322 
   489 
       
   490         # time stuff
       
   491         tz = prefs[timezone]
       
   492         time_fmt = prefs[time_format]
       
   493         
       
   494         # font stuff
   323         font_name, font_path, font_title = prefs[image_font]
   495         font_name, font_path, font_title = prefs[image_font]
   324         font_size = prefs[image_font_size]
   496         font_size = prefs[image_font_size]
   325 
   497 
   326         return fmt_cls(prefs[timezone], prefs[time_format], font_path, font_size)
   498         return fmt_cls(tz, time_fmt, font_path, font_size)
   327 
   499 
   328 class Count (urltree.URLIntegerType, Preference) :
   500 class Count (urltree.URLIntegerType, Preference) :
   329     """
   501     """
   330         Number of lines of log data to display per page
   502         Number of lines of log data to display per page
   331     """
   503     """
   340         super(Count, self).__init__(allow_negative=False, allow_zero=False, max=config.PREF_COUNT_MAX)
   512         super(Count, self).__init__(allow_negative=False, allow_zero=False, max=config.PREF_COUNT_MAX)
   341 
   513 
   342 # and then build the Preferences object
   514 # and then build the Preferences object
   343 time_format     = TimeFormat()
   515 time_format     = TimeFormat()
   344 date_format     = DateFormat()
   516 date_format     = DateFormat()
       
   517 timezone_offset = TimezoneOffset()
   345 timezone        = Timezone()
   518 timezone        = Timezone()
   346 image_font      = ImageFont(config.FORMATTER_IMAGE_FONTS, config.PREF_IMAGE_FONT_DEFAULT)
   519 image_font      = ImageFont(config.FORMATTER_IMAGE_FONTS, config.PREF_IMAGE_FONT_DEFAULT)
   347 image_font_size = ImageFontSize()
   520 image_font_size = ImageFontSize()
   348 formatter       = Formatter(config.LOG_FORMATTERS, config.PREF_FORMATTER_DEFAULT)
   521 formatter       = Formatter(config.LOG_FORMATTERS, config.PREF_FORMATTER_DEFAULT)
   349 count           = Count()
   522 count           = Count()
   350 
   523 
   351 preferences = Preferences([
   524 preferences = Preferences([
   352     time_format,
   525     time_format,
   353     date_format,
   526     date_format,
       
   527     timezone_offset,
   354     timezone,
   528     timezone,
   355     image_font,
   529     image_font,
   356     image_font_size,
   530     image_font_size,
   357     formatter,
   531     formatter,
   358     count,
   532     count,