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/>.
16 from flask
import request
18 from textwrap
import TextWrapper
20 from BTrees
.IOBTree
import IOBTree
21 from cStringIO
import StringIO
23 import MaKaC
.webinterface
.urlHandlers
as urlHandlers
24 import MaKaC
.webinterface
.mail
as mail
25 import MaKaC
.webinterface
.pages
.abstracts
as abstracts
26 import MaKaC
.review
as review
27 from MaKaC
.webinterface
.rh
.conferenceDisplay
import RHConferenceBaseDisplay
28 from MaKaC
.webinterface
.rh
.base
import RHModificationBaseProtected
29 from indico
.core
.db
import DBMgr
30 from MaKaC
.review
import AbstractStatusSubmitted
, AbstractStatusAccepted
31 from MaKaC
.PDFinterface
.conference
import AbstractToPDF
, AbstractsToPDF
32 from MaKaC
.errors
import MaKaCError
, NoReportError
33 import MaKaC
.common
.timezoneUtils
as timezoneUtils
34 from MaKaC
.i18n
import _
35 from indico
.util
.i18n
import i18nformat
36 from indico
.web
.flask
.util
import send_file
37 from MaKaC
.webinterface
.common
.abstractDataWrapper
import AbstractParam
38 from MaKaC
.webinterface
.rh
.fileAccess
import RHFileAccess
39 from MaKaC
.webinterface
.common
.tools
import cleanHTMLHeaderFilename
40 from MaKaC
.PDFinterface
.base
import LatexRunner
43 class RHBaseCFA( RHConferenceBaseDisplay
):
45 def _processIfActive( self
):
46 """only override this method if the CFA must be activated for
47 carrying on the handler execution"""
51 #if the CFA is not activated we show up a form informing about that.
52 # This must be done at RH level because there can be some RH not
54 cfaMgr
= self
._conf
.getAbstractMgr()
55 if not cfaMgr
.isActive() or not self
._conf
.hasEnabledSection("cfa"):
56 p
= abstracts
.WPCFAInactive( self
, self
._conf
)
59 return self
._processIfActive
()
62 class RHConferenceCFA( RHBaseCFA
):
63 _uh
= urlHandlers
.UHConferenceCFA
65 def _processIfActive( self
):
66 p
= abstracts
.WPConferenceCFA( self
, self
._target
)
70 class RHAbstractSubmissionBase(RHBaseCFA
):
72 def _checkProtection(self
):
73 self
._checkSessionUser
()
74 RHBaseCFA
._checkProtection
(self
)
76 def _processIfOpened(self
):
77 """only override this method if the submission period must be opened
78 for the request handling"""
81 def _processIfActive(self
):
82 cfaMgr
= self
._conf
.getAbstractMgr()
83 #if the user is in the autorized list, don't check period
84 if self
._getUser
() in cfaMgr
.getAuthorizedSubmitterList():
85 return self
._processIfOpened
()
86 #if the submission period is not yet opened we show up a form informing
88 if timezoneUtils
.nowutc() < cfaMgr
.getStartSubmissionDate():
89 #if the submission period is already closed we show up a form informing
91 p
= abstracts
.WPCFANotYetOpened(self
, self
._conf
)
93 elif timezoneUtils
.nowutc() > cfaMgr
.getEndSubmissionDate():
94 p
= abstracts
.WPCFAClosed(self
, self
._conf
, False)
97 return self
._processIfOpened
()
100 class _AbstractSubmissionNotification
:
102 def __init__(self
, abstract
):
103 self
._abstract
= abstract
104 self
._conf
= self
._abstract
.getConference()
105 self
._subject
= _("Abstract submission confirmation (%s)") % self
._conf
.getTitle()
107 def getAttachments(self
):
110 def getSubject(self
):
113 def setSubject(self
, s
):
116 def getDestination(self
):
117 return self
._abstract
.getSubmitter()
119 def getFromAddr(self
):
120 return self
._conf
.getSupportInfo().getEmail(returnNoReply
=True)
123 return self
._abstract
.getOwner().getSubmissionNotification().getCCList()
126 return self
._abstract
.getOwner().getSubmissionNotification().getToList()
130 for auth
in self
._abstract
.getPrimaryAuthorList():
131 primary_authors
.append("""%s (%s) <%s>""" % (auth
.getFullName(), auth
.getAffiliation(), auth
.getEmail()))
133 for auth
in self
._abstract
.getCoAuthorList():
135 if auth
.getEmail() != "":
136 email
= " <%s>" % auth
.getEmail()
137 co_authors
.append("""%s (%s)%s""" % (auth
.getFullName(), auth
.getAffiliation(), email
))
139 for spk
in self
._abstract
.getSpeakerList():
140 speakers
.append(spk
.getFullName())
142 for track
in self
._abstract
.getTrackListSorted():
143 tracks
.append("""%s""" % track
.getTitle())
144 msg
= [i18nformat("""_("Dear") %s,""") % self
._abstract
.getSubmitter().getStraightFullName()]
146 msg
.append(_("The submission of your abstract has been successfully processed."))
148 msg
.append(i18nformat("""_("Abstract submitted"):\n<%s>.""") % urlHandlers
.UHUserAbstracts
.getURL(self
._conf
))
150 msg
.append(i18nformat("""_("Status of your abstract"):\n<%s>.""") % urlHandlers
.UHAbstractDisplay
.getURL(self
._abstract
))
152 msg
.append(i18nformat("""_("See below a detailed summary of your submitted abstract"):"""))
154 msg
.append(i18nformat("""_("Conference"): %s""") % self
._conf
.getTitle())
156 msg
.append(i18nformat("""_("Submitted by"): %s""") % self
._abstract
.getSubmitter().getFullName())
158 msg
.append(i18nformat("""_("Submitted on"): %s""") % self
._abstract
.getSubmissionDate().strftime("%d %B %Y %H:%M"))
160 msg
.append(i18nformat("""_("Title"): %s""") % self
._abstract
.getTitle())
162 for f
in self
._conf
.getAbstractMgr().getAbstractFieldsMgr().getFields():
163 msg
.append(f
.getCaption())
164 msg
.append(str(self
._abstract
.getField(f
.getId())))
166 msg
.append(i18nformat("""_("Primary Authors"):"""))
167 msg
+= primary_authors
169 msg
.append(i18nformat("""_("Co-authors"):"""))
172 msg
.append(i18nformat("""_("Abstract presenters"):"""))
175 msg
.append(i18nformat("""_("Track classification"):"""))
178 ctype
= i18nformat("""--_("not specified")--""")
179 if self
._abstract
.getContribType() is not None:
180 ctype
= self
._abstract
.getContribType().getName()
181 msg
.append(i18nformat("""_("Presentation type"): %s""") % ctype
)
183 msg
.append(i18nformat("""_("Comments"): %s""") % self
._abstract
.getComments())
185 return "\n".join(msg
)
189 return i18nformat("""
190 _("The following email has been sent to %s"):
194 %s""") % (self
.getDestination().getFullName(), msg
)
197 class RHAbstractModificationAction(RHAbstractSubmissionBase
, AbstractParam
):
200 RHAbstractSubmissionBase
.__init
__(self
)
201 AbstractParam
.__init
__(self
)
203 def _checkParams( self
, params
):
204 RHAbstractSubmissionBase
._checkParams
(self
, params
)
205 #if the user is not logged in we return immediately as this form needs
206 # the user to be logged in and therefore all the checking below is not
208 if self
._getUser
() is None:
211 AbstractParam
._checkParams
(self
, params
, self
._conf
, request
.content_length
)
214 class RHAbstractSubmission( RHAbstractModificationAction
):
215 _uh
= urlHandlers
.UHAbstractSubmission
217 def _doValidate( self
):
218 #First, one must validate that the information is fine
219 errors
= self
._abstractData
.check()
221 p
= abstracts
.WPAbstractSubmission( self
, self
._target
)
222 pars
= self
._abstractData
.toDict()
223 pars
["action"] = self
._action
224 pars
["attachments"] = []
225 return p
.display( **pars
)
226 #Then, we create the abstract object and set its data to the one
228 cfaMgr
= self
._target
.getAbstractMgr()
229 abstract
= cfaMgr
.newAbstract( self
._getUser
() )
230 self
._abstractData
.setAbstractData(abstract
)
231 #The commit must be forced before sending the confirmation
232 DBMgr
.getInstance().commit()
233 #Email confirmation about the submission
234 mail
.Mailer
.send( _AbstractSubmissionNotification( abstract
), self
._conf
.getSupportInfo().getEmail(returnNoReply
=True) )
235 #Email confirmation about the submission to coordinators
236 if cfaMgr
.getSubmissionNotification().hasDestination():
237 asn
=_AbstractSubmissionNotification( abstract
)
238 asn
.setSubject(_("[Indico] New abstract submission: %s")%asn
.getDestination().getFullName())
239 mail
.GenericMailer
.send( asn
)
240 #We must perform some actions: email warning to the authors
241 #Finally, we display a confirmation form
242 self
._redirect
( urlHandlers
.UHAbstractSubmissionConfirmation
.getURL( abstract
) )
244 def _processIfOpened( self
):
245 if self
._action
== "CANCEL":
246 self
._redirect
( urlHandlers
.UHConferenceCFA
.getURL( self
._conf
) )
247 elif self
._action
== "VALIDATE":
248 return self
._doValidate
()
250 p
= abstracts
.WPAbstractSubmission( self
, self
._target
)
251 pars
= self
._abstractData
.toDict()
252 return p
.display( **pars
)
255 class RHAbstractModify(RHAbstractModificationAction
, RHModificationBaseProtected
):
256 _uh
= urlHandlers
.UHAbstractModify
258 def _checkProtection(self
):
259 RHModificationBaseProtected
._checkProtection
(self
)
261 def _checkParams(self
, params
):
262 RHAbstractModificationAction
._checkParams
(self
, params
)
263 if self
._getUser
() is None:
265 if self
._action
== "":
267 afm
= self
._conf
.getAbstractMgr().getAbstractFieldsMgr()
268 self
._abstractData
.title
= self
._abstract
.getTitle()
269 for f
in afm
.getFields():
271 self
._abstractData
.setFieldValue(id, self
._abstract
.getField(id))
272 self
._abstractData
.type = self
._abstract
.getContribType()
274 for track
in self
._abstract
.getTrackListSorted():
275 trackIds
.append(track
.getId())
276 self
._abstractData
.tracks
= trackIds
277 self
._abstractData
.comments
= self
._abstract
.getComments()
279 def _processIfActive(self
):
280 #We overload this method to allow modification after the CFA is closed if the modification deadline is after the submission deadline
281 cfaMgr
= self
._conf
.getAbstractMgr()
282 modifDeadLine
= cfaMgr
.getModificationDeadline()
283 if not modifDeadLine
:
284 modifDeadLine
= cfaMgr
.getEndSubmissionDate()
285 #if the user is in the autorized list, don't check period
286 if self
._getUser
() in cfaMgr
.getAuthorizedSubmitterList():
287 return self
._processIfOpened
()
288 #if the submission period is not yet opened we show up a form informing
290 if timezoneUtils
.nowutc() < cfaMgr
.getStartSubmissionDate():
291 #if the submission period is already closed we show up a form informing
293 p
= abstracts
.WPCFANotYetOpened(self
, self
._conf
)
295 #elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate() :
296 elif timezoneUtils
.nowutc() > cfaMgr
.getEndSubmissionDate() and timezoneUtils
.nowutc() > modifDeadLine
:
297 p
= abstracts
.WPCFAClosed(self
, self
._conf
, True)
300 return self
._processIfOpened
()
302 def _doValidate(self
):
303 #First, one must validate that the information is fine
304 errors
= self
._abstractData
.check()
306 p
= abstracts
.WPAbstractModify(self
, self
._target
)
307 pars
= self
._abstractData
.toDict()
308 pars
["action"] = self
._action
309 # restart the current value of the param attachments to show the existing files
310 pars
["attachments"] = self
._abstract
.getAttachments().values()
311 return p
.display(**pars
)
312 self
._abstract
.clearAuthors()
313 self
._abstractData
.setAbstractData(self
._abstract
)
314 self
._redirect
(urlHandlers
.UHAbstractDisplay
.getURL(self
._abstract
))
316 def _processIfOpened(self
):
317 #check if the modification period is not over or if the abstract
318 # is in a different status than Submitted
319 if not self
._conf
.getAbstractMgr().inModificationPeriod() or \
320 not isinstance(self
._abstract
.getCurrentStatus(),
321 AbstractStatusSubmitted
):
322 wp
= abstracts
.WPAbstractCannotBeModified(self
, self
._abstract
)
324 if self
._action
== "CANCEL":
325 self
._redirect
(urlHandlers
.UHAbstractDisplay
.getURL(self
._abstract
))
326 elif self
._action
== "VALIDATE":
327 return self
._doValidate
()
329 p
= abstracts
.WPAbstractModify(self
, self
._target
)
330 pars
= self
._abstractData
.toDict()
331 pars
["action"] = self
._action
332 return p
.display(**pars
)
335 class RHUserAbstracts( RHAbstractSubmissionBase
):
336 _uh
= urlHandlers
.UHUserAbstracts
338 def _processIfActive( self
):
339 p
= abstracts
.WPUserAbstracts( self
, self
._conf
)
343 class RHAbstractDisplayBase( RHAbstractSubmissionBase
):
345 def _checkParams( self
, params
):
346 RHAbstractSubmissionBase
._checkParams
( self
, params
)
347 cfaMgr
= self
._conf
.getAbstractMgr()
348 if not params
.has_key("abstractId") and params
.has_key("contribId"):
349 params
["abstractId"] = params
["contribId"]
350 self
._abstract
= self
._target
= cfaMgr
.getAbstractById( params
["abstractId"] )
351 if self
._abstract
== None:
352 raise NoReportError(_("The abstract you are trying to access does not exist or has been deleted"))
355 class RHAbstractSubmissionConfirmation( RHAbstractDisplayBase
):
356 _uh
= urlHandlers
.UHAbstractSubmissionConfirmation
358 def _processIfOpened( self
):
359 p
= abstracts
.WPAbstractSubmissionConfirmation( self
, self
._target
)
363 class RHAbstractDisplay( RHAbstractDisplayBase
):
364 _uh
= urlHandlers
.UHAbstractDisplay
366 def _processIfActive( self
):
367 p
= abstracts
.WPAbstractDisplay( self
, self
._target
)
371 class RHAbstractDisplayPDF( RHAbstractDisplayBase
):
373 def _checkProtection( self
):
374 RHConferenceBaseDisplay
._checkProtection
(self
)
375 if not self
._conf
.getAbstractMgr().isActive() or not self
._conf
.hasEnabledSection("cfa"):
376 raise MaKaCError( _("The Call For Abstracts was disabled by the conference managers"))
379 tz
= timezoneUtils
.DisplayTZ(self
._aw
, self
._conf
).getDisplayTZ()
380 filename
= '%s - Abstract.pdf' % self
._target
.getTitle()
381 pdf
= AbstractToPDF(self
._target
, tz
=tz
)
382 return send_file(filename
, pdf
.generate(), 'PDF')
385 class RHUserAbstractsPDF(RHAbstractSubmissionBase
):
387 def _processIfActive(self
):
388 tz
= timezoneUtils
.DisplayTZ(self
._aw
, self
._conf
).getDisplayTZ()
389 cfaMgr
= self
._conf
.getAbstractMgr()
390 abstracts
= set(cfaMgr
.getAbstractListForAvatar(self
._aw
.getUser()))
391 abstracts |
= set(cfaMgr
.getAbstractListForAuthorEmail(self
._aw
.getUser().getEmail()))
392 self
._abstractIds
= sorted(abstract
.getId() for abstract
in abstracts
)
393 if not self
._abstractIds
:
394 return _("No abstract to print")
396 filename
= 'my-abstracts.pdf'
397 pdf
= AbstractsToPDF(self
._conf
, self
._abstractIds
, tz
=tz
)
398 return send_file(filename
, pdf
.generate(), 'PDF')
401 class RHAbstractModificationBase(RHAbstractDisplayBase
, RHModificationBaseProtected
):
403 def _checkProtection(self
):
404 RHModificationBaseProtected
._checkProtection
(self
)
406 def _processIfActive(self
):
407 #We overload this method to alow modification after the CFA is closed if the modification deadline is after the submission deadline
408 cfaMgr
= self
._conf
.getAbstractMgr()
409 modifDeadLine
= cfaMgr
.getModificationDeadline()
410 if not modifDeadLine
:
411 modifDeadLine
= cfaMgr
.getEndSubmissionDate()
412 #if the user is in the autorized list, don't check period
413 if self
._getUser
() in cfaMgr
.getAuthorizedSubmitterList():
414 return self
._processIfOpened
()
415 #if the submission period is not yet opened we show up a form informing
417 if timezoneUtils
.nowutc() < cfaMgr
.getStartSubmissionDate():
418 #if the submission period is already closed we show up a form informing
420 p
= abstracts
.WPCFANotYetOpened(self
, self
._conf
)
422 #elif timezoneUtils.nowutc() > cfaMgr.getEndSubmissionDate() :
423 elif timezoneUtils
.nowutc() > cfaMgr
.getEndSubmissionDate() and timezoneUtils
.nowutc() > modifDeadLine
:
424 p
= abstracts
.WPCFAClosed(self
, self
._conf
, True)
427 return self
._processIfOpened
()
430 class RHAbstractWithdraw( RHAbstractModificationBase
):
431 _uh
= urlHandlers
.UHAbstractWithdraw
433 def _checkParams( self
, params
):
434 RHAbstractModificationBase
._checkParams
( self
, params
)
436 self
._comments
= params
.get( "comment", "" )
437 if params
.has_key("OK"):
438 self
._action
= "WITHDRAW"
439 elif params
.has_key("cancel"):
440 self
._action
= "CANCEL"
442 def _processIfOpened( self
):
443 if self
._action
== "CANCEL":
444 self
._redirect
( urlHandlers
.UHAbstractDisplay
.getURL( self
._abstract
) )
445 elif self
._action
== "WITHDRAW":
446 if self
._abstract
.getCurrentStatus().__class
__ not in \
447 [review
.AbstractStatusSubmitted
,
448 review
.AbstractStatusUnderReview
,
449 review
.AbstractStatusInConflict
,
450 review
.AbstractStatusProposedToAccept
,
451 review
.AbstractStatusProposedToReject
]:
452 raise MaKaCError( _("this abstract cannot be withdrawn, please contact the conference organisers in order to do so"))
453 self
._abstract
.withdraw(self
._getUser
(),self
._comments
)
454 self
._redirect
( urlHandlers
.UHAbstractDisplay
.getURL( self
._abstract
) )
456 wp
= abstracts
.WPAbstractWithdraw( self
, self
._abstract
)
460 class RHAbstractRecovery( RHAbstractModificationBase
):
461 _uh
= urlHandlers
.UHAbstractWithdraw
463 def _checkParams( self
, params
):
464 RHAbstractModificationBase
._checkParams
( self
, params
)
466 if params
.has_key("OK"):
467 self
._action
= "RECOVER"
468 elif params
.has_key("cancel"):
469 self
._action
= "CANCEL"
471 def _processIfOpened( self
):
472 if self
._action
== "CANCEL":
473 self
._redirect
( urlHandlers
.UHAbstractDisplay
.getURL( self
._abstract
) )
474 elif self
._action
== "RECOVER":
475 status
=self
._abstract
.getCurrentStatus()
476 if isinstance(status
,review
.AbstractStatusWithdrawn
):
477 if status
.getResponsible()!=self
._getUser
():
478 raise MaKaCError( _("you are not allowed to recover this abstract"))
479 self
._abstract
.recover()
480 self
._redirect
( urlHandlers
.UHAbstractDisplay
.getURL( self
._abstract
) )
482 wp
= abstracts
.WPAbstractRecovery( self
, self
._abstract
)
485 class RHGetAttachedFile(RHFileAccess
):
487 def _checkProtection( self
):
488 if not self
._conf
.getAbstractMgr().showAttachedFilesContribList():
489 # Same protection as the abstract
490 temptarget
=self
._target
491 self
._target
= self
._target
.getOwner()
492 RHFileAccess
._checkProtection
( self
)
493 self
._target
= temptarget