Kirti Savalia(OpenERP) has proposed merging lp:~openerp-dev/openobject-addons/trunk-temporal-db-branch-ksa into lp:~openerp-dev/openobject-addons/trunk-temporal-db.
Requested reviews: Rucha (Open ERP) (rpa-openerp) For more details, see: https://code.launchpad.net/~openerp-dev/openobject-addons/trunk-temporal-db-branch-ksa/+merge/62871 Base Temporal Moudule with all method ( create, write, search, read, unlink and timeline function) and YML. -- https://code.launchpad.net/~openerp-dev/openobject-addons/trunk-temporal-db-branch-ksa/+merge/62871 Your team OpenERP R&D Team is subscribed to branch lp:~openerp-dev/openobject-addons/trunk-temporal-db.
=== added directory 'base_temporal' === added file 'base_temporal/__init__.py' --- base_temporal/__init__.py 1970-01-01 00:00:00 +0000 +++ base_temporal/__init__.py 2011-05-30 12:12:31 +0000 @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# 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/>. +# +############################################################################## +import base_temporal + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: === added file 'base_temporal/__openerp__.py' --- base_temporal/__openerp__.py 1970-01-01 00:00:00 +0000 +++ base_temporal/__openerp__.py 2011-05-30 12:12:31 +0000 @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# 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/>. +# +############################################################################## + +{ + 'name': 'Base Temporal', + 'version': '1.0', + 'description': """ +This module provides to be able to find the original record of an historization record. + """, + 'author': 'OpenERP SA', + 'website': 'http://openerp.com', + 'depends': ['base'], + 'init_xml': [], + 'update_xml': ['base_temporal_view.xml','security/ir.model.access.csv'], + 'test': [ + 'test/base_temporal_test.yml' + ], + 'installable': True, + 'active': False, + 'certificate' : '00475023941677743389', +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: === added file 'base_temporal/base_temporal.py' --- base_temporal/base_temporal.py 1970-01-01 00:00:00 +0000 +++ base_temporal/base_temporal.py 2011-05-30 12:12:31 +0000 @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# 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/>. +# +############################################################################## +from osv import fields, osv +import time + +class base_temporal_test(osv.osv): + _name = "base.temporal.test" +base_temporal_test() + +class base_temporal_test_line(osv.osv): + _name = "base.temporal.test.line" + _description = "Base Temporal Line" + _columns = { + 'name': fields.char('Temporal Name', size=64), + 'test_id': fields.many2one('base.temporal.test','Test ID'), + } +base_temporal_test_line() + +class base_temporal_test(osv.osv): + + def _set_date(self, cr, uid, ids, field_name, arg, context=None): + res = {} + for record in self.browse(cr, uid, ids, context=context): + next_id = self.get_next_id_in_timeline(cr, uid, [record.id], context={'temporal_mode': False}) + res[record.id] = next_id and self.read(cr, uid, next_id, ['temporal_date_from'])[0]['temporal_date_from'] or False + return res + + def get_ids_of_same_group(self, cr, uid, ids, context=None): + res = {} + for record in self.browse(cr, uid, ids, context=context): + search_ids = [record.id] + if record.temporal_parent_id: + search_ids.append(record.temporal_parent_id) + res[record.id] = self.search(cr, uid, ['|',('temporal_parent_id', 'in', search_ids), ('id', 'in', search_ids)], context=context) + return res + + def get_next_id_in_timeline(self, cr, uid, ids, context=None): + res = self.get_ids_of_same_group(cr, uid, ids, context) + for record in self.browse(cr, uid, ids, context=context): + search_id = self.search(cr, uid, [('id', 'in', res[record.id]), ('temporal_date_from', '>', record.temporal_date_from)], order='temporal_date_from asc', limit=1, context={'temporal_mode': False}) + return search_id + + def _get_previous_id_in_timeline(self, cr, uid, ids, context=None): + res = self.get_ids_of_same_group(cr, uid, ids, context) + for record in self.browse(cr, uid, ids, context=context): + search_id = self.search(cr, uid, [('id', 'in', res[record.id]), ('temporal_date_from', '<', record.temporal_date_from)], order='temporal_date_from desc', limit=1, context={'temporal_mode': False}) + return search_id + ids + + _name = "base.temporal.test" + _inherit = 'base.temporal.test' + _description = "Base Temporal" + _columns = { + 'name': fields.char('Temporal Name', size=64), + 'line_ids': fields.one2many('base.temporal.test.line', 'test_id', 'Line IdS'), + 'temporal_date_from': fields.datetime('Temporal From Date', select=True), + 'temporal_date_to': fields.function(_set_date, method=True, select=True, string='Temporal To Date', type='datetime', + store = { + 'base.temporal.test': (_get_previous_id_in_timeline, ['temporal_date_from'], 10), + }), + 'temporal_parent_id': fields.integer('Temporal Parent ID', select=True) + } + + def create(self, cr, uid, vals, context=None): + """on creation, fill the temporal_date_from and the temporal_parent_id""" + if not vals.get('temporal_date_from'): + vals.update({'temporal_date_from': time.strftime('%Y-%m-%d %H:%M:%S')}) + res_id = super(base_temporal_test, self).create(cr, uid, vals, context=context) + if not'temporal_parent_id' in vals: + self.write(cr, uid, res_id, {'temporal_parent_id': res_id}, context={'temporal_mode': False}) + return res_id + + def write(self, cr, uid, ids, vals, context=None): + if context is None: + context = {} + if isinstance(ids, (int, long)): + ids = [ids] + if vals.get('temporal_date_from'): + return super(base_temporal_test, self).write(cr, uid, ids, vals, context=context) # if temporal_date_from pass in vals just call super without any copy + timenow = time.strftime('%Y-%m-%d %H:%M:%S') + + if context.get('temporal_mode', True): + for record in self.browse(cr, uid, ids, context=context): + # avoid creating history records when writing on a reocrd that is already an history record + if not((not record.temporal_date_from or record.temporal_date_from <= timenow) and (not record.temporal_date_to or timenow < record.temporal_date_to)): + continue + + vals.update({'temporal_date_from': timenow}) # get current time when make new copy of the current record + defaults = {'temporal_date_from': record.temporal_date_from, 'temporal_parent_id': record.id} + self.copy(cr, uid, record.id, defaults, context) + + return super(base_temporal_test, self).write(cr, uid, ids, vals, context=context) + + def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False): + if context is None: + context = {} + #if the key 'temporal_mode' is in the context, just make a normal read by calling super() + if context.get('temporal_mode', True): + return super(base_temporal_test,self).search(cr, user, args, offset, limit, order, context, count) + #if present in the args, replace the clause with field 'id' + new_args = [] + for item in args: + if isinstance(item, tuple): + a, b, c = item + if a == 'id': + new_args += ['|', ('temporal_parent_id', b, c)] + new_args.append(item) + + #add the time constraint + timenow = time.strftime('%Y-%m-%d %H:%M:%S') + temporal_date = context.get("temporal_date", timenow) + new_args += ['|',('temporal_date_from','=',False),('temporal_date_from', '<=', temporal_date), '|', ('temporal_date_to', '=', False), ('temporal_date_to', '>', temporal_date)] + return super(base_temporal_test, self).search(cr, user, new_args, offset, limit, order, context, count) + + def read(self, cr, uid, ids_orig, fields=None, context=None, load='_classic_read'): + ids = isinstance(ids_orig, (int, long)) and [ids_orig] or ids_orig + if context is None: + context = {} + #if the context doesn't contain the key 'temporal_date', just call super() and make a normal read + if not context.get('temporal_date'): + result = super(base_temporal_test, self).read(cr, uid, ids, fields=fields, context=context, load=load) + else: + #get the temporal_parent_id of given ids + temporal_parent_ids = super(base_temporal_test, self).read(cr, uid, ids, fields=['temporal_parent_id'], context=context, load=load) + + #search for real ids to read + ids2 = [] + i = 0 + for id in ids: + search_criteria = [('temporal_parent_id','=', temporal_parent_ids[i]['temporal_parent_id'])] + ids2 += self.search(cr, uid, search_criteria, context=context) + result = super(base_temporal_test, self).read(cr, uid, ids2, fields=fields, context=context, load=load) + if isinstance(ids_orig, (int, long)): + return result[0] + return result + + def unlink(self, cr, uid, ids, context=None): + if context.get('temporal_mode',True): + #get the temporal_parent_id of given ids + temporal_parent_ids = super(base_temporal_test, self).read(cr, uid, ids, fields=['temporal_parent_id'], context=context) + #search for real ids to delete + ids2 = list(ids) + i = 0 + for id in ids2: + search_criteria = [('temporal_parent_id','=', temporal_parent_ids[i]['temporal_parent_id'])] + ids += self.search(cr, uid, search_criteria, context=context) + return super(base_temporal_test, self).unlink(cr, uid, ids, context) + +base_temporal_test() + === added file 'base_temporal/base_temporal_view.xml' --- base_temporal/base_temporal_view.xml 1970-01-01 00:00:00 +0000 +++ base_temporal/base_temporal_view.xml 2011-05-30 12:12:31 +0000 @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<openerp> + <data> + + <record model="ir.ui.view" id="view_base_temporal_test_form"> + <field name="name">base.temporal.test.form</field> + <field name="model">base.temporal.test</field> + <field name="type">form</field> + <field name="arch" type="xml"> + <form string="Base Temporal Test"> + <field name="name"/> + <field name="temporal_date_from"/> + <field name="temporal_date_to"/> + <field name="temporal_parent_id"/> + <field colspan="4" name="line_ids" nolabel="1" mode="tree,form"> + <tree string="Base Temporal Test Lines"> + <field name="name"/> + <field name="test_id"/> + </tree> + <form string="Base Temporal Test Lines"> + <field name="name"/> + <field name="test_id"/> + </form> + </field> + </form> + </field> + </record> + + <record model="ir.ui.view" id="view_base_temporal_test_tree"> + <field name="name">base.temporal.test.tree</field> + <field name="model">base.temporal.test</field> + <field name="type">tree</field> + <field name="arch" type="xml"> + <tree string="Base Temporal Test"> + <field name="name"/> + <field name="temporal_date_from"/> + <field name="temporal_date_to"/> + <field name="temporal_parent_id"/> + </tree> + </field> + </record> + + <record model="ir.actions.act_window" id="action_base_temporal_test_tree"> + <field name="name">Base Temporal Test</field> + <field name="res_model">base.temporal.test</field> + <field name="type">ir.actions.act_window</field> + <field name="view_type">form</field> + <field name="view_mode">tree,form</field> + <field name="context">{'temporal_mode': True}</field> + <field name="search_view_id" ref="view_base_temporal_test_tree"/> + </record> + + <menuitem action="action_base_temporal_test_tree" + id="menu_partner_temporal_tree" + parent="base.menu_base_partner" sequence="50"/> + </data> +</openerp> + === added directory 'base_temporal/security' === added file 'base_temporal/security/ir.model.access.csv' --- base_temporal/security/ir.model.access.csv 1970-01-01 00:00:00 +0000 +++ base_temporal/security/ir.model.access.csv 2011-05-30 12:12:31 +0000 @@ -0,0 +1,3 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"access_base_temporal_test","base.temporal.test","model_base_temporal_test","base.group_user",1,1,0,0 +"access_base_temporal_test_line","base.temporal.test.line","model_base_temporal_test_line","base.group_user",1,1,0,0 === added directory 'base_temporal/test' === added file 'base_temporal/test/base_temporal_test.yml' --- base_temporal/test/base_temporal_test.yml 1970-01-01 00:00:00 +0000 +++ base_temporal/test/base_temporal_test.yml 2011-05-30 12:12:31 +0000 @@ -0,0 +1,181 @@ +- + In order to test base_temporal module in OpenERP, I create a base_temporal.test called "My Contract". +- + !record {model: base.temporal.test, id: base_temporal_my_contract}: + name: My Contract + temporal_date_from: !eval time.strftime('%Y-%m-%d %H:%M:%S') +- + I create a base_temporal.test called "My Contract" created two days ago. +- + !python {model: base.temporal.test}: | + import datetime + from dateutil.relativedelta import * + date_from = datetime.datetime.today() - relativedelta(days=2) + from_date = date_from.isoformat().split('.')[0].replace('T',' ') + self.write(cr, uid, [(ref("base_temporal_my_contract"))], {'temporal_date_from': from_date}) +- + I update the contract to "My Yesterday's Contract" at the date of yesterday. +- + !python {model: base.temporal.test}: | + import datetime + from dateutil.relativedelta import * + date_from = datetime.datetime.today() - relativedelta(days=1) + from_date = date_from.isoformat().split('.')[0].replace('T',' ') + ctx={} + vals={} + ctx.update({'model':'base.temporal.test', 'active_ids':[ref('base_temporal_my_contract')], 'temporal_mode': True}) + if context.get('temporal_mode', True) and not vals.get('temporal_date_from'): + record = self.browse(cr, uid, ref("base_temporal_my_contract"), context=context) + vals.update({'temporal_date_from': from_date, 'name': 'My Yesterday Contract'}) + defaults = {'temporal_date_from': record.temporal_date_from, 'temporal_parent_id': record.id} + self.copy(cr, uid, record.id, defaults, context) + self.write(cr, uid, [(ref("base_temporal_my_contract"))],vals, context=ctx) +- + I update the contract to "Future Contract" at the date of tomorrow. +- + !python {model: base.temporal.test}: | + import datetime + from dateutil.relativedelta import * + date_from = datetime.datetime.today() + relativedelta(days=1) + from_date = date_from.isoformat().split('.')[0].replace('T',' ') + ctx={} + vals={} + ctx.update({'model': 'base.temporal.test','active_ids':[ref('base_temporal_my_contract')], 'temporal_mode': True}) + if context.get('temporal_mode', True) and not vals.get('temporal_date_from'): + record = self.browse(cr, uid, ref("base_temporal_my_contract"), context=context) + vals.update({'temporal_date_from': from_date, 'name':'Future Contract'}) + defaults = {'temporal_date_from': record.temporal_date_from, 'temporal_parent_id': record.id} + self.copy(cr, uid, record.id, defaults, context) + self.write(cr, uid, [(ref("base_temporal_my_contract"))],vals, context=ctx) +- + I update the contract to "Current Contract" at the date of today. +- + !python {model: base.temporal.test}: | + import datetime + from dateutil.relativedelta import * + date_from = datetime.datetime.today() + from_date = date_from.isoformat().split('.')[0].replace('T',' ') + ctx={} + vals={} + ctx.update({'model':'base.temporal.test','active_ids':[ref('base_temporal_my_contract')], 'temporal_mode': True}) + if context.get('temporal_mode', True) and not vals.get('temporal_date_from'): + record = self.browse(cr, uid, ref("base_temporal_my_contract"), context=context) + vals.update({'temporal_date_from': from_date, 'name':'Current Contract'}) + defaults = {'temporal_date_from': record.temporal_date_from, 'temporal_parent_id': record.id} + self.copy(cr, uid, record.id, defaults, context) + self.write(cr, uid, [(ref("base_temporal_my_contract"))], vals, context=ctx) +- + I check that I have 4 versions of the "My Contract". +- + !python {model: base.temporal.test}: | + from tools.translate import _ + record = self.browse(cr, uid, ref("base_temporal_my_contract")) + version_ids = self.search(cr, uid, ['|',('temporal_parent_id','=',record.id),('id','=',record.id)]) + assert version_ids, _('Not Found 4 versions of the My Contract') +- + I create a new contract called "Contract 2" at the date of today. +- + !record {model: base.temporal.test, id: base_temporal_contract2}: + name: Contract 2 + temporal_date_from: !eval time.strftime('%Y-%m-%d %H:%M:%S') +- + I check that I have 1 version the new contract called "Contract 2". +- + !python {model: base.temporal.test}: | + from tools.translate import _ + record = self.browse(cr, uid, ref("base_temporal_contract2")) + version_ids = self.search(cr, uid, ['|',('temporal_parent_id','=',record.id),('id','=',record.id)]) + assert version_ids, _('Not Found 1 versions of the Contract2') +- + I check that when I read the name of the first contract at the date of yesterday, I see "My Yesterday's Contract" +- + !python {model: base.temporal.test}: | + import datetime + from datetime import datetime, timedelta + from tools.translate import _ + record = self.browse(cr, uid, ref("base_temporal_my_contract")) + temp_date = (datetime.strptime(record.temporal_date_from,"%Y-%m-%d %H:%M:%S")-timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S') + yesterday_ids = self.search(cr, uid, [('temporal_date_from','=',temp_date),('temporal_parent_id','=',record.id)]) + assert yesterday_ids, _('Not Found My Yesterday Contract') +- + I check that when I read the name of the first contract at the date of in one month, I see "Future Contract". +- + !python {model: base.temporal.test}: | + import datetime + from datetime import datetime, timedelta + from tools.translate import _ + record = self.browse(cr, uid, ref("base_temporal_my_contract")) + temp_date = (datetime.strptime(record.temporal_date_from,"%Y-%m-%d %H:%M:%S")+timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S') + future_ids = self.search(cr, uid, [('temporal_date_from','=',temp_date),('temporal_parent_id','=',record.id)]) + assert future_ids, _('Not Found Future Contract') +- + I change the "Future Contract" and set the date_from to one month ago in the past. +- + !python {model: base.temporal.test}: | + import datetime + from dateutil.relativedelta import * + from datetime import datetime, timedelta + date_from = datetime.today() - relativedelta(months=1) + from_date = date_from.isoformat().split('.')[0].replace('T',' ') + record = self.browse(cr, uid, ref("base_temporal_my_contract")) + temp_date = (datetime.strptime(record.temporal_date_from,"%Y-%m-%d %H:%M:%S")+timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S') + future_ids = self.search(cr, uid, [('temporal_date_from','=',temp_date),('temporal_parent_id','=',record.id)]) + self.write(cr, uid, future_ids, {'temporal_date_from': from_date}) +- + I check that when I read the name of the first contract at the date of in one month, I see "Current Contract". +- + !python {model: base.temporal.test}: | + import datetime + from datetime import datetime, timedelta + from tools.translate import _ + record = self.browse(cr, uid, ref("base_temporal_my_contract")) + temp_date = (datetime.strptime(record.temporal_date_from,"%Y-%m-%d %H:%M:%S")).strftime('%Y-%m-%d %H:%M:%S') + current_ids = self.search(cr, uid, [('temporal_date_from','=',temp_date),('id','=',record.id)]) + assert current_ids, _('Not Found Current Contract') +- + I create a new contract called "Contract 3" with two lines on this contract "Line 3.1" at the date of today. +- + !record {model: base.temporal.test, id: base_temporal_contract3}: + name: Contract 3 + temporal_date_from: !eval time.strftime('%Y-%m-%d %H:%M:%S') + line_ids: + - name: Line 3.1 +- + I create a new contract called "Contract 3" with two lines on this contract "Line 3.2" at the date of today. +- + !record {model: base.temporal.test, id: base_temporal_contract3}: + name: Contract 3 + temporal_date_from: !eval time.strftime('%Y-%m-%d %H:%M:%S') + line_ids: + - name: Line 3.2 +- + I update "Contract 3" at the date of tomorrow and change name to "Contract 3 Bis" and first line name to "Line 3.2 Bis". +- + !python {model: base.temporal.test}: | + import datetime + from datetime import datetime, timedelta + record = self.browse(cr, uid, ref("base_temporal_contract3")) + temp_date = (datetime.strptime(record.temporal_date_from,"%Y-%m-%d %H:%M:%S")+timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S') + contract_ids = self.search(cr, uid, [('name','=','Contract 3')]) + self.write(cr, uid, contract_ids, {'name': 'Contract 3 Bis'}) + self.write(cr, uid, contract_ids, {'temporal_date_from': temp_date}) +- + I read "contract 3" at the date of today and check name "Contract 3" and lines "Line 3.1, Line 3.2" . +- + !python {model: base.temporal.test}: | + from tools.translate import _ + from datetime import datetime, timedelta + date_from = datetime.today() + from_date = date_from.isoformat().split('.')[0].replace('T',' ') + contract_ids = self.search(cr, uid, [('name','=','Contract 3'),('temporal_date_from','=',from_date)]) + assert contract_ids, _('Not Found Contract 3') +- + I read "Contract 3" at the date of tomorrow and check name "Contract 3 Bis" and lines "Line 3.1, Line 3.2 Bis" . +- + !python {model: base.temporal.test}: | + from tools.translate import _ + from datetime import datetime, timedelta + date_from = datetime.today()+timedelta(days=1) + from_date = date_from.isoformat().split('.')[0].replace('T',' ') + contract_ids = self.search(cr, uid, [('name','=','Contract 3 Bis'),('temporal_date_from','=',from_date)]) + assert contract_ids, _('Not Found Contract 3 Bis')
_______________________________________________ Mailing list: https://launchpad.net/~openerp-dev-web Post to : openerp-dev-web@lists.launchpad.net Unsubscribe : https://launchpad.net/~openerp-dev-web More help : https://help.launchpad.net/ListHelp