797 lines
30 KiB
Python
797 lines
30 KiB
Python
# -*- coding: iso-8859-15 -*-
|
|
|
|
|
|
# Glasnost
|
|
# By: Odile Bénassy <obenassy@entrouvert.com>
|
|
# Romain Chantereau <rchantereau@entrouvert.com>
|
|
# Nicolas Clapiès <nclapies@easter-eggs.org>
|
|
# Pierre-Antoine Dejace <padejace@entrouvert.be>
|
|
# Thierry Dulieu <tdulieu@easter-eggs.com>
|
|
# Florent Monnier <monnier@codelutin.com>
|
|
# Cédric Musso <cmusso@easter-eggs.org>
|
|
# Frédéric Péters <fpeters@entrouvert.be>
|
|
# Benjamin Poussin <poussin@codelutin.com>
|
|
# Emmanuel Raviart <eraviart@entrouvert.com>
|
|
# Sébastien Régnier <regnier@codelutin.com>
|
|
# Emmanuel Saracco <esaracco@easter-eggs.com>
|
|
#
|
|
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
|
|
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
|
|
# Emmanuel Saracco & Théridion
|
|
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
|
|
# Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
|
|
# Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
|
|
# Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
|
|
# Régnier, Emmanuel Saracco, Théridion & Vecam
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
|
|
__doc__ = """Glasnost Appointments Web"""
|
|
|
|
__version__ = '$Revision$'[11:-2]
|
|
|
|
import calendar
|
|
import time
|
|
|
|
import glasnost.common.tools_new as commonTools
|
|
|
|
from glasnost.proxy.AppointmentsProxy import *
|
|
from ObjectsWeb import register, AdminMixin, ObjectWebMixin, ObjectsWebMixin
|
|
|
|
import calendaring
|
|
from tools import *
|
|
|
|
class AdminAppointments(AdminMixin, AdminAppointments):
|
|
categoriesGroupId_kind_widget_fieldLabel = N_('Categories Group')
|
|
categoriesGroupId_kind_widgetName = 'SelectId'
|
|
register(AdminAppointments)
|
|
|
|
|
|
_Appointment = Appointment
|
|
class Appointment(ObjectWebMixin, Appointment):
|
|
body_kind_widget_fieldLabel = N_('Text')
|
|
body_kind_widget_cols = 40
|
|
body_kind_widget_colSpan = 2
|
|
body_kind_widget_rows = 10
|
|
body_kind_widgetName = 'TextArea'
|
|
|
|
categoriesSet = None
|
|
class categoriesSet_kindClass:
|
|
_kindName = 'Sequence'
|
|
importExport = 'private'
|
|
isRequiredInEditMode = 1
|
|
label = N_('Categories')
|
|
requiredCount = 0
|
|
stateInEditMode = 'read-write'
|
|
class itemKind_valueClass:
|
|
_kindName = 'Id'
|
|
def getValues(self, slot):
|
|
admin = slot.getObject().getWeb().getAdmin()
|
|
groupsProxy = getProxyForServerRole('groups')
|
|
if not admin.categoriesGroupId:
|
|
return []
|
|
group = groupsProxy.getObject(admin.categoriesGroupId)
|
|
return group.membersSet
|
|
|
|
end_kind_widget_fieldLabel = N_('End')
|
|
|
|
participantsSet_kind_itemKind_value_defaultValue = 'user'
|
|
participantsSet_kind_balloonHelp = N_(
|
|
'Select the people and groups who are assigned to this appointment.')
|
|
participantsSet_kind_label = N_('Participants')
|
|
|
|
start_kind_widget_fieldLabel = N_('Start')
|
|
|
|
title_kind_widget_fieldLabel = N_('Title')
|
|
title_kind_widget_size = 40
|
|
title_kind_widgetName = 'InputText'
|
|
|
|
#def getOrderedLayoutSlotNames(self, parentSlot = None):
|
|
# slotNames = _Appointment.getOrderedLayoutSlotNames(
|
|
# self, parentSlot = parentSlot)
|
|
# #slotNames += [ 'categoriesSet' ]
|
|
# return slotNames
|
|
|
|
def getLabelTranslated(self, destinationLanguages = None, multiCall = None,
|
|
withDate = 0, withHour = 0):
|
|
label = ObjectWebMixin.getLabelTranslated(self, destinationLanguages,
|
|
multiCall)
|
|
dateTime = self.getDateTime(withDate, withHour)
|
|
if dateTime:
|
|
return '%s - %s' % (dateTime, label)
|
|
return label
|
|
|
|
def getSlotNames(self, parentSlot = None):
|
|
slotNames = _Appointment.getSlotNames(self, parentSlot = parentSlot)
|
|
admin = self.getWeb().getAdmin()
|
|
if not admin.categoriesGroupId:
|
|
slotNames.remove('categoriesSet')
|
|
return slotNames
|
|
|
|
def toVevent(self):
|
|
s = []
|
|
s.append("""BEGIN:VEVENT
|
|
UID:%s
|
|
SUMMARY:%s
|
|
URL:%s
|
|
SEQUENCE:%s""" % (
|
|
self.id,
|
|
self.title,
|
|
X.idUrl(self.id).getAsAbsoluteUrl(),
|
|
self.version))
|
|
if self.body:
|
|
# TODO: extract text from body
|
|
s.append("""DESCRIPTION:%s""" % utf8(self.body.replace('\n', '\\n')))
|
|
if not self.getBeginningHourAndMinute():
|
|
date = '%d%02d%02d' % tuple(
|
|
time.localtime(self.start)[:3])
|
|
s.append("""DTSTART
|
|
;VALUE=DATE
|
|
:%s
|
|
DTEND:%sT235900""" % (date, date) )
|
|
else:
|
|
beginning = '%d%02d%02dT%02d%02d%02d' % tuple(
|
|
time.localtime(self.start)[:6])
|
|
if not self.end:
|
|
self.end = self.start + 3600
|
|
end = '%d%02d%02dT%02d%02d%02d' % tuple(
|
|
time.localtime(self.end)[:6])
|
|
s.append("""DTSTART:%s\nDTEND:%s""" % (beginning, end))
|
|
|
|
s.append('END:VEVENT\n')
|
|
return '\n'.join(s)
|
|
register(Appointment)
|
|
|
|
|
|
def weekNumberToDate(year, weekNumber):
|
|
jan4thSecs = time.mktime((year, 1, 4, 0, 0, 0, 0, 0, 0))
|
|
secondsSinceJan4th = jan4thSecs + (weekNumber-1)*7*24*60*60
|
|
weekTuple = time.localtime(secondsSinceJan4th)
|
|
daysToAdd = - weekTuple[6]
|
|
secondsSinceJan4th += daysToAdd*24*60*60
|
|
weekTuple = time.localtime(secondsSinceJan4th)
|
|
return weekTuple
|
|
|
|
|
|
def dateToWeekNumber(year, month, day):
|
|
janFirstSecs = time.mktime((year, 1, 1, 0, 0, 0, 0, 0, 0))
|
|
janFirstTuple = time.localtime(janFirstSecs)
|
|
if janFirstTuple[6] < 4:
|
|
week0Secs = janFirstSecs - janFirstTuple[6]*24*60*60
|
|
else:
|
|
week0Secs = janFirstSecs + (7-janFirstTuple[6])*24*60*60
|
|
|
|
secondsSinceEpoch = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
|
|
diffSecs = secondsSinceEpoch - week0Secs
|
|
weekNumber, mod = divmod(diffSecs, 7*24*60*60)
|
|
weekNumber = int(weekNumber) + 1
|
|
if weekNumber == 53:
|
|
year, weekNumber = year+1, 1
|
|
if weekNumber == 0:
|
|
year, weekNumber = year-1, 52
|
|
return (year, weekNumber)
|
|
|
|
|
|
class AppointmentsWeb(ObjectsWebMixin, AppointmentsProxy):
|
|
|
|
def agenda(self):
|
|
now = time.time()
|
|
objects = self.getObjects().values()
|
|
|
|
pastObjects = [x for x in objects if x.start < now]
|
|
pastObjects.sort(lambda x,y: -cmp(x.start, y.start))
|
|
|
|
futureObjects = [x for x in objects if x.start > now]
|
|
futureObjects.sort(lambda x,y: cmp(x.start, y.start))
|
|
|
|
layout = X.array()
|
|
|
|
if futureObjects:
|
|
table = X.table(_class = 'objects-table')
|
|
table += X.tr(
|
|
X.th(_('Date')),
|
|
X.th(_('Label')))
|
|
for object in futureObjects:
|
|
table += X.tr(
|
|
X.td(object.getDateTime(withDate = 1, withHour = 1)),
|
|
X.td(X.objectHypertextLabel(object.id)))
|
|
layout += X.h2(_('Future events'))
|
|
layout += table
|
|
|
|
if pastObjects:
|
|
table = X.table(_class = 'objects-table')
|
|
table += X.tr(
|
|
X.th(_('Date')),
|
|
X.th(_('Label')))
|
|
for object in pastObjects:
|
|
table += X.tr(
|
|
X.td(object.getDateTime(withDate = 1, withHour = 1)),
|
|
X.td(X.objectHypertextLabel(object.id)))
|
|
layout += X.h2(_('Past events'))
|
|
layout += table
|
|
|
|
return writePageLayout(layout, _('Agenda'))
|
|
agenda.isPublicForWeb = 1
|
|
|
|
def day(self, year, month, day):
|
|
canAdd = self.canAddObject()
|
|
thisDay = time.mktime( [int(year), int(month), int(day)] + [0]*5 +
|
|
[time.localtime()[-1]] )
|
|
appointments = self.getAppointments(thisDay, thisDay+86400).values()
|
|
dayAppointments = [x for x in appointments if x.start == thisDay]
|
|
|
|
layout = X.array()
|
|
|
|
if len(dayAppointments):
|
|
layout += X.h2(_('Day long appointments'))
|
|
ul = X.ul()
|
|
for d in dayAppointments:
|
|
ul += X.li(X.a(href = X.idUrl(d.id))(d.getTitle()))
|
|
layout += ul
|
|
|
|
table = X.table(_class = 'calendar-day-full')
|
|
layout += table
|
|
|
|
caption = X.caption()
|
|
yesterday = time.localtime(thisDay-86400)
|
|
tomorrow = time.localtime(thisDay+86400)
|
|
caption += X.a(href = X.actionUrl('day/%d/%d/%d' % (
|
|
yesterday[0], yesterday[1], yesterday[2])),
|
|
_class = 'previous-day')('<<')
|
|
caption += X.span()(self.formatDate(time.localtime(thisDay)))
|
|
caption += X.a(href = X.actionUrl('day/%d/%d/%d' % (
|
|
tomorrow[0], tomorrow[1], tomorrow[2])),
|
|
_class = 'next-day')('>>')
|
|
table += caption
|
|
|
|
skipTo = 0
|
|
for hour in range(8, 21): # TODO: "opening hours" should be an option
|
|
tr = X.tr()
|
|
table += tr
|
|
tr += X.th(scope = 'row')('%02d:00' % hour)
|
|
if hour < skipTo:
|
|
continue
|
|
thisDate = list(time.localtime(thisDay))
|
|
thisDate[3] = hour
|
|
nextDate = thisDate[:]
|
|
nextDate[3] += 1
|
|
maxEnd = time.mktime(nextDate)
|
|
while 1:
|
|
hourAppointments = [x for x in appointments if \
|
|
x.start >= time.mktime(thisDate) and \
|
|
x.start < time.mktime(nextDate)]
|
|
maxEnd = max([maxEnd] + [x.end for x in hourAppointments])
|
|
if time.mktime(nextDate) < maxEnd:
|
|
nextDate = time.localtime(maxEnd)
|
|
else:
|
|
break
|
|
nbHour = int( math.ceil((
|
|
time.mktime(nextDate) - time.mktime(thisDate)) / 3600))
|
|
|
|
hourContent = X.array()
|
|
ul = X.ul()
|
|
hourAppointments.sort(lambda x,y: cmp(x.start, y.start))
|
|
for d in hourAppointments:
|
|
ul += X.li(X.a(href = X.idUrl(d.id))(d.getLabelTranslated()))
|
|
hourContent += ul
|
|
className = ''
|
|
if len(hourAppointments):
|
|
className = 'hour-busy'
|
|
td = X.td(_class = className)(hourContent)
|
|
else:
|
|
td = X.td()
|
|
if nbHour > 1:
|
|
td.setAttribute('rowspan', nbHour)
|
|
tr += td
|
|
skipTo = hour + nbHour - 1
|
|
|
|
layout += self.getLinksLayout(year = int(year), month = int(month),
|
|
day = int(day), exclude = ['day'])
|
|
|
|
return writePageLayout(layout, self.formatDate(time.localtime(thisDay)))
|
|
day.isPublicForWeb = 1
|
|
|
|
def formatDate(self, dayTuple):
|
|
# TODO: grab user or site preference for date format (euro/us/...)
|
|
year, month, day = dayTuple[:3]
|
|
monthLabel = calendaring.getMonthLabelNoCapitalization(month)
|
|
return '%d %s %d' % (day, monthLabel, year)
|
|
|
|
def getObject_handleResult(self, lazyObject):
|
|
object = AppointmentsProxy.getObject_handleResult(self, lazyObject)
|
|
groupsProxy = getProxyForServerRole('groups')
|
|
object.categoriesSet = groupsProxy.getObjectIdsWithContent(object.id)
|
|
return object
|
|
|
|
|
|
def week(self, year, weekNumber):
|
|
webTools.addContextualHeader('calendar.css')
|
|
canAdd = self.canAddObject()
|
|
|
|
today = list(time.localtime(time.time())[:3])
|
|
|
|
year, weekNumber = int(year), int(weekNumber)
|
|
|
|
prevYear, prevWeek = year, weekNumber-1
|
|
if prevWeek == 0:
|
|
prevYear, prevWeek = prevYear-1, 52
|
|
nextYear, nextWeek = year, weekNumber+1
|
|
if nextWeek > 52:
|
|
nextYear, nextWeek = nextYear+1, 1
|
|
|
|
weekTuple = weekNumberToDate(year, weekNumber)
|
|
appointments = self.getAppointments(
|
|
time.mktime(weekTuple),
|
|
time.mktime(weekTuple)+7*24*60*60 ).values()
|
|
|
|
table = X.table(_class = 'calendar-week-full')
|
|
|
|
caption = X.caption()
|
|
caption += X.a(href = X.actionUrl('week/%d/%d' % (
|
|
prevYear, prevWeek)), _class = 'previous-week')('<<')
|
|
caption += X.span()(_('Week %d of %d') % (weekNumber, year))
|
|
caption += X.a(href = X.actionUrl('week/%d/%d' % (
|
|
nextYear, nextWeek)), _class = 'next-week')('>>')
|
|
table += caption
|
|
|
|
table += X.colgroup()
|
|
table += X.colgroup(_class = 'weekdays', span = 5)
|
|
table += X.colgroup(_class = 'weekend', span = 2)
|
|
|
|
weekTime = time.mktime(weekTuple)
|
|
|
|
tr = X.tr()
|
|
tr += X.th()
|
|
for i in range(7):
|
|
thisDate = time.localtime(weekTime + i*24*60*60)
|
|
wd = _(calendaring.dayLabels[i])
|
|
dayLabel = '%s %d %s' % (wd, thisDate[2],
|
|
calendaring.getMonthLabelNoCapitalization(thisDate[1]))
|
|
tr += X.th(scope = 'col')(dayLabel)
|
|
table += X.thead(tr)
|
|
|
|
tbody = X.tbody()
|
|
table += tbody
|
|
daysFilled = [0] * 7
|
|
tr = X.tr()
|
|
tr += X.th(scope = 'row')
|
|
allDay = 0
|
|
for i in range(7):
|
|
thisDate = list(time.localtime(weekTime + i*24*60*60))
|
|
thisDateTime = time.mktime(thisDate)
|
|
dayAppointments = [x for x in appointments \
|
|
if x.start == thisDateTime]
|
|
if len(dayAppointments) == 0:
|
|
if i in (5, 6):
|
|
tr += X.td(_class = 'weekend')
|
|
else:
|
|
tr += X.td()
|
|
continue
|
|
allDay = 1
|
|
content = X.array()
|
|
ul = X.ul()
|
|
for d in dayAppointments:
|
|
ul += X.li(X.a(href = X.idUrl(d.id))(d.getTitle()))
|
|
content += ul
|
|
className = 'hour-busy'
|
|
if i in (5, 6):
|
|
className += ' weekend'
|
|
tr += X.td(_class = className)(content)
|
|
if allDay:
|
|
tbody += tr
|
|
|
|
for hour in range(8, 21):
|
|
# TODO: "opening hours" should be an option
|
|
# TODO: should also show (where) appointments that are not in the
|
|
# opening hours
|
|
tr = X.tr()
|
|
tbody += tr
|
|
tr += X.th(scope = 'row')('%02d:00' % hour)
|
|
for i in range(7):
|
|
if daysFilled[i]:
|
|
daysFilled[i] -= 1
|
|
continue
|
|
thisDate = list(time.localtime(weekTime + i*24*60*60))
|
|
thisDate[3] = hour
|
|
nextDate = thisDate[:]
|
|
nextDate[3] += 1
|
|
maxEnd = time.mktime(nextDate)
|
|
previousDuration = 1
|
|
while 1:
|
|
hourAppointments = [x for x in appointments if \
|
|
x.start >= time.mktime(thisDate) and \
|
|
x.start < time.mktime(nextDate)]
|
|
maxEnd = max([maxEnd] + [x.end for x in hourAppointments])
|
|
if time.mktime(nextDate) < maxEnd:
|
|
nextDate = time.localtime(maxEnd)
|
|
else:
|
|
break
|
|
if len(hourAppointments) == 0:
|
|
if i in (5, 6):
|
|
tr += X.td(_class = 'weekend')
|
|
else:
|
|
tr += X.td()
|
|
continue
|
|
nbHour = int( math.ceil(
|
|
(time.mktime(nextDate) - time.mktime(thisDate)) / 3600))
|
|
daysFilled[i] = nbHour-1
|
|
|
|
hourContent = X.array()
|
|
ul = X.ul()
|
|
for d in hourAppointments:
|
|
ul += X.li(X.a(href = X.idUrl(d.id))(d.getTitle()))
|
|
hourContent += ul
|
|
className = 'hour-busy'
|
|
if i in (5, 6):
|
|
className += ' weekend'
|
|
td = X.td(_class = className)(hourContent)
|
|
if nbHour > 1:
|
|
td.setAttribute('rowspan', nbHour)
|
|
tr += td
|
|
|
|
layout = X.array(table)
|
|
month = time.localtime(weekTime)[1]
|
|
layout += self.getLinksLayout(year = year, month = month,
|
|
exclude = ['week'])
|
|
dayStart = self.formatDate(weekTuple)
|
|
dayEnd = self.formatDate(time.localtime( \
|
|
time.mktime(weekTuple)+6*24*60*60))
|
|
return writePageLayout(layout, '%s - %s' % (dayStart, dayEnd))
|
|
week.isPublicForWeb = 1
|
|
|
|
def getSmallMonthLayout(self, year = 0, month = 0):
|
|
today = list(time.localtime(time.time())[:3])
|
|
if not year or not month:
|
|
year, month = today[:2]
|
|
|
|
year, month = int(year), int(month)
|
|
table = X.table(_class = 'calendar-month-small')
|
|
|
|
prevYear, prevMonth = year, month-1
|
|
if prevMonth == 0:
|
|
prevMonth, prevYear = 12, prevYear - 1
|
|
nextYear, nextMonth = year, month+1
|
|
if nextMonth == 13:
|
|
nextMonth, nextYear = 1, nextYear + 1
|
|
|
|
appointments = self.getAppointments(
|
|
time.mktime((year, month, 1, 0, 0, 0, 0, 0, 0)),
|
|
time.mktime((nextYear, nextMonth, 1, 0, 0, 0, 0, 0, 0)) ).values()
|
|
|
|
caption = X.caption()
|
|
caption += X.a(href = X.roleUrl(self.serverRole, 'month/%d/%d' % (
|
|
year, month)))(
|
|
'%s %s' % (calendaring.getMonthLabel(month), year))
|
|
table += caption
|
|
|
|
tr = X.tr()
|
|
for wd in calendaring.dayLabels:
|
|
tr += X.th(scope = 'col')(_(wd)[0])
|
|
table += X.thead(tr)
|
|
|
|
cal = calendar.monthcalendar(year, month)
|
|
|
|
tbody = X.tbody()
|
|
table += tbody
|
|
for i in range(len(cal)):
|
|
tr = X.tr()
|
|
tbody += tr
|
|
|
|
for j in range(len(cal[i])):
|
|
d = cal[i][j]
|
|
if d == 0:
|
|
dayLabel = ''
|
|
tr += X.td(_class = 'noday')
|
|
continue
|
|
else:
|
|
dayLabel = str(d)
|
|
thisDate = [year, month, d]
|
|
nextDate = [year, month, d+1]
|
|
timeStart = time.mktime(thisDate + [0]*5 +
|
|
[time.localtime()[-1]])
|
|
timeEnd = time.mktime(nextDate + [0]*5 +
|
|
[time.localtime()[-1]])
|
|
dayAppointments = [x for x in appointments if \
|
|
x.start >= timeStart and x.start < timeEnd]
|
|
className = ''
|
|
if thisDate == today:
|
|
className += 'today'
|
|
elif thisDate < today:
|
|
className += 'past'
|
|
else:
|
|
className += 'future'
|
|
if len(dayAppointments) == 0:
|
|
dayContent = X.asIs(dayLabel)
|
|
elif len(dayAppointments) == 1:
|
|
className += ' busy'
|
|
app = dayAppointments[0]
|
|
dayContent = X.a(href = X.idUrl(app.id),
|
|
title = app.getLabelTranslated(withHour = 1))(
|
|
dayLabel)
|
|
else:
|
|
className += ' busy'
|
|
title = ', '.join([x.getLabelTranslated() for x in dayAppointments])
|
|
dayContent = X.a(
|
|
href = '/%s/day/%d/%d/%d' % (
|
|
self.serverRole, year, month, d),
|
|
title = title)(dayLabel)
|
|
|
|
className += ' d%d' % (j+1)
|
|
if j in (5, 6):
|
|
className += ' weekend'
|
|
tr += X.td(_class = className)(dayContent)
|
|
|
|
layout = X.array(table)
|
|
|
|
return layout
|
|
|
|
|
|
def month(self, year, month):
|
|
webTools.addContextualHeader('calendar.css')
|
|
canAdd = self.canAddObject()
|
|
|
|
today = list(time.localtime(time.time())[:3])
|
|
|
|
year, month = int(year), int(month)
|
|
table = X.table(_class = 'calendar-month-full')
|
|
|
|
prevYear, prevMonth = year, month-1
|
|
if prevMonth == 0:
|
|
prevMonth, prevYear = 12, prevYear - 1
|
|
nextYear, nextMonth = year, month+1
|
|
if nextMonth == 13:
|
|
nextMonth, nextYear = 1, nextYear + 1
|
|
|
|
appointments = self.getAppointments(
|
|
time.mktime((year, month, 1, 0, 0, 0, 0, 0, 0)),
|
|
time.mktime((nextYear, nextMonth, 1, 0, 0, 0, 0, 0, 0)) ).values()
|
|
|
|
caption = X.caption()
|
|
caption += X.a(href = X.actionUrl('month/%d/%d' % (
|
|
prevYear, prevMonth)), _class = 'previous-month')('<<')
|
|
caption += X.span()('%s %s' % (calendaring.getMonthLabel(month), year))
|
|
caption += X.a(href = X.actionUrl('month/%d/%d' % (
|
|
nextYear, nextMonth)), _class = 'next-month')('>>')
|
|
table += caption
|
|
|
|
tr = X.tr()
|
|
tr += X.th()
|
|
for wd in calendaring.dayLabels:
|
|
tr += X.th(scope = 'col')(_(wd))
|
|
table += X.thead(tr)
|
|
|
|
cal = calendar.monthcalendar(year, month)
|
|
weekNumber = dateToWeekNumber(year, month, 1) [1]
|
|
|
|
tbody = X.tbody()
|
|
table += tbody
|
|
for i in range(len(cal)):
|
|
tr = X.tr()
|
|
tbody += tr
|
|
|
|
weekLabel = X.a(href = X.actionUrl('week/%d/%d' % (
|
|
year, weekNumber)))(weekNumber)
|
|
tr += X.th(scope = 'row')(weekLabel)
|
|
weekNumber += 1
|
|
if weekNumber == 53:
|
|
weekNumber = 1
|
|
|
|
for j in range(len(cal[i])):
|
|
d = cal[i][j]
|
|
if d == 0:
|
|
dayLabel = ''
|
|
tr += X.td(_class = 'noday')
|
|
continue
|
|
else:
|
|
dayLabel = str(d)
|
|
thisDate = [year, month, d]
|
|
nextDate = [year, month, d+1]
|
|
if canAdd:
|
|
dayLabel = X.array(
|
|
X.asIs(dayLabel),
|
|
X.a(href = X.actionUrl('edit').add(
|
|
'start',
|
|
'%d-%02d-%02d' % (year, month, d)).add(
|
|
'nextUri', cleanUpUnparsedUri([])))('+'))
|
|
dayContent = X.array()
|
|
dayContent += X.span(_class = 'day-title')(dayLabel)
|
|
|
|
thisDateTime = time.mktime(thisDate + [0]*5 +
|
|
[time.localtime()[-1]] )
|
|
nextDateTime = time.mktime(nextDate + [0]*5 +
|
|
[time.localtime()[-1]] )
|
|
dayAppointments = [x for x in appointments if \
|
|
(x.start >= thisDateTime and
|
|
x.start < nextDateTime )
|
|
or ( x.end and
|
|
x.start < thisDateTime and
|
|
x.end >= nextDateTime ) ]
|
|
|
|
if len(dayAppointments):
|
|
ul = X.ul()
|
|
for d in dayAppointments:
|
|
hasDetails = 0
|
|
li = X.li()
|
|
disp, role, id = commonTools.splitId(d.id)
|
|
idAttribute = 'appointment-%s-%s' % (disp, id)
|
|
li += X.a(href = X.idUrl(d.id), id = idAttribute)(
|
|
d.getLabelTranslated(withHour = 1))
|
|
details = X.ul(_class = 'appointment-details tooltip',
|
|
id = 'for-' + idAttribute)
|
|
start = d.getBeginningHourAndMinute()
|
|
end = d.getEndHourAndMinute()
|
|
if start and end:
|
|
details += X.li(_('Hour: %s - %s') % (start, end))
|
|
hasDetails = 1
|
|
if d.body:
|
|
details += X.li(d.body)
|
|
hasDetails = 1
|
|
if hasDetails and \
|
|
context.getVar('virtualHost').showTooltips:
|
|
webTools.addContextualHeader('tooltips.js')
|
|
li += details
|
|
ul += li
|
|
dayContent += ul
|
|
className = ''
|
|
if thisDate == today:
|
|
className = 'today'
|
|
elif thisDate < today:
|
|
className = 'past'
|
|
else:
|
|
className = 'future'
|
|
className += ' d%d' % (j+1)
|
|
if j in (5, 6):
|
|
className += ' weekend'
|
|
tr += X.td(_class = className)(dayContent)
|
|
|
|
layout = X.array(table)
|
|
layout += self.getLinksLayout(year = year, month = month,
|
|
exclude = ['month'])
|
|
|
|
return writePageLayout(layout,
|
|
'%s %s' % (calendaring.getMonthLabel(month), year))
|
|
month.isPublicForWeb = 1
|
|
|
|
def getLinksLayout(self, year = None, month = None, day = None, exclude = None):
|
|
todayYear, todayMonth, todayDay = list(time.localtime(time.time())[:3])
|
|
thisYear = year or todayYear
|
|
thisMonth = month or todayMonth
|
|
thisDay = day or todayDay
|
|
thisWeek = dateToWeekNumber(thisYear, thisMonth, thisDay) [1]
|
|
dayLink = X.a(href = X.actionUrl('day/%d/%d/%d' % (
|
|
thisYear, thisMonth, thisDay))) (_('Day'))
|
|
weekLink = X.a(href = X.actionUrl('week/%d/%d' % (
|
|
thisYear, thisWeek))) (_('Week'))
|
|
monthLink = X.a(href = X.actionUrl('month/%d/%d' % (
|
|
thisYear, thisMonth))) (_('Month'))
|
|
yearLink = X.a(href = X.actionUrl('year/%d' % thisYear)) (_('Year'))
|
|
ul = X.ul(_class = 'calendar-links')
|
|
if exclude is None:
|
|
exclude = []
|
|
if not 'agenda' in exclude:
|
|
ul += X.li( X.a(href = X.actionUrl('agenda')) (_('Agenda')))
|
|
if not 'day' in exclude:
|
|
ul += X.li(dayLink)
|
|
if not 'week' in exclude:
|
|
ul += X.li(weekLink)
|
|
if not 'month' in exclude:
|
|
ul += X.li(monthLink)
|
|
if not 'year' in exclude:
|
|
ul += X.li(yearLink)
|
|
return X.array(ul, self.getViewAllButtonsBarLayout())
|
|
|
|
def submitAddObject(self, object):
|
|
result = ObjectsWebMixin.submitAddObject(self, object)
|
|
if result:
|
|
return result
|
|
groupsProxy = getProxyForServerRole('groups')
|
|
for groupId in object.categoriesSet or []:
|
|
groupsProxy.addObjectMember(groupId, object.id)
|
|
# what happens if it fails ?
|
|
|
|
def submitModifyObject(self, object):
|
|
result = ObjectsWebMixin.submitModifyObject(self, object)
|
|
if result:
|
|
return result
|
|
groupsProxy = getProxyForServerRole('groups')
|
|
pastGroups = groupsProxy.getObjectIdsWithContent(object.id) or []
|
|
nowGroups = object.categoriesSet or []
|
|
done = []
|
|
for groupId in pastGroups + nowGroups:
|
|
if groupId in done:
|
|
continue
|
|
done.append(groupId)
|
|
if groupId not in pastGroups:
|
|
# adding
|
|
groupsProxy.addObjectMember(groupId, object.id)
|
|
continue
|
|
if groupId not in nowGroups:
|
|
# removing
|
|
groupsProxy.deleteObjectMember(groupId, object.id)
|
|
continue
|
|
|
|
def vcal(self, id = ''):
|
|
method = context.getVar('httpMethod')
|
|
req = context.getVar('req')
|
|
if id:
|
|
if not self.hasObject(id):
|
|
return pageNotFound()
|
|
if not self.canGetObject(id):
|
|
return accessForbidden()
|
|
if method == 'DELETE':
|
|
try:
|
|
self.deleteObject(id)
|
|
except faults.Fault:
|
|
return HTTP_FORBIDDEN
|
|
return HTTP_NO_CONTENT
|
|
elif method == 'PUT':
|
|
vCal = req.read()
|
|
# TODO: sending vCalendar to
|
|
# appointmentsServer.modifyObjectWithvCal(id, vCal)
|
|
return HTTP_NOT_IMPLEMENTED
|
|
objects = [self.getObject(id)]
|
|
else:
|
|
if method == 'DELETE':
|
|
return HTTP_FORBIDDEN
|
|
if method == 'PUT':
|
|
vCal = req.read()
|
|
self.updateFromVCalendar(vCal)
|
|
return HTTP_NO_CONTENT
|
|
objects = self.getObjects().values()
|
|
req.content_type = 'text/calendar; charset=iso-8859-15'
|
|
req.send_http_header()
|
|
if req.method == 'HEAD':
|
|
return OK
|
|
if req.caching:
|
|
req.openCachePage()
|
|
|
|
for object in objects:
|
|
req.write("""BEGIN:VCALENDAR
|
|
VERSION:2.0
|
|
PROPID:-//glasnost.entrouvert.org/NONSGML Glasnost Calendar//EN
|
|
METHOD:PUBLISH
|
|
""")
|
|
req.write(object.toVevent())
|
|
req.write("""END:VCALENDAR\n""")
|
|
|
|
if req.caching:
|
|
req.closeCachePage()
|
|
return OK
|
|
vcal.isPublicForWeb = ('GET', 'DELETE', 'PUT')
|
|
|
|
def viewAll(self):
|
|
year, month = time.localtime(time.time())[:2]
|
|
return self.month(year, month)
|
|
viewAll.isPublicForWeb = 1
|
|
|
|
def year(self, year):
|
|
try:
|
|
year = int(year)
|
|
except:
|
|
return pageNotFound()
|
|
ul = X.ul()
|
|
for i in range(12):
|
|
ul += X.li( X.a(href = X.actionUrl('month/%d/%d' % (year, i+1))) (
|
|
'%s %s' % (calendaring.getMonthLabel(i+1), year)))
|
|
layout = X.array()
|
|
layout += ul
|
|
layout += self.getLinksLayout(year = year, exclude = ['year'])
|
|
|
|
return writePageLayout(layout, '%s' % year)
|
|
year.isPublicForWeb = 1
|
|
|