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 |
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 """ |
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, |