Support stuff-other-than-updates.
[zzz-spline-frontpage.git] / splinext / frontpage / controllers / frontpage.py
1 import datetime
2 import logging
3
4 from pylons import config, request, response, session, tmpl_context as c, url
5 from pylons.controllers.util import abort, redirect
6 from routes import request_config
7 from sqlalchemy.orm.exc import NoResultFound
8
9 from spline.lib import helpers as h
10 from spline.lib.base import BaseController, render
11 from spline.lib.plugin.load import run_hooks
12 from spline.model import meta
13 from splinext.frontpage.sources import max_age_to_datetime
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 Plugins may also respond to the `frontpage_extras` hook with other
50 interesting things to put on the front page. There's no way to
51 customize the order of these extras or which appear and which don't, at
52 the moment. Such hooks should return an object with at least a
53 `template` attribute; the template will be called with the object
54 passed in as its `obj` argument.
55
56 Local plugins can override the fairly simple index.mako template to
57 customize the front page layout.
58 """
59
60 updates = []
61 global_limit = config['spline-frontpage.limit']
62 global_max_age = max_age_to_datetime(
63 config['spline-frontpage.max_age'])
64
65 c.sources = config['spline-frontpage.sources']
66 for source in c.sources:
67 new_updates = source.poll(global_limit, global_max_age)
68 updates.extend(new_updates)
69
70 # Little optimization: once there are global_limit items, anything
71 # older than the oldest cannot possibly make it onto the list. So,
72 # bump global_max_age to that oldest time if this is ever the case.
73 updates.sort(key=lambda obj: obj.time, reverse=True)
74 del updates[global_limit:]
75
76 if updates and len(updates) == global_limit:
77 global_max_age = updates[-1].time
78
79 # Find the oldest unseen item, to draw a divider after it.
80 # If this stays as None, the divider goes at the top
81 c.last_seen_item = None
82 # Could have a timestamp in the stash if this is a user, or in a cookie
83 # if this session has ever been logged out...
84 times = []
85 for source in (c.user.stash, request.cookies):
86 try:
87 times.append( int(source['frontpage-last-seen-time']) )
88 except (KeyError, ValueError):
89 pass
90
91 if times:
92 last_seen_time = datetime.datetime.fromtimestamp(max(times))
93 for update in updates:
94 if update.time > last_seen_time:
95 c.last_seen_item = update
96 else:
97 break
98
99 # Save ~now~ as the last-seen time
100 now = datetime.datetime.now().strftime('%s')
101 if c.user:
102 c.user.stash['frontpage-last-seen-time'] = now
103 meta.Session.add(c.user)
104 else:
105 response.set_cookie('frontpage-last-seen-time', now)
106
107 # Done! Feed to template
108 c.updates = updates
109
110 # Hook for non-update interesting things to put on the front page.
111 # This hook should return objects with a 'template' attribute, and
112 # whatever else they need
113 c.extras = run_hooks('frontpage_extras')
114
115 ret = render('/index.mako')
116
117 # Commit AFTER rendering the template! Committing invalidates
118 # everything in the session, undoing any eagerloading that may have
119 # been done by sources
120 meta.Session.commit()
121 return ret