From 037a4b523b41a7ed158d65dbc39bcb188f01d7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Thu, 17 Sep 2015 06:53:35 +0200 Subject: [PATCH] Fixed calendar rules. Almost finished :-) --- server/src/uds/REST/methods/calendarrules.py | 19 +++- server/src/uds/REST/processors.py | 6 +- server/src/uds/core/util/calendar/__init__.py | 30 +++-- server/src/uds/models/CalendarRule.py | 24 +++- server/src/uds/static/adm/js/api-tools.coffee | 25 ++++- .../uds/static/adm/js/gui-d-calendar.coffee | 103 +++++++++++++++--- server/src/uds/static/adm/js/gui-tools.coffee | 7 +- server/src/uds/static/adm/js/gui.coffee | 1 + .../uds/admin/tmpl/calendar_rule.html | 27 ++--- 9 files changed, 185 insertions(+), 57 deletions(-) diff --git a/server/src/uds/REST/methods/calendarrules.py b/server/src/uds/REST/methods/calendarrules.py index 7794aa77e..0325f0202 100644 --- a/server/src/uds/REST/methods/calendarrules.py +++ b/server/src/uds/REST/methods/calendarrules.py @@ -36,6 +36,7 @@ from django.utils.translation import ugettext as _ from uds.models.CalendarRule import freqs, CalendarRule +from uds.models.Util import getSqlDatetime from uds.core.util import log from uds.core.util import permissions @@ -47,6 +48,7 @@ from django.db import IntegrityError import six import logging +import datetime logger = logging.getLogger(__name__) @@ -95,7 +97,7 @@ class CalendarRules(DetailHandler): # pylint: disable=too-many-public-methods return [ {'name': {'title': _('Rule name')}}, {'start': {'title': _('Start'), 'type': 'datetime'}}, - {'end': {'title': _('End'), 'type': 'datetime'}}, + {'end': {'title': _('End'), 'type': 'date'}}, {'frequency': {'title': _('Frequency'), 'type': 'dict', 'dict': dict((v[0], six.text_type(v[1])) for v in freqs) }}, {'interval': {'title': _('Interval'), 'type': 'callback'}}, {'duration': {'title': _('Duration'), 'type': 'callback'}}, @@ -106,7 +108,12 @@ class CalendarRules(DetailHandler): # pylint: disable=too-many-public-methods # Extract item db fields # We need this fields for all logger.debug('Saving rule {0} / {1}'.format(parent, item)) - fields = self.readFieldsFromParams(['name', 'comments', 'data_type']) + fields = self.readFieldsFromParams(['name', 'comments', 'frequency', 'start', 'end', 'interval', 'duration']) + # Convert timestamps to datetimes + fields['start'] = datetime.datetime.fromtimestamp(fields['start']) + if fields['end'] != None: + fields['end'] = datetime.datetime.fromtimestamp(fields['end']) + calRule = None try: if item is None: # Create new @@ -126,14 +133,14 @@ class CalendarRules(DetailHandler): # pylint: disable=too-many-public-methods return self.getItems(parent, calRule.uuid) def deleteItem(self, parent, item): + logger.debug('Deleting rule {} from {}'.format(item, parent)) try: calRule = parent.rules.get(uuid=processUuid(item)) - - if calRule.deployedServices.count() != 0: - raise RequestError('Item has associated deployed rules') - + calRule.calendar.modified = getSqlDatetime() + calRule.calendar.save() calRule.delete() except Exception: + logger.exception('Exception') self.invalidItemException() return 'deleted' diff --git a/server/src/uds/REST/processors.py b/server/src/uds/REST/processors.py index 91e19b793..776203b6e 100644 --- a/server/src/uds/REST/processors.py +++ b/server/src/uds/REST/processors.py @@ -94,7 +94,9 @@ class ContentProcessor(object): ''' Helper for renderers. Alters some types so they can be serialized correctly (as we want them to be) ''' - if isinstance(obj, (bool, int, float, six.text_type)): + if obj is None: + return None + elif isinstance(obj, (bool, int, float, six.text_type)): return obj elif isinstance(obj, long): return int(obj) @@ -108,7 +110,7 @@ class ContentProcessor(object): for v in obj: res.append(ContentProcessor.procesForRender(v)) return res - elif isinstance(obj, datetime.datetime): + elif isinstance(obj, (datetime.datetime, datetime.date)): return int(time.mktime(obj.timetuple())) elif isinstance(obj, six.binary_type): return obj.decode('utf-8') diff --git a/server/src/uds/core/util/calendar/__init__.py b/server/src/uds/core/util/calendar/__init__.py index 284da1f37..f71660937 100644 --- a/server/src/uds/core/util/calendar/__init__.py +++ b/server/src/uds/core/util/calendar/__init__.py @@ -42,7 +42,7 @@ import datetime import bitarray import logging -__updated__ = '2015-09-09' +__updated__ = '2015-09-17' logger = logging.getLogger(__name__) @@ -51,6 +51,7 @@ class CalendarChecker(object): calendar = None inverse = False cache = None + updates = 0 def __init__(self, calendar, inverse=False): self.calendar = calendar @@ -60,25 +61,36 @@ class CalendarChecker(object): self.data_time = None def _updateData(self, dtime): + self.updates += 1 self.calendar_modified = self.calendar.modified self.data_time = dtime.date() self.data = bitarray.bitarray(60 * 24) # Granurality is minute self.data.setall(False) - start = datetime.datetime.combine(datetime.date.today(), datetime.datetime.min.time()) - end = datetime.datetime.combine(datetime.date.today(), datetime.datetime.max.time()) + + start = datetime.datetime.combine(self.data_time, datetime.datetime.min.time()) + end = datetime.datetime.combine(self.data_time, datetime.datetime.max.time()) for rule in self.calendar.rules.all(): rr = rule.as_rrule() + + r_end = datetime.datetime.combine(rule.end, datetime.datetime.min.time()) if rule.end is not None else None + duration = rule.duration - _start = start if start > rule.start else rule.start - datetime.timedelta(seconds=1) - _end = end if rule.end is None or end < rule.end else rule.end - print(_start, end) + # Relative start, rrule can "spawn" the days, so we get the start at least the duration of rule to see if it "matches" + _start = (start if start > rule.start else rule.start) - datetime.timedelta(minutes=rule.freqInMinutes()) + _end = end if r_end is None or end < r_end else r_end for val in rr.between(_start, _end, inc=True): - pos = val.hour * 60 + val.minute - posdur = pos + duration + if val.date() != self.data_time: + diff = int((start - val).total_seconds() / 60) + pos = 0 + posdur = duration - diff + if posdur <= 0: + continue + else: + pos = val.hour * 60 + val.minute + posdur = pos + duration if posdur >= 60 * 24: posdur = 60 * 24 - 1 - print(pos, posdur) self.data[pos:posdur] = True # Now self.data can be accessed as an array of booleans, and if diff --git a/server/src/uds/models/CalendarRule.py b/server/src/uds/models/CalendarRule.py index b522e27c3..e588971e7 100644 --- a/server/src/uds/models/CalendarRule.py +++ b/server/src/uds/models/CalendarRule.py @@ -33,7 +33,7 @@ from __future__ import unicode_literals -__updated__ = '2015-09-09' +__updated__ = '2015-09-17' from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -42,7 +42,9 @@ from dateutil import rrule as rules from .UUIDModel import UUIDModel from .Calendar import Calendar +from .Util import getSqlDatetime +import datetime import logging logger = logging.getLogger(__name__) @@ -63,6 +65,13 @@ frq_to_rrl = { 'DAILY': rules.DAILY, } +frq_to_mins = { + 'YEARLY': 366 * 24 * 60, + 'MONTHLY': 31 * 24 * 60, + 'WEEKLY': 7 * 24 * 60, + 'DAILY': 24 * 60, +} + weekdays = [rules.SU, rules.MO, rules.TU, rules.WE, rules.TH, rules.FR, rules.SA] @@ -98,6 +107,19 @@ class CalendarRule(UUIDModel): else: return rules.rrule(frq_to_rrl[self.frequency], interval=self.interval, dtstart=self.start) + def freqInMinutes(self): + if self.frequency != WEEKDAYS: + return frq_to_mins.get(self.frequency, 0) * self.interval + else: + return 7 * 24 * 60 + + + def save(self, *args, **kwargs): + logger.debug('Saving...') + self.calendar.modified = getSqlDatetime() + self.calendar.save() + + return UUIDModel.save(self, *args, **kwargs) def __str__(self): return 'Rule {0}: {1}-{2}, {3}, Interval: {4}, duration: {5}'.format(self.name, self.start, self.end, self.frequency, self.interval, self.duration) diff --git a/server/src/uds/static/adm/js/api-tools.coffee b/server/src/uds/static/adm/js/api-tools.coffee index d9509ce1d..3bb653cef 100644 --- a/server/src/uds/static/adm/js/api-tools.coffee +++ b/server/src/uds/static/adm/js/api-tools.coffee @@ -1,12 +1,33 @@ # jshint strict: true ((api, $, undefined_) -> "use strict" - api.tools = base64: (s) -> - window.btoa unescape(encodeURIComponent(s)) + api.tools = + base64: (s) -> + window.btoa unescape(encodeURIComponent(s)) + input2timeStamp: (inputDate, inputTime) -> + # Just parses date & time in two separate inputs + # inputTime is in format hours:minutes + if inputDate is null or inputDate is undefined + v = new Date(0) + else + tmp = inputDate.split('-') + v = new Date(tmp[0], parseInt(tmp[1])-1, tmp[2]) + + if inputTime != null and inputTime != undefined + tmp = inputTime.split(':') + if v.getTime() != 0 + v.setHours(tmp[0]) + v.setMinutes(tmp[1]) + else + return parseInt(tmp[0])*3600 + parseInt(tmp[1]) * 60 + + return v.getTime() / 1000 + return ) window.api = window.api or {}, jQuery + # Insert strftime into tools # #strftime diff --git a/server/src/uds/static/adm/js/gui-d-calendar.coffee b/server/src/uds/static/adm/js/gui-d-calendar.coffee index 71f462b70..477bf8039 100644 --- a/server/src/uds/static/adm/js/gui-d-calendar.coffee +++ b/server/src/uds/static/adm/js/gui-d-calendar.coffee @@ -1,4 +1,4 @@ -gui.calendars = new GuiElement(api.calendars, "imgal") +gui.calendars = new GuiElement(api.calendars, "calendars") gui.calendars.link = -> "use strict" @@ -45,17 +45,36 @@ gui.calendars.link = -> return Math.floor(data/60) + ":" + ("00" + data%60).slice(-2) + " " + gettext("hours") return fld - newEditFnc = (forEdit) -> - realFnc = (value, refreshFnc) -> - sortFnc = (a, b) -> - return 1 if a.value > b.value - return -1 if a.value < b.value - return 0 + newEditFnc = (rules, forEdit) -> + days = (w.substr(0, 3) for w in weekDays) + sortFnc = (a, b) -> + return 1 if a.value > b.value + return -1 if a.value < b.value + return 0 + fillDateTime = (idDate, stamp) -> + if stamp is null + return + date = new Date(stamp * 1000) + $(idDate).val(api.tools.strftime('%Y-%m-%d', date)) + $(idDate + "-time").val(date.toTimeString().split(':')[0..1].join(':')) + + getDateTime = (idDate, withoutTime) -> + date = $(idDate).val() + if date == '' or date == null + return null + + if withoutTime is undefined + time = $(idDate + '-time').val() + return api.tools.input2timeStamp(date, time) + else + return apit.tools.input2timeStamp(date) + + realFnc = (value, refreshFnc) -> api.templates.get "calendar_rule", (tmpl) -> content = api.templates.evaluate(tmpl, freqs: ( {id: key, value: val[2]} for own key, val of freqDct) - days: (w.substr(0, 3) for w in weekDays) + days: days ) modalId = gui.launchModal((if value is null then gettext("New rule") else gettext("Edit rule") + ' ' + value.name + '' ), content, actionButton: "" @@ -63,15 +82,43 @@ gui.calendars.link = -> $('#div-interval').show() $('#div-weekdays').hide() - # Fill in fields if needed + # + # Fill in fields if needed (editing) + # if value != null - alert('ok') + gui.doLog "Value: ", value + $('#id-rule-name').val(value.name) + $('#id-rule-comments').val(value.comments) + fillDateTime '#id-rule-start', value.start + fillDateTime '#id-rule-end', value.end + $('#id-rule-duration').val(Math.floor(value.duration/60) + ':' + ("00" + value.duration%60).slice(-2)) + # If weekdays, set checkboxes + $('#id-rule-freq').val(value.frequency) + if value.frequency == 'WEEKDAYS' + $('#div-interval').hide() + $('#div-weekdays').show() + gui.doLog "Interval", value.interval + n = value.interval + # Set up Weekdays + for i in [0..6] + if n & 1 != 0 + chk = $('#rule-wd-'+days[i]) + chk.prop('checked', true) + chk.parent().addClass('active') + n >>= 1 + else + $('#id-rule-interval-num').val(value.interval) + + # # apply styles + # gui.tools.applyCustoms modalId + # # Event handlers + # # Change frequency $('#id-rule-freq').on 'change', () -> @@ -96,18 +143,39 @@ gui.calendars.link = -> return $(modalId + ' :input[type=checkbox]').each ()-> - gui.doLog this.name, $(this).prop('checked') values[this.name] = $(this).prop('checked') return - value.name = values.rule_name + data = + name: values.rule_name + comments: values.rule_comments + frequency: values.rule_frequency + start: getDateTime('#id-rule-start') + end: getDateTime('#id-rule-end') + duration: api.tools.input2timeStamp(null, values.rule_duration)/60 if $('#id-rule-freq').val() == 'WEEKDAYS' - value.interval = -1 + n = 1 + val = 0 + for i in [0..6] + if values['wd_'+days[i]] is true + val += n + n <<= 1 + data.interval = val else - value.interval = values.rule_interval + data.interval = values.rule_interval - gui.doLog value, values + closeAndRefresh = () -> + $(modalId).modal "hide" + refreshFnc() + + if value is null + rules.rest.create data, closeAndRefresh, gui.failRequestModalFnc(gettext('Error creating rule'), true) + else + data.id = value.id + rules.rest.save data, closeAndRefresh, gui.failRequestModalFnc(gettext('Error saving rule'), true) + + gui.doLog value, data if forEdit is true @@ -148,8 +216,9 @@ gui.calendars.link = -> onLoad: (k) -> # gui.tools.unblockUI() return # null return - onNew: newEditFnc false - onEdit: newEditFnc true + onNew: newEditFnc rules, false + onEdit: newEditFnc rules, true + onDelete: gui.methods.del(rules, gettext("Delete rule"), gettext("Rule deletion error")) ) return diff --git a/server/src/uds/static/adm/js/gui-tools.coffee b/server/src/uds/static/adm/js/gui-tools.coffee index a30e51964..19bae4378 100644 --- a/server/src/uds/static/adm/js/gui-tools.coffee +++ b/server/src/uds/static/adm/js/gui-tools.coffee @@ -91,17 +91,18 @@ # timepicker $.each $(selector + " input[type=time]:not([readonly])"), (index, tspn) -> $tspn = $(tspn) - $tspn.timepicker + opts = showMeridian: false defaultTime: false + $tspn.timepicker opts + # Activate "cool" selects $.each $(selector + " .selectpicker"), (index, tspn) -> $tspn = $(tspn) length = $tspn.children('option').length if length >= 6 $tspn.attr("data-live-search", "true") - gui.doLog "Length: " + length $tspn.selectpicker() # Activate Touchspinner @@ -143,7 +144,7 @@ # Datetime renderer (with specified format) renderDate: (format) -> (data, type, full) -> - if data == "None" + if data == "None" or data is null data = 7226578800 val = gettext('Never') else diff --git a/server/src/uds/static/adm/js/gui.coffee b/server/src/uds/static/adm/js/gui.coffee index 47dc36fd3..cb8c86b15 100644 --- a/server/src/uds/static/adm/js/gui.coffee +++ b/server/src/uds/static/adm/js/gui.coffee @@ -10,6 +10,7 @@ if gui.debug try console.log args + return diff --git a/server/src/uds/templates/uds/admin/tmpl/calendar_rule.html b/server/src/uds/templates/uds/admin/tmpl/calendar_rule.html index 012468210..a2ce9d812 100644 --- a/server/src/uds/templates/uds/admin/tmpl/calendar_rule.html +++ b/server/src/uds/templates/uds/admin/tmpl/calendar_rule.html @@ -26,7 +26,7 @@
- +
@@ -37,18 +37,8 @@
-
-
-
- -
-
-
-
- - -
-
+
+
@@ -67,7 +57,7 @@
- +
@@ -77,7 +67,7 @@
{% verbatim %}{{#each days}} {{/each}}{% endverbatim %}
@@ -85,9 +75,12 @@
- +
- +
+ + +