Browse Source

..

pull/6/head
Mathias Markl 6 years ago
parent
commit
bf6fbc6fb1
  1. 2
      muk_autovacuum/__manifest__.py
  2. 5
      muk_autovacuum/doc/changelog.rst
  3. 84
      muk_autovacuum/models/ir_autovacuum.py
  4. 94
      muk_autovacuum/models/rules.py
  5. 26
      muk_autovacuum/views/rules.xml

2
muk_autovacuum/__manifest__.py

@ -20,7 +20,7 @@
{ {
"name": "MuK Autovacuum", "name": "MuK Autovacuum",
"summary": """Configure automatic garbage collection""", "summary": """Configure automatic garbage collection""",
"version": "11.0.2.0.0",
"version": "11.0.2.1.0",
"category": "Extra Tools", "category": "Extra Tools",
"license": "AGPL-3", "license": "AGPL-3",
"website": "https://www.mukit.at", "website": "https://www.mukit.at",

5
muk_autovacuum/doc/changelog.rst

@ -1,6 +1,11 @@
`2.0.0` `2.0.0`
------- -------
- Added Python Expressions
`2.0.0`
-------
- Migrated to Python 3 - Migrated to Python 3
`1.1.0` `1.1.0`

84
muk_autovacuum/models/ir_autovacuum.py

@ -42,48 +42,54 @@ class AutoVacuum(models.AbstractModel):
_inherit = 'ir.autovacuum' _inherit = 'ir.autovacuum'
@api.model
def _eval_context(self):
return {
'datetime': datetime,
'dateutil': dateutil,
'time': time,
'uid': self.env.uid,
'user': self.env.user}
@api.model @api.model
def power_on(self, *args, **kwargs): def power_on(self, *args, **kwargs):
res = super(AutoVacuum, self).power_on(*args, **kwargs) res = super(AutoVacuum, self).power_on(*args, **kwargs)
rules = self.env['muk_autovacuum.rules'].sudo().search([], order='sequence asc') rules = self.env['muk_autovacuum.rules'].sudo().search([], order='sequence asc')
for rule in rules: for rule in rules:
model = self.env[rule.model.model].sudo()
records = self.env[rule.model.model]
if rule.state == 'time':
computed_time = datetime.datetime.utcnow() - _types[rule.time_type](rule.time)
domain = [(rule.time_field.name, '<', computed_time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
if rule.protect_starred and "starred" in rule.model.field_id.mapped("name"):
domain.append(('starred', '=', False))
if rule.only_inactive and "active" in rule.model.field_id.mapped("name"):
domain.append(('active', '=', False))
_logger.info(_("GC domain: %s"), domain)
records = model.with_context({'active_test': False}).search(domain)
elif rule.state == 'size':
size = rule.size if rule.size_type == 'fixed' else rule.size_parameter_value
count = model.with_context({'active_test': False}).search([], count=True)
if count > size:
limit = count - size
_logger.info(_("GC domain: [] order: %s limit: %s"), rule.size_order, limit)
records = model.with_context({'active_test': False}).search([], order=rule.size_order, limit=limit)
elif rule.state == 'domain':
_logger.info(_("GC domain: %s"), rule.domain)
context = {
'datetime': datetime,
'dateutil': dateutil,
'time': time,
'uid': self.env.uid,
'user': self.env.user}
domain = safe_eval(rule.domain, context)
records = model.with_context({'active_test': False}).search(domain)
if rule.only_attachments:
attachments = self.env['ir.attachment'].sudo().search([
('res_model', '=', rule.model.model),
('res_id', 'in', records.mapped('id'))])
count = len(attachments)
attachments.unlink()
_logger.info(_("GC'd %s attachments from %s entries"), count, rule.model.model)
else:
count = len(records)
records.unlink()
_logger.info(_("GC'd %s %s records"), count, rule.model.model)
if rule.state in ['time', 'size', 'domain']:
model = self.env[rule.model.model].sudo()
records = self.env[rule.model.model]
if rule.state == 'time':
computed_time = datetime.datetime.utcnow() - _types[rule.time_type](rule.time)
domain = [(rule.time_field.name, '<', computed_time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
if rule.protect_starred and "starred" in rule.model.field_id.mapped("name"):
domain.append(('starred', '=', False))
if rule.only_inactive and "active" in rule.model.field_id.mapped("name"):
domain.append(('active', '=', False))
_logger.info(_("GC domain: %s"), domain)
records = model.with_context({'active_test': False}).search(domain)
elif rule.state == 'size':
size = rule.size if rule.size_type == 'fixed' else rule.size_parameter_value
count = model.with_context({'active_test': False}).search([], count=True)
if count > size:
limit = count - size
_logger.info(_("GC domain: [] order: %s limit: %s"), rule.size_order, limit)
records = model.with_context({'active_test': False}).search([], order=rule.size_order, limit=limit)
elif rule.state == 'domain':
_logger.info(_("GC domain: %s"), rule.domain)
domain = safe_eval(rule.domain, self._eval_context())
records = model.with_context({'active_test': False}).search(domain)
if rule.only_attachments:
attachments = self.env['ir.attachment'].sudo().search([
('res_model', '=', rule.model.model),
('res_id', 'in', records.mapped('id'))])
count = len(attachments)
attachments.unlink()
_logger.info(_("GC'd %s attachments from %s entries"), count, rule.model.model)
else:
count = len(records)
records.unlink()
_logger.info(_("GC'd %s %s records"), count, rule.model.model)
elif rule.state == 'code':
safe_eval(rule.code.strip(), rule._get_eval_context(), mode="exec")
return res return res

94
muk_autovacuum/models/rules.py

@ -17,11 +17,19 @@
# #
################################################################################### ###################################################################################
import time
import logging import logging
import datetime
import dateutil
from pytz import timezone
from odoo import _ from odoo import _
from odoo import models, api, fields from odoo import models, api, fields
from odoo.exceptions import ValidationError
from odoo.exceptions import ValidationError, Warning
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools.safe_eval import safe_eval, test_python_expr
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -58,7 +66,8 @@ class AutoVacuumRules(models.Model):
selection=[ selection=[
('time', 'Time Based'), ('time', 'Time Based'),
('size', 'Size Based'), ('size', 'Size Based'),
('domain', 'Domain Based')],
('domain', 'Domain Based'),
('code', 'Code Based')],
string='Rule Type', string='Rule Type',
default='time', default='time',
required=True) required=True)
@ -89,7 +98,8 @@ class AutoVacuumRules(models.Model):
states={ states={
'time': [('required', True)], 'time': [('required', True)],
'size': [('invisible', True)], 'size': [('invisible', True)],
'domain': [('invisible', True)]})
'domain': [('invisible', True)],
'code': [('invisible', True)]})
time_type = fields.Selection( time_type = fields.Selection(
selection=[ selection=[
@ -104,7 +114,8 @@ class AutoVacuumRules(models.Model):
states={ states={
'time': [('required', True)], 'time': [('required', True)],
'size': [('invisible', True)], 'size': [('invisible', True)],
'domain': [('invisible', True)]})
'domain': [('invisible', True)],
'code': [('invisible', True)]})
time = fields.Integer( time = fields.Integer(
string='Time', string='Time',
@ -112,7 +123,8 @@ class AutoVacuumRules(models.Model):
states={ states={
'time': [('required', True)], 'time': [('required', True)],
'size': [('invisible', True)], 'size': [('invisible', True)],
'domain': [('invisible', True)]},
'domain': [('invisible', True)],
'code': [('invisible', True)]},
help="Delete older data than x.") help="Delete older data than x.")
size_type = fields.Selection( size_type = fields.Selection(
@ -124,7 +136,8 @@ class AutoVacuumRules(models.Model):
states={ states={
'time': [('invisible', True)], 'time': [('invisible', True)],
'size': [('required', True)], 'size': [('required', True)],
'domain': [('invisible', True)]})
'domain': [('invisible', True)],
'code': [('invisible', True)]})
size_parameter = fields.Many2one( size_parameter = fields.Many2one(
comodel_name='ir.config_parameter', comodel_name='ir.config_parameter',
@ -133,7 +146,8 @@ class AutoVacuumRules(models.Model):
states={ states={
'time': [('invisible', True)], 'time': [('invisible', True)],
'size': [('required', True)], 'size': [('required', True)],
'domain': [('invisible', True)]})
'domain': [('invisible', True)],
'code': [('invisible', True)]})
size_parameter_value = fields.Integer( size_parameter_value = fields.Integer(
compute='_compute_size_parameter_value', compute='_compute_size_parameter_value',
@ -141,7 +155,8 @@ class AutoVacuumRules(models.Model):
states={ states={
'time': [('invisible', True)], 'time': [('invisible', True)],
'size': [('readonly', True)], 'size': [('readonly', True)],
'domain': [('invisible', True)]},
'domain': [('invisible', True)],
'code': [('invisible', True)]},
help="Delete records with am index greater than x.") help="Delete records with am index greater than x.")
size_order = fields.Char( size_order = fields.Char(
@ -150,7 +165,8 @@ class AutoVacuumRules(models.Model):
states={ states={
'time': [('invisible', True)], 'time': [('invisible', True)],
'size': [('required', True)], 'size': [('required', True)],
'domain': [('invisible', True)]},
'domain': [('invisible', True)],
'code': [('invisible', True)]},
help="Order by which the index is defined.") help="Order by which the index is defined.")
size = fields.Integer( size = fields.Integer(
@ -159,24 +175,36 @@ class AutoVacuumRules(models.Model):
states={ states={
'time': [('invisible', True)], 'time': [('invisible', True)],
'size': [('required', True)], 'size': [('required', True)],
'domain': [('invisible', True)]},
'domain': [('invisible', True)],
'code': [('invisible', True)]},
help="Delete records with am index greater than x.") help="Delete records with am index greater than x.")
domain = fields.Char( domain = fields.Char(
string='Before Update Domain',
string='Domain',
states={ states={
'time': [('invisible', True)], 'time': [('invisible', True)],
'size': [('invisible', True)], 'size': [('invisible', True)],
'domain': [('required', True)]},
'domain': [('required', True)],
'code': [('invisible', True)]},
help="Delete all records which match the domain.") help="Delete all records which match the domain.")
code = fields.Text(
string='Code',
states={
'time': [('invisible', True)],
'size': [('invisible', True)],
'domain': [('invisible', True)] ,
'code': [('required', True)]},
help="Code which will be executed during the clean up.")
protect_starred = fields.Boolean( protect_starred = fields.Boolean(
string='Protect Starred', string='Protect Starred',
default=True, default=True,
states={ states={
'time': [('invisible', False)], 'time': [('invisible', False)],
'size': [('invisible', True)], 'size': [('invisible', True)],
'domain': [('invisible', True)]},
'domain': [('invisible', True)],
'code': [('invisible', True)]},
help="Do not delete starred records.") help="Do not delete starred records.")
only_inactive = fields.Boolean( only_inactive = fields.Boolean(
@ -185,14 +213,40 @@ class AutoVacuumRules(models.Model):
states={ states={
'time': [('invisible', False)], 'time': [('invisible', False)],
'size': [('invisible', True)], 'size': [('invisible', True)],
'domain': [('invisible', True)]},
'domain': [('invisible', True)],
'code': [('invisible', True)]},
help="Only delete archived records.") help="Only delete archived records.")
only_attachments = fields.Boolean( only_attachments = fields.Boolean(
string='Only Attachments', string='Only Attachments',
default=False, default=False,
states={
'time': [('invisible', False)],
'size': [('invisible', False)],
'domain': [('invisible', False)],
'code': [('invisible', True)]},
help="Only delete record attachments.") help="Only delete record attachments.")
#----------------------------------------------------------
# Functions
#----------------------------------------------------------
def _get_eval_context(self):
return {
'env': self.env,
'model': self.env[self.model_name],
'uid': self.env.user.id,
'user': self.env.user,
'time': time,
'datetime': datetime,
'dateutil': dateutil,
'timezone': timezone,
'date_format': DEFAULT_SERVER_DATE_FORMAT,
'datetime_format': DEFAULT_SERVER_DATETIME_FORMAT,
'Warning': Warning,
'logger': logging.getLogger("%s (%s)" % (__name__, self.name)),
}
#---------------------------------------------------------- #----------------------------------------------------------
# View # View
#---------------------------------------------------------- #----------------------------------------------------------
@ -225,15 +279,23 @@ class AutoVacuumRules(models.Model):
# Create, Update, Delete # Create, Update, Delete
#---------------------------------------------------------- #----------------------------------------------------------
@api.constrains('code')
def _check_code(self):
for record in self.sudo().filtered('code'):
message = test_python_expr(expr=record.code.strip(), mode="exec")
if message:
raise ValidationError(message)
@api.constrains( @api.constrains(
'state', 'model', 'domain',
'state', 'model', 'domain', 'code',
'time_field', 'time_type', 'time', 'time_field', 'time_type', 'time',
'size_type', 'size_parameter', 'size_order', 'size') 'size_type', 'size_parameter', 'size_order', 'size')
def validate(self):
def _validate(self):
validators = { validators = {
'time': lambda rec: rec.time_field and rec.time_type and rec.time, 'time': lambda rec: rec.time_field and rec.time_type and rec.time,
'size': lambda rec: rec.size_order and (rec.size_parameter or rec.size), 'size': lambda rec: rec.size_order and (rec.size_parameter or rec.size),
'domain': lambda rec: rec.domain, 'domain': lambda rec: rec.domain,
'code': lambda rec: rec.code,
} }
for record in self: for record in self:
if not validators[record.state](record): if not validators[record.state](record):

26
muk_autovacuum/views/rules.xml

@ -94,7 +94,7 @@
</group> </group>
</group> </group>
</page> </page>
<page string="Size Settings" name='time' autofocus="autofocus"
<page string="Size Settings" name='size' autofocus="autofocus"
attrs="{'invisible': [('state', '!=', 'size')]}"> attrs="{'invisible': [('state', '!=', 'size')]}">
<group> <group>
<group> <group>
@ -111,10 +111,32 @@
</group> </group>
</group> </group>
</page> </page>
<page string="Domain Settings" name='time' autofocus="autofocus"
<page string="Domain Settings" name='domain' autofocus="autofocus"
attrs="{'invisible': [('state', '!=', 'domain')]}"> attrs="{'invisible': [('state', '!=', 'domain')]}">
<field name="domain" widget="domain" options="{'model': 'model_name'}" /> <field name="domain" widget="domain" options="{'model': 'model_name'}" />
</page> </page>
<page string="Code Settings" name='code' autofocus="autofocus"
attrs="{'invisible': [('state', '!=', 'code')]}">
<field name="code" widget="ace" options="{'mode': 'python'}"
placeholder="Enter Python code here. Help about Python expression is available in the help tab of this document."/>
</page>
<page string="Help" attrs="{'invisible': [('state', '!=', 'code')]}">
<group>
<div style="margin-top: 4px;">
<h3>Help with Python expressions</h3>
<p>Various fields may use Python code or Python expressions. The following variables can be used:</p>
<ul>
<li><code>uid</code>, <code>user</code>: User on which the rule is triggered</li>
<li><code>env</code>: Odoo Environment on which the rule is triggered</li>
<li><code>model</code>: Odoo Model of the record on which the rule is triggered</li>
<li><code>time</code>, <code>datetime</code>, <code>dateutil</code>, <code>timezone</code>: useful Python libraries</li>
<li><code>date_format</code>, <code>datetime_format</code>: server date and time formats</li>
<li><code>logger.info(message)</code>: Python logging framework</li>
<li><code>Warning</code>: Warning Exception to use with <code>raise</code></li>
</ul>
</div>
</group>
</page>
</notebook> </notebook>
</sheet> </sheet>
</form> </form>

Loading…
Cancel
Save