mirror of
https://github.com/dkmstr/openuds.git
synced 2024-12-22 13:34:04 +03:00
Fixed calendar rules. Almost finished :-)
This commit is contained in:
parent
522d493557
commit
037a4b523b
@ -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'
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
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
|
||||
|
@ -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)
|
||||
|
@ -1,12 +1,33 @@
|
||||
# jshint strict: true
|
||||
((api, $, undefined_) ->
|
||||
"use strict"
|
||||
api.tools = base64: (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
|
||||
|
@ -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) ->
|
||||
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") + ' <b>' + value.name + '</b>' ), content,
|
||||
actionButton: "<button type=\"button\" class=\"btn btn-success button-accept\">" + gettext("Save") + "</button>"
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -10,6 +10,7 @@
|
||||
if gui.debug
|
||||
try
|
||||
console.log args
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -37,18 +37,8 @@
|
||||
<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>
|
||||
<div class="col-sm-9">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -67,7 +57,7 @@
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@ -77,7 +67,7 @@
|
||||
<div class="btn-group" data-toggle="buttons">
|
||||
{% verbatim %}{{#each days}}
|
||||
<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>
|
||||
{{/each}}{% endverbatim %}
|
||||
</div>
|
||||
@ -85,9 +75,12 @@
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<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>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user