def prev_yearly_vacation_date (db, user, ctype, date) : d = date - common.day dyn = vac_get_user_dynamic (db, user, ctype, d) if ( not dyn or dyn.valid_from > d or dyn.vacation_month is None or dyn.vacation_day is None ) : return None y = int (d.get_tuple () [0]) prev_date = Date \ ('%04d-%02d-%02d' % (y, dyn.vacation_month, dyn.vacation_day)) if prev_date >= date : prev_date = Date \ ('%04d-%02d-%02d' % (y - 1, dyn.vacation_month, dyn.vacation_day)) assert prev_date < date while dyn.valid_from > prev_date : dyn = vac_prev_user_dynamic (db, dyn) if not dyn : return prev_date yday = dyn.vacation_day ymon = dyn.vacation_month if yday is None or ymon is None : return prev_date prev_date = Date ('%04d-%02d-%02d' % (y, ymon, yday)) if prev_date >= date : prev_date = Date ('%04d-%02d-%02d' % (y - 1, ymon, yday)) return prev_date
def get_daily_record (db, user, date) : """ Use caching: prefetch all records from given date until now and store them. Use a per-user cache of the earliest dr found. """ pdate = date.pretty (ymd) date = Date (pdate) now = Date (Date ('.').pretty (ymd)) if not getattr (db, 'daily_record_cache', None) : db.daily_record_cache = {} def daily_record_cache_clear (db) : db.daily_record_cache = {} db.registerClearCacheCallback (daily_record_cache_clear, db) if (user, pdate) not in db.daily_record_cache : if date < now : start = date end = now else : start = now end = date range = common.pretty_range (start, end) drs = db.daily_record.filter \ (None, dict (user = user, date = range), sort = ('+', 'date')) next = start for drid in drs : dr = db.daily_record.getnode (drid) _update_empty_dr (db, user, dr.date, next) db.daily_record_cache [(user, dr.date.pretty (ymd))] = dr next = dr.date + day _update_empty_dr (db, user, end, next) return db.daily_record_cache [(user, pdate)]
def ldap_picture(self, luser, attr): try: lpic = luser[attr][0] except KeyError: return None uid = None try: uid = self.db.user.lookup(luser.uid[0]) except KeyError: pass if uid: upicids = self.db.user.get(uid, 'pictures') pics = [self.db.file.getnode(i) for i in upicids] for n, p in enumerate \ (sorted (pics, reverse = True, key = lambda x : x.activity)) : if p.content == lpic: if n: # refresh name to put it in front self.db.file.set(p.id, name=str(Date('.'))) break else: f = self.db.file.create \ ( name = str (Date ('.')) , content = lpic , type = 'image/jpeg' ) upicids.append(f) return upicids else: f = self.db.file.create \ ( name = str (Date ('.')) , content = lpic , type = 'image/jpeg' ) return [f]
def dynuser_half_frozen (dyn) : db = dyn._db userid = dyn.user.id val_from = Date (str (dyn.valid_from._value)) val_to = dyn.valid_to._value if val_to : val_to = Date (str (val_to)) return _dynuser_half_frozen (db, userid, val_from, val_to - common.day)
def testDateInterval(self): ae = self.assertEqual date = Date("2000-06-26.00:34:02 + 2d") ae(str(date), '2000-06-28.00:34:02') date = Date("2000-02-27 + 2d") ae(str(date), '2000-02-29.00:00:00') date = Date("2001-02-27 + 2d") ae(str(date), '2001-03-01.00:00:00')
def check_start_end_duration \ (date, start, end, duration, new_values, dist = 0) : """ either duration or both start/end must be set but not both of duration/end set duration from start/end if duration empty set end from start/duration if end empty Note: We are using naive times (with timezone 0) here, this means we can safely use date.pretty for converting back to string. """ dstart = dend = None if dist : check_duration (dist) if start and ":" not in start : start = start + ":00" if end and ":" not in end : end = end + ":00" if 'end' in new_values : if not start : attr = _ ('start') raise Reject, _ (''"%(attr)s must be specified") % locals () if 'duration' in new_values : raise Reject, _ (''"Either specify duration or start/end") dstart, dend, sp, ep, dur = check_timestamps (start, end, date) duration = dur new_values ['duration'] = duration new_values ['start'] = sp new_values ['end'] = ep else : check_duration (duration, 24) if 'duration' in new_values : new_values ['duration'] = duration if start : if 'start' in new_values or 'duration' in new_values : minutes = duration * 60 hours = int (duration % 60) minutes = minutes - hours * 60 ds = Date (start, offset = 0) t = (ds + Interval ('%d:%d' % (hours, minutes))).pretty \ (hour_format) if duration > 0 and t == '00:00' : t = '24:00' dstart, dend, sp, ep, dur = check_timestamps (start, t, date) assert dur == duration new_values ['start'] = sp new_values ['end'] = ep if dist and dist < duration : duration -= dist if start : hours = int (dist) minutes = (dist - hours) * 60 if not dstart : dstart = Date (start, offset = 0) dstart = dstart + Interval ('%d:%d' % (hours, minutes)) new_values ['start'] = dstart.pretty (hour_format) new_values ['duration'] = duration return dstart, dend
def testGranularity(self): ae = self.assertEqual ae(str(Date('2003-2-12', add_granularity=1)), '2003-02-12.23:59:59') ae(str(Date('2003-1-1.23:00', add_granularity=1)), '2003-01-01.23:00:59') ae(str(Date('2003', add_granularity=1)), '2003-12-31.23:59:59') ae(str(Date('2003-5', add_granularity=1)), '2003-05-31.23:59:59') ae(str(Date('2003-12', add_granularity=1)), '2003-12-31.23:59:59') ae(str(Interval('+1w', add_granularity=1)), '+ 14d') ae(str(Interval('-2m 3w', add_granularity=1)), '- 2m 14d')
def _find_next(db, daily_record_freeze, direction='+', frozen=None): user = daily_record_freeze.user.id date = daily_record_freeze.date if not isinstance(date, Date): try: date = Date(date._value) except AttributeError: date = Date(date) return find_next_dr_freeze(db, user, date, direction, frozen=frozen)
def rough_date_diff (left, right, format = "%Y-%m-%d") : """returns the interval between the two dates left - right. format is used for the granularity when interpreting the two values. left and right need to be Date values. format needs to be a Date parseable format. """ l_d = Date (left.pretty (format)) r_d = Date (right.pretty (format)) return l_d - r_d
def testDateInterval(self): ae = self.assertEqual date = Date("2000-06-26.00:34:02 + 2d") ae(str(date), '2000-06-28.00:34:02') date = Date("2000-02-27 + 2d") ae(str(date), '2000-02-29.00:00:00') date = Date("2001-02-27 + 2d") ae(str(date), '2001-03-01.00:00:00') date = Date("2009", add_granularity=True) self.assertRaises(ValueError, Date, ". +30d", add_granularity=True)
def testIntervalSubtractYearBoundary(self): # force the transition over a year boundary now = Date('2003-01-01.00:00:00') then = now - Interval('2d') self.assertEqual(str(then), '2002-12-30.00:00:00') now = Date('2004-02-01.00:00:00') then = now - Interval('365d') self.assertEqual(str(then), '2003-02-01.00:00:00') now = Date('2005-02-01.00:00:00') then = now - Interval('365d') self.assertEqual(str(then), '2004-02-02.00:00:00')
def testSimpleTZ(self): ae = self.assertEqual # local to utc date = Date('2006-04-04.12:00:00', 2) ae(str(date), '2006-04-04.10:00:00') # utc to local date = Date('2006-04-04.10:00:00') date = date.local(2) ae(str(date), '2006-04-04.12:00:00') # from Date instance date = Date('2006-04-04.12:00:00') date = Date(date, 2) ae(str(date), '2006-04-04.10:00:00')
def testTZ(self): ae = self.assertEqual tz = 'Europe/Warsaw' # local to utc, DST date = Date('2006-04-04.12:00:00', tz) ae(str(date), '2006-04-04.10:00:00') # local to utc, no DST date = Date('2006-01-01.12:00:00', tz) ae(str(date), '2006-01-01.11:00:00') # utc to local, DST date = Date('2006-04-04.10:00:00') date = date.local(tz) ae(str(date), '2006-04-04.12:00:00') # utc to local, no DST date = Date('2006-01-01.10:00:00') date = date.local(tz) ae(str(date), '2006-01-01.11:00:00') date = Date('2006-04-04.12:00:00') date = Date(date, tz) ae(str(date), '2006-04-04.10:00:00') date = Date('2006-01-01.12:00:00') date = Date(date, tz) ae(str(date), '2006-01-01.11:00:00')
def leave_days(db, user, first_day, last_day): if not isinstance(first_day, Date): try: first_day = Date(first_day._value) except AttributeError: first_day = Date(first_day) if not isinstance(last_day, Date): try: last_day = Date(last_day._value) except AttributeError: last_day = Date(last_day) try: return vacation.leave_days(db, user, first_day, last_day) except AssertionError: return 'Invalid data'
def set_first_reply (db, cl, nodeid, new_values) : """ Set first_reply field in case the support issue hasn't set this yet *and* status goes to closed or satisfied. """ issue = cl.getnode (nodeid) if issue.first_reply : return if 'status' in new_values and 'first_reply' not in new_values : closed = db.sup_status.lookup ('closed') satis = db.sup_status.lookup ('satisfied') cust = db.sup_status.lookup ('customer') if new_values ['status'] in (closed, satis, cust) : new_values ['first_reply'] = Date ('.') elif new_values.get ('set_first_reply', None) : new_values ['first_reply'] = Date ('.')
def range_frozen(db, user, range): """Check if a given range of dates is completely frozen """ if not range or not user: return False date = Date(range.split(';', 1)[-1]) return frozen(db, user, date)
def audit_user_fields(db, cl, nodeid, new_values): for n in \ ( 'firstname' , 'lastname' , 'lunch_duration' , 'lunch_start' , 'shadow_inactive' , 'shadow_max' , 'shadow_min' , 'shadow_warning' , 'uid' ) : if n in new_values and new_values[n] is None and cl.get(nodeid, n): raise Reject, "%(attr)s may not be undefined" % {'attr': _(n)} if 'status' in cl.properties: status = new_values.get('status', cl.get(nodeid, 'status')) if status == db.user_status.lookup('valid'): for n in \ ( 'org_location' , 'department' ) : if n in new_values and new_values[n] is None: dyn = get_user_dynamic(db, nodeid, Date('.')) if dyn: new_values[n] = getattr(dyn, n) else: raise Reject, "%(attr)s may not be undefined" \ % {'attr' : _ (n)} common_user_checks(db, cl, nodeid, new_values)
def _get_ctype(db, user, start, end): now = Date('.') if start <= now <= end: dyn = user_dynamic.get_user_dynamic(db, user, now) else: dyn = user_dynamic.get_user_dynamic(db, user, start) return dyn.contract_type
def remaining_until(db): db = db._db now = Date('.') uid = db.getuid() dyn = user_dynamic.get_user_dynamic(db, uid, now) return vacation.next_yearly_vacation_date \ (db, uid, dyn.contract_type, now) - common.day
def user_leave_submissions(db, context): dt = '%s;' % Date('. - 14m').pretty(common.ymd) uid = db._db.getuid() d = dict(user=uid, first_day=dt) s = [('+', 'first_day')] ls = db.leave_submission.filter(None, d, s) return ls
def eoy_vacation(db, user, date): eoy = Date(date.pretty('%Y-12-31')) vc = vacation.get_vacation_correction(db, user) assert vc yday, pd, carry, ltot = vacation.vacation_params(db, user, eoy, vc) cons = vacation.consolidated_vacation(db, user, vc.contract_type, eoy) return ceil(cons - ltot + carry)
def avg_hours_per_week_this_year (db, user, date_in_year) : """ Loop over all dyn records in this year and use only those with all-in set. For those we count the hours and compute the average over all all-in days. """ y = common.start_of_year (date_in_year) eoy = common.end_of_year (y) now = Date ('.') if eoy > now : eoy = now hours = 0.0 dsecs = 0.0 ds = 24 * 60 * 60 for dyn in user_dynamic.user_dynamic_year_iter (db, user, y) : if not dyn.all_in : continue vf = dyn.valid_from if vf < y : vf = y vt = dyn.valid_to if not vt or vt > eoy + common.day : vt = eoy + common.day dsecs += (vt - vf).as_seconds () drs = db.daily_record.filter \ (None, dict (date = common.pretty_range (vf, vt), user = user)) for drid in drs : dr = db.daily_record.getnode (drid) dur = user_dynamic.update_tr_duration (db, dr) hours += dur days = dsecs / ds assert days <= 366 if not days : return 0 avgday = hours / float (days) return avgday * 7
def fix_vacation (db, uid, date_from = None, date_to = None) : """ Fix vacation for a user where the dyn. user record has been changed *after* the user already booked vacation. We search for all time-records with a daily-record in state 'leave' since the last frozen time or date_from if given. """ #print ("fix_vacation: %s %s %s" % (uid, date_from, date_to)) if date_from is None : date_from = Date ('2000-01-01') frozen = db.daily_record_freeze.filter \ (None, dict (user = uid, frozen = True), sort = ('-', 'date')) if frozen : frozen = db.daily_record_freeze.getnode (frozen [0]) date_from = frozen.date + common.day leave = db.daily_record_status.lookup ('leave') d = dict () d ['daily_record.user'] = uid d ['daily_record.date'] = common.pretty_range (date_from, date_to) d ['daily_record.status'] = leave trs = db.time_record.filter (None, d) for trid in trs : tr = db.time_record.getnode (trid) dr = db.daily_record.getnode (tr.daily_record) wp = db.time_wp.getnode (tr.wp) tp = db.time_project.getnode (wp.project) if not tp.is_vacation and not tp.is_public_holiday : continue du = leave_duration (db, uid, dr.date, tp.is_public_holiday) if tr.duration != du : #print "Wrong: time_record%s: %s->%s" % (trid, tr.duration, du) db.time_record.set (trid, duration = du)
def valid_wps \ (db, filter = {}, user = None, date = None, srt = None, future = False) : srt = srt or [('+', 'id')] wps = {} date = date or Date ('.') dt = (date + common.day).pretty (common.ymd) d = {} if not future : d ['time_start'] = ';%s' % date.pretty (common.ymd) # Only select WPs that are not exclusively managed by external tool d ['is_extern'] = False d ['project.is_extern'] = False d.update (filter) wp = [] if user : d1 = dict (d, is_public = True, has_expiration_date = False) wp.extend (db.time_wp.filter (None, d1, srt)) d1 = dict (d, is_public = True, time_end = '%s;' % dt) wp.extend (db.time_wp.filter (None, d1, srt)) d1 = dict (d, bookers = user, has_expiration_date = False) wp.extend (db.time_wp.filter (None, d1, srt)) d1 = dict (d, bookers = user, time_end = '%s;' % dt) wp.extend (db.time_wp.filter (None, d1, srt)) else : d1 = dict (d, has_expiration_date = False) wp.extend (db.time_wp.filter (None, d1, srt)) d1 = dict (d, time_end = '%s;' % dt) wp.extend (db.time_wp.filter (None, d1, srt)) # Filter again via db to get sorting right return db.time_wp.filter (wp, {}, sort = srt)
def vacation_time_sum (db, user, ctype, start, end) : dt = common.pretty_range (start, end) dr = db.daily_record.filter (None, dict (user = user, date = dt)) dtt = [('+', 'daily_record.date')] vwp = vacation_wps (db) trs = db.time_record.filter \ (None, dict (daily_record = dr, wp = vwp), sort = dtt) vac = 0.0 if ctype == -1 : ctype = _get_ctype (db, user, Date ('.')) by_dr = {} for tid in trs : tr = db.time_record.getnode (tid) dr = db.daily_record.getnode (tr.daily_record) dyn = user_dynamic.get_user_dynamic (db, user, dr.date) # dyn is None if time_records booked but dyn record revoked for this period: if not dyn or dyn.contract_type != ctype : continue wh = user_dynamic.day_work_hours (dyn, dr.date) assert wh if dr.id not in by_dr : by_dr [dr.id] = (wh, []) assert by_dr [dr.id][0] == wh by_dr [dr.id][1].append (tr.duration) for wh, durs in by_dr.itervalues () : vac += ceil (sum (durs) / wh * 2) / 2. return vac
def smtp_send(self, to, message, sender=None): """Send a message over SMTP, using roundup's config. Arguments: - to: a list of addresses usable by rfc822.parseaddr(). - message: a StringIO instance with a full message. - sender: if not 'None', the email address to use as the envelope sender. If 'None', the admin email is used. """ if not sender: sender = self.config.ADMIN_EMAIL if self.debug: # don't send - just write to a file, use unix from line so # that resulting file can be openened in a mailer fmt = '%a %b %m %H:%M:%S %Y' unixfrm = 'From %s %s' % (sender, Date('.').pretty(fmt)) debug_fh = open(self.debug, 'a') debug_fh.write('%s\nFROM: %s\nTO: %s\n%s\n\n' % (unixfrm, sender, ', '.join(to), message)) debug_fh.close() else: # now try to send the message try: # send the message as admin so bounces are sent there # instead of to roundup smtp = SMTPConnection(self.config) smtp.sendmail(sender, to, message) except socket.error as value: raise MessageSendError("Error: couldn't send email: " "mailhost %s" % value) except smtplib.SMTPException as msg: raise MessageSendError("Error: couldn't send email: %s" % msg)
def handle (self) : self.set_request () filterspec = self.request.filterspec try : weeknostr = filterspec ['weekno'] except KeyError : weeknostr = self.request.form ['weekno'].value try : year, weekno = [int (i) for i in weeknostr.split ('/')] except ValueError : year = Date ('.').year weekno = int (weeknostr) filterspec ['date'] = common.pretty_range \ (* common.from_week_number (year, weekno)) try : return self.__super.handle () except Redirect : pass args = \ { ':action' : 'search' , ':template' : 'edit' , ':sort' : 'date' , ':group' : 'user' , ':filter' : ','.join (self.request.filterspec.keys ()) , ':startwith' : '0' , ':ok_message' : self.ok_msg } url = self.request.indexargs_url ('', args) raise Redirect, url
def set_defaults (db, cl, nodeid, new_values) : for i in ('aboprice', 'subscriber') : if not new_values.has_key (i) : raise Reject, err ('mandatory', attr = i) for i in ('invoices', 'end') : if new_values.has_key (i) : raise Reject, err ('forbidden', attr = i) if not new_values.has_key ('amount') : new_values ['amount'] = \ db.abo_price.get (new_values ['aboprice'], 'amount') # if no begin-date is specified, use start of next month # or today if the start of month is today. if not new_values.has_key ('begin') : year, month, day = localtime ()[:3] if day != 1 : day = 1 month += 1 if month > 12 : month = 1 year += 1 new_values ['begin'] = Date ('%d-%d-%d' % (year, month, day)) else : new_values ['begin'] = fix_date (new_values ['begin']) if not new_values.has_key ('payer') : new_values ['payer'] = new_values ['subscriber']
def handle_decline(db, vs): now = Date('.') try_send_mail \ ( db, vs, now , 'MAIL_LEAVE_USER_DECLINE_TEXT' , 'MAIL_LEAVE_USER_DECLINE_SUBJECT' )
def get_vacation_correction (db, user, ctype = -1, date = None) : """ Get latest absolute vacation_correction. Special handling of ctype: None means ctype 'None' while -1 means "don't care, search for *any* ctype". Note that roundups interface for searching specifies -1 when searching for an empty link.... """ if date is None : date = Date ('.') dt = ";%s" % date.pretty (common.ymd) d = dict \ ( user = user , absolute = True , date = dt ) # If no ctype given, try to get dyn. user record on date and use # ctype from there. If not found we simply search for the latest # vacation correction before date. if ctype == -1 : dyn = user_dynamic.get_user_dynamic (db, user, date) if dyn : ctype = dyn.contract_type if ctype != -1 : d ['contract_type'] = ctype if ctype is None : d ['contract_type'] = '-1' # roundup: -1 means search empty vcs = db.vacation_correction.filter (None, d, sort = [('-', 'date')]) if not vcs : return for id in vcs : vc = db.vacation_correction.getnode (id) if ctype == -1 or vc.contract_type == ctype : return vc