source: indico/indico/MaKaC/plugins/Collaboration/http_api.py @ 22a5d9

hello-world-walkthroughipv6v0.98-seriesv0.98.2v0.98.3v0.99v1.0v1.1
Last change on this file since 22a5d9 was 22a5d9, checked in by Jose Benito <jose.benito.gonzalez@…>, 15 months ago

[FIX] iCal VS export issues with contributions

  • Implemented CSBooking iCal Wrapper
  • All contributions exported where required
  • Updated utils for parsing title

Conflicts:

indico/MaKaC/plugins/Collaboration/http_api.py

  • Property mode set to 100644
File size: 12.5 KB
Line 
1# -*- coding: utf-8 -*-
2##
3##
4## This file is part of CDS Indico.
5## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2011 CERN.
6##
7## CDS Indico is free software; you can redistribute it and/or
8## modify it under the terms of the GNU General Public License as
9## published by the Free Software Foundation; either version 2 of the
10## License, or (at your option) any later version.
11##
12## CDS Indico is distributed in the hope that it will be useful, but
13## WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15## General Public License for more details.
16##
17## You should have received a copy of the GNU General Public License
18## along with CDS Indico; if not, write to the Free Software Foundation, Inc.,
19## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20
21import icalendar as ical
22from datetime import timedelta
23
24from indico.web.http_api import HTTPAPIHook, DataFetcher
25from indico.web.http_api.ical import ICalSerializer
26from indico.web.http_api.util import get_query_parameter
27from indico.web.http_api.responses import HTTPAPIError
28from indico.web.wsgi import webinterface_handler_config as apache
29from indico.util.fossilize import fossilize, IFossil
30from indico.util.fossilize.conversion import Conversion
31
32from MaKaC.webinterface.rh.collaboration import RCCollaborationAdmin
33from MaKaC.common.indexes import IndexesHolder
34from MaKaC.plugins.Collaboration.RecordingManager.common import createIndicoLink
35from MaKaC.plugins.Collaboration.collaborationTools import CollaborationTools
36from MaKaC.conference import ConferenceHolder
37from MaKaC.plugins.Collaboration.base import SpeakerStatusEnum
38from MaKaC.plugins.Collaboration.fossils import ICollaborationMetadataFossil
39
40
41globalHTTPAPIHooks = ['CollaborationAPIHook', 'CollaborationExportHook', 'VideoEventHook']
42
43
44def serialize_collaboration_alarm(fossil, now):
45    alarm = ical.Alarm()
46    trigger = timedelta(minutes=-int(fossil['alarm']))
47    alarm.set('trigger', trigger)
48    alarm.set('action', 'DISPLAY')
49    alarm.set('summary', VideoExportUtilities.getCondensedPrefix(fossil['type'],
50                                                                 fossil['status']) + fossil['title'].decode('utf-8'))
51    alarm.set('description', str(fossil['url']))
52
53    return alarm
54
55
56def serialize_collaboration(cal, fossil, now):
57    event = ical.Event()
58    url = str(fossil['url'])
59    event.set('uid', 'indico-collaboration-%s@cern.ch' % fossil['uniqueId'])
60    event.set('dtstamp', now)
61    event.set('dtstart', fossil['startDate'])
62    event.set('dtend', fossil['endDate'])
63    event.set('url', url)
64    event.set('categories', "VideoService - " + fossil['type'])
65    event.set('summary', VideoExportUtilities.getCondensedPrefix(fossil['type'],
66                                                                 fossil['status']) + fossil['title'].decode('utf-8'))
67    event.set('description', url)
68
69    # If there is an alarm required, add a subcomponent to the Event
70    if fossil.has_key('alarm'):
71        event.add_component(serialize_collaboration_alarm(fossil, now))
72    cal.add_component(event)
73
74
75# the iCal serializer needs some extra info on how to display things
76ICalSerializer.register_mapper('collaborationMetadata', serialize_collaboration)
77
78
79class VideoExportUtilities():
80
81    SERVICE_MAP = {
82        'CERNMCU': 'MCU',
83        'WebcastRequest': 'W',
84        'RecordingRequest': 'R'
85    }
86
87    @staticmethod
88    def getCondensedPrefix(service, status):
89        """ Condense down the summary lines based on the above dictionaries
90            for easier reading in calendar applications.
91        """
92        prefix = ""
93
94        if VideoExportUtilities.SERVICE_MAP.has_key(service):
95            prefix += VideoExportUtilities.SERVICE_MAP.get(service)
96        else:
97            prefix += service
98
99        if status:
100            prefix += '-' + status
101
102        return (prefix + ": ")
103
104
105class CollaborationAPIHook(HTTPAPIHook):
106    PREFIX = 'api'
107    TYPES = ('recordingManager',)
108    RE = r'createLink'
109    GUEST_ALLOWED = False
110    VALID_FORMATS = ('json',)
111    COMMIT = True
112    HTTP_POST = True
113
114    def _hasAccess(self, aw):
115        return RCCollaborationAdmin.hasRights(user=aw.getUser())
116
117    def _getParams(self):
118
119        super(CollaborationAPIHook, self)._getParams()
120        self._indicoID = get_query_parameter(self._queryParams, ['iid', 'indicoID'])
121        self._cdsID = get_query_parameter(self._queryParams, ['cid', 'cdsID'])
122
123    def api_recordingManager(self, aw):
124        if not self._indicoID or not self._cdsID:
125            raise HTTPAPIError('A required argument is missing.', apache.HTTP_BAD_REQUEST)
126
127        success = createIndicoLink(self._indicoID, self._cdsID)
128        return {'success': success}
129
130
131class CollaborationExportHook(HTTPAPIHook):
132    TYPES = ('eAgreements', )
133    RE = r'(?P<confId>\w+)'
134    GUEST_ALLOWED = False
135    VALID_FORMATS = ('json', 'jsonp', 'xml')
136
137    def _hasAccess(self, aw):
138        return RCCollaborationAdmin.hasRights(user=aw.getUser())
139
140    def _getParams(self):
141        super(CollaborationExportHook, self)._getParams()
142        self._conf = ConferenceHolder().getById(self._pathParams['confId'], True)
143        if not self._conf:
144            raise HTTPAPIError('Conference does not exist.', apache.HTTP_BAD_REQUEST)
145
146    def export_eAgreements(self, aw):
147        manager = self._conf.getCSBookingManager()
148        requestType = CollaborationTools.getRequestTypeUserCanManage(self._conf, aw.getUser())
149        contributions = manager.getContributionSpeakerByType(requestType)
150        for cont, speakers in contributions.items():
151            for spk in speakers:
152                sw = manager.getSpeakerWrapperByUniqueId('%s.%s' % (cont, spk.getId()))
153                if not sw:
154                    continue
155                signed = None
156                if sw.getStatus() in (SpeakerStatusEnum.FROMFILE, SpeakerStatusEnum.SIGNED):
157                    signed = True
158                elif sw.getStatus() == SpeakerStatusEnum.REFUSED:
159                    signed = False
160                yield {
161                    'confId': sw.getConference().getId(),
162                    'contrib': cont,
163                    'type': sw.getRequestType(),
164                    'status': sw.getStatus(),
165                    'signed': signed,
166                    'speaker': {
167                        'id': spk.getId(),
168                        'name': spk.getFullName(),
169                        'email': spk.getEmail()
170                    }
171                }
172
173
174class VideoEventHook(HTTPAPIHook):
175    """
176    This has been defined as a separate hook to CollaborationExportHook et al
177    due to the different input expected for both. It would be beneficial to
178    find a way to amalgamate the two at a later date.
179    """
180
181    TYPES = ('video', )
182    RE = r'(?P<idlist>\w+(?:-\w+)*)'
183    DEFAULT_DETAIL = 'all'
184    MAX_RECORDS = { # @TODO: Ascertain reasonable limits for each section.
185        'all': 100000,
186        'vidyo': 50000,
187        'webcast': 50000,
188        'mcu': 50000,
189        'evo': 50000
190    }
191    GUEST_ALLOWED = False
192
193    def _getParams(self):
194        super(VideoEventHook, self)._getParams()
195
196        """ In this case, idlist refers to the different indicies which can
197            be called, e.g: vidyo, evo, mcu etc.
198        """
199        self._idList = self._pathParams['idlist'].split('-')
200
201        if not self._queryParams.has_key('alarms'):
202            self._alarms = None
203        else:
204            self._alarms = get_query_parameter(self._queryParams, ['alarms'], 0, True)
205
206    def export_video(self, aw):
207        expInt = VideoEventFetcher(aw, self)
208        return expInt.video(self._idList, self._alarms)
209
210
211class VideoEventFetcher(DataFetcher):
212    DETAIL_INTERFACES = {
213        'all' : ICollaborationMetadataFossil
214    }
215    ID_TO_IDX = {
216        'all' : 'all',
217        'vidyo' : 'Vidyo',
218        'webcast' : 'WebcastRequest',
219        'recording' : 'RecordingRequest',
220        'mcu' : 'CERNMCU',
221        'evo' : 'EVO'
222    }
223
224    def __init__(self, aw, hook):
225        super(VideoEventFetcher, self).__init__(aw, hook)
226        self._alarm = None
227
228    def _postprocess(self, obj, fossil, iface):
229        if self._alarm is not None:
230            fossil['alarm'] = self._alarm
231
232        return fossil
233
234    def video(self, idList, alarm = None):
235
236        idx = IndexesHolder().getById('collaboration');
237        bookings = []
238        dateFormat = '%d/%m/%Y'
239        self._alarm = alarm
240
241        for vsid in idList:
242            tempBookings = idx.getBookings(self.ID_TO_IDX[vsid], "conferenceStartDate",
243                                           self._orderBy, self._fromDT, self._toDT,
244                                           'UTC', False, None, None, False, dateFormat)
245            bookings.extend(tempBookings.getResults())
246
247        def _iter_bookings(objs):
248            for obj in objs:
249                for bk in obj[1]:
250                    bk._conf = obj[0] # Ensure all CSBookings are aware of their Conference
251
252                    """ This is for plugins whose structure include 'talkSelected',
253                        examples of which in CERN Indico being WebcastRequest and
254                        RecordingRequest.
255                    """
256                    if bk.hasTalkSelection():
257                        ts = bk.getTalkSelectionList()
258                        contributions = []
259
260                        if ts is None: # No individual talks, therefore an event for every contribution
261                            contributions = bk._conf.getContributionList()
262                        else:
263                            for contribId in ts:
264                                tempContrib = bk._conf.getContributionById(contribId)
265                                contributions.append(tempContrib)
266
267                        if len(contributions) == 0: # If we are here, no contributions but a request exists.
268                            bk.setStartDate(bk._conf.getStartDate())
269                            bk.setEndDate(bk._conf.getEndDate())
270                            yield bk
271                        else: # Contributions is the list of all to be exported now
272                            for contrib in contributions:
273                                # Wrap the CSBooking object for export
274                                bkw = CSBookingContributionWrapper(bk, contrib)
275
276                                if contrib.isScheduled():
277                                    bkw.setStartDate(contrib.getStartDate())
278                                    bkw.setEndDate(contrib.getEndDate())
279                                else:
280                                    bkw.setStartDate(bk._conf.getStartDate())
281                                    bkw.setEndDate(bk._conf.getEndDate())
282
283                                yield bkw
284                    else:
285                        yield bk
286
287        """ Simple filter, as this method can return None for Pending and True
288            for accepted, both are valid booking statuses for exporting.
289        """
290        def filter(obj):
291            return obj.getAcceptRejectStatus() is not False
292
293        iface = self.DETAIL_INTERFACES.get('all')
294
295        for booking in self._process(_iter_bookings(bookings), filter, iface):
296            yield booking
297
298
299class CSBookingContributionWrapper():
300    """ This wrapper is required in order to export each contribution through
301        the iCal interface, giving each object its own unique address and
302        allows for the construction of iCal specific unique identifiers.
303    """
304
305    _orig = None
306    _contrib = None
307    _startDate = None
308    _endDate = None
309
310    def __init__(self, booking, contrib):
311        self._orig = booking
312        self._contrib = contrib
313
314    def __getattr__(self, name):
315        """ Checks for overridden method in this class, if not present then
316            delegates to original CSBooking object.
317        """
318
319        if name in self.__dict__:
320            return getattr(self, name)
321
322        return getattr(self._orig, name)
323
324    def _getShortTypeSuffix(self):
325        type = self._orig.getType()
326        return type.lower()[0:2]
327
328    def getUniqueId(self):
329        """ Each contribution will need a unique UID for each iCal event,
330            as RecordingRequests and WebcastRequests would share the same
331            UID per contribution, append a suffix denoting the type.
332        """
333        return self._contrib.getUniqueId() + self._getShortTypeSuffix()
334
335    def getTitle(self):
336        return self._orig._conf.getTitle() + ' - ' + self._contrib.getTitle()
337
338    def setStartDate(self, date):
339        self._startDate = date
340
341    def getStartDate(self):
342        return self._startDate
343
344    def setEndDate(self, date):
345        self._endDate = date
346
347    def getEndDate(self):
348        return self._endDate
Note: See TracBrowser for help on using the repository browser.