1
0
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:
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.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'

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)
'''
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')

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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") + ' <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

View File

@ -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

View File

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

View File

@ -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>
<div class="input-group date">
<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>