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