@ -1,36 +1,54 @@
# -*- coding: utf-8 -*-
# © 2016 Therp BV <http://therp.nl>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import pytz
from dateutil.rrule import rrule , rruleset
from dateutil.tz import gettz
from openerp import fields , models
_DATETIME_FIELDS = [ ' _until ' , ' _dtstart ' ]
_SCALAR_FIELDS = [
_RRULE_ DATETIME_FIELDS = [ ' _until ' , ' _dtstart ' ]
_RRULE_ SCALAR_FIELDS = [
' _wkst ' , ' _cache ' , ' _until ' , ' _dtstart ' , ' _count ' , ' _freq ' , ' _interval ' ,
]
_ZERO_IS_NOT_NONE = [ ' _freq ' ]
_RRULE_ ZERO_IS_NOT_NONE = [ ' _freq ' ]
class SerializableRRuleSet ( rruleset , list ) :
class LocalRRuleSet ( rruleset ) :
""" An rruleset that yields the naive utc representation of a date if the
original date was timezone aware """
def __iter__ ( self ) :
for date in rruleset . __iter__ ( self ) :
if not date . tzinfo :
yield date
else :
yield date . astimezone ( pytz . utc ) . replace ( tzinfo = None )
class SerializableRRuleSet ( list ) :
""" Getting our rule set json stringified is tricky, because we can ' t
just inject our own json encoder . Now we pretend our set is a list ,
then json . dumps will try to iterate , which is why we can do our specific
stuff in __iter__ """
def __init__ ( self , * args ) :
self . _rrule = [ ]
self . _rrule = list ( args )
self . tz = None
super ( SerializableRRuleSet , self ) . __init__ ( self )
for arg in args :
self . rrule ( arg )
def __iter__ ( self ) :
for rule in self . _rrule :
yield dict ( type = ' rrule ' , * * {
key [ 1 : ] :
fields . Datetime . to_string ( getattr ( rule , key ) )
if key in _DATETIME_FIELDS
fields . Datetime . to_string (
getattr ( rule , key ) if
not self . tz or not getattr ( rule , key ) or
not getattr ( rule , key ) . tzinfo
else getattr ( rule , key ) . astimezone ( pytz . utc )
)
if key in _RRULE_DATETIME_FIELDS
else
[ ] if getattr ( rule , key ) is None and key not in _SCALAR_FIELDS
[ ] if getattr ( rule , key ) is None and
key not in _RRULE_SCALAR_FIELDS
else
list ( getattr ( rule , key ) ) if key not in _SCALAR_FIELDS
list ( getattr ( rule , key ) ) if key not in _RRULE_ SCALAR_FIELDS
else getattr ( rule , key )
for key in [
' _byhour ' , ' _wkst ' , ' _bysecond ' , ' _bymonthday ' ,
@ -40,6 +58,8 @@ class SerializableRRuleSet(rruleset, list):
' _byeaster ' ,
]
} )
if self . tz :
yield dict ( type = ' tz ' , tz = self . tz )
# TODO: implement rdate, exrule, exdate
def __call__ ( self , default_self = None ) :
@ -48,11 +68,8 @@ class SerializableRRuleSet(rruleset, list):
and empty recordset as parameter . In this case , we need self """
if isinstance ( default_self , models . BaseModel ) :
return self
result = rrules et( )
result = LocalRRuleS et( )
result . _rrule = self . _rrule
result . _rdate = self . _rdate
result . _exrule = self . _exrule
result . _exdate = self . _exdate
return result
def __nonzero__ ( self ) :
@ -87,31 +104,81 @@ class SerializableRRuleSet(rruleset, list):
class FieldRRule ( fields . Serialized ) :
_slots = {
' stable_times ' : None ,
}
def convert_to_cache ( self , value , record , validate = True ) :
result = SerializableRRuleSet ( )
if not value :
return result
if isinstance ( value , SerializableRRuleSet ) :
if self . stable_times and not value . tz :
self . _add_tz ( value , record . env . user . tz or ' utc ' )
return value
assert isinstance ( value , list ) , ' An RRULE \' s content must be a list '
tz = None
for data in value :
assert isinstance ( data , dict ) , ' The list must contain dictionaries '
assert ' type ' in data , ' The dictionary must contain a type '
data_type = data [ ' type ' ]
data = {
key : fields . Datetime . from_string ( value )
if ' _ %s ' % key in _DATETIME_FIELDS
else map ( int , value )
if value and ' _ %s ' % key not in _SCALAR_FIELDS
else int ( value ) if value
else None if not value and ' _ %s ' % key not in _ZERO_IS_NOT_NONE
else value
for key , value in data . iteritems ( )
if key != ' type '
}
if hasattr ( self , ' convert_to_cache_parse_data_ %s ' % data_type ) :
data = getattr (
self , ' convert_to_cache_parse_data_ %s ' % data_type
) ( record , data )
if data_type == ' rrule ' :
result . rrule ( rrule ( * * data ) )
result . _rrule . append ( rrule ( * * data ) )
elif data_type == ' tz ' and self . stable_times :
tz = data [ ' tz ' ]
# TODO: implement rdate, exrule, exdate
else :
raise ValueError ( ' Unknown type given ' )
if self . stable_times :
self . _add_tz ( result , tz or record . env . user . tz or ' utc ' )
return result
def convert_to_cache_parse_data_rrule ( self , record , data ) :
""" parse a data dictionary from the database """
return {
key : fields . Datetime . from_string ( value )
if ' _ %s ' % key in _RRULE_DATETIME_FIELDS
else map ( int , value )
if value and ' _ %s ' % key not in _RRULE_SCALAR_FIELDS
else int ( value ) if value
else None
if not value and ' _ %s ' % key not in _RRULE_ZERO_IS_NOT_NONE
else value
for key , value in data . iteritems ( )
if key != ' type '
}
def to_column ( self ) :
""" set our flag on the resulting column """
result = super ( FieldRRule , self ) . to_column ( )
result . stable_times = self . stable_times
return result
def _add_tz ( self , value , tz ) :
""" set the timezone on an rruleset and adjust dates there """
value . tz = tz
tz = gettz ( tz )
for rule in value . _rrule :
for fieldname in _RRULE_DATETIME_FIELDS :
date = getattr ( rule , fieldname )
if not date :
continue
setattr (
rule , fieldname ,
date . replace ( tzinfo = pytz . utc ) . astimezone ( tz ) ,
)
rule . _tzinfo = tz
rule . _timeset = tuple ( [
rule . _dtstart . replace (
hour = time . hour ,
minute = time . minute ,
second = time . second ,
tzinfo = pytz . utc ,
) . astimezone ( tz ) . timetz ( )
if not time . tzinfo else time
for time in rule . _timeset
] )