1 # -*- coding: utf-8 -*-
4 ## This file is part of Indico.
5 ## Copyright (C) 2002 - 2014 European Organization for Nuclear Research (CERN).
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.
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.
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
21 from textwrap import TextWrapper
23 from BTrees.IOBTree import IOBTree
24 from cStringIO import StringIO
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
46 class RHBaseCFA( RHConferenceBaseDisplay ):
48 def _processIfActive( self ):
49 """only override this method if the CFA must be activated for
50 carrying on the handler execution"""
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
57 cfaMgr = self._conf.getAbstractMgr()
58 if not cfaMgr.isActive() or not self._conf.hasEnabledSection("cfa"):
59 p = abstracts.WPCFAInactive( self, self._conf )
62 return self._processIfActive()
65 class RHConferenceCFA( RHBaseCFA ):
66 _uh = urlHandlers.UHConferenceCFA
68 def _processIfActive( self ):
69 p = abstracts.WPConferenceCFA( self, self._target )
73 class RHAbstractSubmissionBase(RHBaseCFA):
75 def _checkProtection(self):
76 self._checkSessionUser()
77 RHBaseCFA._checkProtection(self)
79 def _processIfOpened(self):
80 """only override this method if the submission period must be opened
81 for the request handling"""
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
91 if timezoneUtils.nowutc() < cfaMgr.getStartSubmissionDate():
92 #if the submission period is already closed we show up a form informing
94 p = abstracts.WPCFANotYetOpened(self, self._conf)
96 elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate():
97 p = abstracts.WPCFAClosed(self, self._conf, False)
100 return self._processIfOpened()
103 class _AbstractSubmissionNotification:
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()
110 def getAttachments(self):
113 def getSubject(self):
116 def setSubject(self, s):
119 def getDestination(self):
120 return self._abstract.getSubmitter()
122 def getFromAddr(self):
123 return self._conf.getSupportInfo().getEmail(returnNoReply=True)
126 return self._abstract.getOwner().getSubmissionNotification().getCCList()
129 return self._abstract.getOwner().getSubmissionNotification().getToList()
133 for auth in self._abstract.getPrimaryAuthorList():
134 primary_authors.append("""%s (%s) <%s>""" % (auth.getFullName(), auth.getAffiliation(), auth.getEmail()))
136 for auth in self._abstract.getCoAuthorList():
138 if auth.getEmail() != "":
139 email = " <%s>" % auth.getEmail()
140 co_authors.append("""%s (%s)%s""" % (auth.getFullName(), auth.getAffiliation(), email))
142 for spk in self._abstract.getSpeakerList():
143 speakers.append(spk.getFullName())
145 for track in self._abstract.getTrackListSorted():
146 tracks.append("""%s""" % track.getTitle())
148 msg = [i18nformat("""_("Dear") %s,""") % self._abstract.getSubmitter().getStraightFullName()]
150 msg.append(tw.fill(_("The submission of your abstract has been successfully processed!")))
152 tw.break_long_words = False
153 msg.append(tw.fill(i18nformat("""_("Abstract submitted"):\n<%s>.""") % urlHandlers.UHUserAbstracts.getURL(self._conf)))
155 msg.append(tw.fill(i18nformat("""_("Status of your abstract"):\n<%s>.""") % urlHandlers.UHAbstractDisplay.getURL(self._abstract)))
157 tw.subsequent_indent = ""
158 msg.append(tw.fill(i18nformat("""_("See below a detailed summary of your submitted abstract"):""")))
160 tw.subsequent_indent = " "*3
161 msg.append(tw.fill(i18nformat("""_("Conference"): %s""") % self._conf.getTitle()))
163 msg.append(tw.fill(i18nformat("""_("Submitted by"): %s""") % self._abstract.getSubmitter().getFullName()))
165 msg.append(tw.fill(i18nformat("""_("Submitted on"): %s""") % self._abstract.getSubmissionDate().strftime("%d %B %Y %H:%M")))
167 msg.append(tw.fill(i18nformat("""_("Title"): %s""") % self._abstract.getTitle()))
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())))
173 msg.append(tw.fill(i18nformat("""_("Primary Authors"):""")))
174 msg += primary_authors
176 msg.append(tw.fill(i18nformat("""_("Co-authors"):""")))
179 msg.append(tw.fill(i18nformat("""_("Abstract presenters"):""")))
182 msg.append(tw.fill(i18nformat("""_("Track classification"):""")))
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))
190 msg.append(tw.fill(i18nformat("""_("Comments"): %s""") % self._abstract.getComments()))
192 return "\n".join(msg)
196 return i18nformat("""
197 _("The following email has been sent to %s"):
201 %s""") % (self.getDestination().getFullName(), msg)
204 class RHAbstractModificationAction(RHAbstractSubmissionBase, AbstractParam):
206 def __init__(self, req):
207 RHAbstractSubmissionBase.__init__(self, req)
208 AbstractParam.__init__(self)
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
215 if self._getUser() is None:
218 AbstractParam._checkParams(self, params, self._conf, request.content_length)
221 class RHAbstractSubmission( RHAbstractModificationAction ):
222 _uh = urlHandlers.UHAbstractSubmission
224 def _doValidate( self ):
225 #First, one must validate that the information is fine
226 errors = self._abstractData.check()
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
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 ) )
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()
257 p = abstracts.WPAbstractSubmission( self, self._target )
258 pars = self._abstractData.toDict()
259 return p.display( **pars )
262 class RHAbstractModify(RHAbstractModificationAction, RHModificationBaseProtected):
263 _uh = urlHandlers.UHAbstractModify
265 def _checkProtection(self):
266 RHModificationBaseProtected._checkProtection(self)
268 def _checkParams(self, params):
269 RHAbstractModificationAction._checkParams(self, params)
270 if self._getUser() is None:
272 if self._action == "":
274 afm = self._conf.getAbstractMgr().getAbstractFieldsMgr()
275 self._abstractData.title = self._abstract.getTitle()
276 for f in afm.getFields():
278 self._abstractData.setFieldValue(id, self._abstract.getField(id))
279 self._abstractData.type = self._abstract.getContribType()
281 for track in self._abstract.getTrackListSorted():
282 trackIds.append(track.getId())
283 self._abstractData.tracks = trackIds
284 self._abstractData.comments = self._abstract.getComments()
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
297 if timezoneUtils.nowutc() < cfaMgr.getStartSubmissionDate():
298 #if the submission period is already closed we show up a form informing
300 p = abstracts.WPCFANotYetOpened(self, self._conf)
302 #elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate() :
303 elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate() and timezoneUtils.nowutc() > modifDeadLine:
304 p = abstracts.WPCFAClosed(self, self._conf, True)
307 return self._processIfOpened()
309 def _doValidate(self):
310 #First, one must validate that the information is fine
311 errors = self._abstractData.check()
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))
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)
331 if self._action == "CANCEL":
332 self._redirect(urlHandlers.UHAbstractDisplay.getURL(self._abstract))
333 elif self._action == "VALIDATE":
334 return self._doValidate()
336 p = abstracts.WPAbstractModify(self, self._target)
337 pars = self._abstractData.toDict()
338 pars["action"] = self._action
339 return p.display(**pars)
342 class RHUserAbstracts( RHAbstractSubmissionBase ):
343 _uh = urlHandlers.UHUserAbstracts
345 def _processIfActive( self ):
346 p = abstracts.WPUserAbstracts( self, self._conf )
350 class RHAbstractDisplayBase( RHAbstractSubmissionBase ):
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"))
362 class RHAbstractSubmissionConfirmation( RHAbstractDisplayBase ):
363 _uh = urlHandlers.UHAbstractSubmissionConfirmation
365 def _processIfOpened( self ):
366 p = abstracts.WPAbstractSubmissionConfirmation( self, self._target )
370 class RHAbstractDisplay( RHAbstractDisplayBase ):
371 _uh = urlHandlers.UHAbstractDisplay
373 def _processIfActive( self ):
374 p = abstracts.WPAbstractDisplay( self, self._target )
378 class RHAbstractDisplayPDF( RHAbstractDisplayBase ):
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"))
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')
392 class RHUserAbstractsPDF(RHAbstractSubmissionBase):
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")
403 filename = 'my-abstracts.pdf'
404 pdf = AbstractsToPDF(self._conf, self._abstractIds, tz=tz)
405 return send_file(filename, pdf.generate(), 'PDF')
408 class RHAbstractModificationBase(RHAbstractDisplayBase, RHModificationBaseProtected):
410 def _checkProtection(self):
411 RHModificationBaseProtected._checkProtection(self)
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
424 if timezoneUtils.nowutc() < cfaMgr.getStartSubmissionDate():
425 #if the submission period is already closed we show up a form informing
427 p = abstracts.WPCFANotYetOpened(self, self._conf)
429 #elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate() :
430 elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate() and timezoneUtils.nowutc() > modifDeadLine:
431 p = abstracts.WPCFAClosed(self, self._conf, True)
434 return self._processIfOpened()
437 class RHAbstractWithdraw( RHAbstractModificationBase ):
438 _uh = urlHandlers.UHAbstractWithdraw
440 def _checkParams( self, params ):
441 RHAbstractModificationBase._checkParams( self, params )
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"
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 ) )
463 wp = abstracts.WPAbstractWithdraw( self, self._abstract )
467 class RHAbstractRecovery( RHAbstractModificationBase ):
468 _uh = urlHandlers.UHAbstractWithdraw
470 def _checkParams( self, params ):
471 RHAbstractModificationBase._checkParams( self, params )
473 if params.has_key("OK"):
474 self._action = "RECOVER"
475 elif params.has_key("cancel"):
476 self._action = "CANCEL"
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 ) )
489 wp = abstracts.WPAbstractRecovery( self, self._abstract )
492 class RHGetAttachedFile(RHFileAccess):
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