1 # -*- coding: utf-8 -*-
\r
2 ##############################################################################
\r
4 # OpenERP, Open Source Management Solution
\r
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
\r
7 # This program is free software: you can redistribute it and/or modify
\r
8 # it under the terms of the GNU Affero General Public License as
\r
9 # published by the Free Software Foundation, either version 3 of the
\r
10 # License, or (at your option) any later version.
\r
12 # This program is distributed in the hope that it will be useful,
\r
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
15 # GNU Affero General Public License for more details.
\r
17 # You should have received a copy of the GNU Affero General Public License
\r
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
\r
20 ##############################################################################
\r
27 from manager import ustr
\r
28 from win32com.mapi import mapitags
\r
35 def execute(connector, method, *args):
\r
39 res = getattr(connector,method)(*args)
\r
40 except socket.error,e:
\r
41 if e.args[0] == 111:
\r
42 if wait_count > wait_limit:
\r
43 print "Server is taking too long to start, it has exceeded the maximum limit of %d seconds."%(wait_limit)
\r
46 print 'Please wait %d sec to start server....'%(waittime)
\r
48 time.sleep(waittime)
\r
49 res = execute(connector, method, *args)
\r
55 class XMLRpcConn(object):
\r
56 __name__ = 'XMLRpcConn'
\r
57 _com_interfaces_ = ['_IDTExtensibility2']
\r
58 _public_methods_ = ['GetDBList', 'login', 'GetAllObjects', 'GetObjList', 'InsertObj', 'DeleteObject', 'GetCSList', \
\r
59 'ArchiveToOpenERP', 'IsCRMInstalled', 'GetPartners', 'GetObjectItems', \
\r
60 'CreateCase', 'MakeAttachment', 'CreateContact', 'CreatePartner', 'getitem', 'setitem', \
\r
61 'SearchPartnerDetail', 'WritePartnerValues', 'GetAllState', 'GetAllCountry', 'SearchPartner', 'SearchEmailResources', \
\r
62 'GetCountry', 'GetStates', 'FindCountryForState','CreateEmailAttachment','SearchPartners']
\r
63 _reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER
\r
64 _reg_clsid_ = "{C6399AFD-763A-400F-8191-7F9D0503CAE2}"
\r
65 _reg_progid_ = "Python.OpenERP.XMLRpcConn"
\r
66 _reg_policy_spec_ = "win32com.server.policy.EventHandlerPolicy"
\r
67 def __init__(self,server='localhost',port=8069,uri='http://localhost:8069',webserver='localhost', webport=8080):
\r
71 self._webserver=webserver
\r
72 self._webport=webport
\r
81 self.partner_id_list=None
\r
85 def getitem(self, attrib):
\r
86 v=self.__getattribute__(attrib)
\r
89 def setitem(self, attrib, value):
\r
90 return self.__setattr__(attrib, value)
\r
92 def GetDBList(self):
\r
93 conn = xmlrpclib.ServerProxy(self._uri + '/xmlrpc/db')
\r
95 db_list = execute(conn, 'list')
\r
96 if db_list == False:
\r
106 def login(self,dbname, user, pwd):
\r
107 self._dbname = dbname
\r
110 conn = xmlrpclib.ServerProxy(str(self._uri) + '/xmlrpc/common')
\r
111 uid = execute(conn,'login',dbname, ustr(user), ustr(pwd))
\r
114 def GetAllObjects(self):
\r
115 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
116 ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','search',[])
\r
117 objects = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','read',ids,['model'])
\r
118 obj_list = [item['model'] for item in objects]
\r
121 def GetObjList(self):
\r
122 self._obj_list=list(self._obj_list)
\r
123 self._obj_list.sort(reverse=True)
\r
124 return self._obj_list
\r
126 def InsertObj(self, obj_title,obj_name,image_path):
\r
127 self._obj_list=list(self._obj_list)
\r
128 self._obj_list.append((obj_title,obj_name,ustr(image_path)))
\r
129 self._obj_list.sort(reverse=True)
\r
131 def DeleteObject(self,sel_text):
\r
132 self._obj_list=list(self._obj_list)
\r
133 for obj in self._obj_list:
\r
134 if obj[0] == sel_text:
\r
135 self._obj_list.remove(obj)
\r
138 def ArchiveToOpenERP(self, recs, mail):
\r
141 conn = xmlrpclib.ServerProxy(self._uri + '/xmlrpc/object')
\r
143 new_msg = ext_msg =""
\r
144 message_id = referances = None
\r
146 session = win32com.client.Dispatch("MAPI.session")
\r
147 session.Logon('Outlook')
\r
148 objMessage = session.GetMessage(mail.EntryID, mail.Parent.StoreID)
\r
149 objFields = objMessage.Fields
\r
150 strheader = objFields.Item(mapitags.PR_TRANSPORT_MESSAGE_HEADERS)
\r
151 strheader = ustr(strheader).encode('iso-8859-1')
\r
153 strheader = strheader.replace("\n ", " ").splitlines()
\r
154 for line in strheader:
\r
155 split_here = line.find(":")
\r
156 headers[line[:split_here]] = line[split_here:]
\r
157 temp1 = headers.get('Message-ID')
\r
158 temp2 = headers.get('Message-Id')
\r
159 referances = headers.get('References')
\r
160 if temp1 == None: message_id = temp2
\r
161 if temp2 == None: message_id = temp1
\r
162 startCut = message_id.find("<")
\r
163 endCut = message_id.find(">")
\r
164 message_id = message_id[startCut:endCut+1]
\r
165 if not referances == None:
\r
166 startCut = referances.find("<")
\r
167 endCut = referances.find(">")
\r
168 referances = referances[startCut:endCut+1]
\r
169 except Exception,e:
\r
170 win32ui.MessageBox(str(e),"Archive To OpenERP")
\r
172 attachments=mail.Attachments
\r
173 for rec in recs: #[('res.partner', 3, 'Agrolait')]
\r
176 #Check if mailgate installed
\r
177 object_id = execute ( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','search',[('model','=','mailgate.message')])
\r
179 win32ui.MessageBox("Mailgate is not installed on your configured database '%s' !!\n\nPlease install it to archive the mail."%(self._dbname),"Mailgate not installed",win32con.MB_ICONERROR)
\r
181 object_ids = execute ( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','search',[('model','=',model)])
\r
182 object_name = execute( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','read',object_ids,['name'])[0]['name']
\r
183 #Reading the Object ir.model Name
\r
184 ext_ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'mailgate.message','search',[('message_id','=',message_id),('model','=',model),('res_id','=',res_id)])
\r
186 name = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,model,'read',res_id,['name'])['name']
\r
187 ext_msg += """This mail is already archived to {0} '{1}'.\n""".format(object_name,name)
\r
191 'subject':mail.Subject,
\r
192 'date':str(mail.ReceivedTime),
\r
195 'from':mail.SenderEmailAddress,
\r
197 'message-id':message_id,
\r
198 'references':ustr(referances),
\r
200 obj_list= ['crm.lead','project.issue','hr.applicant','res.partner']
\r
201 if rec[0] not in obj_list:
\r
202 self.CreateEmailAttachment(rec,mail)
\r
205 result = self.MakeAttachment([rec], mail)
\r
206 attachment_ids = result.get(model, {}).get(res_id, [])
\r
207 execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'email.server.tools','history',model, res_id, msg, attachment_ids)
\r
208 new_msg += """- {0} : {1}\n""".format(object_name,str(rec[2]))
\r
212 t = """Mail archived Successfully with attachments.\n"""+ext_msg
\r
214 win32ui.MessageBox(t,"Archived to OpenERP",win32con.MB_ICONINFORMATION)
\r
217 def IsCRMInstalled(self):
\r
218 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
219 id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','search',[('model','=','crm.lead')])
\r
222 def GetCSList(self):
\r
223 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
224 ids = execute(conn,'execute',self._dbname,int(int(self._uid)),self._pwd,'crm.case.section','search',[])
\r
225 objects = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'crm.case.section','read',ids,['name'])
\r
226 obj_list = [ustr(item['name']).encode('iso-8859-1') for item in objects]
\r
229 def GetPartners(self, search_partner=''):
\r
230 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
234 if not search_partner.strip() == '':
\r
235 domain.append(('name','ilike',ustr(search_partner)))
\r
236 ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.partner','search',domain)
\r
239 obj_list.append((-999, ustr('')))
\r
241 object = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.partner','read',[id],['id','name'])[0]
\r
242 obj_list.append((object['id'], ustr(object['name'])))
\r
243 obj_list.sort(lambda x, y: cmp(x[1],y[1]))
\r
246 def GetObjectItems(self, search_list=[], search_text=''):
\r
248 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
249 for obj in search_list:
\r
250 object_ids = execute ( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','search',[('model','=',obj)])
\r
251 object_name = execute( conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.model','read',object_ids,['name'])[0]['name']
\r
252 if obj == "res.partner.address":
\r
253 ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,obj,'search',['|',('name','ilike',ustr(search_text)),('email','ilike',ustr(search_text))])
\r
254 recs = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,obj,'read',ids,['id','name','street','city'])
\r
256 name = ustr(rec['name'])
\r
258 name += ', ' + ustr(rec['street'])
\r
260 name += ', ' + ustr(rec['city'])
\r
261 res.append((obj,rec['id'],name,object_name))
\r
263 ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,obj,'search',[('name','ilike',ustr(search_text))])
\r
264 recs = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,obj,'read',ids,['id','name'])
\r
266 name = ustr(rec['name'])
\r
267 res.append((obj,rec['id'],name,object_name))
\r
270 def CreateCase(self, section, mail, partner_ids, with_attachments=True):
\r
275 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
276 email, path = eml.generateEML(mail)
\r
278 session = win32com.client.Dispatch("MAPI.session")
\r
279 session.Logon('Outlook')
\r
280 objMessage = session.GetMessage(mail.EntryID, mail.Parent.StoreID)
\r
281 objFields = objMessage.Fields
\r
282 strheader = objFields.Item(mapitags.PR_TRANSPORT_MESSAGE_HEADERS)
\r
283 strheader = ustr(strheader).encode('iso-8859-1')
\r
285 strheader = strheader.replace("\n ", " ").splitlines()
\r
286 for line in strheader:
\r
287 split_here = line.find(":")
\r
288 headers[line[:split_here]] = line[split_here:]
\r
289 temp1 = headers.get('Message-ID')
\r
290 temp2 = headers.get('Message-Id')
\r
291 referances = headers.get('References')
\r
292 if temp1 == None: message_id = temp2
\r
293 if temp2 == None: message_id = temp1
\r
294 startCut = message_id.find("<")
\r
295 endCut = message_id.find(">")
\r
296 message_id = message_id[startCut:endCut+1]
\r
297 email.replace_header('Message-Id',message_id)
\r
298 id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'email.server.tools','process_email',section, str(email))
\r
305 except Exception,e:
\r
306 win32ui.MessageBox("Create Case\n"+str(e),"Mail Reading Error")
\r
309 def MakeAttachment(self, recs, mail):
\r
310 attachments = mail.Attachments
\r
312 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
313 att_folder_path = os.path.abspath(os.path.dirname("%temp%\\"))
\r
314 if not os.path.exists(att_folder_path):
\r
315 os.makedirs(att_folder_path)
\r
316 for rec in recs: #[('res.partner', 3, 'Agrolait')]
\r
320 res['res_model'] = obj
\r
321 attachment_ids = []
\r
322 if obj not in result:
\r
324 for i in xrange(1, attachments.Count+1):
\r
325 fn = ustr(attachments[i].FileName)
\r
329 fn = f[0][0:l] + '.' + f[-1]
\r
330 att_path = os.path.join(att_folder_path,fn)
\r
331 attachments[i].SaveAsFile(att_path)
\r
332 f=open(att_path,"rb")
\r
333 content = "".join(f.readlines()).encode('base64')
\r
335 res['name'] = ustr(attachments[i].DisplayName)
\r
336 res['datas_fname'] = ustr(fn)
\r
337 res['datas'] = content
\r
338 res['res_id'] = obj_id
\r
339 id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.attachment','create',res)
\r
340 attachment_ids.append(id)
\r
341 result[obj].update({obj_id: attachment_ids})
\r
344 def CreateContact(self, res=None):
\r
346 partner = res['partner_id']
\r
347 state = res['state_id']
\r
348 country = res['country_id']
\r
349 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
350 if not partner.strip() == '':
\r
351 partner_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.partner', 'search', [('name','=',ustr(partner))])
\r
352 res.update({'partner_id' : partner_id[0]})
\r
354 res.pop('partner_id')
\r
355 if not state == "":
\r
356 country_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.country', 'search', [('name','=',ustr(country))])
\r
357 res.update({'country_id' : country_id[0]})
\r
359 res.pop('country_id')
\r
360 if not country == "":
\r
361 state_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.country.state', 'search', [('name','=',ustr(state))])
\r
362 res.update({'state_id' : state_id[0]})
\r
364 res.pop('state_id')
\r
365 id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.partner.address','create',res)
\r
368 def CreatePartner(self, res):
\r
370 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
371 ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.partner','search',[('name','=',res['name'])])
\r
374 id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.partner','create',res)
\r
377 def SearchPartnerDetail(self, search_email_id):
\r
379 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
380 address_id = execute(conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.partner.address', 'search', [('email','ilike',ustr(search_email_id))])
\r
381 if not address_id :
\r
383 address = execute(conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.partner.address','read',address_id[0],['id','partner_id','name','street','street2','city','state_id','country_id','phone','mobile','email','fax','zip'])
\r
384 for key, vals in address.items():
\r
385 res_vals.append([key,vals])
\r
388 def WritePartnerValues(self, new_vals):
\r
390 new_dict = dict(new_vals)
\r
391 email=new_dict['email']
\r
392 partner = new_dict['partner']
\r
393 country_val = new_dict['country']
\r
394 state_val = new_dict['state']
\r
395 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
396 partner_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.partner', 'search', [('name','=',ustr(partner))])
\r
397 country_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.country', 'search', [('name','=',ustr(country_val))])
\r
398 state_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.country.state', 'search', [('name','=',ustr(state_val))])
\r
399 address_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.partner.address', 'search', [('email','=',ustr(email))])
\r
400 if not partner_id or not address_id or not country_id or not state_id:
\r
403 'partner_id' : partner_id[0],
\r
404 'name' : new_dict['name'],
\r
405 'street':new_dict['street'],
\r
406 'street2' : new_dict['street2'],
\r
407 'city' : new_dict['city'],
\r
408 'phone' : new_dict['phone'],
\r
409 'mobile' : new_dict['mobile'],
\r
410 'fax' : new_dict['fax'],
\r
411 'zip' : new_dict['zip'],
\r
412 'country_id' : country_id[0],
\r
413 'state_id' : state_id[0]
\r
415 temp = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.partner.address', 'write', address_id, vals_res_address)
\r
422 def GetAllState(self):
\r
425 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
426 state_ids = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.country.state', 'search', [])
\r
427 for state_id in state_ids:
\r
428 obj = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.country.state', 'read', [state_id],['id','name'])[0]
\r
429 state_list.append((obj['id'], ustr(obj['name'])))
\r
432 def GetAllCountry(self):
\r
435 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
436 country_ids = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.country', 'search', [])
\r
437 for country_id in country_ids:
\r
438 obj = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.country','read', [country_id], ['id','name'])[0]
\r
439 country_list.append((obj['id'], ustr(obj['name'])))
\r
440 return country_list
\r
442 def SearchPartner(self, mail_id = ""):
\r
443 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
444 address = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.partner.address', 'search', [('email','=',ustr(mail_id))])
\r
448 add_rec = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'res.partner.address', 'read', address[0])
\r
449 partner = add_rec.get('partner_id',False)
\r
454 def SearchEmailResources(self, message_id):
\r
456 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
458 mail_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message', 'search', [('message_id','=',message_id)])
\r
461 ref_mail_id = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message', 'search', [('references','=',message_id)])
\r
463 win32ui.MessageBox(str(ref_mail_id),"ref_mail_id")
\r
464 address = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message','read',ref_mail_id[0],['model','res_id'])
\r
465 win32ui.MessageBox(str(address),"address")
\r
466 for key, vals in address.items():
\r
467 res_vals.append([key,vals])
\r
470 address = execute( conn, 'execute', self._dbname, int(self._uid), self._pwd, 'mailgate.message','read',mail_id[0],['model','res_id'])
\r
471 for key, vals in address.items():
\r
472 res_vals.append([key,vals])
\r
476 def GetCountry(self, country_search=''):
\r
477 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
481 if not country_search.strip() == '':
\r
482 domain.append(('name','ilike',ustr(country_search)))
\r
483 ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.country','search',domain)
\r
487 object = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.country','read',[id],['id','name'])[0]
\r
488 obj_list.append((object['id'], ustr(object['name'])))
\r
489 obj_list.sort(lambda x, y: cmp(x[1],y[1]))
\r
492 def GetStates(self, state_search='', country=None):
\r
493 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
498 if not state_search.strip() == '':
\r
499 domain.append(('name','ilike',ustr(state_search)))
\r
501 if country == None:
\r
502 ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.country.state','search',domain)
\r
503 if not country == None:
\r
504 c_id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.country','search',[('name','=',ustr(country))])
\r
505 domain.append(('country_id','=',c_id[0]))
\r
506 ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.country.state','search',domain)
\r
510 object = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.country.state','read',[id],['id','name'])[0]
\r
511 obj_list.append((object['id'], ustr(object['name'])))
\r
512 obj_list.sort(lambda x, y: cmp(x[1],y[1]))
\r
515 def FindCountryForState(self, state_search=''):
\r
516 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
517 ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.country.state','search',[('name','=',ustr(state_search))])
\r
520 object = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'res.country.state','read',ids)[0]
\r
521 country = object['country_id'][1]
\r
524 def CreateEmailAttachment(self, rec, mail):
\r
526 email, path = eml.generateEML(mail)
\r
527 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
531 res['res_model'] = obj
\r
532 ls = ['*', '/', '\\', '<', '>', ':', '?', '"', '|', '\t', '\n',':','~']
\r
533 fn = (mail.Subject).replace(' ','')
\r
535 fn = fn.replace(c,'')
\r
539 fn = '-'.join(f[1:])
\r
543 fn = f[0][0:l] + '.' + f[-1]
\r
544 fn = fn[:-4]+".eml"
\r
546 content = "".join(f.readlines()).encode('base64')
\r
549 res['datas_fname'] = fn
\r
550 res['datas'] = content
\r
551 res['res_id'] = obj_id
\r
552 id = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,'ir.attachment','create',res)
\r
555 def SearchPartners(self):
\r
557 conn = xmlrpclib.ServerProxy(self._uri+ '/xmlrpc/object')
\r
558 obj = 'res.partner'
\r
559 ids = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,obj,'search',[])
\r
560 recs = execute(conn,'execute',self._dbname,int(self._uid),self._pwd,obj,'read',ids,['id','name'])
\r
562 name = ustr(rec['name'])
\r
563 res.append((obj,rec['id'],name,obj))
\r