source: indico/indico/MaKaC/review.py @ c7e1c5

hello-world-walkthroughipv6v0.98-seriesv0.98.2v0.98.3v0.98b2v0.99v1.0v1.1
Last change on this file since c7e1c5 was c7e1c5, checked in by Pedro Ferreira <jose.pedro.ferreira@…>, 17 months ago

[FIX] Fix review status

  • Take into account the reallocation.
  • Refactoring of the review lists.
  • Property mode set to 100644
File size: 121.5 KB
Line 
1# -*- coding: utf-8 -*-
2##
3##
4## This file is part of CDS Indico.
5## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 CERN.
6##
7## CDS Indico is free software; you can redistribute it and/or
8## modify it under the terms of the GNU General Public License as
9## published by the Free Software Foundation; either version 2 of the
10## License, or (at your option) any later version.
11##
12## CDS Indico is distributed in the hope that it will be useful, but
13## WITHOUT ANY WARRANTY; without even the implied warranty of
14## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15## General Public License for more details.
16##
17## You should have received a copy of the GNU General Public License
18## along with CDS Indico; if not, write to the Free Software Foundation, Inc.,
19## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20
21from copy import copy
22from pytz import timezone
23from indico.util.i18n import i18nformat
24import ZODB
25from persistent import Persistent
26from persistent.list import PersistentList
27from BTrees.OOBTree import OOBTree, intersection, union
28from BTrees.IOBTree import IOBTree
29import BTrees.OIBTree as OIBTree
30from datetime import datetime,timedelta
31import MaKaC
32from MaKaC.common.Counter import Counter
33from MaKaC.errors import MaKaCError
34from MaKaC.accessControl import AdminList
35from MaKaC.trashCan import TrashCanManager
36from MaKaC.common.timezoneUtils import nowutc
37from MaKaC.i18n import _
38from MaKaC.common import Config
39import tempfile
40
41
42class AbstractSorter:
43    pass
44
45class AbstractFilter:
46    pass
47
48
49class _AbstractParticipationIndex(Persistent):
50    """This class allows to index abstract participations (submitters)
51        for a single CFA process; this means that clients will be able to
52        efficiently perform queries of the type "give me all the abstracts
53        in which a certain registered user is implied".
54       For being able to perform this indexing, it is supposed that the Avatar
55        identifier is unique among other avatars and that it cannot change.
56       This index must be maintained by clients (i.e. the CFAMgr) as it doesn't
57        keep track of the changes on Participantons.
58       The key of the index is the Avatar and the values the different
59        Participations that user has within the current CFA process. For
60        performance reasons, the Avatar id will be used as index key (using the
61        whole Avatar object would make the index bigger and as the Avatar id
62        cannot change it's enough); the clients would have to keep the
63        integrity of the index.
64    """
65
66    def __init__( self ):
67        self._idx = OOBTree()
68
69    def index( self, participation ):
70        """Add a new participation to the index
71        """
72        #if the Participation is not linked to an Avatar there's no point to
73        #   index it
74        a = participation.getAvatar()
75        if not a:
76            return
77        #ToDo: if the Participation corresponds to an abstract which doesn't
78        #   correspond to the current CFAMgr, then an error must be raised
79
80        if not self._idx.has_key( a.getId() ):
81            self._idx[a.getId()] = PersistentList()
82        #if the participation is already in the index, no need for adding it
83        if participation in self._idx[a.getId()]:
84            return
85        self._idx[a.getId()].append( participation )
86
87    def unindex( self, participation ):
88        """Remove an existing participation from the index
89        """
90        #if the Participation is not linked to an Avatar there's no point to
91        #   unindex it
92        a = participation.getAvatar()
93
94        if not a:
95            return
96        #if the Avatar associated to the participation isn't in the index do
97        #   nothing
98        if not self._idx.has_key( a.getId() ):
99            return
100        #if the given participation is indexed remove it, otherwise do nothing
101        if participation in self._idx[a.getId()]:
102             self._idx[a.getId()].remove( participation )
103
104    def getParticipationList( self, av ):
105        try:
106            return self._idx[ av.getId() ]
107        except KeyError, e:
108            return []
109
110
111class AbstractParticipation(Persistent):
112
113    def __init__( self, abstract, **data ):
114        self._abstract = abstract
115        self._firstName = ""
116        self._surName = ""
117        self._email = ""
118        self._affilliation = ""
119        self._address = ""
120        self._telephone = ""
121        self._fax = ""
122        self._title = ""
123        self.setData( **data )
124
125    def setFromAvatar( self, av ):
126        data = {"title": av.getTitle(), \
127                "firstName": av.getName(), \
128                "surName": av.getSurName(), \
129                "email": av.getEmail(), \
130                "affiliation": av.getOrganisation(), \
131                "address": av.getAddress(), \
132                "telephone": av.getTelephone(), \
133                "fax": av.getFax() }
134        self.setData( **data )
135
136    def setFromAbstractParticipation(self,part):
137        data = {"title": part.getTitle(), \
138                "firstName": part.getFirstName(), \
139                "surName": part.getSurName(), \
140                "email": part.getEmail(), \
141                "affiliation": part.getAffiliation(), \
142                "address": part.getAddress(), \
143                "telephone": part.getTelephone(), \
144                "fax": part.getFax() }
145        self.setData( **data )
146
147    def setData( self, **data ):
148        if "firstName" in data:
149            self.setFirstName( data["firstName"] )
150        if "surName" in data:
151            self.setSurName( data["surName"] )
152        if "email" in data:
153            self.setEmail( data["email"] )
154        if "affiliation" in data:
155            self.setAffiliation( data["affiliation"] )
156        if "address" in data:
157            self.setAddress( data["address"] )
158        if "telephone" in data:
159            self.setTelephone( data["telephone"] )
160        if "fax" in data:
161            self.setFax( data["fax"] )
162        if "title" in data:
163            self.setTitle( data["title"] )
164    setValues = setData
165
166    def getData(self):
167        data = {}
168        data["firstName"] = self.getFirstName()
169        data["surName"] = self.getSurName()
170        data["email"] = self.getEmail()
171        data["affiliation"] = self.getAffiliation()
172        data["address"] = self.getAddress()
173        data["telephone"] = self.getTelephone()
174        data["fax"] = self.getFax()
175        data["title"] = self.getTitle()
176
177        return data
178    getValues = getData
179
180    def clone (self, abstract):
181        ap = AbstractParticipation(abstract,self.getData())
182        return ap
183
184    def _notifyModification(self):
185        self._abstract._notifyModification()
186
187    def _unindex(self):
188        abs=self.getAbstract()
189        if abs is not None:
190            mgr=abs.getOwner()
191            if mgr is not None:
192                mgr.unindexAuthor(self)
193
194    def _index(self):
195        abs=self.getAbstract()
196        if abs is not None:
197            mgr=abs.getOwner()
198            if mgr is not None:
199                mgr.indexAuthor(self)
200
201    def setFirstName( self, name ):
202        tmp=name.strip()
203        if tmp==self.getFirstName():
204            return
205        self._unindex()
206        self._firstName=tmp
207        self._index()
208        self._notifyModification()
209
210    def getFirstName( self ):
211        return self._firstName
212
213    def getName( self ):
214        return self._firstName
215
216    def setSurName( self, name ):
217        tmp=name.strip()
218        if tmp==self.getSurName():
219            return
220        self._unindex()
221        self._surName=tmp
222        self._index()
223        self._notifyModification()
224
225    def getSurName( self ):
226        return self._surName
227
228    def getFamilyName( self ):
229        return self._surName
230
231    def setEmail( self, email ):
232        email = email.strip().lower()
233        if email != self.getEmail():
234            self._unindex()
235            self._email = email
236            self._index()
237            self._notifyModification()
238
239    def getEmail( self ):
240        return self._email
241
242    def setAffiliation( self, af ):
243        self._affilliation = af.strip()
244        self._notifyModification()
245
246    setAffilliation=setAffiliation
247
248    def getAffiliation( self ):
249        return self._affilliation
250
251    def setAddress( self, address ):
252        self._address = address.strip()
253        self._notifyModification()
254
255    def getAddress( self ):
256        return self._address
257
258    def setTelephone( self, telf ):
259        self._telephone = telf.strip()
260        self._notifyModification()
261
262    def getTelephone( self ):
263        return self._telephone
264
265    def setFax( self, fax ):
266        self._fax = fax.strip()
267        self._notifyModification()
268
269    def getFax(self):
270        return self._fax
271
272    def setTitle( self, title ):
273        self._title = title.strip()
274        self._notifyModification()
275
276    def getTitle( self ):
277        return self._title
278
279    def getFullName( self ):
280        res = self.getSurName().decode('utf-8').upper().encode('utf-8')
281        tmp=[]
282        for name in self.getFirstName().lower().split(" "):
283            if name.strip() == "":
284                continue
285            name=name.strip()
286            tmp.append("%s%s"%(name[0].upper(),name[1:]))
287        firstName=" ".join(tmp)
288        if firstName != "":
289            res = "%s, %s"%( res, firstName )
290        if self.getTitle() != "":
291            res = "%s %s"%( self.getTitle(), res )
292        return res
293
294    def getStraightFullName(self):
295        name = ""
296        if self.getName() != "":
297            name = "%s "%self.getName()
298        return "%s%s"%(name, self.getSurName())
299
300    def getAbrName(self):
301        res = self.getSurName()
302        if self.getFirstName() != "":
303            if res != "":
304                res = "%s, "%res
305            res = "%s%s."%(res, self.getFirstName()[0].upper())
306        return res
307
308    def getAbstract( self ):
309        return self._abstract
310
311    def setAbstract(self, abs):
312        self._abstract = abs
313
314    def delete( self ):
315        self._abstract = None
316        TrashCanManager().add(self)
317
318    def recover(self):
319        TrashCanManager().remove(self)
320
321
322class Author( AbstractParticipation ):
323
324    def __init__( self, abstract, **data ):
325        AbstractParticipation.__init__( self, abstract, **data )
326        self._abstractId = ""
327
328    def getId( self ):
329        return self._id
330
331    def setId( self, newId ):
332        self._id = str( newId )
333
334    def clone(self, abstract):
335        auth = Author(abstract, self.getData())
336        return auth
337
338    def isSpeaker(self):
339        return self._abstract.isSpeaker(self)
340
341class Submitter( AbstractParticipation ):
342
343    def __init__( self, abstract, av ):
344        if av == None:
345            raise MaKaCError( _("abstract submitter cannot be None") )
346        AbstractParticipation.__init__( self, abstract )
347        self._user = None
348        self._setUser( av )
349        self.setFromAvatar( av )
350
351    def _setUser( self, av ):
352        if self.getUser() == av:
353            return
354        #if currently there's an association with a registered user, we notify
355        #   the unidexation of the participation
356        if self.getUser():
357            self.getAbstract().getOwner().unregisterParticipation( self )
358        self._user = av
359        #if the participation is associated to any avatar, we make the
360        #   association and index it
361        if self.getUser():
362            self.getAbstract().getOwner().registerParticipation( self )
363
364    def clone(self, abstract):
365        sub = Submitter(abstract,self.getAvatar())
366        sub.setData(self.getData())
367        return sub
368
369    def getUser( self ):
370        return self._user
371
372    def getAvatar( self ):
373        return self._user
374
375    def representsUser( self, av ):
376        return self.getUser() == av
377
378
379class _AuthIdx(Persistent):
380
381    def __init__(self,mgr):
382        self._mgr=mgr
383        self._idx=OOBTree()
384
385    def _getKey(self,auth):
386        return "%s %s"%(auth.getSurName().lower(),auth.getFirstName().lower())
387
388    def index(self,auth):
389        if auth.getAbstract() is None:
390            raise MaKaCError( _("cannot index an author of an abstract which is not included in a conference"))
391        if auth.getAbstract().getOwner()!=self._mgr:
392            raise MaKaCError( _("cannot index an author of an abstract which does not belong to this conference"))
393        key=self._getKey(auth)
394        abstractId=str(auth.getAbstract().getId())
395        if not self._idx.has_key(key):
396            self._idx[key]=OIBTree.OIBTree()
397        if not self._idx[key].has_key(abstractId):
398            self._idx[key][abstractId]=0
399        self._idx[key][abstractId]+=1
400
401    def unindex(self,auth):
402        if auth.getAbstract() is None:
403            raise MaKaCError( _("cannot unindex an author of an abstract which is not included in a conference"))
404        if auth.getAbstract().getOwner()!=self._mgr:
405            raise MaKaCError( _("cannot unindex an author of an abstract which does not belong to this conference"))
406        key=self._getKey(auth)
407        if not self._idx.has_key(key):
408            return
409        abstractId=str(auth.getAbstract().getId())
410        if abstractId not in self._idx[key]:
411            return
412        self._idx[key][abstractId]-=1
413        if self._idx[key][abstractId]<=0:
414            del self._idx[key][abstractId]
415        if len(self._idx[key])<=0:
416            del self._idx[key]
417
418    def match(self,query):
419        query=query.lower().strip()
420        res=OIBTree.OISet()
421        for k in self._idx.keys():
422            if k.find(query)!=-1:
423                res=OIBTree.union(res,self._idx[k])
424        return res
425
426
427class _PrimAuthIdx(_AuthIdx):
428
429    def __init__(self,mgr):
430        _AuthIdx.__init__(self,mgr)
431        for abs in self._mgr.getAbstractList():
432            for auth in abs.getPrimaryAuthorList():
433                self.index(auth)
434
435class _AuthEmailIdx(_AuthIdx):
436
437    def __init__(self, mgr):
438        _AuthIdx.__init__(self, mgr)
439        for abs in self._mgr.getAbstractList():
440            for auth in abs.getPrimaryAuthorList():
441                self.index(auth)
442            for auth in abs.getCoAuthorList():
443                self.index(auth)
444
445    def _getKey(self, auth):
446        return auth.getEmail().lower()
447
448class AbstractField(Persistent):
449    _fieldTypes = [ 'input', 'textarea' ]
450
451    def __init__(self, id, name, caption, maxlength=0, isMandatory=False, type="textarea", limitation="chars"):
452        self._id = id
453        self._name = name
454        self._caption = caption
455        self._active = True
456        self._maxLength = maxlength
457        self._isMandatory = isMandatory
458        self._type = type
459        self._limitation = limitation # possible values: chars, words
460
461    def clone(self):
462        af = AbstractField(self.getId(),self.getName(),self.getCaption(),self.getMaxLength(), self.isMandatory(), self.getType(), self.getLimitation())
463        return af
464
465    def _notifyModification(self):
466        self._p_changed = 1
467
468    def getType(self):
469        try:
470            return self._type
471        except:
472            self._type = "textarea"
473            return self._type
474
475    def setType(self, type):
476        self._type = type
477
478    def getLimitation(self):
479        try:
480            return self._limitation
481        except:
482            self._limitation = "chars"
483            return self._limitation
484
485    def setLimitation(self, limitation):
486        self._limitation = limitation
487
488    def isMandatory(self):
489        try:
490            return self._isMandatory
491        except:
492            self._isMandatory = False
493            return self._isMandatory
494
495    def setMandatory(self, isMandatory=False):
496        self._isMandatory = isMandatory
497        self._notifyModification()
498
499    def getMaxLength(self):
500        try:
501            return self._maxLength
502        except:
503            self._maxLength=0
504            return self._maxLength
505
506    def setValues(self, name, caption, maxlength, isMandatory, fieldType, fieldLimitation):
507        self._name = name
508        self._caption = caption
509        self._maxLength = maxlength
510        self._isMandatory = isMandatory
511        self._type = fieldType
512        self._limitation = fieldLimitation
513        self._notifyModification()
514
515    def setMaxLength(self, maxLength=0):
516        self._maxLength = maxLength
517        self._notifyModification()
518
519    def getId(self):
520        return self._id
521
522    def setId(self, id):
523        self._id=id
524        self._notifyModification()
525
526    def getName(self):
527        try:
528            if self._name:
529                pass
530        except AttributeError, e:
531            try:
532                if int(self._id):
533                    pass
534                self._name = ""
535            except ValueError, er:
536                self._name = self._id
537        return self._name
538
539    def setName(self, name):
540        self._name=name
541        self._notifyModification()
542
543    def getCaption(self):
544        return self._caption
545
546    def setCaption(self, caption):
547        self._caption=caption
548        self._notifyModification()
549
550    def isActive(self):
551        return self._active
552
553    def setActive(self, active):
554        self._active=active
555        self._notifyModification()
556
557class AbstractFieldsMgr(Persistent):
558
559    def __init__(self):
560        self._fields=self._initFields()
561        self.__fieldGenerator = Counter()
562
563    def clone(self):
564        afm = AbstractFieldsMgr()
565        for f in self.getFields():
566            afm._addField(f.clone())
567        return afm
568
569    def getFieldGenerator(self):
570        try:
571            if self.__fieldGenerator:
572                pass
573        except AttributeError,e:
574            self.__fieldGenerator = Counter()
575        return self.__fieldGenerator
576
577    def _notifyModification(self):
578        self._p_changed = 1
579
580    def _initFields(self):
581        d=[]
582        su=AbstractField("content", "Content", "Abstract content", 0, True)
583        d.append(su)
584        su=AbstractField("summary", "Summary", "Summary",0)
585        d.append(su)
586        return d
587
588    def hasField(self, id):
589        for f in self._fields:
590            if f.getId() == id:
591                return True
592        return False
593
594    def getFields(self):
595        if not self.hasField("content"):
596            ac=AbstractField("content","Content", _("Abstract content"),0,True)
597            self._fields.insert(0, ac)
598        return self._fields
599
600    def getActiveFields(self):
601        fl = []
602        for f in self.getFields():
603            if f.isActive():
604                fl.append(f)
605        return fl
606
607    def hasActiveField(self, id):
608        return self.hasField(id) and self.getFieldById(id).isActive()
609
610    def hasAnyActiveField( self ):
611        for f in self._fields:
612            if f.isActive():
613                return True
614        return False
615
616    def enableField(self, id):
617        if self.hasField(id):
618            self.getFieldById(id).setActive(True)
619            self._notifyModification()
620
621    def disableField(self, id):
622        if self.hasField(id):
623            self.getFieldById(id).setActive(False)
624            self._notifyModification()
625
626    def getFieldKeys(self):
627        keys = []
628        for f in self._fields:
629            keys.append(f.getId())
630        return keys
631
632    def getFieldById(self, id):
633        for f in self._fields:
634            if f.getId() == id:
635                return f
636        return None
637
638    def _addField(self, field):
639        self._fields.append(field)
640
641    def addField(self, id, name, caption, maxlength=0, isMandatory=False, fieldType="textarea", fieldLimitation="chars"):
642        if self.hasField(id):
643            self.getFieldById(id).setValues(name, caption, maxlength, isMandatory, fieldType, fieldLimitation)
644        else:
645            id=str(self.getFieldGenerator().newCount())
646            absf = AbstractField(id, name, caption, maxlength, isMandatory, fieldType, fieldLimitation)
647            self._fields.append(absf)
648        self._notifyModification()
649        return id
650
651    def removeField(self, id):
652        if self.hasField(id):
653            self._fields.remove(self.getFieldById(id))
654            self._notifyModification()
655
656    def moveAbsFieldUp(self, id):
657        if self.hasField(id):
658            f = self.getFieldById(id)
659            idx = self._fields.index(f)
660            self._fields.remove(f)
661            if idx == 0:
662                self._fields.append(f)
663            else:
664                self._fields.insert(idx-1,f)
665            self._notifyModification()
666
667    def moveAbsFieldDown(self, id):
668        if self.hasField(id):
669            f = self.getFieldById(id)
670            idx = self._fields.index(f)
671            self._fields.remove(f)
672            if idx == len(self._fields):
673                self._fields.insert(0,f)
674            else:
675                self._fields.insert(idx+1,f)
676            self._notifyModification()
677
678class AbstractMgr(Persistent):
679
680    def __init__(self, owner):
681        self._owner = owner
682        self._abstracts = OOBTree()
683        self._participationIdx = _AbstractParticipationIndex()
684        self.__abstractGenerator = Counter()
685        self._activated = False
686        self.setStartSubmissionDate( datetime.now() )
687        self.setEndSubmissionDate( datetime.now() )
688##        self._contribTypes = PersistentList()
689        self.setAnnouncement( "" )
690        self._notifTpls=IOBTree()
691        self._notifTplsOrder=PersistentList()
692        self.__notifTplsCounter=Counter()
693        self._authorizedSubmitter = PersistentList()
694        self._primAuthIdx =_PrimAuthIdx(self)
695        self._authEmailIdx =_AuthEmailIdx(self)
696        self._abstractFieldsMgr=AbstractFieldsMgr()
697        self._submissionNotification=SubmissionNotification()
698        self._multipleTracks = True
699        self._tracksMandatory = False
700        self._attachFiles = False
701
702    def getMultipleTracks( self ):
703        try:
704            return self._multipleTracks
705        except:
706            self.setMultipleTracks( True )
707            return self._multipleTracks
708
709    def setMultipleTracks( self, multipleTracks = True ):
710        self._multipleTracks = multipleTracks
711
712    def areTracksMandatory( self ):
713        try:
714            return self._tracksMandatory
715        except:
716            self.setTracksMandatory( False )
717            return self._tracksMandatory
718
719    def canAttachFiles(self):
720        try:
721            return self._attachFiles
722        except:
723            self.setAllowAttachFiles(False)
724            return self._attachFiles
725
726    def setAllowAttachFiles(self, attachedFiles):
727        self._attachFiles = attachedFiles
728
729    def setTracksMandatory( self, tracksMandatory = False ):
730        self._tracksMandatory = tracksMandatory
731
732    def getAbstractFieldsMgr(self):
733        try:
734            return self._abstractFieldsMgr
735        except:
736            self._abstractFieldsMgr=AbstractFieldsMgr()
737            return self._abstractFieldsMgr
738
739    def clone(self, conference):
740        amgr = AbstractMgr(conference)
741        amgr._abstractFieldsMgr = self.getAbstractFieldsMgr().clone()
742        amgr.setAnnouncement(self.getAnnouncement())
743
744        timeDifference = conference.getStartDate() - self.getOwner().getStartDate()
745        amgr.setStartSubmissionDate(self.getStartSubmissionDate() + timeDifference)
746        amgr.setEndSubmissionDate(self.getEndSubmissionDate() + timeDifference)
747
748        modifDeadline = self.getModificationDeadline()
749        if modifDeadline is not None :
750            amgr.setModificationDeadline(self.getModificationDeadline() + timeDifference)
751
752        amgr.setActive(self.isActive())
753        if self.getCFAStatus() :
754            amgr.activeCFA()
755        else :
756            amgr.desactiveCFA()
757
758        for a in self.getAbstractList() :
759            amgr.addAbstract(a.clone(conference, amgr._generateNewAbstractId()))
760
761        for tpl in self.getNotificationTplList() :
762            amgr.addNotificationTpl(tpl.clone())
763
764        # Cloning submission notification:
765        amgr.setSubmissionNotification(self.getSubmissionNotification().clone())
766
767        return amgr
768
769    def getOwner(self):
770        return self._owner
771    getConference = getOwner
772
773    def getTimezone(self):
774        return self.getConference().getTimezone()
775
776    def activeCFA(self):
777        self._activated = True
778
779    def desactiveCFA(self):
780        self._activated = False
781
782    def getAuthorizedSubmitterList(self):
783        try:
784            return self._authorizedSubmitter
785        except AttributeError:
786            self._authorizedSubmitter = PersistentList()
787            return self._authorizedSubmitter
788
789    def addAuthorizedSubmitter(self, av):
790        try:
791            if self._authorizedSubmitter:
792                pass
793        except AttributeError:
794            self._authorizedSubmitter = PersistentList()
795        if not av in self._authorizedSubmitter:
796            self._authorizedSubmitter.append(av)
797            av.linkTo(self, "abstractSubmitter")
798
799    def removeAuthorizedSubmitter(self, av):
800        try:
801            if self._authorizedSubmitter:
802                pass
803        except:
804            self._authorizedSubmitter = PersistentList()
805        if av in self._authorizedSubmitter:
806            self._authorizedSubmitter.remove(av)
807            av.unlinkTo(self, "abstractSubmitter")
808
809    def getCFAStatus(self):
810        return self._activated
811
812    def setActive(self, value):
813        if value:
814            self.activeCFA()
815        else:
816            self.desactiveCFA()
817
818    def isActive(self):
819        return self._activated
820
821    def setStartSubmissionDate(self, date):
822        self._submissionStartDate = datetime(date.year,date.month,date.day,0,0,0)
823
824    def getStartSubmissionDate(self):
825        return timezone(self.getTimezone()).localize(self._submissionStartDate)
826
827    def setEndSubmissionDate(self, date):
828        self._submissionEndDate = datetime(date.year,date.month,date.day,23,59,59)
829
830    def getEndSubmissionDate(self):
831        return timezone(self.getTimezone()).localize(self._submissionEndDate)
832
833    def inSubmissionPeriod( self, date = None ):
834        if date is None:
835            date=nowutc()
836        sd = self.getStartSubmissionDate()
837        ed = self.getEndSubmissionDate()
838        return date <= ed and date >= sd
839
840    def getModificationDeadline( self ):
841        """Returns the deadline for modifications on the submitted abstracts.
842        """
843        try:
844            if self._modifDeadline:
845                pass
846        except AttributeError, e:
847            self._modifDeadline = None
848        if self._modifDeadline != None:
849            return timezone(self.getTimezone()).localize(self._modifDeadline)
850        else:
851            return None
852
853    def setModificationDeadline( self, newDL ):
854        """Sets a new deadline for modifications on the submitted abstracts.
855        """
856        if newDL != None:
857            self._modifDeadline = datetime( newDL.year,newDL.month,newDL.day,23,59,59)
858        else:
859            self._modifDeadline = newDL
860
861    def inModificationPeriod( self, date=None ):
862        """Tells whether is possible to modify a submitted abstract in a
863            certain date.
864        """
865        if date is None:
866            date=nowutc()
867        if not self.getModificationDeadline():
868            return True
869        return date <= self.getModificationDeadline()
870
871    def getAnnouncement( self ):
872        #to be removed
873        try:
874            if self._announcement:
875                pass
876        except AttributeError, e:
877            self._announcement = ""
878
879        return self._announcement
880
881    def setAnnouncement( self, newAnnouncement ):
882        self._announcement = newAnnouncement.strip()
883
884##    def addContribType(self, type):
885##        type = type.strip()
886##        if type == "":
887##            raise MaKaCError("Cannot add an empty contribution type")
888##        self._contribTypes.append(type)
889##
890##    def removeContribType(self, type):
891##        if type in self._contribTypes:
892##            self._contribTypes.remove(type)
893##
894##    def getContribTypeList(self):
895##        return self._contribTypes
896
897    def _generateNewAbstractId( self ):
898        """Returns a new unique identifier for the current conference
899            contributions
900        """
901        #instead of having a own counter, the abstract manager will request
902        #   abstract ids to the conference which will ensure a unique id
903        #   which will persist afterwards when an abstract is accepted
904        return str(self.getConference().genNewAbstractId())
905
906    def _getOldAbstractCounter(self):
907        return self.__abstractGenerator._getCount()
908
909    def newAbstract( self, av, **data ):
910        """Creates a new abstract under this manager
911        """
912        id = self._generateNewAbstractId()
913        a = Abstract( self, id, av, **data )
914        self._abstracts[ id ] = a
915        for auth in a.getPrimaryAuthorList():
916            self.indexAuthor(auth)
917        return a
918
919    def addAbstract(self, abstract):
920        if abstract in self.getAbstractList():
921            return
922        if isinstance(abstract.getCurrentStatus(),AbstractStatusWithdrawn):
923            raise MaKaCError(  _("Cannot add an abstract which has been withdrawn"), ("Event"))
924        abstract._setOwner(self)
925        self._abstracts[abstract.getId()] = abstract
926        for auth in abstract.getPrimaryAuthorList():
927            self.indexAuthor(auth)
928
929    def removeAbstract( self, abstract ):
930        if self._abstracts.has_key( abstract.getId() ):
931            #for auth in abstract.getPrimaryAuthorList():
932            #    self.unindexAuthor(auth)
933            # * Remove dependencies with another abstracts:
934            #       - If it's an accepted abstract-->remove abstract from contribution
935            if isinstance(abstract.getCurrentStatus(), AbstractStatusAccepted):
936                raise MaKaCError( _("Cannot remove an accepted abstract before removing the contribution linked to it"))
937            # If it's a withdrawn abstract-->remove abstract from contribution
938            if isinstance(abstract.getCurrentStatus(), AbstractStatusWithdrawn) and abstract.getContribution():
939                raise MaKaCError( _("Cannot remove the abstract before removing the contribution linked to it"))
940            for abs in self._abstracts.values():
941                if abs != abstract:
942                    st = abs.getCurrentStatus()
943                    if isinstance(st, AbstractStatusDuplicated):
944                        #if the abstract to delete is the orginal in another "duplicated", change status to submitted
945                        if st.getOriginal() == abstract:
946                            abs.setCurrentStatus(AbstractStatusSubmitted(abs))
947                    elif isinstance(st, AbstractStatusMerged):
948                        #if the abstract to delete is the target one in another "merged", change status to submitted
949                        if st.getTargetAbstract() == abstract:
950                            abs.setCurrentStatus(AbstractStatusSubmitted(abs))
951            #unindex participations!!!
952            self.unregisterParticipation(abstract.getSubmitter())
953            del self._abstracts[ abstract.getId() ]
954            abstract.delete()
955
956    def recoverAbstract(self, abstract):
957        self.addAbstract(abstract)
958        abstract.recoverFromTrashCan()
959    def getAbstractList(self):
960        return self._abstracts.values()
961
962    def getAbstractById(self, id):
963        return self._abstracts.get(str(id),None)
964
965    def registerParticipation( self, p ):
966        self._participationIdx.index( p )
967
968    def unregisterParticipation( self, p ):
969        self._participationIdx.unindex( p )
970
971    def getAbstractListForAvatar( self, av ):
972        try:
973            if self._participationIdx:
974                pass
975        except AttributeError, e:
976            self._participationIdx = self._partipationIdx
977            self._partipationIdx = None
978        res = []
979        for participation in self._participationIdx.getParticipationList( av ):
980            abstract = participation.getAbstract()
981            if abstract is not None and abstract.isSubmitter( av ):
982                if abstract not in res:
983                    res.append( abstract )
984        return res
985
986    def getAbstractListForAuthorEmail(self, email):
987        """ Get list of abstracts where the email belongs to an author"""
988        return [self.getAbstractById(i) for i in self._getAuthEmailIndex().match(email)]
989
990    def getNotificationTplList(self):
991        try:
992            if self._notifTpls:
993                pass
994        except AttributeError:
995            self._notifTpls=IOBTree()
996        try:
997            if self._notifTplsOrder:
998                pass
999        except AttributeError:
1000            self._notifTplsOrder=PersistentList()
1001            for tpl in self._notifTpls.values():
1002                self._notifTplsOrder.append(tpl)
1003        return self._notifTplsOrder
1004
1005    def addNotificationTpl(self,tpl):
1006        try:
1007            if self._notifTpls:
1008                pass
1009        except AttributeError:
1010            self._notifTpls=IOBTree()
1011        try:
1012            if self._notifTplsOrder:
1013                pass
1014        except AttributeError:
1015            self._notifTplsOrder=PersistentList()
1016            for tpl in self._notifTpls.values():
1017                self._notifTplsOrder.append(tpl)
1018        try:
1019            if self._notifTplsCounter:
1020                pass
1021        except AttributeError:
1022            self._notifTplsCounter=Counter()
1023        if tpl.getOwner()==self and self._notifTpls.has_key(tpl.getId()):
1024            return
1025        id = tpl.getId()
1026        if id == "":
1027            id=self._notifTplsCounter.newCount()
1028        tpl.includeInOwner(self,id)
1029        self._notifTpls[int(id)]=tpl
1030        self._notifTplsOrder.append(tpl)
1031
1032    def removeNotificationTpl(self,tpl):
1033        try:
1034            if self._notifTpls:
1035                pass
1036        except AttributeError:
1037            self._notifTpls=IOBTree()
1038        try:
1039            if self._notifTplsOrder:
1040                pass
1041        except AttributeError:
1042            self._notifTplsOrder=PersistentList()
1043            for tpl in self._notifTpls.values():
1044                self._notifTplsOrder.append(tpl)
1045        if tpl.getOwner()!=self or not self._notifTpls.has_key(int(tpl.getId())):
1046            return
1047        del self._notifTpls[int(tpl.getId())]
1048        self._notifTplsOrder.remove(tpl)
1049        tpl.includeInOwner(None,tpl.getId()) # We don't change the id for
1050                                             # recovery purposes.
1051        tpl.delete()
1052
1053    def recoverNotificationTpl(self, tpl):
1054        self.addNotificationTpl(tpl)
1055        tpl.recover()
1056
1057    def getNotificationTplById(self,id):
1058        try:
1059            if self._notifTpls:
1060                pass
1061        except AttributeError:
1062            self._notifTpls=IOBTree()
1063        return self._notifTpls.get(int(id),None)
1064
1065    def getNotifTplForAbstract(self,abs):
1066        """
1067        """
1068        for tpl in self.getNotificationTplList():
1069            if tpl.satisfies(abs):
1070                return tpl
1071        return None
1072
1073    def moveUpNotifTpl(self,tpl):
1074        """
1075        """
1076        try:
1077            if self._notifTplsOrder:
1078                pass
1079        except AttributeError:
1080            self._notifTplsOrder=PersistentList()
1081            for tpl in self._notifTpls.values():
1082                self._notifTplsOrder.append(tpl)
1083        if tpl not in self._notifTplsOrder:
1084            return
1085        idx=self._notifTplsOrder.index(tpl)
1086        if idx==0:
1087            return
1088        self._notifTplsOrder.remove(tpl)
1089        self._notifTplsOrder.insert(idx-1,tpl)
1090
1091
1092    def moveDownNotifTpl(self,tpl):
1093        """
1094        """
1095        try:
1096            if self._notifTplsOrder:
1097                pass
1098        except AttributeError:
1099            self._notifTplsOrder=PersistentList()
1100            for tpl in self._notifTpls.values():
1101                self._notifTplsOrder.append(tpl)
1102        idx=self._notifTplsOrder.index(tpl)
1103        if idx==len(self._notifTplsOrder):
1104            return
1105        self._notifTplsOrder.remove(tpl)
1106        self._notifTplsOrder.insert(idx+1,tpl)
1107
1108    def indexAuthor(self,auth):
1109        a=auth.getAbstract()
1110        if a.isPrimaryAuthor(auth):
1111            self._getPrimAuthIndex().index(auth)
1112        self._getAuthEmailIndex().index(auth)
1113
1114    def unindexAuthor(self,auth):
1115        a=auth.getAbstract()
1116        if a.isPrimaryAuthor(auth):
1117            self._getPrimAuthIndex().unindex(auth)
1118        self._getAuthEmailIndex().unindex(auth)
1119
1120    def _getPrimAuthIndex(self):
1121        try:
1122            if self._primAuthIdx:
1123                pass
1124        except AttributeError:
1125            self._primAuthIdx=_PrimAuthIdx(self)
1126        return self._primAuthIdx
1127
1128    def _getAuthEmailIndex(self):
1129        if not hasattr(self, '_authEmailIdx'):
1130            self._authEmailIdx = _AuthEmailIdx(self)
1131        return self._authEmailIdx
1132
1133    def getAbstractsMatchingAuth(self,query,onlyPrimary=True):
1134        if str(query).strip()=="":
1135            return self.getAbstractList()
1136        res=self._getPrimAuthIndex().match(query)
1137        return [self.getAbstractById(id) for id in res]
1138
1139    def addAbstractField(self, id, name, caption, maxLength=0, isMandatory=False, fieldType="textarea", fieldLimitation="chars"):
1140        return self.getAbstractFieldsMgr().addField(id, name, caption, maxLength, isMandatory, fieldType, fieldLimitation)
1141
1142    def removeAbstractField(self, id):
1143        self.getAbstractFieldsMgr().removeField(id)
1144
1145    def hasAnyEnabledAbstractField( self ):
1146        return self.getAbstractFieldsMgr().hasAnyActiveField()
1147
1148    def hasEnabledAbstractField(self, key):
1149        return self.getAbstractFieldsMgr().hasActiveField(key)
1150
1151    def enableAbstractField(self, abstractField):
1152        self.getAbstractFieldsMgr().enableField(abstractField)
1153        self.notifyModification()
1154
1155    def disableAbstractField(self, abstractField):
1156        self.getAbstractFieldsMgr().disableField(abstractField)
1157        self.notifyModification()
1158
1159    def moveAbsFieldUp(self, id):
1160        self.getAbstractFieldsMgr().moveAbsFieldUp(id)
1161        self.notifyModification()
1162
1163    def moveAbsFieldDown(self, id):
1164        self.getAbstractFieldsMgr().moveAbsFieldDown(id)
1165        self.notifyModification()
1166
1167    def getSubmissionNotification(self):
1168        try:
1169            if self._submissionNotification:
1170                pass
1171        except AttributeError, e:
1172            self._submissionNotification=SubmissionNotification()
1173        return self._submissionNotification
1174
1175    def setSubmissionNotification(self, sn):
1176        self._submissionNotification=sn
1177
1178    def recalculateAbstractsRating(self, scaleLower, scaleHigher):
1179        ''' recalculate the values of the rating for all the abstracts in the conference '''
1180        for abs in self.getAbstractList():
1181            abs.updateRating((scaleLower, scaleHigher))
1182
1183    def removeAnswersOfQuestion(self, questionId):
1184        ''' Remove a question results for each abstract '''
1185        for abs in self.getAbstractList():
1186            abs.removeAnswersOfQuestion(questionId)
1187
1188    def notifyModification(self):
1189        self._p_changed=1
1190
1191class SubmissionNotification(Persistent):
1192
1193    def __init__(self):
1194        self._toList = PersistentList()
1195        self._ccList = PersistentList()
1196
1197    def hasDestination(self):
1198        return self._toList!=[] or self._toList!=[]
1199
1200    def getToList(self):
1201        return self._toList
1202
1203    def setToList(self, tl):
1204        self._toList = tl
1205
1206    def addToList(self, to):
1207        self._toList.append(to)
1208
1209    def clearToList(self):
1210        self._toList = PersistentList()
1211
1212    def getCCList(self):
1213        return self._ccList
1214
1215    def setCCList(self, cl):
1216        self._ccList = cl
1217
1218    def addCCList(self, cc):
1219        self._ccList.append(cc)
1220
1221    def clearCCList(self):
1222        self._ccList = PersistentList()
1223
1224    def clone(self):
1225        nsn=SubmissionNotification()
1226        for i in self.getToList():
1227            nsn.addToList(i)
1228        for i in self.getCCList():
1229            nsn.addCCList(i)
1230        return nsn
1231
1232class Comment(Persistent):
1233
1234    def __init__(self,res,content=""):
1235        self._abstract=None
1236        self._id=""
1237        self._responsible=res
1238        self._content=""
1239        self._creationDate=nowutc()
1240        self._modificationDate=nowutc()
1241
1242    def getLocator(self):
1243        loc=self._abstract.getLocator()
1244        loc["intCommentId"]=self._id
1245        return loc
1246
1247    def includeInAbstract(self,abstract,id):
1248        self._abstract=abstract
1249        self._id=id
1250
1251    def delete(self):
1252        self._abstract=None
1253        TrashCanManager().add(self)
1254
1255    def recover(self):
1256        TrashCanManager().remove(self)
1257
1258    def _notifyModification(self, dt=None):
1259        if dt:
1260            self._modificationDate = dt
1261        else:
1262            self._modificationDate=nowutc()
1263
1264    def getResponsible(self):
1265        return self._responsible
1266
1267    def getAbstract(self):
1268        return self._abstract
1269
1270    def getId(self):
1271        return self._id
1272
1273    def getContent(self):
1274        return self._content
1275
1276    def setContent(self, newContent):
1277        self._content=newContent
1278        self._notifyModification()
1279
1280    def getCreationDate(self):
1281        return self._creationDate
1282
1283    def getModificationDate(self):
1284        return self._modificationDate
1285
1286    def canModify(self,aw):
1287        return self.canUserModify(aw.getUser())
1288
1289    def canUserModify(self,user):
1290        abstract=self.getAbstract()
1291        conf=abstract.getConference()
1292        return self.getResponsible()==user and \
1293            (abstract.canUserModify(user) or \
1294            len(conf.getConference().getCoordinatedTracks(user))>0)
1295
1296class Abstract(Persistent):
1297
1298    def __init__(self, owner, id, submitter, **abstractData):
1299        self._setOwner( owner )
1300        self._setId( id )
1301        self._title = ""
1302        self._fields = {}
1303        self._authorGen = Counter()
1304        self._authors = OOBTree()
1305        self._primaryAuthors = PersistentList()
1306        self._coAuthors = PersistentList()
1307        self._speakers = PersistentList()
1308        self._tracks = OOBTree()
1309        self._contribTypes = PersistentList( [""] )
1310        self._setSubmissionDate( nowutc() )
1311        self._modificationDate = nowutc()
1312        self._currentStatus = AbstractStatusSubmitted( self )
1313        self._trackAcceptances = OOBTree()
1314        self._trackRejections = OOBTree()
1315        self._trackReallocations = OOBTree()
1316        self._trackJudgementsHistorical={}
1317        self._comments = ""
1318        self._contribution = None
1319        self._intCommentGen=Counter()
1320        self._intComments=PersistentList()
1321        self._mergeFromList = PersistentList()
1322        self._notifLog=NotificationLog(self)
1323        self._submitter=None
1324        self._setSubmitter( submitter )
1325        self._rating = None # It needs to be none to avoid the case of having the same value as the lowest value in the judgement
1326        self._attachments = {}
1327        self._attachmentsCounter = Counter()
1328
1329    def clone(self, conference, abstractId):
1330
1331        # abstractId - internal in abstract manager of the conference
1332        abs = Abstract(conference.getAbstractMgr(), abstractId, self.getSubmitter().getAvatar())
1333        abs.setTitle(self.getTitle())
1334        for key in self.getFields().keys():
1335            abs.setField(key,self.getField(key))
1336        abs.setComments(self.getComments())
1337
1338        abs._setSubmissionDate(self.getSubmissionDate())
1339        abs._modificationDate = self.getModificationDate()
1340
1341        # Cloning of primary- and coauthors
1342        # if an author is also a speaker, an appropriate object will be
1343        # appended also to the speaker list
1344        for pa in self.getPrimaryAuthorList() :
1345            npa = abs.newPrimaryAuthor(**(pa.getData()))
1346            if self.isSpeaker(pa) :
1347                abs.addSpeaker(npa)
1348        for ca in self.getCoAuthorList() :
1349            nca = abs.newCoAuthor(**(ca.getData()))
1350            if self.isSpeaker(ca) :
1351                abs.addSpeaker(nca)
1352
1353        # Cloning of speakers
1354        # only those, who are not authors :
1355            for sp in self.getSpeakerList() :
1356                if not self.isAuthor(sp) :
1357                    abs.addSpeaker(sp.clone())
1358
1359        abs.setSubmitter(self.getSubmitter().getAvatar())
1360
1361        if self.getContribType() is not None :
1362            for ct in conference.getContribTypeList() :
1363                if self.getContribType().getName() == ct.getName() :
1364                    abs.setContribType(ct)
1365                    break
1366        else :
1367            abs.setContribType(None)
1368
1369        # the track, to which the abstract belongs to
1370        # legacy list implementation
1371        for tr in self.getTrackList() :
1372            for newtrack in conference.getTrackList():
1373                if newtrack.getTitle() == tr.getTitle() :
1374                    abs.addTrack(newtrack)
1375
1376        # overall abstract status (accepted / rejected)
1377        abs._currentStatus = self._currentStatus.clone(abs)
1378
1379        for ta in self.getTrackAcceptanceList() :
1380            for newtrack in conference.getTrackList():
1381                if newtrack.getTitle() == ta.getTrack().getTitle() :
1382                    newta = ta.clone(newtrack)
1383                    abs._addTrackAcceptance(newta)
1384                    abs._addTrackJudgementToHistorical(newta)
1385
1386        for trj in self.getTrackRejections().values() :
1387            for newtrack in conference.getTrackList():
1388                if newtrack.getTitle() == trj.getTrack().getTitle() :
1389                    newtrj = trj.clone(newtrack)
1390                    abs._addTrackRejection(newtrj)
1391                    abs._addTrackJudgementToHistorical(newtrj)
1392
1393        for trl in self.getTrackReallocations().values() :
1394            for newtrack in conference.getTrackList():
1395                if newtrack.getTitle() == trl.getTrack().getTitle() :
1396                    newtrl = trl.clone(newtrack)
1397                    abs._addTrackReallocation(newtrl)
1398                    abs._addTrackJudgementToHistorical(newtrl)
1399
1400        # Cloning materials
1401        for f in self.getAttachments().values():
1402            newFile = f.clone(abs, protection=False)
1403            abs.__addFile(newFile)
1404
1405        return abs
1406
1407    def getUniqueId( self ):
1408        """returns (string) the unique identifier of the item"""
1409        """used only in the web session access key table"""
1410        """it is the same as the conference since only the conf can"""
1411        """be protected with an access key"""
1412        return self.getConference().getUniqueId()
1413
1414    def getMergeFromList(self):
1415        try:
1416            return self._mergeFromList
1417        except AttributeError:
1418            self._mergeFromList = PersistentList()
1419            return self._mergeFromList
1420
1421    def addMergeFromAbstract(self, abstract):
1422        try:
1423            if self._mergeFromList:
1424                pass
1425        except AttributeError:
1426            self._mergeFromList = PersistentList()
1427        self._mergeFromList.append(abstract)
1428
1429    def removeMergeFromAbstract(self, abstract):
1430        try:
1431            if self._mergeFromList:
1432                pass
1433        except AttributeError:
1434            self._mergeFromList = PersistentList()
1435
1436        if abstract in self._mergeFromList:
1437            self._mergeFromList.remove(abstract)
1438
1439    def getComments(self):
1440        try:
1441            return self._comments
1442        except AttributeError:
1443            self._comments = ""
1444            return self._comments
1445
1446    def setComments(self, comments):
1447        self._comments = comments
1448
1449    def __addFile(self, file):
1450        file.archive(self.getConference()._getRepository())
1451        self.getAttachments()[file.getId()] = file
1452        self._notifyModification()
1453
1454
1455    def saveFiles(self, files):
1456        cfg = Config.getInstance()
1457        from MaKaC.conference import LocalFile
1458        for fileUploaded in files:
1459            # create a temp file
1460            tempPath = cfg.getUploadedFilesTempDir()
1461            tempFileName = tempfile.mkstemp(suffix="IndicoAbstract.tmp", dir=tempPath)[1]
1462            f = open(tempFileName, "wb")
1463            f.write(fileUploaded.file.read() )
1464            f.close()
1465            file = LocalFile()
1466            file.setFileName(fileUploaded.filename)
1467            file.setFilePath(tempFileName)
1468            file.setOwner(self)
1469            file.setId(self._getAttachmentsCounter())
1470            self.__addFile(file)
1471
1472    def deleteFilesNotInList(self, keys):
1473        """This method is used in order to delete all the files that are not present (by id) in the
1474        parameter "keys".
1475        This is useful when files are deleted from the abstract form using Javascript, and so it is
1476        the only way to know that they are deleted.
1477        """
1478        existingKeys = self.getAttachments().keys()
1479        for key in existingKeys:
1480            if not key in keys:
1481                self._deleteFile(key)
1482
1483    def _deleteFile(self, key):
1484        file = self.getAttachments()[key]
1485        file.delete()
1486        del self.getAttachments()[key]
1487        self._notifyModification()
1488
1489    def removeResource(self, res):
1490        """Necessary because LocalFile.delete (see _deleteFile) is calling this method.
1491        In our case, nothing to do.
1492        """
1493        pass
1494
1495    def _setOwner( self, owner ):
1496        self._owner = owner
1497
1498    def getOwner( self ):
1499        return self._owner
1500
1501    def _setId( self, id ):
1502        self._id = str( id )
1503
1504    def getId(self):
1505        return self._id
1506
1507    def _setSubmissionDate( self, newDate ):
1508        self._submissionDate = newDate
1509
1510    def setModificationDate(self, dt = None):
1511        if dt:
1512            self._modificationDate = dt
1513        else:
1514            self._modificationDate = nowutc()
1515
1516    def _notifyModification( self, dt=None ):
1517        self.setModificationDate(dt)
1518        self._p_changed = 1
1519
1520    def getModificationDate( self ):
1521        return self._modificationDate
1522
1523    def _setSubmitter( self, av ):
1524        if not av:
1525            raise MaKaCError( _("An abstract must have a submitter"))
1526        if self._submitter:
1527            self.getOwner().unregisterParticipation( self._submitter )
1528            self._submitter.getUser().unlinkTo(self, "submitter")
1529            self._submitter.delete()
1530        self._submitter=Submitter( self, av )
1531        av.linkTo(self, "submitter")
1532        self.getOwner().registerParticipation( self._submitter )
1533        self._notifyModification()
1534
1535    def recoverSubmitter(self, subm):
1536        if not subm:
1537            raise MaKaCError( _("An abstract must have a submitter"))
1538        if self._submitter:
1539            self.getOwner().unregisterParticipation( self._submitter )
1540            self._submitter.delete()
1541        self._submitter = subm
1542        self._submitter.setAbstract(self)
1543        self.getOwner().registerParticipation( self._submitter )
1544        subm.recover()
1545        self._notifyModification()
1546
1547    def setSubmitter( self, av ):
1548        self._setSubmitter(av)
1549
1550    def getSubmitter( self ):
1551        return self._submitter
1552
1553    def isSubmitter( self, av ):
1554        return self.getSubmitter().representsUser( av )
1555
1556    def setTitle(self, title):
1557        self._title = title.strip()
1558        self._notifyModification()
1559
1560    def getTitle(self):
1561        return self._title
1562
1563    def getFields( self ):
1564        try:
1565            return self._fields
1566        except:
1567            self._fields = {}
1568            try:
1569                if self._content != "":
1570                    self._fields["content"] = self._content
1571                del self._content
1572            except:
1573                pass
1574            try:
1575                if self._summary != "":
1576                    self._fields["summary"] = self._summary
1577                del self._summary
1578            except:
1579                pass
1580            return self._fields
1581
1582    def removeField(self, field):
1583        if self.getFields().has_key(field):
1584            del self.getFields()[field]
1585            self._notifyModification()
1586
1587    def setField( self, field, value ):
1588        try:
1589            self.getFields()[field] = value
1590            self._notifyModification()
1591        except:
1592            pass
1593
1594    def getField( self, field):
1595        try:
1596            if self._content != "":
1597                self._fields["content"] = self._content
1598            del self._content
1599        except:
1600            pass
1601        try:
1602            if self._summary != "":
1603                self._fields["summary"] = self._summary
1604            del self._summary
1605        except:
1606            pass
1607        if self.getFields().has_key(field):
1608            return self.getFields()[field]
1609        else:
1610            return ""
1611
1612    def getSubmissionDate( self ):
1613        try:
1614            if self._submissionDate:
1615                pass
1616        except AttributeError:
1617            self._submissionDate=nowutc()
1618        return self._submissionDate
1619
1620    def getConference( self ):
1621        return self.getOwner().getOwner()
1622
1623    def _newAuthor( self, **data ):
1624        author = Author( self, **data )
1625        author.setId( self._authorGen.newCount() )
1626        self._authors[ author.getId() ] = author
1627        return author
1628
1629    def _removeAuthor(self,part):
1630        if not self.isAuthor(part):
1631            return
1632        part.delete()
1633        del self._authors[part.getId()]
1634
1635    def isAuthor( self, part ):
1636        return self._authors.has_key( part.getId() )
1637
1638    def getAuthorList( self ):
1639        return self._authors.values()
1640
1641    def getAuthorById(self, id):
1642        return self._authors.get(str(id), None)
1643
1644    def clearAuthors( self ):
1645        self.clearPrimaryAuthors()
1646        self.clearCoAuthors()
1647        self._notifyModification()
1648
1649    def newPrimaryAuthor(self,**data):
1650        auth=self._newAuthor(**data)
1651        self._addPrimaryAuthor(auth)
1652        self._notifyModification()
1653        return auth
1654
1655    def isPrimaryAuthor( self, part ):
1656        return part in self._primaryAuthors
1657
1658    def getPrimaryAuthorList( self ):
1659        return self._primaryAuthors
1660    #XXX: I keep it for compatibility but it should be removed
1661    getPrimaryAuthorsList = getPrimaryAuthorList
1662
1663    def getPrimaryAuthorEmailList(self, lower=False):
1664        emailList = []
1665        for pAuthor in self.getPrimaryAuthorList():
1666            emailList.append(pAuthor.getEmail().lower() if lower else pAuthor.getEmail())
1667        return emailList
1668
1669    def clearPrimaryAuthors(self):
1670        while len(self._primaryAuthors)>0:
1671            self._removePrimaryAuthor(self._primaryAuthors[0])
1672        self._notifyModification()
1673
1674    def _addPrimaryAuthor( self, part ):
1675        if not self.isAuthor( part ):
1676            raise MaKaCError( _("The participation you want to set as primary author is not an author of the abstract"))
1677        if part in self._primaryAuthors:
1678            return
1679        self._primaryAuthors.append( part )
1680        self.getOwner().indexAuthor(part)
1681
1682    def _removePrimaryAuthor(self,part):
1683        if not self.isPrimaryAuthor(part):
1684            return
1685        if self.isSpeaker(part):
1686            self.removeSpeaker(part)
1687        self.getOwner().unindexAuthor(part)
1688        self._primaryAuthors.remove(part)
1689        self._removeAuthor(part)
1690
1691    def recoverPrimaryAuthor(self, auth):
1692        self._authors[ auth.getId() ] = auth
1693        auth.setAbstract(self)
1694        self._addPrimaryAuthor(auth)
1695        auth.recover()
1696        self._notifyModification()
1697
1698    def newCoAuthor(self,**data):
1699        auth=self._newAuthor(**data)
1700        self._addCoAuthor(auth)
1701        self._notifyModification()
1702        return auth
1703
1704    def _comp_CoAuthors(self):
1705        try:
1706            if self._coAuthors!=None:
1707                return
1708        except AttributeError:
1709            self._coAuthors=PersistentList()
1710        for auth in self._authors.values():
1711            if not self.isPrimaryAuthor(auth):
1712                self._addCoAuthor(auth)
1713
1714    def isCoAuthor( self, part ):
1715        self._comp_CoAuthors()
1716        return part in self._coAuthors
1717
1718    def getCoAuthorList( self ):
1719        self._comp_CoAuthors()
1720        return self._coAuthors
1721
1722    def getCoAuthorEmailList(self, lower=False):
1723        emailList = []
1724        for coAuthor in self.getCoAuthorList():
1725            emailList.append(coAuthor.getEmail().lower() if lower else coAuthor.getEmail())
1726        return emailList
1727
1728    def clearCoAuthors(self):
1729        while len(self._coAuthors)>0:
1730            self._removeCoAuthor(self._coAuthors[0])
1731        self._notifyModification()
1732
1733    def _addCoAuthor( self, part ):
1734        self._comp_CoAuthors()
1735        if not self.isAuthor( part ):
1736            raise MaKaCError( _("The participation you want to set as primary author is not an author of the abstract"))
1737        if part in self._coAuthors:
1738            return
1739        self._coAuthors.append( part )
1740
1741    def _removeCoAuthor(self,part):
1742        if not self.isCoAuthor(part):
1743            return
1744        if self.isSpeaker(part):
1745            self.removeSpeaker(part)
1746        self._coAuthors.remove(part)
1747        self._removeAuthor(part)
1748
1749    def recoverCoAuthor(self, auth):
1750        self._authors[ auth.getId() ] = auth
1751        auth.setAbstract(self)
1752        self._addCoAuthor(auth)
1753        auth.recover()
1754        self._notifyModification()
1755
1756    def addSpeaker( self, part ):
1757        if not self.isAuthor( part ):
1758            raise MaKaCError( _("The participation you want to set as speaker is not an author of the abstract"))
1759        if part in self._speakers:
1760            return
1761        self._speakers.append( part )
1762        self._notifyModification()
1763
1764    def removeSpeaker(self,part):
1765        if part not in self._speakers:
1766            return
1767        self._speakers.remove(part)
1768
1769    def clearSpeakers( self ):
1770        while len(self.getSpeakerList()) > 0:
1771            self.removeSpeaker(self.getSpeakerList()[0])
1772        self._speakers = PersistentList()
1773
1774    def getSpeakerList( self ):
1775        return self._speakers
1776
1777    def isSpeaker( self, part ):
1778        return part in self._speakers
1779
1780    def setContribType( self, contribType ):
1781        self._contribTypes[0] = contribType
1782        self._notifyModification()
1783
1784    def getContribType( self ):
1785        return self._contribTypes[0]
1786
1787
1788    def _addTrack( self, track ):
1789        """Adds the specified track to the suggested track list. Any
1790            verification must be done by the caller.
1791        """
1792        self._tracks[ track.getId() ] = track
1793        track.addAbstract( self )
1794        self._notifyModification()
1795
1796    def addTrack( self, track ):
1797        self._changeTracksImpl()
1798        if not self._tracks.has_key( track.getId() ):
1799            self._addTrack( track )
1800            self.getCurrentStatus().update()
1801
1802    def _removeTrack( self, track ):
1803        """Removes the specified track from the track list. Any verification
1804            must be done by the caller.
1805        """
1806        del self._tracks[ track.getId() ]
1807        track.removeAbstract( self )
1808        self._notifyModification()
1809
1810    def removeTrack( self, track ):
1811        if self._tracks.has_key( track.getId() ):
1812            self._removeTrack( track )
1813            self.getCurrentStatus().update()
1814        if isinstance(self.getCurrentStatus(), AbstractStatusAccepted):
1815            self.getCurrentStatus()._setTrack(None)
1816
1817    def _changeTracksImpl( self ):
1818        if self._tracks.__class__ != OOBTree:
1819            oldTrackList = self._tracks
1820            self._tracks = OOBTree()
1821            for track in oldTrackList:
1822                self._addTrack( track )
1823            self.getCurrentStatus().update()
1824
1825    def getTrackList( self ):
1826        self._changeTracksImpl()
1827
1828        return self._tracks.values()
1829
1830    def hasTrack( self, track ):
1831        self._changeTracksImpl()
1832
1833        return self._tracks.has_key( track.getId() )
1834
1835    def getTrackListSorted( self ):
1836        self._changeTracksImpl()
1837        return self.getConference().sortTrackList( self._tracks.values() )
1838
1839    def clearTracks( self ):
1840        self._changeTracksImpl()
1841
1842        while len(self.getTrackList())>0:
1843            track = self.getTrackList()[0]
1844            self._removeTrack( track )
1845        self.getCurrentStatus().update()
1846
1847    def setTracks( self, trackList ):
1848        """Set the suggested track classification of the current abstract to
1849            the specified list
1850        """
1851        #We need to do it in 2 steps otherwise the list over which we are
1852        #   iterating gets modified
1853        toBeRemoved = []
1854        toBeAdded = copy( trackList )
1855        for track in self.getTrackList():
1856            if track not in trackList:
1857                toBeRemoved.append( track )
1858            else:
1859                toBeAdded.remove( track )
1860        for track in toBeRemoved:
1861            self._removeTrack( track )
1862        for track in toBeAdded:
1863            self._addTrack( track )
1864        self.getCurrentStatus().update()
1865
1866    def isProposedForTrack( self, track ):
1867        return self._tracks.has_key( track.getId() )
1868
1869    def getNumTracks( self ):
1870        return len( self._tracks )
1871
1872    def getLocator(self):
1873        loc = self.getConference().getLocator()
1874        loc["abstractId"] = self.getId()
1875        return loc
1876
1877    def isAllowedToCoordinate(self,av):
1878        """Tells whether or not the specified user can coordinate any of the
1879            tracks of this abstract
1880        """
1881        for track in self.getTrackList():
1882            if track.canUserCoordinate(av):
1883                return True
1884        return False
1885
1886    def canAuthorAccess(self, user):
1887        if user is None:
1888            return False
1889        el=self.getCoAuthorEmailList(True)+self.getPrimaryAuthorEmailList(True)
1890        for e in user.getEmails():
1891            if e.lower() in el:
1892                return True
1893        return False
1894
1895    def isAllowedToAccess( self, av ):
1896        """Tells whether or not an avatar can access an abstract independently
1897            of the protection
1898        """
1899        #any author is allowed to access
1900        #CFA managers are allowed to access
1901        #any user being able to modify is also allowed to access
1902        #any TC is allowed to access
1903        if self.canAuthorAccess(av):
1904            return True
1905        if self.isAllowedToCoordinate(av):
1906            return True
1907        return self.canUserModify(av)
1908
1909    def canAccess(self,aw):
1910        #if the conference is protected, then only allowed AW can access
1911        return self.isAllowedToAccess( aw.getUser() )
1912
1913    def canView(self, aw):
1914        #in the future it would be possible to add an access control
1915        #only those users allowed to access are allowed to view
1916        return self.isAllowedToAccess( aw.getUser() )
1917
1918    def canModify( self, aw ):
1919        return self.canUserModify( aw.getUser() )
1920
1921    def canUserModify( self, av ):
1922        #the submitter can modify
1923        if self.isSubmitter( av ):
1924            return True
1925        #??? any CFA manager can modify
1926        #??? any user granted with modification privileges can modify
1927        #conference managers can modify
1928        conf = self.getConference()
1929        return conf.canUserModify( av )
1930
1931    def getModifKey( self ):
1932        return ""
1933
1934    def getAccessKey( self ):
1935        return ""
1936
1937    def getAccessController(self):
1938        return self.getConference().getAccessController()
1939
1940    def isProtected(self):
1941        return self.getConference().isProtected()
1942
1943    def delete( self ):
1944        if self._owner:
1945            self.getSubmitter().delete()
1946            self._submitter = None
1947            self.clearAuthors()
1948            self.clearSpeakers()
1949            self.clearTracks()
1950            owner = self._owner
1951            self._owner = None
1952            owner.removeAbstract( self )
1953            self.setCurrentStatus(AbstractStatusNone(self))
1954            TrashCanManager().add(self)
1955
1956    def recoverFromTrashCan(self):
1957        TrashCanManager().remove(self)
1958
1959    def getCurrentStatus( self ):
1960        try:
1961            if self._currentStatus:
1962                pass
1963        except AttributeError, e:
1964            self._currentStatus = AbstractStatusSubmitted( self )
1965        return self._currentStatus
1966
1967    def setCurrentStatus( self, newStatus ):
1968        self._currentStatus = newStatus
1969        #If we want to keep a history of status changes we should add here
1970        #   the old status to a list
1971
1972    def accept(self,responsible,destTrack,type,comments="",session=None):
1973        """
1974        """
1975        self.getCurrentStatus().accept(responsible,destTrack,type,comments )
1976        #add the abstract to the track for which it has been accepted so it
1977        #   is visible for it.
1978        if destTrack is not None:
1979            destTrack.addAbstract( self )
1980        #once the abstract is accepted a new contribution under the destination
1981        #   track must be created
1982        # ATTENTION: This import is placed here explicitely for solving
1983        #   problems with circular imports
1984        from MaKaC.conference import AcceptedContribution
1985        contrib=AcceptedContribution(self)
1986        if session != None:
1987            contrib.setSession(session)
1988        self.getCurrentStatus().setContribution( contrib )
1989        self._setContribution( contrib )
1990
1991    def reject( self, responsible, comments="" ):
1992        """
1993        """
1994        self.getCurrentStatus().reject( responsible, comments )
1995
1996    def _cmpByDate(self, tj1, tj2):
1997        return cmp(tj1.getDate(), tj2.getDate())
1998
1999    def getTrackJudgementsHistorical(self):
2000        try:
2001            if self._trackJudgementsHistorical:
2002                pass
2003            if type(self._trackJudgementsHistorical) == tuple:
2004                self._trackJudgementsHistorical={}
2005        except AttributeError, e:
2006            self._trackJudgementsHistorical={}
2007            for track in self.getTrackList():
2008                if self.getTrackJudgement(track) is not None:
2009                    self._trackJudgementsHistorical[track.getId()]= [self.getTrackJudgement(track)]
2010            self._notifyModification()
2011        return self._trackJudgementsHistorical
2012
2013    def getJudgementHistoryByTrack(self, track):
2014        id = "notrack"
2015        if track is not None:
2016            id = track.getId()
2017        if self.getTrackJudgementsHistorical().has_key(id):
2018            return self.getTrackJudgementsHistorical()[id]
2019        return []
2020
2021    def _addTrackJudgementToHistorical(self, tj):
2022        id = "notrack"
2023        if tj.getTrack() is not None:
2024            id = tj.getTrack().getId()
2025        if self.getTrackJudgementsHistorical().has_key(id):
2026            if tj not in self.getTrackJudgementsHistorical()[id]:
2027                self.getTrackJudgementsHistorical()[id].insert(0, tj)
2028        else:
2029            self.getTrackJudgementsHistorical()[id] = [tj]
2030        self._notifyModification()
2031
2032    def _removeTrackAcceptance( self, track ):
2033        """
2034        """
2035        if self.getTrackAcceptances().has_key( track.getId() ):
2036            del self.getTrackAcceptances()[ track.getId() ]
2037
2038    def _addTrackAcceptance( self, judgement ):
2039        """
2040        """
2041        self._removeTrackRejection( judgement.getTrack() )
2042        self._removeTrackReallocation( judgement.getTrack() )
2043        self.getTrackAcceptances()[ judgement.getTrack().getId() ] = judgement
2044        self._addTrackJudgementToHistorical(judgement)
2045
2046    def _removeTrackRejection( self, track ):
2047        """
2048        """
2049        if self.getTrackRejections().has_key( track.getId() ):
2050            del self.getTrackRejections()[ track.getId() ]
2051
2052    def _addTrackRejection( self, judgement ):
2053        """
2054        """
2055        self._removeTrackAcceptance( judgement.getTrack() )
2056        self._removeTrackReallocation( judgement.getTrack() )
2057        self.getTrackRejections()[ judgement.getTrack().getId() ] = judgement
2058        self._addTrackJudgementToHistorical(judgement)
2059
2060    def _removeTrackReallocation( self, track ):
2061        """
2062        """
2063        if self.getTrackReallocations().has_key( track.getId() ):
2064            del self.getTrackReallocations()[ track.getId() ]
2065
2066    def _addTrackReallocation( self, judgement ):
2067        """
2068        """
2069        self._removeTrackAcceptance( judgement.getTrack() )
2070        self._removeTrackRejection( judgement.getTrack() )
2071        self.getTrackReallocations()[ judgement.getTrack().getId() ] = judgement
2072        self._addTrackJudgementToHistorical(judgement)
2073
2074    def _clearTrackRejections( self ):
2075        while len(self.getTrackRejections().values())>0:
2076            t = self.getTrackRejections().values()[0].getTrack()
2077            self._removeTrackRejection( t )
2078
2079    def _clearTrackAcceptances( self ):
2080        while len(self.getTrackAcceptances().values())>0:
2081            t = self.getTrackAcceptances().values()[0].getTrack()
2082            self._removeTrackAcceptance( t )
2083
2084    def _clearTrackReallocations( self ):
2085        while len(self.getTrackReallocations().values())>0:
2086            t = self.getTrackReallocations().values()[0].getTrack()
2087            self._removeTrackReallocation(t)
2088
2089    def _removePreviousJud(self, responsible, track):
2090        ''' Check if there is a previous judgement and remove it '''
2091        toDelete = [] # list of judgements to delete
2092        for jud in self.getJudgementHistoryByTrack(track):
2093            if jud.getResponsible() == responsible:
2094                toDelete.append(jud)
2095
2096        for x in toDelete:
2097            self.getTrackJudgementsHistorical()[track.getId()].remove(x)
2098
2099
2100    def proposeToAccept( self, responsible, track, contribType, comment="", answers=[] ):
2101        """
2102        """
2103        # the proposal has to be done for a track
2104        if track is None:
2105            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"))
2106        #We check the track for which the abstract is proposed to be accepted
2107        #   is in the current abstract
2108        if not self.isProposedForTrack( track ):
2109            raise MaKaCError( _("Cannot propose to accept an abstract which is not proposed for the specified track"))
2110        # check if there is a previous judgement of this author in for this abstract in this track
2111        self._removePreviousJud(responsible, track)
2112        # Create the new judgement
2113        jud = AbstractAcceptance( track, responsible, contribType, answers )
2114        jud.setComment( comment )
2115        self._addTrackAcceptance( jud )
2116        # Update the rating of the abstract
2117        self.updateRating()
2118        #We trigger the state transition
2119        self.getCurrentStatus().proposeToAccept()
2120
2121    def proposeToReject( self, responsible, track, comment="", answers=[] ):
2122        """
2123        """
2124        # the proposal has to be done for a track
2125        if track is None:
2126            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"))
2127        #We check the track for which the abstract is proposed to be accepted
2128        #   is in the current abstract
2129        if not self.isProposedForTrack( track ):
2130            raise MaKaCError( _("Cannot propose to reject an abstract which is not proposed for the specified track"))
2131        # check if there is a previous judgement of this author in for this abstract in this track
2132        self._removePreviousJud(responsible, track)
2133        # Create the new judgement
2134        jud = AbstractRejection( track, responsible, answers )
2135        jud.setComment( comment )
2136        self._addTrackRejection( jud )
2137        # Update the rating of the abstract
2138        self.updateRating()
2139        #We trigger the state transition
2140        self.getCurrentStatus().proposeToReject()
2141
2142    def proposeForOtherTracks( self, responsible, track, comment, propTracks, answers=[] ):
2143        """
2144        """
2145        #We check the track which proposes to allocate the abstract is in the
2146        #   current abstract
2147        if not self.isProposedForTrack( track ):
2148            raise MaKaCError( _("Cannot propose to reallocate an abstract which is not proposed for the specified track"))
2149        # check if there is a previous judgement of this author in for this abstract in this track
2150        self._removePreviousJud(responsible, track)
2151        #We keep the track judgement
2152        jud = AbstractReallocation( track, responsible, propTracks, answers )
2153        jud.setComment( comment )
2154        self._addTrackReallocation( jud )
2155        #We add the proposed tracks to the abstract
2156        for track in propTracks:
2157            self._addTrack( track )
2158        #We trigger the state transition
2159        self.getCurrentStatus().proposeToReallocate()
2160        # Update the rating of the abstract
2161        self.updateRating()
2162
2163    def withdraw(self,resp,comment=""):
2164        """
2165        """
2166        self.getCurrentStatus().withdraw(resp,comment)
2167
2168    def recover( self ):
2169        """Puts a withdrawn abstract back in the list of submitted abstracts.
2170           HAS NOTHING TO DO WITH THE RECOVERY PROCESS...
2171        """
2172        #we must clear any track judgement
2173        #self._clearTrackAcceptances()
2174        #self._clearTrackRejections()
2175        #self._clearTrackReallocations()
2176        self.getCurrentStatus().recover() #status change
2177        #if succeeded we must reset the submission date
2178        self._setSubmissionDate( nowutc() )
2179        self._notifyModification()
2180
2181    def getTrackJudgement( self, track ):
2182        """
2183        """
2184        if self.getTrackAcceptances().has_key( track.getId() ):
2185            return self.getTrackAcceptances()[ track.getId() ]
2186        elif self.getTrackRejections().has_key( track.getId() ):
2187            return self.getTrackRejections()[ track.getId() ]
2188        elif self.getTrackReallocations().has_key( track.getId() ):
2189            return self.getTrackReallocations()[ track.getId() ]
2190        return None
2191
2192    def getTrackAcceptances( self ):
2193        try:
2194            if self._trackAcceptances:
2195                pass
2196        except AttributeError, e:
2197            self._trackAcceptances = OOBTree()
2198        return self._trackAcceptances
2199
2200    def getTrackAcceptanceList( self ):
2201        res = []
2202        for trackId in intersection( self._tracks, self.getTrackAcceptances() ):
2203            res.append( self.getTrackAcceptances()[ trackId ] )
2204        return res
2205
2206    def getNumProposedToAccept( self ):
2207        return len( intersection( self._tracks, self.getTrackAcceptances() ) )
2208
2209    def getTrackRejections( self ):
2210        try:
2211            if self._trackRejections:
2212                pass
2213        except AttributeError, e:
2214            self._trackRejections = OOBTree()
2215        return self._trackRejections
2216
2217    def getNumProposedToReject( self ):
2218        return len( intersection( self._tracks, self.getTrackRejections() ) )
2219
2220    def getTrackReallocations( self ):
2221        try:
2222            if self._trackReallocations:
2223                pass
2224        except AttributeError, e:
2225            self._trackReallocations = OOBTree()
2226        return self._trackReallocations
2227
2228
2229    def getNumProposedToReallocate( self ):
2230        return len( intersection( self._tracks, self.getTrackReallocations() ) )
2231
2232
2233    def getNumJudgements( self ):
2234        """
2235        Returns the number of tracks for which some proposal has been done.
2236        For instance, let's suppose:
2237           Track 1: 2 propose to accept, 3 propose to reject
2238           Track 2: 1 propose to accept
2239           Track 3: None
2240        The result would be 2 (out of 3)
2241        """
2242        tmp1 = union( self.getTrackAcceptances(), self.getTrackRejections() )
2243        judgements = union( tmp1, self.getTrackReallocations() )
2244        return len( intersection( self._tracks, judgements ) )
2245
2246    def getReallocationTargetedList( self, track ):
2247        #XXX: not optimal
2248        res = []
2249        for r in self.getTrackReallocations().values():
2250            if track in r.getProposedTrackList():
2251                res.append( r )
2252        return res
2253
2254    def getContribution( self ):
2255        try:
2256            if self._contribution:
2257                pass
2258        except AttributeError:
2259            self._contribution = None
2260        status = self.getCurrentStatus()
2261        if isinstance(status,AbstractStatusAccepted) and \
2262                                            self._contribution is None:
2263            self._contribution=status.getContribution()
2264        return self._contribution
2265
2266    def _setContribution(self,contrib):
2267        self._contribution = contrib
2268
2269    def getIntCommentList(self):
2270        try:
2271            if self._intComments:
2272                pass
2273        except AttributeError:
2274            self._intComments=PersistentList()
2275        return self._intComments
2276
2277    def addIntComment(self,newComment):
2278        try:
2279            if self._intComments:
2280                pass
2281        except AttributeError:
2282            self._intComments=PersistentList()
2283        try:
2284            if self._intCommentsGen:
2285                pass
2286        except AttributeError:
2287            self._intCommentsGen=Counter()
2288        if newComment in self._intComments:
2289            return
2290        id = newComment.getId()
2291        if id == "":
2292            id = self._authorGen.newCount()
2293        newComment.includeInAbstract(self, id)
2294        self._intComments.append(newComment)
2295
2296    def getIntCommentById(self,id):
2297        try:
2298            if self._intComments:
2299                pass
2300        except AttributeError:
2301            self._intComments=PersistentList()
2302        for comment in self._intComments:
2303            if id.strip()==comment.getId():
2304                return comment
2305        return None
2306
2307    def clearIntCommentList(self):
2308        while len(self.getIntCommentList()) > 0:
2309            self.removeIntComment(self.getIntCommentList()[0])
2310
2311    def removeIntComment(self,comment):
2312        try:
2313            if self._intComments:
2314                pass
2315        except AttributeError:
2316            self._intComments=PersistentList()
2317        if comment not in self._intComments:
2318            return
2319        self._intComments.remove(comment)
2320        comment.delete()
2321
2322    def recoverIntComment(self, comment):
2323        self.addIntComment(comment)
2324        comment.recover()
2325
2326    def markAsDuplicated(self,responsible,originalAbstract,comments="", track=None, answers=[]):
2327        """
2328        """
2329        self.getCurrentStatus().markAsDuplicated(responsible,originalAbstract,comments)
2330        # check if there is a previous judgement of this author in for this abstract in this track
2331        self._removePreviousJud(responsible, track)
2332
2333        if track is not None:
2334            jud = AbstractMarkedAsDuplicated( track, responsible, originalAbstract, answers )
2335            jud.setComment( comments )
2336            self._addTrackJudgementToHistorical(jud)
2337        else:
2338            for t in self.getTrackList():
2339                jud = AbstractMarkedAsDuplicated( t, responsible, originalAbstract, answers )
2340                jud.setComment( comments )
2341                self._addTrackJudgementToHistorical(jud)
2342        # Update the rating of the abstract
2343        self.updateRating()
2344
2345    def unMarkAsDuplicated(self,responsible,comments="", track=None, answers=[]):
2346        """
2347        """
2348
2349        #we must clear any track judgement
2350        self._clearTrackAcceptances()
2351        self._clearTrackRejections()
2352        self._clearTrackReallocations()
2353        #self.getCurrentStatus().recover() #status change
2354        self.getCurrentStatus().unMarkAsDuplicated(responsible,comments)
2355
2356        # check if there is a previous judgement of this author in for this abstract in this track
2357        self._removePreviousJud(responsible, track)
2358
2359        if track is not None:
2360            jud = AbstractUnMarkedAsDuplicated(track, responsible, answers )
2361            jud.setComment( comments )
2362            self._addTrackJudgementToHistorical(jud)
2363        else:
2364            for t in self.getTrackList():
2365                jud = AbstractUnMarkedAsDuplicated( t, responsible, answers )
2366                jud.setComment( comments )
2367                self._addTrackJudgementToHistorical(jud)
2368        # Update the rating of the abstract
2369        self.updateRating()
2370        self._notifyModification()
2371
2372    def mergeInto(self,responsible,targetAbs,mergeAuthors=False,comments=""):
2373        """
2374        """
2375        self.getCurrentStatus().mergeInto(responsible,targetAbs,comments)
2376        targetAbs.addMergeFromAbstract(self)
2377        if mergeAuthors:
2378            #for auth in self.getAuthorList():
2379            #    newAuth=targetAbs.newAuthor()
2380            #    newAuth.setFromAbstractParticipation(auth)
2381            #    if self.isPrimaryAuthor(auth):
2382            #        targetAbs.addPrimaryAuthor(newAuth)
2383            for auth in self.getPrimaryAuthorList():
2384                newAuth=targetAbs.newPrimaryAuthor()
2385                newAuth.setFromAbstractParticipation(auth)
2386            for auth in self.getCoAuthorList():
2387                newAuth=targetAbs.newCoAuthor()
2388                newAuth.setFromAbstractParticipation(auth)
2389
2390    def notify(self,notificator,responsible):
2391        """notifies the abstract responsibles with a matching template
2392        """
2393        tpl=self.getOwner().getNotifTplForAbstract(self)
2394        if not tpl:
2395            return
2396        notificator.notify(self,tpl)
2397        self.getNotificationLog().addEntry(NotifLogEntry(responsible,tpl))
2398
2399    def unMerge(self,responsible,comments=""):
2400        #we must clear any track judgement
2401        self._clearTrackAcceptances()
2402        self._clearTrackRejections()
2403        self._clearTrackReallocations()
2404        self.getCurrentStatus().getTargetAbstract().removeMergeFromAbstract(self)
2405        self.getCurrentStatus().unMerge(responsible,comments)
2406        self._notifyModification()
2407
2408    def getNotificationLog(self):
2409        try:
2410            if self._notifLog:
2411                pass
2412        except AttributeError:
2413            self._notifLog=NotificationLog(self)
2414        return self._notifLog
2415
2416    # Rating methods
2417    def getRating(self):
2418        """ Get the average rating of the abstract """
2419        try:
2420            if self._rating:
2421                pass
2422        except AttributeError:
2423            self._rating = None
2424        return self._rating
2425
2426    def updateRating(self, scale = None):
2427        """
2428            Update the average rating of the abstract which is calculated with the average of each judgement.
2429            If the scale (tuple with lower,higher) is passed, the judgement are re-adjusted to the new scale.
2430        """
2431        self._rating = None
2432        # calculate the total valoration
2433        judNum = 0
2434        ratingSum = 0
2435        for track in self.getTrackListSorted():
2436            for jud in self.getJudgementHistoryByTrack(track):
2437                if scale:
2438                    # calculate the new values for each judgement
2439                    scaleLower, scaleHigher = scale
2440                    jud.recalculateJudgementValues(scaleLower, scaleHigher)
2441                if jud.getJudValue() != None: # it means there is a numeric value for the judgement
2442                    ratingSum += jud.getJudValue()
2443                    judNum += 1
2444        # Calculate the average
2445        if judNum != 0:
2446            self._rating = float(ratingSum) / judNum
2447
2448    def getQuestionsAverage(self):
2449        '''Get the list of questions answered in the reviews for an abstract '''
2450        dTotals = {} # {idQ1: total_value, idQ2: total_value ...}
2451        dTimes = {} # {idQ1: times_answered, idQ2: times_answered}
2452        for track in self.getTrackListSorted():
2453            for jud in self.getJudgementHistoryByTrack(track):
2454                for answer in jud.getAnswers():
2455                    # check if the question is in d and sum the answers value or insert in d the new question
2456                    if dTotals.has_key(answer.getQuestion().getText()):
2457                        dTotals[answer.getQuestion().getText()] += answer.getValue()
2458                        dTimes[answer.getQuestion().getText()] += 1
2459                    else: # first time
2460                        dTotals[answer.getQuestion().getText()] = answer.getValue()
2461                        dTimes[answer.getQuestion().getText()] = 1
2462        # get the questions average
2463        questionsAverage = {}
2464        for q, v in dTotals.iteritems():
2465            # insert the element and calculate the average for the value
2466            questionsAverage[q] = float(v)/dTimes[q]
2467        return questionsAverage
2468
2469    def removeAnswersOfQuestion(self, questionId):
2470        ''' Remove the answers of the question with questionId value '''
2471        for track in self.getTrackListSorted():
2472            for jud in self.getJudgementHistoryByTrack(track):
2473                jud.removeAnswer(questionId)
2474
2475    def getRatingPerReviewer(self, user, track):
2476        """
2477            Get the rating of the user for the abstract in the track given.
2478        """
2479        for jud in self.getJudgementHistoryByTrack(track):
2480            if (jud.getResponsible() == user):
2481                return jud.getJudValue()
2482
2483    def _getAttachmentsCounter(self):
2484        try:
2485            if self._attachmentsCounter:
2486                pass
2487        except AttributeError:
2488            self._attachmentsCounter = Counter()
2489        return self._attachmentsCounter.newCount()
2490
2491    def setAttachments(self, attachments):
2492        self._attachments = attachments
2493
2494    def getAttachments(self):
2495        try:
2496            if self._attachments:
2497                pass
2498        except AttributeError:
2499            self._attachments = {}
2500        return self._attachments
2501
2502    def getAttachmentById(self, id):
2503        return self.getAttachments().get(id, None)
2504
2505
2506class AbstractJudgement( Persistent ):
2507    """This class represents each of the judgements made by a track about a
2508        certain abstract. Each track for which an abstract is proposed can
2509        make a judgement proposing the abstract to be accepted or rejected.
2510        Different track judgements must be kept so the referees who have to
2511        take the final decission can overview different opinions from the
2512        track coordinators.
2513      Together with the judgement some useful information like the date when
2514        it was done and the user who did it will be kept.
2515    """
2516
2517    def __init__( self, track, responsible, answers ):
2518        self._track = track
2519        self._setResponsible( responsible )
2520        self._date = nowutc()
2521        self._comment = ""
2522        self._answers = answers
2523        self._judValue = self.calculateJudgementAverage() # judgement average value
2524        self._totalJudValue = self.calculateAnswersTotalValue()
2525
2526
2527    def _setResponsible( self, newRes ):
2528        self._responsible = newRes
2529
2530    def getResponsible( self ):
2531        return self._responsible
2532
2533    def getDate( self ):
2534        return self._date
2535
2536    def setDate(self, date):
2537        self._date = date
2538
2539    def getTrack( self ):
2540        return self._track
2541
2542    def setComment( self, newComment ):
2543        self._comment = newComment.strip()
2544
2545    def getComment( self ):
2546        return self._comment
2547
2548    def getAnswers(self):
2549        try:
2550            if self._answers:
2551                pass
2552        except AttributeError:
2553            self._answers = []
2554        return self._answers
2555
2556    def calculateJudgementAverage(self):
2557        '''Calculate the average value of the given answers'''
2558        result = 0
2559        if (len(self.getAnswers()) != 0):
2560            # convert the values into float types
2561            floatList = [ans.getValue() for ans in self._answers]
2562            result = sum(floatList) / float(len(floatList)) # calculate the average
2563        else:
2564            # there are no questions
2565            result = None
2566        return result
2567
2568    def getJudValue(self):
2569        try:
2570            if self._judValue:
2571                pass
2572        except AttributeError:
2573            self._judValue = self.calculateJudgementAverage() # judgement average value
2574        return self._judValue
2575
2576    def getTotalJudValue(self):
2577        try:
2578            if self._totalJudValue:
2579                pass
2580        except AttributeError:
2581            self._totalJudValue = self.calculateAnswersTotalValue()
2582        return self._totalJudValue
2583
2584    def calculateAnswersTotalValue(self):
2585        ''' Calculate the sum of all the ratings '''
2586        result = 0
2587        for ans in self.getAnswers():
2588            result += ans.getValue()
2589        return result
2590
2591    def recalculateJudgementValues(self, scaleLower, scaleHigher):
2592        ''' Update the values of the judgement. This function is called when the scale is changed.'''
2593        for ans in self.getAnswers():
2594            ans.calculateRatingValue(scaleLower, scaleHigher)
2595        self._judValue = self.calculateJudgementAverage()
2596        self._totalJudValue = self.calculateAnswersTotalValue()
2597
2598    def removeAnswer(self, questionId):
2599        ''' Remove the current answers of the questionId '''
2600        for ans in self.getAnswers():
2601            if ans.getQuestion().getId() == questionId:
2602                self._answers.remove(ans)
2603                self._notifyModification()
2604
2605    def _notifyModification(self):
2606        self._p_changed = 1
2607
2608
2609class AbstractAcceptance( AbstractJudgement ):
2610
2611    def __init__( self, track, responsible, contribType, answers ):
2612        AbstractJudgement.__init__( self, track, responsible, answers )
2613        self._contribType = contribType
2614
2615    def clone(self,track):
2616        aa = AbstractAcceptance(track,self.getResponsible(), self.getContribType(), self.getAnswers())
2617        return aa
2618
2619    def getContribType( self ):
2620        try:
2621            if self._contribType:
2622                pass
2623        except AttributeError, e:
2624            self._contribType = None
2625        return self._contribType
2626
2627
2628class AbstractRejection( AbstractJudgement ):
2629
2630    def clone(self, track):
2631        arj = AbstractRejection(track,self.getResponsible(), self.getAnswers())
2632        return arj
2633
2634class AbstractReallocation( AbstractJudgement ):
2635
2636    def __init__( self, track, responsible, propTracks, answers ):
2637        AbstractJudgement.__init__( self, track, responsible, answers )
2638        self._proposedTracks = PersistentList( propTracks )
2639
2640    def clone(self, track):
2641        arl = AbstractReallocation(track, self.getResponsible(), self.getProposedTrackList(), self.getAnswers())
2642        return arl
2643
2644    def getProposedTrackList( self ):
2645        return self._proposedTracks
2646
2647class AbstractMarkedAsDuplicated( AbstractJudgement ):
2648
2649    def __init__( self, track, responsible, originalAbst, answers ):
2650        AbstractJudgement.__init__( self, track, responsible, answers )
2651        self._originalAbst=originalAbst
2652
2653    def clone(self,track):
2654        amad = AbstractMarkedAsDuplicated(track,self.getResponsible(), self.getOriginalAbstract(), self.getAnswers())
2655        return amad
2656
2657    def getOriginalAbstract(self):
2658        return self._originalAbst
2659
2660
2661class AbstractUnMarkedAsDuplicated( AbstractJudgement ):
2662
2663    def clone(self,track):
2664        auad = AbstractUnMarkedAsDuplicated(track,self.getResponsible())
2665        return auad
2666
2667
2668class AbstractStatus( Persistent ):
2669    """This class represents any of the status in which an abstract can be.
2670        From the moment they are submitted (and therefore created), abstracts
2671        can go throuugh different status each having a different meaning.
2672       As there can be many status, the transitions between them are quite
2673        complex and as the system evolves we could require to add or delete
2674        new status the "Status" pattern is applied. This is the base class.
2675       Apart from giving information about the status of an abstract, this
2676        class is responsible to store information about how the status was
2677        reached (who provoke the transition, when, ...).
2678    """
2679    _name = ""
2680
2681    def __init__( self, abstract ):
2682        self._setAbstract( abstract )
2683        self._setDate( nowutc() )
2684
2685    def getName(self):
2686        return self._name
2687
2688    def _setAbstract( self, abs ):
2689        self._abstract = abs
2690
2691    def getAbstract( self ):
2692        return self._abstract
2693
2694    def _setDate( self, date ):
2695        self._date = date
2696
2697    def getDate( self ):
2698        return self._date
2699
2700    def accept(self,responsible,destTrack,type,comments=""):
2701        """
2702        """
2703        s = AbstractStatusAccepted(self.getAbstract(),responsible,destTrack,type,comments)
2704        self.getAbstract().setCurrentStatus( s )
2705
2706    def reject( self, responsible, comments = "" ):
2707        """
2708        """
2709        s = AbstractStatusRejected( self.getAbstract(), responsible, comments )
2710        self.getAbstract().setCurrentStatus( s )
2711
2712    def _getStatusClass( self ):
2713        """
2714        """
2715        numAccepts = self._abstract.getNumProposedToAccept() # number of tracks that have at least one proposal to accept
2716        numReallocate = self._abstract.getNumProposedToReallocate() # number of tracks that have at least one proposal to reallocate
2717        numJudgements = self._abstract.getNumJudgements() # number of tracks that have at least one judgement
2718        if numJudgements>0:
2719            numTracks = self._abstract.getNumTracks() # number of tracks that this abstract has assigned
2720            if numTracks == numJudgements: # Do we have judgements for all tracks?
2721                if numReallocate == numTracks:
2722                    s = AbstractStatusInConflict
2723                elif numAccepts == 1:
2724                    s = AbstractStatusProposedToAccept
2725                elif numAccepts == 0:
2726                    s = AbstractStatusProposedToReject
2727                else:
2728                    s = AbstractStatusInConflict
2729            else:
2730                s=AbstractStatusUnderReview
2731        else:
2732            s=AbstractStatusSubmitted
2733        return s
2734
2735
2736    def update( self ):
2737        """
2738        """
2739        newStatusClass = self._getStatusClass()
2740        if self.__class__ != newStatusClass:
2741            self.getAbstract().setCurrentStatus( newStatusClass( self._abstract ) )
2742
2743    def proposeToAccept( self ):
2744        """
2745        """
2746        s = self._getStatusClass()( self._abstract )
2747        self.getAbstract().setCurrentStatus( s )
2748
2749    def proposeToReject( self ):
2750        """
2751        """
2752        s = self._getStatusClass()( self._abstract )
2753        self.getAbstract().setCurrentStatus( s )
2754
2755    def proposeToReallocate( self ):
2756        """
2757        """
2758        s = self._getStatusClass()( self._abstract )
2759        self.getAbstract().setCurrentStatus( s )
2760
2761    def withdraw(self,resp,comments=""):
2762        """
2763        """
2764        s=AbstractStatusWithdrawn(self.getAbstract(), resp, self, comments)
2765        self.getAbstract().setCurrentStatus(s)
2766
2767    def recover( self ):
2768        """
2769        """
2770        raise MaKaCError( _("only withdrawn abstracts can be recovered"))
2771
2772    def markAsDuplicated(self,responsible,originalAbs,comments=""):
2773        """
2774        """
2775        if self.getAbstract()==originalAbs:
2776            raise MaKaCError( _("the original abstract is the same as the duplicated one"))
2777        if isinstance(originalAbs.getCurrentStatus(),AbstractStatusDuplicated):
2778            raise MaKaCError( _("cannot set as original abstract one which is already marked as duplicated"))
2779        s=AbstractStatusDuplicated(self.getAbstract(),responsible,originalAbs,comments)
2780        self.getAbstract().setCurrentStatus(s)
2781
2782    def unMarkAsDuplicated(self,responsible,comments=""):
2783        """
2784        """
2785        raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
2786
2787    def mergeInto(self,responsible,targetAbs,comments=""):
2788        """
2789        """
2790        if self.getAbstract()==targetAbs:
2791            raise MaKaCError( _("An abstract cannot be merged into itself"))
2792        if targetAbs.getCurrentStatus().__class__ not in [AbstractStatusSubmitted,AbstractStatusUnderReview,AbstractStatusProposedToAccept,AbstractStatusProposedToReject,AbstractStatusInConflict]:
2793            raise MaKaCError(_("Target abstract is in a status which cannot receive mergings"))
2794        s=AbstractStatusMerged(self.getAbstract(),responsible,targetAbs,comments)
2795        self.getAbstract().setCurrentStatus(s)
2796
2797    def unMerge(self,responsible,comments=""):
2798        """
2799        """
2800        raise MaKaCError( _("Only merged abstracts can be unmerged"))
2801
2802
2803
2804class AbstractStatusSubmitted( AbstractStatus ):
2805    """
2806    """
2807
2808    def clone(self,abstract):
2809        ass = AbstractStatusSubmitted(abstract)
2810        return ass
2811
2812    def update( self ):
2813        #if an abstract that has been submitted has no judgement it
2814        #   must remain in the submitted status
2815        if self._abstract.getNumJudgements() == 0:
2816            return
2817        AbstractStatus.update( self )
2818
2819
2820class AbstractStatusAccepted( AbstractStatus ):
2821    """
2822    """
2823    def __init__(self,abstract,responsible,destTrack,type,comments=""):
2824        AbstractStatus.__init__( self, abstract )
2825        self._setResponsible( responsible )
2826        self._setTrack( destTrack )
2827        self._setComments( comments )
2828        self._setType( type )
2829        self._contrib = None
2830
2831    def clone(self,abstract):
2832        asa = AbstractStatusAccepted(abstract,self.getResponsible(), self.getTrack(), self.getType(), self.getComments())
2833        return asa
2834
2835    def _setResponsible( self, res ):
2836        self._responsible = res
2837
2838    def getResponsible( self ):
2839        return self._responsible
2840
2841    def _setComments( self, comments ):
2842        self._comments = str( comments ).strip()
2843
2844    def getComments( self ):
2845        try:
2846            if self._comments:
2847                pass
2848        except AttributeError:
2849            self._comments = ""
2850        return self._comments
2851
2852    def _setTrack( self, track ):
2853        self._track = track
2854
2855    def getTrack( self ):
2856        try:
2857            if self._track:
2858                pass
2859        except AttributeError:
2860            self._track = None
2861        return self._track
2862
2863    def _setType( self, type ):
2864        self._contribType = type
2865
2866    def getType( self ):
2867        try:
2868            if self._contribType:
2869                pass
2870        except AttributeError:
2871            self._contribType = None
2872        return self._contribType
2873
2874    def setContribution( self, newContrib ):
2875        self._contrib = newContrib
2876
2877    def getContribution( self ):
2878        try:
2879            if self._contrib:
2880                pass
2881        except AttributeError:
2882            self._contrib = None
2883        return self._contrib
2884
2885    def update( self ):
2886        return
2887
2888    def accept(self,responsible,destTrack,type,comments="" ):
2889        raise MaKaCError( _("Cannot accept an abstract which is already accepted"))
2890
2891    def reject( self, responsible, comments="" ):
2892        raise MaKaCError( _("Cannot reject an abstract which is already accepted"))
2893
2894    def proposeToAccept( self ):
2895        raise MaKaCError( _("Cannot propose for acceptance an abstract which is already accepted"))
2896
2897    def proposeToReject( self ):
2898        raise MaKaCError( _("Cannot propose for rejection an abstract which is already accepted"))
2899
2900    def proposeToReallocate( self ):
2901        raise MaKaCError( _("Cannot propose for reallocation an abstract which is already accepted"))
2902
2903    #def withdraw( self, comments="" ):
2904    #    raise MaKaCError( "Cannot withdraw an ACCEPTED abstract" )
2905
2906    def markAsDuplicated(self,responsible,originalAbs,comments=""):
2907        raise MaKaCError( _("Cannot mark as duplicated an abstract which is accepted"))
2908
2909    def unMarkAsDuplicated(self,responsible,comments=""):
2910        """
2911        """
2912        raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
2913
2914    def mergeInto(self,responsible,targetAbs,comments=""):
2915        raise MaKaCError( _("Cannot merge an abstract which is already accepted"))
2916
2917    def withdraw(self,resp,comments=""):
2918        """
2919        """
2920        contrib=self.getContribution()
2921        #this import is made here and not at the top of the file in order to
2922        #   avoid recursive import troubles
2923        from MaKaC.conference import ContribStatusWithdrawn
2924        if contrib is not None and \
2925                not isinstance(contrib.getCurrentStatus(),ContribStatusWithdrawn):
2926            contrib.withdraw(resp, i18nformat(""" _("abstract withdrawn"): %s""")%comments)
2927        AbstractStatus.withdraw(self,resp,comments)
2928
2929
2930class AbstractStatusRejected( AbstractStatus ):
2931    """
2932    """
2933    def __init__( self, abstract, responsible, comments = "" ):
2934        AbstractStatus.__init__( self, abstract )
2935        self._setResponsible( responsible )
2936        self._setComments( comments )
2937
2938    def clone(self,abstract):
2939        asr = AbstractStatusRejected(abstract, self.getResponsible(), self.getComments())
2940        return asr
2941
2942    def _setResponsible( self, res ):
2943        self._responsible = res
2944
2945    def getResponsible( self ):
2946        return self._responsible
2947
2948    def _setComments( self, comments ):
2949        self._comments = str( comments ).strip()
2950
2951    def getComments( self ):
2952        try:
2953            if self._comments:
2954                pass
2955        except AttributeError:
2956            self._comments = ""
2957        return self._comments
2958
2959    def update( self ):
2960        return
2961
2962    def reject( self, responsible, comments="" ):
2963        raise MaKaCError(  _("Cannot reject an abstract which is already rejected"))
2964
2965    def proposeToAccept( self ):
2966        raise MaKaCError(  _("Cannot propose for acceptance an abstract which is already rejected"))
2967
2968    def proposeToReject( self ):
2969        raise MaKaCError(  _("Cannot propose for rejection an abstract which is already rejected"))
2970
2971    def proposeToReallocate( self ):
2972        raise MaKaCError(  _("Cannot propose for reallocation an abstract which is already rejected"))
2973
2974    def withdraw(self,resp,comments=""):
2975        raise MaKaCError(  _("Cannot withdraw a REJECTED abstract"))
2976
2977    def markAsDuplicated(self,responsible,originalAbs,comments=""):
2978        raise MaKaCError( _("Cannot mark as duplicated an abstract which is rejected"))
2979
2980    def unMarkAsDuplicated(self,responsible,comments=""):
2981        """
2982        """
2983        raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
2984
2985    def mergeInto(self,responsible,targetAbs,comments=""):
2986        raise MaKaCError( _("Cannot merge an abstract which is rejected"))
2987
2988
2989class AbstractStatusUnderReview( AbstractStatus ):
2990    """
2991    """
2992    def clone(self,abstract):
2993        asur = AbstractStatusUnderReview(abstract)
2994        return asur
2995
2996class AbstractStatusProposedToAccept( AbstractStatus ):
2997    """
2998    """
2999    def clone(self, abstract):
3000        aspta = AbstractStatusProposedToAccept(abstract)
3001        return aspta
3002
3003    def getTrack(self):
3004        jud=self.getAbstract().getTrackAcceptanceList()[0]
3005        return jud.getTrack()
3006
3007    def getType(self):
3008        jud=self.getAbstract().getTrackAcceptanceList()[0]
3009        return jud.getContribType()
3010
3011
3012class AbstractStatusProposedToReject( AbstractStatus ):
3013    """
3014    """
3015    def clone(self, abstract):
3016        asptr = AbstractStatusProposedToReject(abstract)
3017        return asptr
3018
3019class AbstractStatusInConflict( AbstractStatus ):
3020    """
3021    """
3022    def clone(self,abstract):
3023        asic = AbstractStatusInConflict(abstract)
3024        return asic
3025
3026class AbstractStatusWithdrawn(AbstractStatus):
3027    """
3028    """
3029    def __init__(self,abstract,responsible, prevStatus,comments=""):
3030        AbstractStatus.__init__(self,abstract)
3031        self._setComments(comments)
3032        self._setResponsible(responsible)
3033        self._prevStatus=prevStatus
3034
3035    def clone(self,abstract):
3036        asw = AbstractStatusWithdrawn(abstract,self.getResponsible(),self.getComments())
3037        return asw
3038
3039    def _setResponsible(self,newResp):
3040        self._responsible=newResp
3041
3042    def getResponsible(self):
3043        try:
3044            if self._responsible:
3045                pass
3046        except AttributeError,e:
3047            self._responsible=self._abstract.getSubmitter().getAvatar()
3048        return self._responsible
3049
3050    def getPrevStatus(self):
3051        try:
3052            if self._prevStatus:
3053                pass
3054        except AttributeError,e:
3055            self._prevStatus=None
3056        return self._prevStatus
3057
3058    def _setComments( self, comments ):
3059        self._comments = str( comments ).strip()
3060
3061    def getComments( self ):
3062        return self._comments
3063
3064    def update( self ):
3065        return
3066
3067    def accept(self,responsible,destTrack,type,comments=""):
3068        raise MaKaCError( _("Cannot accept an abstract wich is withdrawn"))
3069
3070    def reject( self, responsible, comments="" ):
3071        raise MaKaCError(  _("Cannot reject an abstract which is withdrawn"))
3072
3073    def proposeToAccept( self ):
3074        raise MaKaCError(  _("Cannot propose for acceptance an abstract which withdrawn"))
3075
3076    def proposeToReject( self ):
3077        raise MaKaCError(  _("Cannot propose for rejection an abstract which is withdrawn"))
3078
3079    def recover( self ):
3080        if self.getPrevStatus() is None:
3081            # reset all the judgments
3082            self._clearTrackAcceptances()
3083            self._clearTrackRejections()
3084            self._clearTrackReallocations()
3085            # setting the status
3086            contrib=self.getAbstract().getContribution()
3087            if contrib is None:
3088                s = AbstractStatusSubmitted( self.getAbstract() )
3089            else:
3090                s = AbstractStatusAccepted(self.getAbstract(),self.getResponsible(),contrib.getTrack(),contrib.getType(),"")
3091        else:
3092            contrib=self.getAbstract().getContribution()
3093            if contrib is not None and not isinstance(self.getPrevStatus(), AbstractStatusAccepted):
3094                s = AbstractStatusAccepted(self.getAbstract(),self.getResponsible(),contrib.getTrack(),contrib.getType(),"")
3095            else:
3096                s=self.getPrevStatus()
3097        self.getAbstract().setCurrentStatus( s )
3098
3099    def markAsDuplicated(self,responsible,originalAbs,comments=""):
3100        raise MaKaCError( _("Cannot mark as duplicated an abstract which is withdrawn"))
3101
3102    def unMarkAsDuplicated(self,responsible,comments=""):
3103        """
3104        """
3105        raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3106
3107    def mergeInto(self,responsible,targetAbs,comments=""):
3108        raise MaKaCError( _("Cannot merge an abstract which is withdrawn"))
3109
3110    def withdraw(self,resp,comments=""):
3111        raise MaKaCError( _("This abstract is already withdrawn"))
3112
3113
3114
3115class AbstractStatusDuplicated(AbstractStatus):
3116    """
3117    """
3118    def __init__( self,abstract,responsible,originalAbstract,comments=""):
3119        AbstractStatus.__init__(self,abstract)
3120        self._setResponsible(responsible)
3121        self._setComments(comments)
3122        self._setOriginalAbstract(originalAbstract)
3123
3124    def clone(self, abstract):
3125        asd = AbstractStatusDuplicated(abstract,self.getResponsible(),self.getOriginal(),self.getComments())
3126        return asd
3127
3128    def _setResponsible( self, res ):
3129        self._responsible = res
3130
3131    def getResponsible(self):
3132        return self._responsible
3133
3134    def _setComments( self, comments ):
3135        self._comments = str( comments ).strip()
3136
3137    def getComments( self ):
3138        return self._comments
3139
3140    def _setOriginalAbstract(self,abs):
3141        self._original=abs
3142
3143    def getOriginal(self):
3144        return self._original
3145
3146    def update( self ):
3147        return
3148
3149    def reject( self, responsible, comments="" ):
3150        raise MaKaCError( _("Cannot reject an abstract which is duplicated"))
3151
3152    def proposeToAccept( self ):
3153        raise MaKaCError( _("Cannot propose for acceptance an abstract which is duplicated"))
3154
3155    def proposeToReject( self ):
3156        raise MaKaCError( _("Cannot propose for rejection an abstract which is duplicated"))
3157
3158    def proposeToReallocate( self ):
3159        raise MaKaCError( _("Cannot propose for reallocation an abstract which is duplicated"))
3160
3161    def withdraw(self,resp,comments=""):
3162        raise MaKaCError( _("Cannot withdraw a duplicated abstract"))
3163
3164    def markAsDuplicated(self,responsible,originalAbs,comments=""):
3165        raise MaKaCError( _("This abstract is already duplicated"))
3166
3167    def unMarkAsDuplicated(self,responsible,comments=""):
3168        s = AbstractStatusSubmitted( self.getAbstract() )
3169        self.getAbstract().setCurrentStatus( s )
3170
3171    def mergeInto(self,responsible,targetAbs,comments=""):
3172        raise MaKaCError( _("Cannot merge an abstract which is marked as a duplicate"))
3173
3174
3175class AbstractStatusMerged(AbstractStatus):
3176    """
3177    """
3178
3179    def __init__(self,abstract,responsible,targetAbstract,comments=""):
3180        AbstractStatus.__init__(self,abstract)
3181        self._setResponsible(responsible)
3182        self._setComments(comments)
3183        self._setTargetAbstract(targetAbstract)
3184
3185    def clone(self,abstract):
3186        asm = AbstractStatusMerged(abstract,self.getResponsible(),self.getTargetAbstract(),self.getComments())
3187        return asm
3188
3189    def _setResponsible( self, res ):
3190        self._responsible = res
3191
3192    def getResponsible( self ):
3193        return self._responsible
3194
3195    def _setComments( self, comments ):
3196        self._comments = str( comments ).strip()
3197
3198    def getComments( self ):
3199        return self._comments
3200
3201    def _setTargetAbstract(self,abstract):
3202        self._target=abstract
3203
3204    def getTargetAbstract(self):
3205        return self._target
3206
3207    def update( self ):
3208        return
3209
3210    def reject( self, responsible, comments="" ):
3211        raise MaKaCError( _("Cannot reject an abstract which is merged into another one"))
3212
3213    def proposeToAccept( self ):
3214        raise MaKaCError( _("Cannot propose for acceptance an abstract which is merged into another one"))
3215
3216    def proposeToReject( self ):
3217        raise MaKaCError( _("Cannot propose for rejection an abstract which is merged into another one"))
3218
3219    def proposeToReallocate( self ):
3220        raise MaKaCError( _("Cannot propose for reallocation an abstract which is merged into another one"))
3221
3222    def withdraw(self,resp,comments=""):
3223        raise MaKaCError( _("Cannot withdraw an abstract which is merged into another one"))
3224
3225    def markAsDuplicated(self,responsible,originalAbs,comments=""):
3226        raise MaKaCError( _("Cannot mark as duplicated an abstract which is merged into another one"))
3227
3228    def unMarkAsDuplicated(self,responsible,comments=""):
3229        """
3230        """
3231        raise MaKaCError( _("Only duplicated abstract can be unmark as duplicated"))
3232
3233    def mergeInto(self,responsible,target,comments=""):
3234        raise MaKaCError( _("This abstract is already merged into another one"))
3235
3236    def unMerge(self,responsible,comments=""):
3237        s = AbstractStatusSubmitted( self.getAbstract() )
3238        self.getAbstract().setCurrentStatus( s )
3239
3240class AbstractStatusNone(AbstractStatus):
3241# This is a special status we assign to abstracts that are put in the trash can.
3242
3243    def __init__(self,abstract):
3244        AbstractStatus.__init__(self,abstract)
3245
3246    def clone(self,abstract):
3247        asn = AbstractStatusNone(abstract)
3248        return asn
3249
3250class NotificationTemplate(Persistent):
3251
3252    def __init__(self):
3253        self._owner=None
3254        self._id=""
3255        self._name=""
3256        self._description=""
3257        self._tplSubject=""
3258        self._tplBody=""
3259        self._fromAddr = ""
3260        self._CAasCCAddr = False
3261        self._ccAddrList=PersistentList()
3262        self._toAddrs = PersistentList()
3263        self._conditions=PersistentList()
3264        self._toAddrGenerator=Counter()
3265        self._condGenerator=Counter()
3266
3267    def clone(self):
3268        tpl = NotificationTemplate()
3269        tpl.setName(self.getName())
3270        tpl.setDescription(self.getDescription())
3271        tpl.setTplSubject(self.getTplSubject())
3272        tpl.setTplBody(self.getTplBody())
3273        tpl.setFromAddr(self.getFromAddr())
3274        tpl.setCAasCCAddr(self.getCAasCCAddr())
3275
3276        for cc in self.getCCAddrList() :
3277            tpl.addCCAddr(cc)
3278        for to in self.getToAddrList() :
3279            tpl.addToAddr(to)
3280
3281        for con in self.getConditionList() :
3282            tpl.addCondition(con.clone(tpl))
3283
3284        # kto to jest OWNER..??
3285        # bo nie konferencja..!!
3286
3287        return tpl
3288
3289    def delete(self):
3290        self.clearToAddrs()
3291        self.clearCCAddrList()
3292        self.clearConditionList()
3293        TrashCanManager().add(self)
3294
3295    def recover(self):
3296        TrashCanManager().remove(self)
3297
3298##    def getResponsible( self ):
3299##        return self._responsible
3300##
3301##    def _setComments( self, comments ):
3302##        self._comments = str( comments ).strip()
3303##
3304##    def getComments( self ):
3305##        return self._comments
3306##
3307##    def _setOriginalAbstract(self,abstract):
3308##        self._original=abstract
3309
3310    def canModify(self, aw):
3311        return self.getConference().canModify(aw)
3312
3313    def getLocator(self):
3314        loc = self.getOwner().getConference().getLocator()
3315        loc["notifTplId"] = self._id
3316        return loc
3317
3318    def getConference(self):
3319        return self._owner.getConference()
3320
3321    def includeInOwner(self,owner,id):
3322        self._owner=owner
3323        self._id=id
3324
3325    def getOwner(self):
3326        return self._owner
3327
3328    def getId(self):
3329        return self._id
3330
3331    def setName(self,newName):
3332        self._name=newName.strip()
3333
3334    def getName(self):
3335        return self._name
3336
3337    def setDescription(self,newDesc):
3338        self._description=newDesc.strip()
3339
3340    def getDescription(self):
3341        return self._description
3342
3343    def setTplSubject(self,newSubject, varList):
3344        self._tplSubject=self.parseTplContent(newSubject, varList).strip()
3345
3346    def getTplSubject(self):
3347        return self._tplSubject
3348
3349    def getTplSubjectShow(self, varList):
3350        return self.parseTplContentUndo(self._tplSubject, varList)
3351
3352    def setTplBody(self,newBody, varList):
3353        self._tplBody=self.parseTplContent(newBody, varList).strip()
3354
3355    def getTplBody(self):
3356        return self._tplBody
3357
3358    def getTplBodyShow(self, varList):
3359        return self.parseTplContentUndo(self._tplBody, varList)
3360
3361    def getCCAddrList(self):
3362        try:
3363            if self._ccAddrList:
3364                pass
3365        except AttributeError:
3366            self._ccAddrList=PersistentList()
3367        return self._ccAddrList
3368
3369    def addCCAddr(self,newAddr):
3370        try:
3371            if self._ccAddrList:
3372                pass
3373        except AttributeError:
3374            self._ccAddrList=PersistentList()
3375        ccAddr=newAddr.strip()
3376        if ccAddr!="" and ccAddr not in self._ccAddrList:
3377            self._ccAddrList.append(ccAddr)
3378
3379    def setCCAddrList(self,l):
3380        self.clearCCAddrList()
3381        for addr in l:
3382            self.addCCAddr(addr)
3383
3384    def setCAasCCAddr(self, CAasCCAddr):
3385        self._CAasCCAddr = CAasCCAddr
3386
3387    def getCAasCCAddr(self):
3388        try:
3389            if self._CAasCCAddr:
3390                pass
3391        except AttributeError:
3392            self._CAasCCAddr = False
3393        return self._CAasCCAddr
3394
3395    def clearCCAddrList(self):
3396        self._ccAddrList=PersistentList()
3397
3398    def getFromAddr(self):
3399        try:
3400            return self._fromAddr
3401        except AttributeError:
3402            self._fromAddr = self._owner.getConference().getSupportEmail()
3403            return self._fromAddr
3404
3405    def setFromAddr(self, addr):
3406        self._fromAddr = addr
3407
3408    def addToAddr(self,toAddr):
3409        """
3410        """
3411        if self.hasToAddr(toAddr.__class__):
3412            return
3413        try:
3414            if self._toAddrGenerator:
3415                pass
3416        except AttributeError, e:
3417            self._toAddrGenerator =  Counter()
3418        id = toAddr.getId()
3419        if id == -1:
3420            id = int(self._toAddrGenerator.newCount())
3421        toAddr.includeInTpl(self,id)
3422        self.getToAddrList().append(toAddr)
3423
3424    def removeToAddr(self,toAddr):
3425        """
3426        """
3427        if not self.hasToAddr(toAddr.__class__):
3428            return
3429        self.getToAddrList().remove(toAddr)
3430        toAddr.includeInTpl(None,toAddr.getId())
3431        toAddr.delete()
3432
3433    def recoverToAddr(self, toAddr):
3434        self.addToAddr(toAddr)
3435        toAddr.recover()
3436
3437    def getToAddrs(self, abs):
3438        users = []
3439        for toAddr in self.getToAddrList():
3440            users += toAddr.getToAddrList(abs)
3441        return users
3442
3443    def getToAddrList(self):
3444        """
3445        """
3446        try:
3447            if self._toAddrs:
3448                pass
3449        except AttributeError, e:
3450            self._toAddrs = PersistentList()
3451        return self._toAddrs
3452
3453    def getToAddrById(self,id):
3454        """
3455        """
3456        for toAddr in self.getToAddrList():
3457            if toAddr.getId()==int(id):
3458                return toAddr
3459        return None
3460
3461    def hasToAddr(self,toAddrKlass):
3462        """Returns True if the TPL contains a "toAddr" which class is "toAddrKlass"
3463        """
3464        for toAddr in self.getToAddrList():
3465            if toAddr.__class__ == toAddrKlass:
3466                return True
3467        return False
3468
3469    def clearToAddrs(self):
3470        while(len(self.getToAddrList())>0):
3471            self.removeToAddr(self.getToAddrList()[0])
3472
3473    def addCondition(self,cond):
3474        """
3475        """
3476        if cond in self._conditions:
3477            return
3478        id = cond.getId()
3479        if id == -1:
3480            id = int(self._condGenerator.newCount())
3481        cond.includeInTpl(self, id)
3482        self._conditions.append(cond)
3483
3484    def removeCondition(self,cond):
3485        """
3486        """
3487        if cond not in self._conditions:
3488            return
3489        self._conditions.remove(cond)
3490        cond.delete()
3491
3492    def recoverCondition(self, cond):
3493        self.addCondition(cond)
3494        cond.recover()
3495
3496    def getConditionList(self):
3497        """
3498        """
3499        return self._conditions
3500
3501    def getConditionById(self,id):
3502        """
3503        """
3504        for cond in self._conditions:
3505            if cond.getId()==int(id):
3506                return cond
3507        return None
3508
3509    def clearConditionList(self):
3510        while(len(self.getConditionList())>0):
3511            self.removeCondition(self.getConditionList()[0])
3512
3513    def satisfies(self,abs):
3514        """
3515        """
3516        for cond in self._conditions:
3517            if cond.satisfies(abs):
3518                return True
3519        return False
3520
3521    def parseTplContent(self, content, varList):
3522        # replace the % in order to avoid exceptions
3523        result = content.replace("%", "%%")
3524        # find the vars and make the expressions, it is necessary to do in reverse in order to find the longest tags first
3525        for var in varList:
3526            result = result.replace("{"+var.getName()+"}", "%("+var.getName()+")s")
3527        return result
3528
3529    def parseTplContentUndo(self, content, varList):
3530        # The body content is shown without "%()" and with "%" in instead of "%%" but it is not modified
3531        result = content
3532        for var in varList:
3533            result = result.replace("%("+var.getName()+")s", "{"+var.getName()+"}")
3534        # replace the %% by %
3535        result = result.replace("%%", "%")
3536        return result
3537
3538    def getModifKey( self ):
3539        return self.getConference().getModifKey()
3540
3541
3542
3543class NotifTplToAddr(Persistent):
3544    """
3545    """
3546
3547    def __init__(self):
3548        self._tpl=None
3549        self._id=-1
3550
3551    def clone(self):
3552        ntta = NotifTplToAddr()
3553        return ntta
3554
3555    def delete(self):
3556        TrashCanManager().add(self)
3557
3558    def recover(self):
3559        TrashCanManager().remove(self)
3560
3561    def includeInTpl(self,newTpl,newId):
3562        self._tpl=newTpl
3563        self._id=newId
3564
3565    def getTpl(self):
3566        return self._tpl
3567
3568    def getId(self):
3569        return self._id
3570
3571    def getToAddrList(self,absList):
3572        """
3573        Return a list with all the emails for a group.
3574        """
3575        return []
3576
3577
3578class NotifTplToAddrSubmitter(NotifTplToAddr):
3579
3580    def getToAddrList(self,abs):
3581        l = []
3582        l.append(abs.getSubmitter())
3583        return l
3584
3585    def clone(self):
3586        nttas = NotifTplToAddrSubmitter()
3587        return nttas
3588
3589class NotifTplToAddrPrimaryAuthors(NotifTplToAddr):
3590
3591    def getToAddrList(self,abs):
3592        l = []
3593        for pa in abs.getPrimaryAuthorList():
3594            l.append(pa)
3595        return l
3596
3597    def clone(self):
3598        nttapa = NotifTplToAddrPrimaryAuthors()
3599        return nttapa
3600
3601class NotifTplCondition(Persistent):
3602    """
3603    """
3604
3605    def __init__(self):
3606        self._tpl=None
3607        self._id=-1
3608
3609    def clone(self, template):
3610        con = NotifyCondition()
3611        con.includeInTpl(template)
3612        return con
3613
3614    def delete(self):
3615        TrashCanManager().add(self)
3616
3617    def recover(self):
3618        TrashCanManager().remove(self)
3619
3620    def includeInTpl(self,newTpl,newId):
3621        self._tpl=newTpl
3622        self._id=newId
3623
3624    def getTpl(self):
3625        return self._tpl
3626
3627    def getId(self):
3628        return self._id
3629
3630    def satisfies(self,abs):
3631        return True
3632
3633
3634class NotifTplCondAccepted(NotifTplCondition):
3635
3636    def __init__(self,track="--any--",contribType="--any--"):
3637        NotifTplCondition.__init__(self)
3638        self._track=track
3639        self._contribType=contribType
3640
3641    def clone(self, conference, template):
3642        ntca = NotifTplCondAccepted()
3643        for newtrack in conference.getTrackList() :
3644            if newtrack.getTitle() == self.getTrack().getTitle() :
3645                ntca.setTrack(newtrack)
3646        for newtype in conference.getContribTypeList() :
3647             if newtype.getName() == self.getContribType() :
3648                ntca.setContribType(newtype)
3649
3650        return ntca
3651
3652    def setContribType(self, ct="--any--"):
3653        self._contribType = ct
3654
3655    def getContribType(self):
3656        return self._contribType
3657
3658    def setTrack(self, tr="--any--"):
3659        self._track = tr
3660
3661    def getTrack(self):
3662        try:
3663            if self._track:
3664                pass
3665        except AttributeError:
3666            self._track="--any--"
3667        return self._track
3668
3669    def _satifiesContribType(self,abs):
3670        status=abs.getCurrentStatus()
3671        if self._contribType=="--any--":
3672            return True
3673        else:
3674            if self._contribType=="" or self._contribType==None or \
3675                                            self._contribType=="--none--":
3676                return status.getType()=="" or status.getType()==None
3677            return status.getType()==self._contribType
3678        return False
3679
3680    def _satifiesTrack(self,abs):
3681        status=abs.getCurrentStatus()
3682        if self.getTrack()=="--any--":
3683            return True
3684        else:
3685            if self.getTrack()=="" or self.getTrack() is None or \
3686                                            self.getTrack()=="--none--":
3687                return status.getTrack()=="" or status.getTrack()==None
3688            return status.getTrack()==self.getTrack()
3689        return False
3690
3691    def satisfies(self,abs):
3692        if not isinstance(abs.getCurrentStatus(),AbstractStatusAccepted):
3693            return False
3694        else:
3695            return self._satifiesContribType(abs) and self._satifiesTrack(abs)
3696
3697
3698class NotifTplCondRejected(NotifTplCondition):
3699
3700    def satisfies(self,abs):
3701        return isinstance(abs.getCurrentStatus(),AbstractStatusRejected)
3702
3703    def clone(self, conference, template):
3704        ntcr = NotifTplCondRejected()
3705        ntcr.includeInTpl(template)
3706        return ntcr
3707
3708class NotifTplCondMerged(NotifTplCondition):
3709
3710    def satisfies(self,abs):
3711        return isinstance(abs.getCurrentStatus(),AbstractStatusMerged)
3712
3713    def clone(self, conference, template):
3714        ntcm = NotifTplCondMerged()
3715        ntcm.includeInTpl(newTpl, newId)
3716
3717class NotificationLog(Persistent):
3718
3719    def __init__(self,abstract):
3720        self._abstract=abstract
3721        self._entries=PersistentList()
3722
3723    def getAbstract(self):
3724        return self._abstract
3725
3726    def addEntry(self,newEntry):
3727        if newEntry!=None and newEntry not in self._entries:
3728            self._entries.append(newEntry)
3729
3730    def getEntryList(self):
3731        return self._entries
3732
3733    # The 3 following metods are used only for recovery purposes:
3734
3735    def removeEntry(self, entry):
3736        if entry!=None and entry in self._entries:
3737            self._entries.remove(entry)
3738            entry.delete()
3739
3740    def recoverEntry(self, entry):
3741        self.addEntry(entry)
3742        entry.recover()
3743
3744    def clearEntryList(self):
3745        while len(self.getEntryList()) > 0:
3746            self.removeEntry(self.getEntryList()[0])
3747
3748    # -----------------------------------------------------------
3749
3750class NotifLogEntry(Persistent):
3751
3752    def __init__(self,responsible,tpl):
3753        self._setDate(nowutc())
3754        self._setResponsible(responsible)
3755        self._setTpl(tpl)
3756
3757    def _setDate(self,newDate):
3758        self._date=newDate
3759
3760    def getDate(self):
3761        return self._date
3762
3763    def _setResponsible(self,newResp):
3764        self._responsible=newResp
3765
3766    def getResponsible(self):
3767        return self._responsible
3768
3769    def _setTpl(self,newTpl):
3770        self._tpl=newTpl
3771
3772    def getTpl(self):
3773        return self._tpl
3774
3775    def delete(self):
3776        TrashCanManager().add(self)
3777
3778    def recover(self):
3779        TrashCanManager().remove(self)
Note: See TracBrowser for help on using the repository browser.