trytond-subscription/subscription.py

370 lines
14 KiB
Python

# This file is part of subscription module of Tryton.
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from datetime import datetime
from simpleeval import simple_eval
from trytond.config import config
from trytond.model import ModelView, ModelSQL, fields, Unique
from trytond.pool import Pool
from trytond.pyson import Eval
from trytond.transaction import Transaction
import contextlib
import logging
__all__ = ['SubscriptionSubscription', 'SubscriptionLine', 'SubscriptionHistory']
STATES = {
'readonly': Eval('state') == 'running',
}
DEPENDS = ['state']
logger = logging.getLogger(__name__)
class SubscriptionSubscription(ModelSQL, ModelView):
'Subscription'
__name__ = 'subscription.subscription'
@classmethod
def get_model(cls):
cr = Transaction().cursor
cr.execute('''\
SELECT
m.model,
m.name
FROM
ir_model m
ORDER BY
m.name
''')
return cr.fetchall()
name = fields.Char('Name', select=True, required=True, states=STATES)
user = fields.Many2One('res.user', 'User', required=True,
domain=[('active', '=', False)], states=STATES)
request_user = fields.Many2One(
'res.user', 'Request User', required=True, states=STATES,
help='The user who will receive requests in case of failure.')
request_group = fields.Many2One('res.group', 'Request Group',
required=True, states=STATES,
help='The group who will receive requests.')
active = fields.Boolean('Active', select=True, states=STATES,
help='If the active field is set to False, it will allow you to ' \
'hide the subscription without removing it.')
interval_number = fields.Integer('Interval Qty', states=STATES)
interval_type = fields.Selection([
('days', 'Days'),
('weeks', 'Weeks'),
('months', 'Months'),
], 'Interval Unit', states=STATES)
number_calls = fields.Integer('Number of documents', states=STATES)
next_call = fields.DateTime('First Date', states=STATES)
state = fields.Selection([
('draft','Draft'),
('running','Running'),
('done','Done')], 'State', readonly=True, states=STATES)
model_source = fields.Reference('Source Document',
selection='get_model', depends=['state'],
help='User can choose the source model on which he wants to ' \
'create models.', states=STATES)
lines = fields.One2Many('subscription.line', 'subscription', 'Lines',
states=STATES)
history = fields.One2Many('subscription.history',
'subscription', 'History', states=STATES)
cron = fields.Many2One('ir.cron', 'Cron Job', states=STATES,
help='Scheduler which runs on subscription.', ondelete='CASCADE')
note = fields.Text('Notes', help='Description or Summary of Subscription.')
@classmethod
def __setup__(cls):
super(SubscriptionSubscription, cls).__setup__()
t = cls.__table__()
cls._buttons.update({
'set_process': {
'invisible': Eval('state') != 'draft',
},
'set_done': {
'invisible': Eval('state') != 'running',
},
'set_draft': {
'invisible': Eval('state') != 'done',
},
})
cls._error_messages.update({
'error': 'Error. Wrong Source Document',
'provide_another_source': 'Please provide another source ' \
'model.\nThis one does not exist!',
'error_creating': 'Error creating document \'%s\'',
'created_successfully': 'Document \'%s\' created successfully',
})
cls._sql_constraints = [
('name_unique', Unique(t, t.name),
'The name of the subscription must be unique!')
]
@staticmethod
def default_next_call():
return datetime.now()
@staticmethod
def default_user():
User = Pool().get('res.user')
user_ids = User.search([
('active', '=', False),
('login', '=', 'user_cron_trigger'),
])
return user_ids[0].id
@staticmethod
def default_request_user():
return Transaction().user
@staticmethod
def default_active():
return True
@staticmethod
def default_interval_number():
return 1
@staticmethod
def default_number_calls():
return 1
@staticmethod
def default_interval_type():
return 'months'
@staticmethod
def default_model_source():
return False
@staticmethod
def default_state():
return 'draft'
@classmethod
@ModelView.button
def set_process(self, subscriptions):
RequestLink = Pool().get('res.request.link')
to_create = []
for subscription in subscriptions:
vals = {
'model': subscription.__name__,
'name': subscription.name,
'user': subscription.user.id,
'request_user': subscription.request_user.id,
'interval_number': subscription.interval_number,
'interval_type': subscription.interval_type,
'number_calls': subscription.number_calls or 1,
'next_call': subscription.next_call,
'args': str([subscription.id]),
'function': 'model_copy',
}
Cron = Pool().get('ir.cron')
domain = [
('model', '=', subscription.__name__),
('name', '=', subscription.name),
('active', '=', False),
]
cron = Cron.search([domain])
if not cron:
cron = Cron.create([vals])[0]
else:
vals['active'] = True
Cron.write(cron, vals)
cron = cron[0]
vals = {
'cron': cron.id,
'state': 'running',
}
self.write([subscription], vals)
# Add a res.request.link record to allow linking the documents copied
# in requests created
request_link = RequestLink.search([
('model', '=', subscription.model_source.__name__),
])
if not request_link:
Model = Pool().get('ir.model')
model = Model.search([
('model', '=', subscription.model_source.__name__),
])
to_create.append({
'model': subscription.model_source.__name__,
'priority': 5,
'name': model and model[0] and model[0].name or False,
})
if to_create:
RequestLink.create(to_create)
@classmethod
def model_copy(cls, subscription_id):
logger.info('Running subscription ID %s' % (subscription_id))
Cron = Pool().get('ir.cron')
History = Pool().get('subscription.history')
subscription = cls(subscription_id)
remaining = Cron.browse([subscription.cron.id])[0].number_calls
model_id = subscription.model_source and subscription.model_source.id \
or False
if model_id:
context = Transaction().context.copy()
Model = Pool().get(subscription.model_source.__name__)
Request = Pool().get('res.request')
default = {'state':'draft'}
context = {
'self': subscription,
'pool': Pool(),
'transaction': Transaction(),
}
req_vals = {
'act_from': subscription.user.id,
'date_sent': datetime.now(),
'state': 'waiting',
'trigger_date': datetime.now(),
}
# Map subscription lines and create a copy of the document
# and save logs in subscription.history model
for line in subscription.lines:
default[line.field.name] = simple_eval(line.value, context)
try:
model = Model.copy([subscription.model_source], default)
except:
history_vals = {
'log': cls.raise_user_error(
error='error_creating',
error_args=subscription.model_source.__name__,
raise_exception=False),
'subscription': subscription,
}
req_vals['name'] = cls.raise_user_error(
error='error_creating',
error_args=subscription.name,
raise_exception=False)
req_vals['body'] = cls.raise_user_error(
error='error_creating',
error_args=subscription.model_source.__name__,
raise_exception=False)
req_vals['priority'] = '2'
else:
history_vals = {
'log': cls.raise_user_error(
error='created_successfully',
error_args=subscription.model_source.__name__,
raise_exception=False),
'subscription': subscription.id,
}
req_vals['name'] = cls.raise_user_error(
error='created_successfully',
error_args=subscription.name,
raise_exception=False)
req_vals['body'] = cls.raise_user_error(
error='created_successfully',
error_args=model[0].id,
raise_exception=False)
req_vals['priority'] = '0'
req_vals['references'] = [
('create', {
'reference': '%s,%s' % \
(subscription.model_source.__name__,
model[0].id)
}
)]
history_vals['document'] = (subscription.model_source.__name__,
model[0].id)
History.create([history_vals])
# Send requests to users in request_group
to_create = []
for user in subscription.request_group.users:
if user != subscription.request_user and user.active:
language = (user.language.code if user.language
else config.get('database', 'language'))
with contextlib.nested(Transaction().set_user(user.id),
Transaction().set_context(language=language)):
req_vals['act_to'] = user.id
to_create.append(req_vals)
Cron._get_request_values(subscription.cron)
if to_create:
Request.create(to_create)
# If it is the last cron execution, set the state of the
# subscriptio to done
if remaining == 1:
subscription.write([subscription], {'state': 'done'})
logger.info('End subscription ID %s.' % subscription_id)
else:
logger.error('Document in subscription %s not found.\n' % \
subscription.name)
@classmethod
@ModelView.button
def set_done(self, subscriptions):
for subscription in subscriptions:
Pool().get('ir.cron').write([subscription.cron], {'active': False})
self.write([subscription], {'state':'draft'})
@classmethod
@ModelView.button
def set_draft(self, subscriptions):
self.write(subscriptions, {'state':'draft'})
class SubscriptionLine(ModelSQL, ModelView):
'Subscription Line'
__name__ = 'subscription.line'
_rec_name = 'field'
subscription = fields.Many2One('subscription.subscription',
'Subscription', ondelete='CASCADE', select=True)
field = fields.Many2One('ir.model.field', 'Field',
# TODO domain to filter available fields of the model model_source of related
# subscription
# domain=[(
# 'model', '=', Eval('_parent_subscription', {}).get('model_source')
# )],
select=True, required=True)
value = fields.Char('Default Value', required=True,
help='Default value is considered for field when new subscription ' \
'is generated. You must put here a Python expression. The ' \
'available variables are:\n' \
' - self: The current subcription object.\n' \
' - pool: The store of the instances of models.\n' \
' - transaction: That contains thread-local parameters of the ' \
'database transaction.\n' \
'As an example to get the current date:\n\n' \
'pool.get(\'ir.date\').today()')
class SubscriptionHistory(ModelSQL, ModelView):
"Subscription History"
__name__ = "subscription.history"
_rec_name = 'date'
@classmethod
def get_model(cls):
cr = Transaction().cursor
cr.execute('''\
SELECT
m.model,
m.name
FROM
ir_model m
ORDER BY
m.name
''')
return cr.fetchall()
date = fields.DateTime('Date', readonly=True)
log = fields.Char('Result', readonly=True)
subscription = fields.Many2One('subscription.subscription',
'Subscription', ondelete='CASCADE', readonly=True)
document = fields.Reference('Source Document', selection='get_model',
readonly=True)
@staticmethod
def default_date():
return datetime.now()