VC: Fix error on clone page for legacy-ID events
[cds-indico.git] / indico / MaKaC / schedule.py
blob8aad7c52187f0fb087b1eb6864acd5d4ba0fda34
1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 """
18 """
19 import copy
20 from persistent import Persistent
21 from datetime import datetime,timedelta
22 from MaKaC.common.Counter import Counter
23 from MaKaC.errors import MaKaCError, TimingError, ParentTimingError,\
24 EntryTimingError
25 from MaKaC.common import utils
26 from MaKaC.trashCan import TrashCanManager
27 from MaKaC.i18n import _
28 from pytz import timezone
29 from indico.util.date_time import iterdays
30 from MaKaC.common.Conversion import Conversion
31 from MaKaC.common.contextManager import ContextManager
32 from MaKaC.common.fossilize import Fossilizable, fossilizes
33 from MaKaC.fossils.schedule import IContribSchEntryDisplayFossil,\
34 IContribSchEntryMgmtFossil, IBreakTimeSchEntryFossil,\
35 IBreakTimeSchEntryMgmtFossil,\
36 ILinkedTimeSchEntryDisplayFossil, ILinkedTimeSchEntryMgmtFossil
37 from MaKaC.common.cache import GenericCache
38 from MaKaC.errors import NoReportError
39 from indico.util.decorators import classproperty
42 class Schedule:
43 """base schedule class. Do NOT instantiate
44 """
46 def __init__( self, owner ):
47 pass
49 def getEntries( self ):
50 return []
52 def addEntry( self, entry, position=None ):
53 return
55 def removeEntry( self, entry ):
56 return
58 def getEntryPosition( self, entry ):
59 return
61 def moveEntry( self, entry, newPosition, after=1 ):
62 return
64 def getEntryLocator( self, entry ):
65 return
67 def getOwner( self ):
68 return
70 def reSchedule( self ):
71 return
73 def getEntryInPos( self, pos ):
74 return None
77 class TimeSchedule(Schedule, Persistent):
78 """
79 """
81 def __init__(self,owner):
82 self._entries=[]
83 self._owner=owner
84 self._entryGen=Counter()
85 self._allowParallel=True
87 def notifyModification(self):
88 self.getOwner().notifyModification()
90 def getEntries( self ):
91 return self._entries
93 def hasEntriesBefore(self,d):
94 """Tells wether there is any entry before the specified date
95 """
96 entries=self.getEntries()
97 if len(entries)==0:
98 return False
99 return entries[0].getStartDate()<d
101 def hasEntriesAfter(self,d):
102 """Tells wether there is any entry after the specified date
104 entries=self.getEntries()
105 if len(entries)==0:
106 return False
107 return self.calculateEndDate()>d
109 def checkSanity( self ):
110 if self.hasEntriesBefore(self.getStartDate()) or self.hasEntriesAfter(self.getEndDate()):
111 raise TimingError("Sorry, cannot perform this date change as some entries in the timetable would fall outside the new dates.")
113 def isOutside(self,entry):
114 """Tells whether an entry is outside the date boundaries of the schedule
116 ######################################
117 # Fermi timezone awareness #
118 ######################################
119 if entry.getStartDate() is not None:
120 if entry.getStartDate()<self.getStartDate('UTC') or \
121 entry.getStartDate()>self.getEndDate('UTC'):
122 return True
123 if entry.getEndDate() is not None:
124 if entry.getEndDate()<self.getStartDate('UTC') or \
125 entry.getEndDate()>self.getEndDate('UTC'):
126 return True
127 return False
129 def hasEntry(self,entry):
130 return entry.isScheduled() and entry.getSchedule()==self and\
131 entry in self._entries
133 def _addEntry(self,entry,check=2):
134 """check parameter:
135 0: no check at all
136 1: check and raise error in case of problem
137 2: check and adapt the owner dates"""
138 if entry.isScheduled():
139 # remove it from the old schedule and add it to this one
140 entry.getSchedule().removeEntry(entry)
142 owner = self.getOwner()
144 tz = owner.getConference().getTimezone()
146 # If user has entered start date use these dates
147 # if the entry has not a pre-defined start date we try to find a place
148 # within the schedule to allocate it
149 if entry.getStartDate() is None:
150 sDate=self.findFirstFreeSlot(entry.getDuration())
151 if sDate is None:
152 if check==2:
153 newEndDate = self.getEndDate() + entry.getDuration()
155 ContextManager.get('autoOps').append((owner,
156 "OWNER_END_DATE_EXTENDED",
157 owner,
158 newEndDate.astimezone(timezone(tz))))
160 owner.setEndDate(newEndDate, check)
161 sDate = self.findFirstFreeSlot(entry.getDuration())
162 if sDate is None:
163 raise ParentTimingError( _("There is not enough time found to add this entry in the schedule (duration: %s)")%entry.getDuration(), _("Add Entry"))
164 entry.setStartDate(sDate)
165 #if the entry has a pre-defined start date we must make sure that it is
166 # not outside the boundaries of the schedule
167 else:
168 if entry.getStartDate() < self.getStartDate('UTC'):
169 if check==1:
170 raise TimingError( _("Cannot schedule this entry because its start date (%s) is before its parents (%s)")%(entry.getAdjustedStartDate(),self.getAdjustedStartDate()),_("Add Entry"))
171 elif check == 2:
172 ContextManager.get('autoOps').append((owner,
173 "OWNER_START_DATE_EXTENDED",
174 owner,
175 entry.getAdjustedStartDate(tz=tz)))
176 owner.setStartDate(entry.getStartDate(),check)
177 elif entry.getEndDate()>self.getEndDate('UTC'):
178 if check==1:
179 raise TimingError( _("Cannot schedule this entry because its end date (%s) is after its parents (%s)")%(entry.getAdjustedEndDate(),self.getAdjustedEndDate()),_("Add Entry"))
180 elif check == 2:
181 ContextManager.get('autoOps').append((owner,
182 "OWNER_END_DATE_EXTENDED",
183 owner,
184 entry.getAdjustedEndDate(tz=tz)))
185 owner.setEndDate(entry.getEndDate(),check)
186 #we make sure the entry end date does not go outside the schedule
187 # boundaries
188 if entry.getEndDate() is not None and \
189 (entry.getEndDate()<self.getStartDate('UTC') or \
190 entry.getEndDate()>self.getEndDate('UTC')):
191 raise TimingError( _("Cannot schedule this entry because its end date (%s) is after its parents (%s)")%(entry.getAdjustedEndDate(),self.getAdjustedEndDate()), _("Add Entry"))
192 self._entries.append(entry)
193 entry.setSchedule(self,self._getNewEntryId())
194 self.reSchedule()
195 self._cleanCache(entry)
196 self._p_changed = 1
198 def _setEntryDuration(self,entry):
199 if entry.getDuration() is None:
200 entry.setDuration(0,5)
202 def addEntry(self,entry):
203 if (entry is None) or self.hasEntry(entry):
204 return
205 self._setEntryDuration(entry)
206 result = self._addEntry(entry)
207 self._p_changed = 1
208 return result
210 def _removeEntry(self,entry):
211 self._cleanCache(entry)
212 self._entries.remove(entry)
213 entry.setSchedule(None,"")
214 entry.setStartDate(None)
215 entry.delete()
216 self._p_changed = 1
218 def _cleanCache(self, entry):
219 if isinstance(entry, ContribSchEntry):
220 entry.getOwner().cleanCache()
221 self.getOwner().cleanCache()
222 elif isinstance(entry, BreakTimeSchEntry):
223 self.getOwner().cleanCache()
224 ScheduleToJson.cleanCache(entry, False)
225 else:
226 entry.getOwner().cleanCache()
228 def removeEntry(self,entry):
229 if entry is None or not self.hasEntry(entry):
230 return
231 self._removeEntry(entry)
233 def getEntryPosition( self, entry ):
234 return self._entries.index( entry )
236 def getOwner( self ):
237 return self._owner
239 ####################################
240 # Fermi timezone awareness #
241 ####################################
243 def getStartDate( self ,tz='UTC'):
244 return self.getOwner().getAdjustedStartDate(tz)
246 def getAdjustedStartDate( self, tz=None ):
247 return self.getOwner().getAdjustedStartDate(tz)
249 def getEndDate( self, tz='UTC'):
250 return self.getOwner().getAdjustedEndDate(tz)
252 def getAdjustedEndDate( self, tz=None):
253 return self.getOwner().getAdjustedEndDate(tz)
255 ####################################
256 # Fermi timezone awareness(end) #
257 ####################################
259 def cmpEntries(self,e1,e2):
260 datePrecedence = cmp(e1.getStartDate(), e2.getStartDate())
262 # if we're tied, let's use duration as a criterion
263 # this keeps zero-duration breaks from making it get stuck
264 if datePrecedence == 0:
265 return cmp(e1.getDuration(), e2.getDuration())
266 else:
267 return datePrecedence
269 def reSchedule(self):
270 try:
271 if self._allowParalell:
272 pass
273 except AttributeError:
274 self._allowParallel=True
275 self._entries.sort(self.cmpEntries)
276 lastEntry=None
277 for entry in self._entries:
278 if lastEntry is not None:
279 if not self._allowParallel:
280 if lastEntry.collides(entry):
281 entry.setStartDate(lastEntry.getEndDate())
282 lastEntry=entry
283 self._p_changed = 1
285 def calculateEndDate( self ):
286 if len(self._entries) == 0:
287 return self.getStartDate()
288 eDate = self.getStartDate()
289 for entry in self._entries:
290 if entry.getEndDate()>eDate:
291 eDate = entry.getEndDate()
292 return eDate
294 def calculateStartDate( self ):
295 if len(self._entries) == 0:
296 return self.getStartDate()
297 else:
298 return self._entries[0].getStartDate()
300 def getTimezone( self ):
301 return self.getOwner().getConference().getTimezone()
303 def getFirstFreeSlotOnDay(self,day):
304 if not day.tzinfo:
305 day = timezone(self.getTimezone()).localize(day)
306 tz = day.tzinfo
307 entries = self.getEntriesOnDay(day)
308 if len(entries)==0:
309 if self.getStartDate().astimezone(tz).date() == day.date():
310 return self.getStartDate().astimezone(tz)
311 return day.astimezone(timezone(self.getTimezone())).replace(hour=8,minute=0).astimezone(tz)
312 else:
313 return self.calculateDayEndDate(day)
315 def calculateDayEndDate(self,day,hour=0,min=0):
316 if day is None:
317 return self.calculateEndDate()
318 if not day.tzinfo:
319 day = timezone(self.getTimezone()).localize(day)
320 tz = day.tzinfo
321 maxDate=day.replace(hour=hour,minute=min)
322 entries = self.getEntriesOnDay(day)
323 if hour != 0 or min != 0:
324 return maxDate
325 elif len(entries)==0:
326 confstime = self.getOwner().getAdjustedStartDate()
327 return day.astimezone(timezone(self.getTimezone())).replace(hour=confstime.hour,minute=confstime.minute).astimezone(tz)
328 else:
329 for entry in entries:
330 if entry.getEndDate()>maxDate:
331 maxDate=entry.getEndDate().astimezone(tz)
332 if maxDate.date() != day.date():
333 maxDate = day.replace(hour=23,minute=59)
334 return maxDate
336 def calculateDayStartDate( self, day ):
338 # This determines where the times start on the time table.
339 # day is a tz aware datetime
340 if not day.tzinfo:
341 day = timezone(self.getTimezone()).localize(day)
342 tz = day.tzinfo
343 for entry in self.getEntries():
344 if entry.inDay( day ):
345 if entry.getStartDate().astimezone(tz).date() >= day.date():
346 return entry.getStartDate().astimezone(tz)
347 else:
348 return day.replace(hour=0,minute=0)
349 return timezone(self.getTimezone()).localize(datetime(day.year,day.month,day.day,8,0)).astimezone(tz)
351 def getEntryInPos( self, pos ):
352 try:
353 return self.getEntries()[int(pos)]
354 except IndexError:
355 return None
357 def getEntriesOnDay( self, day ):
358 """Returns a list containing all the entries which occur whithin the
359 specified day. These entries will be ordered descending.
361 if not day.tzinfo:
362 day = timezone(self.getTimezone()).localize(day)
363 res = []
364 for entry in self.getEntries():
365 if entry.inDay( day ):
366 res.append( entry )
367 return res
369 def getEntriesOnDate( self, date ):
370 """Returns a list containing all the entries which occur whithin the
371 specified day. These entries will be ordered descending.
373 res = []
374 for entry in self.getEntries():
375 if entry.onDate( date ):
376 res.append( entry )
377 return res
379 def _getNewEntryId(self):
380 try:
381 if self._entryGen:
382 pass
383 except AttributeError:
384 self._entryGen=Counter()
385 return str(self._entryGen.newCount())
387 def getEntryById(self,id):
388 for entry in self.getEntries():
389 if entry.getId()==str(id).strip():
390 return entry
391 return None
393 def hasGap(self):
394 """check if schedule has gap between two entries"""
395 entries = self.getEntries()
396 if len(entries) > 1:
397 sDate = self.getStartDate('UTC')
398 for entry in entries:
399 if entry.getStartDate()!=sDate:
400 return True
401 sDate = entry.getEndDate()
402 return False
404 def compact(self):
405 """removes any overlaping among schedule entries and make them go one
406 after the other without any gap
408 refDate=self.getStartDate('UTC')
409 for entry in self._entries:
410 entry.setStartDate(refDate)
411 refDate=entry.getEndDate()
413 def moveUpEntry(self,entry,tz=None):
414 pass
416 def moveDownEntry(self,entry,tz=None):
417 pass
419 def rescheduleTimes(self, type, diff, day, doFit):
421 recalculate and reschedule the entries of the event with a time "diff" of separation.
424 from MaKaC.conference import SessionSlot
425 entries = self.getEntriesOnDay(day)
426 if type == "duration":
427 i = 0
428 while i < len(entries):
429 entry = entries[i]
430 if isinstance(entry.getOwner(), SessionSlot) and entry.getOwner().getSession().isClosed():
431 raise EntryTimingError(_("""The modification of the session "%s" is not allowed because it is closed""") % entry.getOwner().getSession().getTitle())
433 if doFit:
434 if isinstance(entry.getOwner(), SessionSlot):
435 entry.getOwner().fit()
436 if i + 1 == len(entries):
437 dur = entry.getDuration()
438 else:
439 nextentry = entries[i + 1]
440 dur = nextentry.getStartDate() - entry.getStartDate() - diff
441 if dur < timedelta(0):
442 raise EntryTimingError( _("""With the time between entries you've chosen, the entry "%s" will have a duration less than zero minutes. Please, choose another time""") % entry.getTitle())
443 entry.setDuration(dur=dur, check=2)
444 i += 1
445 elif type == "startingTime":
446 st = day.replace(hour=self.getAdjustedStartDate().hour, minute=self.getAdjustedStartDate().minute).astimezone(timezone('UTC'))
447 for entry in entries:
448 if isinstance(entry.getOwner(), SessionSlot) and entry.getOwner().getSession().isClosed():
449 raise EntryTimingError(_("""The modification of the session "%s" is not allowed because it is closed""") % entry.getOwner().getSession().getTitle())
450 if doFit:
451 if isinstance(entry.getOwner(), SessionSlot):
452 entry.getOwner().fit()
453 entry.setStartDate(st, check=2, moveEntries=1)
454 st = entry.getEndDate() + diff
455 elif type == "noAction" and doFit:
456 for entry in entries:
457 if isinstance(entry.getOwner(), SessionSlot):
458 if entry.getOwner().getSession().isClosed():
459 raise EntryTimingError(_("""The modification of the session "%s" is not allowed because it is closed""") % entry.getOwner().getSession().getTitle())
460 entry.getOwner().fit()
462 def clear(self):
463 while len(self._entries)>0:
464 self._removeEntry(self._entries[0])
465 self._p_changed = 1
468 def findFirstFreeSlot(self,reqDur=None):
469 """Tries to find the first free time slot available where an entry with
470 the specified duration could be placed
472 d=self.getStartDate('UTC')
473 for entry in self.getEntries():
474 availDur=entry.getStartDate()-d
475 if availDur!=0:
476 if reqDur is not None and reqDur!=0:
477 if reqDur<=availDur:
478 return d
479 else:
480 return d
481 d=entry.getEndDate()
482 availDur=self.getEndDate()-d
483 if availDur!=0:
484 if reqDur is not None and reqDur!=0:
485 if reqDur<=availDur:
486 return d
487 else:
488 return d
489 return None
491 def moveEntriesBelow(self, diff, entriesList, check=2):
492 """diff: the difference we have to increase/decrease each entry of the list.
493 entriesList: list of entries for applying the diff"""
495 if diff is not None:
496 from MaKaC.conference import SessionSlot
497 sessionsAlreadyModif = []
498 for entry in entriesList:
499 if isinstance(entry.getOwner(), SessionSlot):
500 session = entry.getOwner().getSession()
501 if session not in sessionsAlreadyModif:
502 # if the slot is the first in the session schedule
503 # we also change the session start date
504 if session.getSchedule().getEntries()[0].getOwner() == entry.getOwner():
505 session.setStartDate(session.getStartDate() + diff, check=0, moveEntries=0)
506 sessionsAlreadyModif.append(session)
507 entry.setStartDate(entry.getStartDate() + diff, check=check, moveEntries=1)
510 class SchEntry(Persistent, Fossilizable):
511 """base schedule entry class. Do NOT instantiate
514 def __init__(self):
515 self._sch=None
516 self.title = ""
517 self.description = ""
518 self._id=""
520 def getId(self):
521 try:
522 if self._id:
523 pass
524 except AttributeError:
525 self._id=str(self.getSchedule()._getNewEntryId())
526 return self._id
528 def notifyModification(self):
529 if self.getSchedule():
530 self.getSchedule().notifyModification()
532 def setSchedule(self,sch,id):
533 if self.getSchedule() is not None:
534 self.getSchedule().removeEntry(self)
535 self._sch=sch
536 self._id=str(id)
537 if self.getSchedule() is not None:
538 sch.addEntry(self)
540 def getSchedule(self):
541 try:
542 return self._sch
543 except:
544 self._sch = None
545 return self._sch
547 def isScheduled(self):
548 return self.getSchedule() is not None
550 def setValues( self, data ):
551 """Sets all the values of the current schedule entry object from a
552 dictionary containing the following key-value pairs:
553 title-(str)
554 description-(str)
555 Please, note that this method sets ALL values which means that if
556 the given dictionary doesn't contain any of the keys the value
557 will set to a default value.
559 if data.has_key("title"):
560 self.setTitle(data["title"])
561 if data.has_key("description"):
562 self.setDescription(data["description"])
564 def getTitle( self ):
565 return self.title
567 def setTitle( self, newTitle ):
568 self.title = newTitle.strip()
570 def getDescription( self ):
571 return self.description
573 def setDescription( self, newDesc ):
574 self.description = newDesc
576 def getLocator( self ):
577 if self.getSchedule() is None:
578 return None
579 loc=self.getSchedule().getOwner().getLocator()
580 loc["schEntryId"]=self.getId()
581 return loc
583 def synchro( self ):
584 if self.getSchedule() is not None:
585 self.getSchedule().reSchedule()
587 def delete(self):
588 pass
590 def recover(self):
591 pass
593 class ConferenceSchedule(TimeSchedule, Fossilizable):
597 # fossilizes(IConferenceScheduleDisplayFossil, IConferenceScheduleMgmtFossil)
599 def __init__(self,conf):
600 TimeSchedule.__init__(self,conf)
602 def addEntry(self,entry,check=2):
603 """check parameter:
604 0: no check at all
605 1: check and raise error in case of problem
606 2: check and adapt the owner dates"""
608 if (entry is None) or self.hasEntry(entry):
609 return
610 if isinstance(entry,LinkedTimeSchEntry):
611 from MaKaC.conference import Session, Contribution
612 if isinstance(entry.getOwner(),Session):
613 raise MaKaCError( _("Sessions cannot be scheduled into the event, schedule their slots instead"), _("Event"))
614 elif isinstance(entry.getOwner(),Contribution):
615 if entry.getOwner().getSession() is not None:
616 raise MaKaCError( _("Cannot schedule into the event a contribution that belongs to a session"), _("Event"))
617 if not self.getOwner().hasContribution(entry.getOwner()):
618 raise MaKaCError( _("Cannot schedule into the event a contribution that does not belong to it"), _("Event"))
619 self._setEntryDuration(entry)
620 return self._addEntry(entry,check)
622 def moveUpEntry(self,entry,tz=None):
623 #not very smart, should be improved: contribs with same start date,
624 # can cause overlapings
626 if not tz:
627 tz = self.getTimezone()
628 entriesDay=self.getEntriesOnDay(entry.getAdjustedStartDate())
629 if len(entriesDay)<2:
630 return
631 entrypos = 0
632 if entry in entriesDay:
633 entrypos = entriesDay.index(entry)
634 #if the entry is the first one...then it goes to the end.
635 if entrypos == 0 and len(entriesDay)>1:
636 entriesDay[1].setStartDate(entriesDay[0].getStartDate(), check=0, moveEntries=1)
637 i = 2
638 while(i < len(entriesDay)):
639 entry = entriesDay[i]
640 preventry = entriesDay[i-1]
641 entry.setStartDate(preventry.getEndDate(), check=0, moveEntries=1)
642 i += 1
643 entriesDay[0].setStartDate(entriesDay[len(entriesDay)-1].getEndDate(), check=0, moveEntries=1)
644 else:
645 preventry = entriesDay[entrypos-1]
646 entry.setStartDate(preventry.getStartDate(), check=0, moveEntries=1)
647 preventry.setStartDate(entry.getEndDate(), check=0, moveEntries=1)
648 self.reSchedule()
649 self._p_changed = 1
651 def moveDownEntry(self,entry,tz=None):
652 if not tz:
653 tz = self.getTimezone()
654 entriesDay=self.getEntriesOnDay(entry.getAdjustedStartDate())
655 if len(entriesDay)<2:
656 return
657 entrypos = 0
658 if entry in entriesDay:
659 entrypos = entriesDay.index(entry)
660 #if the entry is the last one...then it goes to the first place.
661 if entrypos+1 == len(entriesDay) and len(entriesDay)>1:
662 entriesDay[len(entriesDay)-1].setStartDate(entriesDay[0].getStartDate(), check=0, moveEntries=1)
663 i = -1
664 while(i < len(entriesDay)-2):
665 entry = entriesDay[i]
666 nextentry = entriesDay[i+1]
667 nextentry.setStartDate(entry.getEndDate(), check=0, moveEntries=1)
668 i += 1
669 else:
670 nextentry = entriesDay[entrypos+1]
671 nextentry.setStartDate(entry.getStartDate(), check=0, moveEntries=1)
672 entry.setStartDate(nextentry.getEndDate(), check=0, moveEntries=1)
673 self.reSchedule()
674 self._p_changed = 1
677 class SessionSchedule(TimeSchedule):
681 def __init__(self,session):
682 TimeSchedule.__init__(self,session)
684 def checkSanity( self ):
685 if self.hasEntriesBefore(self.getStartDate()) or self.hasEntriesAfter(self.getEndDate()):
686 raise TimingError( _("Sorry, cannot perform this date change: Some entries in the schedule would be outside the new dates"))
688 def addEntry(self,entry,check=1):
689 if (entry is None) or self.hasEntry(entry):
690 return
691 if isinstance(entry,LinkedTimeSchEntry):
692 from MaKaC.conference import SessionSlot
693 if not(isinstance(entry.getOwner(),SessionSlot)):
694 raise MaKaCError( _("objects of class %s cannot be scheduled into a session")%(entry.getOwner().__class__), _("Session Schedule"))
695 else:
696 raise MaKaCError( _("objects of class %s cannot be scheduled into a session")%(entry.__class__), _("Session Schedule"))
697 self._addEntry(entry)
699 def removeEntry(self,entry):
700 if entry is None or not self.hasEntry(entry):
701 return
702 if entry.getOwner() in self.getOwner().getSlotList():
703 raise MaKaCError( _("Cannot remove a slot without removing it from the session slot list"), _("Session Schedule"))
704 self._removeEntry(entry)
706 def moveEntriesBelow(self, diff, entriesList):
707 """diff: the difference we have to increase/decrease each entry of the list.
708 entriesList: list of entries for applying the diff"""
709 if diff is not None:
710 for entry in entriesList:
711 entry.setStartDate(entry.getStartDate()+diff, check=0, moveEntries=1)
714 class SlotSchedule(TimeSchedule):
718 def __init__(self,slot):
719 TimeSchedule.__init__(self,slot)
721 def _setEntryDuration(self,entry):
722 entryDur=entry.getDuration()
723 if entryDur is None:
724 ownerDur=self.getOwner().getContribDuration()
725 if ownerDur is not None and ownerDur!=timedelta(0):
726 entry.setDuration(dur=ownerDur)
727 else:
728 sessionDur=self.getOwner().getSession().getContribDuration()
729 entry.setDuration(dur=sessionDur)
731 def addEntry(self,entry,check=2):
732 """check parameter:
733 0: no check at all
734 1: check and raise error in case of problem
735 2: check and adapt the owner dates
738 tz = self.getTimezone();
740 owner = self.getOwner()
741 if (entry is None) or self.hasEntry(entry):
742 return
743 if isinstance(entry,LinkedTimeSchEntry):
744 from MaKaC.conference import Contribution
745 if not(isinstance(entry.getOwner(),Contribution)):
746 raise MaKaCError( _("objects of class %s cannot be scheduled into a session slot"), _("Slot"))
747 if (entry.getOwner().getSession() is None) or (not self.getOwner().getSession().hasContribution(entry.getOwner())):
748 raise MaKaCError( _("Cannot schedule into this session a contribution which does not belong to it"), _("Slot"))
749 if entry.getStartDate()!=None and entry.getStartDate() < self.getOwner().getStartDate():
750 if check == 1:
751 raise ParentTimingError( _("The entry would start at %s, which is before the start time of the time slot (%s)")%\
752 (entry.getEndDate().strftime('%Y-%m-%d %H:%M'),\
753 self.getOwner().getStartDate().strftime('%Y-%m-%d %H:%M')),\
754 _("Slot"))
755 elif check == 2:
756 ContextManager.get('autoOps').append((owner,
757 "OWNER_START_DATE_EXTENDED",
758 owner,
759 entry.getAdjustedStartDate(tz=tz)))
760 self.getOwner().setStartDate(entry.getStartDate(),check,0)
761 if entry.getEndDate()!=None and entry.getEndDate() > self.getOwner().getEndDate():
762 if check == 1:
763 raise ParentTimingError( _("The entry would finish at %s, which is after the end of the time slot (%s)")%\
764 (entry.getAdjustedEndDate(tz=tz).strftime('%Y-%m-%d %H:%M'),\
765 self.getOwner().getAdjustedEndDate(tz=tz).strftime('%Y-%m-%d %H:%M')),\
766 "Slot")
767 elif check == 2:
768 ContextManager.get('autoOps').append((owner,
769 "OWNER_END_DATE_EXTENDED",
770 owner,
771 entry.getAdjustedEndDate(tz=tz)))
772 self.getOwner().setEndDate(entry.getEndDate(),check)
773 self._setEntryDuration(entry)
774 self._addEntry(entry,check)
778 def moveUpEntry(self,entry):
779 #not very smart, should be improved: contribs with same start date,
780 # can cause overlapings
781 entries = self.getEntriesOnDay(entry.getAdjustedStartDate())
782 if len(entries)<2:
783 return
784 entrypos = 0
785 if entry in entries:
786 entrypos = entries.index(entry)
787 #if the entry is the first one...then it goes to the end.
788 if entrypos == 0 and len(entries)>1:
789 entries[1].setStartDate(entries[0].getStartDate(),check=0,moveEntries=1)
790 i = 2
791 while(i < len(entries)):
792 entry = entries[i]
793 preventry = entries[i-1]
794 entry.setStartDate(preventry.getEndDate(),check=0,moveEntries=1)
795 i += 1
796 entries[0].setStartDate(entries[len(entries)-1].getEndDate(),check=0,moveEntries=1)
797 else:
798 preventry = entries[entrypos-1]
799 entry.setStartDate(preventry.getStartDate(),check=0,moveEntries=1)
800 preventry.setStartDate(entry.getEndDate(),check=0,moveEntries=1)
801 self.reSchedule()
802 self._p_changed = 1
804 def moveDownEntry(self,entry):
805 entries = self.getEntriesOnDay(entry.getAdjustedStartDate())
806 if len(entries)<2:
807 return
808 entrypos = 0
809 if entry in entries:
810 entrypos = entries.index(entry)
811 #if the entry is the last one...then it goes to the first place.
812 if entrypos+1 == len(entries) and len(entries)>1:
813 entries[len(entries)-1].setStartDate(entries[0].getStartDate(), check=0,moveEntries=1)
814 i = -1
815 while(i < len(entries)-2):
816 entry = entries[i]
817 nextentry = entries[i+1]
818 nextentry.setStartDate(entry.getEndDate(),check=0,moveEntries=1)
819 i += 1
820 else:
821 nextentry = entries[entrypos+1]
822 nextentry.setStartDate(entry.getStartDate(),check=0,moveEntries=1)
823 entry.setStartDate(nextentry.getEndDate(),check=0,moveEntries=1)
824 self.reSchedule()
825 self._p_changed = 1
827 def moveEntriesBelow(self, diff, entriesList):
828 """diff: the difference we have to increase/decrease each entry of the list.
829 entriesList: list of entries for applying the diff"""
830 if diff is not None:
831 for entry in entriesList:
832 entry.setStartDate(entry.getStartDate() + diff, check=2, moveEntries=1)
834 def rescheduleTimes(self, type, diff, day, doFit):
835 pass
838 class PosterSlotSchedule(SlotSchedule):
840 def _setEntryDuration(self,entry):
841 #In the posters schedulers the duration will (by default) always be the
842 # same for every entry within the slot
843 if entry.getOwner().getDuration() != None and entry.getOwner().getDuration() != 0 \
844 and entry.getOwner().getDuration().seconds!=0:
845 return
846 ownerDur=self.getOwner().getContribDuration()
847 if ownerDur is not None and \
848 (ownerDur > timedelta(0)):
849 entry.setDuration(dur=ownerDur)
850 else:
851 sessionDur=self.getOwner().getSession().getContribDuration()
852 entry.setDuration(dur=sessionDur)
854 def addEntry(self,entry,check=0):
855 # check=0 is here only because we must have 3 parameters.
856 if (entry is None) or self.hasEntry(entry):
857 return
858 from MaKaC.conference import Contribution
859 if not isinstance(entry,LinkedTimeSchEntry) or \
860 not isinstance(entry.getOwner(),Contribution):
861 raise MaKaCError( _("objects of class %s cannot be scheduled into a poster session slot")%entry, _("Slot"))
862 if (entry.getOwner().getSession() is None) or \
863 (not self.getOwner().getSession().hasContribution(entry.getOwner())):
864 raise MaKaCError( _("Cannot schedule into this session a contribution which does not belong to it"), _("Slot"))
865 self._setEntryDuration(entry)
866 if entry.isScheduled():
867 #remove it from the old schedule and add it to this one
868 entry.getSchedule().removeEntry(entry)
869 entry.setStartDate(self.getStartDate())
870 self._entries.append(entry)
871 entry.setSchedule(self,self._getNewEntryId())
872 self._p_changed = 1
874 def reSchedule(self):
875 for e in self._entries:
876 if e.getStartDate() != self.getStartDate():
877 e.setStartDate(self.getStartDate())
879 class SlotSchTypeFactory:
880 _sch={"standard":SlotSchedule,"poster":PosterSlotSchedule}
881 _default="standard"
883 def getScheduleKlass(cls,id):
884 id=id.strip().lower()
885 if not cls._sch.has_key(id):
886 id=cls._default
887 return cls._sch[id]
888 getScheduleKlass=classmethod(getScheduleKlass)
890 def getDefaultKlass(cls):
891 return cls._sch[cls._default]
892 getDefaultKlass=classmethod(getDefaultKlass)
894 def getDefaultId(cls):
895 return cls._default
896 getDefaultId=classmethod(getDefaultId)
898 def getIdList(cls):
899 return cls._sch.keys()
900 getIdList=classmethod(getIdList)
902 def getId(cls,sch):
903 for (id,schKlass) in cls._sch.items():
904 if sch.__class__==schKlass:
905 return id
906 return ""
907 getId=classmethod(getId)
909 class TimeSchEntry(SchEntry):
911 def __init__(self):
912 SchEntry.__init__(self)
913 self.startDate=None
914 self.duration=None
916 def getStartDate( self ):
917 pass
919 def setStartDate(self,sDate,check=1, moveEntries=0):
920 pass
922 def getEndDate( self ):
923 pass
925 def getDuration(self):
926 pass
928 def setDuration(self,hours=0,min=15, dur=0):
929 pass
931 def inDay( self, day ):
932 pass
934 def onDate( self, day ):
935 pass
938 class LinkedTimeSchEntry(TimeSchEntry):
940 fossilizes(ILinkedTimeSchEntryDisplayFossil,
941 ILinkedTimeSchEntryMgmtFossil)
943 def __init__(self,owner):
944 SchEntry.__init__(self)
945 self.__owner = owner
947 # fermi - pass tz here.....
948 def getStartDate( self ):
949 return self.__owner.getStartDate()
951 def getAdjustedStartDate( self, tz=None ):
952 return self.__owner.getAdjustedStartDate(tz)
954 def setStartDate(self,newDate,check=2, moveEntries=0):
955 """check parameter:
956 0: no check at all
957 1: check and raise error in case of problem
958 2: check and adapt the owner dates"""
959 return self.getOwner().setStartDate(newDate,check, moveEntries)
961 def getEndDate( self ):
962 return self.__owner.getEndDate()
964 def getAdjustedEndDate( self, tz=None ):
965 return self.__owner.getAdjustedEndDate(tz)
967 def getDuration(self):
968 return self.__owner.getDuration()
970 def setDuration(self,hours=0,minutes=15,dur=0,check=2):
971 if dur!=0:
972 return self.getOwner().setDuration(dur=dur,check=check)
973 else:
974 return self.getOwner().setDuration(hours,minutes,check=check)
976 def getTitle( self ):
977 return self.__owner.getTitle()
979 def getDescription( self ):
980 return self.__owner.getDescription()
982 def getOwner( self ):
983 return self.__owner
985 def inDay( self, day ):
986 """Tells whether or not the current entry occurs whithin the specified
987 day (day is tz-aware)
989 if not self.isScheduled():
990 return False
991 return self.getStartDate().astimezone(day.tzinfo).date()<=day.date() and self.getEndDate().astimezone(day.tzinfo).date()>=day.date()
993 def onDate( self, date ):
994 """Tells whether or not the current entry occurs during the specified
995 date.
997 if not self.isScheduled():
998 return False
999 return self.getStartDate()<=date and \
1000 self.getEndDate()>=date
1002 def collides(self,entry):
1003 return (entry.getStartDate()>=self.getStartDate() and \
1004 entry.getStartDate()<self.getEndDate()) or \
1005 (entry.getEndDate()>self.getStartDate() and \
1006 entry.getEndDate()<=self.getEndDate())
1008 def getUniqueId(self):
1009 return self.getOwner().getUniqueId()
1012 class IndTimeSchEntry(TimeSchEntry):
1014 def setValues( self, data ):
1015 """Sets all the values of the current schedule entry object from a
1016 dictionary containing the following key-value pairs:
1017 title-(str)
1018 description-(str)
1019 year, month, day, sHour, sMinute - (str) => components of the
1020 starting date of the entry, if not specified it will
1021 be set to now.
1022 durationHours, durationMinutes - (str)
1023 Please, note that this method sets ALL values which means that if
1024 the given dictionary doesn't contain any of the keys the value
1025 will set to a default value.
1027 SchEntry.setValues(self,data)
1028 if data.get("sYear", None) != None and \
1029 data.get("sMonth", None) != None and \
1030 data.get("sDay", None) != None and \
1031 data.get("sHour", None) != None and \
1032 data.get("sMinute", None) != None:
1033 self.setStartDate(datetime(int(data["sYear"]),\
1034 int(data["sMonth"]),\
1035 int(data["sDay"]), \
1036 int(data["sHour"]),\
1037 int(data["sMinute"])) )
1038 if data.get("durHours",None)!=None and data.get("durMins",None)!=None:
1039 self.setDuration(data["durHours"],data["durMins"])
1041 def getTimezone( self ):
1042 return self.getSchedule().getOwner().getTimezone()
1044 def getStartDate( self ):
1045 return self.startDate
1047 def getAdjustedStartDate( self, tz=None ):
1048 if not tz:
1049 tz = self.getTimezone()
1051 return self.getStartDate().astimezone(timezone(tz))
1053 def setStartDate(self,sDate,check=1, moveEntries=0):
1054 self.startDate=sDate
1055 self._p_changed=1
1056 if self.isScheduled():
1057 self.getSchedule().reSchedule()
1059 def getEndDate( self ):
1060 if self.getStartDate() is None:
1061 return None
1062 return self.startDate+self.duration
1064 def getAdjustedEndDate( self, tz=None ):
1065 if not tz:
1066 tz = self.getTimezone()
1067 return self.getEndDate().astimezone(timezone(tz))
1069 def getDuration(self):
1070 return self.duration
1072 def setDuration(self,hours=0,min=15,dur=0):
1073 if dur==0:
1074 self.duration=timedelta(hours=int(hours),minutes=int(min))
1075 else:
1076 self.duration=dur
1077 self._p_changed = 1
1078 if self.isScheduled():
1079 self.getSchedule().reSchedule()
1081 def inDay( self, day ):
1082 """Tells whether or not the current entry occurs whithin the specified
1083 day (day is tz-aware)
1085 if not self.isScheduled():
1086 return False
1087 return self.getStartDate().astimezone(day.tzinfo).date()<=day.date() and self.getEndDate().astimezone(day.tzinfo).date()>=day.date()
1089 def onDate( self, date ):
1090 """Tells whether or not the current entry occurs during the specified
1091 date.
1093 if not self.isScheduled():
1094 return False
1095 return self.getStartDate()<=date and \
1096 self.getEndDate()>=date
1099 class BreakTimeSchEntry(IndTimeSchEntry):
1101 fossilizes(IBreakTimeSchEntryFossil, IBreakTimeSchEntryMgmtFossil)
1103 def __init__(self):
1104 IndTimeSchEntry.__init__(self)
1105 self._color="#90C0F0"
1106 self._textColor="#202020"
1107 self._textColorToLink=False
1109 def clone(self, owner):
1110 btse = BreakTimeSchEntry()
1111 btse.setValues(self.getValues())
1112 olddate = self.getOwner().getStartDate()
1113 newdate = owner.getSchedule().getStartDate()
1114 timeDifference = newdate - olddate
1115 btse.setStartDate(btse.getStartDate()+timeDifference)
1116 return btse
1118 def getValues(self):
1119 values = {}
1120 values["startDate"] = self.getStartDate()
1121 values["endDate"] = self.getEndDate()
1122 values["durTimedelta"] = self.getDuration()
1123 values["description"] = self.getDescription()
1124 values["title"] = self.getTitle()
1125 if self.getOwnLocation() is not None :
1126 values["locationName"] = self.getLocation().getName()
1127 else :
1128 values["locationName"] = ""
1129 if self.getOwnRoom() is not None :
1130 values["roomName"] = self.getOwnRoom().getName()
1131 else :
1132 values["roomName"] = ""
1133 values["backgroundColor"] = self.getColor()
1134 values["textColor"] = self.getTextColor()
1135 if self.isTextColorToLinks():
1136 values["textcolortolinks"]="True"
1138 return values
1140 def setValues( self, data, check=2, moveEntriesBelow=0, tz='UTC'):
1141 from MaKaC.conference import CustomLocation, CustomRoom
1142 # In order to move the entries below, it is needed to know the diff (we have to move them)
1143 # and the list of entries to move. It's is needed to take those datas in advance because they
1144 # are going to be modified before the moving.
1145 if moveEntriesBelow == 1 and self.getSchedule():
1146 oldStartDate=copy.copy(self.getStartDate())
1147 oldDuration=copy.copy(self.getDuration())
1148 i=self.getSchedule().getEntries().index(self)+1
1149 entriesList = self.getSchedule().getEntries()[i:]
1150 if data.get("startDate", None) != None:
1151 self.setStartDate(data["startDate"], 0)
1152 elif data.get("sYear", None) != None and \
1153 data.get("sMonth", None) != None and \
1154 data.get("sDay", None) != None and \
1155 data.get("sHour", None) != None and \
1156 data.get("sMinute", None) != None:
1157 #########################################
1158 # Fermi timezone awareness #
1159 # We have to store as UTC, relative #
1160 # to the timezone of the conference. #
1161 #########################################
1162 d = timezone(tz).localize(datetime(int(data["sYear"]),
1163 int(data["sMonth"]),
1164 int(data["sDay"]),
1165 int(data["sHour"]),
1166 int(data["sMinute"])))
1167 sDate = d.astimezone(timezone('UTC'))
1168 self.setStartDate(sDate)
1169 ########################################
1170 # Fermi timezone awareness #
1171 # We have to store as UTC, relative #
1172 # to the timezone of the conference. #
1173 ########################################
1175 if data.get("durTimedelta", None) != None:
1176 self.setDuration(check=0, dur=data["durTimedelta"])
1177 elif data.get("durHours","").strip()!="" and data.get("durMins","").strip()!="":
1178 self.setDuration(data["durHours"], data["durMins"], 0)
1179 else:
1180 h=data.get("durHours","").strip()
1181 m=data.get("durMins","").strip()
1182 force=False
1183 if h!="" or m!="":
1184 h=h or "0"
1185 m=m or "0"
1186 if h!="0" or m!="0":
1187 self.setDuration(int(h), int(m), 0)
1188 else:
1189 force=True
1190 else:
1191 force=True
1192 if force:
1193 if self.getDuration() is None or self.getDuration()==0:
1194 self.setDuration("0", "15", 0)
1195 if data.get( "locationName", "" ).strip() == "":
1196 self.setLocation( None )
1197 else:
1198 loc = self.getOwnLocation()
1199 if not loc:
1200 loc = CustomLocation()
1201 self.setLocation( loc )
1202 loc.setName( data["locationName"] )
1203 loc.setAddress( data.get("locationAddress", "") )
1204 if data.get( "roomName", "" ).strip() == "":
1205 self.setRoom( None )
1206 else:
1207 room = self.getOwnRoom()
1208 if not room:
1209 room = CustomRoom()
1210 self.setRoom( room )
1211 room.setName( data["roomName"] )
1212 room.retrieveFullName(data.get('locationName', ''))
1213 self._color=data.get("backgroundColor","#90C0F0")
1214 if data.has_key("autotextcolor"):
1215 self._textColor=utils.getTextColorFromBackgroundColor(self.getColor())
1216 else:
1217 self._textColor=data.get("textColor","#202020")
1218 self.setTextColorToLinks(data.has_key("textcolortolinks"))
1219 if data.has_key("title"):
1220 self.setTitle(data["title"])
1221 if data.has_key("description"):
1222 self.setDescription(data["description"])
1224 # now check if the slot new time is compatible with its parents limits
1225 if check == 1:
1226 if self.getSchedule() and self.getSchedule().isOutside(self):
1227 raise TimingError( _("This action would move the break out of its parents schedule dates"))
1228 elif check == 2:
1229 if self.getSchedule() and self.getSchedule().isOutside(self):
1230 self.synchro()
1231 # syncrho is not modifying the dates of the session slot. Fit does.
1232 if isinstance(self.getSchedule(), SlotSchedule):
1233 self.getSchedule().getOwner().fit()
1234 if moveEntriesBelow == 1 and self.getSchedule():
1235 diff = (self.getStartDate() - oldStartDate) + (self.getDuration() - oldDuration)
1236 self.getSchedule().moveEntriesBelow(diff, entriesList)
1237 self.notifyModification(False)
1240 def getLocationParent( self ):
1241 if self.getSchedule() is not None:
1242 return self.getSchedule().getOwner()
1243 return None
1245 def setLocation(self, loc):
1246 self.place = loc
1247 self.notifyModification()
1249 def getLocation(self):
1250 if self.getOwnLocation() is None:
1251 return self.getInheritedLocation()
1252 return self.getOwnLocation()
1254 def getInheritedLocation(self):
1255 locParent = self.getLocationParent()
1256 if locParent:
1257 return locParent.getLocation();
1258 else:
1259 return None
1261 def getOwnLocation(self):
1262 try:
1263 if self.place:
1264 pass
1265 except AttributeError:
1266 self.place=None
1267 return self.place
1269 def setRoom(self, room):
1270 self.room = room
1271 self.notifyModification()
1273 def getRoom(self):
1274 if self.getOwnRoom() is None:
1275 return self.getInheritedRoom()
1276 return self.getOwnRoom()
1278 def getInheritedRoom(self):
1279 locParent = self.getLocationParent()
1280 if locParent:
1281 return locParent.getRoom();
1282 else:
1283 return None
1285 def getOwnRoom(self):
1286 try:
1287 if self.room:
1288 pass
1289 except AttributeError:
1290 self.room=None
1291 return self.room
1293 def getOwner(self):
1294 if self.getSchedule() is not None:
1295 return self.getSchedule().getOwner()
1296 return None
1298 def _verifyDuration(self,check=2):
1300 if self.getSchedule() is not None:
1301 owner = self.getSchedule().getOwner()
1302 if self.getEndDate() > owner.getEndDate():
1303 if check==1:
1304 raise ParentTimingError( _("The break cannot end after (%s) its parent (%s)")%\
1305 (self.getEndDate().strftime('%Y-%m-%d %H:%M'),\
1306 owner.getEndDate().strftime('%Y-%m-%d %H:%M')),\
1307 _("Break"))
1308 elif check==2:
1309 # update the schedule
1310 owner.setEndDate(self.getEndDate(),check)
1312 def setDuration(self, hours=0, min=15, check=2,dur=0):
1314 if dur==0:
1315 IndTimeSchEntry.setDuration(self,hours,min)
1316 else:
1317 IndTimeSchEntry.setDuration(self,dur=dur)
1318 self._verifyDuration(check)
1319 self.notifyModification()
1321 def setStartDate(self, newDate,check=2, moveEntries=0):
1323 # try:
1324 # tz = str(newDate.tzinfo)
1325 # except:
1326 # tz = 'undef'
1327 if self.getSchedule() is not None:
1328 owner = self.getSchedule().getOwner()
1329 if newDate < owner.getStartDate():
1330 if check==1:
1331 raise ParentTimingError( _("The break \"%s\" cannot start before (%s) its parent (%s)")%\
1332 (self.getTitle(), \
1333 newDate.astimezone(timezone(self.getTimezone())).strftime('%Y-%m-%d %H:%M'),\
1334 owner.getAdjustedStartDate().strftime('%Y-%m-%d %H:%M')),\
1335 "Break")
1336 elif check==2:
1337 # update the schedule
1338 owner.setStartDate(newDate, check)
1339 ContextManager.get('autoOps').append((self, "OWNER_START_DATE_EXTENDED",
1340 owner, owner.getAdjustedStartDate()))
1341 if newDate > owner.getEndDate():
1342 if check==1:
1343 raise ParentTimingError("The break cannot start after (%s) its parent ends (%s)"%\
1344 (newDate.astimezone(timezone(self.getTimezone())).strftime('%Y-%m-%d %H:%M'),\
1345 owner.getAdjustedEndDate().strftime('%Y-%m-%d %H:%M')),\
1346 _("Break"))
1347 elif check==2:
1348 # update the schedule
1349 owner.setEndDate(newDate,check)
1350 ContextManager.get('autoOps').append((self, "OWNER_END_DATE_EXTENDED",
1351 owner, owner.getAdjustedEndDate()))
1352 IndTimeSchEntry.setStartDate(self, newDate,check)
1353 # Check that after modifying the start date, the end date is still within the limits of the slot
1354 if self.getSchedule() and self.getEndDate() > owner.getEndDate():
1355 if check==1:
1356 raise ParentTimingError("The break cannot end after (%s) its parent ends (%s)"%\
1357 (self.getAdjustedEndDate().strftime('%Y-%m-%d %H:%M'),\
1358 owner.getAdjustedEndDate().strftime('%Y-%m-%d %H:%M')),\
1359 _("Break"))
1360 elif check==2:
1361 # update the schedule
1362 owner.setEndDate(self.getEndDate(),check)
1363 ContextManager.get('autoOps').append((self, "OWNER_END_DATE_EXTENDED",
1364 owner, owner.getAdjustedEndDate()))
1365 self.notifyModification(cleanCache = self.getSchedule() is not None)
1367 def setColor(self,newColor):
1368 self._color=newColor
1369 self.notifyModification()
1370 setBgColor=setColor
1372 def getColor(self):
1373 try:
1374 if self._color:
1375 pass
1376 except AttributeError:
1377 self._color="#AADDEE"
1378 return self._color
1379 getBgColor=getColor
1381 def setTextColor(self,newColor):
1382 self._textColor=newColor
1383 self.notifyModification()
1385 def getTextColor(self):
1386 try:
1387 if self._textColor:
1388 pass
1389 except AttributeError:
1390 self._textColor="#202020"
1391 return self._textColor
1393 def setTextColorToLinks(self,v):
1394 self._textColorToLink=v
1395 self.notifyModification()
1397 def isTextColorToLinks(self):
1398 try:
1399 if self._textColorToLink:
1400 pass
1401 except AttributeError:
1402 self._textColorToLink=False
1403 return self._textColorToLink
1405 def delete(self):
1406 TrashCanManager().add(self)
1408 def recover(self):
1409 TrashCanManager().remove(self)
1411 def getUniqueId(self):
1412 return self.getOwner().getUniqueId() + "brk" + self.getId()
1414 def notifyModification(self, cleanCache = True):
1415 IndTimeSchEntry.notifyModification(self)
1416 if cleanCache and self.getOwner() and not ContextManager.get('clean%s'%self.getUniqueId(), False):
1417 ScheduleToJson.cleanCache(self)
1418 ContextManager.set('clean%s'%self.getUniqueId(), True)
1421 class ContribSchEntry(LinkedTimeSchEntry):
1423 fossilizes(IContribSchEntryDisplayFossil,
1424 IContribSchEntryMgmtFossil )
1426 def __init__(self, owner):
1427 LinkedTimeSchEntry.__init__(self, owner)
1429 def setSchedule(self,sch,id):
1430 status=self.getOwner().getCurrentStatus()
1431 from MaKaC.conference import ContribStatusWithdrawn,ContribStatusNotSch,ContribStatusSch
1432 if isinstance(status,ContribStatusWithdrawn) and sch is not None:
1433 raise MaKaCError( _("Cannot schedule a contribution which has been withdrawn"), _("Contribution"))
1434 LinkedTimeSchEntry.setSchedule(self,sch,id)
1435 if sch is None:
1436 newStatus=ContribStatusNotSch(self.getOwner())
1437 else:
1438 newStatus=ContribStatusSch(self.getOwner())
1439 self.getOwner().setStatus(newStatus)
1441 def getRoom(self):
1442 return self.getOwner().getRoom()
1444 def setRoom(self, room):
1445 self.getOwner().setRoom(room)
1447 def getLocation(self):
1448 return self.getOwner().getLocation()
1450 def setLocation(self, loc):
1451 self.getOwner().setLocation(loc)
1453 def getOwnRoom(self):
1454 return self.getOwner().getOwnRoom()
1456 def getOwnLocation(self):
1457 return self.getOwner().getOwnLocation()
1460 class ScheduleToJson(object):
1462 _cacheEntries = GenericCache("ConfTTEntries")
1463 _cache = GenericCache("ConfTT")
1465 @staticmethod
1466 def get_versioned_key(cache, key, timezone):
1467 num = int(cache.get('_version-%s' % key, 0))
1468 return '%s.%d.%s' % (key, num, timezone)
1470 @classproperty
1471 @classmethod
1472 def use_cache(cls):
1473 return not ContextManager.get('offlineMode', False)
1475 @staticmethod
1476 def bump_cache_version(cache, key):
1477 vkey = '_version-%s' % key
1478 cache.set(vkey, int(cache.get(vkey, 0)) + 1)
1480 @classmethod
1481 def obtainFossil(cls, entry, tz, fossilInterface=None, mgmtMode=False, useAttrCache=False):
1483 if mgmtMode or (not isinstance(entry, BreakTimeSchEntry) and not entry.getOwner().getAccessController().isFullyPublic()):
1484 # We check if it is fully public because it could be some material protected
1485 # that would create a security hole if we cache it
1486 result = entry.fossilize(interfaceArg = fossilInterface, useAttrCache = useAttrCache, tz = tz, convert=True)
1487 else:
1488 cache_key = cls.get_versioned_key(cls._cacheEntries, entry.getUniqueId(), tz)
1490 result = cls._cacheEntries.get(cache_key) if cls.use_cache else None
1492 if result is None:
1493 result = entry.fossilize(interfaceArg = fossilInterface, useAttrCache = useAttrCache, tz = tz, convert=True)
1494 if cls.use_cache:
1495 cls._cacheEntries.set(cache_key, result, timedelta(minutes=5))
1497 return result
1499 @staticmethod
1500 def processEntry(obj, tz, aw, mgmtMode = False, useAttrCache = False):
1502 if mgmtMode:
1503 if isinstance(obj, BreakTimeSchEntry):
1504 entry = ScheduleToJson.obtainFossil(obj, tz, IBreakTimeSchEntryMgmtFossil, mgmtMode, useAttrCache)
1505 elif isinstance(obj, ContribSchEntry):
1506 entry = ScheduleToJson.obtainFossil(obj, tz, IContribSchEntryMgmtFossil, mgmtMode, useAttrCache)
1507 elif isinstance(obj, LinkedTimeSchEntry):
1508 entry = ScheduleToJson.obtainFossil(obj, tz, ILinkedTimeSchEntryMgmtFossil, mgmtMode, useAttrCache)
1509 else:
1510 entry = ScheduleToJson.obtainFossil(obj, tz, None, mgmtMode, useAttrCache)
1511 else:
1512 # the fossils used for the display of entries
1513 # will be taken by default, since they're first
1514 # in the list of their respective Fossilizable
1515 # objects
1516 entry = ScheduleToJson.obtainFossil(obj, tz, None, mgmtMode, useAttrCache)
1518 genId = entry['id']
1520 # sessions that are no poster sessions will be expanded
1521 if entry['entryType'] == 'Session':
1523 sessionSlot = obj.getOwner()
1525 # get session content
1526 entries = {}
1527 for contrib in sessionSlot.getSchedule().getEntries():
1528 if ScheduleToJson.checkProtection(contrib, aw):
1529 if mgmtMode:
1530 if isinstance(contrib, ContribSchEntry):
1531 contribData = ScheduleToJson.obtainFossil(contrib, tz, IContribSchEntryMgmtFossil, mgmtMode, useAttrCache)
1532 elif isinstance(contrib, BreakTimeSchEntry):
1533 contribData = ScheduleToJson.obtainFossil(contrib, tz, IBreakTimeSchEntryMgmtFossil, mgmtMode, useAttrCache)
1534 else:
1535 contribData = ScheduleToJson.obtainFossil(contrib, tz, None, mgmtMode, useAttrCache)
1536 else:
1537 # the fossils used for the display of entries
1538 # will be taken by default, since they're first
1539 # in the list of their respective Fossilizable
1540 # objects
1541 contribData = ScheduleToJson.obtainFossil(contrib, tz, None, mgmtMode, useAttrCache)
1543 entries[contribData['id']] = contribData
1545 entry['entries'] = entries
1547 return genId, entry
1549 @staticmethod
1550 def checkProtection(obj, aw):
1551 if aw is None or ContextManager.get('offlineMode', False):
1552 return True
1554 from MaKaC.conference import SessionSlot
1556 canBeDisplayed = False
1557 if isinstance(obj, BreakTimeSchEntry):
1558 canBeDisplayed = True
1559 else: #contrib or session slot
1560 owner = obj.getOwner()
1561 if isinstance(owner, SessionSlot) and owner.canView(aw):
1562 canBeDisplayed = True
1563 elif not owner.isProtected() or owner.canAccess(aw): #isProtected avoids checking access if public
1564 canBeDisplayed = True
1566 return canBeDisplayed
1568 @staticmethod
1569 def isOnlyWeekend(days):
1571 It checks if the event takes place only during the weekend
1573 # If there are more than 2 days, there is at least one day that is not part of the weekend
1574 if len(days) > 2:
1575 return False
1577 for day in days:
1578 if (datetime.strptime(day, "%Y%m%d").weekday() not in [5, 6]):
1579 return False
1580 return True
1582 @classmethod
1583 def process(cls, schedule, tz, aw, days=None, mgmtMode=False, useAttrCache=False, hideWeekends=False):
1584 scheduleDict = {}
1586 if cls.use_cache and not days and schedule.getOwner().getAccessController().isFullyPublic() and not mgmtMode:
1587 scheduleDict = cls._cache.get(cls.get_versioned_key(cls._cache, schedule.getOwner().getUniqueId(), tz))
1589 if not scheduleDict:
1590 scheduleDict={}
1591 fullTT = False # This flag is used to indicate that we must save the general cache (not entries). When
1592 if not days: # asking only one day, we don't need to cache (it can generate issues)
1593 fullTT = True
1594 days = iterdays(schedule.getAdjustedStartDate(tz), schedule.getAdjustedEndDate(tz))
1596 dates = [d.strftime("%Y%m%d") for d in days]
1598 # Generating the days dictionnary
1599 for d in dates:
1600 scheduleDict[d] = {}
1602 # Filling the day dictionnary with entries
1603 for obj in schedule.getEntries():
1605 if ScheduleToJson.checkProtection(obj, aw):
1606 day = obj.getAdjustedStartDate(tz).strftime("%Y%m%d")
1607 # verify that start date is in dates
1608 if day in dates:
1609 genId, resultData = ScheduleToJson.processEntry(obj, tz, aw, mgmtMode, useAttrCache)
1610 scheduleDict[day][genId] = resultData
1611 if cls.use_cache and fullTT and schedule.getOwner().getAccessController().isFullyPublic() and not mgmtMode:
1612 cls._cache.set(cls.get_versioned_key(cls._cache, schedule.getOwner().getUniqueId(), tz), scheduleDict,
1613 timedelta(minutes=5))
1615 if hideWeekends and not ScheduleToJson.isOnlyWeekend(scheduleDict.keys()):
1616 for entry in scheduleDict.keys():
1617 weekDay = datetime.strptime(entry, "%Y%m%d").weekday()
1618 if scheduleDict[entry] == {} and (weekDay == 5 or weekDay == 6):
1619 del scheduleDict[entry]
1621 return scheduleDict
1623 @staticmethod
1624 def sort_dict(dict):
1625 new_dict = {}
1626 sorted_keys = dict.keys()
1627 sorted_keys.sort()
1629 for key in sorted_keys:
1630 new_dict[key] = dict[key]
1632 return new_dict
1634 @classmethod
1635 def cleanCache(cls, obj, cleanConferenceCache=True):
1636 cls.bump_cache_version(cls._cacheEntries, obj.getUniqueId())
1637 if cleanConferenceCache:
1638 cls.cleanConferenceCache(obj.getOwner().getConference())
1640 @classmethod
1641 def cleanConferenceCache(cls, obj):
1642 cls.bump_cache_version(cls._cache, obj.getUniqueId())