Source

trytond-subscription / subscription.py

Full commit
# 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 ...config import CONFIG
from datetime import datetime
from trytond.model import ModelView, ModelSQL, fields
from trytond.pool import Pool
from trytond.pyson import Eval
from trytond.tools import safe_eval
from trytond.transaction import Transaction
import contextlib
import logging

__all__ = [
    'SubscriptionSubscription',
    'SubscriptionLine',
    'SubscriptionHistory',
]

STATES = {
    'readonly': Eval('state') == 'running',
}
DEPENDS = ['state']

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__()
        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(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):
        Cron = Pool().get('ir.cron')
        History = Pool().get('subscription.history')
        subscription = cls(subscription_id)
        logger = logging.getLogger('subscription_subscription')
        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] = safe_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].reference or 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['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'})
        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()