Code cleanup and workflow improvements.

- Removed '_id' postfix in relation fields.
- Added relation with templatebox in document boxes.
- Improved workflow with background alternative to scan process.
- Background scanning process sends a request to the user when it's finished.
This commit is contained in:
Albert Cervera i Areny 2008-10-31 00:33:15 +01:00
parent 67edd150b1
commit 100fc8b3bc
8 changed files with 144 additions and 48 deletions

View File

@ -607,7 +607,7 @@ class MainWindow(QMainWindow):
if self._template.id: if self._template.id:
Rpc.session.call( '/object', 'execute', 'nan.template', 'write', [self._template.id], {'name': self._template.name } ) Rpc.session.call( '/object', 'execute', 'nan.template', 'write', [self._template.id], {'name': self._template.name } )
ids = Rpc.session.call( '/object', 'execute', 'nan.template.box', 'search', [('template_id','=',self._template.id)] ) ids = Rpc.session.call( '/object', 'execute', 'nan.template.box', 'search', [('template','=',self._template.id)] )
Rpc.session.call( '/object', 'execute', 'nan.template.box', 'unlink', ids ) Rpc.session.call( '/object', 'execute', 'nan.template.box', 'unlink', ids )
else: else:
self._template.id = Rpc.session.call( '/object', 'execute', 'nan.template', 'create', {'name': self._template.name } ) self._template.id = Rpc.session.call( '/object', 'execute', 'nan.template', 'create', {'name': self._template.name } )
@ -621,7 +621,7 @@ class MainWindow(QMainWindow):
'feature_y' : x.featureRect.y(), 'feature_y' : x.featureRect.y(),
'feature_width' : x.featureRect.width(), 'feature_width' : x.featureRect.width(),
'feature_height' : x.featureRect.height(), 'feature_height' : x.featureRect.height(),
'template_id': self._template.id, 'template': self._template.id,
'name': x.name, 'name': x.name,
'text': x.text, 'text': x.text,
'recognizer': x.recognizer, 'recognizer': x.recognizer,

View File

@ -34,7 +34,7 @@ class TemplateStorageManager:
def save(template): def save(template):
if template.id: if template.id:
Rpc.session.call( '/object', 'execute', 'nan.template', 'write', [template.id], {'name': template.name } ) Rpc.session.call( '/object', 'execute', 'nan.template', 'write', [template.id], {'name': template.name } )
ids = Rpc.session.call( '/object', 'execute', 'nan.template.box', 'search', [('template_id','=',template.id)] ) ids = Rpc.session.call( '/object', 'execute', 'nan.template.box', 'search', [('template','=',template.id)] )
Rpc.session.call( '/object', 'execute', 'nan.template.box', 'unlink', ids ) Rpc.session.call( '/object', 'execute', 'nan.template.box', 'unlink', ids )
else: else:
template.id = Rpc.session.call( '/object', 'execute', 'nan.template', 'create', {'name': template.name } ) template.id = Rpc.session.call( '/object', 'execute', 'nan.template', 'create', {'name': template.name } )
@ -48,7 +48,7 @@ class TemplateStorageManager:
'feature_y' : x.featureRect.y(), 'feature_y' : x.featureRect.y(),
'feature_width' : x.featureRect.width(), 'feature_width' : x.featureRect.width(),
'feature_height' : x.featureRect.height(), 'feature_height' : x.featureRect.height(),
'template_id': template.id, 'template': template.id,
'name': x.name, 'name': x.name,
'text': x.text, 'text': x.text,
'recognizer': x.recognizer, 'recognizer': x.recognizer,

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
export PYTHONPATH=../../ktiny/bin export PYTHONPATH=/home/albert/d/koo
./loader.py $1 $2 ./loader.py $1 $2

View File

@ -35,7 +35,8 @@
"category" : "Generic Modules/Attachments", "category" : "Generic Modules/Attachments",
"init_xml" : [], "init_xml" : [],
"demo_xml" : [], "demo_xml" : [],
"update_xml" : ["auto_attach_view.xml", "auto_attach_wizard.xml", "auto_attach_workflow.xml"], "update_xml" : ["auto_attach_view.xml", "auto_attach_wizard.xml",
"auto_attach_workflow.xml", "auto_attach_data.xml"],
"active": False, "active": False,
"installable": True "installable": True
} }

View File

@ -46,10 +46,10 @@ class nan_template(osv.osv):
_name = 'nan.template' _name = 'nan.template'
_columns = { _columns = {
'name' : fields.char('Name', 64, required=True), 'name' : fields.char('Name', 64, required=True),
'boxes' : fields.one2many('nan.template.box', 'template_id', 'Boxes'), 'boxes' : fields.one2many('nan.template.box', 'template', 'Boxes'),
'attach_function' : fields.char('Attachment Function', 256), 'attach_function' : fields.char('Attachment Function', 256),
'action_function' : fields.char('Action Function', 256), 'action_function' : fields.char('Action Function', 256),
'documents' : fields.one2many('nan.document', 'template_id', 'Documents') 'documents' : fields.one2many('nan.document', 'template', 'Documents')
} }
# Returns a Template from the fields of a template. You'll usually use # Returns a Template from the fields of a template. You'll usually use
@ -57,10 +57,11 @@ class nan_template(osv.osv):
def getTemplateFromData(self, cr, uid, data): def getTemplateFromData(self, cr, uid, data):
template = Template( data['name'] ) template = Template( data['name'] )
template.id = data['id'] template.id = data['id']
ids = self.pool.get('nan.template.box').search( cr, uid, [('template_id','=',data['id'])] ) ids = self.pool.get('nan.template.box').search( cr, uid, [('template','=',data['id'])] )
boxes = self.pool.get('nan.template.box').read(cr, uid, ids) boxes = self.pool.get('nan.template.box').read(cr, uid, ids)
for y in boxes: for y in boxes:
box = TemplateBox() box = TemplateBox()
box.id = y['id']
box.rect = QRectF( y['x'], y['y'], y['width'], y['height'] ) box.rect = QRectF( y['x'], y['y'], y['width'], y['height'] )
box.name = y['name'] box.name = y['name']
box.text = y['text'] box.text = y['text']
@ -97,7 +98,7 @@ nan_template()
class nan_template_box(osv.osv): class nan_template_box(osv.osv):
_name = 'nan.template.box' _name = 'nan.template.box'
_columns = { _columns = {
'template_id' : fields.many2one('nan.template', 'Template', required=True, ondelete='cascade'), 'template' : fields.many2one('nan.template', 'Template', required=True, ondelete='cascade'),
'x' : fields.float('X'), 'x' : fields.float('X'),
'y' : fields.float('Y'), 'y' : fields.float('Y'),
'width' : fields.float('Width'), 'width' : fields.float('Width'),
@ -125,12 +126,13 @@ class nan_document(osv.osv):
_columns = { _columns = {
'name' : fields.char('Name', 64), 'name' : fields.char('Name', 64),
'datas': fields.binary('Data'), 'datas': fields.binary('Data'),
'properties': fields.one2many('nan.document.property', 'document_id', 'Properties'), 'properties': fields.one2many('nan.document.property', 'document', 'Properties'),
'template_id': fields.many2one('nan.template', 'Template' ), 'template': fields.many2one('nan.template', 'Template' ),
'document': fields.reference('Document', selection=attachableDocuments, size=128), 'document': fields.reference('Document', selection=attachableDocuments, size=128),
'task' : fields.text('Task', readonly=True), 'task' : fields.text('Task', readonly=True),
'state': fields.selection( [('pending','Pending'),('scanned','Scanned'), 'state': fields.selection( [('pending','Pending'),('scanning','Scanning'),
('verified','Verified'),('processed','Processed')], ('scanned','Scanned'), ('verified','Verified'),('processing','Processing'),
('processed','Processed')],
'State', required=True, readonly=True ) 'State', required=True, readonly=True )
} }
_defaults = { _defaults = {
@ -142,20 +144,31 @@ class nan_document(osv.osv):
# the meanwhile" would be thrown, so by now check which of the records # the meanwhile" would be thrown, so by now check which of the records
# we'll want to scan later # we'll want to scan later
toScan = [] toScan = []
if 'template_id' in values: if 'template' in values:
for x in self.read( cr, uid, ids, ['state', 'template_id'], context ): for x in self.read( cr, uid, ids, ['state', 'template'], context ):
# We only scan the document if template_id has changed and the document # We only scan the document if template has changed and the document
# is in 'scanned' state. # is in 'scanned' state.
if x['state'] == 'scanned' and x['template_id'] != values['template_id']: if x['state'] == 'scanned' and x['template'] != values['template']:
toScan.append( {'id': x['id'], 'template_id': values['template_id'] } ) toScan.append( {'id': x['id'], 'template': values['template'] } )
ret = super(nan_document, self).write(cr, uid, ids, values, context) ret = super(nan_document, self).write(cr, uid, ids, values, context)
for x in toScan: for x in toScan:
self.scanDocumentWithTemplate( cr, uid, x['id'], x['template_id'] ) self.scanDocumentWithTemplate( cr, uid, x['id'], x['template'] )
return ret return ret
def scan_document(self, cr, uid, imageIds): def scan_document_background(self, cr, uid, imageIds):
self.pool.get('ir.cron').create(cr, uid, {
'name': 'Scan document',
'user_id': uid,
'model': 'nan.document',
'function': 'scan_document',
'args': repr([ imageIds, True ])
})
cr.commit()
# If notify=True sends a request/notification to uid
def scan_document(self, cr, uid, imageIds, notify=False):
# Load templates into 'templates' list # Load templates into 'templates' list
templates = self.pool.get('nan.template').getAllTemplates( cr, uid ) templates = self.pool.get('nan.template').getAllTemplates( cr, uid )
@ -165,15 +178,14 @@ class nan_document(osv.osv):
# Iterate over all images and try to find the most similar template # Iterate over all images and try to find the most similar template
for document in self.browse(cr, uid, imageIds): for document in self.browse(cr, uid, imageIds):
#TODO: Enable workflow test if document.state not in ('pending','scanning'):
#if document.state != 'pending': continue
# continue
fp, image = tempfile.mkstemp() fp, image = tempfile.mkstemp()
fp = os.fdopen( fp, 'wb+' ) fp = os.fdopen( fp, 'wb+' )
fp.write( base64.decodestring(document.datas) ) fp.write( base64.decodestring(document.datas) )
fp.close() fp.close()
recognizer.recognize( image ) recognizer.recognize( QImage( image ) )
result = recognizer.findMatchingTemplate( templates ) result = recognizer.findMatchingTemplateByOffset( templates )
template = result['template'] template = result['template']
doc = result['document'] doc = result['document']
if not template: if not template:
@ -185,14 +197,29 @@ class nan_document(osv.osv):
template_id = template.id template_id = template.id
else: else:
template_id = False template_id = False
self.write(cr, uid, [document.id], {'template_id': template_id, 'state': 'scanned'}) self.write(cr, uid, [document.id], {'template': template_id, 'state': 'scanned'})
if doc: if doc:
obj = self.pool.get('nan.document.property') obj = self.pool.get('nan.document.property')
for box in doc.boxes: for box in doc.boxes:
obj.create(cr, uid, { 'name' : box.templateBox.name, 'value' : box.text, 'document_id': document.id } ) obj.create(cr, uid, {
'name': box.templateBox.name,
'value': box.text,
'document': document.id,
'template_box': box.templateBox.id
})
if notify:
self.pool.get('res.request').create( cr, uid, {
'act_from': uid,
'act_to': uid,
'name': 'Finished scanning document',
'body': 'The auto_attach system has finished scanning the document you requested. A reference to the document can be found in field Document Ref 1.',
'ref_doc1': 'nan.document,%d' % document.id,
})
self.executeAttachs( cr, uid, imageIds ) self.executeAttachs( cr, uid, imageIds )
self.executeActions( cr, uid, imageIds, True ) self.executeActions( cr, uid, imageIds, True )
cr.commit() cr.commit()
def scanDocumentWithTemplate(self, cr, uid, documentId, templateId): def scanDocumentWithTemplate(self, cr, uid, documentId, templateId):
@ -200,7 +227,7 @@ class nan_document(osv.osv):
# Whether templateId is valid or not # Whether templateId is valid or not
# Remove previous properties # Remove previous properties
obj = self.pool.get('nan.document.property') obj = self.pool.get('nan.document.property')
ids = obj.search( cr, uid, [('document_id','=',documentId)] ) ids = obj.search( cr, uid, [('document','=',documentId)] )
obj.unlink( cr, uid, ids ) obj.unlink( cr, uid, ids )
if templateId: if templateId:
@ -224,7 +251,12 @@ class nan_document(osv.osv):
doc = recognizer.extractWithTemplate( image, template ) doc = recognizer.extractWithTemplate( image, template )
for box in doc.boxes: for box in doc.boxes:
obj.create(cr, uid, { 'name' : box.templateBox.name, 'value' : box.text, 'document_id': document['id'] } ) obj.create(cr, uid, {
'name': box.templateBox.name,
'value': box.text,
'document': document['id'],
'template_box': box.templateBox.id
})
self.executeAttachs( cr, uid, [documentId] ) self.executeAttachs( cr, uid, [documentId] )
self.executeActions( cr, uid, [documentId], True ) self.executeActions( cr, uid, [documentId], True )
cr.commit() cr.commit()
@ -259,13 +291,14 @@ class nan_document(osv.osv):
def executeActions( self, cr, uid, ids, explain ): def executeActions( self, cr, uid, ids, explain ):
for document in self.browse( cr, uid, ids ): for document in self.browse( cr, uid, ids ):
#TODO: Enable workflow test print "Executing action on document with state ", document.state
#if not explain and document.state != 'verified': if not explain and document.state not in ('verified','processing'):
#continue continue
print "Yes"
task = None task = None
if document.template_id: if document.template:
function = document.template_id.action_function function = document.template.action_function
if function: if function:
properties = dict( [(x.name, unicode(x.value)) for x in document.properties] ) properties = dict( [(x.name, unicode(x.value)) for x in document.properties] )
(name, parameters) = self._parseFunction(function, properties) (name, parameters) = self._parseFunction(function, properties)
@ -279,23 +312,22 @@ class nan_document(osv.osv):
ref = document.document.split(',') ref = document.document.split(',')
model = ref[0] model = ref[0]
id = ref[1] id = ref[1]
attach = { self.pool.get( 'ir.attachment' ).create( cr, uid, {
'res_id': id, 'res_id': id,
'res_model': model, 'res_model': model,
'name': document.name, 'name': document.name,
'datas': document.datas, 'datas': document.datas,
'datas_fname': document.name, 'datas_fname': document.name,
'description': 'Attached automatically' 'description': 'Document attached automatically'
} })
self.pool.get( 'ir.attachment' ).create( cr, uid, attach )
self.write(cr, uid, [document.id], {'state': 'processed'}) self.write(cr, uid, [document.id], {'state': 'processed'})
def executeAttachs( self, cr, uid, ids ): def executeAttachs( self, cr, uid, ids ):
for document in self.browse( cr, uid, ids ): for document in self.browse( cr, uid, ids ):
reference = None reference = None
if document.template_id: if document.template:
function = document.template_id.attach_function function = document.template.attach_function
if function: if function:
properties = dict( [(x.name, unicode( x.value, 'latin-1' )) for x in document.properties] ) properties = dict( [(x.name, unicode( x.value, 'latin-1' )) for x in document.properties] )
@ -357,6 +389,8 @@ class nan_document_property(osv.osv):
_columns = { _columns = {
'name' : fields.char('Text', 256), 'name' : fields.char('Text', 256),
'value' : fields.char('Value', 256), 'value' : fields.char('Value', 256),
'document_id' : fields.many2one('nan.document', 'Document', required=True, ondelete='cascade') 'document' : fields.many2one('nan.document', 'Document', required=True, ondelete='cascade'),
'template_box' : fields.many2one('nan.template.box', 'Template Box', required=True, ondelete='set null')
} }
nan_document_property() nan_document_property()

View File

@ -0,0 +1,10 @@
<?xml version="1.0" ?>
<terp>
<data>
<record model="res.request.link" id="wkf">
<field name="name">Document in Queue</field>
<field name="object">nan.document</field>
</record>
</data>
</terp>

View File

@ -10,7 +10,7 @@
<tree string="Documents in queue"> <tree string="Documents in queue">
<field name="name"/> <field name="name"/>
<field name="state"/> <field name="state"/>
<field name="template_id"/> <field name="template"/>
</tree> </tree>
</field> </field>
</record> </record>
@ -24,16 +24,20 @@
<page string="General"> <page string="General">
<field name="name"/> <field name="name"/>
<field name="state"/> <field name="state"/>
<field name="template_id"/> <field name="template"/>
<field name="document"/> <field name="document"/>
<separator string="Task"/> <separator string="Task"/>
<field name="task" nolabel="1" colspan="4"/> <field name="task" nolabel="1" colspan="4"/>
<separator string="Image"/> <separator string="Image"/>
<field name="datas" widget="image" img_width="300" img_height="300" nolabel="1" colspan="4"/> <field name="datas" widget="image" img_width="300" img_height="300" nolabel="1" colspan="4"/>
<group colspan="4"> <group colspan="4">
<button name="scan_document" states="pending" string="Scan Document"/> <button name="scan_document" states="pending" string="Scan document"/>
<button name="verify_document" states="scanned" string="Verify Document"/> <button name="pending_to_scanning" states="pending" string="Scan document in the background"/>
<button name="process_document" states="verified" string="Process Document"/> <button name="verify_document" states="scanned" string="Verify document"/>
<button name="scanned_to_pending" states="scanned" string="Back to pending"/>
<button name="process_document" states="verified" string="Process document"/>
<button name="verified_to_processing" states="verified" string="Process document in the background"/>
<button name="verified_to_scanned" states="verified" string="Unset verified"/>
</group> </group>
</page> </page>
<page string="Properties"> <page string="Properties">
@ -41,10 +45,12 @@
<form string="Properties"> <form string="Properties">
<field name="name"/> <field name="name"/>
<field name="value"/> <field name="value"/>
<field name="template_box"/>
</form> </form>
<tree string="Properties"> <tree string="Properties">
<field name="name"/> <field name="name"/>
<field name="value"/> <field name="value"/>
<field name="template_box"/>
</tree> </tree>
</field> </field>
</page> </page>

View File

@ -12,6 +12,13 @@
<field name="wkf_id" ref="wkf"/> <field name="wkf_id" ref="wkf"/>
<field name="flow_start">True</field> <field name="flow_start">True</field>
<field name="name">pending</field> <field name="name">pending</field>
<field name="action">write({'state':'pending'})</field>
</record>
<record model="workflow.activity" id="scanning">
<field name="wkf_id" ref="wkf"/>
<field name="name">scanning</field>
<field name="action">scan_document_background()&#10;write({'state':'scanning'})</field>
<field name="kind">function</field>
</record> </record>
<record model="workflow.activity" id="scanned"> <record model="workflow.activity" id="scanned">
<field name="wkf_id" ref="wkf"/> <field name="wkf_id" ref="wkf"/>
@ -25,11 +32,17 @@
<field name="action">write({'state':'verified'})</field> <field name="action">write({'state':'verified'})</field>
<field name="kind">function</field> <field name="kind">function</field>
</record> </record>
<record model="workflow.activity" id="processing">
<field name="wkf_id" ref="wkf"/>
<field name="name">processing</field>
<field name="action">process_document_background()&#10;write({'state':'processing'})</field>
<field name="kind">function</field>
</record>
<record model="workflow.activity" id="processed"> <record model="workflow.activity" id="processed">
<field name="wkf_id" ref="wkf"/> <field name="wkf_id" ref="wkf"/>
<field name="flow_stop">True</field> <field name="flow_stop">True</field>
<field name="name">processed</field> <field name="name">processed</field>
<field name="action">write({'state':'processed'})</field> <field name="action">process_document()&#10;write({'state':'processed'})</field>
<field name="kind">function</field> <field name="kind">function</field>
</record> </record>
@ -51,5 +64,37 @@
<field name="act_to" ref="processed"/> <field name="act_to" ref="processed"/>
<field name="signal">process_document</field> <field name="signal">process_document</field>
</record> </record>
<record model="workflow.transition" id="t4">
<field name="act_from" ref="pending"/>
<field name="act_to" ref="scanning"/>
<field name="signal">pending_to_scanning</field>
</record>
<record model="workflow.transition" id="t5">
<field name="act_from" ref="scanning"/>
<field name="act_to" ref="scanned"/>
<field name="signal">scanning_to_scanned</field>
</record>
<record model="workflow.transition" id="t6">
<field name="act_from" ref="verified"/>
<field name="act_to" ref="processing"/>
<field name="signal">verified_to_processing</field>
</record>
<record model="workflow.transition" id="t7">
<field name="act_from" ref="processing"/>
<field name="act_to" ref="processed"/>
<field name="signal">processing_to_processed</field>
</record>
<record model="workflow.transition" id="t8">
<field name="act_from" ref="scanned"/>
<field name="act_to" ref="pending"/>
<field name="signal">scanned_to_pending</field>
</record>
<record model="workflow.transition" id="t9">
<field name="act_from" ref="verified"/>
<field name="act_to" ref="scanned"/>
<field name="signal">verified_to_scanned</field>
</record>
</data> </data>
</terp> </terp>