[MERGE]: merging from the server development team branch

location : lp:~openerp-dev/openobject-server/trunk-dev-framework

bzr revid: mga@mga-20100317132329-kma6kea8k1clpuco
This commit is contained in:
Mantavya Gajjar
2010-03-17 18:53:29 +05:30
10 changed files with 494 additions and 313 deletions
+2 -1
View File
@@ -32,6 +32,7 @@ import pooler
import netsvc
from osv import fields
import addons
import zipfile
import release
@@ -301,7 +302,7 @@ def load_information_from_description_file(module):
:param module: The name of the module (sale, purchase, ...)
"""
for filename in ['__openerp__.py', '__terp__.py']:
description_file = get_module_resource(module, filename)
description_file = addons.get_module_resource(module, filename)
if os.path.isfile(description_file):
return eval(tools.file_open(description_file).read())
+4 -2
View File
@@ -159,7 +159,7 @@ class act_window(osv.osv):
]
def get_filters(self, cr, uid, model):
cr.execute('select id from ir_act_window a where a.id not in (select act_id from ir_act_window_user_rel) and a.res_model=\''+model+'\' and a.filter=\'1\';')
cr.execute('SELECT id FROM ir_act_window a WHERE a.id not in (SELECT act_id FROM ir_act_window_user_rel) AND a.res_model=\''+model+'\' and a.filter=\'1\';')
all_ids = cr.fetchall()
filter_ids = map(lambda x:x[0],all_ids)
act_ids = self.search(cr,uid,[('res_model','=',model),('filter','=',1),('default_user_ids','in',(','.join(map(str,[uid,])),))])
@@ -377,7 +377,9 @@ server_object_lines()
class actions_server(osv.osv):
def _select_signals(self, cr, uid, context={}):
cr.execute("select distinct w.osv, t.signal from wkf w, wkf_activity a, wkf_transition t where w.id = a.wkf_id and t.act_from = a.id or t.act_to = a.id and t.signal!='' and t.signal not in (null, NULL)")
cr.execute("SELECT distinct w.osv, t.signal FROM wkf w, wkf_activity a, wkf_transition t \
WHERE w.id = a.wkf_id AND t.act_from = a.id OR t.act_to = a.id AND t.signal!='' \
AND t.signal not in (null, NULL)")
result = cr.fetchall() or []
res = []
for rs in result:
+62 -2
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
@@ -15,12 +15,13 @@
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields,osv
from lxml import etree
from tools import graph
import tools
import netsvc
import os
@@ -59,6 +60,7 @@ class view(osv.osv):
('mdx','mdx'),
('graph', 'Graph'),
('calendar', 'Calendar'),
('diagram','Diagram'),
('gantt', 'Gantt'),
('search','Search')), 'View Type', required=True),
'arch': fields.text('View Architecture', required=True),
@@ -129,6 +131,64 @@ class view(osv.osv):
return result
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={}):
nodes=[]
nodes_name=[]
transitions=[]
start=[]
tres={}
sig={}
no_ancester=[]
blank_nodes = []
_Model_Obj=self.pool.get(model)
_Node_Obj=self.pool.get(node_obj)
_Arrow_Obj=self.pool.get(conn_obj)
for model_key,model_value in _Model_Obj._columns.items():
if model_value._type=='one2many':
if model_value._obj==node_obj:
_Node_Field=model_key
_Model_Field=model_value._fields_id
flag=False
for node_key,node_value in _Node_Obj._columns.items():
if node_value._type=='one2many':
if node_value._obj==conn_obj:
if src_node in _Arrow_Obj._columns and flag:
_Source_Field=node_key
if des_node in _Arrow_Obj._columns and not flag:
_Destination_Field=node_key
flag = True
datas = _Model_Obj.read(cr, uid, id, [],context)
for a in _Node_Obj.read(cr,uid,datas[_Node_Field],[]):
if a[_Source_Field] or a[_Destination_Field]:
nodes_name.append((a['id'],a['name']))
nodes.append(a['id'])
else:
blank_nodes.append({'id': a['id'],'name':a['name']})
if a.has_key('flow_start') and a['flow_start']:
start.append(a['id'])
else:
if not a[_Source_Field]:
no_ancester.append(a['id'])
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,' '))
g = graph(nodes, transitions, no_ancester)
g.process(start)
g.scale(*scale)
result = g.result_get()
results = {}
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}
view()
class view_sc(osv.osv):
+53 -34
View File
@@ -44,45 +44,64 @@ class workflow(osv.osv):
wf_service.clear_cache(cr, user)
return super(workflow, self).write(cr, user, ids, vals, context=context)
def get_active_workitems(self, cr, uid, res, res_id, context={}):
cr.execute('select * from wkf where osv=%s limit 1',(res,))
wkfinfo = cr.dictfetchone()
workitems = []
if wkfinfo:
cr.execute('SELECT id FROM wkf_instance \
WHERE res_id=%s AND wkf_id=%s \
ORDER BY state LIMIT 1',
(res_id, wkfinfo['id']))
inst_id = cr.fetchone()
cr.execute('select act_id,count(*) from wkf_workitem where inst_id=%s group by act_id', (inst_id,))
workitems = dict(cr.fetchall())
return {'wkf': wkfinfo, 'workitems': workitems}
#
# scale = (vertical-distance, horizontal-distance, min-node-width(optional), min-node-height(optional), margin(default=20))
#
def graph_get(self, cr, uid, id, scale, context={}):
nodes= []
nodes_name = []
transitions = []
start = []
tres = {}
no_ancester = []
workflow = self.browse(cr, uid, id, context)
for a in workflow.activities:
nodes_name.append((a.id,a.name))
nodes.append(a.id)
if a.flow_start:
start.append(a.id)
else:
if not a.in_transitions:
no_ancester.append(a.id)
for t in a.out_transitions:
transitions.append((a.id, t.act_to.id))
tres[t.id] = (a.id, t.act_to.id)
g = graph(nodes, transitions, no_ancester)
g.process(start)
g.scale(*scale)
result = g.result_get()
results = {}
for node in nodes_name:
results[str(node[0])] = result[node[0]]
results[str(node[0])]['name'] = node[1]
return {'nodes': results, 'transitions': tres}
# def graph_get(self, cr, uid, id, scale, context={}):
#
# nodes= []
# nodes_name = []
# transitions = []
# start = []
# tres = {}
# no_ancester = []
# workflow = self.browse(cr, uid, id, context)
# for a in workflow.activities:
# nodes_name.append((a.id,a.name))
# nodes.append(a.id)
# if a.flow_start:
# start.append(a.id)
# else:
# if not a.in_transitions:
# no_ancester.append(a.id)
#
# for t in a.out_transitions:
# transitions.append((a.id, t.act_to.id))
# tres[t.id] = (a.id, t.act_to.id)
#
#
# g = graph(nodes, transitions, no_ancester)
# g.process(start)
# g.scale(*scale)
# result = g.result_get()
# results = {}
#
# for node in nodes_name:
# results[str(node[0])] = result[node[0]]
# results[str(node[0])]['name'] = node[1]
#
# return {'nodes': results, 'transitions': tres}
def create(self, cr, user, vals, context=None):
+36 -16
View File
@@ -2,13 +2,13 @@
<openerp>
<data>
<menuitem id="menu_workflow_root" name="Workflow Definitions" parent="menu_custom"/>
<!--
================================
Workflows
================================
-->
<record id="view_workflow_form" model="ir.ui.view">
<field name="name">workflow.form</field>
<field name="model">workflow</field>
@@ -23,6 +23,25 @@
</form>
</field>
</record>
<record id="view_workflow_diagram" model="ir.ui.view">
<field name="name">workflow.diagram</field>
<field name="model">workflow</field>
<field name="type">diagram</field>
<field name="arch" type="xml">
<diagram string="Workflow Editor">
<node object="workflow.activity">
<field name="name"/>
<field name="kind"/>
</node>
<arrow object="workflow.transition" source="act_from" destination="act_to" signal="signal">
<field name="act_from"/>
<field name="act_to"/>
<field name="signal"/>
</arrow>
</diagram>
</field>
</record>
<record id="view_workflow_tree" model="ir.ui.view">
<field name="name">workflow.tree</field>
<field name="model">workflow</field>
@@ -35,12 +54,13 @@
</tree>
</field>
</record>
<record id="action_workflow_form" model="ir.actions.act_window">
<field name="name">Workflows</field>
<field name="res_model">workflow</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_workflow_tree"/>
<field name="view_mode">tree,form,diagram</field>
</record>
<menuitem action="action_workflow_form" id="menu_workflow" parent="base.menu_workflow_root"/>
@@ -109,7 +129,7 @@
</tree>
</field>
</record>
<record id="action_workflow_activity_form" model="ir.actions.act_window">
<field name="name">Activities</field>
<field name="res_model">workflow.activity</field>
@@ -117,14 +137,14 @@
<field name="view_id" ref="view_workflow_activity_tree"/>
</record>
<menuitem action="action_workflow_activity_form" id="menu_workflow_activity" parent="base.menu_workflow_root"/>
<!--
================================
Transitions
================================
-->
<record id="view_workflow_transition_form" model="ir.ui.view">
<field name="name">workflow.transition.form</field>
<field name="model">workflow.transition</field>
@@ -157,8 +177,8 @@
</tree>
</field>
</record>
<record id="action_workflow_transition_form" model="ir.actions.act_window">
<field name="name">Transitions</field>
<field name="res_model">workflow.transition</field>
@@ -166,13 +186,13 @@
<field name="view_id" ref="view_workflow_transition_tree"/>
</record>
<menuitem action="action_workflow_transition_form" id="menu_workflow_transition" parent="base.menu_workflow_root"/>
<!--
================================
Instances
================================
-->
<record id="view_workflow_instance_form" model="ir.ui.view">
<field name="name">workflow.instance.form</field>
<field name="model">workflow.instance</field>
@@ -201,7 +221,7 @@
</tree>
</field>
</record>
<record id="action_workflow_instance_form" model="ir.actions.act_window">
<field name="name">Instances</field>
<field name="res_model">workflow.instance</field>
@@ -209,13 +229,13 @@
<field name="view_id" ref="view_workflow_instance_tree"/>
</record>
<menuitem action="action_workflow_instance_form" id="menu_workflow_instance" parent="base.menu_low_workflow"/>
<!--
================================
Workitems
================================
-->
<record id="view_workflow_workitem_form" model="ir.ui.view">
<field name="name">workflow.workitem.form</field>
<field name="model">workflow.workitem</field>
@@ -242,7 +262,7 @@
</tree>
</field>
</record>
<record id="action_workflow_workitem_form" model="ir.actions.act_window">
<field name="name">Workitems</field>
<field name="res_model">workflow.workitem</field>
@@ -250,6 +270,6 @@
<field name="view_id" ref="view_workflow_workitem_tree"/>
</record>
<menuitem action="action_workflow_workitem_form" id="menu_workflow_workitem" parent="base.menu_low_workflow"/>
</data>
</openerp>
+2
View File
@@ -237,6 +237,8 @@ class module(osv.osv):
def button_upgrade(self, cr, uid, ids, context=None):
depobj = self.pool.get('ir.module.module.dependency')
todo = self.browse(cr, uid, ids, context=context)
self.update_list(cr, uid)
i = 0
while i<len(todo):
mod = todo[i]
+48 -5
View File
@@ -31,6 +31,45 @@
</rng:element>
</rng:define>
<rng:define name="diagram">
<rng:element name="diagram">
<rng:optional><rng:attribute name="string"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="node"/>
<rng:ref name="arrow"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="node">
<rng:element name="node">
<rng:attribute name="object"/>
<rng:optional><rng:attribute name="shape"/></rng:optional>
<rng:optional><rng:attribute name="bgcolor"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="field"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="arrow">
<rng:element name="arrow">
<rng:attribute name="object" />
<rng:attribute name="source" />
<rng:attribute name="destination" />
<rng:optional><rng:attribute name="signal"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="field"/>
</rng:choice>
</rng:zeroOrMore>
</rng:element>
</rng:define>
<rng:define name="tree">
<rng:element name="tree">
<rng:optional><rng:attribute name="string"/></rng:optional>
@@ -89,7 +128,7 @@
<rng:attribute name="name"/>
</rng:element>
</rng:define>
<rng:define name="html">
<rng:element name="html">
<rng:zeroOrMore>
@@ -139,7 +178,7 @@
<rng:oneOrMore>
<rng:optional><rng:ref name="field"/></rng:optional>
</rng:oneOrMore>
<rng:zeroOrMore>
<rng:zeroOrMore>
<rng:optional><rng:ref name="html"/></rng:optional>
</rng:zeroOrMore>
</rng:element>
@@ -357,8 +396,10 @@
<rng:optional><rng:attribute name="rowspan"/></rng:optional>
<rng:optional><rng:attribute name="align"/></rng:optional>
<rng:optional><rng:attribute name="selection"/></rng:optional>
<rng:optional><rng:attribute name="default_focus"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="diagram"/>
<rng:ref name="attribute" />
<rng:ref name="form"/>
<rng:ref name="data"/>
@@ -429,7 +470,7 @@
<rng:oneOrMore>
<rng:ref name="field"/>
</rng:oneOrMore>
<rng:zeroOrMore>
<rng:zeroOrMore>
<rng:optional><rng:ref name="html"/></rng:optional>
</rng:zeroOrMore>
</rng:element>
@@ -442,9 +483,9 @@
<rng:optional><rng:attribute name="type" /></rng:optional>
<rng:optional><rng:attribute name="color"/></rng:optional>
<rng:oneOrMore>
<rng:ref name="field"/>
<rng:ref name="field"/>
</rng:oneOrMore>
<rng:zeroOrMore>
<rng:zeroOrMore>
<rng:optional><rng:ref name="html"/></rng:optional>
</rng:zeroOrMore>
</rng:element>
@@ -469,6 +510,7 @@
<rng:optional><rng:attribute name="context"/></rng:optional>
<rng:optional><rng:attribute name="confirm"/></rng:optional>
<rng:optional><rng:attribute name="help"/></rng:optional>
<rng:optional><rng:attribute name="default_focus"/></rng:optional>
<rng:zeroOrMore>
<rng:choice>
<rng:ref name="attribute" />
@@ -545,6 +587,7 @@
<rng:start>
<rng:choice>
<rng:ref name="diagram" />
<rng:ref name="form" />
<rng:ref name="search" />
<rng:ref name="group" />
+3 -1
View File
@@ -81,6 +81,7 @@ 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])
@@ -365,7 +366,8 @@ class one2many(_column):
_prefetch = False
_type = 'one2many'
def __init__(self, obj, fields_id, string='unknown', limit=None, **args):
def __init__(self, obj, fields_id, string='unknown', limit=20, **args):
args.update({'parent_field':fields_id})
_column.__init__(self, string=string, **args)
self._obj = obj
self._fields_id = fields_id
+37 -5
View File
@@ -247,6 +247,15 @@ class browse_record(object):
new_data[n] = browse_null()
elif f._type in ('one2many', 'many2many') and len(data[n]):
new_data[n] = self._list_class([browse_record(self._cr, self._uid, id, self._table.pool.get(f._obj), self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process) for id in data[n]], self._context)
elif f._type in ('reference'):
if data[n]:
ref_obj, ref_id = data[n].split(',')
ref_id = long(ref_id)
obj = self._table.pool.get(ref_obj)
compids = False
new_data[n] = browse_record(self._cr, self._uid, ref_id, obj, self._cache, context=self._context, list_class=self._list_class, fields_process=self._fields_process)
else:
new_data[n] = browse_null()
else:
new_data[n] = data[n]
self._data[data['id']].update(new_data)
@@ -370,6 +379,10 @@ class orm_template(object):
CONCURRENCY_CHECK_FIELD = '__last_update'
def view_init(self, cr , uid , fields_list, context=None):
"""Override this method to do specific things when a view on the object is opened."""
pass
def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None):
raise _('The read_group method is not implemented on this object !')
@@ -383,7 +396,7 @@ class orm_template(object):
model_id = cr.fetchone()[0]
if 'module' in context:
name_id = 'model_'+self._name.replace('.','_')
cr.execute('select * from ir_model_data where name=%s and res_id=%s', (name_id,model_id))
cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id,model_id,context['module']))
if not cr.rowcount:
cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
(name_id, context['module'], 'ir.model', model_id)
@@ -711,6 +724,7 @@ class orm_template(object):
else:
module, xml_id = current_module, line[i]
id = ir_model_data_obj._get_id(cr, uid, module, xml_id)
res_res_id = ir_model_data_obj.read(cr, uid, [id],
['res_id'])
if res_res_id:
@@ -1008,7 +1022,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'):
'change_default', 'translate', 'help', 'select', 'selectable','parent_field'):
if getattr(self._columns[f], arg):
res[f][arg] = getattr(self._columns[f], arg)
if not read_access:
@@ -1188,7 +1202,21 @@ class orm_template(object):
button.set('readonly', str(int(not can_click)))
arch = etree.tostring(node, encoding="utf-8").replace('\t', '')
fields = self.fields_get(cr, user, fields_def.keys(), context)
#code for diagram view.
fields={}
if node.tag=='diagram':
if node.getchildren()[0].tag=='node':
node_fields=self.pool.get(node.getchildren()[0].get('object')).fields_get(cr, user, fields_def.keys(), context)
if node.getchildren()[1].tag=='arrow':
arrow_fields = self.pool.get(node.getchildren()[1].get('object')).fields_get(cr, user, fields_def.keys(), context)
for key,value in node_fields.items():
fields[key]=value
for key,value in arrow_fields.items():
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)
@@ -1204,7 +1232,6 @@ class orm_template(object):
msg += "\n\nEither you wrongly customised this view, or some modules bringing those views are not compatible with your current data model"
netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
raise except_orm('View error', msg)
return arch, fields
def __get_default_calendar_view(self):
@@ -1463,7 +1490,7 @@ class orm_template(object):
if context and context.get('active_id',False):
data_menu = self.pool.get('ir.ui.menu').browse(cr, user, context['active_id'], context).action
if data_menu:
act_id = int(data_menu.split(',')[1])
act_id = data_menu.id
if act_id:
data_action = self.pool.get('ir.actions.act_window').browse(cr, user, [act_id], context)[0]
result['submenu'] = getattr(data_action,'menus', False)
@@ -1501,6 +1528,10 @@ class orm_template(object):
'action': resaction,
'relate': resrelate
}
if result['type']=='form' and result['arch'].count("default_focus")>1:
msg = "Form View contain more than one default_focus attribute"
netsvc.Logger().notifyChannel('orm', netsvc.LOG_ERROR, msg)
raise except_orm('View Error !',msg)
return result
_view_look_dom_arch = __view_look_dom_arch
@@ -1679,6 +1710,7 @@ class orm_memory(orm_template):
return id_new
def default_get(self, cr, uid, fields_list, context=None):
self.view_init(cr, uid, fields_list, context)
if not context:
context = {}
value = {}
+247 -247
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
##############################################################################
#
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
@@ -16,7 +16,7 @@
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
@@ -26,80 +26,80 @@ import math
class graph(object):
def __init__(self, nodes, transitions, no_ancester=None):
"""Initailize graph's object
@param nodes: list of ids of nodes in the graph
@param transitions: list of edges in the graph in the form (source_node, destination_node)
@param no_ancester: list of nodes with no incoming edges
@param no_ancester: list of nodes with no incoming edges
"""
self.nodes = nodes or []
self.edges = transitions or []
self.no_ancester = no_ancester or {}
trans = {}
for t in transitions:
trans.setdefault(t[0], [])
trans[t[0]].append(t[1])
self.transitions = trans
self.result = {}
def init_rank(self):
"""Computes rank of the nodes of the graph by finding initial feasible tree
"""
self.edge_wt = {}
for link in self.links:
self.edge_wt[link] = self.result[link[1]]['x'] - self.result[link[0]]['x']
tot_node = self.partial_order.__len__()
#do until all the nodes in the component are searched
#do until all the nodes in the component are searched
while self.tight_tree()<tot_node:
list_node = []
list_edge = []
for node in self.nodes:
if node not in self.reachable_nodes:
list_node.append(node)
for edge in self.edge_wt:
if edge not in self.tree_edges:
list_edge.append(edge)
slack = 100
for edge in list_edge:
if ((self.reachable_nodes.__contains__(edge[0]) and edge[1] not in self.reachable_nodes) or
if ((self.reachable_nodes.__contains__(edge[0]) and edge[1] not in self.reachable_nodes) or
(self.reachable_nodes.__contains__(edge[1]) and edge[0] not in self.reachable_nodes)):
if(slack>self.edge_wt[edge]-1):
slack = self.edge_wt[edge]-1
new_edge = edge
if new_edge[0] not in self.reachable_nodes:
delta = -(self.edge_wt[new_edge]-1)
else:
delta = self.edge_wt[new_edge]-1
for node in self.result:
if node in self.reachable_nodes:
self.result[node]['x'] += delta
for edge in self.edge_wt:
self.edge_wt[edge] = self.result[edge[1]]['x'] - self.result[edge[0]]['x']
self.init_cutvalues()
self.edge_wt[edge] = self.result[edge[1]]['x'] - self.result[edge[0]]['x']
self.init_cutvalues()
def tight_tree(self):
self.reachable_nodes = []
self.tree_edges = []
self.reachable_node(self.start)
self.reachable_node(self.start)
return self.reachable_nodes.__len__()
def reachable_node(self, node):
"""Find the nodes of the graph which are only 1 rank apart from each other
"""Find the nodes of the graph which are only 1 rank apart from each other
"""
if node not in self.reachable_nodes:
self.reachable_nodes.append(node)
for edge in self.edge_wt:
@@ -109,27 +109,27 @@ class graph(object):
if edge[1] not in self.reachable_nodes:
self.reachable_nodes.append(edge[1])
self.reachable_node(edge[1])
def init_cutvalues(self):
"""Initailize cut values of edges of the feasible tree.
Edges with negative cut-values are removed from the tree to optimize rank assignment
Edges with negative cut-values are removed from the tree to optimize rank assignment
"""
self.cut_edges = {}
self.head_nodes = []
i=0;
for edge in self.tree_edges:
self.head_nodes = []
rest_edges = []
rest_edges += self.tree_edges
rest_edges.__delitem__(i)
self.head_component(self.start, rest_edges)
self.head_component(self.start, rest_edges)
i+=1
positive = 0
negative = 0
for source_node in self.transitions:
if source_node in self.head_nodes:
if source_node in self.head_nodes:
for dest_node in self.transitions[source_node]:
if dest_node not in self.head_nodes:
negative+=1
@@ -140,62 +140,62 @@ class graph(object):
self.cut_edges[edge] = positive - negative
def head_component(self, node, rest_edges):
"""Find nodes which are reachable from the starting node, after removing an edge
"""
if node not in self.head_nodes:
self.head_nodes.append(node)
for edge in rest_edges:
if edge[0]==node:
if edge[0]==node:
self.head_component(edge[1],rest_edges)
def process_ranking(self, node, level=0):
"""Computes initial feasible ranking after making graph acyclic with depth-first search
"""
if node not in self.result:
self.result[node] = {'y': None, 'x':level, 'mark':0}
else:
if level > self.result[node]['x']:
self.result[node]['x'] = level
if self.result[node]['mark']==0:
self.result[node]['mark'] = 1
for sec_end in self.transitions.get(node, []):
self.process_ranking(sec_end, level+1)
def make_acyclic(self, parent, node, level, tree):
"""Computes Partial-order of the nodes with depth-first search
"""
if node not in self.partial_order:
self.partial_order[node] = {'level':level, 'mark':0}
if parent:
tree.append((parent, node))
if self.partial_order[node]['mark']==0:
self.partial_order[node]['mark'] = 1
for sec_end in self.transitions.get(node, []):
self.links.append((node, sec_end))
self.make_acyclic(node, sec_end, level+1, tree)
return tree
return tree
def rev_edges(self, tree):
"""reverse the direction of the edges whose source-node-partail_order> destination-node-partail_order
to make the graph acyclic
"""reverse the direction of the edges whose source-node-partail_order> destination-node-partail_order
to make the graph acyclic
"""
Is_Cyclic = False
i=0
i=0
for link in self.links:
src = link[0]
des = link[1]
edge_len = self.partial_order[des]['level'] - self.partial_order[src]['level']
edge_len = self.partial_order[des]['level'] - self.partial_order[src]['level']
if edge_len < 0:
self.links.__delitem__(i)
self.links.insert(i, (des, src))
@@ -205,9 +205,9 @@ class graph(object):
elif math.fabs(edge_len) > 1:
Is_Cyclic = True
i += 1
return Is_Cyclic
def exchange(self, e, f):
"""Exchange edges to make feasible-tree optimized
@param edge: edge with negative cut-value
@@ -216,144 +216,144 @@ class graph(object):
self.tree_edges.__delitem__(self.tree_edges.index(e))
self.tree_edges.append(f)
self.init_cutvalues()
def enter_edge(self, edge):
"""Finds a new_edge with minimum slack value to replace an edge with negative cut-value
@param edge: edge with negative cut-value
"""Finds a new_edge with minimum slack value to replace an edge with negative cut-value
@param edge: edge with negative cut-value
"""
self.head_nodes = []
rest_edges = []
rest_edges += self.tree_edges
rest_edges.__delitem__(rest_edges.index(edge))
self.head_component(self.start, rest_edges)
if self.head_nodes.__contains__(edge[1]):
l = []
for node in self.result:
if not self.head_nodes.__contains__(node):
l.append(node)
l.append(node)
self.head_nodes = l
slack = 100
new_edge = edge
for source_node in self.transitions:
if source_node in self.head_nodes:
if source_node in self.head_nodes:
for dest_node in self.transitions[source_node]:
if dest_node not in self.head_nodes:
if(slack>(self.edge_wt[edge]-1)):
slack = self.edge_wt[edge]-1
new_edge = (source_node, dest_node)
return new_edge
return new_edge
def leave_edge(self):
"""Returns the edge with negative cut_value(if exists)
"""Returns the edge with negative cut_value(if exists)
"""
if self.critical_edges:
for edge in self.critical_edges:
self.cut_edges[edge] = 0
for edge in self.cut_edges:
if self.cut_edges[edge]<0:
return edge
return None
return None
def finalize_rank(self, node, level):
self.result[node]['x'] = level
for destination in self.optimal_edges.get(node, []):
self.finalize_rank(destination, level+1)
def normalize(self):
"""The ranks are normalized by setting the least rank to zero.
"""
least_rank = min(map(lambda x: x['x'], self.result.values()))
if(least_rank!=0):
for node in self.result:
self.result[node]['x']-=least_rank
def make_chain(self):
"""Edges between nodes more than one rank apart are replaced by chains of unit
self.result[node]['x']-=least_rank
def make_chain(self):
"""Edges between nodes more than one rank apart are replaced by chains of unit
length edges between temporary nodes.
"""
for edge in self.edge_wt:
if self.edge_wt[edge]>1:
self.transitions[edge[0]].remove(edge[1])
start = self.result[edge[0]]['x']
end = self.result[edge[1]]['x']
for rank in range(start+1, end):
if not self.result.get((rank, 'temp'), False):
for rank in range(start+1, end):
if not self.result.get((rank, 'temp'), False):
self.result[(rank, 'temp')] = {'y': None, 'x': rank, 'mark': 0}
for rank in range(start, end):
if start==rank:
if start==rank:
self.transitions[edge[0]].append((rank+1, 'temp'))
elif rank==end-1:
self.transitions.setdefault((rank, 'temp'), []).append(edge[1])
else:
self.transitions.setdefault((rank, 'temp'), []).append((rank+1, 'temp'))
self.transitions.setdefault((rank, 'temp'), []).append((rank+1, 'temp'))
def init_order(self, node, level):
"""Initialize orders the nodes in each rank with depth-first search
"""
if not self.result[node]['y']:
"""
if not self.result[node]['y']:
self.result[node]['y'] = self.order[level]
self.order[level] = self.order[level]+1
self.order[level] = self.order[level]+1
for sec_end in self.transitions.get(node, []):
self.init_order(sec_end, self.result[sec_end]['x'])
def order_heuristic(self):
for i in range(12):
self.wmedian()
def wmedian(self):
"""Applies median heuristic to find optimzed order of the nodes with in their ranks
"""
for level in self.levels:
for level in self.levels:
node_median = []
nodes = self.levels[level]
for node in nodes:
nodes = self.levels[level]
for node in nodes:
node_median.append((node, self.median_value(node, level-1)))
sort_list = sorted(node_median, key=operator.itemgetter(1))
new_list = [tuple[0] for tuple in sort_list]
self.levels[level] = new_list
order = 0
for node in nodes:
self.result[node]['y'] = order
order +=1
order +=1
def median_value(self, node, adj_rank):
"""Returns median value of a vertex , defined as the median position of the adjacent vertices
@param node: node to process
@param adj_rank: rank 1 less than the node's rank
"""Returns median value of a vertex , defined as the median position of the adjacent vertices
@param node: node to process
@param adj_rank: rank 1 less than the node's rank
"""
adj_nodes = self.adj_position(node, adj_rank)
l = len(adj_nodes)
m = l/2
if l==0:
return -1.0
return -1.0
elif l%2 == 1:
return adj_nodes[m]#median of the middle element
elif l==2:
@@ -361,106 +361,106 @@ class graph(object):
else:
left = adj_nodes[m-1] - adj_nodes[0]
right = adj_nodes[l-1] - adj_nodes[m]
return ((adj_nodes[m-1]*right) + (adj_nodes[m]*left))/(left+right)
return ((adj_nodes[m-1]*right) + (adj_nodes[m]*left))/(left+right)
def adj_position(self, node, adj_rank):
"""Returns list of the present positions of the nodes adjacent to node in the given adjacent rank.
@param node: node to process
@param adj_rank: rank 1 less than the node's rank
@param node: node to process
@param adj_rank: rank 1 less than the node's rank
"""
pre_level_nodes = self.levels.get(adj_rank, [])
pre_level_nodes = self.levels.get(adj_rank, [])
adj_nodes = []
if pre_level_nodes:
for src in pre_level_nodes:
if (self.transitions.get(src) and self.transitions[src].__contains__(node)):
adj_nodes.append(self.result[src]['y'])
return adj_nodes
return adj_nodes
def preprocess_order(self):
levels = {}
for r in self.partial_order:
l = self.result[r]['x']
levels.setdefault(l,[])
levels[l].append(r)
self.levels = levels
def graph_order(self):
"""Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component
def graph_order(self):
"""Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component
"""
mid_pos = None
mid_pos = 0.0
max_level = max(map(lambda x: len(x), self.levels.values()))
for level in self.levels:
if level:
no = len(self.levels[level])
factor = (max_level - no) * 0.10
list = self.levels[level]
factor = (max_level - no) * 0.10
list = self.levels[level]
list.reverse()
if no%2==0:
first_half = list[no/2:]
factor = -factor
factor = -factor
else:
first_half = list[no/2+1:]
if max_level==1:#for the case when horizontal graph is there
self.result[list[no/2]]['y'] = mid_pos + (self.result[list[no/2]]['x']%2 * 0.5)
else:
self.result[list[no/2]]['y'] = mid_pos + factor
last_half = list[:no/2]
last_half = list[:no/2]
i=1
for node in first_half:
self.result[node]['y'] = mid_pos - (i + factor)
i += 1
i=1
for node in last_half:
self.result[node]['y'] = mid_pos + (i + factor)
i += 1
else:
else:
self.max_order += max_level+1
mid_pos = self.result[self.start]['y']
mid_pos = self.result[self.start]['y']
def tree_order(self, node, last=0):
mid_pos = self.result[node]['y']
l = self.transitions.get(node, [])
l.reverse()
no = len(l)
if no%2==0:
first_half = l[no/2:]
factor = 1
first_half = l[no/2:]
factor = 1
else:
first_half = l[no/2+1:]
factor = 0
last_half = l[:no/2]
last_half = l[:no/2]
i=1
for child in first_half:
self.result[child]['y'] = mid_pos - (i - (factor * 0.5))
i += 1
if self.transitions.get(child, False):
if last:
self.result[child]['y'] = last + len(self.transitions[child])/2 + 1
last = self.tree_order(child, last)
if no%2:
mid_node = l[no/2]
self.result[mid_node]['y'] = mid_pos
self.result[mid_node]['y'] = mid_pos
if self.transitions.get((mid_node), False):
if last:
self.result[mid_node]['y'] = last + len(self.transitions[mid_node])/2 + 1
@@ -469,51 +469,51 @@ class graph(object):
if last:
self.result[mid_node]['y'] = last + 1
self.result[node]['y'] = self.result[mid_node]['y']
mid_pos = self.result[node]['y']
i=1
mid_pos = self.result[node]['y']
i=1
last_child = None
for child in last_half:
for child in last_half:
self.result[child]['y'] = mid_pos + (i - (factor * 0.5))
last_child = child
last_child = child
i += 1
if self.transitions.get(child, False):
if last:
self.result[child]['y'] = last + len(self.transitions[child])/2 + 1
self.result[child]['y'] = last + len(self.transitions[child])/2 + 1
last = self.tree_order(child, last)
if last_child:
last = self.result[last_child]['y']
return last
def process_order(self):
"""Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component
return last
def process_order(self):
"""Finds actual-order of the nodes with respect to maximum number of nodes in a rank in component
"""
if self.Is_Cyclic:
max_level = max(map(lambda x: len(x), self.levels.values()))
if max_level%2:
self.result[self.start]['y'] = (max_level+1)/2 + self.max_order + (self.max_order and 1)
else:
self.result[self.start]['y'] = (max_level)/2 + self.max_order + (self.max_order and 1)
self.graph_order()
else:
self.result[self.start]['y'] = 0
else:
self.result[self.start]['y'] = 0
self.tree_order(self.start, 0)
min_order = math.fabs(min(map(lambda x: x['y'], self.result.values())))
index = self.start_nodes.index(self.start)
index = self.start_nodes.index(self.start)
same = False
roots = []
if index>0:
if index>0:
for start in self.start_nodes[:index]:
same = True
same = True
for edge in self.tree_list[start][1:]:
if self.tree_list[self.start].__contains__(edge):
continue
@@ -522,46 +522,46 @@ class graph(object):
break
if same:
roots.append(start)
if roots:
min_order += self.max_order
if roots:
min_order += self.max_order
else:
min_order += self.max_order + 1
for level in self.levels:
for node in self.levels[level]:
self.result[node]['y'] += min_order
if roots:
roots.append(self.start)
one_level_el = self.tree_list[self.start][0][1]
base = self.result[one_level_el]['y']# * 2 / (index + 2)
no = len(roots)
first_half = roots[:no/2]
if no%2==0:
last_half = roots[no/2:]
else:
last_half = roots[no/2+1:]
factor = -math.floor(no/2)
for start in first_half:
self.result[start]['y'] = base + factor
self.result[start]['y'] = base + factor
factor += 1
if no%2:
self.result[roots[no/2]]['y'] = base + factor
factor +=1
for start in last_half:
self.result[start]['y'] = base + factor
self.result[start]['y'] = base + factor
factor += 1
self.max_order = max(map(lambda x: x['y'], self.result.values()))
def find_starts(self):
def find_starts(self):
"""Finds other start nodes of the graph in the case when graph is disconneted
"""
rem_nodes = []
@@ -577,7 +577,7 @@ class graph(object):
count = 0
new_start = rem_nodes[0]
largest_tree = []
for node in rem_nodes:
self.partial_order = {}
tree = self.make_acyclic(None, node, 0, [])
@@ -589,120 +589,120 @@ class graph(object):
if not largest_tree:
new_start = rem_nodes[0]
rem_nodes.remove(new_start)
self.start_nodes.append(new_start)
for edge in largest_tree:
if rem_nodes.__contains__(edge[0]):
rem_nodes.remove(edge[0])
if rem_nodes.__contains__(edge[1]):
rem_nodes.remove(edge[1])
if not rem_nodes:
break
def rank(self):
"""Finds the optimized rank of the nodes using Network-simplex algorithm
@param start: starting node of the component
"""
self.levels = {}
self.levels = {}
self.critical_edges = []
self.partial_order = {}
self.links = []
self.Is_Cyclic = False
self.tree_list[self.start] = self.make_acyclic(None, self.start, 0, [])
self.Is_Cyclic = self.rev_edges(self.tree_list[self.start])
self.Is_Cyclic = self.rev_edges(self.tree_list[self.start])
self.process_ranking(self.start)
self.init_rank()
#make cut values of all tree edges to 0 to optimize feasible tree
e = self.leave_edge()
e = self.leave_edge()
while e :
f = self.enter_edge(e)
if e==f:
self.critical_edges.append(e)
else:
self.exchange(e,f)
self.exchange(e,f)
e = self.leave_edge()
#finalize rank using optimum feasible tree
# self.optimal_edges = {}
# for edge in self.tree_edges:
# source = self.optimal_edges.setdefault(edge[0], [])
# source.append(edge[1])
# self.finalize_rank(self.start, 0)
#normalization
self.normalize()
self.normalize()
for edge in self.edge_wt:
self.edge_wt[edge] = self.result[edge[1]]['x'] - self.result[edge[0]]['x']
def order_in_rank(self):
"""Finds optimized order of the nodes within their ranks using median heuristic
@param start: starting node of the component
@param start: starting node of the component
"""
self.make_chain()
self.preprocess_order()
self.order = {}
max_rank = max(map(lambda x: x, self.levels.keys()))
for i in range(max_rank+1):
self.order[i] = 0
self.init_order(self.start, self.result[self.start]['x'])
for level in self.levels:
self.levels[level].sort(lambda x, y: cmp(self.result[x]['y'], self.result[y]['y']))
self.order_heuristic()
self.order_heuristic()
self.process_order()
def process(self, starting_node):
"""Process the graph to find ranks and order of the nodes
@param starting_node: node from where to start the graph search
@param starting_node: node from where to start the graph search
"""
self.start_nodes = starting_node or []
self.partial_order = {}
self.links = []
self.tree_list = {}
self.partial_order = {}
self.links = []
self.tree_list = {}
if self.nodes:
if self.start_nodes:
#add dummy edges to the nodes which does not have any incoming edges
tree = self.make_acyclic(None, self.start_nodes[0], 0, [])
for node in self.no_ancester:
for sec_node in self.transitions.get(node, []):
if sec_node in self.partial_order.keys():
self.transitions[self.start_nodes[0]].append(node)
break
self.partial_order = {}
tree = self.make_acyclic(None, self.start_nodes[0], 0, [])
# if graph is disconnected or no start-node is given
#than to find starting_node for each component of the node
# if graph is disconnected or no start-node is given
#than to find starting_node for each component of the node
if len(self.nodes) > len(self.partial_order):
self.find_starts()
self.max_order = 0
self.find_starts()
self.max_order = 0
#for each component of the graph find ranks and order of the nodes
for s in self.start_nodes:
for s in self.start_nodes:
self.start = s
self.rank() # First step:Netwoek simplex algorithm
self.order_in_rank() #Second step: ordering nodes within ranks
self.order_in_rank() #Second step: ordering nodes within ranks
def __str__(self):
result = ''
@@ -716,21 +716,21 @@ class graph(object):
def scale(self, maxx, maxy, nwidth=0, nheight=0, margin=20):
"""Computes actual co-ordiantes of the nodes
"""
#for flat edges ie. source an destination nodes are on the same rank
for src in self.transitions:
for des in self.transitions[src]:
if (self.result[des]['x'] - self.result[src]['x'] == 0):
if (self.result[des]['x'] - self.result[src]['x'] == 0):
self.result[src]['x'] += 0.08
self.result[des]['x'] -= 0.08
factorX = maxx + nheight
factorY = maxy + nwidth
factorY = maxy + nwidth
for node in self.result:
self.result[node]['y'] = (self.result[node]['y']) * factorX + margin
self.result[node]['x'] = (self.result[node]['x']) * factorY + margin
def result_get(self):
return self.result
@@ -763,14 +763,14 @@ if __name__=='__main__':
import ImageDraw
img = Image.new("RGB", (800, 600), "#ffffff")
draw = ImageDraw.Draw(img)
result = g.result_get()
node_res = {}
for node in nodes:
node_res[node] = result[node]
for name,node in node_res.items():
draw.arc( (int(node['y']-radius), int(node['x']-radius),int(node['y']+radius), int(node['x']+radius) ), 0, 360, (128,128,128))
draw.text( (int(node['y']), int(node['x'])), name, (128,128,128))