[gen] Calendar field: added a validation mechanism.

This commit is contained in:
Gaetan Delannay 2015-03-04 14:35:02 +01:00
parent d1aec8d5e6
commit 09bf03f9bf
13 changed files with 251 additions and 65 deletions

View file

@ -2,7 +2,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import types import types
from appy import Object from appy import Object
from appy.shared.utils import splitList, IterSub from appy.shared import utils as sutils
from appy.gen import Field from appy.gen import Field
from appy.px import Px from appy.px import Px
from DateTime import DateTime from DateTime import DateTime
@ -33,6 +33,22 @@ class Timeslot:
if not self.eventTypes: return True if not self.eventTypes: return True
return eventType in self.eventTypes return eventType in self.eventTypes
# ------------------------------------------------------------------------------
class Validation:
'''The validation process for a calendar consists in "converting" some event
types being "wishes" to other event types being the corresponding
validated events. This class holds information about this validation
process. For more information, see the Calendar constructor, parameter
"validation".'''
def __init__(self, method, schema):
# p_method holds a method that must return True if the currently logged
# user can validate whish events.
self.method = method
# p_schema must hold a dict whose keys are the event types being wishes
# and whose values are the event types being the corresponding validated
# event types.
self.schema = schema
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
class Other: class Other:
'''Identifies a Calendar field that must be shown within another Calendar '''Identifies a Calendar field that must be shown within another Calendar
@ -104,9 +120,10 @@ class Calendar(Field):
DateTime = DateTime DateTime = DateTime
# Access to Calendar utility classes via the Calendar class # Access to Calendar utility classes via the Calendar class
Timeslot = Timeslot Timeslot = Timeslot
Validation = Validation
Other = Other Other = Other
Event = Event Event = Event
IterSub = IterSub IterSub = sutils.IterSub
# Error messages # Error messages
TIMESLOT_USED = 'An event is already defined at this timeslot.' TIMESLOT_USED = 'An event is already defined at this timeslot.'
DAY_FULL = 'No more place for adding this event.' DAY_FULL = 'No more place for adding this event.'
@ -155,6 +172,7 @@ class Calendar(Field):
# Timeline view for a calendar # Timeline view for a calendar
pxViewTimeline = Px(''' pxViewTimeline = Px('''
<table cellpadding="0" cellspacing="0" class="list timeline" <table cellpadding="0" cellspacing="0" class="list timeline"
id=":ajaxHookId + '_cal'"
var="monthsInfos=field.getTimelineMonths(grid, zobj)"> var="monthsInfos=field.getTimelineMonths(grid, zobj)">
<!-- Column specifiers --> <!-- Column specifiers -->
<colgroup> <colgroup>
@ -202,14 +220,12 @@ class Calendar(Field):
# Popup for adding an event in the month view # Popup for adding an event in the month view
pxAddPopup = Px(''' pxAddPopup = Px('''
<div var="prefix='%s_newEvent' % field.name; <div var="popupId=ajaxHookId + '_new'"
popupId=prefix + 'Popup'"
id=":popupId" class="popup" align="center"> id=":popupId" class="popup" align="center">
<form id=":prefix + 'Form'" method="post"> <form id=":popupId + 'Form'" method="post" action="/process">
<input type="hidden" name="fieldName" value=":field.name"/> <input type="hidden" name="fieldName" value=":field.name"/>
<input type="hidden" name="month" value=":month"/> <input type="hidden" name="month" value=":month"/>
<input type="hidden" name="name" value=":field.name"/> <input type="hidden" name="name" value=":field.name"/>
<input type="hidden" name="action" value="process"/>
<input type="hidden" name="actionType" value="createEvent"/> <input type="hidden" name="actionType" value="createEvent"/>
<input type="hidden" name="day"/> <input type="hidden" name="day"/>
@ -236,9 +252,8 @@ class Calendar(Field):
</div> </div>
<input type="button" <input type="button"
value=":_('object_save')" value=":_('object_save')"
onclick=":'triggerCalendarEvent(%s, %s, %s, %s, \ onclick=":'triggerCalendarEvent(%s, %s, %s_maxEventLength)' % \
%s_maxEventLength)' % (q('new'), q(ajaxHookId), \ (q(ajaxHookId), q('new'), field.name)"/>
q(field.name), q(objUrl), field.name)"/>
<input type="button" <input type="button"
value=":_('object_cancel')" value=":_('object_cancel')"
onclick=":'closePopup(%s)' % q(popupId)"/> onclick=":'closePopup(%s)' % q(popupId)"/>
@ -247,14 +262,12 @@ class Calendar(Field):
# Popup for removing events in the month view # Popup for removing events in the month view
pxDelPopup = Px(''' pxDelPopup = Px('''
<div var="prefix='%s_delEvent' % field.name; <div var="popupId=ajaxHookId + '_del'"
popupId=prefix + 'Popup'"
id=":popupId" class="popup" align="center"> id=":popupId" class="popup" align="center">
<form id=":prefix + 'Form'" method="post"> <form id=":popupId + 'Form'" method="post" action="/process">
<input type="hidden" name="fieldName" value=":field.name"/> <input type="hidden" name="fieldName" value=":field.name"/>
<input type="hidden" name="month" value=":month"/> <input type="hidden" name="month" value=":month"/>
<input type="hidden" name="name" value=":field.name"/> <input type="hidden" name="name" value=":field.name"/>
<input type="hidden" name="action" value="process"/>
<input type="hidden" name="actionType" value="deleteEvent"/> <input type="hidden" name="actionType" value="deleteEvent"/>
<input type="hidden" name="timeslot" value="main"/> <input type="hidden" name="timeslot" value="main"/>
<input type="hidden" name="day"/> <input type="hidden" name="day"/>
@ -263,8 +276,8 @@ class Calendar(Field):
<!-- Delete successive events ? --> <!-- Delete successive events ? -->
<div class="discreet" style="margin-bottom: 10px" <div class="discreet" style="margin-bottom: 10px"
id=":prefix + 'DelNextEvent'" id=":ajaxHookId + '_DelNextEvent'"
var="cbId=prefix + '_cb'; hdId=prefix + '_hd'"> var="cbId=popupId + '_cb'; hdId=popupId + '_hd'">
<input type="checkbox" name="deleteNext_cb" id=":cbId" <input type="checkbox" name="deleteNext_cb" id=":cbId"
onClick=":'toggleCheckbox(%s, %s)' % (q(cbId), q(hdId))"/> onClick=":'toggleCheckbox(%s, %s)' % (q(cbId), q(hdId))"/>
<input type="hidden" id=":hdId" name="deleteNext"/> <input type="hidden" id=":hdId" name="deleteNext"/>
@ -272,8 +285,8 @@ class Calendar(Field):
style="text-transform: none">:_('del_next_events')</label> style="text-transform: none">:_('del_next_events')</label>
</div> </div>
<input type="button" value=":_('yes')" <input type="button" value=":_('yes')"
onClick=":'triggerCalendarEvent(%s, %s, %s, %s)' % \ onClick=":'triggerCalendarEvent(%s, %s)' % \
(q('del'), q(ajaxHookId), q(field.name), q(objUrl))"/> (q(ajaxHookId), q('del'))"/>
<input type="button" value=":_('no')" <input type="button" value=":_('no')"
onclick=":'closePopup(%s)' % q(popupId)"/> onclick=":'closePopup(%s)' % q(popupId)"/>
</form> </form>
@ -282,7 +295,7 @@ class Calendar(Field):
# Month view for a calendar # Month view for a calendar
pxViewMonth = Px(''' pxViewMonth = Px('''
<table cellpadding="0" cellspacing="0" width="100%" class="list" <table cellpadding="0" cellspacing="0" width="100%" class="list"
style="font-size: 95%" style="font-size: 95%" id=":ajaxHookId + '_cal'"
var="rowHeight=int(field.height/float(len(grid)))"> var="rowHeight=int(field.height/float(len(grid)))">
<!-- 1st row: names of days --> <!-- 1st row: names of days -->
<tr height="22px"> <tr height="22px">
@ -321,23 +334,28 @@ class Calendar(Field):
var2="freeSlots=field.getFreeSlotsAt(date, events, slotIds,\ var2="freeSlots=field.getFreeSlotsAt(date, events, slotIds,\
slotIdsStr, True)" slotIdsStr, True)"
onclick=":'openEventPopup(%s,%s,%s,null,null,%s,%s,%s)' % \ onclick=":'openEventPopup(%s,%s,%s,null,null,%s,%s,%s)' % \
(q('new'), q(field.name), q(dayString), q(info.eventTypes), \ (q(ajaxHookId), q('new'), q(dayString), q(info.eventTypes), \
q(info.message), q(freeSlots))"/> q(info.message), q(freeSlots))"/>
</x> </x>
<!-- Icon for deleting event(s) --> <!-- Icon for deleting event(s) -->
<img if="mayDelete" class="clickable" style="visibility:hidden" <img if="mayDelete" class="clickable" style="visibility:hidden"
src=":url(single and 'delete' or 'deleteMany')" src=":url(single and 'delete' or 'deleteMany')"
onclick=":'openEventPopup(%s,%s,%s,%s,%s)' % (q('del'), \ onclick=":'openEventPopup(%s,%s,%s,%s,%s)' % (q(ajaxHookId), \
q(field.name), q(dayString), q('main'), q(spansDays))"/> q('del'), q(dayString), q('main'), q(spansDays))"/>
<!-- Events --> <!-- Events -->
<x if="events"> <x if="events">
<div for="event in events" style="color: grey"> <div for="event in events" style="color: grey">
<!-- Checkbox for validating the event -->
<input type="checkbox" checked="checked" class="smallbox"
if="mayValidate and (event.eventType in field.validation.schema)"
id=":'%s_%s_%s' % (date.strftime('%Y%m%d'), event.eventType, \
event.timeslot)"/>
<x>::event.getName(allEventNames)</x> <x>::event.getName(allEventNames)</x>
<!-- Icon for delete this particular event --> <!-- Icon for delete this particular event -->
<img if="mayDelete and not single" class="clickable" <img if="mayDelete and not single" class="clickable"
src=":url('delete')" style="visibility:hidden" src=":url('delete')" style="visibility:hidden"
onclick=":'openEventPopup(%s,%s,%s,%s)' % (q('del'), \ onclick=":'openEventPopup(%s,%s,%s,%s)' % (q(ajaxHookId), \
q(field.name), q(dayString), q(event.timeslot))"/> q('del'), q(dayString), q(event.timeslot))"/>
</div> </div>
</x> </x>
<!-- Events from other calendars --> <!-- Events from other calendars -->
@ -384,12 +402,15 @@ class Calendar(Field):
namesOfDays=field.getNamesOfDays(_); namesOfDays=field.getNamesOfDays(_);
showTimeslots=len(field.timeslots) &gt; 1; showTimeslots=len(field.timeslots) &gt; 1;
slotIds=[slot.id for slot in field.timeslots]; slotIds=[slot.id for slot in field.timeslots];
slotIdsStr=','.join(slotIds)" slotIdsStr=','.join(slotIds);
mayValidate=field.mayValidate(zobj)"
id=":ajaxHookId"> id=":ajaxHookId">
<script>:'var %s_maxEventLength = %d;' % \ <script>:'var %s_maxEventLength = %d;' % \
(field.name, field.maxEventLength)</script> (field.name, field.maxEventLength)</script>
<script>:field.getAjaxData(ajaxHookId, zobj, render=render, \
month=defaultDateMonth)</script>
<!-- Month chooser --> <!-- Actions (month chooser, validation) -->
<div style="margin-bottom: 5px" <div style="margin-bottom: 5px"
var="fmt='%Y/%m/%d'; var="fmt='%Y/%m/%d';
goBack=not startDate or (startDate.strftime(fmt) &lt; \ goBack=not startDate or (startDate.strftime(fmt) &lt; \
@ -398,23 +419,26 @@ class Calendar(Field):
grid[-1][-1].strftime(fmt))"> grid[-1][-1].strftime(fmt))">
<!-- Go to the previous month --> <!-- Go to the previous month -->
<img class="clickable" if="goBack" src=":url('arrowLeft')" <img class="clickable" if="goBack" src=":url('arrowLeft')"
onclick=":'askCalendar(%s,%s,%s,%s,%s)' % (q(ajaxHookId), \ onclick=":'askMonth(%s,%s)' % (q(ajaxHookId), q(previousMonth))"/>
q(objUrl), q(render), q(field.name), q(previousMonth))"/>
<!-- Go back to the default date --> <!-- Go back to the default date -->
<input type="button" if="goBack or goForward" <input type="button" if="goBack or goForward"
var="fmt='%Y/%m'; var="fmt='%Y/%m';
label=(defaultDate.strftime(fmt)==today.strftime(fmt)) and \ label=(defaultDate.strftime(fmt)==today.strftime(fmt)) and \
'today' or 'goto_source'" 'today' or 'goto_source'"
value=":_(label)" value=":_(label)"
onclick=":'askCalendar(%s,%s,%s,%s,%s)' % (q(ajaxHookId), \ onclick=":'askMonth(%s,%s)' % (q(ajaxHookId),q(defaultDateMonth))"
q(objUrl), q(render), q(field.name), q(defaultDateMonth))"
disabled=":defaultDate.strftime(fmt)==monthDayOne.strftime(fmt)"/> disabled=":defaultDate.strftime(fmt)==monthDayOne.strftime(fmt)"/>
<!-- Go to the next month --> <!-- Go to the next month -->
<img class="clickable" if="goForward" src=":url('arrowRight')" <img class="clickable" if="goForward" src=":url('arrowRight')"
onclick=":'askCalendar(%s,%s,%s,%s,%s)' % (q(ajaxHookId), \ onclick=":'askMonth(%s,%s)' % (q(ajaxHookId), q(nextMonth))"/>
q(objUrl), q(render), q(field.name), q(nextMonth))"/>
<span>:_('month_%s' % monthDayOne.aMonth())</span> <span>:_('month_%s' % monthDayOne.aMonth())</span>
<span>:month.split('/')[0]</span> <span>:month.split('/')[0]</span>
<!-- Validate button -->
<input if="mayValidate" type="button" value=":_('validate_events')"
class="buttonSmall button" style=":url('validate', bg=True)"
var2="js='validateEvents(%s)' % q(ajaxHookId)"
onclick=":'askConfirm(%s,%s,%s)' % (q('script'), q(js, False), \
q(_('validate_events_confirm')))"/>
</div> </div>
<x>:getattr(field, 'pxView%s' % render.capitalize())</x> <x>:getattr(field, 'pxView%s' % render.capitalize())</x>
</div>''') </div>''')
@ -430,7 +454,8 @@ class Calendar(Field):
others=None, timelineName=None, additionalInfo=None, others=None, timelineName=None, additionalInfo=None,
startDate=None, endDate=None, defaultDate=None, timeslots=None, startDate=None, endDate=None, defaultDate=None, timeslots=None,
colors=None, showUncolored=False, preCompute=None, colors=None, showUncolored=False, preCompute=None,
applicableEvents=None, view=None, xml=None, delete=True): applicableEvents=None, validation=None, view=None, xml=None,
delete=True):
Field.__init__(self, validator, (0,1), default, show, page, group, Field.__init__(self, validator, (0,1), default, show, page, group,
layouts, move, False, True, False, specificReadPermission, layouts, move, False, True, False, specificReadPermission,
specificWritePermission, width, height, None, colspan, specificWritePermission, width, height, None, colspan,
@ -533,6 +558,12 @@ class Calendar(Field):
# for explaining him why he can, for this day, only create events of a # for explaining him why he can, for this day, only create events of a
# sub-set of the possible event types (or even no event at all). # sub-set of the possible event types (or even no event at all).
self.applicableEvents = applicableEvents self.applicableEvents = applicableEvents
# A validation process can be associated to a Calendar event. It
# consists in identifying validators and letting them "convert" event
# types being wished to final, validated event types. If you want to
# enable this, define a Validation instance (see the hereabove class)
# in parameter "validation".
self.validation = validation
# May the user delete events in this calendar? If "delete" is a method, # May the user delete events in this calendar? If "delete" is a method,
# it must accept an event type as single arg. # it must accept an event type as single arg.
self.delete = delete self.delete = delete
@ -717,17 +748,21 @@ class Calendar(Field):
return ','.join(res) return ','.join(res)
def getEventsAt(self, obj, date): def getEventsAt(self, obj, date):
'''Returns the list of events that exist at some p_date (=day).''' '''Returns the list of events that exist at some p_date (=day). p_date
can be a DateTime instance or a tuple (i_year, i_month, i_day).'''
obj = obj.o # Ensure p_obj is not a wrapper obj = obj.o # Ensure p_obj is not a wrapper
if not hasattr(obj.aq_base, self.name): return if not hasattr(obj.aq_base, self.name): return
years = getattr(obj, self.name) years = getattr(obj, self.name)
year = date.year() # Get year, month and name from p_date
if isinstance(date, tuple):
year, month, day = date
else:
year, month, day = date.year(), date.month(), date.day()
# Dig into the oobtree
if year not in years: return if year not in years: return
months = years[year] months = years[year]
month = date.month()
if month not in months: return if month not in months: return
days = months[month] days = months[month]
day = date.day()
if day not in days: return if day not in days: return
return days[day] return days[day]
@ -865,7 +900,7 @@ class Calendar(Field):
if isinstance(others, Other): if isinstance(others, Other):
others.getEventsAt(res, self, date, eventNames, isTimeline, colors) others.getEventsAt(res, self, date, eventNames, isTimeline, colors)
else: else:
for other in IterSub(others): for other in sutils.IterSub(others):
other.getEventsAt(res, self, date, eventNames,isTimeline,colors) other.getEventsAt(res, self, date, eventNames,isTimeline,colors)
return res return res
@ -888,7 +923,7 @@ class Calendar(Field):
res[0].append(et) res[0].append(et)
res[1][et] = self.getEventName(obj, et) res[1][et] = self.getEventName(obj, et)
if not others: return res if not others: return res
for other in IterSub(others): for other in sutils.IterSub(others):
eventTypes = other.field.getEventTypes(other.obj) eventTypes = other.field.getEventTypes(other.obj)
if eventTypes: if eventTypes:
for et in eventTypes: for et in eventTypes:
@ -1126,5 +1161,48 @@ class Calendar(Field):
m.month = text m.month = text
return res return res
def splitList(self, l, sub): return splitList(l, sub) def splitList(self, l, sub): return sutils.splitList(l, sub)
def mayValidate(self, obj):
'''May the currently logged user validate wish events ?'''
if not self.validation: return
return self.validation.method(obj.appy())
def getAjaxData(self, hook, zobj, **params):
'''Initializes an AjaxData object on the DOM node corresponding to
this calendar field.'''
params = sutils.getStringDict(params)
return "new AjaxData('%s', '%s:pxView', %s, null, '%s')" % \
(hook, self.name, params, zobj.absolute_url())
def validateEvents(self, obj):
'''Validate or discard events from the request.'''
rq = obj.REQUEST.form
counts = {'validated': 0, 'discarded': 0}
for action in ('validated', 'discarded'):
if not rq[action]: continue
for info in rq[action].split(','):
sdate, eventType, timeslot = info.split('_')
# Get the events defined at that date
date = int(sdate[:4]), int(sdate[4:6]), int(sdate[6:8])
events = self.getEventsAt(obj, date)
i = len(events) - 1
while i >= 0:
# Get the event at that timeslot
event = events[i]
if event.timeslot == timeslot:
# We have found the event
if event.eventType != eventType:
raise Exception('Wrong event type')
# Validate or discard it
if action == 'validated':
event.eventType = self.validation.schema[eventType]
else:
del events[i]
counts[action] += 1
i -= 1
obj.log('%s:%s: %d event(s) validated and %d discarded.' % \
(obj.id, self.name, counts['validated'], counts['discarded']))
if not counts['validated'] and not counts['discarded']:
return obj.translate('action_null')
return obj.translate('validate_events_done', mapping=counts)
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View file

@ -1254,8 +1254,8 @@ class Ref(Field):
(obj.id, self.name, code % ('objs', 'objs'), poss) (obj.id, self.name, code % ('objs', 'objs'), poss)
def getAjaxData(self, hook, zobj, **params): def getAjaxData(self, hook, zobj, **params):
'''Initializes an AjaxData object on the DOM node corresponding to '''Initializes an AjaxData object on the DOM node corresponding to this
p_hook = the whole search result.''' Ref field.'''
# Complete params with default parameters # Complete params with default parameters
params['ajaxHookId'] = hook; params['ajaxHookId'] = hook;
params['scope'] = hook.rsplit('_', 1)[-1] params['scope'] = hook.rsplit('_', 1)[-1]

View file

@ -727,6 +727,18 @@ msgstr ""
msgid "timeslot_misfit" msgid "timeslot_misfit"
msgstr "" msgstr ""
#. Default: "Validate events"
msgid "validate_events"
msgstr ""
#. Default: "All the checked events will be confirmed, while the unchecked ones will be discarded. The concerned user(s) will be warned. Are you sure?"
msgid "validate_events_confirm"
msgstr ""
#. Default: "${validated} event(s) was (were) validated and ${discarded} was (were) discarded."
msgid "validate_events_done"
msgstr ""
#. Default: "Inserted by ${userName}" #. Default: "Inserted by ${userName}"
msgid "history_insert" msgid "history_insert"
msgstr "" msgstr ""

View file

@ -727,6 +727,18 @@ msgstr ""
msgid "timeslot_misfit" msgid "timeslot_misfit"
msgstr "" msgstr ""
#. Default: "Validate events"
msgid "validate_events"
msgstr ""
#. Default: "All the checked events will be confirmed, while the unchecked ones will be discarded. The concerned user(s) will be warned. Are you sure?"
msgid "validate_events_confirm"
msgstr ""
#. Default: "${validated} event(s) was (were) validated and ${discarded} was (were) discarded."
msgid "validate_events_done"
msgstr ""
#. Default: "Inserted by ${userName}" #. Default: "Inserted by ${userName}"
msgid "history_insert" msgid "history_insert"
msgstr "" msgstr ""

View file

@ -727,6 +727,18 @@ msgstr ""
msgid "timeslot_misfit" msgid "timeslot_misfit"
msgstr "" msgstr ""
#. Default: "Validate events"
msgid "validate_events"
msgstr ""
#. Default: "All the checked events will be confirmed, while the unchecked ones will be discarded. The concerned user(s) will be warned. Are you sure?"
msgid "validate_events_confirm"
msgstr ""
#. Default: "${validated} event(s) was (were) validated and ${discarded} was (were) discarded."
msgid "validate_events_done"
msgstr ""
#. Default: "Inserted by ${userName}" #. Default: "Inserted by ${userName}"
msgid "history_insert" msgid "history_insert"
msgstr "" msgstr ""

View file

@ -728,6 +728,18 @@ msgstr "All day"
msgid "timeslot_misfit" msgid "timeslot_misfit"
msgstr "Cannot create such an event in the ${slot} slot." msgstr "Cannot create such an event in the ${slot} slot."
#. Default: "Validate events"
msgid "validate_events"
msgstr "Validate events"
#. Default: "All the checked events will be confirmed, while the unchecked ones will be discarded. The concerned user(s) will be warned. Are you sure?"
msgid "validate_events_confirm"
msgstr "All the checked events will be confirmed, while the unchecked ones will be discarded. The concerned user(s) will be warned. Are you sure?"
#. Default: "${validated} event(s) was (were) validated and ${discarded} was (were) discarded."
msgid "validate_events_done"
msgstr "${validated} event(s) was (were) validated and ${discarded} was (were) discarded."
#. Default: "Inserted by ${userName}" #. Default: "Inserted by ${userName}"
msgid "history_insert" msgid "history_insert"
msgstr "Inserted by ${userName}" msgstr "Inserted by ${userName}"

View file

@ -727,6 +727,18 @@ msgstr ""
msgid "timeslot_misfit" msgid "timeslot_misfit"
msgstr "" msgstr ""
#. Default: "Validate events"
msgid "validate_events"
msgstr ""
#. Default: "All the checked events will be confirmed, while the unchecked ones will be discarded. The concerned user(s) will be warned. Are you sure?"
msgid "validate_events_confirm"
msgstr ""
#. Default: "${validated} event(s) was (were) validated and ${discarded} was (were) discarded."
msgid "validate_events_done"
msgstr ""
#. Default: "Inserted by ${userName}" #. Default: "Inserted by ${userName}"
msgid "history_insert" msgid "history_insert"
msgstr "" msgstr ""

View file

@ -728,6 +728,18 @@ msgstr "Toute la journée"
msgid "timeslot_misfit" msgid "timeslot_misfit"
msgstr "Impossible de créer ce type d'événement dans la plage horaire ${slot}." msgstr "Impossible de créer ce type d'événement dans la plage horaire ${slot}."
#. Default: "Validate events"
msgid "validate_events"
msgstr "Valider les événements"
#. Default: "All the checked events will be confirmed, while the unchecked ones will be discarded. The concerned user(s) will be warned. Are you sure?"
msgid "validate_events_confirm"
msgstr "Tous les événements sélectionnés seront confirmés, tandis que ceux qui sont désélectionnés seront rejetés. Le ou les utilisateurs concernés seront prévenus. Êtes-vous sûr?"
#. Default: "${validated} event(s) was (were) validated and ${discarded} was (were) discarded."
msgid "validate_events_done"
msgstr "${validated} événement(s) a (ont) été validé(s) et ${discarded} a (ont) été rejeté(s)."
#. Default: "Inserted by ${userName}" #. Default: "Inserted by ${userName}"
msgid "history_insert" msgid "history_insert"
msgstr "Inséré par ${userName}" msgstr "Inséré par ${userName}"

View file

@ -727,6 +727,18 @@ msgstr ""
msgid "timeslot_misfit" msgid "timeslot_misfit"
msgstr "" msgstr ""
#. Default: "Validate events"
msgid "validate_events"
msgstr ""
#. Default: "All the checked events will be confirmed, while the unchecked ones will be discarded. The concerned user(s) will be warned. Are you sure?"
msgid "validate_events_confirm"
msgstr ""
#. Default: "${validated} event(s) was (were) validated and ${discarded} was (were) discarded."
msgid "validate_events_done"
msgstr ""
#. Default: "Inserted by ${userName}" #. Default: "Inserted by ${userName}"
msgid "history_insert" msgid "history_insert"
msgstr "" msgstr ""

View file

@ -727,6 +727,18 @@ msgstr ""
msgid "timeslot_misfit" msgid "timeslot_misfit"
msgstr "" msgstr ""
#. Default: "Validate events"
msgid "validate_events"
msgstr ""
#. Default: "All the checked events will be confirmed, while the unchecked ones will be discarded. The concerned user(s) will be warned. Are you sure?"
msgid "validate_events_confirm"
msgstr ""
#. Default: "${validated} event(s) was (were) validated and ${discarded} was (were) discarded."
msgid "validate_events_done"
msgstr ""
#. Default: "Inserted by ${userName}" #. Default: "Inserted by ${userName}"
msgid "history_insert" msgid "history_insert"
msgstr "Ingevuld door ${userName}" msgstr "Ingevuld door ${userName}"

View file

@ -198,3 +198,4 @@ td.search { padding-top: 8px }
.highlight { background-color: yellow } .highlight { background-color: yellow }
.globalActions { margin-bottom: 4px } .globalActions { margin-bottom: 4px }
.objectActions { margin: 2px 0 } .objectActions { margin: 2px 0 }
.smallbox { margin: 0 }

View file

@ -295,9 +295,10 @@ function askAjax(hook, form, params) {
} }
else var mode = d.mode; else var mode = d.mode;
// Get p_params if given. Note that they override anything else. // Get p_params if given. Note that they override anything else.
if (params && ('mode' in params)) {
mode = params['mode']; delete params['mode'] }
if (params) { for (var key in params) d.params[key] = params[key]; } if (params) { for (var key in params) d.params[key] = params[key]; }
askAjaxChunk(hook, mode, d.url, d.px, d.params, d.beforeSend, askAjaxChunk(hook,mode,d.url,d.px,d.params,d.beforeSend,evalInnerScripts);
evalInnerScripts);
} }
function askBunch(hookId, startNumber) { function askBunch(hookId, startNumber) {

View file

@ -8,11 +8,8 @@ function toggleVisibility(node, nodeType){
} }
} }
function askCalendar(hookId, objectUrl, render, fieldName, month) {
// Sends an Ajax request for getting the calendar, at p_month // Sends an Ajax request for getting the calendar, at p_month
var params = {'month': month, 'render': render}; function askMonth(hookId, month) {askAjax(hookId, null, {'month': month})}
askAjaxChunk(hookId, 'GET', objectUrl, fieldName+':pxView', params);
}
function enableOptions(select, enabled, selectFirst, message){ function enableOptions(select, enabled, selectFirst, message){
/* This function disables, in p_select, all options that are not in p_enabled. /* This function disables, in p_select, all options that are not in p_enabled.
@ -46,7 +43,7 @@ function enableOptions(select, enabled, selectFirst, message){
} }
} }
function openEventPopup(action, fieldName, day, timeslot, spansDays, function openEventPopup(hookId, action, day, timeslot, spansDays,
applicableEventTypes, message, freeSlots) { applicableEventTypes, message, freeSlots) {
/* Opens the popup for creating (or deleting, depending on p_action) a /* 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 the calendar event at some p_day. When action is "del", we need to know the
@ -57,13 +54,13 @@ function openEventPopup(action, fieldName, day, timeslot, spansDays,
p_applicableEventTypes; p_message contains an optional message explaining p_applicableEventTypes; p_message contains an optional message explaining
why not applicable types are not applicable. When "new", p_freeSlots may why not applicable types are not applicable. When "new", p_freeSlots may
list the available timeslots at p_day. */ list the available timeslots at p_day. */
var prefix = fieldName + '_' + action + 'Event'; var popupId = hookId + '_' + action;
var f = document.getElementById(prefix + 'Form'); var f = document.getElementById(popupId + 'Form');
f.day.value = day; f.day.value = day;
if (action == 'del') { if (action == 'del') {
if (f.timeslot) f.timeslot.value = timeslot; if (f.timeslot) f.timeslot.value = timeslot;
// Show or hide the checkbox for deleting the event for successive days // Show or hide the checkbox for deleting the event for successive days
var elem = document.getElementById(prefix + 'DelNextEvent'); var elem = document.getElementById(hookId + '_DelNextEvent');
var cb = elem.getElementsByTagName('input'); var cb = elem.getElementsByTagName('input');
cb[0].checked = false; cb[0].checked = false;
cb[1].value = 'False'; cb[1].value = 'False';
@ -78,15 +75,15 @@ function openEventPopup(action, fieldName, day, timeslot, spansDays,
enableOptions(f.eventType, applicableEventTypes, false, message); enableOptions(f.eventType, applicableEventTypes, false, message);
if (f.timeslot) enableOptions(f.timeslot, freeSlots, true, 'Not free'); if (f.timeslot) enableOptions(f.timeslot, freeSlots, true, 'Not free');
} }
openPopup(prefix + 'Popup'); openPopup(popupId);
} }
function triggerCalendarEvent(action, hookId, fieldName, objectUrl, function triggerCalendarEvent(hookId, action, maxEventLength) {
maxEventLength) {
/* Sends an Ajax request for triggering a calendar event (create or delete an /* Sends an Ajax request for triggering a calendar event (create or delete an
event) and refreshing the view month. */ event) and refreshing the view month. */
var prefix = fieldName + '_' + action + 'Event'; var popupId = hookId + '_' + action;
var f = document.getElementById(prefix + 'Form'); var formId = popupId + 'Form';
var f = document.getElementById(formId);
if (action == 'new') { if (action == 'new') {
// Check that an event span has been specified // Check that an event span has been specified
if (f.eventType.selectedIndex == 0) { if (f.eventType.selectedIndex == 0) {
@ -105,12 +102,25 @@ function triggerCalendarEvent(action, hookId, fieldName, objectUrl,
} }
} }
} }
var elems = f.elements; closePopup(popupId);
var params = {}; askAjax(hookId, formId);
// Put form elements into "params"
for (var i=0; i < elems.length; i++) {
params[elems[i].name] = elems[i].value;
} }
closePopup(prefix + 'Popup');
askAjaxChunk(hookId, 'POST', objectUrl, fieldName+':pxView', params); // Function for validating and discarding calendar events
function validateEvents(hookId) {
// Collect checkboxes from hookId and identify checked and unchecked ones
var validated = [];
var discarded = [];
var node = document.getElementById(hookId + '_cal');
var cbs = node.getElementsByTagName('input');
for (var i=0; i<cbs.length; i++) {
if (cbs[i].type != 'checkbox') continue;
if (cbs[i].checked) validated.push(cbs[i].id);
else discarded.push(cbs[i].id);
}
validated = validated.join()
discarded = discarded.join()
var params = {'action': 'validateEvents', 'validated': validated,
'discarded': discarded, 'mode': 'POST'};
askAjax(hookId, null, params);
} }