appypod-rattail/gen/calendar.py

213 lines
9.1 KiB
Python
Raw Normal View History

# ------------------------------------------------------------------------------
from appy import Object
from appy.gen import Type
from DateTime import DateTime
from BTrees.IOBTree import IOBTree
from persistent.list import PersistentList
# ------------------------------------------------------------------------------
class Calendar(Type):
'''This field allows to produce an agenda and view/edit events on it.'''
jsFiles = {'view': ('widgets/calendar.js',)}
def __init__(self, eventTypes, validator=None, default=None, show='view',
page='main', group=None, layouts=None, move=0,
specificReadPermission=False, specificWritePermission=False,
width=None, height=300, colspan=1, master=None,
masterValue=None, focus=False, mapping=None, label=None,
maxEventLength=50, otherCalendars=None):
Type.__init__(self, validator, (0,1), None, default, False, False,
show, page, group, layouts, move, False, False,
specificReadPermission, specificWritePermission,
width, height, None, colspan, master, masterValue, focus,
False, True, mapping, label)
# eventTypes is a list of strings that identify the types of events
# that are supported by this calendar.
self.eventTypes = eventTypes
# It is not possible to create events that span more days than
# maxEventLength.
self.maxEventLength = maxEventLength
# If a method is specified in the following parameters, it must return
# a list of calendars whose events must be shown within this agenda.
# Every element in this list must be a sub-list [object, name, color]
# (not a tuple):
# - object must refer to the other object on which the other calendar
# field is defined;
# - name is the name of the field on this object that stores the
# calendar;
# - color must be a string containing the HTML color (including the
# leading "#" when relevant) into which events of the calendar must
# appear.
self.otherCalendars = otherCalendars
def getSiblingMonth(self, month, prevNext):
'''Gets the next or previous month (depending of p_prevNext) relative
to p_month.'''
dayOne = DateTime('%s/01' % month)
if prevNext == 'previous':
refDate = dayOne - 1
elif prevNext == 'next':
refDate = dayOne + 33
return refDate.strftime('%Y/%m')
weekDays = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')
def getNamesOfDays(self, obj, short=True):
res = []
for day in self.weekDays:
if short:
suffix = '_short'
else:
suffix = ''
res.append(obj.translate('day_%s%s' % (day, suffix)))
return res
def getMonthGrid(self, month):
'''Creates a list of lists of DateTime objects representing the calendar
grid to render for a given p_month.'''
# Month is a string "YYYY/mm".
currentDay = DateTime('%s/01 12:00' % month)
currentMonth = currentDay.month()
res = [[]]
dayOneNb = currentDay.dow() or 7 # This way, Sunday is 7 and not 0.
if dayOneNb != 1:
previousDate = DateTime(currentDay)
# If the 1st day of the month is not a Monday, start the row with
# the last days of the previous month.
for i in range(1, dayOneNb):
previousDate = previousDate - 1
res[0].insert(0, previousDate)
finished = False
while not finished:
# Insert currentDay in the grid
if len(res[-1]) == 7:
# Create a new row
res.append([currentDay])
else:
res[-1].append(currentDay)
currentDay = currentDay + 1
if currentDay.month() != currentMonth:
finished = True
# Complete, if needed, the last row with the first days of the next
# month.
if len(res[-1]) != 7:
while len(res[-1]) != 7:
res[-1].append(currentDay)
currentDay = currentDay + 1
return res
def getOtherCalendars(self, obj):
'''Returns the list of other calendars whose events must also be shown
on this calendar.'''
if self.otherCalendars:
res = self.callMethod(obj, self.otherCalendars)
# Replace field names with field objects
for i in range(len(res)):
res[i][1] = res[i][0].getField(res[i][1])
return res
def getEventsAt(self, obj, date, asDict=True):
'''Returns the list of events that exist at some p_date (=day).'''
if not hasattr(obj, self.name): return
years = getattr(obj, self.name)
year = date.year()
if year not in years: return
months = years[year]
month = date.month()
if month not in months: return
days = months[month]
day = date.day()
if day not in days: return
if asDict:
res = [e.__dict__ for e in days[day]]
else:
res = days[day]
return res
def hasEventsAt(self, obj, date, otherEvents):
'''Returns True if, at p_date, an event is found of the same type as
p_otherEvents.'''
if not otherEvents: return False
events = self.getEventsAt(obj, date, asDict=False)
if not events: return False
return events[0].eventType == otherEvents[0]['eventType']
def getOtherEventsAt(self, obj, date, otherCalendars):
'''Gets events that are defined in p_otherCalendars at some p_date.'''
res = []
for o, field, color in otherCalendars:
events = field.getEventsAt(o.o, date, asDict=False)
if events:
eventType = events[0].eventType
label = '%s_event_%s' % (field.labelId, eventType)
info = Object(name=obj.translate(label), color=color)
res.append(info.__dict__)
return res
def createEvent(self, obj, date, handleEventSpan=True):
'''Create a new event in the calendar, at some p_date (day). If
p_handleEventSpan is True, we will use rq["eventSpan"] and also
create the same event for successive days.'''
rq = obj.REQUEST
year, month, day = date.year(), date.month(), date.day()
# Check that the "preferences" dict exists or not.
if not hasattr(obj.aq_base, self.name):
# 1st level: create a IOBTree whose keys are years.
setattr(obj, self.name, IOBTree())
yearsDict = getattr(obj, self.name)
# Get the sub-dict storing months for a given year
if year in yearsDict:
monthsDict = yearsDict[year]
else:
yearsDict[year] = monthsDict = IOBTree()
# Get the sub-dict storing days of a given month
if month in monthsDict:
daysDict = monthsDict[month]
else:
monthsDict[month] = daysDict = IOBTree()
# Get the list of events for a given day
if day in daysDict:
events = daysDict[day]
else:
daysDict[day] = events = PersistentList()
# Create and store the event, excepted if an event already exists.
if not events:
event = Object(eventType=rq['eventType'])
events.append(event)
# Span the event on the successive days if required
if handleEventSpan and rq['eventSpan']:
nbOfDays = min(int(rq['eventSpan']), self.maxEventLength)
for i in range(nbOfDays):
date = date + 1
self.createEvent(obj, date, handleEventSpan=False)
def deleteEvent(self, obj, date, handleEventSpan=True):
'''Deletes an event. It actually deletes all events at rq['day'].
If p_handleEventSpan is True, we will use rq["deleteNext"] to
delete successive events, too.'''
rq = obj.REQUEST
if not self.getEventsAt(obj, date): return
daysDict = getattr(obj, self.name)[date.year()][date.month()]
# Remember events, in case we must delete similar ones for next days.
events = self.getEventsAt(obj, date)
del daysDict[date.day()]
if handleEventSpan and rq.has_key('deleteNext') and \
(rq['deleteNext'] == 'True'):
while True:
date = date + 1
if self.hasEventsAt(obj, date, events):
self.deleteEvent(obj, date, handleEventSpan=False)
else:
break
def process(self, obj):
'''Processes an action coming from the calendar widget, ie, the creation
or deletion of a calendar event.'''
rq = obj.REQUEST
action = rq['actionType']
# Get the date for this action
if action == 'createEvent':
return self.createEvent(obj, DateTime(rq['day']))
elif action == 'deleteEvent':
return self.deleteEvent(obj, DateTime(rq['day']))
# ------------------------------------------------------------------------------