1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
18 *Upcoming events* module
21 import os
, datetime
, pickle
, operator
, time
22 from pytz
import timezone
23 from persistent
import Persistent
25 from MaKaC
.common
.cache
import GenericCache
26 from MaKaC
.common
import timezoneUtils
, indexes
28 from MaKaC
.common
.fossilize
import Fossilizable
, fossilizes
29 from MaKaC
.fossils
.modules
import IObservedObjectFossil
30 import MaKaC
.conference
as conference
32 from indico
.core
.logger
import Logger
33 from MaKaC
.common
.utils
import formatTime
, formatDateTime
34 import MaKaC
.webinterface
.wcomponents
as wcomponents
36 from indico
.util
.i18n
import _
37 from indico
.modules
import Module
40 class ObservedObject(Persistent
, Fossilizable
):
42 fossilizes(IObservedObjectFossil
)
44 def __init__(self
, obj
, weight
, advertisingDelta
):
46 obj - the object to encapsulate
47 weight - the weight that is associated with
53 self
.advertisingDelta
= advertisingDelta
61 def getAdvertisingDelta(self
):
62 return self
.advertisingDelta
64 class UpcomingEventsModule(Module
):
66 This module contains all the information and logics that support
67 the *upcoming events* feature.
70 id = "upcoming_events"
74 # id, weight dictionary = id is the category id and
75 # weight determines the position of the categories'
79 self
._ttl
= datetime
.timedelta(minutes
=5)
83 return GenericCache('UpcomingEvents')
85 def setCacheTTL(self
, ttl
):
87 self
._cache
.delete('public')
89 def getCacheTTL(self
):
90 if not hasattr(self
, '_ttl'):
91 self
._ttl
= datetime
.timedelta(minutes
=5)
94 def setNumberItems(self
, number
):
95 self
._maxEvents
= number
96 self
._cache
.delete('public')
98 def getNumberItems(self
):
99 return self
._maxEvents
101 def addObject(self
, object, weight
, delta
):
103 category - Category object
104 weight - integer (negative allowed), zero by default
106 self
._objects
.append(ObservedObject(object, weight
, delta
))
107 self
._cache
.delete('public')
110 def removeObject(self
, obj
):
112 for observed
in self
._objects
:
113 if observed
.getObject() == obj
:
118 raise Exception('Element not in list')
119 self
._objects
.remove(element
)
120 self
._cache
.delete('public')
123 def hasObject(self
, obj
):
124 for observed
in self
._objects
:
125 if observed
.getObject() == obj
:
131 def getObjectList(self
):
134 def processEvent(self
, date
, eventObj
, obj
, objDict
):
136 if (not eventObj
.hasAnyProtection() and
137 (date
> (eventObj
.getStartDate() - obj
.getAdvertisingDelta()))):
138 weight
= float(obj
.getWeight())
139 if not objDict
.has_key(weight
):
142 objDict
[weight
].append(eventObj
)
145 def _processEventDisplay(self
, event
):
147 dateTime
= event
.getStartDate()
149 if dateTime
< timezoneUtils
.nowutc():
151 # return end date instead (for 'ongoing till...')
152 dateTime
= event
.getEndDate()
153 elif dateTime
.date() == timezoneUtils
.nowutc().date():
158 return (status
, dateTime
, event
.getTitle(), event
.getId())
160 def getUpcomingEventList(self
):
162 # check if there's a valid cached copy first
163 resultList
= self
._cache
.get('public')
165 if resultList
is None:
166 resultList
= map(self
._processEventDisplay
, self
._generateList
())
167 self
._cache
.set('public', resultList
, self
.getCacheTTL())
171 def _generateList(self
, date
=None):
174 date
= timezoneUtils
.nowutc()
176 categDateIdx
= indexes
.IndexesHolder().getById('categoryDate')
180 for obj
in self
._objects
:
181 wrappedObj
= obj
.getObject()
182 if isinstance(wrappedObj
, conference
.Conference
):
183 self
.processEvent(date
, wrappedObj
, obj
, objDict
)
184 elif isinstance(wrappedObj
, conference
.Category
):
185 events
= categDateIdx
.getObjectsIn(
187 date
, date
+ obj
.getAdvertisingDelta())
189 self
.processEvent(date
, conf
, obj
, objDict
)
192 keys
= objDict
.keys()
193 keys
.sort(reverse
=True)
196 sortedEvents
= objDict
[weight
]
197 sortedEvents
.sort(key
=operator
.attrgetter('startDate'))
198 for elem
in sortedEvents
:
199 resultList
.append(elem
)
200 if len(resultList
) == self
._maxEvents
:
203 # sort again, so that the final result is an ordered list
204 # that is suitable for display
205 resultList
.sort(key
=operator
.attrgetter('startDate'))
207 Logger
.get('upcoming_events').info("Regenerated upcoming event cache")
212 class WUpcomingEvents(wcomponents
.WTemplated
):
214 def formatDateTime(self
, dateTime
):
215 now
= timezoneUtils
.nowutc().astimezone(self
._timezone
)
217 if dateTime
.date() == now
.date():
218 return _("today") + " " + formatTime(dateTime
.time())
219 elif dateTime
.date() == (now
+ datetime
.timedelta(days
=1)).date():
220 return _("tomorrow") + " " + formatTime(dateTime
.time())
221 elif dateTime
< (now
+ datetime
.timedelta(days
=6)):
222 return formatDateTime(dateTime
, format
="EEEE H:mm")
223 elif dateTime
.date().year
== now
.date().year
:
224 return formatDateTime(dateTime
, format
="d MMM")
226 return formatDateTime(dateTime
, format
="d MMM yyyy")
228 def _getUpcomingEvents(self
):
229 # Just convert UTC to display timezone
231 return map(lambda x
: (x
[0], x
[1].astimezone(self
._timezone
), x
[2], x
[3]),
235 vars = wcomponents
.WTemplated
.getVars(self
)
236 vars['upcomingEvents'] = self
._getUpcomingEvents
()
239 def __init__(self
, timezone
, upcoming_list
):
240 self
._timezone
= timezone
241 self
._list
= upcoming_list
242 wcomponents
.WTemplated
.__init
__(self
)