diff --git a/fields/calendar.py b/fields/calendar.py
index 55e7b14..4239947 100644
--- a/fields/calendar.py
+++ b/fields/calendar.py
@@ -8,6 +8,24 @@ from appy.px import Px
from DateTime import DateTime
from BTrees.IOBTree import IOBTree
from persistent.list import PersistentList
+from persistent import Persistent
+
+# ------------------------------------------------------------------------------
+class Timeslot:
+ '''A timeslot defines a time range within a single day'''
+ def __init__(self, id, start=None, end=None, name=None, eventTypes=None):
+ # A short, human-readable string identifier, unique among all timeslots
+ # for a given Calendar. Id "main" is reserved for the main timeslot that
+ # represents the whole day.
+ self.id = id
+ # The time range can be defined by p_start ~(i_hour, i_minute)~ and
+ # p_end (idem), or by a simple name, like "AM" or "PM".
+ self.start = start
+ self.end = end
+ self.name = name or id
+ # The event types (among all event types defined at the Calendar level)
+ # that can be assigned to this slot.
+ self.eventTypes = eventTypes # "None" means "all"
# ------------------------------------------------------------------------------
class Other:
@@ -49,16 +67,33 @@ class Other:
info.name = eventNames[eventType]
res.append(info)
+# ------------------------------------------------------------------------------
+class Event(Persistent):
+ '''An event as will be stored in the database'''
+ def __init__(self, eventType, timeslot='main'):
+ self.eventType = eventType
+ self.timeslot = timeslot
+
+ def getName(self, allEventNames):
+ '''Gets the name for this event, that depends on it type and may include
+ the timeslot if not "main".'''
+ res = allEventNames[self.eventType]
+ if self.timeslot != 'main': res += ' ' + self.timeslot
+ return res
+
# ------------------------------------------------------------------------------
class Calendar(Field):
'''This field allows to produce an agenda (monthly view) and view/edit
events on it.'''
jsFiles = {'view': ('calendar.js',)}
DateTime = DateTime
- Other = Other # Access to the Other class via the Calendar class
+ # Access to Calendar utility classes via the Calendar class
+ Timeslot = Timeslot
+ Other = Other
+ Event = Event
IterSub = IterSub
- timelineBgColors = {'Fri': '#a6a6a6', 'Sat': '#c0c0c0', 'Sun': '#c0c0c0'}
+ timelineBgColors = {'Fri': '#dedede', 'Sat': '#c0c0c0', 'Sun': '#c0c0c0'}
# For timeline rendering, the row displaying month names
pxTimeLineMonths = Px('''
@@ -120,27 +155,114 @@ class Calendar(Field):
|
-
+
-
-
-
:allEventNames[eventType]
+ src=":url(single and 'delete' or 'deleteMany')"
+ onclick=":'openEventPopup(%s, %s, %s, %s, %s, null, null)' % \
+ (q('del'), q(field.name), q(dayString), q('main'), q(spansDays))"/>
+
+
+
+
:event.getName(allEventNames)
+
+
+
-
-
- ''')
+
+
+ :field.pxAddEvent:field.pxDelEvent''')
pxView = pxCell = Px('''
1:
+ events.data.sort(key=lambda e: timeslots.index(e.timeslot))
+ events._p_changed = 1
# Span the event on the successive days if required
- if handleEventSpan and eventSpan:
+ if handleEventSpan and eventSpan and (timeslot != 'main'):
nbOfDays = min(int(eventSpan), self.maxEventLength)
for i in range(nbOfDays):
date = date + 1
- self.createEvent(obj, date, handleEventSpan=False)
+ self.createEvent(obj, date, timeslot, handleEventSpan=False)
def mayDelete(self, obj, events):
'''May the user delete p_events?'''
@@ -819,25 +942,38 @@ class Calendar(Field):
if callable(self.delete): return self.delete(obj, events[0].eventType)
return True
- def deleteEvent(self, obj, date, handleEventSpan=True):
- '''Deletes an event. It actually deletes all events at p_date.
- If p_handleEventSpan is True, we will use rq["deleteNext"] to
- delete successive events, too.'''
- obj = obj.o # Ensure p_obj is not a wrapper.
+ def deleteEvent(self, obj, date, timeslot, handleEventSpan=True):
+ '''Deletes an event. If t_timeslot is "main", it deletes all events at
+ p_date, be there a single event on the main timeslot or several
+ events on other timeslots. Else, it only deletes the event at
+ p_timeslot. If p_handleEventSpan is True, we will use
+ rq["deleteNext"] to delete successive events, too.'''
+ obj = obj.o # Ensure p_obj is not a wrapper
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()]
- rq = obj.REQUEST
- 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:
+ if timeslot == 'main':
+ # Delete all events; delete them also in the following days when
+ # relevant.
+ del daysDict[date.day()]
+ rq = obj.REQUEST
+ 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, timeslot,
+ handleEventSpan=False)
+ else:
+ break
+ else:
+ # Delete the event at p_timeslot
+ i = len(events) - 1
+ while i >= 0:
+ if events[i].timeslot == timeslot:
+ del events[i]
break
+ i -= 1
def process(self, obj):
'''Processes an action coming from the calendar widget, ie, the creation
@@ -846,11 +982,13 @@ class Calendar(Field):
action = rq['actionType']
# Security check
obj.mayEdit(self.writePermission, raiseError=True)
- # Get the date for this action
+ # Get the date and timeslot for this action
+ date = DateTime(rq['day'])
+ timeslot = rq.get('timeslot', 'main')
if action == 'createEvent':
- return self.createEvent(obj, DateTime(rq['day']))
+ return self.createEvent(obj, date, timeslot)
elif action == 'deleteEvent':
- return self.deleteEvent(obj, DateTime(rq['day']))
+ return self.deleteEvent(obj, date, timeslot)
def getColumnStyle(self, obj, date, render, today):
'''What style(s) must apply to the table column representing p_date
diff --git a/gen/tr/Appy.pot b/gen/tr/Appy.pot
index 8fdba99..abf4704 100644
--- a/gen/tr/Appy.pot
+++ b/gen/tr/Appy.pot
@@ -715,6 +715,14 @@ msgstr ""
msgid "del_next_events"
msgstr ""
+#. Default: "Timeslot"
+msgid "timeslot"
+msgstr ""
+
+#. Default: "All day"
+msgid "timeslot_main"
+msgstr ""
+
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr ""
diff --git a/gen/tr/ar.po b/gen/tr/ar.po
index 188e71b..0db6f44 100644
--- a/gen/tr/ar.po
+++ b/gen/tr/ar.po
@@ -715,6 +715,14 @@ msgstr ""
msgid "del_next_events"
msgstr ""
+#. Default: "Timeslot"
+msgid "timeslot"
+msgstr ""
+
+#. Default: "All day"
+msgid "timeslot_main"
+msgstr ""
+
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr ""
diff --git a/gen/tr/de.po b/gen/tr/de.po
index b1b9cd6..21f8ea1 100644
--- a/gen/tr/de.po
+++ b/gen/tr/de.po
@@ -715,6 +715,14 @@ msgstr ""
msgid "del_next_events"
msgstr ""
+#. Default: "Timeslot"
+msgid "timeslot"
+msgstr ""
+
+#. Default: "All day"
+msgid "timeslot_main"
+msgstr ""
+
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr ""
diff --git a/gen/tr/en.po b/gen/tr/en.po
index 572b94a..be7c2a0 100644
--- a/gen/tr/en.po
+++ b/gen/tr/en.po
@@ -716,6 +716,14 @@ msgstr "Extend the event on the following number of days (leave blank to create
msgid "del_next_events"
msgstr "Also delete successive events of the same type."
+#. Default: "Timeslot"
+msgid "timeslot"
+msgstr "Timeslot"
+
+#. Default: "All day"
+msgid "timeslot_main"
+msgstr "All day"
+
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr "Inserted by ${userName}"
diff --git a/gen/tr/es.po b/gen/tr/es.po
index de529a9..8f1ccba 100644
--- a/gen/tr/es.po
+++ b/gen/tr/es.po
@@ -715,6 +715,14 @@ msgstr ""
msgid "del_next_events"
msgstr ""
+#. Default: "Timeslot"
+msgid "timeslot"
+msgstr ""
+
+#. Default: "All day"
+msgid "timeslot_main"
+msgstr ""
+
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr ""
diff --git a/gen/tr/fr.po b/gen/tr/fr.po
index a1dfdeb..eadd2bd 100644
--- a/gen/tr/fr.po
+++ b/gen/tr/fr.po
@@ -716,6 +716,14 @@ msgstr "Étendre l'événement sur le nombre de jours suivants (laissez vide pou
msgid "del_next_events"
msgstr "Supprimer aussi les événements successifs de même type"
+#. Default: "Timeslot"
+msgid "timeslot"
+msgstr "Plage horaire"
+
+#. Default: "All day"
+msgid "timeslot_main"
+msgstr "Toute la journée"
+
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr "Inséré par ${userName}"
diff --git a/gen/tr/it.po b/gen/tr/it.po
index 3066452..5e526c0 100644
--- a/gen/tr/it.po
+++ b/gen/tr/it.po
@@ -715,6 +715,14 @@ msgstr ""
msgid "del_next_events"
msgstr ""
+#. Default: "Timeslot"
+msgid "timeslot"
+msgstr ""
+
+#. Default: "All day"
+msgid "timeslot_main"
+msgstr ""
+
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr ""
diff --git a/gen/tr/nl.po b/gen/tr/nl.po
index fb0bae4..12d5dca 100644
--- a/gen/tr/nl.po
+++ b/gen/tr/nl.po
@@ -715,6 +715,14 @@ msgstr "Het event uitbreiden naar de volgende dagen (leeg laten om een event aan
msgid "del_next_events"
msgstr "Verwijder ook alle opeenvolgende events van hetzelfde type"
+#. Default: "Timeslot"
+msgid "timeslot"
+msgstr ""
+
+#. Default: "All day"
+msgid "timeslot_main"
+msgstr ""
+
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr "Ingevuld door ${userName}"
diff --git a/gen/ui/calendar.js b/gen/ui/calendar.js
index 20e9d30..df0e739 100644
--- a/gen/ui/calendar.js
+++ b/gen/ui/calendar.js
@@ -14,26 +14,28 @@ function askCalendar(hookId, objectUrl, render, fieldName, month) {
askAjaxChunk(hookId, 'GET', objectUrl, fieldName+':pxView', params);
}
-function openEventPopup(action, fieldName, day, spansDays,
+function openEventPopup(action, fieldName, day, timeslot, spansDays,
applicableEventTypes, message) {
/* Opens the popup for creating (or deleting, depending on p_action) a
- calendar event at some p_day. When action is "del", we need to know
- (from p_spansDays) if the event spans more days, in order to propose a
- checkbox allowing to delete events for those successive days. When action
- is "new", a possibly restricted list of applicable event types for this
- day is given in p_applicableEventTypes; p_message contains an optional
- message explaining why not applicable types are not applicable. */
+ calendar event at some p_day. When action is "del", we need to know the
+ p_timeslot where the event is assigned and if the event spans more days
+ (from p_spansDays), in order to propose a checkbox allowing to delete
+ events for those successive days. When action is "new", a possibly
+ restricted list of applicable event types for this day is given in
+ p_applicableEventTypes; p_message contains an optional message explaining
+ why not applicable types are not applicable. */
var prefix = fieldName + '_' + action + 'Event';
var f = document.getElementById(prefix + 'Form');
f.day.value = day;
if (action == 'del') {
- // Show or hide the checkbox for deleting the event for successive days.
+ f.timeslot.value = timeslot;
+ // Show or hide the checkbox for deleting the event for successive days
var elem = document.getElementById(prefix + 'DelNextEvent');
var cb = elem.getElementsByTagName('input');
cb[0].checked = false;
cb[1].value = 'False';
- if (spansDays == 'True') { elem.style.display = 'block' }
- else { elem.style.display = 'none' }
+ if (spansDays == 'True') elem.style.display = 'block';
+ else elem.style.display = 'none';
}
else if (action == 'new') {
// First: reinitialise input fields
@@ -42,8 +44,8 @@ function openEventPopup(action, fieldName, day, spansDays,
for (var i=0; i < allOptions.length; i++) {
allOptions[i].selected = false;
}
- f.eventSpan.style.background = '';
- // Among all event types, show applicable ones and hide the others.
+ if (f.eventSpan) f.eventSpan.style.background = '';
+ // Among all event types, show applicable ones and hide the others
var applicable = applicableEventTypes.split(',');
var applicableDict = {};
for (var i=0; i < applicable.length; i++) {
@@ -76,13 +78,15 @@ function triggerCalendarEvent(action, hookId, fieldName, objectUrl,
f.eventType.style.background = wrongTextInput;
return;
}
- // Check that eventSpan is empty or contains a valid number
- var spanNumber = f.eventSpan.value.replace(' ', '');
- if (spanNumber) {
- spanNumber = parseInt(spanNumber);
- if (isNaN(spanNumber) || (spanNumber > maxEventLength)) {
- f.eventSpan.style.background = wrongTextInput;
- return;
+ if (f.eventSpan) {
+ // Check that eventSpan is empty or contains a valid number
+ var spanNumber = f.eventSpan.value.replace(' ', '');
+ if (spanNumber) {
+ spanNumber = parseInt(spanNumber);
+ if (isNaN(spanNumber) || (spanNumber > maxEventLength)) {
+ f.eventSpan.style.background = wrongTextInput;
+ return;
+ }
}
}
}