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 from pytz
import timezone
19 from indico
.util
.string
import safe_upper
, safe_slice
20 from indico
.util
.i18n
import i18nformat
21 from persistent
import Persistent
22 from persistent
.list import PersistentList
23 from BTrees
.OOBTree
import OOBTree
, intersection
, union
24 from BTrees
.IOBTree
import IOBTree
25 import BTrees
.OIBTree
as OIBTree
26 from datetime
import datetime
27 from MaKaC
.common
.Counter
import Counter
28 from MaKaC
.errors
import MaKaCError
, NoReportError
29 from MaKaC
.trashCan
import TrashCanManager
30 from MaKaC
.common
.timezoneUtils
import nowutc
31 from MaKaC
.i18n
import _
32 from indico
.core
.config
import Config
33 from MaKaC
.common
.fossilize
import fossilizes
, Fossilizable
34 from MaKaC
.fossils
.abstracts
import IAbstractFieldFossil
35 from MaKaC
.fossils
.abstracts
import IAbstractTextFieldFossil
36 from MaKaC
.fossils
.abstracts
import IAbstractSelectionFieldFossil
37 from MaKaC
.fossils
.abstracts
import ISelectionFieldOptionFossil
38 from indico
.util
.i18n
import N_
39 from indico
.util
.text
import wordsCounter
44 class _AbstractParticipationIndex(Persistent
):
45 """This class allows to index abstract participations (submitters)
46 for a single CFA process; this means that clients will be able to
47 efficiently perform queries of the type "give me all the abstracts
48 in which a certain registered user is implied".
49 For being able to perform this indexing, it is supposed that the Avatar
50 identifier is unique among other avatars and that it cannot change.
51 This index must be maintained by clients (i.e. the CFAMgr) as it doesn't
52 keep track of the changes on Participantons.
53 The key of the index is the Avatar and the values the different
54 Participations that user has within the current CFA process. For
55 performance reasons, the Avatar id will be used as index key (using the
56 whole Avatar object would make the index bigger and as the Avatar id
57 cannot change it's enough); the clients would have to keep the
58 integrity of the index.
64 def index(self
, participation
):
65 """Add a new participation to the index
67 #if the Participation is not linked to an Avatar there's no point to
69 a
= participation
.getAvatar()
72 #ToDo: if the Participation corresponds to an abstract which doesn't
73 # correspond to the current CFAMgr, then an error must be raised
75 if not self
._idx
.has_key(a
.getId()):
76 self
._idx
[a
.getId()] = PersistentList()
77 #if the participation is already in the index, no need for adding it
78 if participation
in self
._idx
[a
.getId()]:
80 self
._idx
[a
.getId()].append(participation
)
82 def unindex(self
, participation
):
83 """Remove an existing participation from the index
85 #if the Participation is not linked to an Avatar there's no point to
87 a
= participation
.getAvatar()
91 #if the Avatar associated to the participation isn't in the index do
93 if not self
._idx
.has_key(a
.getId()):
95 #if the given participation is indexed remove it, otherwise do nothing
96 if participation
in self
._idx
[a
.getId()]:
97 self
._idx
[a
.getId()].remove(participation
)
99 def getParticipationList(self
, av
):
101 return self
._idx
[av
.getId()]
106 class AbstractParticipation(Persistent
):
108 def __init__(self
, abstract
, **data
):
109 self
._abstract
= abstract
113 self
._affilliation
= ""
120 def setFromAvatar(self
, av
):
121 data
= {"title": av
.getTitle(),
122 "firstName": av
.getName(),
123 "surName": av
.getSurName(),
124 "email": av
.getEmail(),
125 "affiliation": av
.getOrganisation(),
126 "address": av
.getAddress(),
127 "telephone": av
.getTelephone(),
131 def setFromAbstractParticipation(self
, part
):
132 data
= {"title": part
.getTitle(),
133 "firstName": part
.getFirstName(),
134 "surName": part
.getSurName(),
135 "email": part
.getEmail(),
136 "affiliation": part
.getAffiliation(),
137 "address": part
.getAddress(),
138 "telephone": part
.getTelephone(),
139 "fax": part
.getFax()}
142 def setData(self
, **data
):
143 if "firstName" in data
:
144 self
.setFirstName(data
["firstName"])
145 if "surName" in data
:
146 self
.setSurName(data
["surName"])
148 self
.setEmail(data
["email"])
149 if "affiliation" in data
:
150 self
.setAffiliation(data
["affiliation"])
151 if "address" in data
:
152 self
.setAddress(data
["address"])
153 if "telephone" in data
:
154 self
.setTelephone(data
["telephone"])
156 self
.setFax(data
["fax"])
158 self
.setTitle(data
["title"])
163 data
["firstName"] = self
.getFirstName()
164 data
["surName"] = self
.getSurName()
165 data
["email"] = self
.getEmail()
166 data
["affiliation"] = self
.getAffiliation()
167 data
["address"] = self
.getAddress()
168 data
["telephone"] = self
.getTelephone()
169 data
["fax"] = self
.getFax()
170 data
["title"] = self
.getTitle()
175 def clone(self
, abstract
):
176 ap
= AbstractParticipation(abstract
, self
.getData())
179 def _notifyModification(self
):
180 self
._abstract
._notifyModification
()
183 abs = self
.getAbstract()
187 mgr
.unindexAuthor(self
)
190 abs = self
.getAbstract()
194 mgr
.indexAuthor(self
)
196 def setFirstName(self
, name
):
198 if tmp
== self
.getFirstName():
201 self
._firstName
= tmp
203 self
._notifyModification
()
205 def getFirstName(self
):
206 return self
._firstName
209 return self
._firstName
211 def setSurName(self
, name
):
213 if tmp
== self
.getSurName():
218 self
._notifyModification
()
220 def getSurName(self
):
223 def getFamilyName(self
):
226 def setEmail(self
, email
):
227 email
= email
.strip().lower()
228 if email
!= self
.getEmail():
232 self
._notifyModification
()
237 def setAffiliation(self
, af
):
238 self
._affilliation
= af
.strip()
239 self
._notifyModification
()
241 setAffilliation
= setAffiliation
243 def getAffiliation(self
):
244 return self
._affilliation
246 def setAddress(self
, address
):
247 self
._address
= address
.strip()
248 self
._notifyModification
()
250 def getAddress(self
):
253 def setTelephone(self
, telf
):
254 self
._telephone
= telf
.strip()
255 self
._notifyModification
()
257 def getTelephone(self
):
258 return self
._telephone
260 def setFax(self
, fax
):
261 self
._fax
= fax
.strip()
262 self
._notifyModification
()
267 def setTitle(self
, title
):
268 self
._title
= title
.strip()
269 self
._notifyModification
()
274 def getFullName(self
):
275 res
= safe_upper(self
.getSurName())
277 for name
in self
.getFirstName().lower().split(" "):
281 tmp
.append(safe_upper(safe_slice(name
, 0, 1)) + safe_slice(name
, 1))
282 firstName
= " ".join(tmp
)
284 res
= "%s, %s" % (res
, firstName
)
286 res
= "%s %s" % (self
.getTitle(), res
)
289 def getStraightFullName(self
):
292 name
= "%s " % self
.getName()
293 return "%s%s" % (name
, self
.getSurName())
295 def getAbrName(self
):
296 res
= self
.getSurName()
297 if self
.getFirstName():
300 res
= "%s%s." % (res
, safe_upper(safe_slice(self
.getFirstName(), 0, 1)))
303 def getAbstract(self
):
304 return self
._abstract
306 def setAbstract(self
, abs):
311 self
._abstract
= None
312 TrashCanManager().add(self
)
315 TrashCanManager().remove(self
)
318 class Author(AbstractParticipation
):
320 def __init__(self
, abstract
, **data
):
321 AbstractParticipation
.__init
__(self
, abstract
, **data
)
322 self
._abstractId
= ""
327 def setId(self
, newId
):
328 self
._id
= str(newId
)
330 def clone(self
, abstract
):
331 auth
= Author(abstract
, self
.getData())
335 return self
._abstract
.isSpeaker(self
)
338 class Submitter(AbstractParticipation
):
340 def __init__(self
, abstract
, av
):
342 raise MaKaCError(_("abstract submitter cannot be None"))
343 AbstractParticipation
.__init
__(self
, abstract
)
346 self
.setFromAvatar(av
)
348 def _setUser(self
, av
):
349 if self
.getUser() == av
:
351 #if currently there's an association with a registered user, we notify
352 # the unidexation of the participation
354 self
.getAbstract().getOwner().unregisterParticipation(self
)
356 #if the participation is associated to any avatar, we make the
357 # association and index it
359 self
.getAbstract().getOwner().registerParticipation(self
)
361 def clone(self
, abstract
):
362 sub
= Submitter(abstract
, self
.getAvatar())
363 sub
.setData(self
.getData())
372 def representsUser(self
, av
):
373 return self
.getUser() == av
376 class _AuthIdx(Persistent
):
378 def __init__(self
, mgr
):
380 self
._idx
= OOBTree()
382 def _getKey(self
, auth
):
383 return "%s %s" % (auth
.getSurName().lower(), auth
.getFirstName().lower())
385 def index(self
, auth
):
386 if auth
.getAbstract() is None:
387 raise MaKaCError(_("cannot index an author of an abstract which is not included in a conference"))
388 if auth
.getAbstract().getOwner() != self
._mgr
:
389 raise MaKaCError(_("cannot index an author of an abstract which does not belong to this conference"))
390 key
= self
._getKey
(auth
)
391 abstractId
= str(auth
.getAbstract().getId())
392 if not self
._idx
.has_key(key
):
393 self
._idx
[key
] = OIBTree
.OIBTree()
394 if not self
._idx
[key
].has_key(abstractId
):
395 self
._idx
[key
][abstractId
] = 0
396 self
._idx
[key
][abstractId
] += 1
398 def unindex(self
, auth
):
399 if auth
.getAbstract() is None:
400 raise MaKaCError(_("cannot unindex an author of an abstract which is not included in a conference"))
401 if auth
.getAbstract().getOwner() != self
._mgr
:
402 raise MaKaCError(_("cannot unindex an author of an abstract which does not belong to this conference"))
403 key
= self
._getKey
(auth
)
404 if not self
._idx
.has_key(key
):
406 abstractId
= str(auth
.getAbstract().getId())
407 if abstractId
not in self
._idx
[key
]:
409 self
._idx
[key
][abstractId
] -= 1
410 if self
._idx
[key
][abstractId
] <= 0:
411 del self
._idx
[key
][abstractId
]
412 if len(self
._idx
[key
]) <= 0:
415 def match(self
, query
):
416 query
= query
.lower().strip()
417 res
= OIBTree
.OISet()
418 for k
in self
._idx
.keys():
419 if k
.find(query
) != -1:
420 res
= OIBTree
.union(res
, self
._idx
[k
])
424 class _PrimAuthIdx(_AuthIdx
):
426 def __init__(self
, mgr
):
427 _AuthIdx
.__init
__(self
, mgr
)
428 for abs in self
._mgr
.getAbstractList():
429 for auth
in abs.getPrimaryAuthorList():
433 class _AuthEmailIdx(_AuthIdx
):
435 def __init__(self
, mgr
):
436 _AuthIdx
.__init
__(self
, mgr
)
437 for abs in self
._mgr
.getAbstractList():
438 for auth
in abs.getPrimaryAuthorList():
440 for auth
in abs.getCoAuthorList():
443 def _getKey(self
, auth
):
444 return auth
.getEmail().lower()
447 class AbstractField(Persistent
, Fossilizable
):
448 fossilizes(IAbstractFieldFossil
)
450 fieldtypes
= ["textarea", "input", "selection"]
453 def makefield(cls
, params
):
454 fieldType
= params
["type"]
455 if fieldType
not in cls
.fieldtypes
:
456 return AbstractTextAreaField(params
)
457 elif fieldType
== "textarea":
458 return AbstractTextAreaField(params
)
459 elif fieldType
== "input":
460 return AbstractInputField(params
)
461 elif fieldType
== "selection":
462 return AbstractSelectionField(params
)
464 def __init__(self
, params
):
465 self
._id
= params
["id"]
466 self
._caption
= params
.get("caption") if params
.get("caption") else self
._id
467 self
._isMandatory
= params
.get("isMandatory") if params
.get("isMandatory") else False
471 """ To be implemented by subclasses """
474 def _notifyModification(self
):
477 def check(self
, content
):
480 if self
._active
and self
._isMandatory
and content
== "":
481 errors
.append(_("The field '%s' is mandatory") % self
._caption
)
488 def isMandatory(self
):
489 return self
._isMandatory
491 def setMandatory(self
, isMandatory
=False):
492 self
._isMandatory
= isMandatory
493 self
._notifyModification
()
500 self
._notifyModification
()
502 def getCaption(self
):
505 def setCaption(self
, caption
):
506 self
._caption
= caption
507 self
._notifyModification
()
512 def setActive(self
, active
):
513 self
._active
= active
514 self
._notifyModification
()
518 values
["id"] = self
.getId()
519 values
["caption"] = self
.getCaption()
520 values
["isMandatory"] = self
.isMandatory()
523 def setValues(self
, params
):
524 self
.setCaption(params
.get("caption") if params
.get("caption") else self
._id
)
525 self
.setMandatory(params
.get("isMandatory") if params
.get("isMandatory") else False)
526 self
._notifyModification
()
529 class AbstractTextField(AbstractField
):
530 fossilizes(IAbstractTextFieldFossil
)
532 limitationtypes
= ["chars", "words"]
534 def __init__(self
, params
):
535 AbstractField
.__init
__(self
, params
)
536 self
._maxLength
= params
.get("maxLength") if params
.get("maxLength") else 0
537 self
._limitation
= params
.get("limitation") if params
.get("limitation") in self
.limitationtypes
else "chars"
540 return AbstractTextField(self
.getValues())
542 def check(self
, content
):
543 errors
= AbstractField
.check(self
, content
)
545 if self
._maxLength
!= 0:
546 if self
._limitation
== "words" and wordsCounter(str(content
)) > self
._maxLength
:
547 errors
.append(_("The field '%s' cannot be more than %s words") % (self
._caption
, self
._maxLength
))
548 elif self
._limitation
== "chars" and len(content
) > self
._maxLength
:
549 errors
.append(_("The field '%s' cannot be more than %s characters") % (self
._caption
, self
._maxLength
))
553 def getLimitation(self
):
554 return self
._limitation
556 def getMaxLength(self
):
557 return self
._maxLength
559 def setLimitation(self
, limitation
="chars"):
560 self
._limitation
= limitation
if limitation
in self
.limitationtypes
else "chars"
561 self
._notifyModification
()
563 def setMaxLength(self
, maxLength
=0):
564 self
._maxLength
= maxLength
565 self
._notifyModification
()
568 values
= AbstractField
.getValues(self
)
569 values
["maxLength"] = self
.getMaxLength()
570 values
["limitation"] = self
.getLimitation()
573 def setValues(self
, params
):
574 AbstractField
.setValues(self
, params
)
575 self
.setMaxLength(params
.get("maxLength") if params
.get("maxLength") else 0)
576 self
.setLimitation(params
.get("limitation") if params
.get("limitation") in self
.limitationtypes
else "chars")
577 self
._notifyModification
()
580 class AbstractTextAreaField(AbstractTextField
):
585 class AbstractInputField(AbstractTextField
):
590 class AbstractSelectionField(AbstractField
):
591 fossilizes(IAbstractSelectionFieldFossil
)
594 def __init__(self
, params
):
595 AbstractField
.__init
__(self
, params
)
596 self
.__id
_generator
= Counter()
598 self
._deleted
_options
= []
599 for o
in params
.get("options") if params
.get("options") else []:
602 def _deleteOption(self
, option
):
603 self
._options
.remove(option
)
604 self
._deleted
_options
.append(option
)
606 def _updateDeletedOptions(self
, options
=[]):
607 stored_options
= set(self
._options
)
608 updated_options
= set(self
.getOption(o
["id"]) for o
in options
)
610 for deleted_option
in stored_options
- updated_options
:
611 self
._deleteOption
(deleted_option
)
613 def _setOption(self
, option
, index
=None):
614 stored
= self
.getOption(option
["id"])
616 stored
.value
= option
["value"]
617 oldindex
= self
._options
.index(stored
)
618 self
._options
.insert(index
, self
._options
.pop(oldindex
))
619 elif option
["value"] is not "":
620 option
["id"] = self
.__id
_generator
.newCount()
621 self
._options
.append(SelectionFieldOption(option
["id"], option
["value"]))
624 return AbstractSelectionField(self
.getValues())
626 def check(self
, content
):
627 errors
= AbstractField
.check(self
, content
)
629 if self
._active
and self
._isMandatory
and content
== "":
630 errors
.append(_("The field '%s' is mandatory") % self
._caption
)
632 if next((op
for op
in self
._options
if op
.id == content
), None) is None:
633 errors
.append(_("The option with ID '%s' in the field %s") % (content
, self
._caption
))
637 def getDeletedOption(self
, id):
638 return next((o
for o
in self
._deleted
_options
if o
.getId() == id), None)
640 def getDeletedOptions(self
, id):
641 return self
._deleted
_options
643 def getOption(self
, id):
644 return next((o
for o
in self
._options
if o
.getId() == id), None)
646 def getOptions(self
):
649 def setOptions(self
, options
=[]):
650 self
._updateDeletedOptions
(options
)
651 for i
, o
in enumerate(options
):
652 self
._setOption
(o
, i
)
653 self
._notifyModification
()
656 values
= AbstractField
.getValues(self
)
659 for o
in self
._options
:
660 options
.append(o
.__dict
__)
661 values
["options"] = options
665 def setValues(self
, params
):
666 AbstractField
.setValues(self
, params
)
667 self
.setOptions(params
.get("options"))
668 self
._notifyModification
()
671 class SelectionFieldOption(Fossilizable
):
672 fossilizes(ISelectionFieldOptionFossil
)
674 def __init__(self
, id, value
):
679 def __eq__(self
, other
):
680 if isinstance(other
, SelectionFieldOption
):
681 return self
.id == other
.id
703 class AbstractFieldContent(Persistent
):
705 def __init__(self
, field
, value
):
709 def __eq__(self
, other
):
710 if isinstance(other
, AbstractFieldContent
) and self
.field
== other
.field
:
711 return self
.value
== other
.value
712 elif not isinstance(other
, AbstractFieldContent
):
713 return self
.value
== other
717 return len(self
.value
)
719 def __ne__(self
, other
):
720 if isinstance(other
, AbstractFieldContent
) and self
.field
== other
.field
:
721 return self
.value
!= other
.value
722 elif not isinstance(other
, AbstractFieldContent
):
723 return self
.value
!= other
727 if isinstance(self
.field
, AbstractSelectionField
):
728 return str(self
.field
.getOption(self
.value
))
730 return str(self
.value
)
733 class AbstractFieldsMgr(Persistent
):
736 self
._fields
= self
._initFields
()
737 self
.__fieldGenerator
= Counter()
740 afm
= AbstractFieldsMgr()
741 for f
in self
.getFields():
742 afm
._addField
(f
.clone())
745 def getFieldGenerator(self
):
747 if self
.__fieldGenerator
:
749 except AttributeError, e
:
750 self
.__fieldGenerator
= Counter()
751 return self
.__fieldGenerator
753 def _notifyModification(self
):
756 def _initFields(self
):
758 params
= {"type": "textarea", "id": "content", "caption": N_("Content"), "isMandatory": True}
759 d
.append(AbstractField
.makefield(params
))
760 params
= {"type": "textarea", "id": "summary", "caption": N_("Summary")}
761 d
.append(AbstractField
.makefield(params
))
764 def hasField(self
, id):
765 for f
in self
._fields
:
771 if not self
.hasField("content"):
772 params
= {"type": "textarea", "id": "content", "caption": _("Content"), "isMandatory": True}
773 ac
= AbstractField
.makefield(params
)
774 self
._fields
.insert(0, ac
)
777 def getActiveFields(self
):
779 for f
in self
.getFields():
784 def hasActiveField(self
, id):
785 return self
.hasField(id) and self
.getFieldById(id).isActive()
787 def hasAnyActiveField(self
):
788 for f
in self
._fields
:
793 def enableField(self
, id):
794 if self
.hasField(id):
795 self
.getFieldById(id).setActive(True)
796 self
._notifyModification
()
798 def disableField(self
, id):
799 if self
.hasField(id):
800 self
.getFieldById(id).setActive(False)
801 self
._notifyModification
()
803 def getFieldKeys(self
):
805 for f
in self
._fields
:
806 keys
.append(f
.getId())
809 def getFieldById(self
, id):
810 for f
in self
._fields
:
815 def _addField(self
, field
):
816 self
._fields
.append(field
)
818 def setField(self
, params
):
819 if self
.hasField(params
["id"]):
820 self
.getFieldById(params
["id"]).setValues(params
)
822 params
["id"] = str(self
.getFieldGenerator().newCount())
823 absf
= AbstractField
.makefield(params
)
824 self
._fields
.append(absf
)
825 self
._notifyModification
()
828 def removeField(self
, id):
829 if self
.hasField(id):
830 self
._fields
.remove(self
.getFieldById(id))
831 self
._notifyModification
()
833 def moveAbsFieldUp(self
, id):
834 if self
.hasField(id):
835 f
= self
.getFieldById(id)
836 idx
= self
._fields
.index(f
)
837 self
._fields
.remove(f
)
839 self
._fields
.append(f
)
841 self
._fields
.insert(idx
-1, f
)
842 self
._notifyModification
()
844 def moveAbsFieldDown(self
, id):
845 if self
.hasField(id):
846 f
= self
.getFieldById(id)
847 idx
= self
._fields
.index(f
)
848 self
._fields
.remove(f
)
849 if idx
== len(self
._fields
):
850 self
._fields
.insert(0, f
)
852 self
._fields
.insert(idx
+1, f
)
853 self
._notifyModification
()
856 class AbstractMgr(Persistent
):
858 def __init__(self
, owner
):
860 self
._abstracts
= OOBTree()
861 self
._participationIdx
= _AbstractParticipationIndex()
862 self
.__abstractGenerator
= Counter()
863 self
._activated
= False
864 self
.setStartSubmissionDate(datetime
.now())
865 self
.setEndSubmissionDate(datetime
.now())
866 ## self._contribTypes = PersistentList()
867 self
.setAnnouncement("")
868 self
._notifTpls
= IOBTree()
869 self
._notifTplsOrder
= PersistentList()
870 self
.__notifTplsCounter
= Counter()
871 self
._authorizedSubmitter
= PersistentList()
872 self
._primAuthIdx
= _PrimAuthIdx(self
)
873 self
._authEmailIdx
= _AuthEmailIdx(self
)
874 self
._abstractFieldsMgr
= AbstractFieldsMgr()
875 self
._submissionNotification
= SubmissionNotification()
876 self
._multipleTracks
= True
877 self
._tracksMandatory
= False
878 self
._attachFiles
= False
879 self
._showSelectAsSpeaker
= True
880 self
._selectSpeakerMandatory
= True
881 self
._showAttachedFilesContribList
= False
883 def getMultipleTracks(self
):
885 return self
._multipleTracks
887 self
.setMultipleTracks(True)
888 return self
._multipleTracks
890 def setMultipleTracks(self
, multipleTracks
=True):
891 self
._multipleTracks
= multipleTracks
893 def areTracksMandatory(self
):
895 return self
._tracksMandatory
897 self
.setTracksMandatory(False)
898 return self
._tracksMandatory
900 def canAttachFiles(self
):
902 return self
._attachFiles
904 self
.setAllowAttachFiles(False)
905 return self
._attachFiles
907 def setAllowAttachFiles(self
, attachedFiles
):
908 self
._attachFiles
= attachedFiles
910 def setTracksMandatory(self
, tracksMandatory
=False):
911 self
._tracksMandatory
= tracksMandatory
913 def showSelectAsSpeaker(self
):
915 return self
._showSelectAsSpeaker
917 self
._showSelectAsSpeaker
= True
918 return self
._showSelectAsSpeaker
920 def setShowSelectAsSpeaker(self
, showSelectAsSpeaker
):
921 self
._showSelectAsSpeaker
= showSelectAsSpeaker
923 def isSelectSpeakerMandatory(self
):
925 return self
._selectSpeakerMandatory
927 self
._selectSpeakerMandatory
= True
928 return self
._selectSpeakerMandatory
930 def setSelectSpeakerMandatory(self
, selectSpeakerMandatory
):
931 self
._selectSpeakerMandatory
= selectSpeakerMandatory
933 def showAttachedFilesContribList(self
):
935 return self
._showAttachedFilesContribList
937 self
._showAttachedFilesContribList
= False
938 return self
._showAttachedFilesContribList
940 def setSwitchShowAttachedFilesContribList(self
, showshowAttachedFilesContribList
):
941 self
._showAttachedFilesContribList
= showshowAttachedFilesContribList
943 def getAbstractFieldsMgr(self
):
945 return self
._abstractFieldsMgr
947 self
._abstractFieldsMgr
= AbstractFieldsMgr()
948 return self
._abstractFieldsMgr
950 def clone(self
, conference
):
951 amgr
= AbstractMgr(conference
)
952 amgr
._abstractFieldsMgr
= self
.getAbstractFieldsMgr().clone()
953 amgr
.setAnnouncement(self
.getAnnouncement())
955 timeDifference
= conference
.getStartDate() - self
.getOwner().getStartDate()
956 amgr
.setStartSubmissionDate(self
.getStartSubmissionDate() + timeDifference
)
957 amgr
.setEndSubmissionDate(self
.getEndSubmissionDate() + timeDifference
)
959 modifDeadline
= self
.getModificationDeadline()
960 if modifDeadline
is not None:
961 amgr
.setModificationDeadline(self
.getModificationDeadline() + timeDifference
)
963 amgr
.setActive(self
.isActive())
964 if self
.getCFAStatus():
969 for a
in self
.getAbstractList():
970 amgr
.addAbstract(a
.clone(conference
, amgr
._generateNewAbstractId
()))
972 for tpl
in self
.getNotificationTplList():
973 amgr
.addNotificationTpl(tpl
.clone())
975 # Cloning submission notification:
976 amgr
.setSubmissionNotification(self
.getSubmissionNotification().clone())
982 getConference
= getOwner
984 def getTimezone(self
):
985 return self
.getConference().getTimezone()
988 self
._activated
= True
990 def desactiveCFA(self
):
991 self
._activated
= False
993 def getAuthorizedSubmitterList(self
):
995 return self
._authorizedSubmitter
996 except AttributeError:
997 self
._authorizedSubmitter
= PersistentList()
998 return self
._authorizedSubmitter
1000 def addAuthorizedSubmitter(self
, av
):
1002 if self
._authorizedSubmitter
:
1004 except AttributeError:
1005 self
._authorizedSubmitter
= PersistentList()
1006 if not av
in self
._authorizedSubmitter
:
1007 self
._authorizedSubmitter
.append(av
)
1009 def removeAuthorizedSubmitter(self
, av
):
1011 if self
._authorizedSubmitter
:
1014 self
._authorizedSubmitter
= PersistentList()
1015 if av
in self
._authorizedSubmitter
:
1016 self
._authorizedSubmitter
.remove(av
)
1018 def getCFAStatus(self
):
1019 return self
._activated
1021 def setActive(self
, value
):
1028 return self
._activated
1030 def setStartSubmissionDate(self
, date
):
1031 self
._submissionStartDate
= datetime(date
.year
, date
.month
, date
.day
, 0, 0, 0)
1033 def getStartSubmissionDate(self
):
1034 return timezone(self
.getTimezone()).localize(self
._submissionStartDate
)
1036 def setEndSubmissionDate(self
, date
):
1037 self
._submissionEndDate
= datetime(date
.year
, date
.month
, date
.day
, 23, 59, 59)
1039 def getEndSubmissionDate(self
):
1040 return timezone(self
.getTimezone()).localize(self
._submissionEndDate
)
1042 def inSubmissionPeriod(self
, date
=None):
1045 sd
= self
.getStartSubmissionDate()
1046 ed
= self
.getEndSubmissionDate()
1047 return date
<= ed
and date
>= sd
1049 def getModificationDeadline(self
):
1050 """Returns the deadline for modifications on the submitted abstracts.
1053 if self
._modifDeadline
:
1055 except AttributeError, e
:
1056 self
._modifDeadline
= None
1057 if self
._modifDeadline
is not None:
1058 return timezone(self
.getTimezone()).localize(self
._modifDeadline
)
1062 def setModificationDeadline(self
, newDL
):
1063 """Sets a new deadline for modifications on the submitted abstracts.
1065 if newDL
is not None:
1066 self
._modifDeadline
= datetime(newDL
.year
, newDL
.month
, newDL
.day
, 23, 59, 59)
1068 self
._modifDeadline
= newDL
1070 def inModificationPeriod(self
, date
=None):
1071 """Tells whether is possible to modify a submitted abstract in a
1076 if not self
.getModificationDeadline():
1078 return date
<= self
.getModificationDeadline()
1080 def getAnnouncement(self
):
1083 if self
._announcement
:
1085 except AttributeError, e
:
1086 self
._announcement
= ""
1088 return self
._announcement
1090 def setAnnouncement(self
, newAnnouncement
):
1091 self
._announcement
= newAnnouncement
.strip()
1093 ## def addContribType(self, type):
1094 ## type = type.strip()
1096 ## raise MaKaCError("Cannot add an empty contribution type")
1097 ## self._contribTypes.append(type)
1099 ## def removeContribType(self, type):
1100 ## if type in self._contribTypes:
1101 ## self._contribTypes.remove(type)
1103 ## def getContribTypeList(self):
1104 ## return self._contribTypes
1106 def _generateNewAbstractId(self
):
1107 """Returns a new unique identifier for the current conference
1110 #instead of having a own counter, the abstract manager will request
1111 # abstract ids to the conference which will ensure a unique id
1112 # which will persist afterwards when an abstract is accepted
1113 return str(self
.getConference().genNewAbstractId())
1115 def _getOldAbstractCounter(self
):
1116 return self
.__abstractGenerator
._getCount
()
1118 def newAbstract(self
, av
, **data
):
1119 """Creates a new abstract under this manager
1121 id = self
._generateNewAbstractId
()
1122 a
= Abstract(self
, id, av
, **data
)
1123 self
._abstracts
[id] = a
1124 for auth
in a
.getPrimaryAuthorList():
1125 self
.indexAuthor(auth
)
1128 def addAbstract(self
, abstract
):
1129 if abstract
in self
.getAbstractList():
1131 if isinstance(abstract
.getCurrentStatus(), AbstractStatusWithdrawn
):
1132 raise MaKaCError(_("Cannot add an abstract which has been withdrawn"), ("Event"))
1133 abstract
._setOwner
(self
)
1134 self
._abstracts
[abstract
.getId()] = abstract
1135 for auth
in abstract
.getPrimaryAuthorList():
1136 self
.indexAuthor(auth
)
1138 def removeAbstract(self
, abstract
):
1139 if self
._abstracts
.has_key(abstract
.getId()):
1140 #for auth in abstract.getPrimaryAuthorList():
1141 # self.unindexAuthor(auth)
1142 # * Remove dependencies with another abstracts:
1143 # - If it's an accepted abstract-->remove abstract from contribution
1144 if isinstance(abstract
.getCurrentStatus(), AbstractStatusAccepted
):
1145 raise NoReportError(_("Cannot remove an accepted abstract before removing the contribution linked to it"))
1146 # If it's a withdrawn abstract-->remove abstract from contribution
1147 if isinstance(abstract
.getCurrentStatus(), AbstractStatusWithdrawn
) and abstract
.getContribution():
1148 raise NoReportError(_("Cannot remove the abstract before removing the contribution linked to it"))
1149 for abs in self
._abstracts
.values():
1151 st
= abs.getCurrentStatus()
1152 if isinstance(st
, AbstractStatusDuplicated
):
1153 #if the abstract to delete is the orginal in another "duplicated", change status to submitted
1154 if st
.getOriginal() == abstract
:
1155 abs.setCurrentStatus(AbstractStatusSubmitted(abs))
1156 elif isinstance(st
, AbstractStatusMerged
):
1157 #if the abstract to delete is the target one in another "merged", change status to submitted
1158 if st
.getTargetAbstract() == abstract
:
1159 abs.setCurrentStatus(AbstractStatusSubmitted(abs))
1160 #unindex participations!!!
1161 self
.unregisterParticipation(abstract
.getSubmitter())
1162 del self
._abstracts
[abstract
.getId()]
1165 def recoverAbstract(self
, abstract
):
1166 self
.addAbstract(abstract
)
1167 abstract
.recoverFromTrashCan()
1169 def getAbstractList(self
):
1170 return self
._abstracts
.values()
1172 def getAbstractById(self
, id):
1173 return self
._abstracts
.get(str(id), None)
1175 def registerParticipation(self
, p
):
1176 self
._participationIdx
.index(p
)
1178 def unregisterParticipation(self
, p
):
1179 self
._participationIdx
.unindex(p
)
1181 def getAbstractListForAvatar(self
, av
):
1183 if self
._participationIdx
:
1185 except AttributeError, e
:
1186 self
._participationIdx
= self
._partipationIdx
1187 self
._partipationIdx
= None
1189 for participation
in self
._participationIdx
.getParticipationList(av
):
1190 abstract
= participation
.getAbstract()
1191 if abstract
is not None and abstract
.isSubmitter(av
):
1192 if abstract
not in res
:
1193 res
.append(abstract
)
1196 def getAbstractListForAuthorEmail(self
, email
):
1197 """ Get list of abstracts where the email belongs to an author"""
1198 return [self
.getAbstractById(i
) for i
in self
._getAuthEmailIndex
().match(email
)]
1200 def getNotificationTplList(self
):
1204 except AttributeError:
1205 self
._notifTpls
= IOBTree()
1207 if self
._notifTplsOrder
:
1209 except AttributeError:
1210 self
._notifTplsOrder
= PersistentList()
1211 for tpl
in self
._notifTpls
.values():
1212 self
._notifTplsOrder
.append(tpl
)
1213 return self
._notifTplsOrder
1215 def addNotificationTpl(self
, tpl
):
1219 except AttributeError:
1220 self
._notifTpls
= IOBTree()
1222 if self
._notifTplsOrder
:
1224 except AttributeError:
1225 self
._notifTplsOrder
= PersistentList()
1226 for tpl
in self
._notifTpls
.values():
1227 self
._notifTplsOrder
.append(tpl
)
1229 if self
._notifTplsCounter
:
1231 except AttributeError:
1232 self
._notifTplsCounter
= Counter()
1233 if tpl
.getOwner() == self
and self
._notifTpls
.has_key(tpl
.getId()):
1237 id = self
._notifTplsCounter
.newCount()
1238 tpl
.includeInOwner(self
, id)
1239 self
._notifTpls
[int(id)] = tpl
1240 self
._notifTplsOrder
.append(tpl
)
1242 def removeNotificationTpl(self
, tpl
):
1246 except AttributeError:
1247 self
._notifTpls
= IOBTree()
1249 if self
._notifTplsOrder
:
1251 except AttributeError:
1252 self
._notifTplsOrder
= PersistentList()
1253 for tpl
in self
._notifTpls
.values():
1254 self
._notifTplsOrder
.append(tpl
)
1255 if tpl
.getOwner() != self
or not self
._notifTpls
.has_key(int(tpl
.getId())):
1257 del self
._notifTpls
[int(tpl
.getId())]
1258 self
._notifTplsOrder
.remove(tpl
)
1259 tpl
.includeInOwner(None, tpl
.getId()) # We don't change the id for
1260 # recovery purposes.
1263 def recoverNotificationTpl(self
, tpl
):
1264 self
.addNotificationTpl(tpl
)
1267 def getNotificationTplById(self
, id):
1271 except AttributeError:
1272 self
._notifTpls
= IOBTree()
1273 return self
._notifTpls
.get(int(id), None)
1275 def getNotifTplForAbstract(self
, abs):
1278 for tpl
in self
.getNotificationTplList():
1279 if tpl
.satisfies(abs):
1283 def moveUpNotifTpl(self
, tpl
):
1287 if self
._notifTplsOrder
:
1289 except AttributeError:
1290 self
._notifTplsOrder
= PersistentList()
1291 for tpl
in self
._notifTpls
.values():
1292 self
._notifTplsOrder
.append(tpl
)
1293 if tpl
not in self
._notifTplsOrder
:
1295 idx
= self
._notifTplsOrder
.index(tpl
)
1298 self
._notifTplsOrder
.remove(tpl
)
1299 self
._notifTplsOrder
.insert(idx
-1, tpl
)
1301 def moveDownNotifTpl(self
, tpl
):
1305 if self
._notifTplsOrder
:
1307 except AttributeError:
1308 self
._notifTplsOrder
= PersistentList()
1309 for tpl
in self
._notifTpls
.values():
1310 self
._notifTplsOrder
.append(tpl
)
1311 idx
= self
._notifTplsOrder
.index(tpl
)
1312 if idx
== len(self
._notifTplsOrder
):
1314 self
._notifTplsOrder
.remove(tpl
)
1315 self
._notifTplsOrder
.insert(idx
+1, tpl
)
1317 def indexAuthor(self
, auth
):
1318 a
= auth
.getAbstract()
1319 if a
.isPrimaryAuthor(auth
):
1320 self
._getPrimAuthIndex
().index(auth
)
1321 self
._getAuthEmailIndex
().index(auth
)
1323 def unindexAuthor(self
, auth
):
1324 a
= auth
.getAbstract()
1325 if a
.isPrimaryAuthor(auth
):
1326 self
._getPrimAuthIndex
().unindex(auth
)
1327 self
._getAuthEmailIndex
().unindex(auth
)
1329 def _getPrimAuthIndex(self
):
1331 if self
._primAuthIdx
:
1333 except AttributeError:
1334 self
._primAuthIdx
= _PrimAuthIdx(self
)
1335 return self
._primAuthIdx
1337 def _getAuthEmailIndex(self
):
1338 if not hasattr(self
, '_authEmailIdx'):
1339 self
._authEmailIdx
= _AuthEmailIdx(self
)
1340 return self
._authEmailIdx
1342 def getAbstractsMatchingAuth(self
, query
, onlyPrimary
=True):
1343 if str(query
).strip() == "":
1344 return self
.getAbstractList()
1345 res
= self
._getPrimAuthIndex
().match(query
)
1346 return [self
.getAbstractById(id) for id in res
]
1348 def setAbstractField(self
, params
):
1349 return self
.getAbstractFieldsMgr().setField(params
)
1351 def removeAbstractField(self
, id):
1352 self
.getAbstractFieldsMgr().removeField(id)
1354 def hasAnyEnabledAbstractField(self
):
1355 return self
.getAbstractFieldsMgr().hasAnyActiveField()
1357 def hasEnabledAbstractField(self
, key
):
1358 return self
.getAbstractFieldsMgr().hasActiveField(key
)
1360 def enableAbstractField(self
, abstractField
):
1361 self
.getAbstractFieldsMgr().enableField(abstractField
)
1362 self
.notifyModification()
1364 def disableAbstractField(self
, abstractField
):
1365 self
.getAbstractFieldsMgr().disableField(abstractField
)
1366 self
.notifyModification()
1368 def moveAbsFieldUp(self
, id):
1369 self
.getAbstractFieldsMgr().moveAbsFieldUp(id)
1370 self
.notifyModification()
1372 def moveAbsFieldDown(self
, id):
1373 self
.getAbstractFieldsMgr().moveAbsFieldDown(id)
1374 self
.notifyModification()
1376 def getSubmissionNotification(self
):
1378 if self
._submissionNotification
:
1380 except AttributeError, e
:
1381 self
._submissionNotification
= SubmissionNotification()
1382 return self
._submissionNotification
1384 def setSubmissionNotification(self
, sn
):
1385 self
._submissionNotification
= sn
1387 def recalculateAbstractsRating(self
, scaleLower
, scaleHigher
):
1388 ''' recalculate the values of the rating for all the abstracts in the conference '''
1389 for abs in self
.getAbstractList():
1390 abs.updateRating((scaleLower
, scaleHigher
))
1392 def removeAnswersOfQuestion(self
, questionId
):
1393 ''' Remove a question results for each abstract '''
1394 for abs in self
.getAbstractList():
1395 abs.removeAnswersOfQuestion(questionId
)
1397 def notifyModification(self
):
1401 class SubmissionNotification(Persistent
):
1404 self
._toList
= PersistentList()
1405 self
._ccList
= PersistentList()
1407 def hasDestination(self
):
1408 return self
._toList
!= [] or self
._toList
!= []
1410 def getToList(self
):
1413 def setToList(self
, tl
):
1416 def addToList(self
, to
):
1417 self
._toList
.append(to
)
1419 def clearToList(self
):
1420 self
._toList
= PersistentList()
1422 def getCCList(self
):
1425 def setCCList(self
, cl
):
1428 def addCCList(self
, cc
):
1429 self
._ccList
.append(cc
)
1431 def clearCCList(self
):
1432 self
._ccList
= PersistentList()
1435 nsn
= SubmissionNotification()
1436 for i
in self
.getToList():
1438 for i
in self
.getCCList():
1443 class Comment(Persistent
):
1445 def __init__(self
, res
, content
=""):
1446 self
._abstract
= None
1448 self
._responsible
= res
1450 self
._creationDate
= nowutc()
1451 self
._modificationDate
= nowutc()
1453 def getLocator(self
):
1454 loc
= self
._abstract
.getLocator()
1455 loc
["intCommentId"] = self
._id
1458 def includeInAbstract(self
, abstract
, id):
1459 self
._abstract
= abstract
1463 self
._abstract
= None
1464 TrashCanManager().add(self
)
1467 TrashCanManager().remove(self
)
1469 def _notifyModification(self
, dt
=None):
1471 self
._modificationDate
= dt
1473 self
._modificationDate
= nowutc()
1475 def getResponsible(self
):
1476 return self
._responsible
1478 def getAbstract(self
):
1479 return self
._abstract
1484 def getContent(self
):
1485 return self
._content
1487 def setContent(self
, newContent
):
1488 self
._content
= newContent
1489 self
._notifyModification
()
1491 def getCreationDate(self
):
1492 return self
._creationDate
1494 def getModificationDate(self
):
1495 return self
._modificationDate
1497 def canModify(self
, aw_or_user
):
1498 if hasattr(aw_or_user
, 'getUser'):
1499 aw_or_user
= aw_or_user
.getUser()
1500 return self
.canUserModify(aw_or_user
)
1502 def canUserModify(self
, user
):
1503 abstract
= self
.getAbstract()
1504 conf
= abstract
.getConference()
1505 return self
.getResponsible() == user
and \
1506 (abstract
.canUserModify(user
) or \
1507 len(conf
.getConference().getCoordinatedTracks(user
)) > 0)
1510 class Abstract(Persistent
):
1512 def __init__(self
, owner
, id, submitter
, **abstractData
):
1513 self
._setOwner
( owner
)
1517 self
._authorGen
= Counter()
1518 self
._authors
= OOBTree()
1519 self
._primaryAuthors
= PersistentList()
1520 self
._coAuthors
= PersistentList()
1521 self
._speakers
= PersistentList()
1522 self
._tracks
= OOBTree()
1523 self
._contribTypes
= PersistentList( [""] )
1524 self
._setSubmissionDate
( nowutc() )
1525 self
._modificationDate
= nowutc()
1526 self
._currentStatus
= AbstractStatusSubmitted( self
)
1527 self
._trackAcceptances
= OOBTree()
1528 self
._trackRejections
= OOBTree()
1529 self
._trackReallocations
= OOBTree()
1530 self
._trackJudgementsHistorical
={}
1532 self
._contribution
= None
1533 self
._intCommentGen
=Counter()
1534 self
._intComments
=PersistentList()
1535 self
._mergeFromList
= PersistentList()
1536 self
._notifLog
=NotificationLog(self
)
1537 self
._submitter
=None
1538 self
._setSubmitter
( submitter
)
1539 self
._rating
= None # It needs to be none to avoid the case of having the same value as the lowest value in the judgement
1540 self
._attachments
= {}
1541 self
._attachmentsCounter
= Counter()
1543 def __cmp__(self
, other
):
1544 if type(self
) is not type(other
):
1545 # This is actually dangerous and the ZODB manual says not to do this
1546 # because it relies on memory order. However, this branch should never
1547 # be taken anyway since we do not store different types in the same set
1548 # or use them as keys.
1549 return cmp(hash(self
), hash(other
))
1550 if self
.getConference() == other
.getConference():
1551 return cmp(self
.getId(), other
.getId())
1552 return cmp(self
.getConference(), other
.getConference())
1554 def clone(self
, conference
, abstractId
):
1556 # abstractId - internal in abstract manager of the conference
1557 abs = Abstract(conference
.getAbstractMgr(), abstractId
, self
.getSubmitter().getAvatar())
1558 abs.setTitle(self
.getTitle())
1559 for key
in self
.getFields().keys():
1560 abs.setField(key
,self
.getField(key
))
1561 abs.setComments(self
.getComments())
1563 abs._setSubmissionDate
(self
.getSubmissionDate())
1564 abs._modificationDate
= self
.getModificationDate()
1566 # Cloning of primary- and coauthors
1567 # if an author is also a speaker, an appropriate object will be
1568 # appended also to the speaker list
1569 for pa
in self
.getPrimaryAuthorList() :
1570 npa
= abs.newPrimaryAuthor(**(pa
.getData()))
1571 if self
.isSpeaker(pa
) :
1573 for ca
in self
.getCoAuthorList() :
1574 nca
= abs.newCoAuthor(**(ca
.getData()))
1575 if self
.isSpeaker(ca
) :
1578 # Cloning of speakers
1579 # only those, who are not authors :
1580 for sp
in self
.getSpeakerList() :
1581 if not self
.isAuthor(sp
) :
1582 abs.addSpeaker(sp
.clone())
1584 abs.setSubmitter(self
.getSubmitter().getAvatar())
1586 if self
.getContribType() is not None :
1587 for ct
in conference
.getContribTypeList() :
1588 if self
.getContribType().getName() == ct
.getName() :
1589 abs.setContribType(ct
)
1592 abs.setContribType(None)
1594 # the track, to which the abstract belongs to
1595 # legacy list implementation
1596 for tr
in self
.getTrackList() :
1597 for newtrack
in conference
.getTrackList():
1598 if newtrack
.getTitle() == tr
.getTitle() :
1599 abs.addTrack(newtrack
)
1601 # overall abstract status (accepted / rejected)
1602 abs._currentStatus
= self
._currentStatus
.clone(abs)
1604 for ta
in self
.getTrackAcceptanceList() :
1605 for newtrack
in conference
.getTrackList():
1606 if newtrack
.getTitle() == ta
.getTrack().getTitle() :
1607 newta
= ta
.clone(newtrack
)
1608 abs._addTrackAcceptance
(newta
)
1609 abs._addTrackJudgementToHistorical
(newta
)
1611 for trj
in self
.getTrackRejections().values() :
1612 for newtrack
in conference
.getTrackList():
1613 if newtrack
.getTitle() == trj
.getTrack().getTitle() :
1614 newtrj
= trj
.clone(newtrack
)
1615 abs._addTrackRejection
(newtrj
)
1616 abs._addTrackJudgementToHistorical
(newtrj
)
1618 for trl
in self
.getTrackReallocations().values() :
1619 for newtrack
in conference
.getTrackList():
1620 if newtrack
.getTitle() == trl
.getTrack().getTitle() :
1621 newtrl
= trl
.clone(newtrack
)
1622 abs._addTrackReallocation
(newtrl
)
1623 abs._addTrackJudgementToHistorical
(newtrl
)
1626 for f
in self
.getAttachments().values():
1627 newFile
= f
.clone(abs, protection
=False)
1628 abs.__addFile
(newFile
)
1632 def getUniqueId( self
):
1633 """returns (string) the unique identifier of the item"""
1634 """used only in the web session access key table"""
1635 """it is the same as the conference since only the conf can"""
1636 """be protected with an access key"""
1637 return self
.getConference().getUniqueId()
1639 def getMergeFromList(self
):
1641 return self
._mergeFromList
1642 except AttributeError:
1643 self
._mergeFromList
= PersistentList()
1644 return self
._mergeFromList
1646 def addMergeFromAbstract(self
, abstract
):
1648 if self
._mergeFromList
:
1650 except AttributeError:
1651 self
._mergeFromList
= PersistentList()
1652 self
._mergeFromList
.append(abstract
)
1654 def removeMergeFromAbstract(self
, abstract
):
1656 if self
._mergeFromList
:
1658 except AttributeError:
1659 self
._mergeFromList
= PersistentList()
1661 if abstract
in self
._mergeFromList
:
1662 self
._mergeFromList
.remove(abstract
)
1664 def getComments(self
):
1666 return self
._comments
1667 except AttributeError:
1669 return self
._comments
1671 def setComments(self
, comments
):
1672 self
._comments
= comments
1674 def __addFile(self
, file):
1675 file.archive(self
.getConference()._getRepository
())
1676 self
.getAttachments()[file.getId()] = file
1677 self
._notifyModification
()
1680 def saveFiles(self
, files
):
1681 cfg
= Config
.getInstance()
1682 from MaKaC
.conference
import LocalFile
1683 for fileUploaded
in files
:
1684 if fileUploaded
.filename
:
1685 # create a temp file
1686 tempPath
= cfg
.getUploadedFilesTempDir()
1687 tempFileName
= tempfile
.mkstemp(suffix
="IndicoAbstract.tmp", dir=tempPath
)[1]
1688 f
= open(tempFileName
, "wb")
1689 f
.write(fileUploaded
.file.read() )
1692 file.setFileName(fileUploaded
.filename
)
1693 file.setFilePath(tempFileName
)
1695 file.setId(self
._getAttachmentsCounter
())
1696 self
.__addFile
(file)
1698 def deleteFilesNotInList(self
, keys
):
1699 """This method is used in order to delete all the files that are not present (by id) in the
1701 This is useful when files are deleted from the abstract form using Javascript, and so it is
1702 the only way to know that they are deleted.
1704 existingKeys
= self
.getAttachments().keys()
1705 for key
in existingKeys
:
1707 self
._deleteFile
(key
)
1709 def _deleteFile(self
, key
):
1710 file = self
.getAttachments()[key
]
1712 del self
.getAttachments()[key
]
1713 self
._notifyModification
()
1715 def removeResource(self
, res
):
1716 """Necessary because LocalFile.delete (see _deleteFile) is calling this method.
1717 In our case, nothing to do.
1721 def _setOwner( self
, owner
):
1724 def getOwner( self
):
1727 def _setId( self
, id ):
1728 self
._id
= str( id )
1733 def _setSubmissionDate( self
, newDate
):
1734 self
._submissionDate
= newDate
1736 def setModificationDate(self
, dt
= None):
1738 self
._modificationDate
= dt
1740 self
._modificationDate
= nowutc()
1742 def _notifyModification( self
, dt
=None ):
1743 self
.setModificationDate(dt
)
1746 def getModificationDate( self
):
1747 return self
._modificationDate
1749 def _setSubmitter( self
, av
):
1751 raise MaKaCError( _("An abstract must have a submitter"))
1753 self
.getOwner().unregisterParticipation( self
._submitter
)
1754 self
._submitter
.getUser().unlinkTo(self
, "submitter")
1755 self
._submitter
.delete()
1756 self
._submitter
=Submitter( self
, av
)
1757 av
.linkTo(self
, "submitter")
1758 self
.getOwner().registerParticipation( self
._submitter
)
1759 self
._notifyModification
()
1761 def recoverSubmitter(self
, subm
):
1763 raise MaKaCError( _("An abstract must have a submitter"))
1765 self
.getOwner().unregisterParticipation( self
._submitter
)
1766 self
._submitter
.delete()
1767 self
._submitter
= subm
1768 self
._submitter
.setAbstract(self
)
1769 self
.getOwner().registerParticipation( self
._submitter
)
1771 self
._notifyModification
()
1773 def setSubmitter( self
, av
):
1774 self
._setSubmitter
(av
)
1776 def getSubmitter( self
):
1777 return self
._submitter
1779 def isSubmitter( self
, av
):
1780 return self
.getSubmitter().representsUser( av
)
1782 def setTitle(self
, title
):
1783 self
._title
= title
.strip()
1784 self
._notifyModification
()
1789 def getFields(self
):
1792 def removeField(self
, field
):
1793 if self
.getFields().has_key(field
):
1794 del self
.getFields()[field
]
1795 self
._notifyModification
()
1797 def setField(self
, fid
, v
):
1798 if isinstance(v
, AbstractFieldContent
):
1801 self
.getFields()[fid
].value
= v
1802 self
._notifyModification
()
1804 afm
= self
.getConference().getAbstractMgr().getAbstractFieldsMgr()
1805 f
= next(f
for f
in afm
.getFields() if f
.getId() == fid
)
1807 self
.getFields()[fid
] = AbstractFieldContent(f
, v
)
1809 def getField(self
, field
):
1810 if self
.getFields().has_key(field
):
1811 return self
.getFields()[field
]
1815 def getSubmissionDate( self
):
1817 if self
._submissionDate
:
1819 except AttributeError:
1820 self
._submissionDate
=nowutc()
1821 return self
._submissionDate
1823 def getConference( self
):
1824 mgr
= self
.getOwner()
1825 return mgr
.getOwner() if mgr
else None
1827 def _newAuthor( self
, **data
):
1828 author
= Author( self
, **data
)
1829 author
.setId( self
._authorGen
.newCount() )
1830 self
._authors
[ author
.getId() ] = author
1833 def _removeAuthor(self
,part
):
1834 if not self
.isAuthor(part
):
1837 del self
._authors
[part
.getId()]
1839 def isAuthor( self
, part
):
1840 return self
._authors
.has_key( part
.getId() )
1842 def getAuthorList( self
):
1843 return self
._authors
.values()
1845 def getAuthorById(self
, id):
1846 return self
._authors
.get(str(id), None)
1848 def clearAuthors( self
):
1849 self
.clearPrimaryAuthors()
1850 self
.clearCoAuthors()
1851 self
._notifyModification
()
1853 def newPrimaryAuthor(self
,**data
):
1854 auth
=self
._newAuthor
(**data
)
1855 self
._addPrimaryAuthor
(auth
)
1856 self
._notifyModification
()
1859 def isPrimaryAuthor( self
, part
):
1860 return part
in self
._primaryAuthors
1862 def getPrimaryAuthorList( self
):
1863 return self
._primaryAuthors
1864 #XXX: I keep it for compatibility but it should be removed
1865 getPrimaryAuthorsList
= getPrimaryAuthorList
1867 def getPrimaryAuthorEmailList(self
, lower
=False):
1869 for pAuthor
in self
.getPrimaryAuthorList():
1870 emailList
.append(pAuthor
.getEmail().lower() if lower
else pAuthor
.getEmail())
1873 def clearPrimaryAuthors(self
):
1874 while len(self
._primaryAuthors
)>0:
1875 self
._removePrimaryAuthor
(self
._primaryAuthors
[0])
1876 self
._notifyModification
()
1878 def _addPrimaryAuthor( self
, part
):
1879 if not self
.isAuthor( part
):
1880 raise MaKaCError( _("The participation you want to set as primary author is not an author of the abstract"))
1881 if part
in self
._primaryAuthors
:
1883 self
._primaryAuthors
.append( part
)
1884 self
.getOwner().indexAuthor(part
)
1886 def _removePrimaryAuthor(self
,part
):
1887 if not self
.isPrimaryAuthor(part
):
1889 if self
.isSpeaker(part
):
1890 self
.removeSpeaker(part
)
1891 self
.getOwner().unindexAuthor(part
)
1892 self
._primaryAuthors
.remove(part
)
1893 self
._removeAuthor
(part
)
1895 def recoverPrimaryAuthor(self
, auth
):
1896 self
._authors
[ auth
.getId() ] = auth
1897 auth
.setAbstract(self
)
1898 self
._addPrimaryAuthor
(auth
)
1900 self
._notifyModification
()
1902 def newCoAuthor(self
,**data
):
1903 auth
=self
._newAuthor
(**data
)
1904 self
._addCoAuthor
(auth
)
1905 self
._notifyModification
()
1908 def _comp_CoAuthors(self
):
1910 if self
._coAuthors
!=None:
1912 except AttributeError:
1913 self
._coAuthors
=PersistentList()
1914 for auth
in self
._authors
.values():
1915 if not self
.isPrimaryAuthor(auth
):
1916 self
._addCoAuthor
(auth
)
1918 def isCoAuthor( self
, part
):
1919 self
._comp
_CoAuthors
()
1920 return part
in self
._coAuthors
1922 def getCoAuthorList( self
):
1923 self
._comp
_CoAuthors
()
1924 return self
._coAuthors
1926 def getCoAuthorEmailList(self
, lower
=False):
1928 for coAuthor
in self
.getCoAuthorList():
1929 emailList
.append(coAuthor
.getEmail().lower() if lower
else coAuthor
.getEmail())
1932 def clearCoAuthors(self
):
1933 while len(self
._coAuthors
)>0:
1934 self
._removeCoAuthor
(self
._coAuthors
[0])
1935 self
._notifyModification
()
1937 def _addCoAuthor( self
, part
):
1938 self
._comp
_CoAuthors
()
1939 if not self
.isAuthor( part
):
1940 raise MaKaCError( _("The participation you want to set as primary author is not an author of the abstract"))
1941 if part
in self
._coAuthors
:
1943 self
._coAuthors
.append( part
)
1945 def _removeCoAuthor(self
,part
):
1946 if not self
.isCoAuthor(part
):
1948 if self
.isSpeaker(part
):
1949 self
.removeSpeaker(part
)
1950 self
._coAuthors
.remove(part
)
1951 self
._removeAuthor
(part
)
1953 def recoverCoAuthor(self
, auth
):
1954 self
._authors
[ auth
.getId() ] = auth
1955 auth
.setAbstract(self
)
1956 self
._addCoAuthor
(auth
)
1958 self
._notifyModification
()
1960 def addSpeaker( self
, part
):
1961 if not self
.isAuthor( part
):
1962 raise MaKaCError( _("The participation you want to set as speaker is not an author of the abstract"))
1963 if part
in self
._speakers
:
1965 self
._speakers
.append( part
)
1966 self
._notifyModification
()
1968 def removeSpeaker(self
,part
):
1969 if part
not in self
._speakers
:
1971 self
._speakers
.remove(part
)
1973 def clearSpeakers( self
):
1974 while len(self
.getSpeakerList()) > 0:
1975 self
.removeSpeaker(self
.getSpeakerList()[0])
1976 self
._speakers
= PersistentList()
1978 def getSpeakerList( self
):
1979 return self
._speakers
1981 def isSpeaker( self
, part
):
1982 return part
in self
._speakers
1984 def setContribType( self
, contribType
):
1985 self
._contribTypes
[0] = contribType
1986 self
._notifyModification
()
1988 def getContribType( self
):
1989 return self
._contribTypes
[0]
1992 def _addTrack( self
, track
):
1993 """Adds the specified track to the suggested track list. Any
1994 verification must be done by the caller.
1996 self
._tracks
[ track
.getId() ] = track
1997 track
.addAbstract( self
)
1998 self
._notifyModification
()
2000 def addTrack( self
, track
):
2001 self
._changeTracksImpl
()
2002 if not self
._tracks
.has_key( track
.getId() ):
2003 self
._addTrack
( track
)
2004 self
.getCurrentStatus().update()
2006 def _removeTrack( self
, track
):
2007 """Removes the specified track from the track list. Any verification
2008 must be done by the caller.
2010 del self
._tracks
[ track
.getId() ]
2011 track
.removeAbstract( self
)
2012 self
._notifyModification
()
2014 def removeTrack( self
, track
):
2015 if self
._tracks
.has_key( track
.getId() ):
2016 self
._removeTrack
( track
)
2017 self
.getCurrentStatus().update()
2018 if isinstance(self
.getCurrentStatus(), AbstractStatusAccepted
):
2019 self
.getCurrentStatus()._setTrack
(None)
2021 def _changeTracksImpl( self
):
2022 if self
._tracks
.__class
__ != OOBTree
:
2023 oldTrackList
= self
._tracks
2024 self
._tracks
= OOBTree()
2025 for track
in oldTrackList
:
2026 self
._addTrack
( track
)
2027 self
.getCurrentStatus().update()
2029 def getTrackList( self
):
2030 self
._changeTracksImpl
()
2032 return self
._tracks
.values()
2034 def getAcceptedTrack(self
):
2035 status
= self
.getCurrentStatus()
2038 if isinstance(status
, AbstractStatusAccepted
):
2039 return status
.getTrack()
2041 def hasTrack( self
, track
):
2042 self
._changeTracksImpl
()
2044 return self
._tracks
.has_key( track
.getId() )
2046 def getTrackListSorted( self
):
2047 self
._changeTracksImpl
()
2048 return self
.getConference().sortTrackList( self
._tracks
.values() )
2050 def clearTracks( self
):
2051 self
._changeTracksImpl
()
2053 while len(self
.getTrackList())>0:
2054 track
= self
.getTrackList()[0]
2055 self
._removeTrack
( track
)
2056 self
.getCurrentStatus().update()
2058 def setTracks( self
, trackList
):
2059 """Set the suggested track classification of the current abstract to
2062 #We need to do it in 2 steps otherwise the list over which we are
2063 # iterating gets modified
2065 toBeAdded
= copy( trackList
)
2066 for track
in self
.getTrackList():
2067 if track
not in trackList
:
2068 toBeRemoved
.append( track
)
2070 toBeAdded
.remove( track
)
2071 for track
in toBeRemoved
:
2072 self
._removeTrack
( track
)
2073 for track
in toBeAdded
:
2074 self
._addTrack
( track
)
2075 self
.getCurrentStatus().update()
2077 def isProposedForTrack( self
, track
):
2078 return self
._tracks
.has_key( track
.getId() )
2080 def getNumTracks(self
):
2081 return len( self
._tracks
)
2083 def getLocator(self
):
2084 loc
= self
.getConference().getLocator()
2085 loc
["abstractId"] = self
.getId()
2088 def isAllowedToCoordinate(self
, av
):
2089 """Tells whether or not the specified user can coordinate any of the
2090 tracks of this abstract
2092 for track
in self
.getTrackList():
2093 if track
.canUserCoordinate(av
):
2097 def canAuthorAccess(self
, user
):
2100 el
= self
.getCoAuthorEmailList(True)+self
.getPrimaryAuthorEmailList(True)
2101 for e
in user
.getEmails():
2106 def isAllowedToAccess(self
, av
):
2107 """Tells whether or not an avatar can access an abstract independently
2110 #any author is allowed to access
2111 #CFA managers are allowed to access
2112 #any user being able to modify is also allowed to access
2113 #any TC is allowed to access
2114 if self
.canAuthorAccess(av
):
2116 if self
.isAllowedToCoordinate(av
):
2118 return self
.canUserModify(av
)
2120 def canAccess(self
, aw
):
2121 #if the conference is protected, then only allowed AW can access
2122 return self
.isAllowedToAccess(aw
.getUser())
2124 def canView(self
, aw
):
2125 #in the future it would be possible to add an access control
2126 #only those users allowed to access are allowed to view
2127 return self
.isAllowedToAccess(aw
.getUser())
2129 def canModify(self
, aw_or_user
):
2130 if hasattr(aw_or_user
, 'getUser'):
2131 aw_or_user
= aw_or_user
.getUser()
2132 return self
.canUserModify(aw_or_user
)
2134 def canUserModify(self
, av
):
2135 #the submitter can modify
2136 if self
.isSubmitter(av
):
2138 #??? any CFA manager can modify
2139 #??? any user granted with modification privileges can modify
2140 #conference managers can modify
2141 conf
= self
.getConference()
2142 return conf
.canUserModify(av
)
2144 def getModifKey(self
):
2147 def getAccessKey(self
):
2150 def getAccessController(self
):
2151 return self
.getConference().getAccessController()
2153 def isProtected(self
):
2154 return self
.getConference().isProtected()
2158 self
.getOwner().unregisterParticipation(self
._submitter
)
2159 self
._submitter
.getUser().unlinkTo(self
, "submitter")
2160 self
._submitter
.delete()
2161 self
._submitter
= None
2163 self
.clearSpeakers()
2167 owner
.removeAbstract(self
)
2168 self
.setCurrentStatus(AbstractStatusNone(self
))
2169 TrashCanManager().add(self
)
2171 def recoverFromTrashCan(self
):
2172 TrashCanManager().remove(self
)
2174 def getCurrentStatus(self
):
2176 if self
._currentStatus
:
2178 except AttributeError, e
:
2179 self
._currentStatus
= AbstractStatusSubmitted(self
)
2180 return self
._currentStatus
2182 def setCurrentStatus(self
, newStatus
):
2183 self
._currentStatus
= newStatus
2184 #If we want to keep a history of status changes we should add here
2185 # the old status to a list
2187 def accept(self
, responsible
, destTrack
, type, comments
="", session
=None):
2190 self
.getCurrentStatus().accept(responsible
, destTrack
, type, comments
)
2191 #add the abstract to the track for which it has been accepted so it
2192 # is visible for it.
2193 if destTrack
is not None:
2194 destTrack
.addAbstract(self
)
2195 #once the abstract is accepted a new contribution under the destination
2196 # track must be created
2197 # ATTENTION: This import is placed here explicitely for solving
2198 # problems with circular imports
2199 from MaKaC
.conference
import AcceptedContribution
2200 contrib
= AcceptedContribution(self
)
2202 contrib
.setSession(session
)
2203 contrib
.setDuration(dur
=session
.getContribDuration())
2205 contrib
.setDuration()
2206 self
.getCurrentStatus().setContribution(contrib
)
2207 self
._setContribution
(contrib
)
2209 def reject(self
, responsible
, comments
=""):
2212 self
.getCurrentStatus().reject(responsible
, comments
)
2214 def _cmpByDate(self
, tj1
, tj2
):
2215 return cmp(tj1
.getDate(), tj2
.getDate())
2217 def getTrackJudgementsHistorical(self
):
2219 if self
._trackJudgementsHistorical
:
2221 if type(self
._trackJudgementsHistorical
) == tuple:
2222 self
._trackJudgementsHistorical
= {}
2223 except AttributeError:
2224 self
._trackJudgementsHistorical
= {}
2225 for track
in self
.getTrackList():
2227 if self
.getTrackAcceptances().has_key(track
.getId()):
2228 judgement
= self
.getTrackAcceptances()[track
.getId()]
2229 elif self
.getTrackRejections().has_key(track
.getId()):
2230 judgement
= self
.getTrackRejections()[track
.getId()]
2231 elif self
.getTrackReallocations().has_key(track
.getId()):
2232 judgement
= self
.getTrackReallocations()[track
.getId()]
2233 self
._trackJudgementsHistorical
[track
.getId()] = [judgement
]
2234 self
._notifyModification
()
2235 return self
._trackJudgementsHistorical
2237 def getJudgementHistoryByTrack(self
, track
):
2239 if track
is not None:
2241 if self
.getTrackJudgementsHistorical().has_key(id):
2242 return self
.getTrackJudgementsHistorical()[id]
2245 def _addTrackJudgementToHistorical(self
, tj
):
2247 if tj
.getTrack() is not None:
2248 id = tj
.getTrack().getId()
2249 if self
.getTrackJudgementsHistorical().has_key(id):
2250 if tj
not in self
.getTrackJudgementsHistorical()[id]:
2251 self
.getTrackJudgementsHistorical()[id].insert(0, tj
)
2253 self
.getTrackJudgementsHistorical()[id] = [tj
]
2254 self
._notifyModification
()
2256 def _removeTrackAcceptance( self
, track
):
2259 if self
.getTrackAcceptances().has_key( track
.getId() ):
2260 del self
.getTrackAcceptances()[ track
.getId() ]
2262 def _addTrackAcceptance( self
, judgement
):
2265 self
._removeTrackRejection
( judgement
.getTrack() )
2266 self
._removeTrackReallocation
( judgement
.getTrack() )
2267 self
.getTrackAcceptances()[ judgement
.getTrack().getId() ] = judgement
2268 self
._addTrackJudgementToHistorical
(judgement
)
2270 def _removeTrackRejection( self
, track
):
2273 if self
.getTrackRejections().has_key( track
.getId() ):
2274 del self
.getTrackRejections()[ track
.getId() ]
2276 def _addTrackRejection( self
, judgement
):
2279 self
._removeTrackAcceptance
( judgement
.getTrack() )
2280 self
._removeTrackReallocation
( judgement
.getTrack() )
2281 self
.getTrackRejections()[ judgement
.getTrack().getId() ] = judgement
2282 self
._addTrackJudgementToHistorical
(judgement
)
2284 def _removeTrackReallocation( self
, track
):
2287 if self
.getTrackReallocations().has_key( track
.getId() ):
2288 del self
.getTrackReallocations()[ track
.getId() ]
2290 def _addTrackReallocation( self
, judgement
):
2293 self
._removeTrackAcceptance
( judgement
.getTrack() )
2294 self
._removeTrackRejection
( judgement
.getTrack() )
2295 self
.getTrackReallocations()[ judgement
.getTrack().getId() ] = judgement
2296 self
._addTrackJudgementToHistorical
(judgement
)
2298 def _clearTrackRejections( self
):
2299 while len(self
.getTrackRejections().values())>0:
2300 t
= self
.getTrackRejections().values()[0].getTrack()
2301 self
._removeTrackRejection
( t
)
2303 def _clearTrackAcceptances( self
):
2304 while len(self
.getTrackAcceptances().values())>0:
2305 t
= self
.getTrackAcceptances().values()[0].getTrack()
2306 self
._removeTrackAcceptance
( t
)
2308 def _clearTrackReallocations( self
):
2309 while len(self
.getTrackReallocations().values())>0:
2310 t
= self
.getTrackReallocations().values()[0].getTrack()
2311 self
._removeTrackReallocation
(t
)
2313 def _removePreviousJud(self
, responsible
, track
):
2314 ''' Check if there is a previous judgement and remove it '''
2315 toDelete
= [] # list of judgements to delete
2316 for jud
in self
.getJudgementHistoryByTrack(track
):
2317 if jud
.getResponsible() == responsible
:
2318 toDelete
.append(jud
)
2321 self
.getTrackJudgementsHistorical()[track
.getId()].remove(x
)
2324 def proposeToAccept( self
, responsible
, track
, contribType
, comment
="", answers
=[] ):
2327 # the proposal has to be done for a track
2329 raise MaKaCError( _("You have to choose a track in order to do the proposal. If there are not tracks to select, please change the track assignment of the abstract"))
2330 #We check the track for which the abstract is proposed to be accepted
2331 # is in the current abstract
2332 if not self
.isProposedForTrack( track
):
2333 raise MaKaCError( _("Cannot propose to accept an abstract which is not proposed for the specified track"))
2334 # check if there is a previous judgement of this author in for this abstract in this track
2335 self
._removePreviousJud
(responsible
, track
)
2336 # Create the new judgement
2337 jud
= AbstractAcceptance( track
, responsible
, contribType
, answers
)
2338 jud
.setComment( comment
)
2339 self
._addTrackAcceptance
( jud
)
2340 # Update the rating of the abstract
2342 #We trigger the state transition
2343 self
.getCurrentStatus().proposeToAccept()
2345 def proposeToReject( self
, responsible
, track
, comment
="", answers
=[] ):
2348 # the proposal has to be done for a track
2350 raise MaKaCError( _("You have to choose a track in order to do the proposal. If there are not tracks to select, please change the track assignment of the abstract"))
2351 #We check the track for which the abstract is proposed to be accepted
2352 # is in the current abstract
2353 if not self
.isProposedForTrack( track
):
2354 raise MaKaCError( _("Cannot propose to reject an abstract which is not proposed for the specified track"))
2355 # check if there is a previous judgement of this author in for this abstract in this track
2356 self
._removePreviousJud
(responsible
, track
)
2357 # Create the new judgement
2358 jud
= AbstractRejection( track
, responsible
, answers
)
2359 jud
.setComment( comment
)
2360 self
._addTrackRejection
( jud
)
2361 # Update the rating of the abstract
2363 #We trigger the state transition
2364 self
.getCurrentStatus().proposeToReject()
2366 def proposeForOtherTracks( self
, responsible
, track
, comment
, propTracks
, answers
=[] ):
2369 #We check the track which proposes to allocate the abstract is in the
2371 if not self
.isProposedForTrack( track
):
2372 raise MaKaCError( _("Cannot propose to reallocate an abstract which is not proposed for the specified track"))
2373 # check if there is a previous judgement of this author in for this abstract in this track
2374 self
._removePreviousJud
(responsible
, track
)
2375 #We keep the track judgement
2376 jud
= AbstractReallocation( track
, responsible
, propTracks
, answers
)
2377 jud
.setComment( comment
)
2378 self
._addTrackReallocation
( jud
)
2379 #We add the proposed tracks to the abstract
2380 for track
in propTracks
:
2381 self
._addTrack
( track
)
2382 #We trigger the state transition
2383 self
.getCurrentStatus().proposeToReallocate()
2384 # Update the rating of the abstract
2387 def withdraw(self
,resp
,comment
=""):
2390 self
.getCurrentStatus().withdraw(resp
,comment
)
2392 def recover( self
):
2393 """Puts a withdrawn abstract back in the list of submitted abstracts.
2394 HAS NOTHING TO DO WITH THE RECOVERY PROCESS...
2396 #we must clear any track judgement
2397 #self._clearTrackAcceptances()
2398 #self._clearTrackRejections()
2399 #self._clearTrackReallocations()
2400 self
.getCurrentStatus().recover() #status change
2401 #if succeeded we must reset the submission date
2402 self
._setSubmissionDate
( nowutc() )
2403 self
._notifyModification
()
2405 def getTrackJudgement( self
, track
):
2406 if not self
.getJudgementHistoryByTrack(track
):
2408 lastJud
= self
.getJudgementHistoryByTrack(track
)[0]
2409 # check if judgements for specified trak are the same. If not there is a conflict.
2410 if all(jud
.__class
__ == lastJud
.__class
__ for jud
in self
.getJudgementHistoryByTrack(track
)):
2412 return AbstractInConflict(track
)
2414 def getTrackAcceptances( self
):
2416 if self
._trackAcceptances
:
2418 except AttributeError, e
:
2419 self
._trackAcceptances
= OOBTree()
2420 return self
._trackAcceptances
2422 def getTrackAcceptanceList( self
):
2424 for trackId
in intersection( self
._tracks
, self
.getTrackAcceptances() ):
2425 res
.append( self
.getTrackAcceptances()[ trackId
] )
2428 def getNumProposedToAccept( self
):
2429 return len( intersection( self
._tracks
, self
.getTrackAcceptances() ) )
2431 def getTrackRejections( self
):
2433 if self
._trackRejections
:
2435 except AttributeError, e
:
2436 self
._trackRejections
= OOBTree()
2437 return self
._trackRejections
2439 def getNumProposedToReject( self
):
2440 return len( intersection( self
._tracks
, self
.getTrackRejections() ) )
2442 def getTrackReallocations( self
):
2444 if self
._trackReallocations
:
2446 except AttributeError, e
:
2447 self
._trackReallocations
= OOBTree()
2448 return self
._trackReallocations
2451 def getNumProposedToReallocate( self
):
2452 return len( intersection( self
._tracks
, self
.getTrackReallocations() ) )
2455 def getNumJudgements( self
):
2457 Returns the number of tracks for which some proposal has been done.
2458 For instance, let's suppose:
2459 Track 1: 2 propose to accept, 3 propose to reject
2460 Track 2: 1 propose to accept
2462 The result would be 2 (out of 3)
2464 tmp1
= union( self
.getTrackAcceptances(), self
.getTrackRejections() )
2465 judgements
= union( tmp1
, self
.getTrackReallocations() )
2466 return len( intersection( self
._tracks
, judgements
) )
2468 def getReallocationTargetedList( self
, track
):
2471 for r
in self
.getTrackReallocations().values():
2472 if track
in r
.getProposedTrackList():
2476 def getContribution( self
):
2478 if self
._contribution
:
2480 except AttributeError:
2481 self
._contribution
= None
2482 status
= self
.getCurrentStatus()
2483 if isinstance(status
,AbstractStatusAccepted
) and \
2484 self
._contribution
is None:
2485 self
._contribution
=status
.getContribution()
2486 return self
._contribution
2488 def _setContribution(self
,contrib
):
2489 self
._contribution
= contrib
2491 def getIntCommentList(self
):
2493 if self
._intComments
:
2495 except AttributeError:
2496 self
._intComments
=PersistentList()
2497 return self
._intComments
2499 def addIntComment(self
,newComment
):
2501 if self
._intComments
:
2503 except AttributeError:
2504 self
._intComments
=PersistentList()
2506 if self
._intCommentsGen
:
2508 except AttributeError:
2509 self
._intCommentsGen
=Counter()
2510 if newComment
in self
._intComments
:
2512 id = newComment
.getId()
2514 id = self
._authorGen
.newCount()
2515 newComment
.includeInAbstract(self
, id)
2516 self
._intComments
.append(newComment
)
2518 def getIntCommentById(self
,id):
2520 if self
._intComments
:
2522 except AttributeError:
2523 self
._intComments
=PersistentList()
2524 for comment
in self
._intComments
:
2525 if id.strip()==comment
.getId():
2529 def clearIntCommentList(self
):
2530 while len(self
.getIntCommentList()) > 0:
2531 self
.removeIntComment(self
.getIntCommentList()[0])
2533 def removeIntComment(self
,comment
):
2535 if self
._intComments
:
2537 except AttributeError:
2538 self
._intComments
=PersistentList()
2539 if comment
not in self
._intComments
:
2541 self
._intComments
.remove(comment
)
2544 def recoverIntComment(self
, comment
):
2545 self
.addIntComment(comment
)
2548 def markAsDuplicated(self
,responsible
,originalAbstract
,comments
="", track
=None, answers
=[]):
2551 self
.getCurrentStatus().markAsDuplicated(responsible
,originalAbstract
,comments
)
2552 # check if there is a previous judgement of this author in for this abstract in this track
2553 self
._removePreviousJud
(responsible
, track
)
2555 if track
is not None:
2556 jud
= AbstractMarkedAsDuplicated( track
, responsible
, originalAbstract
, answers
)
2557 jud
.setComment( comments
)
2558 self
._addTrackJudgementToHistorical
(jud
)
2560 for t
in self
.getTrackList():
2561 jud
= AbstractMarkedAsDuplicated( t
, responsible
, originalAbstract
, answers
)
2562 jud
.setComment( comments
)
2563 self
._addTrackJudgementToHistorical
(jud
)
2564 # Update the rating of the abstract
2567 def unMarkAsDuplicated(self
,responsible
,comments
="", track
=None, answers
=[]):
2571 #we must clear any track judgement
2572 self
._clearTrackAcceptances
()
2573 self
._clearTrackRejections
()
2574 self
._clearTrackReallocations
()
2575 #self.getCurrentStatus().recover() #status change
2576 self
.getCurrentStatus().unMarkAsDuplicated(responsible
,comments
)
2578 # check if there is a previous judgement of this author in for this abstract in this track
2579 self
._removePreviousJud
(responsible
, track
)
2581 if track
is not None:
2582 jud
= AbstractUnMarkedAsDuplicated(track
, responsible
, answers
)
2583 jud
.setComment( comments
)
2584 self
._addTrackJudgementToHistorical
(jud
)
2586 for t
in self
.getTrackList():
2587 jud
= AbstractUnMarkedAsDuplicated( t
, responsible
, answers
)
2588 jud
.setComment( comments
)
2589 self
._addTrackJudgementToHistorical
(jud
)
2590 # Update the rating of the abstract
2592 self
._notifyModification
()
2594 def mergeInto(self
,responsible
,targetAbs
,mergeAuthors
=False,comments
=""):
2597 self
.getCurrentStatus().mergeInto(responsible
,targetAbs
,comments
)
2598 targetAbs
.addMergeFromAbstract(self
)
2600 #for auth in self.getAuthorList():
2601 # newAuth=targetAbs.newAuthor()
2602 # newAuth.setFromAbstractParticipation(auth)
2603 # if self.isPrimaryAuthor(auth):
2604 # targetAbs.addPrimaryAuthor(newAuth)
2605 for auth
in self
.getPrimaryAuthorList():
2606 newAuth
=targetAbs
.newPrimaryAuthor()
2607 newAuth
.setFromAbstractParticipation(auth
)
2608 for auth
in self
.getCoAuthorList():
2609 newAuth
=targetAbs
.newCoAuthor()
2610 newAuth
.setFromAbstractParticipation(auth
)
2612 def notify(self
,notificator
,responsible
):
2613 """notifies the abstract responsibles with a matching template
2615 tpl
=self
.getOwner().getNotifTplForAbstract(self
)
2618 notificator
.notify(self
,tpl
)
2619 self
.getNotificationLog().addEntry(NotifLogEntry(responsible
,tpl
))
2621 def unMerge(self
,responsible
,comments
=""):
2622 #we must clear any track judgement
2623 self
._clearTrackAcceptances
()
2624 self
._clearTrackRejections
()
2625 self
._clearTrackReallocations
()
2626 self
.getCurrentStatus().getTargetAbstract().removeMergeFromAbstract(self
)
2627 self
.getCurrentStatus().unMerge(responsible
,comments
)
2628 self
._notifyModification
()
2630 def getNotificationLog(self
):
2634 except AttributeError:
2635 self
._notifLog
=NotificationLog(self
)
2636 return self
._notifLog
2639 def getRating(self
):
2640 """ Get the average rating of the abstract """
2644 except AttributeError:
2648 def updateRating(self
, scale
= None):
2650 Update the average rating of the abstract which is calculated with the average of each judgement.
2651 If the scale (tuple with lower,higher) is passed, the judgement are re-adjusted to the new scale.
2654 # calculate the total valoration
2657 for track
in self
.getTrackListSorted():
2658 for jud
in self
.getJudgementHistoryByTrack(track
):
2660 # calculate the new values for each judgement
2661 scaleLower
, scaleHigher
= scale
2662 jud
.recalculateJudgementValues(scaleLower
, scaleHigher
)
2663 if jud
.getJudValue() != None: # it means there is a numeric value for the judgement
2664 ratingSum
+= jud
.getJudValue()
2666 # Calculate the average
2668 self
._rating
= float(ratingSum
) / judNum
2670 def getQuestionsAverage(self
):
2671 '''Get the list of questions answered in the reviews for an abstract '''
2672 dTotals
= {} # {idQ1: total_value, idQ2: total_value ...}
2673 dTimes
= {} # {idQ1: times_answered, idQ2: times_answered}
2674 for track
in self
.getTrackListSorted():
2675 for jud
in self
.getJudgementHistoryByTrack(track
):
2676 for answer
in jud
.getAnswers():
2677 # check if the question is in d and sum the answers value or insert in d the new question
2678 if dTotals
.has_key(answer
.getQuestion().getText()):
2679 dTotals
[answer
.getQuestion().getText()] += answer
.getValue()
2680 dTimes
[answer
.getQuestion().getText()] += 1
2682 dTotals
[answer
.getQuestion().getText()] = answer
.getValue()
2683 dTimes
[answer
.getQuestion().getText()] = 1
2684 # get the questions average
2685 questionsAverage
= {}
2686 for q
, v
in dTotals
.iteritems():
2687 # insert the element and calculate the average for the value
2688 questionsAverage
[q
] = float(v
)/dTimes
[q
]
2689 return questionsAverage
2691 def removeAnswersOfQuestion(self
, questionId
):
2692 ''' Remove the answers of the question with questionId value '''
2693 for track
in self
.getTrackListSorted():
2694 for jud
in self
.getJudgementHistoryByTrack(track
):
2695 jud
.removeAnswer(questionId
)
2697 def getRatingPerReviewer(self
, user
, track
):
2699 Get the rating of the user for the abstract in the track given.
2701 for jud
in self
.getJudgementHistoryByTrack(track
):
2702 if (jud
.getResponsible() == user
):
2703 return jud
.getJudValue()
2705 def getLastJudgementPerReviewer(self
, user
, track
):
2707 Get the last judgement of the user for the abstract in the track given.
2709 for jud
in self
.getJudgementHistoryByTrack(track
):
2710 if (jud
.getResponsible() == user
):
2713 def _getAttachmentsCounter(self
):
2715 if self
._attachmentsCounter
:
2717 except AttributeError:
2718 self
._attachmentsCounter
= Counter()
2719 return self
._attachmentsCounter
.newCount()
2721 def setAttachments(self
, attachments
):
2722 self
._attachments
= attachments
2724 def getAttachments(self
):
2726 if self
._attachments
:
2728 except AttributeError:
2729 self
._attachments
= {}
2730 return self
._attachments
2732 def getAttachmentById(self
, id):
2733 return self
.getAttachments().get(id, None)
2736 class AbstractJudgement( Persistent
):
2737 """This class represents each of the judgements made by a track about a
2738 certain abstract. Each track for which an abstract is proposed can
2739 make a judgement proposing the abstract to be accepted or rejected.
2740 Different track judgements must be kept so the referees who have to
2741 take the final decission can overview different opinions from the
2743 Together with the judgement some useful information like the date when
2744 it was done and the user who did it will be kept.
2747 def __init__( self
, track
, responsible
, answers
):
2749 self
._setResponsible
( responsible
)
2750 self
._date
= nowutc()
2752 self
._answers
= answers
2753 self
._judValue
= self
.calculateJudgementAverage() # judgement average value
2754 self
._totalJudValue
= self
.calculateAnswersTotalValue()
2757 def _setResponsible( self
, newRes
):
2758 self
._responsible
= newRes
2760 def getResponsible( self
):
2761 return self
._responsible
2763 def getDate( self
):
2766 def setDate(self
, date
):
2769 def getTrack( self
):
2772 def setComment( self
, newComment
):
2773 self
._comment
= newComment
.strip()
2775 def getComment( self
):
2776 return self
._comment
2778 def getAnswers(self
):
2782 except AttributeError:
2784 return self
._answers
2786 def calculateJudgementAverage(self
):
2787 '''Calculate the average value of the given answers'''
2789 if (len(self
.getAnswers()) != 0):
2790 # convert the values into float types
2791 floatList
= [ans
.getValue() for ans
in self
._answers
]
2792 result
= sum(floatList
) / float(len(floatList
)) # calculate the average
2794 # there are no questions
2798 def getJudValue(self
):
2802 except AttributeError:
2803 self
._judValue
= self
.calculateJudgementAverage() # judgement average value
2804 return self
._judValue
2806 def getTotalJudValue(self
):
2808 if self
._totalJudValue
:
2810 except AttributeError:
2811 self
._totalJudValue
= self
.calculateAnswersTotalValue()
2812 return self
._totalJudValue
2814 def calculateAnswersTotalValue(self
):
2815 ''' Calculate the sum of all the ratings '''
2817 for ans
in self
.getAnswers():
2818 result
+= ans
.getValue()
2821 def recalculateJudgementValues(self
, scaleLower
, scaleHigher
):
2822 ''' Update the values of the judgement. This function is called when the scale is changed.'''
2823 for ans
in self
.getAnswers():
2824 ans
.calculateRatingValue(scaleLower
, scaleHigher
)
2825 self
._judValue
= self
.calculateJudgementAverage()
2826 self
._totalJudValue
= self
.calculateAnswersTotalValue()
2828 def removeAnswer(self
, questionId
):
2829 ''' Remove the current answers of the questionId '''
2830 for ans
in self
.getAnswers():
2831 if ans
.getQuestion().getId() == questionId
:
2832 self
._answers
.remove(ans
)
2833 self
._notifyModification
()
2835 def _notifyModification(self
):
2839 class AbstractAcceptance( AbstractJudgement
):
2841 def __init__( self
, track
, responsible
, contribType
, answers
):
2842 AbstractJudgement
.__init
__( self
, track
, responsible
, answers
)
2843 self
._contribType
= contribType
2845 def clone(self
,track
):
2846 aa
= AbstractAcceptance(track
,self
.getResponsible(), self
.getContribType(), self
.getAnswers())
2849 def getContribType( self
):
2851 if self
._contribType
:
2853 except AttributeError, e
:
2854 self
._contribType
= None
2855 return self
._contribType
2858 class AbstractRejection( AbstractJudgement
):
2860 def clone(self
, track
):
2861 arj
= AbstractRejection(track
,self
.getResponsible(), self
.getAnswers())
2864 class AbstractReallocation( AbstractJudgement
):
2866 def __init__( self
, track
, responsible
, propTracks
, answers
):
2867 AbstractJudgement
.__init
__( self
, track
, responsible
, answers
)
2868 self
._proposedTracks
= PersistentList( propTracks
)
2870 def clone(self
, track
):
2871 arl
= AbstractReallocation(track
, self
.getResponsible(), self
.getProposedTrackList(), self
.getAnswers())
2874 def getProposedTrackList( self
):
2875 return self
._proposedTracks
2877 class AbstractInConflict( AbstractJudgement
):
2879 def __init__( self
, track
):
2880 AbstractJudgement
.__init
__( self
, track
, None, '' )
2882 def clone(self
, track
):
2883 aic
= AbstractInConflict(track
, None, '')
2886 class AbstractMarkedAsDuplicated( AbstractJudgement
):
2888 def __init__( self
, track
, responsible
, originalAbst
, answers
):
2889 AbstractJudgement
.__init
__( self
, track
, responsible
, answers
)
2890 self
._originalAbst
=originalAbst
2892 def clone(self
,track
):
2893 amad
= AbstractMarkedAsDuplicated(track
,self
.getResponsible(), self
.getOriginalAbstract(), self
.getAnswers())
2896 def getOriginalAbstract(self
):
2897 return self
._originalAbst
2900 class AbstractUnMarkedAsDuplicated( AbstractJudgement
):
2902 def clone(self
,track
):
2903 auad
= AbstractUnMarkedAsDuplicated(track
,self
.getResponsible())
2907 class AbstractStatus( Persistent
):
2908 """This class represents any of the status in which an abstract can be.
2909 From the moment they are submitted (and therefore created), abstracts
2910 can go throuugh different status each having a different meaning.
2911 As there can be many status, the transitions between them are quite
2912 complex and as the system evolves we could require to add or delete
2913 new status the "Status" pattern is applied. This is the base class.
2914 Apart from giving information about the status of an abstract, this
2915 class is responsible to store information about how the status was
2916 reached (who provoke the transition, when, ...).
2920 def __init__( self
, abstract
):
2921 self
._setAbstract
( abstract
)
2922 self
._setDate
( nowutc() )
2927 def _setAbstract( self
, abs ):
2928 self
._abstract
= abs
2930 def getAbstract( self
):
2931 return self
._abstract
2933 def _setDate( self
, date
):
2936 def getDate( self
):
2939 def accept(self
,responsible
,destTrack
,type,comments
=""):
2942 s
= AbstractStatusAccepted(self
.getAbstract(),responsible
,destTrack
,type,comments
)
2943 self
.getAbstract().setCurrentStatus( s
)
2945 def reject( self
, responsible
, comments
= "" ):
2948 s
= AbstractStatusRejected( self
.getAbstract(), responsible
, comments
)
2949 self
.getAbstract().setCurrentStatus( s
)
2951 def _getStatusClass( self
):
2954 numAccepts
= self
._abstract
.getNumProposedToAccept() # number of tracks that have at least one proposal to accept
2955 numReallocate
= self
._abstract
.getNumProposedToReallocate() # number of tracks that have at least one proposal to reallocate
2956 numJudgements
= self
._abstract
.getNumJudgements() # number of tracks that have at least one judgement
2957 if numJudgements
> 0:
2958 # If at least one track status is in conflict the abstract status is in conflict too.
2959 if any(isinstance(self
._abstract
.getTrackJudgement(track
), AbstractInConflict
) for track
in self
._abstract
.getTrackList()):
2960 return AbstractStatusInConflict
2961 numTracks
= self
._abstract
.getNumTracks() # number of tracks that this abstract has assigned
2962 if numTracks
== numJudgements
: # Do we have judgements for all tracks?
2963 if numReallocate
== numTracks
:
2964 return AbstractStatusInConflict
2965 elif numAccepts
== 1:
2966 return AbstractStatusProposedToAccept
2967 elif numAccepts
== 0:
2968 return AbstractStatusProposedToReject
2969 return AbstractStatusInConflict
2970 return AbstractStatusUnderReview
2971 return AbstractStatusSubmitted
2977 newStatusClass
= self
._getStatusClass
()
2978 if self
.__class
__ != newStatusClass
:
2979 self
.getAbstract().setCurrentStatus( newStatusClass( self
._abstract
) )
2981 def proposeToAccept( self
):
2984 s
= self
._getStatusClass
()( self
._abstract
)
2985 self
.getAbstract().setCurrentStatus( s
)
2987 def proposeToReject( self
):
2990 s
= self
._getStatusClass
()( self
._abstract
)
2991 self
.getAbstract().setCurrentStatus( s
)
2993 def proposeToReallocate( self
):
2996 s
= self
._getStatusClass
()( self
._abstract
)
2997 self
.getAbstract().setCurrentStatus( s
)
2999 def withdraw(self
,resp
,comments
=""):
3002 s
=AbstractStatusWithdrawn(self
.getAbstract(), resp
, self
, comments
)
3003 self
.getAbstract().setCurrentStatus(s
)
3005 def recover( self
):
3008 raise MaKaCError( _("only withdrawn abstracts can be recovered"))
3010 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3013 if self
.getAbstract()==originalAbs
:
3014 raise MaKaCError( _("the original abstract is the same as the duplicated one"))
3015 if isinstance(originalAbs
.getCurrentStatus(),AbstractStatusDuplicated
):
3016 raise MaKaCError( _("cannot set as original abstract one which is already marked as duplicated"))
3017 s
=AbstractStatusDuplicated(self
.getAbstract(),responsible
,originalAbs
,comments
)
3018 self
.getAbstract().setCurrentStatus(s
)
3020 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3023 raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3025 def mergeInto(self
,responsible
,targetAbs
,comments
=""):
3028 if self
.getAbstract()==targetAbs
:
3029 raise MaKaCError( _("An abstract cannot be merged into itself"))
3030 if targetAbs
.getCurrentStatus().__class
__ not in [AbstractStatusSubmitted
,AbstractStatusUnderReview
,AbstractStatusProposedToAccept
,AbstractStatusProposedToReject
,AbstractStatusInConflict
]:
3031 raise MaKaCError(_("Target abstract is in a status which cannot receive mergings"))
3032 s
=AbstractStatusMerged(self
.getAbstract(),responsible
,targetAbs
,comments
)
3033 self
.getAbstract().setCurrentStatus(s
)
3035 def unMerge(self
,responsible
,comments
=""):
3038 raise MaKaCError( _("Only merged abstracts can be unmerged"))
3040 def getComments(self
):
3045 class AbstractStatusSubmitted( AbstractStatus
):
3049 def clone(self
,abstract
):
3050 ass
= AbstractStatusSubmitted(abstract
)
3054 #if an abstract that has been submitted has no judgement it
3055 # must remain in the submitted status
3056 if self
._abstract
.getNumJudgements() == 0:
3058 AbstractStatus
.update( self
)
3061 class AbstractStatusAccepted( AbstractStatus
):
3064 def __init__(self
,abstract
,responsible
,destTrack
,type,comments
=""):
3065 AbstractStatus
.__init
__( self
, abstract
)
3066 self
._setResponsible
( responsible
)
3067 self
._setTrack
( destTrack
)
3068 self
._setComments
( comments
)
3069 self
._setType
( type )
3070 self
._contrib
= None
3072 def clone(self
,abstract
):
3073 asa
= AbstractStatusAccepted(abstract
,self
.getResponsible(), self
.getTrack(), self
.getType(), self
.getComments())
3076 def _setResponsible( self
, res
):
3077 self
._responsible
= res
3079 def getResponsible( self
):
3080 return self
._responsible
3082 def _setComments( self
, comments
):
3083 self
._comments
= str( comments
).strip()
3085 def getComments( self
):
3089 except AttributeError:
3091 return self
._comments
3093 def _setTrack( self
, track
):
3096 def getTrack( self
):
3100 except AttributeError:
3104 def _setType( self
, type ):
3105 self
._contribType
= type
3107 def getType( self
):
3109 if self
._contribType
:
3111 except AttributeError:
3112 self
._contribType
= None
3113 return self
._contribType
3115 def setContribution( self
, newContrib
):
3116 self
._contrib
= newContrib
3118 def getContribution( self
):
3122 except AttributeError:
3123 self
._contrib
= None
3124 return self
._contrib
3129 def accept(self
,responsible
,destTrack
,type,comments
="" ):
3130 raise MaKaCError( _("Cannot accept an abstract which is already accepted"))
3132 def reject( self
, responsible
, comments
="" ):
3133 raise MaKaCError( _("Cannot reject an abstract which is already accepted"))
3135 def proposeToAccept( self
):
3136 raise MaKaCError( _("Cannot propose for acceptance an abstract which is already accepted"))
3138 def proposeToReject( self
):
3139 raise MaKaCError( _("Cannot propose for rejection an abstract which is already accepted"))
3141 def proposeToReallocate( self
):
3142 raise MaKaCError( _("Cannot propose for reallocation an abstract which is already accepted"))
3144 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3145 raise MaKaCError( _("Cannot mark as duplicated an abstract which is accepted"))
3147 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3150 raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3152 def mergeInto(self
,responsible
,targetAbs
,comments
=""):
3153 raise MaKaCError( _("Cannot merge an abstract which is already accepted"))
3155 def withdraw(self
,resp
,comments
=""):
3158 contrib
=self
.getContribution()
3159 #this import is made here and not at the top of the file in order to
3160 # avoid recursive import troubles
3161 from MaKaC
.conference
import ContribStatusWithdrawn
3162 if contrib
is not None and \
3163 not isinstance(contrib
.getCurrentStatus(),ContribStatusWithdrawn
):
3164 contrib
.withdraw(resp
, i18nformat(""" _("abstract withdrawn"): %s""")%comments
)
3165 AbstractStatus
.withdraw(self
,resp
,comments
)
3168 class AbstractStatusRejected( AbstractStatus
):
3171 def __init__( self
, abstract
, responsible
, comments
= "" ):
3172 AbstractStatus
.__init
__( self
, abstract
)
3173 self
._setResponsible
( responsible
)
3174 self
._setComments
( comments
)
3176 def clone(self
,abstract
):
3177 asr
= AbstractStatusRejected(abstract
, self
.getResponsible(), self
.getComments())
3180 def _setResponsible( self
, res
):
3181 self
._responsible
= res
3183 def getResponsible( self
):
3184 return self
._responsible
3186 def _setComments( self
, comments
):
3187 self
._comments
= str( comments
).strip()
3189 def getComments( self
):
3193 except AttributeError:
3195 return self
._comments
3200 def reject( self
, responsible
, comments
="" ):
3201 raise MaKaCError( _("Cannot reject an abstract which is already rejected"))
3203 def proposeToAccept( self
):
3204 raise MaKaCError( _("Cannot propose for acceptance an abstract which is already rejected"))
3206 def proposeToReject( self
):
3207 raise MaKaCError( _("Cannot propose for rejection an abstract which is already rejected"))
3209 def proposeToReallocate( self
):
3210 raise MaKaCError( _("Cannot propose for reallocation an abstract which is already rejected"))
3212 def withdraw(self
,resp
,comments
=""):
3213 raise MaKaCError( _("Cannot withdraw a REJECTED abstract"))
3215 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3216 raise MaKaCError( _("Cannot mark as duplicated an abstract which is rejected"))
3218 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3221 raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3223 def mergeInto(self
,responsible
,targetAbs
,comments
=""):
3224 raise MaKaCError( _("Cannot merge an abstract which is rejected"))
3227 class AbstractStatusUnderReview( AbstractStatus
):
3230 def clone(self
,abstract
):
3231 asur
= AbstractStatusUnderReview(abstract
)
3234 class AbstractStatusProposedToAccept( AbstractStatus
):
3237 def clone(self
, abstract
):
3238 aspta
= AbstractStatusProposedToAccept(abstract
)
3242 jud
=self
.getAbstract().getTrackAcceptanceList()[0]
3243 return jud
.getTrack()
3246 jud
=self
.getAbstract().getTrackAcceptanceList()[0]
3247 return jud
.getContribType()
3250 class AbstractStatusProposedToReject( AbstractStatus
):
3253 def clone(self
, abstract
):
3254 asptr
= AbstractStatusProposedToReject(abstract
)
3257 class AbstractStatusInConflict( AbstractStatus
):
3260 def clone(self
,abstract
):
3261 asic
= AbstractStatusInConflict(abstract
)
3264 class AbstractStatusWithdrawn(AbstractStatus
):
3267 def __init__(self
,abstract
,responsible
, prevStatus
,comments
=""):
3268 AbstractStatus
.__init
__(self
,abstract
)
3269 self
._setComments
(comments
)
3270 self
._setResponsible
(responsible
)
3271 self
._prevStatus
=prevStatus
3273 def clone(self
,abstract
):
3274 asw
= AbstractStatusWithdrawn(abstract
,self
.getResponsible(),self
.getComments())
3277 def _setResponsible(self
,newResp
):
3278 self
._responsible
=newResp
3280 def getResponsible(self
):
3282 if self
._responsible
:
3284 except AttributeError,e
:
3285 self
._responsible
=self
._abstract
.getSubmitter().getAvatar()
3286 return self
._responsible
3288 def getPrevStatus(self
):
3290 if self
._prevStatus
:
3292 except AttributeError,e
:
3293 self
._prevStatus
=None
3294 return self
._prevStatus
3296 def _setComments( self
, comments
):
3297 self
._comments
= str( comments
).strip()
3299 def getComments( self
):
3300 return self
._comments
3305 def accept(self
,responsible
,destTrack
,type,comments
=""):
3306 raise MaKaCError( _("Cannot accept an abstract wich is withdrawn"))
3308 def reject( self
, responsible
, comments
="" ):
3309 raise MaKaCError( _("Cannot reject an abstract which is withdrawn"))
3311 def proposeToAccept( self
):
3312 raise MaKaCError( _("Cannot propose for acceptance an abstract which withdrawn"))
3314 def proposeToReject( self
):
3315 raise MaKaCError( _("Cannot propose for rejection an abstract which is withdrawn"))
3317 def recover( self
):
3318 if self
.getPrevStatus() is None:
3319 # reset all the judgments
3320 self
._clearTrackAcceptances
()
3321 self
._clearTrackRejections
()
3322 self
._clearTrackReallocations
()
3323 # setting the status
3324 contrib
=self
.getAbstract().getContribution()
3326 s
= AbstractStatusSubmitted( self
.getAbstract() )
3328 s
= AbstractStatusAccepted(self
.getAbstract(),self
.getResponsible(),contrib
.getTrack(),contrib
.getType(),"")
3330 contrib
=self
.getAbstract().getContribution()
3331 if contrib
is not None and not isinstance(self
.getPrevStatus(), AbstractStatusAccepted
):
3332 s
= AbstractStatusAccepted(self
.getAbstract(),self
.getResponsible(),contrib
.getTrack(),contrib
.getType(),"")
3334 s
=self
.getPrevStatus()
3335 self
.getAbstract().setCurrentStatus( s
)
3337 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3338 raise MaKaCError( _("Cannot mark as duplicated an abstract which is withdrawn"))
3340 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3343 raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3345 def mergeInto(self
,responsible
,targetAbs
,comments
=""):
3346 raise MaKaCError( _("Cannot merge an abstract which is withdrawn"))
3348 def withdraw(self
,resp
,comments
=""):
3349 raise MaKaCError( _("This abstract is already withdrawn"))
3353 class AbstractStatusDuplicated(AbstractStatus
):
3356 def __init__( self
,abstract
,responsible
,originalAbstract
,comments
=""):
3357 AbstractStatus
.__init
__(self
,abstract
)
3358 self
._setResponsible
(responsible
)
3359 self
._setComments
(comments
)
3360 self
._setOriginalAbstract
(originalAbstract
)
3362 def clone(self
, abstract
):
3363 asd
= AbstractStatusDuplicated(abstract
,self
.getResponsible(),self
.getOriginal(),self
.getComments())
3366 def _setResponsible( self
, res
):
3367 self
._responsible
= res
3369 def getResponsible(self
):
3370 return self
._responsible
3372 def _setComments( self
, comments
):
3373 self
._comments
= str( comments
).strip()
3375 def getComments( self
):
3376 return self
._comments
3378 def _setOriginalAbstract(self
,abs):
3381 def getOriginal(self
):
3382 return self
._original
3387 def reject( self
, responsible
, comments
="" ):
3388 raise MaKaCError( _("Cannot reject an abstract which is duplicated"))
3390 def proposeToAccept( self
):
3391 raise MaKaCError( _("Cannot propose for acceptance an abstract which is duplicated"))
3393 def proposeToReject( self
):
3394 raise MaKaCError( _("Cannot propose for rejection an abstract which is duplicated"))
3396 def proposeToReallocate( self
):
3397 raise MaKaCError( _("Cannot propose for reallocation an abstract which is duplicated"))
3399 def withdraw(self
,resp
,comments
=""):
3400 raise MaKaCError( _("Cannot withdraw a duplicated abstract"))
3402 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3403 raise MaKaCError( _("This abstract is already duplicated"))
3405 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3406 s
= AbstractStatusSubmitted( self
.getAbstract() )
3407 self
.getAbstract().setCurrentStatus( s
)
3409 def mergeInto(self
,responsible
,targetAbs
,comments
=""):
3410 raise MaKaCError( _("Cannot merge an abstract which is marked as a duplicate"))
3413 class AbstractStatusMerged(AbstractStatus
):
3417 def __init__(self
,abstract
,responsible
,targetAbstract
,comments
=""):
3418 AbstractStatus
.__init
__(self
,abstract
)
3419 self
._setResponsible
(responsible
)
3420 self
._setComments
(comments
)
3421 self
._setTargetAbstract
(targetAbstract
)
3423 def clone(self
,abstract
):
3424 asm
= AbstractStatusMerged(abstract
,self
.getResponsible(),self
.getTargetAbstract(),self
.getComments())
3427 def _setResponsible( self
, res
):
3428 self
._responsible
= res
3430 def getResponsible( self
):
3431 return self
._responsible
3433 def _setComments( self
, comments
):
3434 self
._comments
= str( comments
).strip()
3436 def getComments( self
):
3437 return self
._comments
3439 def _setTargetAbstract(self
,abstract
):
3440 self
._target
=abstract
3442 def getTargetAbstract(self
):
3448 def reject( self
, responsible
, comments
="" ):
3449 raise MaKaCError( _("Cannot reject an abstract which is merged into another one"))
3451 def proposeToAccept( self
):
3452 raise MaKaCError( _("Cannot propose for acceptance an abstract which is merged into another one"))
3454 def proposeToReject( self
):
3455 raise MaKaCError( _("Cannot propose for rejection an abstract which is merged into another one"))
3457 def proposeToReallocate( self
):
3458 raise MaKaCError( _("Cannot propose for reallocation an abstract which is merged into another one"))
3460 def withdraw(self
,resp
,comments
=""):
3461 raise MaKaCError( _("Cannot withdraw an abstract which is merged into another one"))
3463 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3464 raise MaKaCError( _("Cannot mark as duplicated an abstract which is merged into another one"))
3466 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3469 raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3471 def mergeInto(self
,responsible
,target
,comments
=""):
3472 raise MaKaCError( _("This abstract is already merged into another one"))
3474 def unMerge(self
,responsible
,comments
=""):
3475 s
= AbstractStatusSubmitted( self
.getAbstract() )
3476 self
.getAbstract().setCurrentStatus( s
)
3478 class AbstractStatusNone(AbstractStatus
):
3479 # This is a special status we assign to abstracts that are put in the trash can.
3481 def __init__(self
,abstract
):
3482 AbstractStatus
.__init
__(self
,abstract
)
3484 def clone(self
,abstract
):
3485 asn
= AbstractStatusNone(abstract
)
3488 class NotificationTemplate(Persistent
):
3494 self
._description
=""
3498 self
._CAasCCAddr
= False
3499 self
._ccAddrList
=PersistentList()
3500 self
._toAddrs
= PersistentList()
3501 self
._conditions
=PersistentList()
3502 self
._toAddrGenerator
=Counter()
3503 self
._condGenerator
=Counter()
3506 tpl
= NotificationTemplate()
3507 tpl
.setName(self
.getName())
3508 tpl
.setDescription(self
.getDescription())
3509 tpl
.setTplSubject(self
.getTplSubject())
3510 tpl
.setTplBody(self
.getTplBody())
3511 tpl
.setFromAddr(self
.getFromAddr())
3512 tpl
.setCAasCCAddr(self
.getCAasCCAddr())
3514 for cc
in self
.getCCAddrList() :
3516 for to
in self
.getToAddrList() :
3519 for con
in self
.getConditionList() :
3520 tpl
.addCondition(con
.clone(tpl
))
3526 self
.clearCCAddrList()
3527 self
.clearConditionList()
3528 TrashCanManager().add(self
)
3531 TrashCanManager().remove(self
)
3533 ## def getResponsible( self ):
3534 ## return self._responsible
3536 ## def _setComments( self, comments ):
3537 ## self._comments = str( comments ).strip()
3539 ## def getComments( self ):
3540 ## return self._comments
3542 ## def _setOriginalAbstract(self,abstract):
3543 ## self._original=abstract
3545 def canModify(self
, aw_or_user
):
3546 return self
.getConference().canModify(aw_or_user
)
3548 def getLocator(self
):
3549 loc
= self
.getOwner().getConference().getLocator()
3550 loc
["notifTplId"] = self
._id
3553 def getConference(self
):
3554 return self
._owner
.getConference()
3556 def includeInOwner(self
,owner
,id):
3566 def setName(self
,newName
):
3567 self
._name
=newName
.strip()
3572 def setDescription(self
,newDesc
):
3573 self
._description
=newDesc
.strip()
3575 def getDescription(self
):
3576 return self
._description
3578 def setTplSubject(self
,newSubject
, varList
):
3579 self
._tplSubject
=self
.parseTplContent(newSubject
, varList
).strip()
3581 def getTplSubject(self
):
3582 return self
._tplSubject
3584 def getTplSubjectShow(self
, varList
):
3585 return self
.parseTplContentUndo(self
._tplSubject
, varList
)
3587 def setTplBody(self
,newBody
, varList
):
3588 self
._tplBody
=self
.parseTplContent(newBody
, varList
).strip()
3590 def getTplBody(self
):
3591 return self
._tplBody
3593 def getTplBodyShow(self
, varList
):
3594 return self
.parseTplContentUndo(self
._tplBody
, varList
)
3596 def getCCAddrList(self
):
3598 if self
._ccAddrList
:
3600 except AttributeError:
3601 self
._ccAddrList
=PersistentList()
3602 return self
._ccAddrList
3604 def addCCAddr(self
,newAddr
):
3606 if self
._ccAddrList
:
3608 except AttributeError:
3609 self
._ccAddrList
=PersistentList()
3610 ccAddr
=newAddr
.strip()
3611 if ccAddr
!="" and ccAddr
not in self
._ccAddrList
:
3612 self
._ccAddrList
.append(ccAddr
)
3614 def setCCAddrList(self
,l
):
3615 self
.clearCCAddrList()
3617 self
.addCCAddr(addr
)
3619 def setCAasCCAddr(self
, CAasCCAddr
):
3620 self
._CAasCCAddr
= CAasCCAddr
3622 def getCAasCCAddr(self
):
3624 if self
._CAasCCAddr
:
3626 except AttributeError:
3627 self
._CAasCCAddr
= False
3628 return self
._CAasCCAddr
3630 def clearCCAddrList(self
):
3631 self
._ccAddrList
=PersistentList()
3633 def getFromAddr(self
):
3635 return self
._fromAddr
3636 except AttributeError:
3637 self
._fromAddr
= self
._owner
.getConference().getSupportInfo().getEmail()
3638 return self
._fromAddr
3640 def setFromAddr(self
, addr
):
3641 self
._fromAddr
= addr
3643 def addToAddr(self
,toAddr
):
3646 if self
.hasToAddr(toAddr
.__class
__):
3649 if self
._toAddrGenerator
:
3651 except AttributeError, e
:
3652 self
._toAddrGenerator
= Counter()
3655 id = int(self
._toAddrGenerator
.newCount())
3656 toAddr
.includeInTpl(self
,id)
3657 self
.getToAddrList().append(toAddr
)
3659 def removeToAddr(self
,toAddr
):
3662 if not self
.hasToAddr(toAddr
.__class
__):
3664 self
.getToAddrList().remove(toAddr
)
3665 toAddr
.includeInTpl(None,toAddr
.getId())
3668 def recoverToAddr(self
, toAddr
):
3669 self
.addToAddr(toAddr
)
3672 def getToAddrs(self
, abs):
3674 for toAddr
in self
.getToAddrList():
3675 users
+= toAddr
.getToAddrList(abs)
3678 def getToAddrList(self
):
3684 except AttributeError, e
:
3685 self
._toAddrs
= PersistentList()
3686 return self
._toAddrs
3688 def getToAddrById(self
,id):
3691 for toAddr
in self
.getToAddrList():
3692 if toAddr
.getId()==int(id):
3696 def hasToAddr(self
,toAddrKlass
):
3697 """Returns True if the TPL contains a "toAddr" which class is "toAddrKlass"
3699 for toAddr
in self
.getToAddrList():
3700 if toAddr
.__class
__ == toAddrKlass
:
3704 def clearToAddrs(self
):
3705 while(len(self
.getToAddrList())>0):
3706 self
.removeToAddr(self
.getToAddrList()[0])
3708 def addCondition(self
,cond
):
3711 if cond
in self
._conditions
:
3715 id = int(self
._condGenerator
.newCount())
3716 cond
.includeInTpl(self
, id)
3717 self
._conditions
.append(cond
)
3719 def removeCondition(self
,cond
):
3722 if cond
not in self
._conditions
:
3724 self
._conditions
.remove(cond
)
3727 def recoverCondition(self
, cond
):
3728 self
.addCondition(cond
)
3731 def getConditionList(self
):
3734 return self
._conditions
3736 def getConditionById(self
,id):
3739 for cond
in self
._conditions
:
3740 if cond
.getId()==int(id):
3744 def clearConditionList(self
):
3745 while(len(self
.getConditionList())>0):
3746 self
.removeCondition(self
.getConditionList()[0])
3748 def satisfies(self
,abs):
3751 for cond
in self
._conditions
:
3752 if cond
.satisfies(abs):
3756 def parseTplContent(self
, content
, varList
):
3757 # replace the % in order to avoid exceptions
3758 result
= content
.replace("%", "%%")
3759 # find the vars and make the expressions, it is necessary to do in reverse in order to find the longest tags first
3761 result
= result
.replace("{"+var
.getName()+"}", "%("+var
.getName()+")s")
3764 def parseTplContentUndo(self
, content
, varList
):
3765 # The body content is shown without "%()" and with "%" in instead of "%%" but it is not modified
3768 result
= result
.replace("%("+var
.getName()+")s", "{"+var
.getName()+"}")
3769 # replace the %% by %
3770 result
= result
.replace("%%", "%")
3773 def getModifKey( self
):
3774 return self
.getConference().getModifKey()
3778 class NotifTplToAddr(Persistent
):
3787 ntta
= NotifTplToAddr()
3791 TrashCanManager().add(self
)
3794 TrashCanManager().remove(self
)
3796 def includeInTpl(self
,newTpl
,newId
):
3806 def getToAddrList(self
,absList
):
3808 Return a list with all the emails for a group.
3813 class NotifTplToAddrSubmitter(NotifTplToAddr
):
3815 def getToAddrList(self
,abs):
3817 l
.append(abs.getSubmitter())
3821 nttas
= NotifTplToAddrSubmitter()
3824 class NotifTplToAddrPrimaryAuthors(NotifTplToAddr
):
3826 def getToAddrList(self
,abs):
3828 for pa
in abs.getPrimaryAuthorList():
3833 nttapa
= NotifTplToAddrPrimaryAuthors()
3836 class NotifTplCondition(Persistent
):
3844 def clone(self
, template
):
3845 con
= NotifyCondition()
3846 con
.includeInTpl(template
)
3850 TrashCanManager().add(self
)
3853 TrashCanManager().remove(self
)
3855 def includeInTpl(self
,newTpl
,newId
):
3865 def satisfies(self
,abs):
3869 class NotifTplCondAccepted(NotifTplCondition
):
3871 def __init__(self
,track
="--any--",contribType
="--any--"):
3872 NotifTplCondition
.__init
__(self
)
3874 self
._contribType
=contribType
3876 def clone(self
, conference
, template
):
3877 ntca
= NotifTplCondAccepted()
3878 for newtrack
in conference
.getTrackList() :
3879 if newtrack
.getTitle() == self
.getTrack().getTitle() :
3880 ntca
.setTrack(newtrack
)
3881 for newtype
in conference
.getContribTypeList() :
3882 if newtype
.getName() == self
.getContribType() :
3883 ntca
.setContribType(newtype
)
3887 def setContribType(self
, ct
="--any--"):
3888 self
._contribType
= ct
3890 def getContribType(self
):
3891 return self
._contribType
3893 def setTrack(self
, tr
="--any--"):
3900 except AttributeError:
3901 self
._track
="--any--"
3904 def _satifiesContribType(self
,abs):
3905 status
=abs.getCurrentStatus()
3906 if self
._contribType
=="--any--":
3909 if self
._contribType
=="" or self
._contribType
==None or \
3910 self
._contribType
=="--none--":
3911 return status
.getType()=="" or status
.getType()==None
3912 return status
.getType()==self
._contribType
3915 def _satifiesTrack(self
,abs):
3916 status
=abs.getCurrentStatus()
3917 if self
.getTrack()=="--any--":
3920 if self
.getTrack()=="" or self
.getTrack() is None or \
3921 self
.getTrack()=="--none--":
3922 return status
.getTrack()=="" or status
.getTrack()==None
3923 return status
.getTrack()==self
.getTrack()
3926 def satisfies(self
,abs):
3927 if not isinstance(abs.getCurrentStatus(),AbstractStatusAccepted
):
3930 return self
._satifiesContribType
(abs) and self
._satifiesTrack
(abs)
3933 class NotifTplCondRejected(NotifTplCondition
):
3935 def satisfies(self
,abs):
3936 return isinstance(abs.getCurrentStatus(),AbstractStatusRejected
)
3938 def clone(self
, conference
, template
):
3939 ntcr
= NotifTplCondRejected()
3940 ntcr
.includeInTpl(template
)
3943 class NotifTplCondMerged(NotifTplCondition
):
3945 def satisfies(self
,abs):
3946 return isinstance(abs.getCurrentStatus(),AbstractStatusMerged
)
3948 def clone(self
, conference
, template
):
3949 ntcm
= NotifTplCondMerged()
3950 ntcm
.includeInTpl(newTpl
, newId
)
3952 class NotificationLog(Persistent
):
3954 def __init__(self
,abstract
):
3955 self
._abstract
=abstract
3956 self
._entries
=PersistentList()
3958 def getAbstract(self
):
3959 return self
._abstract
3961 def addEntry(self
,newEntry
):
3962 if newEntry
!=None and newEntry
not in self
._entries
:
3963 self
._entries
.append(newEntry
)
3965 def getEntryList(self
):
3966 return self
._entries
3968 # The 3 following metods are used only for recovery purposes:
3970 def removeEntry(self
, entry
):
3971 if entry
!=None and entry
in self
._entries
:
3972 self
._entries
.remove(entry
)
3975 def recoverEntry(self
, entry
):
3976 self
.addEntry(entry
)
3979 def clearEntryList(self
):
3980 while len(self
.getEntryList()) > 0:
3981 self
.removeEntry(self
.getEntryList()[0])
3983 # -----------------------------------------------------------
3985 class NotifLogEntry(Persistent
):
3987 def __init__(self
,responsible
,tpl
):
3988 self
._setDate
(nowutc())
3989 self
._setResponsible
(responsible
)
3992 def _setDate(self
,newDate
):
3998 def _setResponsible(self
,newResp
):
3999 self
._responsible
=newResp
4001 def getResponsible(self
):
4002 return self
._responsible
4004 def _setTpl(self
,newTpl
):
4011 TrashCanManager().add(self
)
4014 TrashCanManager().remove(self
)