[MERGE]0k/web_fullcalendar branch from github

bzr revid: jke@openerp.com-20131106164700-hiwisdcygno4sj17
This commit is contained in:
jke-openerp 2013-11-06 17:47:00 +01:00
parent 08abc4345a
commit e6b54fa60b
2 changed files with 125 additions and 39 deletions

View File

@ -38,17 +38,41 @@ months = {
10: "October", 11: "November", 12: "December"
}
def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
"""
Get recurrent dates based on Rule string considering exdate and start date.
@param rrulestring: rulestring
@param exdate: list of exception dates for rrule
@param startdate: startdate for computing recurrent dates
def get_recurrent_dates(rrulestring, startdate, exdate=None, tz=None, exrule=None, context=None):
"""Get recurrent dates based on Rule string considering exdate and start date.
All input dates and output dates are in UTC. Dates are infered
thanks to rules in the ``tz`` timezone if given, else it'll be in
the current local timezone as specified in the context.
@param rrulestring: rulestring (ie: 'FREQ=DAILY;INTERVAL=1;COUNT=3')
@param exdate: string of dates separated by commas (ie: '20130506220000Z,20130507220000Z')
@param startdate: string start date for computing recurrent dates
@param tz: pytz timezone for computing recurrent dates
@param exrule: string exrule
@param context: current openerp context (for local timezone if ``tz`` is not provided)
@return: list of Recurrent dates
"""
exdate = exdate.split(',') if exdate else []
startdate = pytz.UTC.localize(
datetime.strptime(startdate, "%Y-%m-%d %H:%M:%S"))
def todate(date):
val = parser.parse(''.join((re.compile('\d')).findall(date)))
return val
## Dates are localized to saved timezone if any, else defaulted to
## current timezone. WARNING: these last event dates are considered as
## "floating" dates.
if not val.tzinfo:
val = pytz.UTC.localize(val)
return val.astimezone(timezone)
## Note that we haven't any context tz info when called by the server, so
## we'll default to UTC which could induce one-day errors in date
## calculation.
timezone = pytz.timezone(tz or context.get('tz') or 'UTC')
if not startdate:
startdate = datetime.now()
@ -56,6 +80,9 @@ def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
if not exdate:
exdate = []
## Convert the start date to saved timezone (or context tz) as it'll
## define the correct hour/day asked by the user to repeat for recurrence.
startdate = startdate.astimezone(timezone)
rset1 = rrule.rrulestr(str(rrulestring), dtstart=startdate, forceset=True)
for date in exdate:
datetime_obj = todate(date)
@ -64,7 +91,9 @@ def get_recurrent_dates(rrulestring, exdate, startdate=None, exrule=None):
if exrule:
rset1.exrule(rrule.rrulestr(str(exrule), dtstart=startdate))
return list(rset1)
return [d.astimezone(pytz.UTC) for d in rset1]
def base_calendar_id2real_id(base_calendar_id=None, with_date=False):
"""
@ -821,10 +850,7 @@ class calendar_alarm(osv.osv):
re_dates = []
if hasattr(res_obj, 'rrule') and res_obj.rrule:
event_date = datetime.strptime(res_obj.date, '%Y-%m-%d %H:%M:%S')
#exdate is a string and we need a list
exdate = res_obj.exdate and res_obj.exdate.split(',') or []
recurrent_dates = get_recurrent_dates(res_obj.rrule, exdate, event_date, res_obj.exrule)
recurrent_dates = get_recurrent_dates(res_obj.rrule, res_obj.date, res_obj.exdate, res_obj.vtimezone, res_obj.exrule, context=context)
trigger_interval = alarm.trigger_interval
if trigger_interval == 'days':
@ -986,6 +1012,47 @@ class calendar_event(osv.osv):
result[event] = ""
return result
def _get_recurrence_end_date(self, cr, uid, ids, name, arg, context=None):
"""Get a good estimate of the end of the timespan concerned by an event.
This means we need to concider the last event of a recurrency, and that we
add its duration. For simple events (no rrule), the date_deadline is sufficient.
This value is stored in database and will help select events that should be
concidered candidate for display when filters are made upon dates (typically
the agenda filter will make one-month, one-week, one-day timespan searches).
"""
if not context:
context = {}
events = super(calendar_event, self).read(
cr, uid, ids, ['rrule', 'exdate', 'exrule', 'duration', 'date_deadline', 'date', 'vtimezone'], context=context)
result = {}
for event in events:
duration = timedelta(hours=event['duration'])
if event['rrule']:
all_dates = get_recurrent_dates(
event['rrule'], event['date'], event['exdate'], event['vtimezone'],
event['exrule'], context=context)
if not event['vtimezone'] and not context.get('tz'):
## We are called by the server probably at update time (no
## context), and no vtimezone was recorded, so we have no
## idea of possible client timezone so we have a possible
## one-day-of error when applying RRULEs on floating dates.
## Let's add a day.
duration += timedelta(days=1)
result[event['id']] = (all_dates[-1] + duration).astimezone(pytz.UTC).strftime("%Y-%m-%d %H:%M:%S") \
if all_dates else None
else:
result[event['id']] = event['date_deadline']
return result
def _rrule_write(self, obj, cr, uid, ids, field_name, field_value, args, context=None):
data = self._get_empty_rrule_data()
if field_value:
@ -1035,6 +1102,10 @@ rule or repeating pattern of time to exclude from the recurring rule."),
'base_calendar_alarm_id': fields.many2one('calendar.alarm', 'Alarm'),
'recurrent_id': fields.integer('Recurrent ID'),
'recurrent_id_date': fields.datetime('Recurrent ID date'),
'recurrence_end_date': fields.function(_get_recurrence_end_date,
type='datetime',
store=True, string='Recurrence end date',
priority=30),
'vtimezone': fields.selection(_tz_get, size=64, string='Timezone'),
'user_id': fields.many2one('res.users', 'Responsible', states={'done': [('readonly', True)]}),
'organizer': fields.char("Organizer", size=256, states={'done': [('readonly', True)]}), # Map with organizer attribute of VEvent.
@ -1154,39 +1225,43 @@ rule or repeating pattern of time to exclude from the recurring rule."),
context = {}
result = []
for data in super(calendar_event, self).read(cr, uid, select, ['rrule', 'recurrency', 'exdate', 'exrule', 'date'], context=context):
# for data in super(calendar_event, self).read(cr, uid, select, ['rrule', 'recurrency', 'exdate', 'exrule', 'date'], context=context):
for data in super(calendar_event, self).read(cr, uid, select, ['rrule', 'recurrency', 'exdate', 'exrule', 'date', 'vtimezone'], context=context):
if not data['recurrency'] or not data['rrule']:
result.append(data['id'])
continue
event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
# event_date = datetime.strptime(data['date'], "%Y-%m-%d %H:%M:%S")
# event_date = pytz.UTC.localize(event_date)
# TOCHECK: the start date should be replaced by event date; the event date will be changed by that of calendar code
if not data['rrule']:
continue
exdate = data['exdate'] and data['exdate'].split(',') or []
rrule_str = data['rrule']
new_rrule_str = []
rrule_until_date = False
is_until = False
for rule in rrule_str.split(';'):
name, value = rule.split('=')
if name == "UNTIL":
is_until = True
value = parser.parse(value)
rrule_until_date = parser.parse(value.strftime("%Y-%m-%d %H:%M:%S"))
value = value.strftime("%Y%m%d%H%M%S")
new_rule = '%s=%s' % (name, value)
new_rrule_str.append(new_rule)
new_rrule_str = ';'.join(new_rrule_str)
rdates = get_recurrent_dates(str(new_rrule_str), exdate, event_date, data['exrule'])
# if not data['rrule']:
# continue
#
# exdate = data['exdate'] and data['exdate'].split(',') or []
# rrule_str = data['rrule']
# new_rrule_str = []
# rrule_until_date = False
# is_until = False
# for rule in rrule_str.split(';'):
# name, value = rule.split('=')
# if name == "UNTIL":
# is_until = True
# value = parser.parse(value)
# rrule_until_date = parser.parse(value.strftime("%Y-%m-%d %H:%M:%S"))
# value = value.strftime("%Y%m%d%H%M%S")
# new_rule = '%s=%s' % (name, value)
# new_rrule_str.append(new_rule)
# new_rrule_str = ';'.join(new_rrule_str)
# rdates = get_recurrent_dates(str(new_rrule_str), exdate, event_date, data['exrule'])
# rdates = get_recurrent_dates(data['rrule'], exdate, event_date, data['exrule'])
rdates = get_recurrent_dates(data['rrule'], data['date'], data['exdate'], data['vtimezone'], data['exrule'], context=context)
for r_date in rdates:
# fix domain evaluation
# step 1: check date and replace expression by True or False, replace other expressions by True
# step 2: evaluation of & and |
# check if there are one False
pile = []
ok = True
for arg in domain:
if str(arg[0]) in (str('date'), str('date_deadline')):
if (arg[1] == '='):
@ -1346,7 +1421,8 @@ rule or repeating pattern of time to exclude from the recurring rule."),
new_arg = arg
if arg[0] in ('date_deadline', unicode('date_deadline')):
if context.get('virtual_id', True):
new_args += ['|','&',('recurrency','=',1),('end_date', arg[1], arg[2])]
# new_args += ['|','&',('recurrency','=',1),('end_date', arg[1], arg[2])]
new_args += ['|','&',('recurrency','=',1),('recurrence_end_date', arg[1], arg[2])]
elif arg[0] == "id":
new_id = get_real_ids(arg[2])
new_arg = (arg[0], arg[1], new_id)
@ -1421,7 +1497,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
new_id = self.copy(cr, uid, real_event_id, default=data, context=context)
date_new = event_id.split('-')[1]
date_new = time.strftime("%Y%m%dT%H%M%S", \
date_new = time.strftime("%Y%m%dT%H%M%SZ", \
time.strptime(date_new, "%Y%m%d%H%M%S"))
exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
res = self.write(cr, uid, [real_event_id], {'exdate': exdate})
@ -1472,7 +1548,8 @@ rule or repeating pattern of time to exclude from the recurring rule."),
context = {}
fields2 = fields and fields[:] or None
EXTRAFIELDS = ('class','user_id','duration')
EXTRAFIELDS = ('class','user_id','duration', 'date',
'rrule', 'vtimezone', 'exrule', 'exdate')
for f in EXTRAFIELDS:
if fields and (f not in fields):
fields2.append(f)
@ -1495,6 +1572,15 @@ rule or repeating pattern of time to exclude from the recurring rule."),
res = real_data[real_id].copy()
ls = base_calendar_id2real_id(base_calendar_id, with_date=res and res.get('duration', 0) or 0)
if not isinstance(ls, (str, int, long)) and len(ls) >= 2:
recurrent_dates = [
d.strftime("%Y-%m-%d %H:%M:%S")
for d in get_recurrent_dates(
res['rrule'], res['date'], res['exdate'],
res['vtimezone'], res['exrule'], context=context)]
if ls[1] not in recurrent_dates:
raise KeyError(
'Virtual id %r is not valid, event %r can '
'not produce it.' % (base_calendar_id, real_id))
res['date'] = ls[1]
res['date_deadline'] = ls[2]
res['id'] = base_calendar_id
@ -1548,7 +1634,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
date_new = time.strftime("%Y%m%dT%H%M%S", \
time.strptime(date_new, "%Y%m%d%H%M%S"))
exdate = (data['exdate'] and (data['exdate'] + ',') or '') + date_new
self.write(cr, uid, [real_event_id], {'exdate': exdate})
self.write(cr, uid, [real_event_id], {'exdate': exdate}, context=context)
ids.remove(event_id)
for event in self.browse(cr, uid, ids, context=context):
if event.attendee_ids:

View File

@ -239,7 +239,7 @@
<field name="model">calendar.event</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
<calendar string="Events" date_start="date" color="show_as" date_delay="duration">
<calendar string="Events" date_start="date" color="show_as" date_delay="duration" all_day="allday">
<field name="name"/>
<field name="class"/>
<field name="show_as"/>