a92750d71c15d014f68878eeb280cde14866b191
[indico-PayWay] / MaKaC / webinterface / rh / CFADisplay.py
1 # -*- coding: utf-8 -*-
2 ##
3 ##
4 ## This file is part of Indico.
5 ## Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN).
6 ##
7 ## 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 3 of the
10 ## License, or (at your option) any later version.
11 ##
12 ## 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 Indico;if not, see <http://www.gnu.org/licenses/>.
19 from flask import request
20
21 from textwrap import TextWrapper
22
23 from BTrees.IOBTree import IOBTree
24 from cStringIO import StringIO
25
26 import MaKaC.webinterface.urlHandlers as urlHandlers
27 import MaKaC.webinterface.mail as mail
28 import MaKaC.webinterface.pages.abstracts as abstracts
29 import MaKaC.review as review
30 from MaKaC.webinterface.rh.conferenceDisplay import RHConferenceBaseDisplay
31 from MaKaC.webinterface.rh.base import RHModificationBaseProtected
32 from indico.core.db import DBMgr
33 from MaKaC.review import AbstractStatusSubmitted, AbstractStatusAccepted
34 from MaKaC.PDFinterface.conference import AbstractToPDF, AbstractsToPDF
35 from MaKaC.errors import MaKaCError, NoReportError
36 import MaKaC.common.timezoneUtils as timezoneUtils
37 from MaKaC.i18n import _
38 from indico.util.i18n import i18nformat
39 from indico.web.flask.util import send_file
40 from MaKaC.webinterface.common.abstractDataWrapper import AbstractParam
41 from MaKaC.webinterface.rh.fileAccess import RHFileAccess
42 from MaKaC.webinterface.common.tools import cleanHTMLHeaderFilename
43 from MaKaC.PDFinterface.base import LatexRunner
44
45
46 class RHBaseCFA( RHConferenceBaseDisplay ):
47
48     def _processIfActive( self ):
49         """only override this method if the CFA must be activated for
50             carrying on the handler execution"""
51         return "cfa"
52
53     def _process( self ):
54         #if the CFA is not activated we show up a form informing about that.
55         #   This must be done at RH level because there can be some RH not
56         #   displaying pages.
57         cfaMgr = self._conf.getAbstractMgr()
58         if not cfaMgr.isActive() or not self._conf.hasEnabledSection("cfa"):
59             p = abstracts.WPCFAInactive( self, self._conf )
60             return p.display()
61         else:
62             return self._processIfActive()
63
64
65 class RHConferenceCFA( RHBaseCFA ):
66     _uh = urlHandlers.UHConferenceCFA
67
68     def _processIfActive( self ):
69         p = abstracts.WPConferenceCFA( self, self._target )
70         return p.display()
71
72
73 class RHAbstractSubmissionBase(RHBaseCFA):
74
75     def _checkProtection(self):
76         self._checkSessionUser()
77         RHBaseCFA._checkProtection(self)
78
79     def _processIfOpened(self):
80         """only override this method if the submission period must be opened
81             for the request handling"""
82         return "cfa opened"
83
84     def _processIfActive(self):
85         cfaMgr = self._conf.getAbstractMgr()
86         #if the user is in the autorized list, don't check period
87         if self._getUser() in cfaMgr.getAuthorizedSubmitterList():
88             return self._processIfOpened()
89         #if the submission period is not yet opened we show up a form informing
90         #   about that.
91         if timezoneUtils.nowutc() < cfaMgr.getStartSubmissionDate():
92         #if the submission period is already closed we show up a form informing
93         #   about that.
94             p = abstracts.WPCFANotYetOpened(self, self._conf)
95             return p.display()
96         elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate():
97             p = abstracts.WPCFAClosed(self, self._conf, False)
98             return p.display()
99         else:
100             return self._processIfOpened()
101
102
103 class _AbstractSubmissionNotification:
104
105     def __init__(self, abstract):
106         self._abstract = abstract
107         self._conf = self._abstract.getConference()
108         self._subject = _("Abstract submission confirmation (%s)") % self._conf.getTitle()
109
110     def getAttachments(self):
111         return []
112
113     def getSubject(self):
114         return self._subject
115
116     def setSubject(self, s):
117         self._subject = s
118
119     def getDestination(self):
120         return self._abstract.getSubmitter()
121
122     def getFromAddr(self):
123         return self._conf.getSupportInfo().getEmail(returnNoReply=True)
124
125     def getCCList(self):
126         return self._abstract.getOwner().getSubmissionNotification().getCCList()
127
128     def getToList(self):
129         return self._abstract.getOwner().getSubmissionNotification().getToList()
130
131     def getMsg(self):
132         primary_authors = []
133         for auth in self._abstract.getPrimaryAuthorList():
134             primary_authors.append("""%s (%s) <%s>""" % (auth.getFullName(), auth.getAffiliation(), auth.getEmail()))
135         co_authors = []
136         for auth in self._abstract.getCoAuthorList():
137             email = ""
138             if auth.getEmail() != "":
139                 email = " <%s>" % auth.getEmail()
140             co_authors.append("""%s (%s)%s""" % (auth.getFullName(), auth.getAffiliation(), email))
141         speakers = []
142         for spk in self._abstract.getSpeakerList():
143             speakers.append(spk.getFullName())
144         tracks = []
145         for track in self._abstract.getTrackListSorted():
146             tracks.append("""%s""" % track.getTitle())
147         tw = TextWrapper()
148         msg = [i18nformat("""_("Dear") %s,""") % self._abstract.getSubmitter().getStraightFullName()]
149         msg.append("")
150         msg.append(tw.fill(_("The submission of your abstract has been successfully processed!")))
151         msg.append("")
152         tw.break_long_words = False
153         msg.append(tw.fill(i18nformat("""_("Abstract submitted"):\n<%s>.""") % urlHandlers.UHUserAbstracts.getURL(self._conf)))
154         msg.append("")
155         msg.append(tw.fill(i18nformat("""_("Status of your abstract"):\n<%s>.""") % urlHandlers.UHAbstractDisplay.getURL(self._abstract)))
156         msg.append("")
157         tw.subsequent_indent = ""
158         msg.append(tw.fill(i18nformat("""_("See below a detailed summary of your submitted abstract"):""")))
159         msg.append("")
160         tw.subsequent_indent = " "*3
161         msg.append(tw.fill(i18nformat("""_("Conference"): %s""") % self._conf.getTitle()))
162         msg.append("")
163         msg.append(tw.fill(i18nformat("""_("Submitted by"): %s""") % self._abstract.getSubmitter().getFullName()))
164         msg.append("")
165         msg.append(tw.fill(i18nformat("""_("Submitted on"): %s""") % self._abstract.getSubmissionDate().strftime("%d %B %Y %H:%M")))
166         msg.append("")
167         msg.append(tw.fill(i18nformat("""_("Title"): %s""") % self._abstract.getTitle()))
168         msg.append("")
169         for f in self._conf.getAbstractMgr().getAbstractFieldsMgr().getFields():
170             msg.append(tw.fill(f.getCaption()))
171             msg.append(str(self._abstract.getField(f.getId())))
172             msg.append("")
173         msg.append(tw.fill(i18nformat("""_("Primary Authors"):""")))
174         msg += primary_authors
175         msg.append("")
176         msg.append(tw.fill(i18nformat("""_("Co-authors"):""")))
177         msg += co_authors
178         msg.append("")
179         msg.append(tw.fill(i18nformat("""_("Abstract presenters"):""")))
180         msg += speakers
181         msg.append("")
182         msg.append(tw.fill(i18nformat("""_("Track classification"):""")))
183         msg += tracks
184         msg.append("")
185         ctype = i18nformat("""--_("not specified")--""")
186         if self._abstract.getContribType() is not None:
187             ctype = self._abstract.getContribType().getName()
188         msg.append(tw.fill(i18nformat("""_("Presentation type"): %s""") % ctype))
189         msg.append("")
190         msg.append(tw.fill(i18nformat("""_("Comments"): %s""") % self._abstract.getComments()))
191         msg.append("")
192         return "\n".join(msg)
193
194     def getBody(self):
195         msg = self.getMsg()
196         return i18nformat("""
197 _("The following email has been sent to %s"):
198
199 ===
200
201 %s""") % (self.getDestination().getFullName(), msg)
202
203
204 class RHAbstractModificationAction(RHAbstractSubmissionBase, AbstractParam):
205
206     def __init__(self, req):
207         RHAbstractSubmissionBase.__init__(self, req)
208         AbstractParam.__init__(self)
209
210     def _checkParams( self, params ):
211         RHAbstractSubmissionBase._checkParams(self, params)
212         #if the user is not logged in we return immediately as this form needs
213         #   the user to be logged in and therefore all the checking below is not
214         #   necessary
215         if self._getUser() is None:
216             return
217
218         AbstractParam._checkParams(self, params, self._conf, request.content_length)
219
220
221 class RHAbstractSubmission( RHAbstractModificationAction ):
222     _uh = urlHandlers.UHAbstractSubmission
223
224     def _doValidate( self ):
225         #First, one must validate that the information is fine
226         errors = self._abstractData.check()
227         if errors:
228             p = abstracts.WPAbstractSubmission( self, self._target )
229             pars = self._abstractData.toDict()
230             pars["action"] = self._action
231             pars["attachments"] = []
232             return p.display( **pars )
233         #Then, we create the abstract object and set its data to the one
234         #   received
235         cfaMgr = self._target.getAbstractMgr()
236         abstract = cfaMgr.newAbstract( self._getUser() )
237         self._abstractData.setAbstractData(abstract)
238         #The commit must be forced before sending the confirmation
239         DBMgr.getInstance().commit()
240         #Email confirmation about the submission
241         mail.Mailer.send( _AbstractSubmissionNotification( abstract ), self._conf.getSupportInfo().getEmail(returnNoReply=True) )
242         #Email confirmation about the submission to coordinators
243         if cfaMgr.getSubmissionNotification().hasDestination():
244             asn=_AbstractSubmissionNotification( abstract )
245             asn.setSubject(_("[Indico] New abstract submission: %s")%asn.getDestination().getFullName())
246             mail.GenericMailer.send( asn )
247         #We must perform some actions: email warning to the authors
248         #Finally, we display a confirmation form
249         self._redirect( urlHandlers.UHAbstractSubmissionConfirmation.getURL( abstract ) )
250
251     def _processIfOpened( self ):
252         if self._action == "CANCEL":
253             self._redirect( urlHandlers.UHConferenceCFA.getURL( self._conf ) )
254         elif self._action == "VALIDATE":
255             return self._doValidate()
256         else:
257             p = abstracts.WPAbstractSubmission( self, self._target )
258             pars = self._abstractData.toDict()
259             return p.display( **pars )
260
261
262 class RHAbstractModify(RHAbstractModificationAction, RHModificationBaseProtected):
263     _uh = urlHandlers.UHAbstractModify
264
265     def _checkProtection(self):
266         RHModificationBaseProtected._checkProtection(self)
267
268     def _checkParams(self, params):
269         RHAbstractModificationAction._checkParams(self, params)
270         if self._getUser() is None:
271             return
272         if self._action == "":
273             #First call
274             afm = self._conf.getAbstractMgr().getAbstractFieldsMgr()
275             self._abstractData.title = self._abstract.getTitle()
276             for f in afm.getFields():
277                 id = f.getId()
278                 self._abstractData.setFieldValue(id, self._abstract.getField(id))
279             self._abstractData.type = self._abstract.getContribType()
280             trackIds = []
281             for track in self._abstract.getTrackListSorted():
282                 trackIds.append(track.getId())
283             self._abstractData.tracks = trackIds
284             self._abstractData.comments = self._abstract.getComments()
285
286     def _processIfActive(self):
287         #We overload this method to allow modification after the CFA is closed if the modification deadline is after the submission deadline
288         cfaMgr = self._conf.getAbstractMgr()
289         modifDeadLine = cfaMgr.getModificationDeadline()
290         if not modifDeadLine:
291             modifDeadLine = cfaMgr.getEndSubmissionDate()
292         #if the user is in the autorized list, don't check period
293         if self._getUser() in cfaMgr.getAuthorizedSubmitterList():
294             return self._processIfOpened()
295         #if the submission period is not yet opened we show up a form informing
296         #   about that.
297         if timezoneUtils.nowutc() < cfaMgr.getStartSubmissionDate():
298         #if the submission period is already closed we show up a form informing
299         #   about that.
300             p = abstracts.WPCFANotYetOpened(self, self._conf)
301             return p.display()
302         #elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate() :
303         elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate() and timezoneUtils.nowutc() > modifDeadLine:
304             p = abstracts.WPCFAClosed(self, self._conf, True)
305             return p.display()
306         else:
307             return self._processIfOpened()
308
309     def _doValidate(self):
310         #First, one must validate that the information is fine
311         errors = self._abstractData.check()
312         if errors:
313             p = abstracts.WPAbstractModify(self, self._target)
314             pars = self._abstractData.toDict()
315             pars["action"] = self._action
316             # restart the current value of the param attachments to show the existing files
317             pars["attachments"] = self._abstract.getAttachments().values()
318             return p.display(**pars)
319         self._abstract.clearAuthors()
320         self._abstractData.setAbstractData(self._abstract)
321         self._redirect(urlHandlers.UHAbstractDisplay.getURL(self._abstract))
322
323     def _processIfOpened(self):
324         #check if the modification period is not over or if the abstract
325         #   is in a different status than Submitted
326         if not self._conf.getAbstractMgr().inModificationPeriod() or \
327                 not isinstance(self._abstract.getCurrentStatus(),
328                                AbstractStatusSubmitted):
329             wp = abstracts.WPAbstractCannotBeModified(self, self._abstract)
330             return wp.display()
331         if self._action == "CANCEL":
332             self._redirect(urlHandlers.UHAbstractDisplay.getURL(self._abstract))
333         elif self._action == "VALIDATE":
334             return self._doValidate()
335         else:
336             p = abstracts.WPAbstractModify(self, self._target)
337             pars = self._abstractData.toDict()
338             pars["action"] = self._action
339             return p.display(**pars)
340
341
342 class RHUserAbstracts( RHAbstractSubmissionBase ):
343     _uh = urlHandlers.UHUserAbstracts
344
345     def _processIfActive( self ):
346         p = abstracts.WPUserAbstracts( self, self._conf )
347         return p.display()
348
349
350 class RHAbstractDisplayBase( RHAbstractSubmissionBase ):
351
352     def _checkParams( self, params ):
353         RHAbstractSubmissionBase._checkParams( self, params )
354         cfaMgr = self._conf.getAbstractMgr()
355         if not params.has_key("abstractId") and params.has_key("contribId"):
356             params["abstractId"] = params["contribId"]
357         self._abstract = self._target = cfaMgr.getAbstractById( params["abstractId"] )
358         if self._abstract == None:
359             raise NoReportError(_("The abstract you are trying to access does not exist or has been deleted"))
360
361
362 class RHAbstractSubmissionConfirmation( RHAbstractDisplayBase ):
363     _uh = urlHandlers.UHAbstractSubmissionConfirmation
364
365     def _processIfOpened( self ):
366         p = abstracts.WPAbstractSubmissionConfirmation( self, self._target )
367         return p.display()
368
369
370 class RHAbstractDisplay( RHAbstractDisplayBase ):
371     _uh = urlHandlers.UHAbstractDisplay
372
373     def _processIfActive( self ):
374         p = abstracts.WPAbstractDisplay( self, self._target )
375         return p.display()
376
377
378 class RHAbstractDisplayPDF( RHAbstractDisplayBase ):
379
380     def _checkProtection( self ):
381         RHConferenceBaseDisplay._checkProtection(self)
382         if not self._conf.getAbstractMgr().isActive() or not self._conf.hasEnabledSection("cfa"):
383             raise MaKaCError( _("The Call For Abstracts was disabled by the conference managers"))
384
385     def _process(self):
386         tz = timezoneUtils.DisplayTZ(self._aw, self._conf).getDisplayTZ()
387         filename = '%s - Abstract.pdf' % self._target.getTitle()
388         pdf = AbstractToPDF(self._target, tz=tz)
389         return send_file(filename, pdf.generate(), 'PDF')
390
391
392 class RHUserAbstractsPDF(RHAbstractSubmissionBase):
393
394     def _processIfActive(self):
395         tz = timezoneUtils.DisplayTZ(self._aw, self._conf).getDisplayTZ()
396         cfaMgr = self._conf.getAbstractMgr()
397         abstracts = set(cfaMgr.getAbstractListForAvatar(self._aw.getUser()))
398         abstracts |= set(cfaMgr.getAbstractListForAuthorEmail(self._aw.getUser().getEmail()))
399         self._abstractIds = sorted(abstract.getId() for abstract in abstracts)
400         if not self._abstractIds:
401             return _("No abstract to print")
402
403         filename = 'my-abstracts.pdf'
404         pdf = AbstractsToPDF(self._conf, self._abstractIds, tz=tz)
405         return send_file(filename, pdf.generate(), 'PDF')
406
407
408 class RHAbstractModificationBase(RHAbstractDisplayBase, RHModificationBaseProtected):
409
410     def _checkProtection(self):
411         RHModificationBaseProtected._checkProtection(self)
412
413     def _processIfActive(self):
414         #We overload this method to alow modification after the CFA is closed if the modification deadline is after the submission deadline
415         cfaMgr = self._conf.getAbstractMgr()
416         modifDeadLine = cfaMgr.getModificationDeadline()
417         if not modifDeadLine:
418             modifDeadLine = cfaMgr.getEndSubmissionDate()
419         #if the user is in the autorized list, don't check period
420         if self._getUser() in cfaMgr.getAuthorizedSubmitterList():
421             return self._processIfOpened()
422         #if the submission period is not yet opened we show up a form informing
423         #   about that.
424         if timezoneUtils.nowutc() < cfaMgr.getStartSubmissionDate():
425         #if the submission period is already closed we show up a form informing
426         #   about that.
427             p = abstracts.WPCFANotYetOpened(self, self._conf)
428             return p.display()
429         #elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate() :
430         elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate() and timezoneUtils.nowutc() > modifDeadLine:
431             p = abstracts.WPCFAClosed(self, self._conf, True)
432             return p.display()
433         else:
434             return self._processIfOpened()
435
436
437 class RHAbstractWithdraw( RHAbstractModificationBase ):
438     _uh = urlHandlers.UHAbstractWithdraw
439
440     def _checkParams( self, params ):
441         RHAbstractModificationBase._checkParams( self, params )
442         self._action = ""
443         self._comments = params.get( "comment", "" )
444         if params.has_key("OK"):
445             self._action = "WITHDRAW"
446         elif params.has_key("cancel"):
447             self._action = "CANCEL"
448
449     def _processIfOpened( self ):
450         if self._action == "CANCEL":
451             self._redirect( urlHandlers.UHAbstractDisplay.getURL( self._abstract ) )
452         elif self._action == "WITHDRAW":
453             if self._abstract.getCurrentStatus().__class__ not in \
454                     [review.AbstractStatusSubmitted,
455                     review.AbstractStatusUnderReview,
456                     review.AbstractStatusInConflict,
457                     review.AbstractStatusProposedToAccept,
458                     review.AbstractStatusProposedToReject]:
459                 raise MaKaCError( _("this abstract cannot be withdrawn, please contact the conference organisers in order to do so"))
460             self._abstract.withdraw(self._getUser(),self._comments)
461             self._redirect( urlHandlers.UHAbstractDisplay.getURL( self._abstract ) )
462         else:
463             wp = abstracts.WPAbstractWithdraw( self, self._abstract )
464             return wp.display()
465
466
467 class RHAbstractRecovery( RHAbstractModificationBase ):
468     _uh = urlHandlers.UHAbstractWithdraw
469
470     def _checkParams( self, params ):
471         RHAbstractModificationBase._checkParams( self, params )
472         self._action = ""
473         if params.has_key("OK"):
474             self._action = "RECOVER"
475         elif params.has_key("cancel"):
476             self._action = "CANCEL"
477
478     def _processIfOpened( self ):
479         if self._action == "CANCEL":
480             self._redirect( urlHandlers.UHAbstractDisplay.getURL( self._abstract ) )
481         elif self._action == "RECOVER":
482             status=self._abstract.getCurrentStatus()
483             if isinstance(status,review.AbstractStatusWithdrawn):
484                 if status.getResponsible()!=self._getUser():
485                     raise MaKaCError( _("you are not allowed to recover this abstract"))
486                 self._abstract.recover()
487             self._redirect( urlHandlers.UHAbstractDisplay.getURL( self._abstract ) )
488         else:
489             wp = abstracts.WPAbstractRecovery( self, self._abstract )
490             return wp.display()
491
492 class RHGetAttachedFile(RHFileAccess):
493
494     def _checkProtection( self ):
495         if not self._conf.getAbstractMgr().showAttachedFilesContribList():
496             # Same protection as the abstract
497             temptarget=self._target
498             self._target = self._target.getOwner()
499             RHFileAccess._checkProtection( self )
500             self._target = temptarget