04386fd53e9b17555dcc59c5c6e282a6b0eaa917
[zzz-spline-frontpage.git] / splinext / frontpage / controllers / frontpage.py
1 from collections import defaultdict
2 import datetime
3 import logging
4 import re
5
6 from pylons import config, request, response, session, tmpl_context as c, url
7 from pylons.controllers.util import abort, redirect_to
8 from routes import request_config
9 from sqlalchemy.orm.exc import NoResultFound
10
11 from spline.lib import helpers as h
12 from spline.lib.base import BaseController, render
13 from spline.lib.plugin.load import run_hooks
14
15 log = logging.getLogger(__name__)
16
17 class FrontPageController(BaseController):
18
19 def index(self):
20 """Magicaltastic front page.
21
22 Plugins can register a hook called 'frontpage_updates_<type>' to add
23 updates to the front page. `<type>` is an arbitrary string indicating
24 the sort of update the plugin knows how to handle; for example,
25 spline-forum has a `frontpage_updates_forum` hook for posting news from
26 a specific forum.
27
28 Hook handlers should return a list of FrontPageUpdate objects.
29
30 Standard hook parameters are:
31 `limit`, the maximum number of items that should ever be returned.
32 `max_age`, the number of seconds after which items expire.
33 `title`, a name for the source.
34 `icon`, an icon to show next to its name.
35
36 `limit` and `max_age` are also global options.
37
38 Updates are configured in the .ini like so:
39
40 spline-frontpage.sources.foo = updatetype
41 spline-frontpage.sources.foo.opt1 = val1
42 spline-frontpage.sources.foo.opt2 = val2
43
44 Note that the 'foo' name is completely arbitrary and is only used for
45 grouping options together. This will result in a call to:
46
47 run_hooks('frontpage_updates_updatetype', opt1=val1, opt2=val2)
48
49 Local plugins can override the fairly simple index.mako template to
50 customize the front page layout.
51 """
52 # XXX no reason to do this on the fly; cache it on server startup
53 update_config = defaultdict(dict) # source_name => config
54 key_rx = re.compile(
55 '(?x) ^ spline-frontpage [.] sources [.] (\w+) (?: [.] (\w+) )? $')
56 for key, val in config.iteritems():
57 match = key_rx.match(key)
58 if not match:
59 continue
60
61 source_name, subkey = match.groups()
62 if not subkey:
63 # This is the type declaration; use a special key
64 subkey = '__type__'
65
66 if subkey in ('limit', 'max_age'):
67 val = int(val)
68 update_config[source_name][subkey] = val
69
70 global_limit = int(config.get('spline-frontpage.limit', 10))
71 now = datetime.datetime.now()
72 try:
73 global_max_age = now - datetime.timedelta(
74 seconds=int(config['spline-frontpage.max_age']))
75 except KeyError:
76 global_max_age = None
77
78 # Ask plugins to deal with this stuff for us!
79 updates = []
80 for source, source_config in update_config.iteritems():
81 hook_name = 'frontpage_updates_' + source_config['__type__']
82
83 # Merge with the global config
84 merged_config = source_config.copy()
85 del merged_config['__type__']
86
87 merged_config['limit'] = min(
88 merged_config.get('limit', global_limit),
89 global_limit,
90 )
91
92 try:
93 local_max_age = now - datetime.timedelta(
94 seconds=merged_config['max_age'])
95 except KeyError:
96 local_max_age = None
97
98 if global_max_age and local_max_age:
99 merged_config['max_age'] = max(global_max_age, local_max_age)
100 else:
101 merged_config['max_age'] = global_max_age or local_max_age
102
103 # XXX bleh
104 updates_lol = run_hooks(hook_name, **merged_config)
105 source_obj = updates_lol[0]
106 updates += source_obj.poll(merged_config['limit'], merged_config['max_age'])
107
108 # Little optimization: maximum age effectively becomes the age of
109 # the oldest thing that would still appear on the page, as anything
110 # older would drop off the end no matter what.
111 # So sort by descending time and crop each iteration...
112 updates.sort(key=lambda obj: obj.time, reverse=True)
113 updates = updates[:global_limit]
114
115 if updates and len(updates) == global_limit:
116 global_max_age = updates[-1].time
117
118 c.updates = updates
119
120 return render('/index.mako')