1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-10 01:17:59 +03:00

Fixed calendar rules. Almost finished :-)

This commit is contained in:
Adolfo Gómez García 2015-09-17 06:53:35 +02:00
parent 522d493557
commit 037a4b523b
9 changed files with 185 additions and 57 deletions

View File

@ -36,6 +36,7 @@ from django.utils.translation import ugettext as _
from uds.models.CalendarRule import freqs, CalendarRule from uds.models.CalendarRule import freqs, CalendarRule
from uds.models.Util import getSqlDatetime
from uds.core.util import log from uds.core.util import log
from uds.core.util import permissions from uds.core.util import permissions
@ -47,6 +48,7 @@ from django.db import IntegrityError
import six import six
import logging import logging
import datetime
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -95,7 +97,7 @@ class CalendarRules(DetailHandler): # pylint: disable=too-many-public-methods
return [ return [
{'name': {'title': _('Rule name')}}, {'name': {'title': _('Rule name')}},
{'start': {'title': _('Start'), 'type': 'datetime'}}, {'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) }}, {'frequency': {'title': _('Frequency'), 'type': 'dict', 'dict': dict((v[0], six.text_type(v[1])) for v in freqs) }},
{'interval': {'title': _('Interval'), 'type': 'callback'}}, {'interval': {'title': _('Interval'), 'type': 'callback'}},
{'duration': {'title': _('Duration'), 'type': 'callback'}}, {'duration': {'title': _('Duration'), 'type': 'callback'}},
@ -106,7 +108,12 @@ class CalendarRules(DetailHandler): # pylint: disable=too-many-public-methods
# Extract item db fields # Extract item db fields
# We need this fields for all # We need this fields for all
logger.debug('Saving rule {0} / {1}'.format(parent, item)) 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 calRule = None
try: try:
if item is None: # Create new 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) return self.getItems(parent, calRule.uuid)
def deleteItem(self, parent, item): def deleteItem(self, parent, item):
logger.debug('Deleting rule {} from {}'.format(item, parent))
try: try:
calRule = parent.rules.get(uuid=processUuid(item)) calRule = parent.rules.get(uuid=processUuid(item))
calRule.calendar.modified = getSqlDatetime()
if calRule.deployedServices.count() != 0: calRule.calendar.save()
raise RequestError('Item has associated deployed rules')
calRule.delete() calRule.delete()
except Exception: except Exception:
logger.exception('Exception')
self.invalidItemException() self.invalidItemException()
return 'deleted' return 'deleted'

View File

@ -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) 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 return obj
elif isinstance(obj, long): elif isinstance(obj, long):
return int(obj) return int(obj)
@ -108,7 +110,7 @@ class ContentProcessor(object):
for v in obj: for v in obj:
res.append(ContentProcessor.procesForRender(v)) res.append(ContentProcessor.procesForRender(v))
return res return res
elif isinstance(obj, datetime.datetime): elif isinstance(obj, (datetime.datetime, datetime.date)):
return int(time.mktime(obj.timetuple())) return int(time.mktime(obj.timetuple()))
elif isinstance(obj, six.binary_type): elif isinstance(obj, six.binary_type):
return obj.decode('utf-8') return obj.decode('utf-8')

View File

@ -42,7 +42,7 @@ import datetime
import bitarray import bitarray
import logging import logging
__updated__ = '2015-09-09' __updated__ = '2015-09-17'
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -51,6 +51,7 @@ class CalendarChecker(object):
calendar = None calendar = None
inverse = False inverse = False
cache = None cache = None
updates = 0
def __init__(self, calendar, inverse=False): def __init__(self, calendar, inverse=False):
self.calendar = calendar self.calendar = calendar
@ -60,25 +61,36 @@ class CalendarChecker(object):
self.data_time = None self.data_time = None
def _updateData(self, dtime): def _updateData(self, dtime):
self.updates += 1
self.calendar_modified = self.calendar.modified self.calendar_modified = self.calendar.modified
self.data_time = dtime.date() self.data_time = dtime.date()
self.data = bitarray.bitarray(60 * 24) # Granurality is minute self.data = bitarray.bitarray(60 * 24) # Granurality is minute
self.data.setall(False) 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(): for rule in self.calendar.rules.all():
rr = rule.as_rrule() 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 duration = rule.duration
_start = start if start > rule.start else rule.start - datetime.timedelta(seconds=1) # Relative start, rrule can "spawn" the days, so we get the start at least the duration of rule to see if it "matches"
_end = end if rule.end is None or end < rule.end else rule.end _start = (start if start > rule.start else rule.start) - datetime.timedelta(minutes=rule.freqInMinutes())
print(_start, end) _end = end if r_end is None or end < r_end else r_end
for val in rr.between(_start, _end, inc=True): for val in rr.between(_start, _end, inc=True):
pos = val.hour * 60 + val.minute if val.date() != self.data_time:
posdur = pos + duration 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: if posdur >= 60 * 24:
posdur = 60 * 24 - 1 posdur = 60 * 24 - 1
print(pos, posdur)
self.data[pos:posdur] = True self.data[pos:posdur] = True
# Now self.data can be accessed as an array of booleans, and if # Now self.data can be accessed as an array of booleans, and if

View File

@ -33,7 +33,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
__updated__ = '2015-09-09' __updated__ = '2015-09-17'
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
@ -42,7 +42,9 @@ from dateutil import rrule as rules
from .UUIDModel import UUIDModel from .UUIDModel import UUIDModel
from .Calendar import Calendar from .Calendar import Calendar
from .Util import getSqlDatetime
import datetime
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -63,6 +65,13 @@ frq_to_rrl = {
'DAILY': rules.DAILY, '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] weekdays = [rules.SU, rules.MO, rules.TU, rules.WE, rules.TH, rules.FR, rules.SA]
@ -98,6 +107,19 @@ class CalendarRule(UUIDModel):
else: else:
return rules.rrule(frq_to_rrl[self.frequency], interval=self.interval, dtstart=self.start) 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): 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) return 'Rule {0}: {1}-{2}, {3}, Interval: {4}, duration: {5}'.format(self.name, self.start, self.end, self.frequency, self.interval, self.duration)

View File

@ -1,12 +1,33 @@
# jshint strict: true # jshint strict: true
((api, $, undefined_) -> ((api, $, undefined_) ->
"use strict" "use strict"
api.tools = base64: (s) -> api.tools =
window.btoa unescape(encodeURIComponent(s)) 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 return
) window.api = window.api or {}, jQuery ) window.api = window.api or {}, jQuery
# Insert strftime into tools # Insert strftime into tools
# #
#strftime #strftime

View File

@ -1,4 +1,4 @@
gui.calendars = new GuiElement(api.calendars, "imgal") gui.calendars = new GuiElement(api.calendars, "calendars")
gui.calendars.link = -> gui.calendars.link = ->
"use strict" "use strict"
@ -45,17 +45,36 @@ gui.calendars.link = ->
return Math.floor(data/60) + ":" + ("00" + data%60).slice(-2) + " " + gettext("hours") return Math.floor(data/60) + ":" + ("00" + data%60).slice(-2) + " " + gettext("hours")
return fld return fld
newEditFnc = (forEdit) -> newEditFnc = (rules, forEdit) ->
realFnc = (value, refreshFnc) -> days = (w.substr(0, 3) for w in weekDays)
sortFnc = (a, b) -> sortFnc = (a, b) ->
return 1 if a.value > b.value return 1 if a.value > b.value
return -1 if a.value < b.value return -1 if a.value < b.value
return 0 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) -> api.templates.get "calendar_rule", (tmpl) ->
content = api.templates.evaluate(tmpl, content = api.templates.evaluate(tmpl,
freqs: ( {id: key, value: val[2]} for own key, val of freqDct) 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") + ' <b>' + value.name + '</b>' ), content, modalId = gui.launchModal((if value is null then gettext("New rule") else gettext("Edit rule") + ' <b>' + value.name + '</b>' ), content,
actionButton: "<button type=\"button\" class=\"btn btn-success button-accept\">" + gettext("Save") + "</button>" actionButton: "<button type=\"button\" class=\"btn btn-success button-accept\">" + gettext("Save") + "</button>"
@ -63,15 +82,43 @@ gui.calendars.link = ->
$('#div-interval').show() $('#div-interval').show()
$('#div-weekdays').hide() $('#div-weekdays').hide()
# Fill in fields if needed #
# Fill in fields if needed (editing)
#
if value != null 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 # apply styles
#
gui.tools.applyCustoms modalId gui.tools.applyCustoms modalId
#
# Event handlers # Event handlers
#
# Change frequency # Change frequency
$('#id-rule-freq').on 'change', () -> $('#id-rule-freq').on 'change', () ->
@ -96,18 +143,39 @@ gui.calendars.link = ->
return return
$(modalId + ' :input[type=checkbox]').each ()-> $(modalId + ' :input[type=checkbox]').each ()->
gui.doLog this.name, $(this).prop('checked')
values[this.name] = $(this).prop('checked') values[this.name] = $(this).prop('checked')
return 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' 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 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 if forEdit is true
@ -148,8 +216,9 @@ gui.calendars.link = ->
onLoad: (k) -> onLoad: (k) ->
# gui.tools.unblockUI() # gui.tools.unblockUI()
return # null return return # null return
onNew: newEditFnc false onNew: newEditFnc rules, false
onEdit: newEditFnc true onEdit: newEditFnc rules, true
onDelete: gui.methods.del(rules, gettext("Delete rule"), gettext("Rule deletion error"))
) )
return return

View File

@ -91,17 +91,18 @@
# timepicker # timepicker
$.each $(selector + " input[type=time]:not([readonly])"), (index, tspn) -> $.each $(selector + " input[type=time]:not([readonly])"), (index, tspn) ->
$tspn = $(tspn) $tspn = $(tspn)
$tspn.timepicker opts =
showMeridian: false showMeridian: false
defaultTime: false defaultTime: false
$tspn.timepicker opts
# Activate "cool" selects # Activate "cool" selects
$.each $(selector + " .selectpicker"), (index, tspn) -> $.each $(selector + " .selectpicker"), (index, tspn) ->
$tspn = $(tspn) $tspn = $(tspn)
length = $tspn.children('option').length length = $tspn.children('option').length
if length >= 6 if length >= 6
$tspn.attr("data-live-search", "true") $tspn.attr("data-live-search", "true")
gui.doLog "Length: " + length
$tspn.selectpicker() $tspn.selectpicker()
# Activate Touchspinner # Activate Touchspinner
@ -143,7 +144,7 @@
# Datetime renderer (with specified format) # Datetime renderer (with specified format)
renderDate: (format) -> renderDate: (format) ->
(data, type, full) -> (data, type, full) ->
if data == "None" if data == "None" or data is null
data = 7226578800 data = 7226578800
val = gettext('Never') val = gettext('Never')
else else

View File

@ -10,6 +10,7 @@
if gui.debug if gui.debug
try try
console.log args console.log args
return return

View File

@ -26,7 +26,7 @@
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<div class="input-group bootstrap-timepicker timepicker"> <div class="input-group bootstrap-timepicker timepicker">
<input type="time" value="00:00" name="rule_start_time" class="form-control input-small"> <input type="time" value="00:00" id="id-rule-start-time" name="rule_start_time" class="form-control input-small">
<span class="input-group-addon"><i class="glyphicon glyphicon-time"></i></span> <span class="input-group-addon"><i class="glyphicon glyphicon-time"></i></span>
</div> </div>
</div> </div>
@ -37,18 +37,8 @@
<div class="form-group"> <div class="form-group">
<label for="id-rule-end" class="col-sm-3 control-label" data-toggle="tooltip" data-title="{% trans 'End date'%}">{% trans 'End' %}</label> <label for="id-rule-end" class="col-sm-3 control-label" data-toggle="tooltip" data-title="{% trans 'End date'%}">{% trans 'End' %}</label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="row"> <div class="input-group date">
<div class="col-sm-6"> <input type="date" required placeholder="ending date for rule" id="id-rule-end" name="rule_end_date" clear="true" class="form-control modal_field_data " aria-required="true"><span class="input-group-addon"><i class="glyphicon glyphicon-th"></i></span>
<div class="input-group date">
<input type="date" required placeholder="ending date for rule" id="id-rule-start" name="rule_end_date" class="form-control modal_field_data " aria-required="true"><span class="input-group-addon"><i class="glyphicon glyphicon-th"></i></span>
</div>
</div>
<div class="col-sm-6">
<div class="input-group bootstrap-timepicker timepicker">
<input type="time" value="00:00" name="rule_end_time" class="form-control input-small">
<span class="input-group-addon"><i class="glyphicon glyphicon-time"></i></span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -67,7 +57,7 @@
<div class="form-group" id="div-interval"> <div class="form-group" id="div-interval">
<label for="id-rule-interval-num" class="col-sm-3 control-label" data-toggle="tooltip" data-title="{% trans 'Repeat interval for this rule in frequency units'%}">{% trans 'Interval' %}</label> <label for="id-rule-interval-num" class="col-sm-3 control-label" data-toggle="tooltip" data-title="{% trans 'Repeat interval for this rule in frequency units'%}">{% trans 'Interval' %}</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="id-rule-interval-num" name="rule_interval" type="numeric" class="form-control" placeholder="{% trans 'Repeat interval for rule' %}" autofocus required> <input id="id-rule-interval-num" name="rule_interval" type="numeric" data-minval="0" class="form-control" placeholder="{% trans 'Repeat interval for rule' %}" autofocus required>
</div> </div>
</div> </div>
@ -77,7 +67,7 @@
<div class="btn-group" data-toggle="buttons"> <div class="btn-group" data-toggle="buttons">
{% verbatim %}{{#each days}} {% verbatim %}{{#each days}}
<label class="btn btn-default"> <label class="btn btn-default">
<input type="checkbox" name="wd_{{ this }}" basic="true" autocomplete="off">{{ this }} <input type="checkbox" name="wd_{{ this }}" id="rule-wd-{{ this }}" basic="true" autocomplete="off">{{ this }}
</label> </label>
{{/each}}{% endverbatim %} {{/each}}{% endverbatim %}
</div> </div>
@ -85,9 +75,12 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="id-rule-duration" class="col-sm-3 control-label" data-toggle="tooltip" data-title="{% trans 'Duration for this rule'%}">{% trans 'Duration' %}</label> <label for="id-rule-duration" class="col-sm-3 control-label" data-toggle="tooltip" data-title="{% trans 'Duration for this rule in hours:minutes (Max duration is 99 hours 59 minutes)'%}">{% trans 'Duration' %}</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="id-rule-duration" name="duration" type="numberic" class="form-control" placeholder="{% trans 'Duration for rule' %}" autofocus required> <div class="input-group bootstrap-timepicker timepicker">
<input type="time" value="00:00" id="id-rule-duration" data-max-hours="24" name="rule_duration" class="form-control input-small">
<span class="input-group-addon"><i class="glyphicon glyphicon-time"></i></span>
</div>
</div> </div>
</div> </div>