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