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
22 from persistent
import Persistent
23 from persistent
.list import PersistentList
24 from BTrees
.OOBTree
import OOBTree
, intersection
, union
25 from BTrees
.IOBTree
import IOBTree
26 import BTrees
.OIBTree
as OIBTree
27 from datetime
import datetime
, timedelta
29 from MaKaC
.common
.Counter
import Counter
30 from MaKaC
.errors
import MaKaCError
, NoReportError
31 from MaKaC
.trashCan
import TrashCanManager
32 from MaKaC
.common
.timezoneUtils
import nowutc
33 from MaKaC
.i18n
import _
34 from indico
.core
.config
import Config
35 from MaKaC
.common
.fossilize
import fossilizes
, Fossilizable
36 from MaKaC
.fossils
.abstracts
import IAbstractFieldFossil
37 from MaKaC
.fossils
.abstracts
import IAbstractTextFieldFossil
38 from MaKaC
.fossils
.abstracts
import IAbstractSelectionFieldFossil
39 from MaKaC
.fossils
.abstracts
import ISelectionFieldOptionFossil
40 from indico
.util
.i18n
import N_
41 from indico
.util
.text
import wordsCounter
54 class _AbstractParticipationIndex(Persistent
):
55 """This class allows to index abstract participations (submitters)
56 for a single CFA process; this means that clients will be able to
57 efficiently perform queries of the type "give me all the abstracts
58 in which a certain registered user is implied".
59 For being able to perform this indexing, it is supposed that the Avatar
60 identifier is unique among other avatars and that it cannot change.
61 This index must be maintained by clients (i.e. the CFAMgr) as it doesn't
62 keep track of the changes on Participantons.
63 The key of the index is the Avatar and the values the different
64 Participations that user has within the current CFA process. For
65 performance reasons, the Avatar id will be used as index key (using the
66 whole Avatar object would make the index bigger and as the Avatar id
67 cannot change it's enough); the clients would have to keep the
68 integrity of the index.
74 def index(self
, participation
):
75 """Add a new participation to the index
77 #if the Participation is not linked to an Avatar there's no point to
79 a
= participation
.getAvatar()
82 #ToDo: if the Participation corresponds to an abstract which doesn't
83 # correspond to the current CFAMgr, then an error must be raised
85 if not self
._idx
.has_key(a
.getId()):
86 self
._idx
[a
.getId()] = PersistentList()
87 #if the participation is already in the index, no need for adding it
88 if participation
in self
._idx
[a
.getId()]:
90 self
._idx
[a
.getId()].append(participation
)
92 def unindex(self
, participation
):
93 """Remove an existing participation from the index
95 #if the Participation is not linked to an Avatar there's no point to
97 a
= participation
.getAvatar()
101 #if the Avatar associated to the participation isn't in the index do
103 if not self
._idx
.has_key(a
.getId()):
105 #if the given participation is indexed remove it, otherwise do nothing
106 if participation
in self
._idx
[a
.getId()]:
107 self
._idx
[a
.getId()].remove(participation
)
109 def getParticipationList(self
, av
):
111 return self
._idx
[av
.getId()]
116 class AbstractParticipation(Persistent
):
118 def __init__(self
, abstract
, **data
):
119 self
._abstract
= abstract
123 self
._affilliation
= ""
130 def setFromAvatar(self
, av
):
131 data
= {"title": av
.getTitle(),
132 "firstName": av
.getName(),
133 "surName": av
.getSurName(),
134 "email": av
.getEmail(),
135 "affiliation": av
.getOrganisation(),
136 "address": av
.getAddress(),
137 "telephone": av
.getTelephone(),
141 def setFromAbstractParticipation(self
, part
):
142 data
= {"title": part
.getTitle(),
143 "firstName": part
.getFirstName(),
144 "surName": part
.getSurName(),
145 "email": part
.getEmail(),
146 "affiliation": part
.getAffiliation(),
147 "address": part
.getAddress(),
148 "telephone": part
.getTelephone(),
149 "fax": part
.getFax()}
152 def setData(self
, **data
):
153 if "firstName" in data
:
154 self
.setFirstName(data
["firstName"])
155 if "surName" in data
:
156 self
.setSurName(data
["surName"])
158 self
.setEmail(data
["email"])
159 if "affiliation" in data
:
160 self
.setAffiliation(data
["affiliation"])
161 if "address" in data
:
162 self
.setAddress(data
["address"])
163 if "telephone" in data
:
164 self
.setTelephone(data
["telephone"])
166 self
.setFax(data
["fax"])
168 self
.setTitle(data
["title"])
173 data
["firstName"] = self
.getFirstName()
174 data
["surName"] = self
.getSurName()
175 data
["email"] = self
.getEmail()
176 data
["affiliation"] = self
.getAffiliation()
177 data
["address"] = self
.getAddress()
178 data
["telephone"] = self
.getTelephone()
179 data
["fax"] = self
.getFax()
180 data
["title"] = self
.getTitle()
185 def clone(self
, abstract
):
186 ap
= AbstractParticipation(abstract
, self
.getData())
189 def _notifyModification(self
):
190 self
._abstract
._notifyModification
()
193 abs = self
.getAbstract()
197 mgr
.unindexAuthor(self
)
200 abs = self
.getAbstract()
204 mgr
.indexAuthor(self
)
206 def setFirstName(self
, name
):
208 if tmp
== self
.getFirstName():
211 self
._firstName
= tmp
213 self
._notifyModification
()
215 def getFirstName(self
):
216 return self
._firstName
219 return self
._firstName
221 def setSurName(self
, name
):
223 if tmp
== self
.getSurName():
228 self
._notifyModification
()
230 def getSurName(self
):
233 def getFamilyName(self
):
236 def setEmail(self
, email
):
237 email
= email
.strip().lower()
238 if email
!= self
.getEmail():
242 self
._notifyModification
()
247 def setAffiliation(self
, af
):
248 self
._affilliation
= af
.strip()
249 self
._notifyModification
()
251 setAffilliation
= setAffiliation
253 def getAffiliation(self
):
254 return self
._affilliation
256 def setAddress(self
, address
):
257 self
._address
= address
.strip()
258 self
._notifyModification
()
260 def getAddress(self
):
263 def setTelephone(self
, telf
):
264 self
._telephone
= telf
.strip()
265 self
._notifyModification
()
267 def getTelephone(self
):
268 return self
._telephone
270 def setFax(self
, fax
):
271 self
._fax
= fax
.strip()
272 self
._notifyModification
()
277 def setTitle(self
, title
):
278 self
._title
= title
.strip()
279 self
._notifyModification
()
284 def getFullName(self
):
285 res
= safe_upper(self
.getSurName())
287 for name
in self
.getFirstName().lower().split(" "):
291 tmp
.append(safe_upper(safe_slice(name
, 0, 1)) + safe_slice(name
, 1))
292 firstName
= " ".join(tmp
)
294 res
= "%s, %s" % (res
, firstName
)
296 res
= "%s %s" % (self
.getTitle(), res
)
299 def getStraightFullName(self
):
302 name
= "%s " % self
.getName()
303 return "%s%s" % (name
, self
.getSurName())
305 def getAbrName(self
):
306 res
= self
.getSurName()
307 if self
.getFirstName():
310 res
= "%s%s." % (res
, safe_upper(safe_slice(self
.getFirstName(), 0, 1)))
313 def getAbstract(self
):
314 return self
._abstract
316 def setAbstract(self
, abs):
321 self
._abstract
= None
322 TrashCanManager().add(self
)
325 TrashCanManager().remove(self
)
328 class Author(AbstractParticipation
):
330 def __init__(self
, abstract
, **data
):
331 AbstractParticipation
.__init
__(self
, abstract
, **data
)
332 self
._abstractId
= ""
337 def setId(self
, newId
):
338 self
._id
= str(newId
)
340 def clone(self
, abstract
):
341 auth
= Author(abstract
, self
.getData())
345 return self
._abstract
.isSpeaker(self
)
348 class Submitter(AbstractParticipation
):
350 def __init__(self
, abstract
, av
):
352 raise MaKaCError(_("abstract submitter cannot be None"))
353 AbstractParticipation
.__init
__(self
, abstract
)
356 self
.setFromAvatar(av
)
358 def _setUser(self
, av
):
359 if self
.getUser() == av
:
361 #if currently there's an association with a registered user, we notify
362 # the unidexation of the participation
364 self
.getAbstract().getOwner().unregisterParticipation(self
)
366 #if the participation is associated to any avatar, we make the
367 # association and index it
369 self
.getAbstract().getOwner().registerParticipation(self
)
371 def clone(self
, abstract
):
372 sub
= Submitter(abstract
, self
.getAvatar())
373 sub
.setData(self
.getData())
382 def representsUser(self
, av
):
383 return self
.getUser() == av
386 class _AuthIdx(Persistent
):
388 def __init__(self
, mgr
):
390 self
._idx
= OOBTree()
392 def _getKey(self
, auth
):
393 return "%s %s" % (auth
.getSurName().lower(), auth
.getFirstName().lower())
395 def index(self
, auth
):
396 if auth
.getAbstract() is None:
397 raise MaKaCError(_("cannot index an author of an abstract which is not included in a conference"))
398 if auth
.getAbstract().getOwner() != self
._mgr
:
399 raise MaKaCError(_("cannot index an author of an abstract which does not belong to this conference"))
400 key
= self
._getKey
(auth
)
401 abstractId
= str(auth
.getAbstract().getId())
402 if not self
._idx
.has_key(key
):
403 self
._idx
[key
] = OIBTree
.OIBTree()
404 if not self
._idx
[key
].has_key(abstractId
):
405 self
._idx
[key
][abstractId
] = 0
406 self
._idx
[key
][abstractId
] += 1
408 def unindex(self
, auth
):
409 if auth
.getAbstract() is None:
410 raise MaKaCError(_("cannot unindex an author of an abstract which is not included in a conference"))
411 if auth
.getAbstract().getOwner() != self
._mgr
:
412 raise MaKaCError(_("cannot unindex an author of an abstract which does not belong to this conference"))
413 key
= self
._getKey
(auth
)
414 if not self
._idx
.has_key(key
):
416 abstractId
= str(auth
.getAbstract().getId())
417 if abstractId
not in self
._idx
[key
]:
419 self
._idx
[key
][abstractId
] -= 1
420 if self
._idx
[key
][abstractId
] <= 0:
421 del self
._idx
[key
][abstractId
]
422 if len(self
._idx
[key
]) <= 0:
425 def match(self
, query
):
426 query
= query
.lower().strip()
427 res
= OIBTree
.OISet()
428 for k
in self
._idx
.keys():
429 if k
.find(query
) != -1:
430 res
= OIBTree
.union(res
, self
._idx
[k
])
434 class _PrimAuthIdx(_AuthIdx
):
436 def __init__(self
, mgr
):
437 _AuthIdx
.__init
__(self
, mgr
)
438 for abs in self
._mgr
.getAbstractList():
439 for auth
in abs.getPrimaryAuthorList():
443 class _AuthEmailIdx(_AuthIdx
):
445 def __init__(self
, mgr
):
446 _AuthIdx
.__init
__(self
, mgr
)
447 for abs in self
._mgr
.getAbstractList():
448 for auth
in abs.getPrimaryAuthorList():
450 for auth
in abs.getCoAuthorList():
453 def _getKey(self
, auth
):
454 return auth
.getEmail().lower()
457 class AbstractField(Persistent
, Fossilizable
):
458 fossilizes(IAbstractFieldFossil
)
460 fieldtypes
= ["textarea", "input", "selection"]
463 def makefield(cls
, params
):
464 fieldType
= params
["type"]
465 if fieldType
not in cls
.fieldtypes
:
466 return AbstractTextAreaField(params
)
467 elif fieldType
== "textarea":
468 return AbstractTextAreaField(params
)
469 elif fieldType
== "input":
470 return AbstractInputField(params
)
471 elif fieldType
== "selection":
472 return AbstractSelectionField(params
)
474 def __init__(self
, params
):
475 self
._id
= params
["id"]
476 self
._caption
= params
.get("caption") if params
.get("caption") else self
._id
477 self
._isMandatory
= params
.get("isMandatory") if params
.get("isMandatory") else False
481 """ To be implemented by subclasses """
484 def _notifyModification(self
):
487 def check(self
, content
):
490 if self
._active
and self
._isMandatory
and content
== "":
491 errors
.append(_("The field '%s' is mandatory") % self
._caption
)
498 def isMandatory(self
):
499 return self
._isMandatory
501 def setMandatory(self
, isMandatory
=False):
502 self
._isMandatory
= isMandatory
503 self
._notifyModification
()
510 self
._notifyModification
()
512 def getCaption(self
):
515 def setCaption(self
, caption
):
516 self
._caption
= caption
517 self
._notifyModification
()
522 def setActive(self
, active
):
523 self
._active
= active
524 self
._notifyModification
()
528 values
["id"] = self
.getId()
529 values
["caption"] = self
.getCaption()
530 values
["isMandatory"] = self
.isMandatory()
533 def setValues(self
, params
):
534 self
.setCaption(params
.get("caption") if params
.get("caption") else self
._id
)
535 self
.setMandatory(params
.get("isMandatory") if params
.get("isMandatory") else False)
536 self
._notifyModification
()
539 class AbstractTextField(AbstractField
):
540 fossilizes(IAbstractTextFieldFossil
)
542 limitationtypes
= ["chars", "words"]
544 def __init__(self
, params
):
545 AbstractField
.__init
__(self
, params
)
546 self
._maxLength
= params
.get("maxLength") if params
.get("maxLength") else 0
547 self
._limitation
= params
.get("limitation") if params
.get("limitation") in self
.limitationtypes
else "chars"
550 return AbstractTextField(self
.getValues())
552 def check(self
, content
):
553 errors
= AbstractField
.check(self
, content
)
555 if self
._maxLength
!= 0:
556 if self
._limitation
== "words" and wordsCounter(str(content
)) > self
._maxLength
:
557 errors
.append(_("The field '%s' cannot be more than %s words") % (self
._caption
, self
._maxLength
))
558 elif self
._limitation
== "chars" and len(content
) > self
._maxLength
:
559 errors
.append(_("The field '%s' cannot be more than %s characters") % (self
._caption
, self
._maxLength
))
563 def getLimitation(self
):
564 return self
._limitation
566 def getMaxLength(self
):
567 return self
._maxLength
569 def setLimitation(self
, limitation
="chars"):
570 self
._limitation
= limitation
if limitation
in self
.limitationtypes
else "chars"
571 self
._notifyModification
()
573 def setMaxLength(self
, maxLength
=0):
574 self
._maxLength
= maxLength
575 self
._notifyModification
()
578 values
= AbstractField
.getValues(self
)
579 values
["maxLength"] = self
.getMaxLength()
580 values
["limitation"] = self
.getLimitation()
583 def setValues(self
, params
):
584 AbstractField
.setValues(self
, params
)
585 self
.setMaxLength(params
.get("maxLength") if params
.get("maxLength") else 0)
586 self
.setLimitation(params
.get("limitation") if params
.get("limitation") in self
.limitationtypes
else "chars")
587 self
._notifyModification
()
590 class AbstractTextAreaField(AbstractTextField
):
595 class AbstractInputField(AbstractTextField
):
600 class AbstractSelectionField(AbstractField
):
601 fossilizes(IAbstractSelectionFieldFossil
)
604 def __init__(self
, params
):
605 AbstractField
.__init
__(self
, params
)
606 self
.__id
_generator
= Counter()
608 self
._deleted
_options
= []
609 for o
in params
.get("options") if params
.get("options") else []:
612 def _deleteOption(self
, option
):
613 self
._options
.remove(option
)
614 self
._deleted
_options
.append(option
)
616 def _updateDeletedOptions(self
, options
=[]):
617 stored_options
= set(self
._options
)
618 updated_options
= set(self
.getOption(o
["id"]) for o
in options
)
620 for deleted_option
in stored_options
- updated_options
:
621 self
._deleteOption
(deleted_option
)
623 def _setOption(self
, option
, index
=None):
624 stored
= self
.getOption(option
["id"])
626 stored
.value
= option
["value"]
627 oldindex
= self
._options
.index(stored
)
628 self
._options
.insert(index
, self
._options
.pop(oldindex
))
629 elif option
["value"] is not "":
630 option
["id"] = self
.__id
_generator
.newCount()
631 self
._options
.append(SelectionFieldOption(option
["id"], option
["value"]))
634 return AbstractSelectionField(self
.getValues())
636 def check(self
, content
):
637 errors
= AbstractField
.check(self
, content
)
639 if self
._active
and self
._isMandatory
and content
== "":
640 errors
.append(_("The field '%s' is mandatory") % self
._caption
)
642 if next((op
for op
in self
._options
if op
.id == content
), None) is None:
643 errors
.append(_("The option with ID '%s' in the field %s") % (content
, self
._caption
))
647 def getDeletedOption(self
, id):
648 return next((o
for o
in self
._deleted
_options
if o
.getId() == id), None)
650 def getDeletedOptions(self
, id):
651 return self
._deleted
_options
653 def getOption(self
, id):
654 return next((o
for o
in self
._options
if o
.getId() == id), None)
656 def getOptions(self
):
659 def setOptions(self
, options
=[]):
660 self
._updateDeletedOptions
(options
)
661 for i
, o
in enumerate(options
):
662 self
._setOption
(o
, i
)
663 self
._notifyModification
()
666 values
= AbstractField
.getValues(self
)
669 for o
in self
._options
:
670 options
.append(o
.__dict
__)
671 values
["options"] = options
675 def setValues(self
, params
):
676 AbstractField
.setValues(self
, params
)
677 self
.setOptions(params
.get("options"))
678 self
._notifyModification
()
681 class SelectionFieldOption(Fossilizable
):
682 fossilizes(ISelectionFieldOptionFossil
)
684 def __init__(self
, id, value
):
689 def __eq__(self
, other
):
690 if isinstance(other
, SelectionFieldOption
):
691 return self
.id == other
.id
713 class AbstractFieldContent(Persistent
):
715 def __init__(self
, field
, value
):
719 def __eq__(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 return len(self
.value
)
729 def __ne__(self
, other
):
730 if isinstance(other
, AbstractFieldContent
) and self
.field
== other
.field
:
731 return self
.value
!= other
.value
732 elif not isinstance(other
, AbstractFieldContent
):
733 return self
.value
!= other
737 if isinstance(self
.field
, AbstractSelectionField
):
738 return str(self
.field
.getOption(self
.value
))
740 return str(self
.value
)
743 class AbstractFieldsMgr(Persistent
):
746 self
._fields
= self
._initFields
()
747 self
.__fieldGenerator
= Counter()
750 afm
= AbstractFieldsMgr()
751 for f
in self
.getFields():
752 afm
._addField
(f
.clone())
755 def getFieldGenerator(self
):
757 if self
.__fieldGenerator
:
759 except AttributeError, e
:
760 self
.__fieldGenerator
= Counter()
761 return self
.__fieldGenerator
763 def _notifyModification(self
):
766 def _initFields(self
):
768 params
= {"type": "textarea", "id": "content", "caption": N_("Content"), "isMandatory": True}
769 d
.append(AbstractField
.makefield(params
))
770 params
= {"type": "textarea", "id": "summary", "caption": N_("Summary")}
771 d
.append(AbstractField
.makefield(params
))
774 def hasField(self
, id):
775 for f
in self
._fields
:
781 if not self
.hasField("content"):
782 params
= {"type": "textarea", "id": "content", "caption": _("Content"), "isMandatory": True}
783 ac
= AbstractField
.makefield(params
)
784 self
._fields
.insert(0, ac
)
787 def getActiveFields(self
):
789 for f
in self
.getFields():
794 def hasActiveField(self
, id):
795 return self
.hasField(id) and self
.getFieldById(id).isActive()
797 def hasAnyActiveField(self
):
798 for f
in self
._fields
:
803 def enableField(self
, id):
804 if self
.hasField(id):
805 self
.getFieldById(id).setActive(True)
806 self
._notifyModification
()
808 def disableField(self
, id):
809 if self
.hasField(id):
810 self
.getFieldById(id).setActive(False)
811 self
._notifyModification
()
813 def getFieldKeys(self
):
815 for f
in self
._fields
:
816 keys
.append(f
.getId())
819 def getFieldById(self
, id):
820 for f
in self
._fields
:
825 def _addField(self
, field
):
826 self
._fields
.append(field
)
828 def setField(self
, params
):
829 if self
.hasField(params
["id"]):
830 self
.getFieldById(params
["id"]).setValues(params
)
832 params
["id"] = str(self
.getFieldGenerator().newCount())
833 absf
= AbstractField
.makefield(params
)
834 self
._fields
.append(absf
)
835 self
._notifyModification
()
838 def removeField(self
, id):
839 if self
.hasField(id):
840 self
._fields
.remove(self
.getFieldById(id))
841 self
._notifyModification
()
843 def moveAbsFieldUp(self
, id):
844 if self
.hasField(id):
845 f
= self
.getFieldById(id)
846 idx
= self
._fields
.index(f
)
847 self
._fields
.remove(f
)
849 self
._fields
.append(f
)
851 self
._fields
.insert(idx
-1, f
)
852 self
._notifyModification
()
854 def moveAbsFieldDown(self
, id):
855 if self
.hasField(id):
856 f
= self
.getFieldById(id)
857 idx
= self
._fields
.index(f
)
858 self
._fields
.remove(f
)
859 if idx
== len(self
._fields
):
860 self
._fields
.insert(0, f
)
862 self
._fields
.insert(idx
+1, f
)
863 self
._notifyModification
()
866 class AbstractMgr(Persistent
):
868 def __init__(self
, owner
):
870 self
._abstracts
= OOBTree()
871 self
._participationIdx
= _AbstractParticipationIndex()
872 self
.__abstractGenerator
= Counter()
873 self
._activated
= False
874 self
.setStartSubmissionDate(datetime
.now())
875 self
.setEndSubmissionDate(datetime
.now())
876 ## self._contribTypes = PersistentList()
877 self
.setAnnouncement("")
878 self
._notifTpls
= IOBTree()
879 self
._notifTplsOrder
= PersistentList()
880 self
.__notifTplsCounter
= Counter()
881 self
._authorizedSubmitter
= PersistentList()
882 self
._primAuthIdx
= _PrimAuthIdx(self
)
883 self
._authEmailIdx
= _AuthEmailIdx(self
)
884 self
._abstractFieldsMgr
= AbstractFieldsMgr()
885 self
._submissionNotification
= SubmissionNotification()
886 self
._multipleTracks
= True
887 self
._tracksMandatory
= False
888 self
._attachFiles
= False
889 self
._showSelectAsSpeaker
= True
890 self
._selectSpeakerMandatory
= True
891 self
._showAttachedFilesContribList
= False
893 def getMultipleTracks(self
):
895 return self
._multipleTracks
897 self
.setMultipleTracks(True)
898 return self
._multipleTracks
900 def setMultipleTracks(self
, multipleTracks
=True):
901 self
._multipleTracks
= multipleTracks
903 def areTracksMandatory(self
):
905 return self
._tracksMandatory
907 self
.setTracksMandatory(False)
908 return self
._tracksMandatory
910 def canAttachFiles(self
):
912 return self
._attachFiles
914 self
.setAllowAttachFiles(False)
915 return self
._attachFiles
917 def setAllowAttachFiles(self
, attachedFiles
):
918 self
._attachFiles
= attachedFiles
920 def setTracksMandatory(self
, tracksMandatory
=False):
921 self
._tracksMandatory
= tracksMandatory
923 def showSelectAsSpeaker(self
):
925 return self
._showSelectAsSpeaker
927 self
._showSelectAsSpeaker
= True
928 return self
._showSelectAsSpeaker
930 def setShowSelectAsSpeaker(self
, showSelectAsSpeaker
):
931 self
._showSelectAsSpeaker
= showSelectAsSpeaker
933 def isSelectSpeakerMandatory(self
):
935 return self
._selectSpeakerMandatory
937 self
._selectSpeakerMandatory
= True
938 return self
._selectSpeakerMandatory
940 def setSelectSpeakerMandatory(self
, selectSpeakerMandatory
):
941 self
._selectSpeakerMandatory
= selectSpeakerMandatory
943 def showAttachedFilesContribList(self
):
945 return self
._showAttachedFilesContribList
947 self
._showAttachedFilesContribList
= False
948 return self
._showAttachedFilesContribList
950 def setSwitchShowAttachedFilesContribList(self
, showshowAttachedFilesContribList
):
951 self
._showAttachedFilesContribList
= showshowAttachedFilesContribList
953 def getAbstractFieldsMgr(self
):
955 return self
._abstractFieldsMgr
957 self
._abstractFieldsMgr
= AbstractFieldsMgr()
958 return self
._abstractFieldsMgr
960 def clone(self
, conference
):
961 amgr
= AbstractMgr(conference
)
962 amgr
._abstractFieldsMgr
= self
.getAbstractFieldsMgr().clone()
963 amgr
.setAnnouncement(self
.getAnnouncement())
965 timeDifference
= conference
.getStartDate() - self
.getOwner().getStartDate()
966 amgr
.setStartSubmissionDate(self
.getStartSubmissionDate() + timeDifference
)
967 amgr
.setEndSubmissionDate(self
.getEndSubmissionDate() + timeDifference
)
969 modifDeadline
= self
.getModificationDeadline()
970 if modifDeadline
is not None:
971 amgr
.setModificationDeadline(self
.getModificationDeadline() + timeDifference
)
973 amgr
.setActive(self
.isActive())
974 if self
.getCFAStatus():
979 for a
in self
.getAbstractList():
980 amgr
.addAbstract(a
.clone(conference
, amgr
._generateNewAbstractId
()))
982 for tpl
in self
.getNotificationTplList():
983 amgr
.addNotificationTpl(tpl
.clone())
985 # Cloning submission notification:
986 amgr
.setSubmissionNotification(self
.getSubmissionNotification().clone())
992 getConference
= getOwner
994 def getTimezone(self
):
995 return self
.getConference().getTimezone()
998 self
._activated
= True
1000 def desactiveCFA(self
):
1001 self
._activated
= False
1003 def getAuthorizedSubmitterList(self
):
1005 return self
._authorizedSubmitter
1006 except AttributeError:
1007 self
._authorizedSubmitter
= PersistentList()
1008 return self
._authorizedSubmitter
1010 def addAuthorizedSubmitter(self
, av
):
1012 if self
._authorizedSubmitter
:
1014 except AttributeError:
1015 self
._authorizedSubmitter
= PersistentList()
1016 if not av
in self
._authorizedSubmitter
:
1017 self
._authorizedSubmitter
.append(av
)
1019 def removeAuthorizedSubmitter(self
, av
):
1021 if self
._authorizedSubmitter
:
1024 self
._authorizedSubmitter
= PersistentList()
1025 if av
in self
._authorizedSubmitter
:
1026 self
._authorizedSubmitter
.remove(av
)
1028 def getCFAStatus(self
):
1029 return self
._activated
1031 def setActive(self
, value
):
1038 return self
._activated
1040 def setStartSubmissionDate(self
, date
):
1041 self
._submissionStartDate
= datetime(date
.year
, date
.month
, date
.day
, 0, 0, 0)
1043 def getStartSubmissionDate(self
):
1044 return timezone(self
.getTimezone()).localize(self
._submissionStartDate
)
1046 def setEndSubmissionDate(self
, date
):
1047 self
._submissionEndDate
= datetime(date
.year
, date
.month
, date
.day
, 23, 59, 59)
1049 def getEndSubmissionDate(self
):
1050 return timezone(self
.getTimezone()).localize(self
._submissionEndDate
)
1052 def inSubmissionPeriod(self
, date
=None):
1055 sd
= self
.getStartSubmissionDate()
1056 ed
= self
.getEndSubmissionDate()
1057 return date
<= ed
and date
>= sd
1059 def getModificationDeadline(self
):
1060 """Returns the deadline for modifications on the submitted abstracts.
1063 if self
._modifDeadline
:
1065 except AttributeError, e
:
1066 self
._modifDeadline
= None
1067 if self
._modifDeadline
is not None:
1068 return timezone(self
.getTimezone()).localize(self
._modifDeadline
)
1072 def setModificationDeadline(self
, newDL
):
1073 """Sets a new deadline for modifications on the submitted abstracts.
1075 if newDL
is not None:
1076 self
._modifDeadline
= datetime(newDL
.year
, newDL
.month
, newDL
.day
, 23, 59, 59)
1078 self
._modifDeadline
= newDL
1080 def inModificationPeriod(self
, date
=None):
1081 """Tells whether is possible to modify a submitted abstract in a
1086 if not self
.getModificationDeadline():
1088 return date
<= self
.getModificationDeadline()
1090 def getAnnouncement(self
):
1093 if self
._announcement
:
1095 except AttributeError, e
:
1096 self
._announcement
= ""
1098 return self
._announcement
1100 def setAnnouncement(self
, newAnnouncement
):
1101 self
._announcement
= newAnnouncement
.strip()
1103 ## def addContribType(self, type):
1104 ## type = type.strip()
1106 ## raise MaKaCError("Cannot add an empty contribution type")
1107 ## self._contribTypes.append(type)
1109 ## def removeContribType(self, type):
1110 ## if type in self._contribTypes:
1111 ## self._contribTypes.remove(type)
1113 ## def getContribTypeList(self):
1114 ## return self._contribTypes
1116 def _generateNewAbstractId(self
):
1117 """Returns a new unique identifier for the current conference
1120 #instead of having a own counter, the abstract manager will request
1121 # abstract ids to the conference which will ensure a unique id
1122 # which will persist afterwards when an abstract is accepted
1123 return str(self
.getConference().genNewAbstractId())
1125 def _getOldAbstractCounter(self
):
1126 return self
.__abstractGenerator
._getCount
()
1128 def newAbstract(self
, av
, **data
):
1129 """Creates a new abstract under this manager
1131 id = self
._generateNewAbstractId
()
1132 a
= Abstract(self
, id, av
, **data
)
1133 self
._abstracts
[id] = a
1134 for auth
in a
.getPrimaryAuthorList():
1135 self
.indexAuthor(auth
)
1138 def addAbstract(self
, abstract
):
1139 if abstract
in self
.getAbstractList():
1141 if isinstance(abstract
.getCurrentStatus(), AbstractStatusWithdrawn
):
1142 raise MaKaCError(_("Cannot add an abstract which has been withdrawn"), ("Event"))
1143 abstract
._setOwner
(self
)
1144 self
._abstracts
[abstract
.getId()] = abstract
1145 for auth
in abstract
.getPrimaryAuthorList():
1146 self
.indexAuthor(auth
)
1148 def removeAbstract(self
, abstract
):
1149 if self
._abstracts
.has_key(abstract
.getId()):
1150 #for auth in abstract.getPrimaryAuthorList():
1151 # self.unindexAuthor(auth)
1152 # * Remove dependencies with another abstracts:
1153 # - If it's an accepted abstract-->remove abstract from contribution
1154 if isinstance(abstract
.getCurrentStatus(), AbstractStatusAccepted
):
1155 raise NoReportError(_("Cannot remove an accepted abstract before removing the contribution linked to it"))
1156 # If it's a withdrawn abstract-->remove abstract from contribution
1157 if isinstance(abstract
.getCurrentStatus(), AbstractStatusWithdrawn
) and abstract
.getContribution():
1158 raise NoReportError(_("Cannot remove the abstract before removing the contribution linked to it"))
1159 for abs in self
._abstracts
.values():
1161 st
= abs.getCurrentStatus()
1162 if isinstance(st
, AbstractStatusDuplicated
):
1163 #if the abstract to delete is the orginal in another "duplicated", change status to submitted
1164 if st
.getOriginal() == abstract
:
1165 abs.setCurrentStatus(AbstractStatusSubmitted(abs))
1166 elif isinstance(st
, AbstractStatusMerged
):
1167 #if the abstract to delete is the target one in another "merged", change status to submitted
1168 if st
.getTargetAbstract() == abstract
:
1169 abs.setCurrentStatus(AbstractStatusSubmitted(abs))
1170 #unindex participations!!!
1171 self
.unregisterParticipation(abstract
.getSubmitter())
1172 del self
._abstracts
[abstract
.getId()]
1175 def recoverAbstract(self
, abstract
):
1176 self
.addAbstract(abstract
)
1177 abstract
.recoverFromTrashCan()
1179 def getAbstractList(self
):
1180 return self
._abstracts
.values()
1182 def getAbstractById(self
, id):
1183 return self
._abstracts
.get(str(id), None)
1185 def registerParticipation(self
, p
):
1186 self
._participationIdx
.index(p
)
1188 def unregisterParticipation(self
, p
):
1189 self
._participationIdx
.unindex(p
)
1191 def getAbstractListForAvatar(self
, av
):
1193 if self
._participationIdx
:
1195 except AttributeError, e
:
1196 self
._participationIdx
= self
._partipationIdx
1197 self
._partipationIdx
= None
1199 for participation
in self
._participationIdx
.getParticipationList(av
):
1200 abstract
= participation
.getAbstract()
1201 if abstract
is not None and abstract
.isSubmitter(av
):
1202 if abstract
not in res
:
1203 res
.append(abstract
)
1206 def getAbstractListForAuthorEmail(self
, email
):
1207 """ Get list of abstracts where the email belongs to an author"""
1208 return [self
.getAbstractById(i
) for i
in self
._getAuthEmailIndex
().match(email
)]
1210 def getNotificationTplList(self
):
1214 except AttributeError:
1215 self
._notifTpls
= IOBTree()
1217 if self
._notifTplsOrder
:
1219 except AttributeError:
1220 self
._notifTplsOrder
= PersistentList()
1221 for tpl
in self
._notifTpls
.values():
1222 self
._notifTplsOrder
.append(tpl
)
1223 return self
._notifTplsOrder
1225 def addNotificationTpl(self
, tpl
):
1229 except AttributeError:
1230 self
._notifTpls
= IOBTree()
1232 if self
._notifTplsOrder
:
1234 except AttributeError:
1235 self
._notifTplsOrder
= PersistentList()
1236 for tpl
in self
._notifTpls
.values():
1237 self
._notifTplsOrder
.append(tpl
)
1239 if self
._notifTplsCounter
:
1241 except AttributeError:
1242 self
._notifTplsCounter
= Counter()
1243 if tpl
.getOwner() == self
and self
._notifTpls
.has_key(tpl
.getId()):
1247 id = self
._notifTplsCounter
.newCount()
1248 tpl
.includeInOwner(self
, id)
1249 self
._notifTpls
[int(id)] = tpl
1250 self
._notifTplsOrder
.append(tpl
)
1252 def removeNotificationTpl(self
, tpl
):
1256 except AttributeError:
1257 self
._notifTpls
= IOBTree()
1259 if self
._notifTplsOrder
:
1261 except AttributeError:
1262 self
._notifTplsOrder
= PersistentList()
1263 for tpl
in self
._notifTpls
.values():
1264 self
._notifTplsOrder
.append(tpl
)
1265 if tpl
.getOwner() != self
or not self
._notifTpls
.has_key(int(tpl
.getId())):
1267 del self
._notifTpls
[int(tpl
.getId())]
1268 self
._notifTplsOrder
.remove(tpl
)
1269 tpl
.includeInOwner(None, tpl
.getId()) # We don't change the id for
1270 # recovery purposes.
1273 def recoverNotificationTpl(self
, tpl
):
1274 self
.addNotificationTpl(tpl
)
1277 def getNotificationTplById(self
, id):
1281 except AttributeError:
1282 self
._notifTpls
= IOBTree()
1283 return self
._notifTpls
.get(int(id), None)
1285 def getNotifTplForAbstract(self
, abs):
1288 for tpl
in self
.getNotificationTplList():
1289 if tpl
.satisfies(abs):
1293 def moveUpNotifTpl(self
, tpl
):
1297 if self
._notifTplsOrder
:
1299 except AttributeError:
1300 self
._notifTplsOrder
= PersistentList()
1301 for tpl
in self
._notifTpls
.values():
1302 self
._notifTplsOrder
.append(tpl
)
1303 if tpl
not in self
._notifTplsOrder
:
1305 idx
= self
._notifTplsOrder
.index(tpl
)
1308 self
._notifTplsOrder
.remove(tpl
)
1309 self
._notifTplsOrder
.insert(idx
-1, tpl
)
1311 def moveDownNotifTpl(self
, tpl
):
1315 if self
._notifTplsOrder
:
1317 except AttributeError:
1318 self
._notifTplsOrder
= PersistentList()
1319 for tpl
in self
._notifTpls
.values():
1320 self
._notifTplsOrder
.append(tpl
)
1321 idx
= self
._notifTplsOrder
.index(tpl
)
1322 if idx
== len(self
._notifTplsOrder
):
1324 self
._notifTplsOrder
.remove(tpl
)
1325 self
._notifTplsOrder
.insert(idx
+1, tpl
)
1327 def indexAuthor(self
, auth
):
1328 a
= auth
.getAbstract()
1329 if a
.isPrimaryAuthor(auth
):
1330 self
._getPrimAuthIndex
().index(auth
)
1331 self
._getAuthEmailIndex
().index(auth
)
1333 def unindexAuthor(self
, auth
):
1334 a
= auth
.getAbstract()
1335 if a
.isPrimaryAuthor(auth
):
1336 self
._getPrimAuthIndex
().unindex(auth
)
1337 self
._getAuthEmailIndex
().unindex(auth
)
1339 def _getPrimAuthIndex(self
):
1341 if self
._primAuthIdx
:
1343 except AttributeError:
1344 self
._primAuthIdx
= _PrimAuthIdx(self
)
1345 return self
._primAuthIdx
1347 def _getAuthEmailIndex(self
):
1348 if not hasattr(self
, '_authEmailIdx'):
1349 self
._authEmailIdx
= _AuthEmailIdx(self
)
1350 return self
._authEmailIdx
1352 def getAbstractsMatchingAuth(self
, query
, onlyPrimary
=True):
1353 if str(query
).strip() == "":
1354 return self
.getAbstractList()
1355 res
= self
._getPrimAuthIndex
().match(query
)
1356 return [self
.getAbstractById(id) for id in res
]
1358 def setAbstractField(self
, params
):
1359 return self
.getAbstractFieldsMgr().setField(params
)
1361 def removeAbstractField(self
, id):
1362 self
.getAbstractFieldsMgr().removeField(id)
1364 def hasAnyEnabledAbstractField(self
):
1365 return self
.getAbstractFieldsMgr().hasAnyActiveField()
1367 def hasEnabledAbstractField(self
, key
):
1368 return self
.getAbstractFieldsMgr().hasActiveField(key
)
1370 def enableAbstractField(self
, abstractField
):
1371 self
.getAbstractFieldsMgr().enableField(abstractField
)
1372 self
.notifyModification()
1374 def disableAbstractField(self
, abstractField
):
1375 self
.getAbstractFieldsMgr().disableField(abstractField
)
1376 self
.notifyModification()
1378 def moveAbsFieldUp(self
, id):
1379 self
.getAbstractFieldsMgr().moveAbsFieldUp(id)
1380 self
.notifyModification()
1382 def moveAbsFieldDown(self
, id):
1383 self
.getAbstractFieldsMgr().moveAbsFieldDown(id)
1384 self
.notifyModification()
1386 def getSubmissionNotification(self
):
1388 if self
._submissionNotification
:
1390 except AttributeError, e
:
1391 self
._submissionNotification
= SubmissionNotification()
1392 return self
._submissionNotification
1394 def setSubmissionNotification(self
, sn
):
1395 self
._submissionNotification
= sn
1397 def recalculateAbstractsRating(self
, scaleLower
, scaleHigher
):
1398 ''' recalculate the values of the rating for all the abstracts in the conference '''
1399 for abs in self
.getAbstractList():
1400 abs.updateRating((scaleLower
, scaleHigher
))
1402 def removeAnswersOfQuestion(self
, questionId
):
1403 ''' Remove a question results for each abstract '''
1404 for abs in self
.getAbstractList():
1405 abs.removeAnswersOfQuestion(questionId
)
1407 def notifyModification(self
):
1411 class SubmissionNotification(Persistent
):
1414 self
._toList
= PersistentList()
1415 self
._ccList
= PersistentList()
1417 def hasDestination(self
):
1418 return self
._toList
!= [] or self
._toList
!= []
1420 def getToList(self
):
1423 def setToList(self
, tl
):
1426 def addToList(self
, to
):
1427 self
._toList
.append(to
)
1429 def clearToList(self
):
1430 self
._toList
= PersistentList()
1432 def getCCList(self
):
1435 def setCCList(self
, cl
):
1438 def addCCList(self
, cc
):
1439 self
._ccList
.append(cc
)
1441 def clearCCList(self
):
1442 self
._ccList
= PersistentList()
1445 nsn
= SubmissionNotification()
1446 for i
in self
.getToList():
1448 for i
in self
.getCCList():
1453 class Comment(Persistent
):
1455 def __init__(self
, res
, content
=""):
1456 self
._abstract
= None
1458 self
._responsible
= res
1460 self
._creationDate
= nowutc()
1461 self
._modificationDate
= nowutc()
1463 def getLocator(self
):
1464 loc
= self
._abstract
.getLocator()
1465 loc
["intCommentId"] = self
._id
1468 def includeInAbstract(self
, abstract
, id):
1469 self
._abstract
= abstract
1473 self
._abstract
= None
1474 TrashCanManager().add(self
)
1477 TrashCanManager().remove(self
)
1479 def _notifyModification(self
, dt
=None):
1481 self
._modificationDate
= dt
1483 self
._modificationDate
= nowutc()
1485 def getResponsible(self
):
1486 return self
._responsible
1488 def getAbstract(self
):
1489 return self
._abstract
1494 def getContent(self
):
1495 return self
._content
1497 def setContent(self
, newContent
):
1498 self
._content
= newContent
1499 self
._notifyModification
()
1501 def getCreationDate(self
):
1502 return self
._creationDate
1504 def getModificationDate(self
):
1505 return self
._modificationDate
1507 def canModify(self
, aw_or_user
):
1508 if hasattr(aw_or_user
, 'getUser'):
1509 aw_or_user
= aw_or_user
.getUser()
1510 return self
.canUserModify(aw_or_user
)
1512 def canUserModify(self
, user
):
1513 abstract
= self
.getAbstract()
1514 conf
= abstract
.getConference()
1515 return self
.getResponsible() == user
and \
1516 (abstract
.canUserModify(user
) or \
1517 len(conf
.getConference().getCoordinatedTracks(user
)) > 0)
1520 class Abstract(Persistent
):
1522 def __init__(self
, owner
, id, submitter
, **abstractData
):
1523 self
._setOwner
( owner
)
1527 self
._authorGen
= Counter()
1528 self
._authors
= OOBTree()
1529 self
._primaryAuthors
= PersistentList()
1530 self
._coAuthors
= PersistentList()
1531 self
._speakers
= PersistentList()
1532 self
._tracks
= OOBTree()
1533 self
._contribTypes
= PersistentList( [""] )
1534 self
._setSubmissionDate
( nowutc() )
1535 self
._modificationDate
= nowutc()
1536 self
._currentStatus
= AbstractStatusSubmitted( self
)
1537 self
._trackAcceptances
= OOBTree()
1538 self
._trackRejections
= OOBTree()
1539 self
._trackReallocations
= OOBTree()
1540 self
._trackJudgementsHistorical
={}
1542 self
._contribution
= None
1543 self
._intCommentGen
=Counter()
1544 self
._intComments
=PersistentList()
1545 self
._mergeFromList
= PersistentList()
1546 self
._notifLog
=NotificationLog(self
)
1547 self
._submitter
=None
1548 self
._setSubmitter
( submitter
)
1549 self
._rating
= None # It needs to be none to avoid the case of having the same value as the lowest value in the judgement
1550 self
._attachments
= {}
1551 self
._attachmentsCounter
= Counter()
1553 def __cmp__(self
, other
):
1554 if type(self
) is not type(other
):
1555 # This is actually dangerous and the ZODB manual says not to do this
1556 # because it relies on memory order. However, this branch should never
1557 # be taken anyway since we do not store different types in the same set
1558 # or use them as keys.
1559 return cmp(hash(self
), hash(other
))
1560 if self
.getConference() == other
.getConference():
1561 return cmp(self
.getId(), other
.getId())
1562 return cmp(self
.getConference(), other
.getConference())
1564 def clone(self
, conference
, abstractId
):
1566 # abstractId - internal in abstract manager of the conference
1567 abs = Abstract(conference
.getAbstractMgr(), abstractId
, self
.getSubmitter().getAvatar())
1568 abs.setTitle(self
.getTitle())
1569 for key
in self
.getFields().keys():
1570 abs.setField(key
,self
.getField(key
))
1571 abs.setComments(self
.getComments())
1573 abs._setSubmissionDate
(self
.getSubmissionDate())
1574 abs._modificationDate
= self
.getModificationDate()
1576 # Cloning of primary- and coauthors
1577 # if an author is also a speaker, an appropriate object will be
1578 # appended also to the speaker list
1579 for pa
in self
.getPrimaryAuthorList() :
1580 npa
= abs.newPrimaryAuthor(**(pa
.getData()))
1581 if self
.isSpeaker(pa
) :
1583 for ca
in self
.getCoAuthorList() :
1584 nca
= abs.newCoAuthor(**(ca
.getData()))
1585 if self
.isSpeaker(ca
) :
1588 # Cloning of speakers
1589 # only those, who are not authors :
1590 for sp
in self
.getSpeakerList() :
1591 if not self
.isAuthor(sp
) :
1592 abs.addSpeaker(sp
.clone())
1594 abs.setSubmitter(self
.getSubmitter().getAvatar())
1596 if self
.getContribType() is not None :
1597 for ct
in conference
.getContribTypeList() :
1598 if self
.getContribType().getName() == ct
.getName() :
1599 abs.setContribType(ct
)
1602 abs.setContribType(None)
1604 # the track, to which the abstract belongs to
1605 # legacy list implementation
1606 for tr
in self
.getTrackList() :
1607 for newtrack
in conference
.getTrackList():
1608 if newtrack
.getTitle() == tr
.getTitle() :
1609 abs.addTrack(newtrack
)
1611 # overall abstract status (accepted / rejected)
1612 abs._currentStatus
= self
._currentStatus
.clone(abs)
1614 for ta
in self
.getTrackAcceptanceList() :
1615 for newtrack
in conference
.getTrackList():
1616 if newtrack
.getTitle() == ta
.getTrack().getTitle() :
1617 newta
= ta
.clone(newtrack
)
1618 abs._addTrackAcceptance
(newta
)
1619 abs._addTrackJudgementToHistorical
(newta
)
1621 for trj
in self
.getTrackRejections().values() :
1622 for newtrack
in conference
.getTrackList():
1623 if newtrack
.getTitle() == trj
.getTrack().getTitle() :
1624 newtrj
= trj
.clone(newtrack
)
1625 abs._addTrackRejection
(newtrj
)
1626 abs._addTrackJudgementToHistorical
(newtrj
)
1628 for trl
in self
.getTrackReallocations().values() :
1629 for newtrack
in conference
.getTrackList():
1630 if newtrack
.getTitle() == trl
.getTrack().getTitle() :
1631 newtrl
= trl
.clone(newtrack
)
1632 abs._addTrackReallocation
(newtrl
)
1633 abs._addTrackJudgementToHistorical
(newtrl
)
1636 for f
in self
.getAttachments().values():
1637 newFile
= f
.clone(abs, protection
=False)
1638 abs.__addFile
(newFile
)
1642 def getUniqueId( self
):
1643 """returns (string) the unique identifier of the item"""
1644 """used only in the web session access key table"""
1645 """it is the same as the conference since only the conf can"""
1646 """be protected with an access key"""
1647 return self
.getConference().getUniqueId()
1649 def getMergeFromList(self
):
1651 return self
._mergeFromList
1652 except AttributeError:
1653 self
._mergeFromList
= PersistentList()
1654 return self
._mergeFromList
1656 def addMergeFromAbstract(self
, abstract
):
1658 if self
._mergeFromList
:
1660 except AttributeError:
1661 self
._mergeFromList
= PersistentList()
1662 self
._mergeFromList
.append(abstract
)
1664 def removeMergeFromAbstract(self
, abstract
):
1666 if self
._mergeFromList
:
1668 except AttributeError:
1669 self
._mergeFromList
= PersistentList()
1671 if abstract
in self
._mergeFromList
:
1672 self
._mergeFromList
.remove(abstract
)
1674 def getComments(self
):
1676 return self
._comments
1677 except AttributeError:
1679 return self
._comments
1681 def setComments(self
, comments
):
1682 self
._comments
= comments
1684 def __addFile(self
, file):
1685 file.archive(self
.getConference()._getRepository
())
1686 self
.getAttachments()[file.getId()] = file
1687 self
._notifyModification
()
1690 def saveFiles(self
, files
):
1691 cfg
= Config
.getInstance()
1692 from MaKaC
.conference
import LocalFile
1693 for fileUploaded
in files
:
1694 if fileUploaded
.filename
:
1695 # create a temp file
1696 tempPath
= cfg
.getUploadedFilesTempDir()
1697 tempFileName
= tempfile
.mkstemp(suffix
="IndicoAbstract.tmp", dir=tempPath
)[1]
1698 f
= open(tempFileName
, "wb")
1699 f
.write(fileUploaded
.file.read() )
1702 file.setFileName(fileUploaded
.filename
)
1703 file.setFilePath(tempFileName
)
1705 file.setId(self
._getAttachmentsCounter
())
1706 self
.__addFile
(file)
1708 def deleteFilesNotInList(self
, keys
):
1709 """This method is used in order to delete all the files that are not present (by id) in the
1711 This is useful when files are deleted from the abstract form using Javascript, and so it is
1712 the only way to know that they are deleted.
1714 existingKeys
= self
.getAttachments().keys()
1715 for key
in existingKeys
:
1717 self
._deleteFile
(key
)
1719 def _deleteFile(self
, key
):
1720 file = self
.getAttachments()[key
]
1722 del self
.getAttachments()[key
]
1723 self
._notifyModification
()
1725 def removeResource(self
, res
):
1726 """Necessary because LocalFile.delete (see _deleteFile) is calling this method.
1727 In our case, nothing to do.
1731 def _setOwner( self
, owner
):
1734 def getOwner( self
):
1737 def _setId( self
, id ):
1738 self
._id
= str( id )
1743 def _setSubmissionDate( self
, newDate
):
1744 self
._submissionDate
= newDate
1746 def setModificationDate(self
, dt
= None):
1748 self
._modificationDate
= dt
1750 self
._modificationDate
= nowutc()
1752 def _notifyModification( self
, dt
=None ):
1753 self
.setModificationDate(dt
)
1756 def getModificationDate( self
):
1757 return self
._modificationDate
1759 def _setSubmitter( self
, av
):
1761 raise MaKaCError( _("An abstract must have a submitter"))
1763 self
.getOwner().unregisterParticipation( self
._submitter
)
1764 self
._submitter
.getUser().unlinkTo(self
, "submitter")
1765 self
._submitter
.delete()
1766 self
._submitter
=Submitter( self
, av
)
1767 av
.linkTo(self
, "submitter")
1768 self
.getOwner().registerParticipation( self
._submitter
)
1769 self
._notifyModification
()
1771 def recoverSubmitter(self
, subm
):
1773 raise MaKaCError( _("An abstract must have a submitter"))
1775 self
.getOwner().unregisterParticipation( self
._submitter
)
1776 self
._submitter
.delete()
1777 self
._submitter
= subm
1778 self
._submitter
.setAbstract(self
)
1779 self
.getOwner().registerParticipation( self
._submitter
)
1781 self
._notifyModification
()
1783 def setSubmitter( self
, av
):
1784 self
._setSubmitter
(av
)
1786 def getSubmitter( self
):
1787 return self
._submitter
1789 def isSubmitter( self
, av
):
1790 return self
.getSubmitter().representsUser( av
)
1792 def setTitle(self
, title
):
1793 self
._title
= title
.strip()
1794 self
._notifyModification
()
1799 def getFields(self
):
1802 def removeField(self
, field
):
1803 if self
.getFields().has_key(field
):
1804 del self
.getFields()[field
]
1805 self
._notifyModification
()
1807 def setField(self
, fid
, v
):
1808 if isinstance(v
, AbstractFieldContent
):
1811 self
.getFields()[fid
].value
= v
1812 self
._notifyModification
()
1814 afm
= self
.getConference().getAbstractMgr().getAbstractFieldsMgr()
1815 f
= next(f
for f
in afm
.getFields() if f
.getId() == fid
)
1817 self
.getFields()[fid
] = AbstractFieldContent(f
, v
)
1819 def getField(self
, field
):
1820 if self
.getFields().has_key(field
):
1821 return self
.getFields()[field
]
1825 def getSubmissionDate( self
):
1827 if self
._submissionDate
:
1829 except AttributeError:
1830 self
._submissionDate
=nowutc()
1831 return self
._submissionDate
1833 def getConference( self
):
1834 mgr
= self
.getOwner()
1835 return mgr
.getOwner() if mgr
else None
1837 def _newAuthor( self
, **data
):
1838 author
= Author( self
, **data
)
1839 author
.setId( self
._authorGen
.newCount() )
1840 self
._authors
[ author
.getId() ] = author
1843 def _removeAuthor(self
,part
):
1844 if not self
.isAuthor(part
):
1847 del self
._authors
[part
.getId()]
1849 def isAuthor( self
, part
):
1850 return self
._authors
.has_key( part
.getId() )
1852 def getAuthorList( self
):
1853 return self
._authors
.values()
1855 def getAuthorById(self
, id):
1856 return self
._authors
.get(str(id), None)
1858 def clearAuthors( self
):
1859 self
.clearPrimaryAuthors()
1860 self
.clearCoAuthors()
1861 self
._notifyModification
()
1863 def newPrimaryAuthor(self
,**data
):
1864 auth
=self
._newAuthor
(**data
)
1865 self
._addPrimaryAuthor
(auth
)
1866 self
._notifyModification
()
1869 def isPrimaryAuthor( self
, part
):
1870 return part
in self
._primaryAuthors
1872 def getPrimaryAuthorList( self
):
1873 return self
._primaryAuthors
1874 #XXX: I keep it for compatibility but it should be removed
1875 getPrimaryAuthorsList
= getPrimaryAuthorList
1877 def getPrimaryAuthorEmailList(self
, lower
=False):
1879 for pAuthor
in self
.getPrimaryAuthorList():
1880 emailList
.append(pAuthor
.getEmail().lower() if lower
else pAuthor
.getEmail())
1883 def clearPrimaryAuthors(self
):
1884 while len(self
._primaryAuthors
)>0:
1885 self
._removePrimaryAuthor
(self
._primaryAuthors
[0])
1886 self
._notifyModification
()
1888 def _addPrimaryAuthor( self
, part
):
1889 if not self
.isAuthor( part
):
1890 raise MaKaCError( _("The participation you want to set as primary author is not an author of the abstract"))
1891 if part
in self
._primaryAuthors
:
1893 self
._primaryAuthors
.append( part
)
1894 self
.getOwner().indexAuthor(part
)
1896 def _removePrimaryAuthor(self
,part
):
1897 if not self
.isPrimaryAuthor(part
):
1899 if self
.isSpeaker(part
):
1900 self
.removeSpeaker(part
)
1901 self
.getOwner().unindexAuthor(part
)
1902 self
._primaryAuthors
.remove(part
)
1903 self
._removeAuthor
(part
)
1905 def recoverPrimaryAuthor(self
, auth
):
1906 self
._authors
[ auth
.getId() ] = auth
1907 auth
.setAbstract(self
)
1908 self
._addPrimaryAuthor
(auth
)
1910 self
._notifyModification
()
1912 def newCoAuthor(self
,**data
):
1913 auth
=self
._newAuthor
(**data
)
1914 self
._addCoAuthor
(auth
)
1915 self
._notifyModification
()
1918 def _comp_CoAuthors(self
):
1920 if self
._coAuthors
!=None:
1922 except AttributeError:
1923 self
._coAuthors
=PersistentList()
1924 for auth
in self
._authors
.values():
1925 if not self
.isPrimaryAuthor(auth
):
1926 self
._addCoAuthor
(auth
)
1928 def isCoAuthor( self
, part
):
1929 self
._comp
_CoAuthors
()
1930 return part
in self
._coAuthors
1932 def getCoAuthorList( self
):
1933 self
._comp
_CoAuthors
()
1934 return self
._coAuthors
1936 def getCoAuthorEmailList(self
, lower
=False):
1938 for coAuthor
in self
.getCoAuthorList():
1939 emailList
.append(coAuthor
.getEmail().lower() if lower
else coAuthor
.getEmail())
1942 def clearCoAuthors(self
):
1943 while len(self
._coAuthors
)>0:
1944 self
._removeCoAuthor
(self
._coAuthors
[0])
1945 self
._notifyModification
()
1947 def _addCoAuthor( self
, part
):
1948 self
._comp
_CoAuthors
()
1949 if not self
.isAuthor( part
):
1950 raise MaKaCError( _("The participation you want to set as primary author is not an author of the abstract"))
1951 if part
in self
._coAuthors
:
1953 self
._coAuthors
.append( part
)
1955 def _removeCoAuthor(self
,part
):
1956 if not self
.isCoAuthor(part
):
1958 if self
.isSpeaker(part
):
1959 self
.removeSpeaker(part
)
1960 self
._coAuthors
.remove(part
)
1961 self
._removeAuthor
(part
)
1963 def recoverCoAuthor(self
, auth
):
1964 self
._authors
[ auth
.getId() ] = auth
1965 auth
.setAbstract(self
)
1966 self
._addCoAuthor
(auth
)
1968 self
._notifyModification
()
1970 def addSpeaker( self
, part
):
1971 if not self
.isAuthor( part
):
1972 raise MaKaCError( _("The participation you want to set as speaker is not an author of the abstract"))
1973 if part
in self
._speakers
:
1975 self
._speakers
.append( part
)
1976 self
._notifyModification
()
1978 def removeSpeaker(self
,part
):
1979 if part
not in self
._speakers
:
1981 self
._speakers
.remove(part
)
1983 def clearSpeakers( self
):
1984 while len(self
.getSpeakerList()) > 0:
1985 self
.removeSpeaker(self
.getSpeakerList()[0])
1986 self
._speakers
= PersistentList()
1988 def getSpeakerList( self
):
1989 return self
._speakers
1991 def isSpeaker( self
, part
):
1992 return part
in self
._speakers
1994 def setContribType( self
, contribType
):
1995 self
._contribTypes
[0] = contribType
1996 self
._notifyModification
()
1998 def getContribType( self
):
1999 return self
._contribTypes
[0]
2002 def _addTrack( self
, track
):
2003 """Adds the specified track to the suggested track list. Any
2004 verification must be done by the caller.
2006 self
._tracks
[ track
.getId() ] = track
2007 track
.addAbstract( self
)
2008 self
._notifyModification
()
2010 def addTrack( self
, track
):
2011 self
._changeTracksImpl
()
2012 if not self
._tracks
.has_key( track
.getId() ):
2013 self
._addTrack
( track
)
2014 self
.getCurrentStatus().update()
2016 def _removeTrack( self
, track
):
2017 """Removes the specified track from the track list. Any verification
2018 must be done by the caller.
2020 del self
._tracks
[ track
.getId() ]
2021 track
.removeAbstract( self
)
2022 self
._notifyModification
()
2024 def removeTrack( self
, track
):
2025 if self
._tracks
.has_key( track
.getId() ):
2026 self
._removeTrack
( track
)
2027 self
.getCurrentStatus().update()
2028 if isinstance(self
.getCurrentStatus(), AbstractStatusAccepted
):
2029 self
.getCurrentStatus()._setTrack
(None)
2031 def _changeTracksImpl( self
):
2032 if self
._tracks
.__class
__ != OOBTree
:
2033 oldTrackList
= self
._tracks
2034 self
._tracks
= OOBTree()
2035 for track
in oldTrackList
:
2036 self
._addTrack
( track
)
2037 self
.getCurrentStatus().update()
2039 def getTrackList( self
):
2040 self
._changeTracksImpl
()
2042 return self
._tracks
.values()
2044 def hasTrack( self
, track
):
2045 self
._changeTracksImpl
()
2047 return self
._tracks
.has_key( track
.getId() )
2049 def getTrackListSorted( self
):
2050 self
._changeTracksImpl
()
2051 return self
.getConference().sortTrackList( self
._tracks
.values() )
2053 def clearTracks( self
):
2054 self
._changeTracksImpl
()
2056 while len(self
.getTrackList())>0:
2057 track
= self
.getTrackList()[0]
2058 self
._removeTrack
( track
)
2059 self
.getCurrentStatus().update()
2061 def setTracks( self
, trackList
):
2062 """Set the suggested track classification of the current abstract to
2065 #We need to do it in 2 steps otherwise the list over which we are
2066 # iterating gets modified
2068 toBeAdded
= copy( trackList
)
2069 for track
in self
.getTrackList():
2070 if track
not in trackList
:
2071 toBeRemoved
.append( track
)
2073 toBeAdded
.remove( track
)
2074 for track
in toBeRemoved
:
2075 self
._removeTrack
( track
)
2076 for track
in toBeAdded
:
2077 self
._addTrack
( track
)
2078 self
.getCurrentStatus().update()
2080 def isProposedForTrack( self
, track
):
2081 return self
._tracks
.has_key( track
.getId() )
2083 def getNumTracks(self
):
2084 return len( self
._tracks
)
2086 def getLocator(self
):
2087 loc
= self
.getConference().getLocator()
2088 loc
["abstractId"] = self
.getId()
2091 def isAllowedToCoordinate(self
, av
):
2092 """Tells whether or not the specified user can coordinate any of the
2093 tracks of this abstract
2095 for track
in self
.getTrackList():
2096 if track
.canUserCoordinate(av
):
2100 def canAuthorAccess(self
, user
):
2103 el
= self
.getCoAuthorEmailList(True)+self
.getPrimaryAuthorEmailList(True)
2104 for e
in user
.getEmails():
2109 def isAllowedToAccess(self
, av
):
2110 """Tells whether or not an avatar can access an abstract independently
2113 #any author is allowed to access
2114 #CFA managers are allowed to access
2115 #any user being able to modify is also allowed to access
2116 #any TC is allowed to access
2117 if self
.canAuthorAccess(av
):
2119 if self
.isAllowedToCoordinate(av
):
2121 return self
.canUserModify(av
)
2123 def canAccess(self
, aw
):
2124 #if the conference is protected, then only allowed AW can access
2125 return self
.isAllowedToAccess(aw
.getUser())
2127 def canView(self
, aw
):
2128 #in the future it would be possible to add an access control
2129 #only those users allowed to access are allowed to view
2130 return self
.isAllowedToAccess(aw
.getUser())
2132 def canModify(self
, aw_or_user
):
2133 if hasattr(aw_or_user
, 'getUser'):
2134 aw_or_user
= aw_or_user
.getUser()
2135 return self
.canUserModify(aw_or_user
)
2137 def canUserModify(self
, av
):
2138 #the submitter can modify
2139 if self
.isSubmitter(av
):
2141 #??? any CFA manager can modify
2142 #??? any user granted with modification privileges can modify
2143 #conference managers can modify
2144 conf
= self
.getConference()
2145 return conf
.canUserModify(av
)
2147 def getModifKey(self
):
2150 def getAccessKey(self
):
2153 def getAccessController(self
):
2154 return self
.getConference().getAccessController()
2156 def isProtected(self
):
2157 return self
.getConference().isProtected()
2161 self
.getOwner().unregisterParticipation(self
._submitter
)
2162 self
._submitter
.getUser().unlinkTo(self
, "submitter")
2163 self
._submitter
.delete()
2164 self
._submitter
= None
2166 self
.clearSpeakers()
2170 owner
.removeAbstract(self
)
2171 self
.setCurrentStatus(AbstractStatusNone(self
))
2172 TrashCanManager().add(self
)
2174 def recoverFromTrashCan(self
):
2175 TrashCanManager().remove(self
)
2177 def getCurrentStatus(self
):
2179 if self
._currentStatus
:
2181 except AttributeError, e
:
2182 self
._currentStatus
= AbstractStatusSubmitted(self
)
2183 return self
._currentStatus
2185 def setCurrentStatus(self
, newStatus
):
2186 self
._currentStatus
= newStatus
2187 #If we want to keep a history of status changes we should add here
2188 # the old status to a list
2190 def accept(self
, responsible
, destTrack
, type, comments
="", session
=None):
2193 self
.getCurrentStatus().accept(responsible
, destTrack
, type, comments
)
2194 #add the abstract to the track for which it has been accepted so it
2195 # is visible for it.
2196 if destTrack
is not None:
2197 destTrack
.addAbstract(self
)
2198 #once the abstract is accepted a new contribution under the destination
2199 # track must be created
2200 # ATTENTION: This import is placed here explicitely for solving
2201 # problems with circular imports
2202 from MaKaC
.conference
import AcceptedContribution
2203 contrib
= AcceptedContribution(self
)
2205 contrib
.setSession(session
)
2206 contrib
.setDuration(dur
=session
.getContribDuration())
2208 contrib
.setDuration()
2209 self
.getCurrentStatus().setContribution(contrib
)
2210 self
._setContribution
(contrib
)
2212 def reject(self
, responsible
, comments
=""):
2215 self
.getCurrentStatus().reject(responsible
, comments
)
2217 def _cmpByDate(self
, tj1
, tj2
):
2218 return cmp(tj1
.getDate(), tj2
.getDate())
2220 def getTrackJudgementsHistorical(self
):
2222 if self
._trackJudgementsHistorical
:
2224 if type(self
._trackJudgementsHistorical
) == tuple:
2225 self
._trackJudgementsHistorical
= {}
2226 except AttributeError:
2227 self
._trackJudgementsHistorical
= {}
2228 for track
in self
.getTrackList():
2230 if self
.getTrackAcceptances().has_key(track
.getId()):
2231 judgement
= self
.getTrackAcceptances()[track
.getId()]
2232 elif self
.getTrackRejections().has_key(track
.getId()):
2233 judgement
= self
.getTrackRejections()[track
.getId()]
2234 elif self
.getTrackReallocations().has_key(track
.getId()):
2235 judgement
= self
.getTrackReallocations()[track
.getId()]
2236 self
._trackJudgementsHistorical
[track
.getId()] = [judgement
]
2237 self
._notifyModification
()
2238 return self
._trackJudgementsHistorical
2240 def getJudgementHistoryByTrack(self
, track
):
2242 if track
is not None:
2244 if self
.getTrackJudgementsHistorical().has_key(id):
2245 return self
.getTrackJudgementsHistorical()[id]
2248 def _addTrackJudgementToHistorical(self
, tj
):
2250 if tj
.getTrack() is not None:
2251 id = tj
.getTrack().getId()
2252 if self
.getTrackJudgementsHistorical().has_key(id):
2253 if tj
not in self
.getTrackJudgementsHistorical()[id]:
2254 self
.getTrackJudgementsHistorical()[id].insert(0, tj
)
2256 self
.getTrackJudgementsHistorical()[id] = [tj
]
2257 self
._notifyModification
()
2259 def _removeTrackAcceptance( self
, track
):
2262 if self
.getTrackAcceptances().has_key( track
.getId() ):
2263 del self
.getTrackAcceptances()[ track
.getId() ]
2265 def _addTrackAcceptance( self
, judgement
):
2268 self
._removeTrackRejection
( judgement
.getTrack() )
2269 self
._removeTrackReallocation
( judgement
.getTrack() )
2270 self
.getTrackAcceptances()[ judgement
.getTrack().getId() ] = judgement
2271 self
._addTrackJudgementToHistorical
(judgement
)
2273 def _removeTrackRejection( self
, track
):
2276 if self
.getTrackRejections().has_key( track
.getId() ):
2277 del self
.getTrackRejections()[ track
.getId() ]
2279 def _addTrackRejection( self
, judgement
):
2282 self
._removeTrackAcceptance
( judgement
.getTrack() )
2283 self
._removeTrackReallocation
( judgement
.getTrack() )
2284 self
.getTrackRejections()[ judgement
.getTrack().getId() ] = judgement
2285 self
._addTrackJudgementToHistorical
(judgement
)
2287 def _removeTrackReallocation( self
, track
):
2290 if self
.getTrackReallocations().has_key( track
.getId() ):
2291 del self
.getTrackReallocations()[ track
.getId() ]
2293 def _addTrackReallocation( self
, judgement
):
2296 self
._removeTrackAcceptance
( judgement
.getTrack() )
2297 self
._removeTrackRejection
( judgement
.getTrack() )
2298 self
.getTrackReallocations()[ judgement
.getTrack().getId() ] = judgement
2299 self
._addTrackJudgementToHistorical
(judgement
)
2301 def _clearTrackRejections( self
):
2302 while len(self
.getTrackRejections().values())>0:
2303 t
= self
.getTrackRejections().values()[0].getTrack()
2304 self
._removeTrackRejection
( t
)
2306 def _clearTrackAcceptances( self
):
2307 while len(self
.getTrackAcceptances().values())>0:
2308 t
= self
.getTrackAcceptances().values()[0].getTrack()
2309 self
._removeTrackAcceptance
( t
)
2311 def _clearTrackReallocations( self
):
2312 while len(self
.getTrackReallocations().values())>0:
2313 t
= self
.getTrackReallocations().values()[0].getTrack()
2314 self
._removeTrackReallocation
(t
)
2316 def _removePreviousJud(self
, responsible
, track
):
2317 ''' Check if there is a previous judgement and remove it '''
2318 toDelete
= [] # list of judgements to delete
2319 for jud
in self
.getJudgementHistoryByTrack(track
):
2320 if jud
.getResponsible() == responsible
:
2321 toDelete
.append(jud
)
2324 self
.getTrackJudgementsHistorical()[track
.getId()].remove(x
)
2327 def proposeToAccept( self
, responsible
, track
, contribType
, comment
="", answers
=[] ):
2330 # the proposal has to be done for a track
2332 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"))
2333 #We check the track for which the abstract is proposed to be accepted
2334 # is in the current abstract
2335 if not self
.isProposedForTrack( track
):
2336 raise MaKaCError( _("Cannot propose to accept an abstract which is not proposed for the specified track"))
2337 # check if there is a previous judgement of this author in for this abstract in this track
2338 self
._removePreviousJud
(responsible
, track
)
2339 # Create the new judgement
2340 jud
= AbstractAcceptance( track
, responsible
, contribType
, answers
)
2341 jud
.setComment( comment
)
2342 self
._addTrackAcceptance
( jud
)
2343 # Update the rating of the abstract
2345 #We trigger the state transition
2346 self
.getCurrentStatus().proposeToAccept()
2348 def proposeToReject( self
, responsible
, track
, comment
="", answers
=[] ):
2351 # the proposal has to be done for a track
2353 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"))
2354 #We check the track for which the abstract is proposed to be accepted
2355 # is in the current abstract
2356 if not self
.isProposedForTrack( track
):
2357 raise MaKaCError( _("Cannot propose to reject an abstract which is not proposed for the specified track"))
2358 # check if there is a previous judgement of this author in for this abstract in this track
2359 self
._removePreviousJud
(responsible
, track
)
2360 # Create the new judgement
2361 jud
= AbstractRejection( track
, responsible
, answers
)
2362 jud
.setComment( comment
)
2363 self
._addTrackRejection
( jud
)
2364 # Update the rating of the abstract
2366 #We trigger the state transition
2367 self
.getCurrentStatus().proposeToReject()
2369 def proposeForOtherTracks( self
, responsible
, track
, comment
, propTracks
, answers
=[] ):
2372 #We check the track which proposes to allocate the abstract is in the
2374 if not self
.isProposedForTrack( track
):
2375 raise MaKaCError( _("Cannot propose to reallocate an abstract which is not proposed for the specified track"))
2376 # check if there is a previous judgement of this author in for this abstract in this track
2377 self
._removePreviousJud
(responsible
, track
)
2378 #We keep the track judgement
2379 jud
= AbstractReallocation( track
, responsible
, propTracks
, answers
)
2380 jud
.setComment( comment
)
2381 self
._addTrackReallocation
( jud
)
2382 #We add the proposed tracks to the abstract
2383 for track
in propTracks
:
2384 self
._addTrack
( track
)
2385 #We trigger the state transition
2386 self
.getCurrentStatus().proposeToReallocate()
2387 # Update the rating of the abstract
2390 def withdraw(self
,resp
,comment
=""):
2393 self
.getCurrentStatus().withdraw(resp
,comment
)
2395 def recover( self
):
2396 """Puts a withdrawn abstract back in the list of submitted abstracts.
2397 HAS NOTHING TO DO WITH THE RECOVERY PROCESS...
2399 #we must clear any track judgement
2400 #self._clearTrackAcceptances()
2401 #self._clearTrackRejections()
2402 #self._clearTrackReallocations()
2403 self
.getCurrentStatus().recover() #status change
2404 #if succeeded we must reset the submission date
2405 self
._setSubmissionDate
( nowutc() )
2406 self
._notifyModification
()
2408 def getTrackJudgement( self
, track
):
2409 if not self
.getJudgementHistoryByTrack(track
):
2411 lastJud
= self
.getJudgementHistoryByTrack(track
)[0]
2412 # check if judgements for specified trak are the same. If not there is a conflict.
2413 if all(jud
.__class
__ == lastJud
.__class
__ for jud
in self
.getJudgementHistoryByTrack(track
)):
2415 return AbstractInConflict(track
)
2417 def getTrackAcceptances( self
):
2419 if self
._trackAcceptances
:
2421 except AttributeError, e
:
2422 self
._trackAcceptances
= OOBTree()
2423 return self
._trackAcceptances
2425 def getTrackAcceptanceList( self
):
2427 for trackId
in intersection( self
._tracks
, self
.getTrackAcceptances() ):
2428 res
.append( self
.getTrackAcceptances()[ trackId
] )
2431 def getNumProposedToAccept( self
):
2432 return len( intersection( self
._tracks
, self
.getTrackAcceptances() ) )
2434 def getTrackRejections( self
):
2436 if self
._trackRejections
:
2438 except AttributeError, e
:
2439 self
._trackRejections
= OOBTree()
2440 return self
._trackRejections
2442 def getNumProposedToReject( self
):
2443 return len( intersection( self
._tracks
, self
.getTrackRejections() ) )
2445 def getTrackReallocations( self
):
2447 if self
._trackReallocations
:
2449 except AttributeError, e
:
2450 self
._trackReallocations
= OOBTree()
2451 return self
._trackReallocations
2454 def getNumProposedToReallocate( self
):
2455 return len( intersection( self
._tracks
, self
.getTrackReallocations() ) )
2458 def getNumJudgements( self
):
2460 Returns the number of tracks for which some proposal has been done.
2461 For instance, let's suppose:
2462 Track 1: 2 propose to accept, 3 propose to reject
2463 Track 2: 1 propose to accept
2465 The result would be 2 (out of 3)
2467 tmp1
= union( self
.getTrackAcceptances(), self
.getTrackRejections() )
2468 judgements
= union( tmp1
, self
.getTrackReallocations() )
2469 return len( intersection( self
._tracks
, judgements
) )
2471 def getReallocationTargetedList( self
, track
):
2474 for r
in self
.getTrackReallocations().values():
2475 if track
in r
.getProposedTrackList():
2479 def getContribution( self
):
2481 if self
._contribution
:
2483 except AttributeError:
2484 self
._contribution
= None
2485 status
= self
.getCurrentStatus()
2486 if isinstance(status
,AbstractStatusAccepted
) and \
2487 self
._contribution
is None:
2488 self
._contribution
=status
.getContribution()
2489 return self
._contribution
2491 def _setContribution(self
,contrib
):
2492 self
._contribution
= contrib
2494 def getIntCommentList(self
):
2496 if self
._intComments
:
2498 except AttributeError:
2499 self
._intComments
=PersistentList()
2500 return self
._intComments
2502 def addIntComment(self
,newComment
):
2504 if self
._intComments
:
2506 except AttributeError:
2507 self
._intComments
=PersistentList()
2509 if self
._intCommentsGen
:
2511 except AttributeError:
2512 self
._intCommentsGen
=Counter()
2513 if newComment
in self
._intComments
:
2515 id = newComment
.getId()
2517 id = self
._authorGen
.newCount()
2518 newComment
.includeInAbstract(self
, id)
2519 self
._intComments
.append(newComment
)
2521 def getIntCommentById(self
,id):
2523 if self
._intComments
:
2525 except AttributeError:
2526 self
._intComments
=PersistentList()
2527 for comment
in self
._intComments
:
2528 if id.strip()==comment
.getId():
2532 def clearIntCommentList(self
):
2533 while len(self
.getIntCommentList()) > 0:
2534 self
.removeIntComment(self
.getIntCommentList()[0])
2536 def removeIntComment(self
,comment
):
2538 if self
._intComments
:
2540 except AttributeError:
2541 self
._intComments
=PersistentList()
2542 if comment
not in self
._intComments
:
2544 self
._intComments
.remove(comment
)
2547 def recoverIntComment(self
, comment
):
2548 self
.addIntComment(comment
)
2551 def markAsDuplicated(self
,responsible
,originalAbstract
,comments
="", track
=None, answers
=[]):
2554 self
.getCurrentStatus().markAsDuplicated(responsible
,originalAbstract
,comments
)
2555 # check if there is a previous judgement of this author in for this abstract in this track
2556 self
._removePreviousJud
(responsible
, track
)
2558 if track
is not None:
2559 jud
= AbstractMarkedAsDuplicated( track
, responsible
, originalAbstract
, answers
)
2560 jud
.setComment( comments
)
2561 self
._addTrackJudgementToHistorical
(jud
)
2563 for t
in self
.getTrackList():
2564 jud
= AbstractMarkedAsDuplicated( t
, responsible
, originalAbstract
, answers
)
2565 jud
.setComment( comments
)
2566 self
._addTrackJudgementToHistorical
(jud
)
2567 # Update the rating of the abstract
2570 def unMarkAsDuplicated(self
,responsible
,comments
="", track
=None, answers
=[]):
2574 #we must clear any track judgement
2575 self
._clearTrackAcceptances
()
2576 self
._clearTrackRejections
()
2577 self
._clearTrackReallocations
()
2578 #self.getCurrentStatus().recover() #status change
2579 self
.getCurrentStatus().unMarkAsDuplicated(responsible
,comments
)
2581 # check if there is a previous judgement of this author in for this abstract in this track
2582 self
._removePreviousJud
(responsible
, track
)
2584 if track
is not None:
2585 jud
= AbstractUnMarkedAsDuplicated(track
, responsible
, answers
)
2586 jud
.setComment( comments
)
2587 self
._addTrackJudgementToHistorical
(jud
)
2589 for t
in self
.getTrackList():
2590 jud
= AbstractUnMarkedAsDuplicated( t
, responsible
, answers
)
2591 jud
.setComment( comments
)
2592 self
._addTrackJudgementToHistorical
(jud
)
2593 # Update the rating of the abstract
2595 self
._notifyModification
()
2597 def mergeInto(self
,responsible
,targetAbs
,mergeAuthors
=False,comments
=""):
2600 self
.getCurrentStatus().mergeInto(responsible
,targetAbs
,comments
)
2601 targetAbs
.addMergeFromAbstract(self
)
2603 #for auth in self.getAuthorList():
2604 # newAuth=targetAbs.newAuthor()
2605 # newAuth.setFromAbstractParticipation(auth)
2606 # if self.isPrimaryAuthor(auth):
2607 # targetAbs.addPrimaryAuthor(newAuth)
2608 for auth
in self
.getPrimaryAuthorList():
2609 newAuth
=targetAbs
.newPrimaryAuthor()
2610 newAuth
.setFromAbstractParticipation(auth
)
2611 for auth
in self
.getCoAuthorList():
2612 newAuth
=targetAbs
.newCoAuthor()
2613 newAuth
.setFromAbstractParticipation(auth
)
2615 def notify(self
,notificator
,responsible
):
2616 """notifies the abstract responsibles with a matching template
2618 tpl
=self
.getOwner().getNotifTplForAbstract(self
)
2621 notificator
.notify(self
,tpl
)
2622 self
.getNotificationLog().addEntry(NotifLogEntry(responsible
,tpl
))
2624 def unMerge(self
,responsible
,comments
=""):
2625 #we must clear any track judgement
2626 self
._clearTrackAcceptances
()
2627 self
._clearTrackRejections
()
2628 self
._clearTrackReallocations
()
2629 self
.getCurrentStatus().getTargetAbstract().removeMergeFromAbstract(self
)
2630 self
.getCurrentStatus().unMerge(responsible
,comments
)
2631 self
._notifyModification
()
2633 def getNotificationLog(self
):
2637 except AttributeError:
2638 self
._notifLog
=NotificationLog(self
)
2639 return self
._notifLog
2642 def getRating(self
):
2643 """ Get the average rating of the abstract """
2647 except AttributeError:
2651 def updateRating(self
, scale
= None):
2653 Update the average rating of the abstract which is calculated with the average of each judgement.
2654 If the scale (tuple with lower,higher) is passed, the judgement are re-adjusted to the new scale.
2657 # calculate the total valoration
2660 for track
in self
.getTrackListSorted():
2661 for jud
in self
.getJudgementHistoryByTrack(track
):
2663 # calculate the new values for each judgement
2664 scaleLower
, scaleHigher
= scale
2665 jud
.recalculateJudgementValues(scaleLower
, scaleHigher
)
2666 if jud
.getJudValue() != None: # it means there is a numeric value for the judgement
2667 ratingSum
+= jud
.getJudValue()
2669 # Calculate the average
2671 self
._rating
= float(ratingSum
) / judNum
2673 def getQuestionsAverage(self
):
2674 '''Get the list of questions answered in the reviews for an abstract '''
2675 dTotals
= {} # {idQ1: total_value, idQ2: total_value ...}
2676 dTimes
= {} # {idQ1: times_answered, idQ2: times_answered}
2677 for track
in self
.getTrackListSorted():
2678 for jud
in self
.getJudgementHistoryByTrack(track
):
2679 for answer
in jud
.getAnswers():
2680 # check if the question is in d and sum the answers value or insert in d the new question
2681 if dTotals
.has_key(answer
.getQuestion().getText()):
2682 dTotals
[answer
.getQuestion().getText()] += answer
.getValue()
2683 dTimes
[answer
.getQuestion().getText()] += 1
2685 dTotals
[answer
.getQuestion().getText()] = answer
.getValue()
2686 dTimes
[answer
.getQuestion().getText()] = 1
2687 # get the questions average
2688 questionsAverage
= {}
2689 for q
, v
in dTotals
.iteritems():
2690 # insert the element and calculate the average for the value
2691 questionsAverage
[q
] = float(v
)/dTimes
[q
]
2692 return questionsAverage
2694 def removeAnswersOfQuestion(self
, questionId
):
2695 ''' Remove the answers of the question with questionId value '''
2696 for track
in self
.getTrackListSorted():
2697 for jud
in self
.getJudgementHistoryByTrack(track
):
2698 jud
.removeAnswer(questionId
)
2700 def getRatingPerReviewer(self
, user
, track
):
2702 Get the rating of the user for the abstract in the track given.
2704 for jud
in self
.getJudgementHistoryByTrack(track
):
2705 if (jud
.getResponsible() == user
):
2706 return jud
.getJudValue()
2708 def getLastJudgementPerReviewer(self
, user
, track
):
2710 Get the last judgement of the user for the abstract in the track given.
2712 for jud
in self
.getJudgementHistoryByTrack(track
):
2713 if (jud
.getResponsible() == user
):
2716 def _getAttachmentsCounter(self
):
2718 if self
._attachmentsCounter
:
2720 except AttributeError:
2721 self
._attachmentsCounter
= Counter()
2722 return self
._attachmentsCounter
.newCount()
2724 def setAttachments(self
, attachments
):
2725 self
._attachments
= attachments
2727 def getAttachments(self
):
2729 if self
._attachments
:
2731 except AttributeError:
2732 self
._attachments
= {}
2733 return self
._attachments
2735 def getAttachmentById(self
, id):
2736 return self
.getAttachments().get(id, None)
2739 class AbstractJudgement( Persistent
):
2740 """This class represents each of the judgements made by a track about a
2741 certain abstract. Each track for which an abstract is proposed can
2742 make a judgement proposing the abstract to be accepted or rejected.
2743 Different track judgements must be kept so the referees who have to
2744 take the final decission can overview different opinions from the
2746 Together with the judgement some useful information like the date when
2747 it was done and the user who did it will be kept.
2750 def __init__( self
, track
, responsible
, answers
):
2752 self
._setResponsible
( responsible
)
2753 self
._date
= nowutc()
2755 self
._answers
= answers
2756 self
._judValue
= self
.calculateJudgementAverage() # judgement average value
2757 self
._totalJudValue
= self
.calculateAnswersTotalValue()
2760 def _setResponsible( self
, newRes
):
2761 self
._responsible
= newRes
2763 def getResponsible( self
):
2764 return self
._responsible
2766 def getDate( self
):
2769 def setDate(self
, date
):
2772 def getTrack( self
):
2775 def setComment( self
, newComment
):
2776 self
._comment
= newComment
.strip()
2778 def getComment( self
):
2779 return self
._comment
2781 def getAnswers(self
):
2785 except AttributeError:
2787 return self
._answers
2789 def calculateJudgementAverage(self
):
2790 '''Calculate the average value of the given answers'''
2792 if (len(self
.getAnswers()) != 0):
2793 # convert the values into float types
2794 floatList
= [ans
.getValue() for ans
in self
._answers
]
2795 result
= sum(floatList
) / float(len(floatList
)) # calculate the average
2797 # there are no questions
2801 def getJudValue(self
):
2805 except AttributeError:
2806 self
._judValue
= self
.calculateJudgementAverage() # judgement average value
2807 return self
._judValue
2809 def getTotalJudValue(self
):
2811 if self
._totalJudValue
:
2813 except AttributeError:
2814 self
._totalJudValue
= self
.calculateAnswersTotalValue()
2815 return self
._totalJudValue
2817 def calculateAnswersTotalValue(self
):
2818 ''' Calculate the sum of all the ratings '''
2820 for ans
in self
.getAnswers():
2821 result
+= ans
.getValue()
2824 def recalculateJudgementValues(self
, scaleLower
, scaleHigher
):
2825 ''' Update the values of the judgement. This function is called when the scale is changed.'''
2826 for ans
in self
.getAnswers():
2827 ans
.calculateRatingValue(scaleLower
, scaleHigher
)
2828 self
._judValue
= self
.calculateJudgementAverage()
2829 self
._totalJudValue
= self
.calculateAnswersTotalValue()
2831 def removeAnswer(self
, questionId
):
2832 ''' Remove the current answers of the questionId '''
2833 for ans
in self
.getAnswers():
2834 if ans
.getQuestion().getId() == questionId
:
2835 self
._answers
.remove(ans
)
2836 self
._notifyModification
()
2838 def _notifyModification(self
):
2842 class AbstractAcceptance( AbstractJudgement
):
2844 def __init__( self
, track
, responsible
, contribType
, answers
):
2845 AbstractJudgement
.__init
__( self
, track
, responsible
, answers
)
2846 self
._contribType
= contribType
2848 def clone(self
,track
):
2849 aa
= AbstractAcceptance(track
,self
.getResponsible(), self
.getContribType(), self
.getAnswers())
2852 def getContribType( self
):
2854 if self
._contribType
:
2856 except AttributeError, e
:
2857 self
._contribType
= None
2858 return self
._contribType
2861 class AbstractRejection( AbstractJudgement
):
2863 def clone(self
, track
):
2864 arj
= AbstractRejection(track
,self
.getResponsible(), self
.getAnswers())
2867 class AbstractReallocation( AbstractJudgement
):
2869 def __init__( self
, track
, responsible
, propTracks
, answers
):
2870 AbstractJudgement
.__init
__( self
, track
, responsible
, answers
)
2871 self
._proposedTracks
= PersistentList( propTracks
)
2873 def clone(self
, track
):
2874 arl
= AbstractReallocation(track
, self
.getResponsible(), self
.getProposedTrackList(), self
.getAnswers())
2877 def getProposedTrackList( self
):
2878 return self
._proposedTracks
2880 class AbstractInConflict( AbstractJudgement
):
2882 def __init__( self
, track
):
2883 AbstractJudgement
.__init
__( self
, track
, None, '' )
2885 def clone(self
, track
):
2886 aic
= AbstractInConflict(track
, None, '')
2889 class AbstractMarkedAsDuplicated( AbstractJudgement
):
2891 def __init__( self
, track
, responsible
, originalAbst
, answers
):
2892 AbstractJudgement
.__init
__( self
, track
, responsible
, answers
)
2893 self
._originalAbst
=originalAbst
2895 def clone(self
,track
):
2896 amad
= AbstractMarkedAsDuplicated(track
,self
.getResponsible(), self
.getOriginalAbstract(), self
.getAnswers())
2899 def getOriginalAbstract(self
):
2900 return self
._originalAbst
2903 class AbstractUnMarkedAsDuplicated( AbstractJudgement
):
2905 def clone(self
,track
):
2906 auad
= AbstractUnMarkedAsDuplicated(track
,self
.getResponsible())
2910 class AbstractStatus( Persistent
):
2911 """This class represents any of the status in which an abstract can be.
2912 From the moment they are submitted (and therefore created), abstracts
2913 can go throuugh different status each having a different meaning.
2914 As there can be many status, the transitions between them are quite
2915 complex and as the system evolves we could require to add or delete
2916 new status the "Status" pattern is applied. This is the base class.
2917 Apart from giving information about the status of an abstract, this
2918 class is responsible to store information about how the status was
2919 reached (who provoke the transition, when, ...).
2923 def __init__( self
, abstract
):
2924 self
._setAbstract
( abstract
)
2925 self
._setDate
( nowutc() )
2930 def _setAbstract( self
, abs ):
2931 self
._abstract
= abs
2933 def getAbstract( self
):
2934 return self
._abstract
2936 def _setDate( self
, date
):
2939 def getDate( self
):
2942 def accept(self
,responsible
,destTrack
,type,comments
=""):
2945 s
= AbstractStatusAccepted(self
.getAbstract(),responsible
,destTrack
,type,comments
)
2946 self
.getAbstract().setCurrentStatus( s
)
2948 def reject( self
, responsible
, comments
= "" ):
2951 s
= AbstractStatusRejected( self
.getAbstract(), responsible
, comments
)
2952 self
.getAbstract().setCurrentStatus( s
)
2954 def _getStatusClass( self
):
2957 numAccepts
= self
._abstract
.getNumProposedToAccept() # number of tracks that have at least one proposal to accept
2958 numReallocate
= self
._abstract
.getNumProposedToReallocate() # number of tracks that have at least one proposal to reallocate
2959 numJudgements
= self
._abstract
.getNumJudgements() # number of tracks that have at least one judgement
2960 if numJudgements
> 0:
2961 # If at least one track status is in conflict the abstract status is in conflict too.
2962 if any(isinstance(self
._abstract
.getTrackJudgement(track
), AbstractInConflict
) for track
in self
._abstract
.getTrackList()):
2963 return AbstractStatusInConflict
2964 numTracks
= self
._abstract
.getNumTracks() # number of tracks that this abstract has assigned
2965 if numTracks
== numJudgements
: # Do we have judgements for all tracks?
2966 if numReallocate
== numTracks
:
2967 return AbstractStatusInConflict
2968 elif numAccepts
== 1:
2969 return AbstractStatusProposedToAccept
2970 elif numAccepts
== 0:
2971 return AbstractStatusProposedToReject
2972 return AbstractStatusInConflict
2973 return AbstractStatusUnderReview
2974 return AbstractStatusSubmitted
2980 newStatusClass
= self
._getStatusClass
()
2981 if self
.__class
__ != newStatusClass
:
2982 self
.getAbstract().setCurrentStatus( newStatusClass( self
._abstract
) )
2984 def proposeToAccept( self
):
2987 s
= self
._getStatusClass
()( self
._abstract
)
2988 self
.getAbstract().setCurrentStatus( s
)
2990 def proposeToReject( self
):
2993 s
= self
._getStatusClass
()( self
._abstract
)
2994 self
.getAbstract().setCurrentStatus( s
)
2996 def proposeToReallocate( self
):
2999 s
= self
._getStatusClass
()( self
._abstract
)
3000 self
.getAbstract().setCurrentStatus( s
)
3002 def withdraw(self
,resp
,comments
=""):
3005 s
=AbstractStatusWithdrawn(self
.getAbstract(), resp
, self
, comments
)
3006 self
.getAbstract().setCurrentStatus(s
)
3008 def recover( self
):
3011 raise MaKaCError( _("only withdrawn abstracts can be recovered"))
3013 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3016 if self
.getAbstract()==originalAbs
:
3017 raise MaKaCError( _("the original abstract is the same as the duplicated one"))
3018 if isinstance(originalAbs
.getCurrentStatus(),AbstractStatusDuplicated
):
3019 raise MaKaCError( _("cannot set as original abstract one which is already marked as duplicated"))
3020 s
=AbstractStatusDuplicated(self
.getAbstract(),responsible
,originalAbs
,comments
)
3021 self
.getAbstract().setCurrentStatus(s
)
3023 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3026 raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3028 def mergeInto(self
,responsible
,targetAbs
,comments
=""):
3031 if self
.getAbstract()==targetAbs
:
3032 raise MaKaCError( _("An abstract cannot be merged into itself"))
3033 if targetAbs
.getCurrentStatus().__class
__ not in [AbstractStatusSubmitted
,AbstractStatusUnderReview
,AbstractStatusProposedToAccept
,AbstractStatusProposedToReject
,AbstractStatusInConflict
]:
3034 raise MaKaCError(_("Target abstract is in a status which cannot receive mergings"))
3035 s
=AbstractStatusMerged(self
.getAbstract(),responsible
,targetAbs
,comments
)
3036 self
.getAbstract().setCurrentStatus(s
)
3038 def unMerge(self
,responsible
,comments
=""):
3041 raise MaKaCError( _("Only merged abstracts can be unmerged"))
3043 def getComments(self
):
3048 class AbstractStatusSubmitted( AbstractStatus
):
3052 def clone(self
,abstract
):
3053 ass
= AbstractStatusSubmitted(abstract
)
3057 #if an abstract that has been submitted has no judgement it
3058 # must remain in the submitted status
3059 if self
._abstract
.getNumJudgements() == 0:
3061 AbstractStatus
.update( self
)
3064 class AbstractStatusAccepted( AbstractStatus
):
3067 def __init__(self
,abstract
,responsible
,destTrack
,type,comments
=""):
3068 AbstractStatus
.__init
__( self
, abstract
)
3069 self
._setResponsible
( responsible
)
3070 self
._setTrack
( destTrack
)
3071 self
._setComments
( comments
)
3072 self
._setType
( type )
3073 self
._contrib
= None
3075 def clone(self
,abstract
):
3076 asa
= AbstractStatusAccepted(abstract
,self
.getResponsible(), self
.getTrack(), self
.getType(), self
.getComments())
3079 def _setResponsible( self
, res
):
3080 self
._responsible
= res
3082 def getResponsible( self
):
3083 return self
._responsible
3085 def _setComments( self
, comments
):
3086 self
._comments
= str( comments
).strip()
3088 def getComments( self
):
3092 except AttributeError:
3094 return self
._comments
3096 def _setTrack( self
, track
):
3099 def getTrack( self
):
3103 except AttributeError:
3107 def _setType( self
, type ):
3108 self
._contribType
= type
3110 def getType( self
):
3112 if self
._contribType
:
3114 except AttributeError:
3115 self
._contribType
= None
3116 return self
._contribType
3118 def setContribution( self
, newContrib
):
3119 self
._contrib
= newContrib
3121 def getContribution( self
):
3125 except AttributeError:
3126 self
._contrib
= None
3127 return self
._contrib
3132 def accept(self
,responsible
,destTrack
,type,comments
="" ):
3133 raise MaKaCError( _("Cannot accept an abstract which is already accepted"))
3135 def reject( self
, responsible
, comments
="" ):
3136 raise MaKaCError( _("Cannot reject an abstract which is already accepted"))
3138 def proposeToAccept( self
):
3139 raise MaKaCError( _("Cannot propose for acceptance an abstract which is already accepted"))
3141 def proposeToReject( self
):
3142 raise MaKaCError( _("Cannot propose for rejection an abstract which is already accepted"))
3144 def proposeToReallocate( self
):
3145 raise MaKaCError( _("Cannot propose for reallocation an abstract which is already accepted"))
3147 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3148 raise MaKaCError( _("Cannot mark as duplicated an abstract which is accepted"))
3150 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3153 raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3155 def mergeInto(self
,responsible
,targetAbs
,comments
=""):
3156 raise MaKaCError( _("Cannot merge an abstract which is already accepted"))
3158 def withdraw(self
,resp
,comments
=""):
3161 contrib
=self
.getContribution()
3162 #this import is made here and not at the top of the file in order to
3163 # avoid recursive import troubles
3164 from MaKaC
.conference
import ContribStatusWithdrawn
3165 if contrib
is not None and \
3166 not isinstance(contrib
.getCurrentStatus(),ContribStatusWithdrawn
):
3167 contrib
.withdraw(resp
, i18nformat(""" _("abstract withdrawn"): %s""")%comments
)
3168 AbstractStatus
.withdraw(self
,resp
,comments
)
3171 class AbstractStatusRejected( AbstractStatus
):
3174 def __init__( self
, abstract
, responsible
, comments
= "" ):
3175 AbstractStatus
.__init
__( self
, abstract
)
3176 self
._setResponsible
( responsible
)
3177 self
._setComments
( comments
)
3179 def clone(self
,abstract
):
3180 asr
= AbstractStatusRejected(abstract
, self
.getResponsible(), self
.getComments())
3183 def _setResponsible( self
, res
):
3184 self
._responsible
= res
3186 def getResponsible( self
):
3187 return self
._responsible
3189 def _setComments( self
, comments
):
3190 self
._comments
= str( comments
).strip()
3192 def getComments( self
):
3196 except AttributeError:
3198 return self
._comments
3203 def reject( self
, responsible
, comments
="" ):
3204 raise MaKaCError( _("Cannot reject an abstract which is already rejected"))
3206 def proposeToAccept( self
):
3207 raise MaKaCError( _("Cannot propose for acceptance an abstract which is already rejected"))
3209 def proposeToReject( self
):
3210 raise MaKaCError( _("Cannot propose for rejection an abstract which is already rejected"))
3212 def proposeToReallocate( self
):
3213 raise MaKaCError( _("Cannot propose for reallocation an abstract which is already rejected"))
3215 def withdraw(self
,resp
,comments
=""):
3216 raise MaKaCError( _("Cannot withdraw a REJECTED abstract"))
3218 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3219 raise MaKaCError( _("Cannot mark as duplicated an abstract which is rejected"))
3221 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3224 raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3226 def mergeInto(self
,responsible
,targetAbs
,comments
=""):
3227 raise MaKaCError( _("Cannot merge an abstract which is rejected"))
3230 class AbstractStatusUnderReview( AbstractStatus
):
3233 def clone(self
,abstract
):
3234 asur
= AbstractStatusUnderReview(abstract
)
3237 class AbstractStatusProposedToAccept( AbstractStatus
):
3240 def clone(self
, abstract
):
3241 aspta
= AbstractStatusProposedToAccept(abstract
)
3245 jud
=self
.getAbstract().getTrackAcceptanceList()[0]
3246 return jud
.getTrack()
3249 jud
=self
.getAbstract().getTrackAcceptanceList()[0]
3250 return jud
.getContribType()
3253 class AbstractStatusProposedToReject( AbstractStatus
):
3256 def clone(self
, abstract
):
3257 asptr
= AbstractStatusProposedToReject(abstract
)
3260 class AbstractStatusInConflict( AbstractStatus
):
3263 def clone(self
,abstract
):
3264 asic
= AbstractStatusInConflict(abstract
)
3267 class AbstractStatusWithdrawn(AbstractStatus
):
3270 def __init__(self
,abstract
,responsible
, prevStatus
,comments
=""):
3271 AbstractStatus
.__init
__(self
,abstract
)
3272 self
._setComments
(comments
)
3273 self
._setResponsible
(responsible
)
3274 self
._prevStatus
=prevStatus
3276 def clone(self
,abstract
):
3277 asw
= AbstractStatusWithdrawn(abstract
,self
.getResponsible(),self
.getComments())
3280 def _setResponsible(self
,newResp
):
3281 self
._responsible
=newResp
3283 def getResponsible(self
):
3285 if self
._responsible
:
3287 except AttributeError,e
:
3288 self
._responsible
=self
._abstract
.getSubmitter().getAvatar()
3289 return self
._responsible
3291 def getPrevStatus(self
):
3293 if self
._prevStatus
:
3295 except AttributeError,e
:
3296 self
._prevStatus
=None
3297 return self
._prevStatus
3299 def _setComments( self
, comments
):
3300 self
._comments
= str( comments
).strip()
3302 def getComments( self
):
3303 return self
._comments
3308 def accept(self
,responsible
,destTrack
,type,comments
=""):
3309 raise MaKaCError( _("Cannot accept an abstract wich is withdrawn"))
3311 def reject( self
, responsible
, comments
="" ):
3312 raise MaKaCError( _("Cannot reject an abstract which is withdrawn"))
3314 def proposeToAccept( self
):
3315 raise MaKaCError( _("Cannot propose for acceptance an abstract which withdrawn"))
3317 def proposeToReject( self
):
3318 raise MaKaCError( _("Cannot propose for rejection an abstract which is withdrawn"))
3320 def recover( self
):
3321 if self
.getPrevStatus() is None:
3322 # reset all the judgments
3323 self
._clearTrackAcceptances
()
3324 self
._clearTrackRejections
()
3325 self
._clearTrackReallocations
()
3326 # setting the status
3327 contrib
=self
.getAbstract().getContribution()
3329 s
= AbstractStatusSubmitted( self
.getAbstract() )
3331 s
= AbstractStatusAccepted(self
.getAbstract(),self
.getResponsible(),contrib
.getTrack(),contrib
.getType(),"")
3333 contrib
=self
.getAbstract().getContribution()
3334 if contrib
is not None and not isinstance(self
.getPrevStatus(), AbstractStatusAccepted
):
3335 s
= AbstractStatusAccepted(self
.getAbstract(),self
.getResponsible(),contrib
.getTrack(),contrib
.getType(),"")
3337 s
=self
.getPrevStatus()
3338 self
.getAbstract().setCurrentStatus( s
)
3340 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3341 raise MaKaCError( _("Cannot mark as duplicated an abstract which is withdrawn"))
3343 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3346 raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3348 def mergeInto(self
,responsible
,targetAbs
,comments
=""):
3349 raise MaKaCError( _("Cannot merge an abstract which is withdrawn"))
3351 def withdraw(self
,resp
,comments
=""):
3352 raise MaKaCError( _("This abstract is already withdrawn"))
3356 class AbstractStatusDuplicated(AbstractStatus
):
3359 def __init__( self
,abstract
,responsible
,originalAbstract
,comments
=""):
3360 AbstractStatus
.__init
__(self
,abstract
)
3361 self
._setResponsible
(responsible
)
3362 self
._setComments
(comments
)
3363 self
._setOriginalAbstract
(originalAbstract
)
3365 def clone(self
, abstract
):
3366 asd
= AbstractStatusDuplicated(abstract
,self
.getResponsible(),self
.getOriginal(),self
.getComments())
3369 def _setResponsible( self
, res
):
3370 self
._responsible
= res
3372 def getResponsible(self
):
3373 return self
._responsible
3375 def _setComments( self
, comments
):
3376 self
._comments
= str( comments
).strip()
3378 def getComments( self
):
3379 return self
._comments
3381 def _setOriginalAbstract(self
,abs):
3384 def getOriginal(self
):
3385 return self
._original
3390 def reject( self
, responsible
, comments
="" ):
3391 raise MaKaCError( _("Cannot reject an abstract which is duplicated"))
3393 def proposeToAccept( self
):
3394 raise MaKaCError( _("Cannot propose for acceptance an abstract which is duplicated"))
3396 def proposeToReject( self
):
3397 raise MaKaCError( _("Cannot propose for rejection an abstract which is duplicated"))
3399 def proposeToReallocate( self
):
3400 raise MaKaCError( _("Cannot propose for reallocation an abstract which is duplicated"))
3402 def withdraw(self
,resp
,comments
=""):
3403 raise MaKaCError( _("Cannot withdraw a duplicated abstract"))
3405 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3406 raise MaKaCError( _("This abstract is already duplicated"))
3408 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3409 s
= AbstractStatusSubmitted( self
.getAbstract() )
3410 self
.getAbstract().setCurrentStatus( s
)
3412 def mergeInto(self
,responsible
,targetAbs
,comments
=""):
3413 raise MaKaCError( _("Cannot merge an abstract which is marked as a duplicate"))
3416 class AbstractStatusMerged(AbstractStatus
):
3420 def __init__(self
,abstract
,responsible
,targetAbstract
,comments
=""):
3421 AbstractStatus
.__init
__(self
,abstract
)
3422 self
._setResponsible
(responsible
)
3423 self
._setComments
(comments
)
3424 self
._setTargetAbstract
(targetAbstract
)
3426 def clone(self
,abstract
):
3427 asm
= AbstractStatusMerged(abstract
,self
.getResponsible(),self
.getTargetAbstract(),self
.getComments())
3430 def _setResponsible( self
, res
):
3431 self
._responsible
= res
3433 def getResponsible( self
):
3434 return self
._responsible
3436 def _setComments( self
, comments
):
3437 self
._comments
= str( comments
).strip()
3439 def getComments( self
):
3440 return self
._comments
3442 def _setTargetAbstract(self
,abstract
):
3443 self
._target
=abstract
3445 def getTargetAbstract(self
):
3451 def reject( self
, responsible
, comments
="" ):
3452 raise MaKaCError( _("Cannot reject an abstract which is merged into another one"))
3454 def proposeToAccept( self
):
3455 raise MaKaCError( _("Cannot propose for acceptance an abstract which is merged into another one"))
3457 def proposeToReject( self
):
3458 raise MaKaCError( _("Cannot propose for rejection an abstract which is merged into another one"))
3460 def proposeToReallocate( self
):
3461 raise MaKaCError( _("Cannot propose for reallocation an abstract which is merged into another one"))
3463 def withdraw(self
,resp
,comments
=""):
3464 raise MaKaCError( _("Cannot withdraw an abstract which is merged into another one"))
3466 def markAsDuplicated(self
,responsible
,originalAbs
,comments
=""):
3467 raise MaKaCError( _("Cannot mark as duplicated an abstract which is merged into another one"))
3469 def unMarkAsDuplicated(self
,responsible
,comments
=""):
3472 raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3474 def mergeInto(self
,responsible
,target
,comments
=""):
3475 raise MaKaCError( _("This abstract is already merged into another one"))
3477 def unMerge(self
,responsible
,comments
=""):
3478 s
= AbstractStatusSubmitted( self
.getAbstract() )
3479 self
.getAbstract().setCurrentStatus( s
)
3481 class AbstractStatusNone(AbstractStatus
):
3482 # This is a special status we assign to abstracts that are put in the trash can.
3484 def __init__(self
,abstract
):
3485 AbstractStatus
.__init
__(self
,abstract
)
3487 def clone(self
,abstract
):
3488 asn
= AbstractStatusNone(abstract
)
3491 class NotificationTemplate(Persistent
):
3497 self
._description
=""
3501 self
._CAasCCAddr
= False
3502 self
._ccAddrList
=PersistentList()
3503 self
._toAddrs
= PersistentList()
3504 self
._conditions
=PersistentList()
3505 self
._toAddrGenerator
=Counter()
3506 self
._condGenerator
=Counter()
3509 tpl
= NotificationTemplate()
3510 tpl
.setName(self
.getName())
3511 tpl
.setDescription(self
.getDescription())
3512 tpl
.setTplSubject(self
.getTplSubject())
3513 tpl
.setTplBody(self
.getTplBody())
3514 tpl
.setFromAddr(self
.getFromAddr())
3515 tpl
.setCAasCCAddr(self
.getCAasCCAddr())
3517 for cc
in self
.getCCAddrList() :
3519 for to
in self
.getToAddrList() :
3522 for con
in self
.getConditionList() :
3523 tpl
.addCondition(con
.clone(tpl
))
3529 self
.clearCCAddrList()
3530 self
.clearConditionList()
3531 TrashCanManager().add(self
)
3534 TrashCanManager().remove(self
)
3536 ## def getResponsible( self ):
3537 ## return self._responsible
3539 ## def _setComments( self, comments ):
3540 ## self._comments = str( comments ).strip()
3542 ## def getComments( self ):
3543 ## return self._comments
3545 ## def _setOriginalAbstract(self,abstract):
3546 ## self._original=abstract
3548 def canModify(self
, aw_or_user
):
3549 return self
.getConference().canModify(aw_or_user
)
3551 def getLocator(self
):
3552 loc
= self
.getOwner().getConference().getLocator()
3553 loc
["notifTplId"] = self
._id
3556 def getConference(self
):
3557 return self
._owner
.getConference()
3559 def includeInOwner(self
,owner
,id):
3569 def setName(self
,newName
):
3570 self
._name
=newName
.strip()
3575 def setDescription(self
,newDesc
):
3576 self
._description
=newDesc
.strip()
3578 def getDescription(self
):
3579 return self
._description
3581 def setTplSubject(self
,newSubject
, varList
):
3582 self
._tplSubject
=self
.parseTplContent(newSubject
, varList
).strip()
3584 def getTplSubject(self
):
3585 return self
._tplSubject
3587 def getTplSubjectShow(self
, varList
):
3588 return self
.parseTplContentUndo(self
._tplSubject
, varList
)
3590 def setTplBody(self
,newBody
, varList
):
3591 self
._tplBody
=self
.parseTplContent(newBody
, varList
).strip()
3593 def getTplBody(self
):
3594 return self
._tplBody
3596 def getTplBodyShow(self
, varList
):
3597 return self
.parseTplContentUndo(self
._tplBody
, varList
)
3599 def getCCAddrList(self
):
3601 if self
._ccAddrList
:
3603 except AttributeError:
3604 self
._ccAddrList
=PersistentList()
3605 return self
._ccAddrList
3607 def addCCAddr(self
,newAddr
):
3609 if self
._ccAddrList
:
3611 except AttributeError:
3612 self
._ccAddrList
=PersistentList()
3613 ccAddr
=newAddr
.strip()
3614 if ccAddr
!="" and ccAddr
not in self
._ccAddrList
:
3615 self
._ccAddrList
.append(ccAddr
)
3617 def setCCAddrList(self
,l
):
3618 self
.clearCCAddrList()
3620 self
.addCCAddr(addr
)
3622 def setCAasCCAddr(self
, CAasCCAddr
):
3623 self
._CAasCCAddr
= CAasCCAddr
3625 def getCAasCCAddr(self
):
3627 if self
._CAasCCAddr
:
3629 except AttributeError:
3630 self
._CAasCCAddr
= False
3631 return self
._CAasCCAddr
3633 def clearCCAddrList(self
):
3634 self
._ccAddrList
=PersistentList()
3636 def getFromAddr(self
):
3638 return self
._fromAddr
3639 except AttributeError:
3640 self
._fromAddr
= self
._owner
.getConference().getSupportInfo().getEmail()
3641 return self
._fromAddr
3643 def setFromAddr(self
, addr
):
3644 self
._fromAddr
= addr
3646 def addToAddr(self
,toAddr
):
3649 if self
.hasToAddr(toAddr
.__class
__):
3652 if self
._toAddrGenerator
:
3654 except AttributeError, e
:
3655 self
._toAddrGenerator
= Counter()
3658 id = int(self
._toAddrGenerator
.newCount())
3659 toAddr
.includeInTpl(self
,id)
3660 self
.getToAddrList().append(toAddr
)
3662 def removeToAddr(self
,toAddr
):
3665 if not self
.hasToAddr(toAddr
.__class
__):
3667 self
.getToAddrList().remove(toAddr
)
3668 toAddr
.includeInTpl(None,toAddr
.getId())
3671 def recoverToAddr(self
, toAddr
):
3672 self
.addToAddr(toAddr
)
3675 def getToAddrs(self
, abs):
3677 for toAddr
in self
.getToAddrList():
3678 users
+= toAddr
.getToAddrList(abs)
3681 def getToAddrList(self
):
3687 except AttributeError, e
:
3688 self
._toAddrs
= PersistentList()
3689 return self
._toAddrs
3691 def getToAddrById(self
,id):
3694 for toAddr
in self
.getToAddrList():
3695 if toAddr
.getId()==int(id):
3699 def hasToAddr(self
,toAddrKlass
):
3700 """Returns True if the TPL contains a "toAddr" which class is "toAddrKlass"
3702 for toAddr
in self
.getToAddrList():
3703 if toAddr
.__class
__ == toAddrKlass
:
3707 def clearToAddrs(self
):
3708 while(len(self
.getToAddrList())>0):
3709 self
.removeToAddr(self
.getToAddrList()[0])
3711 def addCondition(self
,cond
):
3714 if cond
in self
._conditions
:
3718 id = int(self
._condGenerator
.newCount())
3719 cond
.includeInTpl(self
, id)
3720 self
._conditions
.append(cond
)
3722 def removeCondition(self
,cond
):
3725 if cond
not in self
._conditions
:
3727 self
._conditions
.remove(cond
)
3730 def recoverCondition(self
, cond
):
3731 self
.addCondition(cond
)
3734 def getConditionList(self
):
3737 return self
._conditions
3739 def getConditionById(self
,id):
3742 for cond
in self
._conditions
:
3743 if cond
.getId()==int(id):
3747 def clearConditionList(self
):
3748 while(len(self
.getConditionList())>0):
3749 self
.removeCondition(self
.getConditionList()[0])
3751 def satisfies(self
,abs):
3754 for cond
in self
._conditions
:
3755 if cond
.satisfies(abs):
3759 def parseTplContent(self
, content
, varList
):
3760 # replace the % in order to avoid exceptions
3761 result
= content
.replace("%", "%%")
3762 # find the vars and make the expressions, it is necessary to do in reverse in order to find the longest tags first
3764 result
= result
.replace("{"+var
.getName()+"}", "%("+var
.getName()+")s")
3767 def parseTplContentUndo(self
, content
, varList
):
3768 # The body content is shown without "%()" and with "%" in instead of "%%" but it is not modified
3771 result
= result
.replace("%("+var
.getName()+")s", "{"+var
.getName()+"}")
3772 # replace the %% by %
3773 result
= result
.replace("%%", "%")
3776 def getModifKey( self
):
3777 return self
.getConference().getModifKey()
3781 class NotifTplToAddr(Persistent
):
3790 ntta
= NotifTplToAddr()
3794 TrashCanManager().add(self
)
3797 TrashCanManager().remove(self
)
3799 def includeInTpl(self
,newTpl
,newId
):
3809 def getToAddrList(self
,absList
):
3811 Return a list with all the emails for a group.
3816 class NotifTplToAddrSubmitter(NotifTplToAddr
):
3818 def getToAddrList(self
,abs):
3820 l
.append(abs.getSubmitter())
3824 nttas
= NotifTplToAddrSubmitter()
3827 class NotifTplToAddrPrimaryAuthors(NotifTplToAddr
):
3829 def getToAddrList(self
,abs):
3831 for pa
in abs.getPrimaryAuthorList():
3836 nttapa
= NotifTplToAddrPrimaryAuthors()
3839 class NotifTplCondition(Persistent
):
3847 def clone(self
, template
):
3848 con
= NotifyCondition()
3849 con
.includeInTpl(template
)
3853 TrashCanManager().add(self
)
3856 TrashCanManager().remove(self
)
3858 def includeInTpl(self
,newTpl
,newId
):
3868 def satisfies(self
,abs):
3872 class NotifTplCondAccepted(NotifTplCondition
):
3874 def __init__(self
,track
="--any--",contribType
="--any--"):
3875 NotifTplCondition
.__init
__(self
)
3877 self
._contribType
=contribType
3879 def clone(self
, conference
, template
):
3880 ntca
= NotifTplCondAccepted()
3881 for newtrack
in conference
.getTrackList() :
3882 if newtrack
.getTitle() == self
.getTrack().getTitle() :
3883 ntca
.setTrack(newtrack
)
3884 for newtype
in conference
.getContribTypeList() :
3885 if newtype
.getName() == self
.getContribType() :
3886 ntca
.setContribType(newtype
)
3890 def setContribType(self
, ct
="--any--"):
3891 self
._contribType
= ct
3893 def getContribType(self
):
3894 return self
._contribType
3896 def setTrack(self
, tr
="--any--"):
3903 except AttributeError:
3904 self
._track
="--any--"
3907 def _satifiesContribType(self
,abs):
3908 status
=abs.getCurrentStatus()
3909 if self
._contribType
=="--any--":
3912 if self
._contribType
=="" or self
._contribType
==None or \
3913 self
._contribType
=="--none--":
3914 return status
.getType()=="" or status
.getType()==None
3915 return status
.getType()==self
._contribType
3918 def _satifiesTrack(self
,abs):
3919 status
=abs.getCurrentStatus()
3920 if self
.getTrack()=="--any--":
3923 if self
.getTrack()=="" or self
.getTrack() is None or \
3924 self
.getTrack()=="--none--":
3925 return status
.getTrack()=="" or status
.getTrack()==None
3926 return status
.getTrack()==self
.getTrack()
3929 def satisfies(self
,abs):
3930 if not isinstance(abs.getCurrentStatus(),AbstractStatusAccepted
):
3933 return self
._satifiesContribType
(abs) and self
._satifiesTrack
(abs)
3936 class NotifTplCondRejected(NotifTplCondition
):
3938 def satisfies(self
,abs):
3939 return isinstance(abs.getCurrentStatus(),AbstractStatusRejected
)
3941 def clone(self
, conference
, template
):
3942 ntcr
= NotifTplCondRejected()
3943 ntcr
.includeInTpl(template
)
3946 class NotifTplCondMerged(NotifTplCondition
):
3948 def satisfies(self
,abs):
3949 return isinstance(abs.getCurrentStatus(),AbstractStatusMerged
)
3951 def clone(self
, conference
, template
):
3952 ntcm
= NotifTplCondMerged()
3953 ntcm
.includeInTpl(newTpl
, newId
)
3955 class NotificationLog(Persistent
):
3957 def __init__(self
,abstract
):
3958 self
._abstract
=abstract
3959 self
._entries
=PersistentList()
3961 def getAbstract(self
):
3962 return self
._abstract
3964 def addEntry(self
,newEntry
):
3965 if newEntry
!=None and newEntry
not in self
._entries
:
3966 self
._entries
.append(newEntry
)
3968 def getEntryList(self
):
3969 return self
._entries
3971 # The 3 following metods are used only for recovery purposes:
3973 def removeEntry(self
, entry
):
3974 if entry
!=None and entry
in self
._entries
:
3975 self
._entries
.remove(entry
)
3978 def recoverEntry(self
, entry
):
3979 self
.addEntry(entry
)
3982 def clearEntryList(self
):
3983 while len(self
.getEntryList()) > 0:
3984 self
.removeEntry(self
.getEntryList()[0])
3986 # -----------------------------------------------------------
3988 class NotifLogEntry(Persistent
):
3990 def __init__(self
,responsible
,tpl
):
3991 self
._setDate
(nowutc())
3992 self
._setResponsible
(responsible
)
3995 def _setDate(self
,newDate
):
4001 def _setResponsible(self
,newResp
):
4002 self
._responsible
=newResp
4004 def getResponsible(self
):
4005 return self
._responsible
4007 def _setTpl(self
,newTpl
):
4014 TrashCanManager().add(self
)
4017 TrashCanManager().remove(self
)