Yannick Vaucher
9 years ago
committed by
Artem Kostyuk
8 changed files with 214 additions and 316 deletions
-
71record_archiver/README.rst
-
88record_archiver/__openerp__.py
-
90record_archiver/models/ir_model.py
-
123record_archiver/models/record_lifespan.py
-
6record_archiver/tests/__init__.py
-
70record_archiver/tests/test_active_search.py
-
74record_archiver/tests/test_archive.py
-
8record_archiver/views/record_lifespan_view.xml
@ -0,0 +1,71 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
|
|||
================ |
|||
Records Archiver |
|||
================ |
|||
|
|||
Create a cron job that deactivates old records in order to optimize |
|||
performance. |
|||
|
|||
Records are deactivated based on their last activity (write_date). |
|||
|
|||
Configuration |
|||
============= |
|||
|
|||
You can configure lifespan of each type of record in |
|||
`Settings -> Configuration -> Records Archiver` |
|||
|
|||
A different lifespan can be configured for each model. |
|||
|
|||
Usage |
|||
===== |
|||
|
|||
Once the lifespans are configured, the cron will automatically |
|||
deactivate the old records. |
|||
|
|||
Known issues / Roadmap |
|||
====================== |
|||
|
|||
The default behavior is to archive all records having a ``write_date`` < |
|||
lifespan and with a state being ``done`` or ``cancel``. If these rules |
|||
need to be modified for a model (e.g. change the states to archive), the |
|||
hook ``RecordLifespan._archive_domain`` can be extended. |
|||
|
|||
Bug Tracker |
|||
=========== |
|||
|
|||
Bugs are tracked on `GitHub Issues |
|||
<https://github.com/OCA/server-tools/issues>`_. In case of trouble, please |
|||
check there if your issue has already been reported. If you spotted it first, |
|||
help us smashing it by providing a detailed and welcomed `feedback |
|||
<https://github.com/OCA/ |
|||
server-tools/issues/new?body=module:%20 |
|||
record_archiver%0Aversion:%20 |
|||
9.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_. |
|||
|
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Yannick Vaucher <yannick.vaucher@camptocamp.com> |
|||
* Guewen Baconnier <guewen.baconnier@camptocamp.com> |
|||
|
|||
Maintainer |
|||
---------- |
|||
|
|||
.. image:: https://odoo-community.org/logo.png |
|||
:alt: Odoo Community Association |
|||
:target: https://odoo-community.org |
|||
|
|||
This module is maintained by the OCA. |
|||
|
|||
OCA, or the Odoo Community Association, is a nonprofit organization whose |
|||
mission is to support the collaborative development of Odoo features and |
|||
promote its widespread use. |
|||
|
|||
To contribute to this module, please visit http://odoo-community.org. |
@ -1,73 +1,39 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# |
|||
# |
|||
# Authors: Guewen Baconnier |
|||
# Copyright 2015 Camptocamp SA |
|||
# |
|||
# 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/>. |
|||
# |
|||
# |
|||
# © 2015 Guewen Baconnier (Camptocamp SA) |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
from openerp import api, fields, models |
|||
|
|||
from openerp.osv import orm, fields |
|||
|
|||
|
|||
class IrModel(orm.Model): |
|||
class IrModel(models.Model): |
|||
_inherit = 'ir.model' |
|||
|
|||
def _compute_has_an_active_field(self, cr, uid, ids, name, |
|||
args, context=None): |
|||
res = {} |
|||
for model_id in ids: |
|||
active_field_ids = self.pool['ir.model.fields'].search( |
|||
cr, uid, |
|||
[('model_id', '=', model_id), |
|||
@api.multi |
|||
def _compute_has_an_active_field(self): |
|||
for model in self: |
|||
active_fields = self.env['ir.model.fields'].search( |
|||
[('model_id', '=', model.id), |
|||
('name', '=', 'active'), |
|||
], |
|||
limit=1, |
|||
context=context) |
|||
res[model_id] = bool(active_field_ids) |
|||
return res |
|||
limit=1) |
|||
model.has_an_active_field = bool(active_fields) |
|||
|
|||
def _search_has_an_active_field(self, cr, uid, obj, name, args, |
|||
context=None): |
|||
if not len(args): |
|||
return [] |
|||
fields_model = self.pool['ir.model.fields'] |
|||
@api.model |
|||
def _search_has_an_active_field(self, operator, value): |
|||
if operator not in ['=', '!=']: |
|||
raise AssertionError('operator %s not allowed' % operator) |
|||
fields_model = self.env['ir.model.fields'] |
|||
domain = [] |
|||
for field, operator, value in args: |
|||
assert field == name |
|||
active_field_ids = fields_model.search( |
|||
cr, uid, [('name', '=', 'active')], context=context) |
|||
active_fields = fields_model.read(cr, uid, active_field_ids, |
|||
fields=['model_id'], |
|||
load='_classic_write', |
|||
context=context) |
|||
model_ids = [field['model_id'] for field in active_fields] |
|||
if operator == '=' or not value: |
|||
domain.append(('id', 'in', model_ids)) |
|||
elif operator == '!=' or value: |
|||
domain.append(('id', 'not in', model_ids)) |
|||
else: |
|||
raise AssertionError('operator %s not allowed' % operator) |
|||
active_fields = fields_model.search( |
|||
[('name', '=', 'active')]) |
|||
models = active_fields.mapped('model_id') |
|||
if operator == '=' and value or operator == '!=' and not value: |
|||
domain.append(('id', 'in', models.ids)) |
|||
else: |
|||
domain.append(('id', 'not in', models.ids)) |
|||
return domain |
|||
|
|||
_columns = { |
|||
'has_an_active_field': fields.function( |
|||
_compute_has_an_active_field, |
|||
fnct_search=_search_has_an_active_field, |
|||
string='Has an active field', |
|||
readonly=True, |
|||
type='boolean', |
|||
), |
|||
} |
|||
has_an_active_field = fields.Boolean( |
|||
compute=_compute_has_an_active_field, |
|||
search=_search_has_an_active_field, |
|||
string='Has an active field', |
|||
) |
@ -1,56 +1,36 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# |
|||
# |
|||
# Authors: Guewen Baconnier |
|||
# Copyright 2015 Camptocamp SA |
|||
# |
|||
# 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/>. |
|||
# |
|||
# |
|||
|
|||
|
|||
# © 2015 Guewen Baconnier (Camptocamp SA) |
|||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). |
|||
import openerp.tests.common as common |
|||
|
|||
|
|||
class TestActiveSearch(common.TransactionCase): |
|||
|
|||
def test_model_with_active_field(self): |
|||
cr, uid = self.cr, self.uid |
|||
IrModel = self.registry('ir.model') |
|||
partner_model_id = IrModel.search(cr, uid, |
|||
[('model', '=', 'res.partner')], |
|||
limit=1)[0] |
|||
partner_model = IrModel.browse(cr, uid, partner_model_id) |
|||
IrModel = self.env['ir.model'] |
|||
partner_model = IrModel.search([('model', '=', 'res.partner')], |
|||
limit=1) |
|||
self.assertTrue(partner_model.has_an_active_field) |
|||
self.assertIn(partner_model_id, |
|||
IrModel.search(cr, uid, |
|||
[('has_an_active_field', '=', True)])) |
|||
self.assertIn(partner_model_id, |
|||
IrModel.search(cr, uid, |
|||
[('has_an_active_field', '!=', False)])) |
|||
self.assertIn(partner_model, |
|||
IrModel.search([('has_an_active_field', '=', True)])) |
|||
self.assertIn(partner_model, |
|||
IrModel.search([('has_an_active_field', '!=', False)])) |
|||
self.assertNotIn(partner_model, |
|||
IrModel.search([('has_an_active_field', '!=', True)])) |
|||
self.assertNotIn(partner_model, |
|||
IrModel.search([('has_an_active_field', '=', False)])) |
|||
|
|||
def test_model_without_active_field(self): |
|||
cr, uid = self.cr, self.uid |
|||
IrModel = self.registry('ir.model') |
|||
country_model_id = IrModel.search(cr, uid, |
|||
[('model', '=', 'res.country')], |
|||
limit=1) |
|||
country_model = IrModel.browse(cr, uid, country_model_id[0]) |
|||
IrModel = self.env['ir.model'] |
|||
country_model = IrModel.search([('model', '=', 'res.country')], |
|||
limit=1) |
|||
self.assertFalse(country_model.has_an_active_field) |
|||
self.assertNotIn(country_model_id, |
|||
IrModel.search(cr, uid, |
|||
[('has_an_active_field', '=', False)])) |
|||
self.assertNotIn(country_model_id, |
|||
IrModel.search(cr, uid, |
|||
[('has_an_active_field', '!=', True)])) |
|||
self.assertIn(country_model, |
|||
IrModel.search([('has_an_active_field', '!=', True)])) |
|||
self.assertIn(country_model, |
|||
IrModel.search([('has_an_active_field', '=', False)])) |
|||
self.assertNotIn(country_model, |
|||
IrModel.search([('has_an_active_field', '=', True)])) |
|||
self.assertNotIn(country_model, |
|||
IrModel.search([('has_an_active_field', '!=', False)]) |
|||
) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue