[MERGE] merged trunk-dev-framework branch, including a.o diagram view improvements, access rights, bugfixes
bzr revid: odo@openerp.com-20100420120017-wpnt9q169cq9iztr
This commit is contained in:
commit
3af6db6180
|
@ -306,7 +306,7 @@ def load_information_from_description_file(module):
|
|||
if os.path.isfile(description_file):
|
||||
return eval(tools.file_open(description_file).read())
|
||||
|
||||
#TODO: refactor the logger in this file to follow the logging guidelines
|
||||
#TODO: refactor the logger in this file to follow the logging guidelines
|
||||
# for 6.0
|
||||
logging.getLogger('addons').debug('The module %s does not contain a description file:'\
|
||||
'__openerp__.py or __terp__.py (deprecated)', module)
|
||||
|
@ -595,14 +595,14 @@ class MigrationManager(object):
|
|||
log = logging.getLogger('init')
|
||||
|
||||
def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs):
|
||||
|
||||
|
||||
def process_sql_file(cr, file):
|
||||
queries = fp.read().split(';')
|
||||
for query in queries:
|
||||
new_query = ' '.join(query.split())
|
||||
if new_query:
|
||||
cr.execute(new_query)
|
||||
|
||||
|
||||
def load_init_update_xml(cr, m, idref, mode, kind):
|
||||
for filename in package.data.get('%s_xml' % kind, []):
|
||||
logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading %s' % (m, filename))
|
||||
|
@ -631,20 +631,20 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs):
|
|||
else:
|
||||
tools.convert_xml_import(cr, m, fp, idref, mode=mode, noupdate=True, **kwargs)
|
||||
fp.close()
|
||||
|
||||
|
||||
def load_data(cr, module_name, id_map, mode):
|
||||
_load_data(cr, module_name, id_map, mode, 'data')
|
||||
|
||||
|
||||
def load_demo(cr, module_name, id_map, mode):
|
||||
_load_data(cr, module_name, id_map, mode, 'demo')
|
||||
|
||||
|
||||
def load_test(cr, module_name, id_map, mode):
|
||||
cr.commit()
|
||||
try:
|
||||
_load_data(cr, module_name, id_map, mode, 'test')
|
||||
finally:
|
||||
cr.rollback()
|
||||
|
||||
|
||||
def _load_data(cr, module_name, id_map, mode, kind):
|
||||
noupdate = (kind == 'demo')
|
||||
for filename in package.data.get(kind, []):
|
||||
|
@ -660,7 +660,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs):
|
|||
else:
|
||||
tools.convert_xml_import(cr, module_name, file, id_map, mode, noupdate)
|
||||
file.close()
|
||||
|
||||
|
||||
# **kwargs is passed directly to convert_xml_import
|
||||
if not status:
|
||||
status = {}
|
||||
|
@ -722,10 +722,11 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs):
|
|||
if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
|
||||
status['progress'] = (float(statusi)+0.75) / len(graph)
|
||||
load_demo_xml(cr, m, idref, mode)
|
||||
load_demo(cr, m, idref, mode)
|
||||
load_demo(cr, m, idref, mode)
|
||||
cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
|
||||
load_test(cr, m, idref, mode)
|
||||
|
||||
|
||||
load_test(cr, m, idref, mode)
|
||||
|
||||
package_todo.append(package.name)
|
||||
|
||||
migrations.migrate_module(package, 'post')
|
||||
|
|
|
@ -115,13 +115,15 @@ class view(osv.osv):
|
|||
|
||||
return super(view, self).write(cr, uid, ids, vals, context)
|
||||
|
||||
def graph_get(self, cr, uid, id, model, node_obj, conn_obj, src_node, des_node,signal,scale,context={}):
|
||||
def graph_get(self, cr, uid, id, model, node_obj, conn_obj, src_node, des_node,label,scale,context={}):
|
||||
if not label:
|
||||
label = []
|
||||
nodes=[]
|
||||
nodes_name=[]
|
||||
transitions=[]
|
||||
start=[]
|
||||
tres={}
|
||||
sig={}
|
||||
labels={}
|
||||
no_ancester=[]
|
||||
blank_nodes = []
|
||||
|
||||
|
@ -160,9 +162,14 @@ class view(osv.osv):
|
|||
for t in _Arrow_Obj.read(cr,uid, a[_Destination_Field],[]):
|
||||
transitions.append((a['id'], t[des_node][0]))
|
||||
tres[str(t['id'])] = (a['id'],t[des_node][0])
|
||||
if t.has_key(str(signal)) and str(t[signal])=='False':
|
||||
t[signal]=' '
|
||||
sig[str(t['id'])] = (a['id'],t.get(signal,' '))
|
||||
label_string = ""
|
||||
if label:
|
||||
for lbl in eval(label):
|
||||
if t.has_key(str(lbl)) and str(t[lbl])=='False':
|
||||
label_string = label_string + ' '
|
||||
else:
|
||||
label_string = label_string + " " + t[lbl]
|
||||
labels[str(t['id'])] = (a['id'],label_string)
|
||||
g = graph(nodes, transitions, no_ancester)
|
||||
g.process(start)
|
||||
g.scale(*scale)
|
||||
|
@ -171,7 +178,7 @@ class view(osv.osv):
|
|||
for node in nodes_name:
|
||||
results[str(node[0])] = result[node[0]]
|
||||
results[str(node[0])]['name'] = node[1]
|
||||
return {'nodes': results, 'transitions': tres, 'signal' : sig, 'blank_nodes': blank_nodes}
|
||||
return {'nodes': results, 'transitions': tres, 'label' : labels, 'blank_nodes': blank_nodes}
|
||||
view()
|
||||
|
||||
class view_sc(osv.osv):
|
||||
|
|
|
@ -30,11 +30,15 @@
|
|||
<field name="type">diagram</field>
|
||||
<field name="arch" type="xml">
|
||||
<diagram string="Workflow Editor">
|
||||
<node object="workflow.activity">
|
||||
<node object="workflow.activity" shape="rectangle:subflow_id!=False" bgcolor="gray:flow_start==True;grey:flow_stop==True">
|
||||
<field name="name"/>
|
||||
<field name="kind"/>
|
||||
<field name="action"/>
|
||||
<field name="flow_start" invisible="1"/>
|
||||
<field name="flow_stop" invisible="1"/>
|
||||
<field name="subflow_id" invisible="1"/>
|
||||
</node>
|
||||
<arrow object="workflow.transition" source="act_from" destination="act_to" signal="signal">
|
||||
<arrow object="workflow.transition" source="act_from" destination="act_to" label="['signal','condition']">
|
||||
<field name="act_from"/>
|
||||
<field name="act_to"/>
|
||||
<field name="signal"/>
|
||||
|
|
|
@ -114,14 +114,14 @@
|
|||
</para>
|
||||
</xsl:when>
|
||||
<xsl:when test="@tree='float'">
|
||||
<para style="float_right"><font fontName="Helvetica-bold" fontSize="9">
|
||||
<para style="float_right"><font fontName="Helvetica-bold" fontSize="9" color="blue">
|
||||
<xsl:value-of select="."/>
|
||||
</font></para>
|
||||
</xsl:when>
|
||||
|
||||
<xsl:otherwise>
|
||||
<para>
|
||||
<font fontName="Helvetica-bold" fontSize="9">
|
||||
<font fontName="Helvetica-bold" fontSize="9" color="blue">
|
||||
<xsl:value-of select="."/>
|
||||
</font>
|
||||
</para>
|
||||
|
|
|
@ -348,7 +348,7 @@
|
|||
<filter string="My Partners" icon="terp-partner" domain="[('user_id','=',uid)]"/>
|
||||
<separator orientation="vertical"/>
|
||||
<filter string="Customers" icon="terp-partner" domain="[('customer','=',1)]" help="Customer Partners"/>
|
||||
<filter string="Suppliers" icon="terp-partner" domain="[('supplier','=',1)]" help="Supplier Partners" name='supplier'/>
|
||||
<filter string="Suppliers" icon="terp-partner" domain="[('supplier','=',1)]" help="Supplier Partners"/>
|
||||
<separator orientation="vertical"/>
|
||||
<field name="name" select="1"/>
|
||||
<field name="address" select="1"/>
|
||||
|
@ -391,7 +391,7 @@
|
|||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">res.partner</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="domain">[('customer','=',1)]</field>
|
||||
<field name="context">{'default_customer':1}</field>
|
||||
<field name="filter" eval="True"/>
|
||||
</record>
|
||||
|
@ -401,7 +401,7 @@
|
|||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">res.partner</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="domain">[('supplier','=',1)]</field>
|
||||
<field name="context">{'default_customer':0, 'default_supplier':1}</field>
|
||||
<field name="filter" eval="True"/>
|
||||
</record>
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
<rng:attribute name="object"/>
|
||||
<rng:optional><rng:attribute name="shape"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="bgcolor"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="form_view_ref"/></rng:optional>
|
||||
<rng:zeroOrMore>
|
||||
<rng:choice>
|
||||
<rng:ref name="field"/>
|
||||
|
@ -61,7 +62,8 @@
|
|||
<rng:attribute name="object" />
|
||||
<rng:attribute name="source" />
|
||||
<rng:attribute name="destination" />
|
||||
<rng:optional><rng:attribute name="signal"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="form_view_ref"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="label"/></rng:optional>
|
||||
<rng:zeroOrMore>
|
||||
<rng:choice>
|
||||
<rng:ref name="field"/>
|
||||
|
@ -552,7 +554,6 @@
|
|||
<rng:optional><rng:attribute name="context"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="help"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="domain"/></rng:optional>
|
||||
<rng:optional><rng:attribute name="default"/></rng:optional>
|
||||
<rng:zeroOrMore>
|
||||
<rng:choice>
|
||||
<rng:ref name="form" />
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"access_ir_sequence_group_user","ir_sequence group_user","model_ir_sequence",,1,1,1,1
|
||||
"access_ir_sequence_type_group_user","ir_sequence_type group_user","model_ir_sequence_type",,1,0,0,0
|
||||
"access_ir_translation_group_system","ir_translation group_system","model_ir_translation",,1,1,1,1
|
||||
"access_ir_ui_menu_group_user","ir_ui_menu group_user","model_ir_ui_menu",,1,0,0,0
|
||||
"access_ir_ui_menu_group_user","ir_ui_menu group_user","model_ir_ui_menu",,1,0,1,0
|
||||
"access_ir_ui_menu_group_system","ir_ui_menu group_system","model_ir_ui_menu","group_system",1,1,1,1
|
||||
"access_ir_ui_view_group_user","ir_ui_view group_user","model_ir_ui_view",,1,0,0,0
|
||||
"access_ir_ui_view_group_system","ir_ui_view group_system","model_ir_ui_view","group_system",1,1,1,1
|
||||
|
@ -37,7 +37,7 @@
|
|||
"access_ir_ui_view_custom_group_system","ir_ui_view_custom_group_system","model_ir_ui_view_custom","group_system",1,1,1,1
|
||||
"access_ir_ui_view_sc_group_user","ir_ui_view_sc group_user","model_ir_ui_view_sc",,1,1,1,1
|
||||
"access_ir_values_group_erp_manager","ir_values group_erp_manager","model_ir_values","group_erp_manager",1,1,1,1
|
||||
"access_ir_values_group_all","ir_values group_all","model_ir_values",,1,0,0,0
|
||||
"access_ir_values_group_all","ir_values group_all","model_ir_values",,1,0,1,0
|
||||
"access_wizard_ir_model_menu_create_group_system","wizard_ir_model_menu_create group_system","model_wizard_ir_model_menu_create","group_system",1,1,1,1
|
||||
"access_wizard_ir_model_menu_create_line_group_system","wizard_ir_model_menu_create_line group_system","model_wizard_ir_model_menu_create_line","group_system",1,1,1,1
|
||||
"access_wizard_module_lang_export_group_system","wizard_module_lang_export group_system","model_wizard_module_lang_export","group_system",1,1,1,1
|
||||
|
@ -89,7 +89,7 @@
|
|||
"access_res_users_group_erp_manager","res_users group_erp_manager","model_res_users","group_erp_manager",1,1,1,1
|
||||
"access_ir_actions_all","ir_actions_all","model_ir_actions_actions",,1,0,0,0
|
||||
"access_ir_actions_group_system","ir_actions_group_system","model_ir_actions_actions","group_system",1,1,1,1
|
||||
"access_ir_actions_act_window_all","ir_actions_act_window_all","model_ir_actions_act_window",,1,0,0,0
|
||||
"access_ir_actions_act_window_all","ir_actions_act_window_all","model_ir_actions_act_window",,1,0,1,0
|
||||
"access_ir_actions_act_window_system","ir_actions_act_window_system","model_ir_actions_act_window","group_system",1,1,1,1
|
||||
"access_ir_actions_act_window_close_all","ir_actions_act_window_close_all","model_ir_actions_act_window_close",,1,0,0,0
|
||||
"access_ir_actions_act_window_close_group_system","ir_actions_act_window_close_group_system","model_ir_actions_act_window_close","group_system",1,1,1,1
|
||||
|
|
|
|
@ -81,7 +81,6 @@ class _column(object):
|
|||
self.select = select
|
||||
self.selectable = True
|
||||
self.group_operator = args.get('group_operator', False)
|
||||
self.parent_field = args.get('parent_field', False)
|
||||
for a in args:
|
||||
if args[a]:
|
||||
setattr(self, a, args[a])
|
||||
|
@ -367,7 +366,6 @@ class one2many(_column):
|
|||
_type = 'one2many'
|
||||
|
||||
def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
|
||||
args.update({'parent_field':fields_id})
|
||||
_column.__init__(self, string=string, **args)
|
||||
self._obj = obj
|
||||
self._fields_id = fields_id
|
||||
|
|
|
@ -1072,7 +1072,7 @@ class orm_template(object):
|
|||
continue
|
||||
res[f] = {'type': self._columns[f]._type}
|
||||
for arg in ('string', 'readonly', 'states', 'size', 'required', 'group_operator',
|
||||
'change_default', 'translate', 'help', 'select', 'selectable','parent_field'):
|
||||
'change_default', 'translate', 'help', 'select', 'selectable'):
|
||||
if getattr(self._columns[f], arg):
|
||||
res[f][arg] = getattr(self._columns[f], arg)
|
||||
if not read_access:
|
||||
|
@ -1134,7 +1134,31 @@ class orm_template(object):
|
|||
fields = {}
|
||||
childs = True
|
||||
|
||||
if node.tag == 'field':
|
||||
def encode(s):
|
||||
if isinstance(s, unicode):
|
||||
return s.encode('utf8')
|
||||
return s
|
||||
|
||||
if node.tag in ('field', 'node', 'arrow'):
|
||||
if node.get('object'):
|
||||
attrs = {}
|
||||
views = {}
|
||||
xml = "<form>"
|
||||
for f in node:
|
||||
if f.tag in ('field'):
|
||||
xml += etree.tostring(f, encoding="utf-8")
|
||||
xml += "</form>"
|
||||
new_xml = etree.fromstring(encode(xml))
|
||||
ctx = context.copy()
|
||||
ctx['base_model_name'] = self._name
|
||||
xarch, xfields = self.pool.get(node.get('object',False)).__view_look_dom_arch(cr, user, new_xml, view_id, ctx)
|
||||
views[str(f.tag)] = {
|
||||
'arch': xarch,
|
||||
'fields': xfields
|
||||
}
|
||||
attrs = {'views': views}
|
||||
view = False
|
||||
fields = views.get('field',False) and views['field'].get('fields',False)
|
||||
if node.get('name'):
|
||||
attrs = {}
|
||||
try:
|
||||
|
@ -1167,7 +1191,7 @@ class orm_template(object):
|
|||
dom = column._domain
|
||||
dom += eval(node.get('domain','[]'), {'uid':user, 'time':time})
|
||||
context.update(eval(node.get('context','{}')))
|
||||
attrs['selection'] = self.pool.get(relation).name_search(cr, user, '', dom, context=context)
|
||||
attrs['selection'] = self.pool.get(relation).name_search(cr, user, '', dom, context=context,limit=None)
|
||||
if (node.get('required') and not int(node.get('required'))) or not column.required:
|
||||
attrs['selection'].append((False,''))
|
||||
fields[node.get('name')] = attrs
|
||||
|
@ -1253,7 +1277,6 @@ class orm_template(object):
|
|||
|
||||
arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
|
||||
|
||||
#code for diagram view.
|
||||
fields={}
|
||||
if node.tag=='diagram':
|
||||
if node.getchildren()[0].tag=='node':
|
||||
|
@ -1266,7 +1289,6 @@ class orm_template(object):
|
|||
fields[key]=value
|
||||
else:
|
||||
fields = self.fields_get(cr, user, fields_def.keys(), context)
|
||||
|
||||
for field in fields_def:
|
||||
if field == 'id':
|
||||
# sometime, the view may containt the (invisible) field 'id' needed for a domain (when 2 objects have cross references)
|
||||
|
|
|
@ -27,19 +27,24 @@ from report import render
|
|||
import locale
|
||||
|
||||
import time, os
|
||||
from operator import itemgetter
|
||||
from datetime import datetime
|
||||
|
||||
class report_printscreen_list(report_int):
|
||||
def __init__(self, name):
|
||||
report_int.__init__(self, name)
|
||||
self.context = {}
|
||||
self.groupby = []
|
||||
|
||||
def _parse_node(self, root_node):
|
||||
result = []
|
||||
for node in root_node:
|
||||
field_name = node.get('name')
|
||||
if not eval(str(node.attrib.get('invisible',False)),{'context':self.context}):
|
||||
if node.tag == 'field':
|
||||
result.append(node.get('name'))
|
||||
if field_name in self.groupby:
|
||||
continue
|
||||
result.append(field_name)
|
||||
else:
|
||||
result.extend(self._parse_node(node))
|
||||
return result
|
||||
|
@ -55,6 +60,7 @@ class report_printscreen_list(report_int):
|
|||
if not context:
|
||||
context={}
|
||||
self.context = context
|
||||
self.groupby = context.get('group_by',[])
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
model = pool.get(datas['model'])
|
||||
model_id = pool.get('ir.model').search(cr, uid, [('model','=',model._name)])
|
||||
|
@ -64,35 +70,37 @@ class report_printscreen_list(report_int):
|
|||
else:
|
||||
model_desc = model._description
|
||||
self.title = model_desc
|
||||
|
||||
datas['ids'] = ids
|
||||
model = pooler.get_pool(cr.dbname).get(datas['model'])
|
||||
result = model.fields_view_get(cr, uid, view_type='tree', context=context)
|
||||
fields_order = self._parse_string(result['arch'])
|
||||
rows = model.read(cr, uid, datas['ids'], result['fields'].keys(), context)
|
||||
ids2 = [x['id'] for x in rows] # getting the ids from read result
|
||||
if datas['ids'] != ids2: # sorted ids were not taken into consideration for print screen
|
||||
rows_new = []
|
||||
for id in datas['ids']:
|
||||
element = [elem for elem in rows if elem['id']==id]
|
||||
rows_new.append(element[0])
|
||||
rows = rows_new
|
||||
if context.get('group_by',False):
|
||||
if context['group_by'] in fields_order:
|
||||
fields_order.remove(context['group_by'])
|
||||
fields_order.insert(0, context['group_by'])
|
||||
re = model.read_group(cr, uid, [('id','in',ids)], fields_order, context.get('group_by',False),0,None,context)
|
||||
rows=[]
|
||||
for r in re:
|
||||
for f in fields_order:
|
||||
if f not in r:
|
||||
r.update({f:False})
|
||||
r['__group']=True
|
||||
rows.append(r)
|
||||
_ids = model.search(cr, uid, r['__domain'])
|
||||
res=model.read(cr, uid, _ids, result['fields'].keys(), context)
|
||||
for r in res:
|
||||
rows.append(r)
|
||||
fields_order = self.groupby + self._parse_string(result['arch'])
|
||||
if self.groupby:
|
||||
rows = []
|
||||
def get_groupby_data(groupby = [], domain = []):
|
||||
records = model.read_group(cr, uid, domain, fields_order, groupby , 0, None, context)
|
||||
for rec in records:
|
||||
rec['__group'] = True
|
||||
for f in fields_order:
|
||||
if f not in rec:
|
||||
rec.update({f:False})
|
||||
rows.append(rec)
|
||||
groupby = (rec.get('__context', {})).get('group_by',[])
|
||||
domain = rec.get('__domain', [])
|
||||
if groupby:
|
||||
get_groupby_data(groupby, domain)
|
||||
else:
|
||||
child_ids = model.search(cr, uid, domain)
|
||||
res = model.read(cr, uid, child_ids, result['fields'].keys(), context)
|
||||
rows.extend(res)
|
||||
get_groupby_data(self.groupby, [('id','in',ids)])
|
||||
else:
|
||||
rows = model.read(cr, uid, datas['ids'], result['fields'].keys(), context)
|
||||
ids2 = map(itemgetter('id'), rows) # getting the ids from read result
|
||||
if datas['ids'] != ids2: # sorted ids were not taken into consideration for print screen
|
||||
rows_new = []
|
||||
for id in datas['ids']:
|
||||
rows_new += [elem for elem in rows if elem['id'] == id]
|
||||
rows = rows_new
|
||||
res = self._create_table(uid, datas['ids'], result['fields'], fields_order, rows, context, model_desc)
|
||||
return (self.obj.get(), 'pdf')
|
||||
|
||||
|
@ -116,13 +124,14 @@ class report_printscreen_list(report_int):
|
|||
|
||||
l = []
|
||||
t = 0
|
||||
rowcount=0;
|
||||
rowcount = 0;
|
||||
strmax = (pageSize[0]-40) * 2.8346
|
||||
temp = []
|
||||
tsum = []
|
||||
count = len(fields_order)
|
||||
for i in range(0, count):
|
||||
temp.append(0)
|
||||
|
||||
tsum.append(0)
|
||||
ince = -1;
|
||||
for f in fields_order:
|
||||
s = 0
|
||||
|
@ -131,7 +140,7 @@ class report_printscreen_list(report_int):
|
|||
s = 60
|
||||
strmax -= s
|
||||
if fields[f]['type'] in ('float','integer'):
|
||||
temp[ince]=1;
|
||||
temp[ince] = 1
|
||||
else:
|
||||
t += fields[f].get('size', 80) / 28 + 1
|
||||
|
||||
|
@ -149,10 +158,6 @@ class report_printscreen_list(report_int):
|
|||
field.text = tools.ustr(fields[f]['string'] or '')
|
||||
|
||||
lines = etree.SubElement(new_doc, 'lines')
|
||||
tsum = []
|
||||
count = len(fields_order)
|
||||
for i in range(0,count):
|
||||
tsum.append(0)
|
||||
for line in results:
|
||||
node_line = etree.SubElement(lines, 'row')
|
||||
count = -1
|
||||
|
@ -212,9 +217,8 @@ class report_printscreen_list(report_int):
|
|||
tsum[count] = float(tsum[count]) + float(line[f]);
|
||||
else:
|
||||
col.text = '/'
|
||||
|
||||
node_line = etree.SubElement(lines, 'row')
|
||||
for f in range(0,count+1):
|
||||
for f in range(0, count+1):
|
||||
col = etree.SubElement(node_line, 'col', para='group', tree='no')
|
||||
if tsum[f] != None:
|
||||
if tsum[f] >= 0.01 :
|
||||
|
|
Loading…
Reference in New Issue