Holger Brunn
9 years ago
No known key found for this signature in database
GPG Key ID: 1C9760FECA3AE18
13 changed files with 647 additions and 0 deletions
-
67field_rrule/README.rst
-
5field_rrule/__init__.py
-
20field_rrule/__openerp__.py
-
26field_rrule/demo/res_partner.py
-
15field_rrule/demo/res_partner.xml
-
102field_rrule/field_rrule.py
-
BINfield_rrule/static/description/icon.png
-
22field_rrule/static/src/css/field_rrule.css
-
261field_rrule/static/src/js/field_rrule.js
-
105field_rrule/static/src/xml/field_rrule.xml
-
4field_rrule/tests/__init__.py
-
9field_rrule/tests/test_field_rrule.py
-
11field_rrule/views/templates.xml
@ -0,0 +1,67 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
|
|||
================ |
|||
Repetition rules |
|||
================ |
|||
|
|||
This module was written to offer a field type that holds rrules according to RFC 2445. |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
To use this module, you need to: |
|||
|
|||
* depend on it |
|||
* say ``from openerp.addons.field_rrule.field_rrule import FieldRRule`` |
|||
* use ``FieldRRule`` like any other field |
|||
* on forms, use ``widget="rrule"`` |
|||
* have a look at `demo/res_partner.*` |
|||
|
|||
Technically, this is a wrapper around serialized fields. The value always will be a subclass of dateutil's ``rruleset``. For technical reasons, this class overrides ``__iter__``, so if you need a proper ``rruleset``, call the value: ``my_browse_record.my_field_of_type_rrule()`` - this gives you a vanilla ``rruleset``. |
|||
|
|||
Known issues / Roadmap |
|||
====================== |
|||
|
|||
* support the unimplemented features of rrules |
|||
* support rdates, exdates, exrules |
|||
* consider multiple widgets with different feature sets |
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues |
|||
<https://github.com/OCA/server-tools/issues>`_. In case of trouble, please |
|||
check there if your issue has already been reported. If you spotted it first, |
|||
help us smashing it by providing a detailed and welcomed feedback. |
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Images |
|||
------ |
|||
|
|||
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_. |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Holger Brunn <hbrunn@therp.nl> |
|||
|
|||
Do not contact contributors directly about help with questions or problems concerning this addon, but use the `community mailing list <mailto:community@mail.odoo.com>`_ or the `appropriate specialized mailinglist <https://odoo-community.org/groups>`_ for help, and the bug tracker linked in `Bug Tracker`_ above for technical issues. |
|||
|
|||
Maintainer |
|||
---------- |
|||
|
|||
.. image:: https://odoo-community.org/logo.png |
|||
:alt: Odoo Community Association |
|||
:target: https://odoo-community.org |
|||
|
|||
This module is maintained by the OCA. |
|||
|
|||
OCA, or the Odoo Community Association, is a nonprofit organization whose |
|||
mission is to support the collaborative development of Odoo features and |
|||
promote its widespread use. |
|||
|
|||
To contribute to this module, please visit https://odoo-community.org. |
@ -0,0 +1,5 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from .field_rrule import FieldRRule |
|||
from . import res_partner |
@ -0,0 +1,20 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
{ |
|||
"name": "Repetition Rules", |
|||
"version": "8.0.1.0.0", |
|||
"author": "Therp BV,Odoo Community Association (OCA)", |
|||
"license": "AGPL-3", |
|||
"category": "Hidden/Dependency", |
|||
"summary": "Provides a field and widget for RRules according to RFC 2445", |
|||
"depends": [ |
|||
'web', |
|||
], |
|||
"data": [ |
|||
'views/templates.xml', |
|||
], |
|||
"qweb": [ |
|||
'static/src/xml/field_rrule.xml', |
|||
], |
|||
} |
@ -0,0 +1,26 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from dateutil.rrule import YEARLY, rrule |
|||
from openerp import api, fields, models |
|||
from openerp.addons.field_rrule.field_rrule import FieldRRule,\ |
|||
SerializableRRuleSet |
|||
|
|||
|
|||
class ResPartner(models.Model): |
|||
_inherit = 'res.partner' |
|||
|
|||
@api.depends('rrule') |
|||
def _compute_rrule_representation(self): |
|||
for this in self: |
|||
if not this.rrule: |
|||
this.rrule_representation = 'You did not fill in rules yet' |
|||
continue |
|||
this.rrule_representation = 'First 5 dates: %s\n%s' % ( |
|||
', '.join(map(str, this.rrule[:5])), |
|||
this.rrule, |
|||
) |
|||
|
|||
rrule = FieldRRule('RRule', default=SerializableRRuleSet(rrule(YEARLY))) |
|||
rrule_representation = fields.Text( |
|||
string='RRule representation', compute=_compute_rrule_representation) |
@ -0,0 +1,15 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<openerp> |
|||
<data> |
|||
<record id="d" model="ir.ui.view"> |
|||
<field name="model">res.partner</field> |
|||
<field name="inherit_id" ref="base.view_partner_form" /> |
|||
<field name="arch" type="xml"> |
|||
<field name="website" position="after"> |
|||
<field name="rrule" widget="rrule" /> |
|||
<field name="rrule_representation" /> |
|||
</field> |
|||
</field> |
|||
</record> |
|||
</data> |
|||
</openerp> |
@ -0,0 +1,102 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from dateutil.rrule import rrule, rruleset, YEARLY |
|||
from openerp import fields, models |
|||
_DATETIME_FIELDS = ['_until', '_dtstart'] |
|||
_SCALAR_FIELDS = [ |
|||
'_wkst', '_cache', '_until', '_dtstart', '_count', '_freq', '_interval', |
|||
] |
|||
_ZERO_IS_NOT_NONE = ['_freq'] |
|||
|
|||
|
|||
class SerializableRRuleSet(rruleset, list): |
|||
"""Getting our rule set json stringified is tricky, because we can't |
|||
just inject our own json encoder. Now we pretend our set is a list, |
|||
then json.dumps will try to iterate, which is why we can do our specific |
|||
stuff in __iter__""" |
|||
def __init__(self, *args): |
|||
self._rrule = [] |
|||
super(SerializableRRuleSet, self).__init__(self) |
|||
for arg in args: |
|||
self.rrule(arg) |
|||
|
|||
def __iter__(self): |
|||
for rule in self._rrule: |
|||
yield dict(type='rrule', **{ |
|||
key[1:]: |
|||
fields.Datetime.to_string(getattr(rule, key)) |
|||
if key in _DATETIME_FIELDS |
|||
else |
|||
[] if getattr(rule, key) is None and key not in _SCALAR_FIELDS |
|||
else |
|||
list(getattr(rule, key)) if key not in _SCALAR_FIELDS |
|||
else getattr(rule, key) |
|||
for key in [ |
|||
'_byhour', '_wkst', '_bysecond', '_bymonthday', |
|||
'_byweekno', '_bysetpos', '_cache', '_bymonth', |
|||
'_byyearday', '_byweekday', '_byminute', |
|||
'_until', '_dtstart', '_count', '_freq', '_interval', |
|||
'_byeaster', |
|||
] |
|||
}) |
|||
# TODO: implement rdate, exrule, exdate |
|||
|
|||
def __call__(self, default_self=None): |
|||
"""convert self to a proper rruleset for iteration. |
|||
If we use defaults on our field, this will be called too with |
|||
and empty recordset as parameter. In this case, we need self""" |
|||
if isinstance(default_self, models.Model): |
|||
return self |
|||
result = rruleset() |
|||
result._rrule = self._rrule |
|||
result._rdate = self._rdate |
|||
result._exrule = self._exrule |
|||
result._exdate = self._exdate |
|||
return result |
|||
|
|||
def __exit__(self): |
|||
pass |
|||
|
|||
def __nonzero__(self): |
|||
return bool(self._rrule) |
|||
|
|||
def __repr__(self): |
|||
return ', '.join(str(a) for a in self) |
|||
|
|||
def __getitem__(self, key): |
|||
return rruleset.__getitem__(self(), key) |
|||
|
|||
def __getslice__(self, i, j): |
|||
return rruleset.__getitem__(self(), slice(i, j)) |
|||
|
|||
|
|||
class FieldRRule(fields.Serialized): |
|||
def convert_to_cache(self, value, record, validate=True): |
|||
result = SerializableRRuleSet() |
|||
if not value: |
|||
return result |
|||
if isinstance(value, SerializableRRuleSet): |
|||
return value |
|||
assert isinstance(value, list), 'An RRULE\'s content must be a list' |
|||
for data in value: |
|||
assert isinstance(data, dict), 'The list must contain dictionaries' |
|||
assert 'type' in data, 'The dictionary must contain a type' |
|||
data_type = data['type'] |
|||
data = { |
|||
key: fields.Datetime.from_string(value) |
|||
if '_%s' % key in _DATETIME_FIELDS |
|||
else map(int, value) |
|||
if value and '_%s' % key not in _SCALAR_FIELDS |
|||
else int(value) if value |
|||
else None if not value and '_%s' % key not in _ZERO_IS_NOT_NONE |
|||
else value |
|||
for key, value in data.iteritems() |
|||
if key != 'type' |
|||
} |
|||
if data_type == 'rrule': |
|||
result.rrule(rrule(**data)) |
|||
# TODO: implement rdate, exrule, exdate |
|||
else: |
|||
raise ValueError('Unknown type given') |
|||
return result |
After Width: 128 | Height: 128 | Size: 9.2 KiB |
@ -0,0 +1,22 @@ |
|||
.oe_form_field_rrule .rule_item label |
|||
{ |
|||
white-space: nowrap; |
|||
} |
|||
.oe_form_field_rrule .rule_header |
|||
{ |
|||
text-align: right; |
|||
padding-right: 8px; |
|||
} |
|||
.oe_form_field_rrule th |
|||
{ |
|||
vertical-align: top; |
|||
padding-right: 8px; |
|||
} |
|||
.oe_form_editable .oe_form_field_rrule form.rule_item:hover .rule_header |
|||
{ |
|||
background: #7c7bad; |
|||
} |
|||
.oe_form_editable .oe_form_field_rrule form.rule_item:hover .rule_header .oe_link |
|||
{ |
|||
color: white; |
|||
} |
@ -0,0 +1,261 @@ |
|||
//-*- coding: utf-8 -*-
|
|||
//© 2016 Therp BV <http://therp.nl>
|
|||
//License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|||
|
|||
openerp.field_rrule = function(instance) |
|||
{ |
|||
instance.field_rrule.FieldRRule = instance.web.form.AbstractField |
|||
.extend(instance.web.form.ReinitializeFieldMixin, { |
|||
template: 'FieldRRule', |
|||
events: { |
|||
'click a.add_rule': 'add_rule', |
|||
'click a.rule_remove': 'remove_rule', |
|||
'change select': 'frequency_changed', |
|||
'change input:not(.rule_ignore_input)': 'input_changed', |
|||
'change input[name="recurrence_type"]': 'toggle_recurrence_type', |
|||
}, |
|||
set_value(val) |
|||
{ |
|||
var result = this._super(jQuery.extend([], val)); |
|||
_.each(this.get('value'), function(rule) |
|||
{ |
|||
rule.__id = _.uniqueId(); |
|||
}); |
|||
this.reinitialize(); |
|||
return result; |
|||
}, |
|||
get_value() |
|||
{ |
|||
var result = jQuery.extend( |
|||
true, [], this._super.apply(this, arguments)); |
|||
_.each(result, function(rule) |
|||
{ |
|||
delete rule['__id']; |
|||
}); |
|||
return result; |
|||
}, |
|||
initialize_content: function() |
|||
{ |
|||
var self = this; |
|||
this.$('select[name="freq"]').trigger('change', true) |
|||
this.$('input[name="recurrence_type"]:checked') |
|||
.trigger('change', true); |
|||
this.$('input[type="datetime"]').each(function() |
|||
{ |
|||
var input = jQuery(this), |
|||
current_value = input.val(); |
|||
input.datetimepicker({ |
|||
onSelect: self.proxy('on_timepicker_select'), |
|||
onClose: self.proxy('on_timepicker_select'), |
|||
closeOnDateSelect: true, |
|||
value: current_value ? instance.web.str_to_datetime( |
|||
instance.web.parse_value( |
|||
input.val(), {type: 'datetime'})) : new Date(), |
|||
}); |
|||
}); |
|||
}, |
|||
frequency_changed: function(e, noreset) |
|||
{ |
|||
var frequency = jQuery(e.currentTarget), |
|||
current_item = frequency |
|||
.parentsUntil('form', 'table.rule_item'); |
|||
current_item.find('[data-visible-freq]').each(function() |
|||
{ |
|||
var node = jQuery(this); |
|||
node.toggle( |
|||
String(node.data('visible-freq')).split(',') |
|||
.indexOf(frequency.val()) >= 0 |
|||
); |
|||
}); |
|||
this.input_changed(e, noreset); |
|||
}, |
|||
input_changed: function(e, noreset) |
|||
{ |
|||
var input = jQuery(e.currentTarget), |
|||
current_item = input |
|||
.parentsUntil('form', 'table.rule_item'), |
|||
all_values = this.get('value') || [], |
|||
old_values = jQuery.extend(true, [], all_values); |
|||
value = _.findWhere(all_values, { |
|||
__id: String(current_item.data('id')), |
|||
}); |
|||
if(jQuery.isArray(value[input.attr('name')])) |
|||
{ |
|||
var input_value = parseInt(input.val()); |
|||
value[input.attr('name')] = _.filter( |
|||
value[input.attr('name')], function(x) { |
|||
return x != input_value |
|||
}); |
|||
if(input.is(':checked')) |
|||
{ |
|||
value[input.attr('name')].push(input_value); |
|||
} |
|||
} |
|||
else if(input.attr('type') == 'datetime') |
|||
{ |
|||
value[input.attr('name')] = instance.web.parse_value( |
|||
input.val(), {type: 'datetime'}); |
|||
} |
|||
else |
|||
{ |
|||
value[input.attr('name')] = input.val() || undefined; |
|||
} |
|||
if(!noreset) |
|||
{ |
|||
this.reset_fields(value, current_item, input.attr('name')); |
|||
} |
|||
this.trigger("change:value", this, { |
|||
oldValue: old_values, |
|||
newValue: all_values, |
|||
}); |
|||
}, |
|||
toggle_recurrence_type: function(e) |
|||
{ |
|||
var type = jQuery(e.currentTarget), |
|||
current_item = type.parentsUntil('tr', 'td'); |
|||
current_item.find('input:not(.rule_ignore_input)').each(function() |
|||
{ |
|||
var input = jQuery(this), |
|||
visible = input.attr('name') == type.attr('value'); |
|||
input.toggle(visible); |
|||
if(visible) |
|||
{ |
|||
input.trigger('change'); |
|||
} |
|||
}); |
|||
}, |
|||
reset_fields: function(rule, current_item, field) |
|||
{ |
|||
// for some fields, we should reset some other fields when they
|
|||
// were changed
|
|||
if(field == 'freq') |
|||
{ |
|||
rule.byweekday = []; |
|||
rule.bymonthday = []; |
|||
rule.byyearday = []; |
|||
current_item.find( |
|||
'[name="byweekday"], [name="bymonthday"], ' + |
|||
'[name="byyearday"]' |
|||
).prop('checked', false); |
|||
} |
|||
if(field == 'dtstart') |
|||
{ |
|||
rule.byhour = []; |
|||
rule.byminute = []; |
|||
rule.bysecond = []; |
|||
current_item.find( |
|||
'[name="byhour"], [name="bysecond"], [name="byminute"]' |
|||
).prop('checked', false); |
|||
} |
|||
if(field == 'until') |
|||
{ |
|||
rule.count = undefined; |
|||
current_item.find('[name="count"]').val('0'); |
|||
} |
|||
if(field == 'count') |
|||
{ |
|||
rule.until = undefined; |
|||
current_item.find('[name="until"]').val(''); |
|||
} |
|||
}, |
|||
add_rule: function(e) |
|||
{ |
|||
var value = this.get('value') || []; |
|||
value.push(this.get_default_rrule()); |
|||
this.set('value', value); |
|||
this.reinitialize(); |
|||
}, |
|||
remove_rule: function(e) |
|||
{ |
|||
var value = this.get('value') || [], |
|||
old_value = jQuery.extend(true, [], value); |
|||
current_item = jQuery(e.currentTarget) |
|||
.parentsUntil('form', 'table.rule_item'), |
|||
current_id = String(current_item.data('id')); |
|||
|
|||
for(var i = 0; i < value.length; i++) |
|||
{ |
|||
if(value[i]['__id'] == current_id) |
|||
{ |
|||
value.splice(i, 1); |
|||
i--; |
|||
} |
|||
} |
|||
this.trigger("change:value", this, { |
|||
oldValue: old_value, |
|||
newValue: value, |
|||
}); |
|||
this.reinitialize(); |
|||
}, |
|||
get_default_rrule: function() |
|||
{ |
|||
return { |
|||
__id: _.uniqueId(), |
|||
type: 'rrule', |
|||
freq: 1, |
|||
count: undefined, |
|||
interval: 1, |
|||
dtstart: instance.datetime_to_str(new Date()), |
|||
byweekday: [], |
|||
bymonthday: [], |
|||
byyearday: [], |
|||
}; |
|||
}, |
|||
on_timepicker_select: function(datestring, input) |
|||
{ |
|||
return this.format_timepicker_value(input.input || input.$input); |
|||
}, |
|||
on_timepicker_month_year: function(year, month, input) |
|||
{ |
|||
return this.format_timepicker_value(input.input || input.$input); |
|||
}, |
|||
format_timepicker_value: function(input) |
|||
{ |
|||
input.val(instance.web.format_value( |
|||
input.datetimepicker('getDate'), {type: 'datetime'})); |
|||
}, |
|||
format_field_weekday: function(weekday) |
|||
{ |
|||
switch(parseInt(weekday)) |
|||
{ |
|||
case 0: return instance.web._t('Monday'); |
|||
case 1: return instance.web._t('Tuesday'); |
|||
case 2: return instance.web._t('Wednesday'); |
|||
case 3: return instance.web._t('Thursday'); |
|||
case 4: return instance.web._t('Friday'); |
|||
case 5: return instance.web._t('Saturday'); |
|||
case 6: return instance.web._t('Sunday'); |
|||
} |
|||
}, |
|||
format_field_freq: function(frequency) |
|||
{ |
|||
switch(parseInt(frequency)) |
|||
{ |
|||
case 0: return instance.web._t('Yearly'); |
|||
case 1: return instance.web._t('Monthly'); |
|||
case 2: return instance.web._t('Weekly'); |
|||
case 3: return instance.web._t('Daily'); |
|||
case 4: return instance.web._t('Hourly'); |
|||
case 5: return instance.web._t('Minutely'); |
|||
case 6: return instance.web._t('Secondly'); |
|||
} |
|||
}, |
|||
format_field_dtstart: function(dtstart) |
|||
{ |
|||
return instance.web.format_value(dtstart, {type: 'datetime'}); |
|||
}, |
|||
format_field_until: function(until) |
|||
{ |
|||
return instance.web.format_value(until, {type: 'datetime'}); |
|||
}, |
|||
format_field_count: function(count) |
|||
{ |
|||
if(!count) |
|||
{ |
|||
return instance.web._t('Indefinitely'); |
|||
} |
|||
return _.str.sprintf(instance.web._t('%s occurrences'), count); |
|||
}, |
|||
}); |
|||
instance.web.form.widgets.add('rrule', 'instance.field_rrule.FieldRRule'); |
|||
} |
@ -0,0 +1,105 @@ |
|||
<template> |
|||
<div t-name="FieldRRule" class="oe_form_field_rrule"> |
|||
<div t-if="!widget.get('effective_readonly')"> |
|||
<a class="oe_link add_rule">Add instance</a> |
|||
</div> |
|||
<form t-foreach="widget.get('value')" t-as="rule" class="rule_item"> |
|||
<table t-if="rule.type == 'rrule'" class="rule_item" t-att-data-id="rule.__id"> |
|||
<tr t-if="!widget.get('effective_readonly')"> |
|||
<td colspan="2" class="rule_header"> |
|||
<a class="oe_link rule_remove" title="Remove this rule"><i class="fa fa-times" /></a> |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<th>Begin</th> |
|||
<td t-if="widget.get('effective_readonly')"> |
|||
<t t-esc="widget.format_field_dtstart(rule.dtstart)" /> |
|||
</td> |
|||
<td t-if="!widget.get('effective_readonly')" class="oe_form_required"> |
|||
<input name="dtstart" type="datetime" t-att-value="widget.format_field_dtstart(rule.dtstart)" required="required" /> |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<th>End</th> |
|||
<td t-if="widget.get('effective_readonly')"> |
|||
<t t-esc="widget.format_field_dtstart(rule.until) or widget.format_field_count(rule.count)" /> |
|||
</td> |
|||
<td t-if="!widget.get('effective_readonly')" class="oe_form_required"> |
|||
<div> |
|||
<label> |
|||
<input type="radio" name="recurrence_type" value="until" class="rule_ignore_input" t-att-checked="rule.until and 'checked' or None" />At a date |
|||
</label> |
|||
<label> |
|||
<input type="radio" name="recurrence_type" value="count" class="rule_ignore_input" t-att-checked="rule.until and None or 'checked'" />After an amount of occurrences |
|||
</label> |
|||
</div> |
|||
<input name="until" type="datetime" t-att-value="widget.format_field_until(rule.until)" required="required" /> |
|||
<input name="count" type="number" step="1" t-att-value="rule.count" required="required" placeholder="Leave empty for no limit" /> |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<th>Recurrence</th> |
|||
<td t-if="widget.get('effective_readonly')"> |
|||
<t t-esc="widget.format_field_freq(rule.freq)" /> |
|||
</td> |
|||
<td t-if="!widget.get('effective_readonly')" class="oe_form_required"> |
|||
<select name="freq" type="select" required="required"> |
|||
<option value="2" t-att-selected="rule.freq == 2 and 'selected' or None">Weekly</option> |
|||
<option value="1" t-att-selected="rule.freq == 1 and 'selected' or None">Monthly</option> |
|||
<option value="0" t-att-selected="rule.freq == 0 and 'selected' or None">Yearly</option> |
|||
</select> |
|||
</td> |
|||
</tr> |
|||
<tr> |
|||
<th>Interval</th> |
|||
<td t-if="widget.get('effective_readonly')"> |
|||
<t t-esc="rule.interval" /> |
|||
</td> |
|||
<td t-if="!widget.get('effective_readonly')" class="oe_form_required"> |
|||
<input type="number" step="1" name="interval" t-att-value="rule.interval" /> |
|||
</td> |
|||
</tr> |
|||
<tr data-visible-freq="0" t-if="!widget.get('effective_readonly') or rule.freq == 0"> |
|||
<th>Day of year</th> |
|||
<td t-if="!widget.get('effective_readonly')"> |
|||
<t t-foreach="_.range(1, 366)" t-as="day_of_year"> |
|||
<label><input name="byyearday" type="checkbox" t-att-value="day_of_year" t-att-checked="rule.byyearday.indexOf(day_of_year) >= 0 and 'checked' or None" /><t t-esc="day_of_year" /></label> |
|||
</t> |
|||
</td> |
|||
<td t-if="widget.get('effective_readonly')"> |
|||
<t t-foreach="rule.byyearday" t-as="day_of_year"> |
|||
<t t-esc="day_of_year" /><t t-if="!day_of_year_last">, </t> |
|||
</t> |
|||
</td> |
|||
</tr> |
|||
<tr data-visible-freq="1" t-if="!widget.get('effective_readonly') or rule.freq == 1"> |
|||
<th>Day of month</th> |
|||
<td t-if="!widget.get('effective_readonly')"> |
|||
<t t-foreach="_.range(1, 32)" t-as="day_of_month"> |
|||
<label><input name="bymonthday" type="checkbox" t-att-value="day_of_month" t-att-checked="rule.bymonthday.indexOf(day_of_month) >= 0 and 'checked' or None" /><t t-esc="day_of_month" /></label> |
|||
</t> |
|||
</td> |
|||
<td t-if="widget.get('effective_readonly')"> |
|||
<t t-foreach="rule.bymonthday" t-as="day_of_month"> |
|||
<t t-esc="day_of_month" /><t t-if="!day_of_month_last">, </t> |
|||
</t> |
|||
</td> |
|||
</tr> |
|||
<tr data-visible-freq="2" t-if="!widget.get('effective_readonly') or rule.freq == 2"> |
|||
<th>Weekday</th> |
|||
<td t-if="!widget.get('effective_readonly')"> |
|||
<t t-foreach="_.range(0, 7)" t-as="weekday"> |
|||
<label><input name="byweekday" type="checkbox" t-att-value="weekday" t-att-checked="rule.byweekday.indexOf(weekday) >= 0 and 'checked' or None" /><t t-esc="widget.format_field_weekday(weekday)" /></label> |
|||
</t> |
|||
</td> |
|||
<td t-if="widget.get('effective_readonly')"> |
|||
<t t-foreach="rule.byweekday" t-as="weekday"> |
|||
<t t-esc="widget.format_field_weekday(weekday)" /><t t-if="!weekday_last">, </t> |
|||
</t> |
|||
</td> |
|||
</tr> |
|||
</table> |
|||
<hr t-if="!rule_last" /> |
|||
</form> |
|||
</div> |
|||
</template> |
@ -0,0 +1,4 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from . import test_field_rrule |
@ -0,0 +1,9 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# © 2016 Therp BV <http://therp.nl> |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from openerp.tests.common import TransactionCase |
|||
|
|||
|
|||
class TestFieldRrule(TransactionCase): |
|||
def test_field_rrule(self): |
|||
pass |
@ -0,0 +1,11 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<openerp> |
|||
<data> |
|||
<template id="assets_backend" name="field_rrule assets" inherit_id="web.assets_backend"> |
|||
<xpath expr="." position="inside"> |
|||
<script type="text/javascript" src="/field_rrule/static/src/js/field_rrule.js"></script> |
|||
<link rel="stylesheet" href="/field_rrule/static/src/css/field_rrule.css"/> |
|||
</xpath> |
|||
</template> |
|||
</data> |
|||
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue