This commit is contained in:
Cédric Krier committed 2011-06-14 12:33:43 +02:00
commit 0c39c8b95d
20 files changed
+657 -308

No files matched your search

+3
View File
@@ -37,6 +37,8 @@ def parse_commandline():
help="file where the server pid will be stored")
parser.add_option("--logfile", dest="logfile",
help="file where the server log will be stored")
parser.add_option("--disable-cron", dest="cron",
action="store_false", help="disable cron")
(opt, _) = parser.parse_args()
@@ -51,6 +53,7 @@ def parse_commandline():
'debug_mode',
'pidfile',
'logfile',
'cron',
):
if getattr(opt, arg) is not None:
options[arg] = getattr(opt, arg)
+3
View File
@@ -90,3 +90,6 @@
# Prevent database listing
#prevent_dblist = False
# Enable cron
# cron = True
+6 -2
View File
@@ -1,5 +1,6 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.const import CONTEXT_CACHE_SIZE, MODEL_CACHE_SIZE
DatabaseIntegrityError = None
DatabaseOperationalError = None
@@ -103,7 +104,8 @@ class CursorInterface(object):
IN_MAX = 1000
def __init__(self):
self.cache = {}
from trytond.cache import LRUDict
self.cache = LRUDict(CONTEXT_CACHE_SIZE)
def get_cache(self, context=None):
'''
@@ -112,6 +114,7 @@ class CursorInterface(object):
:param context: the context
:return: the cache dictionary
'''
from trytond.cache import LRUDict
if context is None:
context = {}
cache_ctx = context.copy()
@@ -119,7 +122,8 @@ class CursorInterface(object):
'_delete_records'):
if i in cache_ctx:
del cache_ctx[i]
return self.cache.setdefault(repr(cache_ctx), {})
return self.cache.setdefault(repr(cache_ctx),
LRUDict(MODEL_CACHE_SIZE))
def execute(self, sql, params=None):
'''
+1 -1
View File
@@ -121,7 +121,7 @@ CREATE TABLE wkf_transition (
act_to BIGINT,
`condition` VARCHAR(255),
trigger_expr_id VARCHAR(255),
signal VARCHAR(255),
`signal` VARCHAR(255),
`group` BIGINT,
PRIMARY KEY(id),
CONSTRAINT wkf_transition_act_from_fkey FOREIGN KEY (act_from) REFERENCES wkf_activity (id) ON DELETE CASCADE,
+30
View File
@@ -7,6 +7,7 @@ from threading import Lock
from trytond.transaction import Transaction
from trytond.config import CONFIG
from trytond.backend import Database
from trytond.tools import OrderedDict
class Cache(object):
@@ -167,3 +168,32 @@ class Cache(object):
cursor.commit()
cursor.close()
Cache._resets_lock.release()
class LRUDict(OrderedDict):
"""
Dictionary with a size limit.
If size limit is reached, it will remove the first added items.
"""
def __init__(self, size_limit, *args, **kwargs):
assert size_limit > 0
self.size_limit = size_limit
super(LRUDict, self).__init__(*args, **kwargs)
self._check_size_limit()
def __setitem__(self, key, value):
super(LRUDict, self).__setitem__(key, value)
self._check_size_limit()
def update(*args, **kwargs):
super(LRUDict, self).update(*args, **kwargs)
self._check_size_limit()
def setdefault(self, key, default=None):
default = super(LRUDict, self).setdefault(key, default=default)
self._check_size_limit()
return default
def _check_size_limit(self):
while len(self) > self.size_limit:
self.popitem(last=False)
+1
View File
@@ -52,6 +52,7 @@ class ConfigManager(object):
'prevent_dblist': False,
'init': {},
'update': {},
'cron': True,
}
self.configfile = None
+3
View File
@@ -18,4 +18,7 @@ OPERATORS = (
'>',
)
CONTEXT_CACHE_SIZE = 10
MODEL_CACHE_SIZE = 200
RECORD_CACHE_SIZE = 2000
BROWSE_FIELD_TRESHOLD = 100
+106 -112
View File
@@ -6,13 +6,14 @@ import datetime
from dateutil.relativedelta import relativedelta
import traceback
import sys
import logging
from trytond.backend import Database
from trytond.model import ModelView, ModelSQL, fields
from trytond.tools import safe_eval
from trytond.transaction import Transaction
from trytond.backend import TableHandler
_INTERVALTYPES = {
'work_days': lambda interval: relativedelta(days=interval),
'days': lambda interval: relativedelta(days=interval),
'hours': lambda interval: relativedelta(hours=interval),
'weeks': lambda interval: relativedelta(weeks=interval),
@@ -26,7 +27,8 @@ class Cron(ModelSQL, ModelView):
_description = __doc__
name = fields.Char('Name', required=True, translate=True)
user = fields.Many2One('res.user', 'Execution User', required=True,
help="The user used to execute this action")
domain=[('active', '=', False)],
help="The user used to execute this action")
request_user = fields.Many2One(
'res.user', 'Request User', required=True,
help="The user who will receive requests in case of failure")
@@ -35,24 +37,20 @@ class Cron(ModelSQL, ModelView):
interval_type = fields.Selection( [
('minutes', 'Minutes'),
('hours', 'Hours'),
('work_days', 'Work Days'),
('days', 'Days'),
('weeks', 'Weeks'),
('months', 'Months'),
], 'Interval Unit')
numbercall = fields.Integer('Number of calls', select=1,
help='Number of times the function is called,\n' \
'a negative number indicates that the function ' \
'will always be called')
doall = fields.Boolean('Repeat missed')
nextcall = fields.DateTime('Next call date', required=True,
number_calls = fields.Integer('Number of Calls', select=1,
help=('Number of times the function is called, a negative '
'number indicates that the function will always be '
'called'))
repeat_missed = fields.Boolean('Repeat Missed')
next_call = fields.DateTime('Next Call', required=True,
select=1)
model = fields.Char('Model')
function = fields.Char('Function')
args = fields.Text('Arguments')
priority = fields.Integer('Priority',
help='0=Very Urgent\n10=Not urgent')
running = fields.Boolean('Running', readonly=True, select=1)
def __init__(self):
super(Cron, self).__init__()
@@ -62,135 +60,131 @@ class Cron(ModelSQL, ModelView):
"properly: \"%s\"\n Traceback: \n\n%s\n"
})
def default_nextcall(self):
def init(self, module_name):
cursor = Transaction().cursor
# Migration from 2.0: rename numbercall, doall and nextcall
table = TableHandler(cursor, self, module_name)
table.column_rename('numbercall', 'number_calls')
table.column_rename('doall', 'repeat_missed')
table.column_rename('nextcall', 'next_call')
super(Cron, self).init(module_name)
# Migration from 2.0: work_days removed
cursor.execute('UPDATE "%s" '
'SET interval_type = %%s '
'WHERE interval_type = %%s' % self._table,
('days', 'work_days'))
def default_next_call(self):
return datetime.datetime.now()
def default_priority(self):
return 5
def default_user(self):
return int(Transaction().user)
def default_interval_number(self):
return 1
def default_interval_type(self):
return 'months'
def default_numbercall(self):
return 1
def default_number_calls(self):
return -1
def default_active(self):
return True
def default_doall(self):
def default_repeat_missed(self):
return True
def default_running(self):
return False
def check_xml_record(self, ids, values):
return True
def get_delta(self, cron):
'''
Return the relativedelta for the next call
'''
return _INTERVALTYPES[cron.interval_type](cron.interval_number)
def _get_request_values(self, cron):
tb_s = reduce(lambda x, y: x + y,
traceback.format_exception(*sys.exc_info()))
tb_s = tb_s.decode('utf-8', 'ignore')
name = self.raise_user_error('request_title',
raise_exception=False)
body = self.raise_user_error('request_body', (cron.name, tb_s),
raise_exception=False)
values = {
'name': name,
'priority': '2',
'act_from': cron.user.id,
'act_to': cron.request_user.id,
'body': body,
'date_sent': datetime.datetime.now(),
'references': [
('create', {
'reference': '%s,%s' % (self._name, cron.id),
}),
],
'state': 'waiting',
'trigger_date': datetime.datetime.now(),
}
return values
def _callback(self, cron):
try:
args = (cron['args'] or []) and safe_eval(cron['args'])
obj = self.pool.get(cron['model'])
if not obj and hasattr(obj, cron['function']):
return False
fct = getattr(obj, cron['function'])
with Transaction().set_user(cron['user']):
fct(*args)
args = (cron.args or []) and safe_eval(cron.args)
model_obj = self.pool.get(cron.model)
with Transaction().set_user(cron.user.id):
getattr(model_obj, cron.function)(*args)
except Exception, error:
Transaction().cursor.rollback()
tb_s = ''
for line in traceback.format_exception(*sys.exc_info()):
try:
line = line.encode('utf-8', 'ignore')
except Exception:
continue
tb_s += line
try:
tb_s += str(error).decode('utf-8', 'ignore')
except Exception:
pass
request_obj = self.pool.get('res.request')
try:
user_obj = self.pool.get('res.user')
req_user = user_obj.browse(cron['request_user'])
language = (req_user.language.code if req_user.language
else 'en_US')
with contextlib.nested(Transaction().set_user(cron['user']),
Transaction().set_context(language=language)):
rid = request_obj.create({
'name': self.raise_user_error('request_title',
raise_exception=False),
'priority': '2',
'act_from': cron['user'],
'act_to': cron['request_user'],
'body': self.raise_user_error('request_body',
(cron['name'], tb_s), raise_exception=False),
'date_sent': datetime.datetime.now(),
'references': [
('create', {
'reference': "ir.cron,%s" % cron['id'],
}),
],
'state': 'waiting',
'trigger_date': datetime.datetime.now(),
})
Transaction().cursor.commit()
except Exception:
Transaction().cursor.rollback()
req_user = cron.request_user
language = (req_user.language.code if req_user.language
else 'en_US')
with contextlib.nested(Transaction().set_user(cron.user.id),
Transaction().set_context(language=language)):
values = self._get_request_values(cron)
rid = request_obj.create(values)
Transaction().cursor.commit()
def pool_jobs(self, db_name):
def run(self, db_name):
now = datetime.datetime.now()
with Transaction().start(db_name, 0) as transaction:
try:
transaction.cursor.lock(self._table)
transaction.cursor.execute('SELECT * FROM ir_cron '
'WHERE numbercall <> 0 '
'AND active '
'AND nextcall <= %s '
'AND NOT running '
'ORDER BY priority', (datetime.datetime.now(),))
crons = transaction.cursor.dictfetchall()
transaction.cursor.lock(self._table)
cron_ids = self.search([
('number_calls', '!=', 0),
('next_call', '<=', datetime.datetime.now()),
])
crons = self.browse(cron_ids)
for cron in crons:
transaction.cursor.execute('UPDATE ir_cron '
'SET running = %s '
'WHERE id = %s', (True, cron['id']))
nextcall = cron['nextcall']
numbercall = cron['numbercall']
done = False
while nextcall < now and numbercall:
if numbercall > 0:
numbercall -= 1
if not done or cron['doall']:
for cron in crons:
try:
next_call = cron.next_call
number_calls = cron.number_calls
first = True
while next_call < now and number_calls != 0:
if first or cron.repeat_missed:
self._callback(cron)
if numbercall:
nextcall += _INTERVALTYPES[cron['interval_type']](
cron['interval_number'])
done = True
next_call += self.get_delta(cron)
if number_calls > 0:
number_calls -= 1
first = False
addsql = ''
addsql_param = []
if not numbercall:
addsql = ', active = %s'
addsql_param = [False]
transaction.cursor.execute("UPDATE ir_cron "
"SET nextcall = %s, "
"running = %s, "
"numbercall = %s" + addsql + " "
"WHERE id = %s",
[nextcall, False, numbercall] + addsql_param +
[cron['id']])
values = {
'next_call': next_call,
'number_calls': number_calls,
}
if not number_calls:
values['active'] = False
self.write(cron.id, values)
transaction.cursor.commit()
except Exception:
transaction.cursor.rollback()
raise
except Exception:
transaction.cursor.rollback()
tb_s = reduce(lambda x, y: x + y,
traceback.format_exception(*sys.exc_info()))
tb_s = tb_s.decode('utf-8', 'ignore')
logger = logging.getLogger('cron')
logger.error('Exception:\n%s' % tb_s)
Cron()
+17 -23
View File
@@ -12,16 +12,14 @@ this repository contains the full copyright notices and license terms. -->
<field name="arch" type="xml">
<![CDATA[
<tree string="Scheduled Actions">
<field name="nextcall"/>
<field name="next_call"/>
<field name="name" select="1"/>
<field name="user" select="1"/>
<field name="request_user" select="2"/>
<field name="priority" select="2"/>
<field name="interval_number"/>
<field name="interval_type"/>
<field name="numbercall" select="2"/>
<field name="number_calls" select="2"/>
<field name="active" select="2"/>
<field name="running" select="2"/>
</tree>
]]>
</field>
@@ -31,37 +29,33 @@ this repository contains the full copyright notices and license terms. -->
<field name="type">form</field>
<field name="arch" type="xml">
<![CDATA[
<form string="Scheduled Action" col="6">
<form string="Scheduled Action">
<label name="name"/>
<field name="name"/>
<label name="active"/>
<field name="active"/>
<label name="user"/>
<field name="user"/>
<label name="request_user"/>
<field name="request_user"/>
<label name="numbercall"/>
<field name="numbercall"/>
<label name="priority"/>
<field name="priority"/>
<label name="active"/>
<field name="active"/>
<label name="interval_number"/>
<field name="interval_number"/>
<label name="interval_type"/>
<field name="interval_type"/>
<label name="nextcall"/>
<field name="nextcall"/>
<label name="doall"/>
<field name="doall"/>
<separator string="Action to trigger" colspan="6"
<label name="number_calls"/>
<field name="number_calls"/>
<label name="next_call"/>
<field name="next_call"/>
<label name="repeat_missed"/>
<field name="repeat_missed"/>
<separator string="Action to trigger" colspan="4"
id="action_trigger"/>
<label name="model"/>
<field name="model" readonly="1"/>
<field name="model"/>
<label name="function"/>
<field name="function" readonly="1"/>
<label name="running"/>
<field name="running"/>
<separator name="args" colspan="6"/>
<field name="args" colspan="6" readonly="1"/>
<field name="function"/>
<label name="args"/>
<field name="args" colspan="3" widget="char"/>
</form>
]]>
</field>
@@ -69,7 +63,7 @@ this repository contains the full copyright notices and license terms. -->
<record model="ir.action.act_window" id="act_cron_form">
<field name="name">Scheduled Actions</field>
<field name="res_model">ir.cron</field>
<field name="context">{'active_test': False}</field>
<field name="context"></field>
</record>
<record model="ir.action.act_window.view"
id="act_cron_form_view1">
+7 -3
View File
@@ -163,6 +163,7 @@ class Rule(ModelSQL, ModelView):
rule_group_user_obj = self.pool.get('ir.rule.group-res.user')
rule_group_group_obj = self.pool.get('ir.rule.group-res.group')
user_group_obj = self.pool.get('res.user-res.group')
user_obj = self.pool.get('res.user')
cursor = Transaction().cursor
cursor.execute('SELECT r.id FROM "' + self._table + '" r ' \
@@ -191,15 +192,17 @@ class Rule(ModelSQL, ModelView):
clause_global = {}
operand2query = self._operand_get('res.user', level=1,
recur=['many2one'], root_tech='user', root='User')
user_id = Transaction().user
with Transaction().set_user(0, set_context=True):
user = user_obj.browse(user_id)
# Use root user without context to prevent recursion
user = Transaction().user
with contextlib.nested(Transaction().set_user(0),
Transaction().set_context(user=0)):
for rule in self.browse(ids):
dom = safe_eval("[('%s', '%s', %s)]" % \
(rule.field.name, rule.operator,
operand2query[rule.operand]), {
'user': self.pool.get('res.user').browse(user),
'user': user,
'time': time,
})
@@ -226,7 +229,8 @@ class Rule(ModelSQL, ModelView):
'FROM "' + rule_group_group_obj._table + '" g_rel ' \
'JOIN "' + user_group_obj._table + '" u_rel ' \
'ON g_rel.group_id = u_rel.gid ' \
'WHERE u_rel.uid = %s))', (model_name, user, user))
'WHERE u_rel.uid = %s))',
(model_name, user_id, user_id))
fetchone = cursor.fetchone()
if fetchone:
group_id = fetchone[0]
+1 -1
View File
@@ -49,7 +49,7 @@ class Sequence(ModelSQL, ModelView):
states={
'invisible': Not(In(Eval('type'), ['incremental'])),
}, depends=['type'])
timestamp_rounding = fields.Integer('Timestamp Rounding', required=True,
timestamp_rounding = fields.Float('Timestamp Rounding', required=True,
states={
'invisible': Not(In(Eval('type'),
['decimal timestamp', 'hexadecimal timestamp'])),
+1
View File
@@ -144,5 +144,6 @@ attlist.xpath &=
attribute position { "inside" | "replace" | "replace_attributes" | "after" | "before" }?
action = element action { attlist.action, empty }
attlist.action &= attribute name { text }
attlist.action &= [ a:defaultValue = "1" ] attribute colspan { text }?
label |= notAllowed
start = data | board | label
+5
View File
@@ -447,6 +447,11 @@
<define name="attlist.action" combine="interleave">
<attribute name="name"/>
</define>
<define name="attlist.action" combine="interleave">
<optional>
<attribute name="colspan" a:defaultValue="1"/>
</optional>
</define>
<define name="label" combine="choice">
<notAllowed/>
</define>
+161 -145
View File
@@ -2,10 +2,13 @@
#this repository contains the full copyright notices and license terms.
from __future__ import with_statement
import contextlib
from itertools import islice, ifilter, ifilterfalse
from itertools import islice, ifilter, ifilterfalse, chain
from trytond.model import fields
from trytond.transaction import Transaction
from trytond.const import BROWSE_FIELD_TRESHOLD
from trytond.cache import LRUDict
from trytond.const import MODEL_CACHE_SIZE, RECORD_CACHE_SIZE, \
BROWSE_FIELD_TRESHOLD
class BrowseRecordList(list):
'''
@@ -46,164 +49,176 @@ class BrowseRecord(object):
An object that represents record defined by a ORM object.
'''
def __init__(self, record_id, model, local_cache=None):
def __init__(self, record_id, model, ids, local_cache):
assert isinstance(ids, list)
if ids:
assert record_id in ids
self._cursor = Transaction().cursor
self._user = Transaction().user
self._id = record_id
self.id = record_id
self._model = model
self._model_name = self._model._name
self._context = Transaction().context
self._ids = ids
cache = self._cursor.get_cache(self._context)
cache.setdefault(model._name, {})
cache.setdefault('_language_cache', {})
if model._name not in cache:
cache[model._name] = LRUDict(RECORD_CACHE_SIZE)
self._data = cache[model._name]
if not record_id in self._data:
self._data[record_id] = {'id': record_id}
self._cache = cache
if local_cache is None:
local_cache = {}
self._local_cache = local_cache
self._local_cache.setdefault(model._name, {})
self._local_cache.setdefault('_language_cache', {})
self._local_data = self._local_cache[model._name]
if not record_id in self._local_data:
self._local_data[record_id] = {}
assert isinstance(local_cache, LRUDict)
self._local_data = local_cache
def __getitem__(self, name):
if name == 'id':
return self._id
if name == 'setLang':
return self.setLang
if not self._data[self._id].has_key(name) \
and not self._local_data[self._id].has_key(name):
# build the list of fields we will fetch
# Access to LRUDict must be atomic
result = self._local_data.get(self.id, {}).get(name)
if (self.id in self._local_data
and name in self._local_data[self.id]):
return result
result = self._data.get(self.id, {}).get(name)
if self.id in self._data and name in self._data[self.id]:
return result
# fetch the definition of the field which was asked for
if name in self._model._columns:
col = self._model._columns[name]
elif name in self._model._inherit_fields:
col = self._model._inherit_fields[name][2]
elif hasattr(self._model, name):
return getattr(self._model, name)
else:
raise Exception('Error', 'Programming error: field "%s" ' \
'does not exist in model "%s"!' \
% (name, self._model._name))
# build the list of fields we will fetch
# fetch the definition of the field which was asked for
if name in self._model._columns:
col = self._model._columns[name]
elif name in self._model._inherit_fields:
col = self._model._inherit_fields[name][2]
elif hasattr(self._model, name):
return getattr(self._model, name)
else:
raise Exception('Error', 'Programming error: field "%s" ' \
'does not exist in model "%s"!' \
% (name, self._model._name))
ffields = {}
if col.loading == 'eager':
field_access_obj = self._model.pool.get('ir.model.field.access')
fread_accesses = {}
for inherit_name in self._model._inherits:
inherit_obj = self._model.pool.get(inherit_name)
fread_accesses.update(field_access_obj.check(inherit_name,
inherit_obj._columns.keys(), 'read', access=True))
fread_accesses.update(field_access_obj.check(self._model._name,
self._model._columns.keys(), 'read', access=True))
to_remove = set(x for x, y in fread_accesses.iteritems()
if not y and x != name)
ffields = {}
if col.loading == 'eager':
field_access_obj = self._model.pool.get('ir.model.field.access')
fread_accesses = {}
for inherit_name in self._model._inherits:
inherit_obj = self._model.pool.get(inherit_name)
fread_accesses.update(field_access_obj.check(inherit_name,
inherit_obj._columns.keys(), 'read', access=True))
fread_accesses.update(field_access_obj.check(self._model._name,
self._model._columns.keys(), 'read', access=True))
to_remove = set(x for x, y in fread_accesses.iteritems()
if not y and x != name)
threshold = BROWSE_FIELD_TRESHOLD
inherit_threshold = threshold - len(self._model._columns)
threshold = BROWSE_FIELD_TRESHOLD
inherit_threshold = threshold - len(self._model._columns)
def not_cached(item):
fname, field = item
return (fname not in self._data[self._id]
and fname not in self._local_data[self._id])
def to_load(item):
fname, field = item
return (field.loading == 'eager'
and fname not in to_remove)
def overrided(item):
fname, field = item
return fname in self._model._columns
def not_cached(item):
fname, field = item
return (fname not in self._data.get(self.id, {})
and fname not in self._local_data.get(self.id, {}))
def to_load(item):
fname, field = item
return (field.loading == 'eager'
and fname not in to_remove)
def overrided(item):
fname, field = item
return fname in self._model._columns
if inherit_threshold > 0:
ifields = ((fname, field)
for fname, (_, _, field) in
self._model._inherit_fields.iteritems())
ifields = ifilterfalse(overrided,
ifilter(to_load,
ifilter(not_cached, ifields)))
ifields = islice(ifields, 0, inherit_threshold)
ffields.update(ifields)
threshold -= inherit_threshold
ifields = ifilter(to_load,
ifilter(not_cached,
self._model._columns.iteritems()))
ifields = islice(ifields, 0, threshold)
if inherit_threshold > 0:
ifields = ((fname, field)
for fname, (_, _, field) in
self._model._inherit_fields.iteritems())
ifields = ifilterfalse(overrided,
ifilter(to_load,
ifilter(not_cached, ifields)))
ifields = islice(ifields, 0, inherit_threshold)
ffields.update(ifields)
threshold -= inherit_threshold
ffields[name] = col
ifields = ifilter(to_load,
ifilter(not_cached,
self._model._columns.iteritems()))
ifields = islice(ifields, 0, threshold)
ffields.update(ifields)
# add datetime_field
for field in ffields:
if hasattr(field, 'datetime_field') and field.datetime_field:
if field.datetime_field in self._model._columns:
date_field = self._model._columns[field.datetime_field]
else:
date_field = self._model._inherit_fields[
field.datetime_field][2]
ffields[field.datetime_field] = datetime_field
ffields[name] = col
if len(self._data) <= self._cursor.IN_MAX:
iterids = self._data.iterkeys()
else:
iterids = self._local_data.iterkeys()
ids = [x for x in iterids \
if not self._data.setdefault(x, {}).has_key(name) \
and not self._local_data.setdefault(x, {}).has_key(name)]
# read the data
with contextlib.nested(Transaction().set_cursor(self._cursor),
Transaction().set_user(self._user),
Transaction().set_context(self._context)):
datas = self._model.read(ids, ffields.keys())
# add datetime_field
for field in ffields:
if hasattr(field, 'datetime_field') and field.datetime_field:
if field.datetime_field in self._model._columns:
date_field = self._model._columns[field.datetime_field]
else:
date_field = self._model._inherit_fields[
field.datetime_field][2]
ffields[field.datetime_field] = datetime_field
# create browse records for 'remote' models
for data in datas:
for i, j in ffields.iteritems():
model = None
if (hasattr(j, 'model_name') and
j.model_name in
self._model.pool.object_name_list()):
model = self._model.pool.get(j.model_name)
elif hasattr(j, 'get_target'):
model = j.get_target(self._model.pool)
if model and j._type in ('many2one', 'one2one'):
if (not data[i]
and not (isinstance(data[i], (int, long))
and not isinstance(data[i],
type(False)))):
data[i] = BrowseRecordNull()
else:
_datetime = None
if (hasattr(j, 'datetime_field')
and j.datetime_field):
_datetime = data[j.datetime_field]
with Transaction().set_context(
_datetime=_datetime):
data[i] = BrowseRecord(data[i], model,
local_cache=self._local_cache)
elif (model
and j._type in ('one2many', 'many2many')
and len(data[i])):
def filter_(id_):
return True
if (id_ in self._local_data
and name in self._local_data[id_]):
return False
if id_ in self._data and name in self._data[id_]:
return False
return True
index = self._ids.index(self.id)
ids = list(islice(ifilter(filter_, chain(self._ids, self._ids)),
index, self._cursor.IN_MAX + index))
model2ids = {}
model2cache = {}
# read the data
with contextlib.nested(Transaction().set_cursor(self._cursor),
Transaction().set_user(self._user),
Transaction().set_context(self._context)):
# create browse records for 'remote' models
for data in self._model.read(ids, ffields.keys()):
for i, j in ffields.iteritems():
model = None
if (hasattr(j, 'model_name') and
j.model_name in
self._model.pool.object_name_list()):
model = self._model.pool.get(j.model_name)
elif hasattr(j, 'get_target'):
model = j.get_target(self._model.pool)
if model and j._type in ('many2one', 'one2one'):
if (not data[i]
and not (isinstance(data[i], (int, long))
and not isinstance(data[i],
type(False)))):
data[i] = BrowseRecordNull()
else:
_datetime = None
if hasattr(j, 'datetime_field') and j.datetime_field:
if (hasattr(j, 'datetime_field')
and j.datetime_field):
_datetime = data[j.datetime_field]
with Transaction().set_context(
_datetime=_datetime):
data[i] = BrowseRecordList(BrowseRecord(
x, model, local_cache=self._local_cache)
for x in data[i])
if isinstance(j, fields.Function):
self._local_data.setdefault(data['id'], {})[i] = data[i]
del data[i]
self._data[data['id']].update(data)
if name in self._local_data[self._id]:
return self._local_data[self._id][name]
return self._data[self._id][name]
ids = model2ids.setdefault(model, [])
ids.append(data[i])
local_cache = model2cache.setdefault(model,
LRUDict(RECORD_CACHE_SIZE))
data[i] = BrowseRecord(data[i], model,
ids, local_cache)
elif (model
and j._type in ('one2many', 'many2many')
and len(data[i])):
_datetime = None
if hasattr(j, 'datetime_field') and j.datetime_field:
_datetime = data[j.datetime_field]
with Transaction().set_context(
_datetime=_datetime):
ids = model2ids.setdefault(model, [])
ids.extend(data[i])
local_cache = model2cache.setdefault(model,
LRUDict(RECORD_CACHE_SIZE))
data[i] = BrowseRecordList(
BrowseRecord(x, model, ids, local_cache)
for x in data[i])
if isinstance(j, fields.Function):
if data['id'] == self.id and i == name:
result = data[i]
self._local_data.setdefault(data['id'], {})[i] = data[i]
del data[i]
self._data.setdefault(data['id'], {}).update(data)
if data['id'] == self.id and name in data:
result = data[name]
return result
def __getattr__(self, name):
# TODO raise an AttributeError exception
@@ -218,16 +233,16 @@ class BrowseRecord(object):
return name in self
def __int__(self):
return self._id
return self.id
def __str__(self):
return "BrowseRecord(%s, %d)" % (self._model_name, self._id)
return "BrowseRecord(%s, %d)" % (self._model_name, self.id)
def __eq__(self, other):
return (self._model_name, self._id) == (other._model_name, other._id)
return (self._model_name, self.id) == (other._model_name, other.id)
def __ne__(self, other):
return (self._model_name, self._id) != (other._model_name, other._id)
return (self._model_name, self.id) != (other._model_name, other.id)
# we need to define __unicode__ even though we've already defined __str__
# because we have overridden __getattr__
@@ -235,7 +250,7 @@ class BrowseRecord(object):
return unicode(str(self))
def __hash__(self):
return hash((self._model_name, self._id))
return hash((self._model_name, self.id))
def __nonzero__(self):
return True
@@ -246,22 +261,23 @@ class BrowseRecord(object):
self._context = self._context.copy()
prev_lang = self._context.get('language') or 'en_US'
self._context['language'] = lang
for cache in (self._cache, self._local_cache):
language_cache = cache['_language_cache']
for cache in (self._cache, self._local_data):
language_cache = cache.setdefault('_language_cache', {})
for model in cache:
if model == '_language_cache':
continue
for record_id in cache[model]:
language_cache.setdefault(prev_lang,
{}).setdefault(model, {})[record_id] = \
cache[model][record_id]
LRUDict(MODEL_CACHE_SIZE)).setdefault(model,
LRUDict(RECORD_CACHE_SIZE))[record_id] = \
cache[model][record_id]
if lang in language_cache \
and model in language_cache[lang] \
and record_id in language_cache[lang][model]:
cache[model][record_id] = \
language_cache[lang][model][record_id]
else:
cache[model][record_id] = {'id': record_id}
cache[model][record_id] = {}
def get_eval(self, name):
res = self[name]
+6 -5
View File
@@ -5,13 +5,15 @@ import contextlib
import datetime
import re
from decimal import Decimal
from itertools import islice
from trytond.model import ModelStorage
from trytond.model import fields
from trytond.backend import FIELDS, TableHandler
from trytond.backend import DatabaseIntegrityError, Database
from trytond.tools import reduce_ids
from trytond.const import OPERATORS
from trytond.const import OPERATORS, RECORD_CACHE_SIZE
from trytond.transaction import Transaction
from trytond.cache import LRUDict
_RE_UNIQUE = re.compile('UNIQUE\s*\((.*)\)', re.I)
_RE_CHECK = re.compile('CHECK\s*\((.*)\)', re.I)
@@ -1196,11 +1198,11 @@ class ModelSQL(ModelStorage):
datas = cursor.dictfetchmany(cursor.IN_MAX)
cache = cursor.get_cache()
cache.setdefault(self._name, {})
cache.setdefault(self._name, LRUDict(RECORD_CACHE_SIZE))
delete_records = Transaction().delete_records.setdefault(self._name,
set())
keys = None
for data in datas:
for data in islice(datas, 0, cache.size_limit):
if data['id'] in delete_records:
continue
if not keys:
@@ -1215,8 +1217,7 @@ class ModelSQL(ModelStorage):
continue
for k in keys:
del data[k]
cache[self._name].setdefault(data['id'], {})
cache[self._name][data['id']].update(data)
cache[self._name].setdefault(data['id'], {}).update(data)
if len(datas) >= cursor.IN_MAX:
select_fields2 = [select_fields[0]]
+6 -5
View File
@@ -19,8 +19,9 @@ from trytond.model.browse import BrowseRecordList, BrowseRecord, \
from trytond.model.browse import EvalEnvironment
from trytond.tools import safe_eval, reduce_domain
from trytond.pyson import PYSONEncoder, PYSONDecoder, PYSON
from trytond.const import OPERATORS
from trytond.const import OPERATORS, RECORD_CACHE_SIZE
from trytond.transaction import Transaction
from trytond.cache import LRUDict
class ModelStorage(Model):
@@ -189,7 +190,7 @@ class ModelStorage(Model):
trigger_ids = eligibles.keys()
if not trigger_ids:
return
records = self.browse(chain(*eligibles.values()))
records = self.browse(list(chain(*eligibles.values())))
id2record = dict((x.id, x) for x in records)
triggers = trigger_obj.browse(trigger_ids)
for trigger in triggers:
@@ -511,10 +512,10 @@ class ModelStorage(Model):
:param ids: a list of ids or an id
:return: a BrowseRecordList or a BrowseRecord
'''
local_cache = LRUDict(RECORD_CACHE_SIZE)
if isinstance(ids, (int, long)):
return BrowseRecord(ids, self)
local_cache = {}
return BrowseRecordList((BrowseRecord(x, self, local_cache=local_cache)
return BrowseRecord(ids, self, [ids], local_cache)
return BrowseRecordList((BrowseRecord(x, self, ids, local_cache)
for x in ids))
def __export_row(self, record, fields_names):
+3 -2
View File
@@ -663,6 +663,7 @@ this repository contains the full copyright notices and license terms. -->
<field name="login">user_cron_trigger</field>
<field name="name">Cron Trigger</field>
<field name="groups" eval="[('add', ref('group_admin'))]"/>
<field name="active" eval="False"/>
</record>
<record model="ir.cron" id="cron_trigger_time">
@@ -672,8 +673,8 @@ this repository contains the full copyright notices and license terms. -->
<field name="active" eval="True"/>
<field name="interval_number">5</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="number_calls">-1</field>
<field name="repeat_missed" eval="False"/>
<field name="model">ir.trigger</field>
<field name="function">trigger_time</field>
</record>
+15 -9
View File
@@ -235,19 +235,25 @@ class TrytonServer(object):
import psyco
psyco.full()
threads = {}
while True:
if CONFIG['auto_reload'] and monitor():
try:
Pool.start()
except Exception:
pass
for dbname in Pool.database_list():
pool = Pool(dbname)
if 'ir.cron' not in pool.object_name_list():
continue
cron_obj = pool.get('ir.cron')
thread = threading.Thread(
target=cron_obj.pool_jobs,
args=(dbname,), kwargs={})
thread.start()
if CONFIG['cron']:
for dbname in Pool.database_list():
thread = threads.get(dbname)
if thread and thread.is_alive():
continue
pool = Pool(dbname)
if 'ir.cron' not in pool.object_name_list():
continue
cron_obj = pool.get('ir.cron')
thread = threading.Thread(
target=cron_obj.run,
args=(dbname,), kwargs={})
thread.start()
threads[dbname] = thread
time.sleep(60)
+4
View File
@@ -2,3 +2,7 @@
#this repository contains the full copyright notices and license terms.
from misc import *
from datetime_strftime import *
try:
from collections import OrderedDict
except ImportError:
from ordereddict import OrderedDict
+278
View File
@@ -0,0 +1,278 @@
# Copyright (C) 2009 by Raymond Hettinger
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
# Passes Python2.7's test suite and incorporates all the latest updates.
try:
from thread import get_ident as _get_ident
except ImportError:
from dummy_thread import get_ident as _get_ident
try:
from _abcoll import KeysView, ValuesView, ItemsView
except ImportError:
pass
class OrderedDict(dict):
'Dictionary that remembers insertion order'
# An inherited dict maps keys to values.
# The inherited dict provides __getitem__, __len__, __contains__, and get.
# The remaining methods are order-aware.
# Big-O running times for all methods are the same as for regular dictionaries.
# The internal self.__map dictionary maps keys to links in a doubly linked list.
# The circular doubly linked list starts and ends with a sentinel element.
# The sentinel element never gets deleted (this simplifies the algorithm).
# Each link is stored as a list of length three: [PREV, NEXT, KEY].
def __init__(self, *args, **kwds):
'''Initialize an ordered dictionary. Signature is the same as for
regular dictionaries, but keyword arguments are not recommended
because their insertion order is arbitrary.
'''
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
try:
self.__root
except AttributeError:
self.__root = root = [] # sentinel node
root[:] = [root, root, None]
self.__map = {}
self.__update(*args, **kwds)
def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
'od.__setitem__(i, y) <==> od[i]=y'
# Setting a new item creates a new link which goes at the end of the linked
# list, and the inherited dictionary is updated with the new key/value pair.
if key not in self:
root = self.__root
last = root[0]
last[1] = root[0] = self.__map[key] = [last, root, key]
dict_setitem(self, key, value)
def __delitem__(self, key, dict_delitem=dict.__delitem__):
'od.__delitem__(y) <==> del od[y]'
# Deleting an existing item uses self.__map to find the link which is
# then removed by updating the links in the predecessor and successor nodes.
dict_delitem(self, key)
link_prev, link_next, key = self.__map.pop(key)
link_prev[1] = link_next
link_next[0] = link_prev
def __iter__(self):
'od.__iter__() <==> iter(od)'
root = self.__root
curr = root[1]
while curr is not root:
yield curr[2]
curr = curr[1]
def __reversed__(self):
'od.__reversed__() <==> reversed(od)'
root = self.__root
curr = root[0]
while curr is not root:
yield curr[2]
curr = curr[0]
def clear(self):
'od.clear() -> None. Remove all items from od.'
try:
for node in self.__map.itervalues():
del node[:]
root = self.__root
root[:] = [root, root, None]
self.__map.clear()
except AttributeError:
pass
dict.clear(self)
def popitem(self, last=True):
'''od.popitem() -> (k, v), return and remove a (key, value) pair.
Pairs are returned in LIFO order if last is true or FIFO order if false.
'''
if not self:
raise KeyError('dictionary is empty')
root = self.__root
if last:
link = root[0]
link_prev = link[0]
link_prev[1] = root
root[0] = link_prev
else:
link = root[1]
link_next = link[1]
root[1] = link_next
link_next[0] = root
key = link[2]
del self.__map[key]
value = dict.pop(self, key)
return key, value
# -- the following methods do not depend on the internal structure --
def keys(self):
'od.keys() -> list of keys in od'
return list(self)
def values(self):
'od.values() -> list of values in od'
return [self[key] for key in self]
def items(self):
'od.items() -> list of (key, value) pairs in od'
return [(key, self[key]) for key in self]
def iterkeys(self):
'od.iterkeys() -> an iterator over the keys in od'
return iter(self)
def itervalues(self):
'od.itervalues -> an iterator over the values in od'
for k in self:
yield self[k]
def iteritems(self):
'od.iteritems -> an iterator over the (key, value) items in od'
for k in self:
yield (k, self[k])
def update(*args, **kwds):
'''od.update(E, **F) -> None. Update od from dict/iterable E and F.
If E is a dict instance, does: for k in E: od[k] = E[k]
If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
Or if E is an iterable of items, does: for k, v in E: od[k] = v
In either case, this is followed by: for k, v in F.items(): od[k] = v
'''
if len(args) > 2:
raise TypeError('update() takes at most 2 positional '
'arguments (%d given)' % (len(args),))
elif not args:
raise TypeError('update() takes at least 1 argument (0 given)')
self = args[0]
# Make progressively weaker assumptions about "other"
other = ()
if len(args) == 2:
other = args[1]
if isinstance(other, dict):
for key in other:
self[key] = other[key]
elif hasattr(other, 'keys'):
for key in other.keys():
self[key] = other[key]
else:
for key, value in other:
self[key] = value
for key, value in kwds.items():
self[key] = value
__update = update # let subclasses override update without breaking __init__
__marker = object()
def pop(self, key, default=__marker):
'''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
If key is not found, d is returned if given, otherwise KeyError is raised.
'''
if key in self:
result = self[key]
del self[key]
return result
if default is self.__marker:
raise KeyError(key)
return default
def setdefault(self, key, default=None):
'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
if key in self:
return self[key]
self[key] = default
return default
def __repr__(self, _repr_running={}):
'od.__repr__() <==> repr(od)'
call_key = id(self), _get_ident()
if call_key in _repr_running:
return '...'
_repr_running[call_key] = 1
try:
if not self:
return '%s()' % (self.__class__.__name__,)
return '%s(%r)' % (self.__class__.__name__, self.items())
finally:
del _repr_running[call_key]
def __reduce__(self):
'Return state information for pickling'
items = [[k, self[k]] for k in self]
inst_dict = vars(self).copy()
for k in vars(OrderedDict()):
inst_dict.pop(k, None)
if inst_dict:
return (self.__class__, (items,), inst_dict)
return self.__class__, (items,)
def copy(self):
'od.copy() -> a shallow copy of od'
return self.__class__(self)
@classmethod
def fromkeys(cls, iterable, value=None):
'''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
and values equal to v (which defaults to None).
'''
d = cls()
for key in iterable:
d[key] = value
return d
def __eq__(self, other):
'''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
while comparison to a regular mapping is order-insensitive.
'''
if isinstance(other, OrderedDict):
return len(self)==len(other) and self.items() == other.items()
return dict.__eq__(self, other)
def __ne__(self, other):
return not self == other
# -- the following methods are only used in Python 2.7 --
def viewkeys(self):
"od.viewkeys() -> a set-like object providing a view on od's keys"
return KeysView(self)
def viewvalues(self):
"od.viewvalues() -> an object providing a view on od's values"
return ValuesView(self)
def viewitems(self):
"od.viewitems() -> a set-like object providing a view on od's items"
return ItemsView(self)